Browse Source

update SDL3 to 3.2.10.

Sasha Szpakowski 8 months ago
parent
commit
375c6f88cd
100 changed files with 1911 additions and 812 deletions
  1. 1 1
      CMakeLists.txt
  2. 2 1
      libs/SDL3/CMakeLists.txt
  3. 1 0
      libs/SDL3/INSTALL.md
  4. 10 0
      libs/SDL3/WhatsNew.txt
  5. 2 2
      libs/SDL3/Xcode/SDL/Info-Framework.plist
  6. 4 4
      libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj
  7. 1 1
      libs/SDL3/Xcode/SDL/pkg-support/SDL.info
  8. 4 4
      libs/SDL3/android-project/app/build.gradle
  9. 1 1
      libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
  10. 1 1
      libs/SDL3/cmake/sdlchecks.cmake
  11. 15 8
      libs/SDL3/docs/INTRO-cmake.md
  12. 95 0
      libs/SDL3/docs/INTRO-mingw.md
  13. 5 3
      libs/SDL3/docs/INTRO-visualstudio.md
  14. 8 8
      libs/SDL3/docs/README-android.md
  15. 8 8
      libs/SDL3/docs/README-ios.md
  16. 1 1
      libs/SDL3/docs/README-psp.md
  17. 4 0
      libs/SDL3/docs/README-wayland.md
  18. 1 1
      libs/SDL3/include/SDL3/SDL.h
  19. 8 8
      libs/SDL3/include/SDL3/SDL_audio.h
  20. 1 1
      libs/SDL3/include/SDL3/SDL_gpu.h
  21. 16 0
      libs/SDL3/include/SDL3/SDL_hints.h
  22. 4 0
      libs/SDL3/include/SDL3/SDL_power.h
  23. 14 2
      libs/SDL3/include/SDL3/SDL_stdinc.h
  24. 127 24
      libs/SDL3/include/SDL3/SDL_surface.h
  25. 1 1
      libs/SDL3/include/SDL3/SDL_version.h
  26. 15 1
      libs/SDL3/src/SDL.c
  27. 16 18
      libs/SDL3/src/SDL_assert.c
  28. 12 0
      libs/SDL3/src/SDL_error_c.h
  29. 2 2
      libs/SDL3/src/audio/SDL_audio.c
  30. 57 0
      libs/SDL3/src/audio/SDL_audiotypecvt.c
  31. 8 1
      libs/SDL3/src/audio/alsa/SDL_alsa_audio.c
  32. 5 1
      libs/SDL3/src/audio/pipewire/SDL_pipewire.c
  33. 5 2
      libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c
  34. 8 0
      libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c
  35. 3 3
      libs/SDL3/src/core/windows/SDL_windows.c
  36. 2 2
      libs/SDL3/src/core/windows/SDL_windows.h
  37. 4 4
      libs/SDL3/src/core/windows/version.rc
  38. 18 1
      libs/SDL3/src/cpuinfo/SDL_cpuinfo.c
  39. 6 4
      libs/SDL3/src/events/SDL_events.c
  40. 3 3
      libs/SDL3/src/events/SDL_keyboard.c
  41. 33 0
      libs/SDL3/src/events/SDL_mouse.c
  42. 7 0
      libs/SDL3/src/events/SDL_mouse_c.h
  43. 5 2
      libs/SDL3/src/gpu/SDL_gpu.c
  44. 1 1
      libs/SDL3/src/gpu/metal/SDL_gpu_metal.m
  45. 26 11
      libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c
  46. 3 2
      libs/SDL3/src/io/SDL_asyncio.c
  47. 12 10
      libs/SDL3/src/joystick/SDL_gamepad.c
  48. 6 0
      libs/SDL3/src/joystick/SDL_gamepad_db.h
  49. 57 0
      libs/SDL3/src/joystick/SDL_joystick.c
  50. 2 0
      libs/SDL3/src/joystick/SDL_sysjoystick.h
  51. 4 5
      libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c
  52. 11 5
      libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c
  53. 88 97
      libs/SDL3/src/render/SDL_render.c
  54. 2 1
      libs/SDL3/src/render/SDL_sysrender.h
  55. 46 37
      libs/SDL3/src/render/direct3d/SDL_render_d3d.c
  56. 4 18
      libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c
  57. 16 19
      libs/SDL3/src/render/direct3d12/SDL_render_d3d12.c
  58. 8 10
      libs/SDL3/src/render/gpu/SDL_render_gpu.c
  59. 1 6
      libs/SDL3/src/render/metal/SDL_render_metal.m
  60. 85 72
      libs/SDL3/src/render/opengl/SDL_render_gl.c
  61. 98 61
      libs/SDL3/src/render/opengles2/SDL_render_gles2.c
  62. 10 16
      libs/SDL3/src/render/ps2/SDL_render_ps2.c
  63. 49 13
      libs/SDL3/src/render/psp/SDL_render_psp.c
  64. 10 15
      libs/SDL3/src/render/software/SDL_render_sw.c
  65. 36 25
      libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm.c
  66. 6 0
      libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_tools.c
  67. 1 0
      libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_tools.h
  68. 2 0
      libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_types.h
  69. 2 16
      libs/SDL3/src/render/vulkan/SDL_render_vulkan.c
  70. 6 0
      libs/SDL3/src/stdlib/SDL_stdlib.c
  71. 10 14
      libs/SDL3/src/stdlib/SDL_string.c
  72. 0 1
      libs/SDL3/src/test/SDL_test_common.c
  73. 1 0
      libs/SDL3/src/test/SDL_test_memory.c
  74. 1 1
      libs/SDL3/src/thread/n3ds/SDL_syssem.c
  75. 17 0
      libs/SDL3/src/time/unix/SDL_systime.c
  76. 47 5
      libs/SDL3/src/video/SDL_blit_A.c
  77. 267 0
      libs/SDL3/src/video/SDL_blit_N.c
  78. 28 0
      libs/SDL3/src/video/SDL_pixels.c
  79. 1 1
      libs/SDL3/src/video/SDL_pixels_c.h
  80. 28 2
      libs/SDL3/src/video/SDL_surface.c
  81. 1 0
      libs/SDL3/src/video/SDL_surface_c.h
  82. 2 0
      libs/SDL3/src/video/SDL_sysvideo.h
  83. 24 2
      libs/SDL3/src/video/SDL_video.c
  84. 31 13
      libs/SDL3/src/video/SDL_yuv.c
  85. 18 14
      libs/SDL3/src/video/cocoa/SDL_cocoamodes.m
  86. 42 47
      libs/SDL3/src/video/cocoa/SDL_cocoawindow.m
  87. 25 12
      libs/SDL3/src/video/emscripten/SDL_emscriptenevents.c
  88. 2 0
      libs/SDL3/src/video/uikit/SDL_uikitvideo.m
  89. 11 3
      libs/SDL3/src/video/wayland/SDL_waylandevents.c
  90. 1 0
      libs/SDL3/src/video/wayland/SDL_waylandmouse.c
  91. 5 2
      libs/SDL3/src/video/wayland/SDL_waylandwindow.c
  92. 14 5
      libs/SDL3/src/video/windows/SDL_windowsevents.c
  93. 14 0
      libs/SDL3/src/video/windows/SDL_windowsvideo.c
  94. 44 0
      libs/SDL3/src/video/windows/SDL_windowsvideo.h
  95. 29 100
      libs/SDL3/src/video/windows/SDL_windowswindow.c
  96. 0 2
      libs/SDL3/src/video/windows/SDL_windowswindow.h
  97. 2 1
      libs/SDL3/src/video/x11/SDL_x11events.c
  98. 22 12
      libs/SDL3/src/video/x11/SDL_x11messagebox.c
  99. 63 9
      libs/SDL3/src/video/x11/SDL_x11modes.c
  100. 25 2
      libs/SDL3/src/video/x11/SDL_x11video.c

+ 1 - 1
CMakeLists.txt

@@ -227,7 +227,7 @@ set(MEGA_LIBVORBIS_VER "1.3.5")
 set(MEGA_LIBTHEORA_VER "1.1.1")
 set(MEGA_LIBTHEORA_VER "1.1.1")
 set(MEGA_FREETYPE_VER "2.13.2")
 set(MEGA_FREETYPE_VER "2.13.2")
 set(MEGA_SDL2_VER "2.28.5")
 set(MEGA_SDL2_VER "2.28.5")
-set(MEGA_SDL3_VER "3.1.1-preview")
+set(MEGA_SDL3_VER "3.2.10")
 set(MEGA_OPENAL_VER "1.23.1-bc7cb17")
 set(MEGA_OPENAL_VER "1.23.1-bc7cb17")
 set(MEGA_MODPLUG_VER "0.8.8.4")
 set(MEGA_MODPLUG_VER "0.8.8.4")
 
 

+ 2 - 1
libs/SDL3/CMakeLists.txt

@@ -5,7 +5,7 @@ if(NOT DEFINED CMAKE_BUILD_TYPE)
 endif()
 endif()
 
 
 # See docs/release_checklist.md
 # See docs/release_checklist.md
-project(SDL3 LANGUAGES C VERSION "3.2.6")
+project(SDL3 LANGUAGES C VERSION "3.2.10")
 
 
 if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
 if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
   set(SDL3_MAINPROJECT ON)
   set(SDL3_MAINPROJECT ON)
@@ -2188,6 +2188,7 @@ elseif(APPLE)
       set(SDL_CAMERA_DRIVER_COREMEDIA 1)
       set(SDL_CAMERA_DRIVER_COREMEDIA 1)
       set(HAVE_CAMERA TRUE)
       set(HAVE_CAMERA TRUE)
       sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/coremedia/*.m")
       sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/coremedia/*.m")
+      set(SDL_FRAMEWORK_AVFOUNDATION 1)
     endif()
     endif()
   endif()
   endif()
 
 

+ 1 - 0
libs/SDL3/INSTALL.md

@@ -3,6 +3,7 @@
 SDL supports a number of development environments:
 SDL supports a number of development environments:
 - [CMake](docs/INTRO-cmake.md)
 - [CMake](docs/INTRO-cmake.md)
 - [Visual Studio on Windows](docs/INTRO-visualstudio.md)
 - [Visual Studio on Windows](docs/INTRO-visualstudio.md)
+- [gcc on Windows](docs/INTRO-mingw.md)
 - [Xcode on Apple platforms](docs/INTRO-xcode.md)
 - [Xcode on Apple platforms](docs/INTRO-xcode.md)
 - [Android Studio](docs/INTRO-androidstudio.md)
 - [Android Studio](docs/INTRO-androidstudio.md)
 - [Emscripten for web](docs/INTRO-emscripten.md)
 - [Emscripten for web](docs/INTRO-emscripten.md)

+ 10 - 0
libs/SDL3/WhatsNew.txt

@@ -1,6 +1,16 @@
 
 
 This is a list of major changes in SDL's version history.
 This is a list of major changes in SDL's version history.
 
 
+---------------------------------------------------------------------------
+3.2.10:
+---------------------------------------------------------------------------
+* Added SDL_HINT_VIDEO_X11_EXTERNAL_WINDOW_INPUT to control whether XSelectInput() should be called on external windows to enable input events.
+
+---------------------------------------------------------------------------
+3.2.4:
+---------------------------------------------------------------------------
+* Added SDL_StretchSurface()
+
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
 3.2.0:
 3.2.0:
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------

+ 2 - 2
libs/SDL3/Xcode/SDL/Info-Framework.plist

@@ -19,10 +19,10 @@
 	<key>CFBundlePackageType</key>
 	<key>CFBundlePackageType</key>
 	<string>FMWK</string>
 	<string>FMWK</string>
 	<key>CFBundleShortVersionString</key>
 	<key>CFBundleShortVersionString</key>
-	<string>3.2.6</string>
+	<string>3.2.10</string>
 	<key>CFBundleSignature</key>
 	<key>CFBundleSignature</key>
 	<string>SDLX</string>
 	<string>SDLX</string>
 	<key>CFBundleVersion</key>
 	<key>CFBundleVersion</key>
-	<string>3.2.6</string>
+	<string>3.2.10</string>
 </dict>
 </dict>
 </plist>
 </plist>

+ 4 - 4
libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj

@@ -3086,7 +3086,7 @@
 				CLANG_ENABLE_OBJC_ARC = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
 				DEPLOYMENT_POSTPROCESSING = YES;
 				DEPLOYMENT_POSTPROCESSING = YES;
 				DYLIB_COMPATIBILITY_VERSION = 201.0.0;
 				DYLIB_COMPATIBILITY_VERSION = 201.0.0;
-				DYLIB_CURRENT_VERSION = 201.6.0;
+				DYLIB_CURRENT_VERSION = 201.10.0;
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				GCC_ALTIVEC_EXTENSIONS = YES;
 				GCC_ALTIVEC_EXTENSIONS = YES;
@@ -3121,7 +3121,7 @@
 					"@loader_path/Frameworks",
 					"@loader_path/Frameworks",
 				);
 				);
 				MACOSX_DEPLOYMENT_TARGET = 10.13;
 				MACOSX_DEPLOYMENT_TARGET = 10.13;
-				MARKETING_VERSION = 3.2.6;
+				MARKETING_VERSION = 3.2.10;
 				OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)";
 				OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)";
 				PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3;
 				PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3;
 				PRODUCT_NAME = SDL3;
 				PRODUCT_NAME = SDL3;
@@ -3150,7 +3150,7 @@
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
 				DYLIB_COMPATIBILITY_VERSION = 201.0.0;
 				DYLIB_COMPATIBILITY_VERSION = 201.0.0;
-				DYLIB_CURRENT_VERSION = 201.6.0;
+				DYLIB_CURRENT_VERSION = 201.10.0;
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_TESTABILITY = YES;
 				ENABLE_TESTABILITY = YES;
@@ -3182,7 +3182,7 @@
 					"@loader_path/Frameworks",
 					"@loader_path/Frameworks",
 				);
 				);
 				MACOSX_DEPLOYMENT_TARGET = 10.13;
 				MACOSX_DEPLOYMENT_TARGET = 10.13;
-				MARKETING_VERSION = 3.2.6;
+				MARKETING_VERSION = 3.2.10;
 				ONLY_ACTIVE_ARCH = YES;
 				ONLY_ACTIVE_ARCH = YES;
 				OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)";
 				OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)";
 				PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3;
 				PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3;

+ 1 - 1
libs/SDL3/Xcode/SDL/pkg-support/SDL.info

@@ -1,4 +1,4 @@
-Title SDL 3.2.6
+Title SDL 3.2.10
 Version 1
 Version 1
 Description SDL Library for macOS (http://www.libsdl.org)
 Description SDL Library for macOS (http://www.libsdl.org)
 DefaultLocation /Library/Frameworks
 DefaultLocation /Library/Frameworks

+ 4 - 4
libs/SDL3/android-project/app/build.gradle

@@ -5,7 +5,7 @@ plugins {
 def buildWithCMake = project.hasProperty('BUILD_WITH_CMAKE');
 def buildWithCMake = project.hasProperty('BUILD_WITH_CMAKE');
 
 
 android {
 android {
-    namespace "org.libsdl.app"
+    namespace = "org.libsdl.app"
     compileSdkVersion 35
     compileSdkVersion 35
     defaultConfig {
     defaultConfig {
         minSdkVersion 21
         minSdkVersion 21
@@ -14,12 +14,12 @@ android {
         versionName "1.0"
         versionName "1.0"
         externalNativeBuild {
         externalNativeBuild {
             ndkBuild {
             ndkBuild {
-                arguments "APP_PLATFORM=android-19"
+                arguments "APP_PLATFORM=android-21"
                 // abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
                 // abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
                 abiFilters 'arm64-v8a'
                 abiFilters 'arm64-v8a'
             }
             }
             cmake {
             cmake {
-                arguments "-DANDROID_PLATFORM=android-19", "-DANDROID_STL=c++_static"
+                arguments "-DANDROID_PLATFORM=android-21", "-DANDROID_STL=c++_static"
                 // abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
                 // abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
                 abiFilters 'arm64-v8a'
                 abiFilters 'arm64-v8a'
             }
             }
@@ -53,7 +53,7 @@ android {
 
 
     }
     }
     lint {
     lint {
-        abortOnError false
+        abortOnError = false
     }
     }
 }
 }
 
 

+ 1 - 1
libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java

@@ -60,7 +60,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
     private static final String TAG = "SDL";
     private static final String TAG = "SDL";
     private static final int SDL_MAJOR_VERSION = 3;
     private static final int SDL_MAJOR_VERSION = 3;
     private static final int SDL_MINOR_VERSION = 2;
     private static final int SDL_MINOR_VERSION = 2;
-    private static final int SDL_MICRO_VERSION = 6;
+    private static final int SDL_MICRO_VERSION = 10;
 /*
 /*
     // Display InputType.SOURCE/CLASS of events and devices
     // Display InputType.SOURCE/CLASS of events and devices
     //
     //

+ 1 - 1
libs/SDL3/cmake/sdlchecks.cmake

@@ -823,7 +823,7 @@ macro(CheckPTHREAD)
       if(CMAKE_C_COMPILER_ID MATCHES "SunPro")
       if(CMAKE_C_COMPILER_ID MATCHES "SunPro")
         set(PTHREAD_LDFLAGS "-mt -lpthread")
         set(PTHREAD_LDFLAGS "-mt -lpthread")
       else()
       else()
-        set(PTHREAD_LDFLAGS "-pthread -lposix4")
+        set(PTHREAD_LDFLAGS "-pthread")
       endif()
       endif()
     elseif(SYSV5)
     elseif(SYSV5)
       set(PTHREAD_CFLAGS "-D_REENTRANT -Kthread")
       set(PTHREAD_CFLAGS "-D_REENTRANT -Kthread")

+ 15 - 8
libs/SDL3/docs/INTRO-cmake.md

@@ -5,7 +5,12 @@ The easiest way to use SDL is to include it as a subproject in your project.
 
 
 We'll start by creating a simple project to build and run [hello.c](hello.c)
 We'll start by creating a simple project to build and run [hello.c](hello.c)
 
 
-Create the file CMakeLists.txt
+# Get a copy of the SDL source:
+```sh
+git clone https://github.com/libsdl-org/SDL.git vendored/SDL
+```
+
+# Create the file CMakeLists.txt
 ```cmake
 ```cmake
 cmake_minimum_required(VERSION 3.16)
 cmake_minimum_required(VERSION 3.16)
 project(hello)
 project(hello)
@@ -25,21 +30,23 @@ add_executable(hello WIN32 hello.c)
 target_link_libraries(hello PRIVATE SDL3::SDL3)
 target_link_libraries(hello PRIVATE SDL3::SDL3)
 ```
 ```
 
 
-Build:
+# Configure and Build:
 ```sh
 ```sh
 cmake -S . -B build
 cmake -S . -B build
 cmake --build build
 cmake --build build
 ```
 ```
 
 
-Run:
-- On Windows the executable is in the build Debug directory:
+# Run:
+The executable should be in the `build` directory:
+
 ```sh
 ```sh
-cd build/Debug
+cd build
 ./hello
 ./hello
-``` 
-- On other platforms the executable is in the build directory:
+```
+
+If there wasn't an executable there despite the above Build section running successfully, it's likely because you're following this guide using the Visual Studio toolchain, it should instead be in the `build/Debug` directory:
 ```sh
 ```sh
-cd build
+cd build/Debug
 ./hello
 ./hello
 ```
 ```
 
 

+ 95 - 0
libs/SDL3/docs/INTRO-mingw.md

@@ -0,0 +1,95 @@
+# Introduction to SDL with MinGW
+
+Without getting deep into the history, MinGW is a long running project that aims to bring gcc to Windows. That said, there's many distributions, versions, and forks floating around. We recommend installing [MSYS2](https://www.msys2.org/), as it's the easiest way to get a modern toolchain with a package manager to help with dependency management. This would allow you to follow the MSYS2 section below. 
+
+Otherwise you'll want to follow the "Other Distributions" section below.
+
+We'll start by creating a simple project to build and run [hello.c](hello.c).
+
+# MSYS2
+
+Open the `MSYS2 UCRT64` prompt and then ensure you've installed the following packages. This will get you working toolchain, CMake, Ninja, and of course SDL3.
+
+```sh
+pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-ninja mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-sdl3
+```
+
+## Create the file CMakeLists.txt
+```cmake
+cmake_minimum_required(VERSION 3.26)
+project(hello C CXX)
+
+find_package(SDL3 REQUIRED)
+
+add_executable(hello)
+
+target_sources(hello
+PRIVATE
+    hello.c
+)
+
+target_link_libraries(hello SDL3::SDL3)
+```
+
+## Configure and Build:
+```sh
+cmake -S . -B build
+cmake --build build
+```
+
+## Run:
+
+The executable is in the `build` directory:
+```sh
+cd build
+./hello
+```
+
+# Other Distributions
+
+Things can get quite complicated with other distributions of MinGW. If you can't follow [the cmake intro](INTRO-cmake.md), perhaps due to issues getting cmake to understand your toolchain, this section should work.
+
+## Acquire SDL
+
+Download the `SDL3-devel-<version>-mingw.zip` asset from [the latest release.](https://github.com/libsdl-org/SDL/releases/latest) Then extract it inside your project folder such that the output of `ls SDL3-<version>` looks like `INSTALL.md  LICENSE.txt  Makefile  README.md  cmake  i686-w64-mingw32  x86_64-w64-mingw32`.
+
+## Know your Target Architecture
+
+It is not uncommon for folks to not realize their distribution is targeting 32bit Windows despite things like the name of the toolchain, or the fact that they're running on a 64bit system. We'll ensure we know up front what we need:
+
+Create a file named `arch.c` with the following contents:
+```c
+#include <stddef.h>
+#include <stdio.h>
+int main() {
+    #if defined(__x86_64__) || defined(_M_X64) || defined(i386) || defined(__i386__) || defined(__i386) || defined(_M_IX86)
+        size_t ptr_size = sizeof(int*);
+        if (4 == ptr_size) puts("i686-w64-mingw32");
+        else if (8 == ptr_size) puts("x86_64-w64-mingw32");
+        else puts("Unknown Architecture");
+    #else
+        puts("Unknown Architecture");
+    #endif
+    return 0;
+}
+```
+
+Then run
+
+```sh
+gcc arch.c
+./a.exe
+```
+
+This should print out which library directory we'll need to use when compiling, keep this value in mind, you'll need to use it when compiling in the next section as `<arch>`. If you get "Unknown Architecture" please [report a bug](https://github.com/libsdl-org/SDL/issues).
+
+
+## Build and Run
+
+Now we should have everything needed to compile and run our program. You'll need to ensure to replace `<version>` with the version of the release of SDL3 you downloaded, as well as use the `<arch>` we learned in the previous section.
+
+```sh
+gcc hello.c -o hello.exe -I SDL3-<version>/<arch>/include -L SDL3-<version>/<arch>/lib -lSDL3 -mwindows 
+cp SDL3-<version>/<arch>/bin/SDL3.dll SDL3.dll
+./hello.exe
+```

+ 5 - 3
libs/SDL3/docs/INTRO-visualstudio.md

@@ -5,10 +5,12 @@ The easiest way to use SDL is to include it as a subproject in your project.
 
 
 We'll start by creating a simple project to build and run [hello.c](hello.c)
 We'll start by creating a simple project to build and run [hello.c](hello.c)
 
 
+- Get a copy of the SDL source, you can clone the repo, or download the "Source Code" asset from [the latest release.](https://github.com/libsdl-org/SDL/releases/latest)
+  - If you've downloaded a release, make sure to extract the contents somewhere you can find it.
 - Create a new project in Visual Studio, using the C++ Empty Project template
 - Create a new project in Visual Studio, using the C++ Empty Project template
 - Add hello.c to the Source Files
 - Add hello.c to the Source Files
-- Right click the solution, select add an existing project, navigate to VisualC/SDL and add SDL.vcxproj
-- Select your main project and go to Project -> Add Reference and select SDL3
-- Select your main project and go to Project -> Properties, set the filter at the top to "All Configurations" and "All Platforms", select VC++ Directories and add the SDL include directory to "Include Directories"
+- Right click the solution, select add an existing project, navigate to `VisualC/SDL` from within the source you cloned or downloaded above and add SDL.vcxproj
+- Select your main project and go to Project -> Add -> Reference and select SDL3
+- Select your main project and go to Project -> Properties, set the filter at the top to "All Configurations" and "All Platforms", select C/C++ -> General and add the SDL include directory to "Additional Include Directories"
 - Build and run!
 - Build and run!
 
 

+ 8 - 8
libs/SDL3/docs/README-android.md

@@ -242,7 +242,7 @@ not give you any processing time after the events are delivered.
 
 
 e.g.
 e.g.
 
 
-    int HandleAppEvents(void *userdata, SDL_Event *event)
+    bool HandleAppEvents(void *userdata, SDL_Event *event)
     {
     {
         switch (event->type)
         switch (event->type)
         {
         {
@@ -250,12 +250,12 @@ e.g.
             /* Terminate the app.
             /* Terminate the app.
                Shut everything down before returning from this function.
                Shut everything down before returning from this function.
             */
             */
-            return 0;
+            return false;
         case SDL_EVENT_LOW_MEMORY:
         case SDL_EVENT_LOW_MEMORY:
             /* You will get this when your app is paused and iOS wants more memory.
             /* You will get this when your app is paused and iOS wants more memory.
                Release as much memory as possible.
                Release as much memory as possible.
             */
             */
-            return 0;
+            return false;
         case SDL_EVENT_WILL_ENTER_BACKGROUND:
         case SDL_EVENT_WILL_ENTER_BACKGROUND:
             /* Prepare your app to go into the background.  Stop loops, etc.
             /* Prepare your app to go into the background.  Stop loops, etc.
                This gets called when the user hits the home button, or gets a call.
                This gets called when the user hits the home button, or gets a call.
@@ -264,15 +264,15 @@ e.g.
                in addition, you should set the render target to NULL, if you're using
                in addition, you should set the render target to NULL, if you're using
                it, e.g. call SDL_SetRenderTarget(renderer, NULL).
                it, e.g. call SDL_SetRenderTarget(renderer, NULL).
             */
             */
-            return 0;
+            return false;
         case SDL_EVENT_DID_ENTER_BACKGROUND:
         case SDL_EVENT_DID_ENTER_BACKGROUND:
             /* Your app is NOT active at this point. */
             /* Your app is NOT active at this point. */
-            return 0;
+            return false;
         case SDL_EVENT_WILL_ENTER_FOREGROUND:
         case SDL_EVENT_WILL_ENTER_FOREGROUND:
             /* This call happens when your app is coming back to the foreground.
             /* This call happens when your app is coming back to the foreground.
                Restore all your state here.
                Restore all your state here.
             */
             */
-            return 0;
+            return false;
         case SDL_EVENT_DID_ENTER_FOREGROUND:
         case SDL_EVENT_DID_ENTER_FOREGROUND:
             /* Restart your loops here.
             /* Restart your loops here.
                Your app is interactive and getting CPU again.
                Your app is interactive and getting CPU again.
@@ -283,10 +283,10 @@ e.g.
                event SDL_EVENT_RENDER_DEVICE_RESET and recreate your OpenGL context and
                event SDL_EVENT_RENDER_DEVICE_RESET and recreate your OpenGL context and
                restore your textures when you get it, or quit the app.
                restore your textures when you get it, or quit the app.
             */
             */
-            return 0;
+            return false;
         default:
         default:
             /* No special processing, add it to the event queue */
             /* No special processing, add it to the event queue */
-            return 1;
+            return true;
         }
         }
     }
     }
 
 

+ 8 - 8
libs/SDL3/docs/README-ios.md

@@ -65,7 +65,7 @@ not give you any processing time after the events are delivered.
 
 
 e.g.
 e.g.
 
 
-    int HandleAppEvents(void *userdata, SDL_Event *event)
+    bool HandleAppEvents(void *userdata, SDL_Event *event)
     {
     {
         switch (event->type)
         switch (event->type)
         {
         {
@@ -73,37 +73,37 @@ e.g.
             /* Terminate the app.
             /* Terminate the app.
                Shut everything down before returning from this function.
                Shut everything down before returning from this function.
             */
             */
-            return 0;
+            return false;
         case SDL_EVENT_LOW_MEMORY:
         case SDL_EVENT_LOW_MEMORY:
             /* You will get this when your app is paused and iOS wants more memory.
             /* You will get this when your app is paused and iOS wants more memory.
                Release as much memory as possible.
                Release as much memory as possible.
             */
             */
-            return 0;
+            return false;
         case SDL_EVENT_WILL_ENTER_BACKGROUND:
         case SDL_EVENT_WILL_ENTER_BACKGROUND:
             /* Prepare your app to go into the background.  Stop loops, etc.
             /* Prepare your app to go into the background.  Stop loops, etc.
                This gets called when the user hits the home button, or gets a call.
                This gets called when the user hits the home button, or gets a call.
             */
             */
-            return 0;
+            return false;
         case SDL_EVENT_DID_ENTER_BACKGROUND:
         case SDL_EVENT_DID_ENTER_BACKGROUND:
             /* This will get called if the user accepted whatever sent your app to the background.
             /* This will get called if the user accepted whatever sent your app to the background.
                If the user got a phone call and canceled it, you'll instead get an SDL_EVENT_DID_ENTER_FOREGROUND event and restart your loops.
                If the user got a phone call and canceled it, you'll instead get an SDL_EVENT_DID_ENTER_FOREGROUND event and restart your loops.
                When you get this, you have 5 seconds to save all your state or the app will be terminated.
                When you get this, you have 5 seconds to save all your state or the app will be terminated.
                Your app is NOT active at this point.
                Your app is NOT active at this point.
             */
             */
-            return 0;
+            return false;
         case SDL_EVENT_WILL_ENTER_FOREGROUND:
         case SDL_EVENT_WILL_ENTER_FOREGROUND:
             /* This call happens when your app is coming back to the foreground.
             /* This call happens when your app is coming back to the foreground.
                Restore all your state here.
                Restore all your state here.
             */
             */
-            return 0;
+            return false;
         case SDL_EVENT_DID_ENTER_FOREGROUND:
         case SDL_EVENT_DID_ENTER_FOREGROUND:
             /* Restart your loops here.
             /* Restart your loops here.
                Your app is interactive and getting CPU again.
                Your app is interactive and getting CPU again.
             */
             */
-            return 0;
+            return false;
         default:
         default:
             /* No special processing, add it to the event queue */
             /* No special processing, add it to the event queue */
-            return 1;
+            return true;
         }
         }
     }
     }
 
 

+ 1 - 1
libs/SDL3/docs/README-psp.md

@@ -29,7 +29,7 @@ cmake --install build
 
 
 
 
 ## Compiling a HelloWorld
 ## Compiling a HelloWorld
-[PSP Hello World](https://psp-dev.org/doku.php?id=tutorial:hello_world)
+[PSP Hello World](https://pspdev.github.io/basic_programs.html#hello-world)
 
 
 ## To Do
 ## To Do
 - PSP Screen Keyboard
 - PSP Screen Keyboard

+ 4 - 0
libs/SDL3/docs/README-wayland.md

@@ -59,6 +59,10 @@ encounter limitations or behavior that is different from other windowing systems
   `SDL_APP_ID` hint string, the desktop entry file name should match the application ID. For example, if your
   `SDL_APP_ID` hint string, the desktop entry file name should match the application ID. For example, if your
   application ID is set to `org.my_org.sdl_app`, the desktop entry file should be named `org.my_org.sdl_app.desktop`.
   application ID is set to `org.my_org.sdl_app`, the desktop entry file should be named `org.my_org.sdl_app.desktop`.
 
 
+### Keyboard grabs don't work when running under XWayland
+
+- On GNOME based desktops, the dconf setting `org/gnome/mutter/wayland/xwayland-allow-grabs` must be enabled.
+
 ## Using custom Wayland windowing protocols with SDL windows
 ## Using custom Wayland windowing protocols with SDL windows
 
 
 Under normal operation, an `SDL_Window` corresponds to an XDG toplevel window, which provides a standard desktop window.
 Under normal operation, an `SDL_Window` corresponds to an XDG toplevel window, which provides a standard desktop window.

+ 1 - 1
libs/SDL3/include/SDL3/SDL.h

@@ -20,7 +20,7 @@
 */
 */
 
 
 /**
 /**
- * Main include header for the SDL library, version 3.2.6
+ * Main include header for the SDL library, version 3.2.10
  *
  *
  * It is almost always best to include just this one header instead of
  * It is almost always best to include just this one header instead of
  * picking out individual headers included here. There are exceptions to
  * picking out individual headers included here. There are exceptions to

+ 8 - 8
libs/SDL3/include/SDL3/SDL_audio.h

@@ -1717,7 +1717,7 @@ typedef void (SDLCALL *SDL_AudioStreamCallback)(void *userdata, SDL_AudioStream
  * audio to the stream during this call; if needed, the request that triggered
  * audio to the stream during this call; if needed, the request that triggered
  * this callback will obtain the new data immediately.
  * this callback will obtain the new data immediately.
  *
  *
- * The callback's `approx_request` argument is roughly how many bytes of
+ * The callback's `additional_amount` argument is roughly how many bytes of
  * _unconverted_ data (in the stream's input format) is needed by the caller,
  * _unconverted_ data (in the stream's input format) is needed by the caller,
  * although this may overestimate a little for safety. This takes into account
  * although this may overestimate a little for safety. This takes into account
  * how much is already in the stream and only asks for any extra necessary to
  * how much is already in the stream and only asks for any extra necessary to
@@ -1762,13 +1762,13 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetAudioStreamGetCallback(SDL_AudioStream *
  * The callback can (optionally) call SDL_GetAudioStreamData() to obtain audio
  * The callback can (optionally) call SDL_GetAudioStreamData() to obtain audio
  * from the stream during this call.
  * from the stream during this call.
  *
  *
- * The callback's `approx_request` argument is how many bytes of _converted_
- * data (in the stream's output format) was provided by the caller, although
- * this may underestimate a little for safety. This value might be less than
- * what is currently available in the stream, if data was already there, and
- * might be less than the caller provided if the stream needs to keep a buffer
- * to aid in resampling. Which means the callback may be provided with zero
- * bytes, and a different amount on each call.
+ * The callback's `additional_amount` argument is how many bytes of
+ * _converted_ data (in the stream's output format) was provided by the
+ * caller, although this may underestimate a little for safety. This value
+ * might be less than what is currently available in the stream, if data was
+ * already there, and might be less than the caller provided if the stream
+ * needs to keep a buffer to aid in resampling. Which means the callback may
+ * be provided with zero bytes, and a different amount on each call.
  *
  *
  * The callback may call SDL_GetAudioStreamAvailable to see the total amount
  * The callback may call SDL_GetAudioStreamAvailable to see the total amount
  * currently available to read from the stream, instead of the total provided
  * currently available to read from the stream, instead of the total provided

+ 1 - 1
libs/SDL3/include/SDL3/SDL_gpu.h

@@ -4145,7 +4145,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GPUTextureSupportsFormat(
  * \param device a GPU context.
  * \param device a GPU context.
  * \param format the texture format to check.
  * \param format the texture format to check.
  * \param sample_count the sample count to check.
  * \param sample_count the sample count to check.
- * \returns a hardware-specific version of min(preferred, possible).
+ * \returns whether the sample count is supported for this texture format.
  *
  *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  */
  */

+ 16 - 0
libs/SDL3/include/SDL3/SDL_hints.h

@@ -3607,6 +3607,22 @@ extern "C" {
  */
  */
 #define SDL_HINT_VIDEO_WIN_D3DCOMPILER "SDL_VIDEO_WIN_D3DCOMPILER"
 #define SDL_HINT_VIDEO_WIN_D3DCOMPILER "SDL_VIDEO_WIN_D3DCOMPILER"
 
 
+/**
+ * A variable controlling whether SDL should call XSelectInput() to enable
+ * input events on X11 windows wrapped by SDL windows.
+ *
+ * The variable can be set to the following values:
+ *
+ * - "0": Don't call XSelectInput(), assuming the native window code has done
+ *   it already.
+ * - "1": Call XSelectInput() to enable input events. (default)
+ *
+ * This hint should be set before creating a window.
+ *
+ * \since This hint is available since SDL 3.2.10.
+ */
+#define SDL_HINT_VIDEO_X11_EXTERNAL_WINDOW_INPUT "SDL_VIDEO_X11_EXTERNAL_WINDOW_INPUT"
+
 /**
 /**
  * A variable controlling whether the X11 _NET_WM_BYPASS_COMPOSITOR hint
  * A variable controlling whether the X11 _NET_WM_BYPASS_COMPOSITOR hint
  * should be used.
  * should be used.

+ 4 - 0
libs/SDL3/include/SDL3/SDL_power.h

@@ -79,6 +79,10 @@ typedef enum SDL_PowerState
  * It's possible a platform can only report battery percentage or time left
  * It's possible a platform can only report battery percentage or time left
  * but not both.
  * but not both.
  *
  *
+ * On some platforms, retrieving power supply details might be expensive. If
+ * you want to display continuous status you could call this function every
+ * minute or so.
+ *
  * \param seconds a pointer filled in with the seconds of battery life left,
  * \param seconds a pointer filled in with the seconds of battery life left,
  *                or NULL to ignore. This will be filled in with -1 if we
  *                or NULL to ignore. This will be filled in with -1 if we
  *                can't determine a value or there is no battery.
  *                can't determine a value or there is no battery.

+ 14 - 2
libs/SDL3/include/SDL3/SDL_stdinc.h

@@ -1299,8 +1299,11 @@ extern "C" {
  *
  *
  * If `size` is 0, it will be set to 1.
  * If `size` is 0, it will be set to 1.
  *
  *
- * If you want to allocate memory aligned to a specific alignment, consider
- * using SDL_aligned_alloc().
+ * If the allocation is successful, the returned pointer is guaranteed to be
+ * aligned to either the *fundamental alignment* (`alignof(max_align_t)` in
+ * C11 and later) or `2 * sizeof(void *)`, whichever is smaller. Use
+ * SDL_aligned_alloc() if you need to allocate memory aligned to an alignment
+ * greater than this guarantee.
  *
  *
  * \param size the size to allocate.
  * \param size the size to allocate.
  * \returns a pointer to the allocated memory, or NULL if allocation failed.
  * \returns a pointer to the allocated memory, or NULL if allocation failed.
@@ -1323,6 +1326,10 @@ extern SDL_DECLSPEC SDL_MALLOC void * SDLCALL SDL_malloc(size_t size);
  *
  *
  * If either of `nmemb` or `size` is 0, they will both be set to 1.
  * If either of `nmemb` or `size` is 0, they will both be set to 1.
  *
  *
+ * If the allocation is successful, the returned pointer is guaranteed to be
+ * aligned to either the *fundamental alignment* (`alignof(max_align_t)` in
+ * C11 and later) or `2 * sizeof(void *)`, whichever is smaller.
+ *
  * \param nmemb the number of elements in the array.
  * \param nmemb the number of elements in the array.
  * \param size the size of each element of the array.
  * \param size the size of each element of the array.
  * \returns a pointer to the allocated array, or NULL if allocation failed.
  * \returns a pointer to the allocated array, or NULL if allocation failed.
@@ -1357,6 +1364,11 @@ extern SDL_DECLSPEC SDL_MALLOC SDL_ALLOC_SIZE2(1, 2) void * SDLCALL SDL_calloc(s
  * - If it returns NULL (indicating failure), then `mem` will remain valid and
  * - If it returns NULL (indicating failure), then `mem` will remain valid and
  *   must still be freed with SDL_free().
  *   must still be freed with SDL_free().
  *
  *
+ * If the allocation is successfully resized, the returned pointer is
+ * guaranteed to be aligned to either the *fundamental alignment*
+ * (`alignof(max_align_t)` in C11 and later) or `2 * sizeof(void *)`,
+ * whichever is smaller.
+ *
  * \param mem a pointer to allocated memory to reallocate, or NULL.
  * \param mem a pointer to allocated memory to reallocate, or NULL.
  * \param size the new size of the memory.
  * \param size the new size of the memory.
  * \returns a pointer to the newly allocated memory, or NULL if allocation
  * \returns a pointer to the newly allocated memory, or NULL if allocation

+ 127 - 24
libs/SDL3/include/SDL3/SDL_surface.h

@@ -82,6 +82,7 @@ typedef Uint32 SDL_SurfaceFlags;
  */
  */
 typedef enum SDL_ScaleMode
 typedef enum SDL_ScaleMode
 {
 {
+    SDL_SCALEMODE_INVALID = -1,
     SDL_SCALEMODE_NEAREST, /**< nearest pixel sampling */
     SDL_SCALEMODE_NEAREST, /**< nearest pixel sampling */
     SDL_SCALEMODE_LINEAR   /**< linear filtering */
     SDL_SCALEMODE_LINEAR   /**< linear filtering */
 } SDL_ScaleMode;
 } SDL_ScaleMode;
@@ -156,6 +157,8 @@ typedef struct SDL_Surface SDL_Surface;
  * \returns the new SDL_Surface structure that is created or NULL on failure;
  * \returns the new SDL_Surface structure that is created or NULL on failure;
  *          call SDL_GetError() for more information.
  *          call SDL_GetError() for more information.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_CreateSurfaceFrom
  * \sa SDL_CreateSurfaceFrom
@@ -184,6 +187,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_CreateSurface(int width, int heigh
  * \returns the new SDL_Surface structure that is created or NULL on failure;
  * \returns the new SDL_Surface structure that is created or NULL on failure;
  *          call SDL_GetError() for more information.
  *          call SDL_GetError() for more information.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_CreateSurface
  * \sa SDL_CreateSurface
@@ -198,6 +203,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_CreateSurfaceFrom(int width, int h
  *
  *
  * \param surface the SDL_Surface to free.
  * \param surface the SDL_Surface to free.
  *
  *
+ * \threadsafety No other thread should be using the surface when it is freed.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_CreateSurface
  * \sa SDL_CreateSurface
@@ -233,6 +240,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroySurface(SDL_Surface *surface);
  * \returns a valid property ID on success or 0 on failure; call
  * \returns a valid property ID on success or 0 on failure; call
  *          SDL_GetError() for more information.
  *          SDL_GetError() for more information.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  */
  */
 extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetSurfaceProperties(SDL_Surface *surface);
 extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetSurfaceProperties(SDL_Surface *surface);
@@ -255,6 +264,8 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetSurfaceProperties(SDL_Surfac
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_GetSurfaceColorspace
  * \sa SDL_GetSurfaceColorspace
@@ -272,6 +283,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetSurfaceColorspace(SDL_Surface *surface,
  * \returns the colorspace used by the surface, or SDL_COLORSPACE_UNKNOWN if
  * \returns the colorspace used by the surface, or SDL_COLORSPACE_UNKNOWN if
  *          the surface is NULL.
  *          the surface is NULL.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_SetSurfaceColorspace
  * \sa SDL_SetSurfaceColorspace
@@ -300,6 +313,8 @@ extern SDL_DECLSPEC SDL_Colorspace SDLCALL SDL_GetSurfaceColorspace(SDL_Surface
  *          the surface didn't have an index format); call SDL_GetError() for
  *          the surface didn't have an index format); call SDL_GetError() for
  *          more information.
  *          more information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_SetPaletteColors
  * \sa SDL_SetPaletteColors
@@ -316,6 +331,8 @@ extern SDL_DECLSPEC SDL_Palette * SDLCALL SDL_CreateSurfacePalette(SDL_Surface *
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_CreatePalette
  * \sa SDL_CreatePalette
@@ -330,6 +347,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetSurfacePalette(SDL_Surface *surface, SDL
  * \returns a pointer to the palette used by the surface, or NULL if there is
  * \returns a pointer to the palette used by the surface, or NULL if there is
  *          no palette used.
  *          no palette used.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_SetSurfacePalette
  * \sa SDL_SetSurfacePalette
@@ -353,6 +372,8 @@ extern SDL_DECLSPEC SDL_Palette * SDLCALL SDL_GetSurfacePalette(SDL_Surface *sur
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_RemoveSurfaceAlternateImages
  * \sa SDL_RemoveSurfaceAlternateImages
@@ -367,6 +388,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_AddSurfaceAlternateImage(SDL_Surface *surfa
  * \param surface the SDL_Surface structure to query.
  * \param surface the SDL_Surface structure to query.
  * \returns true if alternate versions are available or false otherwise.
  * \returns true if alternate versions are available or false otherwise.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_AddSurfaceAlternateImage
  * \sa SDL_AddSurfaceAlternateImage
@@ -392,6 +415,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SurfaceHasAlternateImages(SDL_Surface *surf
  *          failure; call SDL_GetError() for more information. This should be
  *          failure; call SDL_GetError() for more information. This should be
  *          freed with SDL_free() when it is no longer needed.
  *          freed with SDL_free() when it is no longer needed.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_AddSurfaceAlternateImage
  * \sa SDL_AddSurfaceAlternateImage
@@ -408,6 +433,8 @@ extern SDL_DECLSPEC SDL_Surface ** SDLCALL SDL_GetSurfaceImages(SDL_Surface *sur
  *
  *
  * \param surface the SDL_Surface structure to update.
  * \param surface the SDL_Surface structure to update.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_AddSurfaceAlternateImage
  * \sa SDL_AddSurfaceAlternateImage
@@ -432,6 +459,10 @@ extern SDL_DECLSPEC void SDLCALL SDL_RemoveSurfaceAlternateImages(SDL_Surface *s
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe. The locking referred to by
+ *               this function is making the pixels available for direct
+ *               access, not thread-safe locking.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_MUSTLOCK
  * \sa SDL_MUSTLOCK
@@ -444,6 +475,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_LockSurface(SDL_Surface *surface);
  *
  *
  * \param surface the SDL_Surface structure to be unlocked.
  * \param surface the SDL_Surface structure to be unlocked.
  *
  *
+ * \threadsafety This function is not thread safe. The locking referred to by
+ *               this function is making the pixels available for direct
+ *               access, not thread-safe locking.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_LockSurface
  * \sa SDL_LockSurface
@@ -462,6 +497,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnlockSurface(SDL_Surface *surface);
  * \returns a pointer to a new SDL_Surface structure or NULL on failure; call
  * \returns a pointer to a new SDL_Surface structure or NULL on failure; call
  *          SDL_GetError() for more information.
  *          SDL_GetError() for more information.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_DestroySurface
  * \sa SDL_DestroySurface
@@ -480,6 +517,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadBMP_IO(SDL_IOStream *src, bool
  * \returns a pointer to a new SDL_Surface structure or NULL on failure; call
  * \returns a pointer to a new SDL_Surface structure or NULL on failure; call
  *          SDL_GetError() for more information.
  *          SDL_GetError() for more information.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_DestroySurface
  * \sa SDL_DestroySurface
@@ -504,6 +543,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadBMP(const char *file);
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_LoadBMP_IO
  * \sa SDL_LoadBMP_IO
@@ -525,6 +566,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SaveBMP_IO(SDL_Surface *surface, SDL_IOStre
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_LoadBMP
  * \sa SDL_LoadBMP
@@ -543,6 +586,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SaveBMP(SDL_Surface *surface, const char *f
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_BlitSurface
  * \sa SDL_BlitSurface
@@ -559,6 +604,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetSurfaceRLE(SDL_Surface *surface, bool en
  * \param surface the SDL_Surface structure to query.
  * \param surface the SDL_Surface structure to query.
  * \returns true if the surface is RLE enabled, false otherwise.
  * \returns true if the surface is RLE enabled, false otherwise.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_SetSurfaceRLE
  * \sa SDL_SetSurfaceRLE
@@ -581,6 +628,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SurfaceHasRLE(SDL_Surface *surface);
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_GetSurfaceColorKey
  * \sa SDL_GetSurfaceColorKey
@@ -597,6 +646,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetSurfaceColorKey(SDL_Surface *surface, bo
  * \param surface the SDL_Surface structure to query.
  * \param surface the SDL_Surface structure to query.
  * \returns true if the surface has a color key, false otherwise.
  * \returns true if the surface has a color key, false otherwise.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_SetSurfaceColorKey
  * \sa SDL_SetSurfaceColorKey
@@ -617,6 +668,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SurfaceHasColorKey(SDL_Surface *surface);
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_SetSurfaceColorKey
  * \sa SDL_SetSurfaceColorKey
@@ -640,6 +693,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetSurfaceColorKey(SDL_Surface *surface, Ui
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_GetSurfaceColorMod
  * \sa SDL_GetSurfaceColorMod
@@ -658,6 +713,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetSurfaceColorMod(SDL_Surface *surface, Ui
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_GetSurfaceAlphaMod
  * \sa SDL_GetSurfaceAlphaMod
@@ -678,6 +735,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetSurfaceColorMod(SDL_Surface *surface, Ui
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_GetSurfaceAlphaMod
  * \sa SDL_GetSurfaceAlphaMod
@@ -693,6 +752,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetSurfaceAlphaMod(SDL_Surface *surface, Ui
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_GetSurfaceColorMod
  * \sa SDL_GetSurfaceColorMod
@@ -712,6 +773,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetSurfaceAlphaMod(SDL_Surface *surface, Ui
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_GetSurfaceBlendMode
  * \sa SDL_GetSurfaceBlendMode
@@ -726,6 +789,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetSurfaceBlendMode(SDL_Surface *surface, S
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_SetSurfaceBlendMode
  * \sa SDL_SetSurfaceBlendMode
@@ -747,6 +812,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetSurfaceBlendMode(SDL_Surface *surface, S
  * \returns true if the rectangle intersects the surface, otherwise false and
  * \returns true if the rectangle intersects the surface, otherwise false and
  *          blits will be completely clipped.
  *          blits will be completely clipped.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_GetSurfaceClipRect
  * \sa SDL_GetSurfaceClipRect
@@ -766,6 +833,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetSurfaceClipRect(SDL_Surface *surface, co
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_SetSurfaceClipRect
  * \sa SDL_SetSurfaceClipRect
@@ -780,6 +849,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetSurfaceClipRect(SDL_Surface *surface, SD
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  */
  */
 extern SDL_DECLSPEC bool SDLCALL SDL_FlipSurface(SDL_Surface *surface, SDL_FlipMode flip);
 extern SDL_DECLSPEC bool SDLCALL SDL_FlipSurface(SDL_Surface *surface, SDL_FlipMode flip);
@@ -796,6 +867,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_FlipSurface(SDL_Surface *surface, SDL_FlipM
  * \returns a copy of the surface or NULL on failure; call SDL_GetError() for
  * \returns a copy of the surface or NULL on failure; call SDL_GetError() for
  *          more information.
  *          more information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_DestroySurface
  * \sa SDL_DestroySurface
@@ -815,6 +888,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_DuplicateSurface(SDL_Surface *surf
  * \returns a copy of the surface or NULL on failure; call SDL_GetError() for
  * \returns a copy of the surface or NULL on failure; call SDL_GetError() for
  *          more information.
  *          more information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_DestroySurface
  * \sa SDL_DestroySurface
@@ -840,6 +915,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_ScaleSurface(SDL_Surface *surface,
  * \returns the new SDL_Surface structure that is created or NULL on failure;
  * \returns the new SDL_Surface structure that is created or NULL on failure;
  *          call SDL_GetError() for more information.
  *          call SDL_GetError() for more information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_ConvertSurfaceAndColorspace
  * \sa SDL_ConvertSurfaceAndColorspace
@@ -866,6 +943,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_ConvertSurface(SDL_Surface *surfac
  * \returns the new SDL_Surface structure that is created or NULL on failure;
  * \returns the new SDL_Surface structure that is created or NULL on failure;
  *          call SDL_GetError() for more information.
  *          call SDL_GetError() for more information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_ConvertSurface
  * \sa SDL_ConvertSurface
@@ -887,6 +966,10 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_ConvertSurfaceAndColorspace(SDL_Su
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety The same destination pixels should not be used from two
+ *               threads at once. It is safe to use the same source pixels
+ *               from multiple threads.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_ConvertPixelsAndColorspace
  * \sa SDL_ConvertPixelsAndColorspace
@@ -916,6 +999,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ConvertPixels(int width, int height, SDL_Pi
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety The same destination pixels should not be used from two
+ *               threads at once. It is safe to use the same source pixels
+ *               from multiple threads.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_ConvertPixels
  * \sa SDL_ConvertPixels
@@ -940,6 +1027,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ConvertPixelsAndColorspace(int width, int h
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety The same destination pixels should not be used from two
+ *               threads at once. It is safe to use the same source pixels
+ *               from multiple threads.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  */
  */
 extern SDL_DECLSPEC bool SDLCALL SDL_PremultiplyAlpha(int width, int height, SDL_PixelFormat src_format, const void *src, int src_pitch, SDL_PixelFormat dst_format, void *dst, int dst_pitch, bool linear);
 extern SDL_DECLSPEC bool SDLCALL SDL_PremultiplyAlpha(int width, int height, SDL_PixelFormat src_format, const void *src, int src_pitch, SDL_PixelFormat dst_format, void *dst, int dst_pitch, bool linear);
@@ -955,6 +1046,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_PremultiplyAlpha(int width, int height, SDL
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  */
  */
 extern SDL_DECLSPEC bool SDLCALL SDL_PremultiplySurfaceAlpha(SDL_Surface *surface, bool linear);
 extern SDL_DECLSPEC bool SDLCALL SDL_PremultiplySurfaceAlpha(SDL_Surface *surface, bool linear);
@@ -975,6 +1068,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_PremultiplySurfaceAlpha(SDL_Surface *surfac
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  */
  */
 extern SDL_DECLSPEC bool SDLCALL SDL_ClearSurface(SDL_Surface *surface, float r, float g, float b, float a);
 extern SDL_DECLSPEC bool SDLCALL SDL_ClearSurface(SDL_Surface *surface, float r, float g, float b, float a);
@@ -998,6 +1093,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ClearSurface(SDL_Surface *surface, float r,
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_FillSurfaceRects
  * \sa SDL_FillSurfaceRects
@@ -1023,6 +1120,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_FillSurfaceRect(SDL_Surface *dst, const SDL
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_FillSurfaceRect
  * \sa SDL_FillSurfaceRect
@@ -1096,9 +1195,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_FillSurfaceRects(SDL_Surface *dst, const SD
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
- * \threadsafety The same destination surface should not be used from two
- *               threads at once. It is safe to use the same source surface
- *               from multiple threads.
+ * \threadsafety Only one thread should be using the `src` and `dst` surfaces
+ *               at any given time.
  *
  *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
@@ -1121,9 +1219,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_BlitSurface(SDL_Surface *src, const SDL_Rec
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
- * \threadsafety The same destination surface should not be used from two
- *               threads at once. It is safe to use the same source surface
- *               from multiple threads.
+ * \threadsafety Only one thread should be using the `src` and `dst` surfaces
+ *               at any given time.
  *
  *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
@@ -1146,9 +1243,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_BlitSurfaceUnchecked(SDL_Surface *src, cons
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
- * \threadsafety The same destination surface should not be used from two
- *               threads at once. It is safe to use the same source surface
- *               from multiple threads.
+ * \threadsafety Only one thread should be using the `src` and `dst` surfaces
+ *               at any given time.
  *
  *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
@@ -1172,9 +1268,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_BlitSurfaceScaled(SDL_Surface *src, const S
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
- * \threadsafety The same destination surface should not be used from two
- *               threads at once. It is safe to use the same source surface
- *               from multiple threads.
+ * \threadsafety Only one thread should be using the `src` and `dst` surfaces
+ *               at any given time.
  *
  *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
@@ -1195,9 +1290,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
- * \threadsafety The same destination surface should not be used from two
- *               threads at once. It is safe to use the same source surface
- *               from multiple threads.
+ * \threadsafety Only one thread should be using the `src` and `dst` surfaces
+ *               at any given time.
  *
  *
  * \since This function is available since SDL 3.4.0.
  * \since This function is available since SDL 3.4.0.
  *
  *
@@ -1221,9 +1315,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_StretchSurface(SDL_Surface *src, const SDL_
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
- * \threadsafety The same destination surface should not be used from two
- *               threads at once. It is safe to use the same source surface
- *               from multiple threads.
+ * \threadsafety Only one thread should be using the `src` and `dst` surfaces
+ *               at any given time.
  *
  *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
@@ -1251,9 +1344,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_BlitSurfaceTiled(SDL_Surface *src, const SD
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
- * \threadsafety The same destination surface should not be used from two
- *               threads at once. It is safe to use the same source surface
- *               from multiple threads.
+ * \threadsafety Only one thread should be using the `src` and `dst` surfaces
+ *               at any given time.
  *
  *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
@@ -1288,9 +1380,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_BlitSurfaceTiledWithScale(SDL_Surface *src,
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
- * \threadsafety The same destination surface should not be used from two
- *               threads at once. It is safe to use the same source surface
- *               from multiple threads.
+ * \threadsafety Only one thread should be using the `src` and `dst` surfaces
+ *               at any given time.
  *
  *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
@@ -1322,6 +1413,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_BlitSurface9Grid(SDL_Surface *src, const SD
  * \param b the blue component of the pixel in the range 0-255.
  * \param b the blue component of the pixel in the range 0-255.
  * \returns a pixel value.
  * \returns a pixel value.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_MapSurfaceRGBA
  * \sa SDL_MapSurfaceRGBA
@@ -1353,6 +1446,8 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_MapSurfaceRGB(SDL_Surface *surface, Uint8
  * \param a the alpha component of the pixel in the range 0-255.
  * \param a the alpha component of the pixel in the range 0-255.
  * \returns a pixel value.
  * \returns a pixel value.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  *
  *
  * \sa SDL_MapSurfaceRGB
  * \sa SDL_MapSurfaceRGB
@@ -1382,6 +1477,8 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_MapSurfaceRGBA(SDL_Surface *surface, Uint
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  */
  */
 extern SDL_DECLSPEC bool SDLCALL SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a);
 extern SDL_DECLSPEC bool SDLCALL SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a);
@@ -1406,6 +1503,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadSurfacePixel(SDL_Surface *surface, int
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  */
  */
 extern SDL_DECLSPEC bool SDLCALL SDL_ReadSurfacePixelFloat(SDL_Surface *surface, int x, int y, float *r, float *g, float *b, float *a);
 extern SDL_DECLSPEC bool SDLCALL SDL_ReadSurfacePixelFloat(SDL_Surface *surface, int x, int y, float *r, float *g, float *b, float *a);
@@ -1429,6 +1528,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadSurfacePixelFloat(SDL_Surface *surface,
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  */
  */
 extern SDL_DECLSPEC bool SDLCALL SDL_WriteSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 r, Uint8 g, Uint8 b, Uint8 a);
 extern SDL_DECLSPEC bool SDLCALL SDL_WriteSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 r, Uint8 g, Uint8 b, Uint8 a);
@@ -1449,6 +1550,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteSurfacePixel(SDL_Surface *surface, int
  * \returns true on success or false on failure; call SDL_GetError() for more
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *          information.
  *
  *
+ * \threadsafety This function is not thread safe.
+ *
  * \since This function is available since SDL 3.2.0.
  * \since This function is available since SDL 3.2.0.
  */
  */
 extern SDL_DECLSPEC bool SDLCALL SDL_WriteSurfacePixelFloat(SDL_Surface *surface, int x, int y, float r, float g, float b, float a);
 extern SDL_DECLSPEC bool SDLCALL SDL_WriteSurfacePixelFloat(SDL_Surface *surface, int x, int y, float r, float g, float b, float a);

+ 1 - 1
libs/SDL3/include/SDL3/SDL_version.h

@@ -62,7 +62,7 @@ extern "C" {
  *
  *
  * \since This macro is available since SDL 3.2.0.
  * \since This macro is available since SDL 3.2.0.
  */
  */
-#define SDL_MICRO_VERSION   6
+#define SDL_MICRO_VERSION   10
 
 
 /**
 /**
  * This macro turns the version numbers into a numeric value.
  * This macro turns the version numbers into a numeric value.

+ 15 - 1
libs/SDL3/src/SDL.c

@@ -356,7 +356,9 @@ bool SDL_InitSubSystem(SDL_InitFlags flags)
             SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO);
             SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO);
             if (!SDL_VideoInit(NULL)) {
             if (!SDL_VideoInit(NULL)) {
                 SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO);
                 SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO);
+                SDL_PushError();
                 SDL_QuitSubSystem(SDL_INIT_EVENTS);
                 SDL_QuitSubSystem(SDL_INIT_EVENTS);
+                SDL_PopError();
                 goto quit_and_error;
                 goto quit_and_error;
             }
             }
         } else {
         } else {
@@ -381,7 +383,9 @@ bool SDL_InitSubSystem(SDL_InitFlags flags)
             SDL_IncrementSubsystemRefCount(SDL_INIT_AUDIO);
             SDL_IncrementSubsystemRefCount(SDL_INIT_AUDIO);
             if (!SDL_InitAudio(NULL)) {
             if (!SDL_InitAudio(NULL)) {
                 SDL_DecrementSubsystemRefCount(SDL_INIT_AUDIO);
                 SDL_DecrementSubsystemRefCount(SDL_INIT_AUDIO);
+                SDL_PushError();
                 SDL_QuitSubSystem(SDL_INIT_EVENTS);
                 SDL_QuitSubSystem(SDL_INIT_EVENTS);
+                SDL_PopError();
                 goto quit_and_error;
                 goto quit_and_error;
             }
             }
         } else {
         } else {
@@ -406,7 +410,9 @@ bool SDL_InitSubSystem(SDL_InitFlags flags)
             SDL_IncrementSubsystemRefCount(SDL_INIT_JOYSTICK);
             SDL_IncrementSubsystemRefCount(SDL_INIT_JOYSTICK);
             if (!SDL_InitJoysticks()) {
             if (!SDL_InitJoysticks()) {
                 SDL_DecrementSubsystemRefCount(SDL_INIT_JOYSTICK);
                 SDL_DecrementSubsystemRefCount(SDL_INIT_JOYSTICK);
+                SDL_PushError();
                 SDL_QuitSubSystem(SDL_INIT_EVENTS);
                 SDL_QuitSubSystem(SDL_INIT_EVENTS);
+                SDL_PopError();
                 goto quit_and_error;
                 goto quit_and_error;
             }
             }
         } else {
         } else {
@@ -430,7 +436,9 @@ bool SDL_InitSubSystem(SDL_InitFlags flags)
             SDL_IncrementSubsystemRefCount(SDL_INIT_GAMEPAD);
             SDL_IncrementSubsystemRefCount(SDL_INIT_GAMEPAD);
             if (!SDL_InitGamepads()) {
             if (!SDL_InitGamepads()) {
                 SDL_DecrementSubsystemRefCount(SDL_INIT_GAMEPAD);
                 SDL_DecrementSubsystemRefCount(SDL_INIT_GAMEPAD);
+                SDL_PushError();
                 SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
                 SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+                SDL_PopError();
                 goto quit_and_error;
                 goto quit_and_error;
             }
             }
         } else {
         } else {
@@ -493,7 +501,9 @@ bool SDL_InitSubSystem(SDL_InitFlags flags)
             SDL_IncrementSubsystemRefCount(SDL_INIT_CAMERA);
             SDL_IncrementSubsystemRefCount(SDL_INIT_CAMERA);
             if (!SDL_CameraInit(NULL)) {
             if (!SDL_CameraInit(NULL)) {
                 SDL_DecrementSubsystemRefCount(SDL_INIT_CAMERA);
                 SDL_DecrementSubsystemRefCount(SDL_INIT_CAMERA);
+                SDL_PushError();
                 SDL_QuitSubSystem(SDL_INIT_EVENTS);
                 SDL_QuitSubSystem(SDL_INIT_EVENTS);
+                SDL_PopError();
                 goto quit_and_error;
                 goto quit_and_error;
             }
             }
         } else {
         } else {
@@ -511,7 +521,11 @@ bool SDL_InitSubSystem(SDL_InitFlags flags)
     return SDL_ClearError();
     return SDL_ClearError();
 
 
 quit_and_error:
 quit_and_error:
-    SDL_QuitSubSystem(flags_initialized);
+    {
+        SDL_PushError();
+        SDL_QuitSubSystem(flags_initialized);
+        SDL_PopError();
+    }
     return false;
     return false;
 }
 }
 
 

+ 16 - 18
libs/SDL3/src/SDL_assert.c

@@ -34,15 +34,7 @@
 #endif
 #endif
 
 
 #ifdef SDL_PLATFORM_EMSCRIPTEN
 #ifdef SDL_PLATFORM_EMSCRIPTEN
-    #include <emscripten.h>
-    // older Emscriptens don't have this, but we need to for wasm64 compatibility.
-    #ifndef MAIN_THREAD_EM_ASM_PTR
-        #ifdef __wasm64__
-            #error You need to upgrade your Emscripten compiler to support wasm64
-        #else
-            #define MAIN_THREAD_EM_ASM_PTR MAIN_THREAD_EM_ASM_INT
-        #endif
-    #endif
+#include <emscripten.h>
 #endif
 #endif
 
 
 // The size of the stack buffer to use for rendering assert messages.
 // The size of the stack buffer to use for rendering assert messages.
@@ -252,7 +244,7 @@ static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, v
         for (;;) {
         for (;;) {
             bool okay = true;
             bool okay = true;
             /* *INDENT-OFF* */ // clang-format off
             /* *INDENT-OFF* */ // clang-format off
-            char *buf = (char *) MAIN_THREAD_EM_ASM_PTR({
+            int reply = MAIN_THREAD_EM_ASM_INT({
                 var str =
                 var str =
                     UTF8ToString($0) + '\n\n' +
                     UTF8ToString($0) + '\n\n' +
                     'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :';
                     'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :';
@@ -260,26 +252,32 @@ static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, v
                 if (reply === null) {
                 if (reply === null) {
                     reply = "i";
                     reply = "i";
                 }
                 }
-                return allocate(intArrayFromString(reply), 'i8', ALLOC_NORMAL);
+                return reply.length === 1 ? reply.charCodeAt(0) : -1;
             }, message);
             }, message);
             /* *INDENT-ON* */ // clang-format on
             /* *INDENT-ON* */ // clang-format on
 
 
-            if (SDL_strcmp(buf, "a") == 0) {
+            switch (reply) {
+            case 'a':
                 state = SDL_ASSERTION_ABORT;
                 state = SDL_ASSERTION_ABORT;
+                break;
 #if 0 // (currently) no break functionality on Emscripten
 #if 0 // (currently) no break functionality on Emscripten
-            } else if (SDL_strcmp(buf, "b") == 0) {
+            case 'b':
                 state = SDL_ASSERTION_BREAK;
                 state = SDL_ASSERTION_BREAK;
+                break;
 #endif
 #endif
-            } else if (SDL_strcmp(buf, "r") == 0) {
+            case 'r':
                 state = SDL_ASSERTION_RETRY;
                 state = SDL_ASSERTION_RETRY;
-            } else if (SDL_strcmp(buf, "i") == 0) {
+                break;
+            case 'i':
                 state = SDL_ASSERTION_IGNORE;
                 state = SDL_ASSERTION_IGNORE;
-            } else if (SDL_strcmp(buf, "A") == 0) {
+                break;
+            case 'A':
                 state = SDL_ASSERTION_ALWAYS_IGNORE;
                 state = SDL_ASSERTION_ALWAYS_IGNORE;
-            } else {
+                break;
+            default:
                 okay = false;
                 okay = false;
+                break;
             }
             }
-            free(buf);  // This should NOT be SDL_free()
 
 
             if (okay) {
             if (okay) {
                 break;
                 break;

+ 12 - 0
libs/SDL3/src/SDL_error_c.h

@@ -46,4 +46,16 @@ typedef struct SDL_error
 // Defined in SDL_thread.c
 // Defined in SDL_thread.c
 extern SDL_error *SDL_GetErrBuf(bool create);
 extern SDL_error *SDL_GetErrBuf(bool create);
 
 
+// Macros to save and restore error values
+#define SDL_PushError() \
+    char *saved_error = SDL_strdup(SDL_GetError())
+
+#define SDL_PopError()                          \
+    do {                                        \
+        if (saved_error) {                      \
+            SDL_SetError("%s", saved_error);    \
+            SDL_free(saved_error);              \
+        }                                       \
+    } while (0)
+
 #endif // SDL_error_c_h_
 #endif // SDL_error_c_h_

+ 2 - 2
libs/SDL3/src/audio/SDL_audio.c

@@ -1256,11 +1256,11 @@ static int SDLCALL PlaybackAudioThread(void *devicep)  // thread entry point
     SDL_assert(!device->recording);
     SDL_assert(!device->recording);
     SDL_PlaybackAudioThreadSetup(device);
     SDL_PlaybackAudioThreadSetup(device);
 
 
-    do {
+    while (SDL_PlaybackAudioThreadIterate(device)) {
         if (!device->WaitDevice(device)) {
         if (!device->WaitDevice(device)) {
             SDL_AudioDeviceDisconnected(device);  // doh. (but don't break out of the loop, just be a zombie for now!)
             SDL_AudioDeviceDisconnected(device);  // doh. (but don't break out of the loop, just be a zombie for now!)
         }
         }
-    } while (SDL_PlaybackAudioThreadIterate(device));
+    }
 
 
     SDL_PlaybackAudioThreadShutdown(device);
     SDL_PlaybackAudioThreadShutdown(device);
     return 0;
     return 0;

+ 57 - 0
libs/SDL3/src/audio/SDL_audiotypecvt.c

@@ -22,6 +22,10 @@
 
 
 #include "SDL_sysaudio.h"
 #include "SDL_sysaudio.h"
 
 
+#ifdef SDL_NEON_INTRINSICS
+#include <fenv.h>
+#endif
+
 #define DIVBY2147483648 0.0000000004656612873077392578125f // 0x1p-31f
 #define DIVBY2147483648 0.0000000004656612873077392578125f // 0x1p-31f
 
 
 // start fallback scalar converters
 // start fallback scalar converters
@@ -527,9 +531,27 @@ static void SDL_TARGETING("ssse3") SDL_Convert_Swap32_SSSE3(Uint32* dst, const U
 #endif
 #endif
 
 
 #ifdef SDL_NEON_INTRINSICS
 #ifdef SDL_NEON_INTRINSICS
+
+// C99 requires that all code modifying floating point environment should
+// be guarded by the STDC FENV_ACCESS pragma; otherwise, it's undefined
+// behavior. However, the compiler support for this pragma is bad.
+#if defined(__clang__)
+#if __clang_major__ >= 12
+#pragma STDC FENV_ACCESS ON
+#endif
+#elif defined(_MSC_VER)
+#pragma fenv_access (on)
+#elif defined(__GNUC__)
+// GCC does not support the pragma at all
+#else
+#pragma STDC FENV_ACCESS ON
+#endif
+
 static void SDL_Convert_S8_to_F32_NEON(float *dst, const Sint8 *src, int num_samples)
 static void SDL_Convert_S8_to_F32_NEON(float *dst, const Sint8 *src, int num_samples)
 {
 {
     LOG_DEBUG_AUDIO_CONVERT("S8", "F32 (using NEON)");
     LOG_DEBUG_AUDIO_CONVERT("S8", "F32 (using NEON)");
+    fenv_t fenv;
+    feholdexcept(&fenv);
 
 
     CONVERT_16_REV({
     CONVERT_16_REV({
         vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32(src[i]), 7), 0);
         vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32(src[i]), 7), 0);
@@ -549,11 +571,14 @@ static void SDL_Convert_S8_to_F32_NEON(float *dst, const Sint8 *src, int num_sam
         vst1q_f32(&dst[i + 8], floats2);
         vst1q_f32(&dst[i + 8], floats2);
         vst1q_f32(&dst[i + 12], floats3);
         vst1q_f32(&dst[i + 12], floats3);
     })
     })
+    fesetenv(&fenv);
 }
 }
 
 
 static void SDL_Convert_U8_to_F32_NEON(float *dst, const Uint8 *src, int num_samples)
 static void SDL_Convert_U8_to_F32_NEON(float *dst, const Uint8 *src, int num_samples)
 {
 {
     LOG_DEBUG_AUDIO_CONVERT("U8", "F32 (using NEON)");
     LOG_DEBUG_AUDIO_CONVERT("U8", "F32 (using NEON)");
+    fenv_t fenv;
+    feholdexcept(&fenv);
 
 
     uint8x16_t flipper = vdupq_n_u8(0x80);
     uint8x16_t flipper = vdupq_n_u8(0x80);
 
 
@@ -575,11 +600,14 @@ static void SDL_Convert_U8_to_F32_NEON(float *dst, const Uint8 *src, int num_sam
         vst1q_f32(&dst[i + 8], floats2);
         vst1q_f32(&dst[i + 8], floats2);
         vst1q_f32(&dst[i + 12], floats3);
         vst1q_f32(&dst[i + 12], floats3);
     })
     })
+    fesetenv(&fenv);
 }
 }
 
 
 static void SDL_Convert_S16_to_F32_NEON(float *dst, const Sint16 *src, int num_samples)
 static void SDL_Convert_S16_to_F32_NEON(float *dst, const Sint16 *src, int num_samples)
 {
 {
     LOG_DEBUG_AUDIO_CONVERT("S16", "F32 (using NEON)");
     LOG_DEBUG_AUDIO_CONVERT("S16", "F32 (using NEON)");
+    fenv_t fenv;
+    feholdexcept(&fenv);
 
 
     CONVERT_16_REV({
     CONVERT_16_REV({
         vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32(src[i]), 15), 0);
         vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32(src[i]), 15), 0);
@@ -597,11 +625,14 @@ static void SDL_Convert_S16_to_F32_NEON(float *dst, const Sint16 *src, int num_s
         vst1q_f32(&dst[i + 8], floats2);
         vst1q_f32(&dst[i + 8], floats2);
         vst1q_f32(&dst[i + 12], floats3);
         vst1q_f32(&dst[i + 12], floats3);
     })
     })
+    fesetenv(&fenv);
 }
 }
 
 
 static void SDL_Convert_S32_to_F32_NEON(float *dst, const Sint32 *src, int num_samples)
 static void SDL_Convert_S32_to_F32_NEON(float *dst, const Sint32 *src, int num_samples)
 {
 {
     LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using NEON)");
     LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using NEON)");
+    fenv_t fenv;
+    feholdexcept(&fenv);
 
 
     CONVERT_16_FWD({
     CONVERT_16_FWD({
         vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vld1_dup_s32(&src[i]), 31), 0);
         vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vld1_dup_s32(&src[i]), 31), 0);
@@ -621,11 +652,14 @@ static void SDL_Convert_S32_to_F32_NEON(float *dst, const Sint32 *src, int num_s
         vst1q_f32(&dst[i + 8], floats2);
         vst1q_f32(&dst[i + 8], floats2);
         vst1q_f32(&dst[i + 12], floats3);
         vst1q_f32(&dst[i + 12], floats3);
     })
     })
+    fesetenv(&fenv);
 }
 }
 
 
 static void SDL_Convert_F32_to_S8_NEON(Sint8 *dst, const float *src, int num_samples)
 static void SDL_Convert_F32_to_S8_NEON(Sint8 *dst, const float *src, int num_samples)
 {
 {
     LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using NEON)");
     LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using NEON)");
+    fenv_t fenv;
+    feholdexcept(&fenv);
 
 
     CONVERT_16_FWD({
     CONVERT_16_FWD({
         vst1_lane_s8(&dst[i], vreinterpret_s8_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)), 3);
         vst1_lane_s8(&dst[i], vreinterpret_s8_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)), 3);
@@ -647,11 +681,14 @@ static void SDL_Convert_F32_to_S8_NEON(Sint8 *dst, const float *src, int num_sam
 
 
         vst1q_s8(&dst[i], bytes);
         vst1q_s8(&dst[i], bytes);
     })
     })
+    fesetenv(&fenv);
 }
 }
 
 
 static void SDL_Convert_F32_to_U8_NEON(Uint8 *dst, const float *src, int num_samples)
 static void SDL_Convert_F32_to_U8_NEON(Uint8 *dst, const float *src, int num_samples)
 {
 {
     LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using NEON)");
     LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using NEON)");
+    fenv_t fenv;
+    feholdexcept(&fenv);
 
 
     uint8x16_t flipper = vdupq_n_u8(0x80);
     uint8x16_t flipper = vdupq_n_u8(0x80);
 
 
@@ -679,11 +716,14 @@ static void SDL_Convert_F32_to_U8_NEON(Uint8 *dst, const float *src, int num_sam
 
 
         vst1q_u8(&dst[i], bytes);
         vst1q_u8(&dst[i], bytes);
     })
     })
+    fesetenv(&fenv);
 }
 }
 
 
 static void SDL_Convert_F32_to_S16_NEON(Sint16 *dst, const float *src, int num_samples)
 static void SDL_Convert_F32_to_S16_NEON(Sint16 *dst, const float *src, int num_samples)
 {
 {
     LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using NEON)");
     LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using NEON)");
+    fenv_t fenv;
+    feholdexcept(&fenv);
 
 
     CONVERT_16_FWD({
     CONVERT_16_FWD({
         vst1_lane_s16(&dst[i], vreinterpret_s16_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)), 1);
         vst1_lane_s16(&dst[i], vreinterpret_s16_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)), 1);
@@ -704,11 +744,14 @@ static void SDL_Convert_F32_to_S16_NEON(Sint16 *dst, const float *src, int num_s
         vst1q_s16(&dst[i], shorts0);
         vst1q_s16(&dst[i], shorts0);
         vst1q_s16(&dst[i + 8], shorts1);
         vst1q_s16(&dst[i + 8], shorts1);
     })
     })
+    fesetenv(&fenv);
 }
 }
 
 
 static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_samples)
 static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_samples)
 {
 {
     LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using NEON)");
     LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using NEON)");
+    fenv_t fenv;
+    feholdexcept(&fenv);
 
 
     CONVERT_16_FWD({
     CONVERT_16_FWD({
         vst1_lane_s32(&dst[i], vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31), 0);
         vst1_lane_s32(&dst[i], vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31), 0);
@@ -728,6 +771,7 @@ static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_s
         vst1q_s32(&dst[i + 8], ints2);
         vst1q_s32(&dst[i + 8], ints2);
         vst1q_s32(&dst[i + 12], ints3);
         vst1q_s32(&dst[i + 12], ints3);
     })
     })
+    fesetenv(&fenv);
 }
 }
 
 
 static void SDL_Convert_Swap16_NEON(Uint16* dst, const Uint16* src, int num_samples)
 static void SDL_Convert_Swap16_NEON(Uint16* dst, const Uint16* src, int num_samples)
@@ -767,6 +811,19 @@ static void SDL_Convert_Swap32_NEON(Uint32* dst, const Uint32* src, int num_samp
         vst1q_u8((Uint8*)&dst[i + 12], ints3);
         vst1q_u8((Uint8*)&dst[i + 12], ints3);
     })
     })
 }
 }
+
+#if defined(__clang__)
+#if __clang_major__ >= 12
+#pragma STDC FENV_ACCESS DEFAULT
+#endif
+#elif defined(_MSC_VER)
+#pragma fenv_access (off)
+#elif defined(__GNUC__)
+//
+#else
+#pragma STDC FENV_ACCESS DEFAULT
+#endif
+
 #endif
 #endif
 
 
 #undef CONVERT_16_FWD
 #undef CONVERT_16_FWD

+ 8 - 1
libs/SDL3/src/audio/alsa/SDL_alsa_audio.c

@@ -1187,7 +1187,6 @@ static bool ALSA_OpenDevice(SDL_AudioDevice *device)
         ALSA_snd_pcm_nonblock(cfg_ctx.device->hidden->pcm, 0);
         ALSA_snd_pcm_nonblock(cfg_ctx.device->hidden->pcm, 0);
     }
     }
 #endif
 #endif
-    ALSA_snd_pcm_start(cfg_ctx.device->hidden->pcm);
     return true;  // We're ready to rock and roll. :-)
     return true;  // We're ready to rock and roll. :-)
 
 
 err_cleanup_ctx:
 err_cleanup_ctx:
@@ -1200,6 +1199,13 @@ err_free_device_hidden:
     return false;
     return false;
 }
 }
 
 
+static void ALSA_ThreadInit(SDL_AudioDevice *device)
+{
+    SDL_SetCurrentThreadPriority(device->recording ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
+    // do snd_pcm_start as close to the first time we PlayDevice as possible to prevent an underrun at startup.
+    ALSA_snd_pcm_start(device->hidden->pcm);
+}
+
 static ALSA_Device *hotplug_devices = NULL;
 static ALSA_Device *hotplug_devices = NULL;
 
 
 static int hotplug_device_process(snd_ctl_t *ctl, snd_ctl_card_info_t *ctl_card_info, int dev_idx,
 static int hotplug_device_process(snd_ctl_t *ctl, snd_ctl_card_info_t *ctl_card_info, int dev_idx,
@@ -1497,6 +1503,7 @@ static bool ALSA_Init(SDL_AudioDriverImpl *impl)
 
 
     impl->DetectDevices = ALSA_DetectDevices;
     impl->DetectDevices = ALSA_DetectDevices;
     impl->OpenDevice = ALSA_OpenDevice;
     impl->OpenDevice = ALSA_OpenDevice;
+    impl->ThreadInit = ALSA_ThreadInit;
     impl->WaitDevice = ALSA_WaitDevice;
     impl->WaitDevice = ALSA_WaitDevice;
     impl->GetDeviceBuf = ALSA_GetDeviceBuf;
     impl->GetDeviceBuf = ALSA_GetDeviceBuf;
     impl->PlayDevice = ALSA_PlayDevice;
     impl->PlayDevice = ALSA_PlayDevice;

+ 5 - 1
libs/SDL3/src/audio/pipewire/SDL_pipewire.c

@@ -1187,7 +1187,11 @@ static bool PIPEWIRE_OpenDevice(SDL_AudioDevice *device)
     PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%i", device->sample_frames, device->spec.freq);
     PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%i", device->sample_frames, device->spec.freq);
     PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", device->spec.freq);
     PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", device->spec.freq);
     PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true");
     PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true");
-    PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_DONT_RECONNECT, "true");  // Requesting a specific device, don't migrate to new default hardware.
+
+    // UPDATE: This prevents users from moving the audio to a new sink (device) using standard tools. This is slightly in conflict
+    //  with how SDL wants to manage audio devices, but if people want to do it, we should let them, so this is commented out
+    //  for now. We might revisit later.
+    //PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_DONT_RECONNECT, "true");  // Requesting a specific device, don't migrate to new default hardware.
 
 
     if (node_id != PW_ID_ANY) {
     if (node_id != PW_ID_ANY) {
         PIPEWIRE_pw_thread_loop_lock(hotplug_loop);
         PIPEWIRE_pw_thread_loop_lock(hotplug_loop);

+ 5 - 2
libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c

@@ -409,7 +409,7 @@ static bool PULSEAUDIO_WaitDevice(SDL_AudioDevice *device)
     struct SDL_PrivateAudioData *h = device->hidden;
     struct SDL_PrivateAudioData *h = device->hidden;
     bool result = true;
     bool result = true;
 
 
-    //SDL_Log("PULSEAUDIO PLAYDEVICE START! mixlen=%d", available);
+    //SDL_Log("PULSEAUDIO WAITDEVICE START! mixlen=%d", available);
 
 
     PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
     PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
 
 
@@ -701,7 +701,10 @@ static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device)
         PULSEAUDIO_pa_stream_set_state_callback(h->stream, PulseStreamStateChangeCallback, NULL);
         PULSEAUDIO_pa_stream_set_state_callback(h->stream, PulseStreamStateChangeCallback, NULL);
 
 
         // SDL manages device moves if the default changes, so don't ever let Pulse automatically migrate this stream.
         // SDL manages device moves if the default changes, so don't ever let Pulse automatically migrate this stream.
-        flags |= PA_STREAM_DONT_MOVE;
+        // UPDATE: This prevents users from moving the audio to a new sink (device) using standard tools. This is slightly in conflict
+        //  with how SDL wants to manage audio devices, but if people want to do it, we should let them, so this is commented out
+        //  for now. We might revisit later.
+        //flags |= PA_STREAM_DONT_MOVE;
 
 
         const char *device_path = ((PulseDeviceHandle *) device->handle)->device_path;
         const char *device_path = ((PulseDeviceHandle *) device->handle)->device_path;
         if (recording) {
         if (recording) {

+ 8 - 0
libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c

@@ -58,7 +58,9 @@ static bool pipewire_initialized = false;
 
 
 // Pipewire entry points
 // Pipewire entry points
 static const char *(*PIPEWIRE_pw_get_library_version)(void);
 static const char *(*PIPEWIRE_pw_get_library_version)(void);
+#if PW_CHECK_VERSION(0, 3, 75)
 static bool (*PIPEWIRE_pw_check_library_version)(int major, int minor, int micro);
 static bool (*PIPEWIRE_pw_check_library_version)(int major, int minor, int micro);
+#endif
 static void (*PIPEWIRE_pw_init)(int *, char ***);
 static void (*PIPEWIRE_pw_init)(int *, char ***);
 static void (*PIPEWIRE_pw_deinit)(void);
 static void (*PIPEWIRE_pw_deinit)(void);
 static struct pw_main_loop *(*PIPEWIRE_pw_main_loop_new)(const struct spa_dict *loop);
 static struct pw_main_loop *(*PIPEWIRE_pw_main_loop_new)(const struct spa_dict *loop);
@@ -151,7 +153,9 @@ static void unload_pipewire_library(void)
 static bool load_pipewire_syms(void)
 static bool load_pipewire_syms(void)
 {
 {
     SDL_PIPEWIRE_SYM(pw_get_library_version);
     SDL_PIPEWIRE_SYM(pw_get_library_version);
+#if PW_CHECK_VERSION(0, 3, 75)
     SDL_PIPEWIRE_SYM(pw_check_library_version);
     SDL_PIPEWIRE_SYM(pw_check_library_version);
+#endif
     SDL_PIPEWIRE_SYM(pw_init);
     SDL_PIPEWIRE_SYM(pw_init);
     SDL_PIPEWIRE_SYM(pw_deinit);
     SDL_PIPEWIRE_SYM(pw_deinit);
     SDL_PIPEWIRE_SYM(pw_main_loop_new);
     SDL_PIPEWIRE_SYM(pw_main_loop_new);
@@ -1024,7 +1028,11 @@ static bool hotplug_loop_init(void)
 
 
     spa_list_init(&hotplug.global_list);
     spa_list_init(&hotplug.global_list);
 
 
+#if PW_CHECK_VERSION(0, 3, 75)
     hotplug.have_1_0_5 = PIPEWIRE_pw_check_library_version(1,0,5);
     hotplug.have_1_0_5 = PIPEWIRE_pw_check_library_version(1,0,5);
+#else
+    hotplug.have_1_0_5 = false;
+#endif
 
 
     hotplug.loop = PIPEWIRE_pw_thread_loop_new("SDLPwCameraPlug", NULL);
     hotplug.loop = PIPEWIRE_pw_thread_loop_new("SDLPwCameraPlug", NULL);
     if (!hotplug.loop) {
     if (!hotplug.loop) {

+ 3 - 3
libs/SDL3/src/core/windows/SDL_windows.c

@@ -330,10 +330,10 @@ void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect)
     winrect->bottom = sdlrect->y + sdlrect->h - 1;
     winrect->bottom = sdlrect->y + sdlrect->h - 1;
 }
 }
 
 
-BOOL WIN_IsRectEmpty(const RECT *rect)
+bool WIN_WindowRectValid(const RECT *rect)
 {
 {
-    // Calculating this manually because Xbox does not support Win32 IsRectEmpty.
-    return (rect->right <= rect->left) || (rect->bottom <= rect->top);
+    // A window can be resized to zero height, but not zero width
+    return (rect->right > 0);
 }
 }
 
 
 // Some GUIDs we need to know without linking to libraries that aren't available before Vista.
 // Some GUIDs we need to know without linking to libraries that aren't available before Vista.

+ 2 - 2
libs/SDL3/src/core/windows/SDL_windows.h

@@ -156,8 +156,8 @@ extern BOOL WIN_IsEqualIID(REFIID a, REFIID b);
 extern void WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect);
 extern void WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect);
 extern void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect);
 extern void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect);
 
 
-// Returns true if the rect is empty
-extern BOOL WIN_IsRectEmpty(const RECT *rect);
+// Returns false if a window client rect is not valid
+bool WIN_WindowRectValid(const RECT *rect);
 
 
 extern SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat);
 extern SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat);
 
 

+ 4 - 4
libs/SDL3/src/core/windows/version.rc

@@ -9,8 +9,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
 //
 //
 
 
 VS_VERSION_INFO VERSIONINFO
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 3,2,6,0
- PRODUCTVERSION 3,2,6,0
+ FILEVERSION 3,2,10,0
+ PRODUCTVERSION 3,2,10,0
  FILEFLAGSMASK 0x3fL
  FILEFLAGSMASK 0x3fL
  FILEFLAGS 0x0L
  FILEFLAGS 0x0L
  FILEOS 0x40004L
  FILEOS 0x40004L
@@ -23,12 +23,12 @@ BEGIN
         BEGIN
         BEGIN
             VALUE "CompanyName", "\0"
             VALUE "CompanyName", "\0"
             VALUE "FileDescription", "SDL\0"
             VALUE "FileDescription", "SDL\0"
-            VALUE "FileVersion", "3, 2, 6, 0\0"
+            VALUE "FileVersion", "3, 2, 10, 0\0"
             VALUE "InternalName", "SDL\0"
             VALUE "InternalName", "SDL\0"
             VALUE "LegalCopyright", "Copyright (C) 2025 Sam Lantinga\0"
             VALUE "LegalCopyright", "Copyright (C) 2025 Sam Lantinga\0"
             VALUE "OriginalFilename", "SDL3.dll\0"
             VALUE "OriginalFilename", "SDL3.dll\0"
             VALUE "ProductName", "Simple DirectMedia Layer\0"
             VALUE "ProductName", "Simple DirectMedia Layer\0"
-            VALUE "ProductVersion", "3, 2, 6, 0\0"
+            VALUE "ProductVersion", "3, 2, 10, 0\0"
         END
         END
     END
     END
     BLOCK "VarFileInfo"
     BLOCK "VarFileInfo"

+ 18 - 1
libs/SDL3/src/cpuinfo/SDL_cpuinfo.c

@@ -85,7 +85,9 @@
 #include <kernel.h>
 #include <kernel.h>
 #include <swis.h>
 #include <swis.h>
 #endif
 #endif
-
+#ifdef SDL_PLATFORM_3DS
+#include <3ds.h>
+#endif
 #ifdef SDL_PLATFORM_PS2
 #ifdef SDL_PLATFORM_PS2
 #include <kernel.h>
 #include <kernel.h>
 #endif
 #endif
@@ -642,6 +644,15 @@ int SDL_GetNumLogicalCPUCores(void)
             GetSystemInfo(&info);
             GetSystemInfo(&info);
             SDL_NumLogicalCPUCores = info.dwNumberOfProcessors;
             SDL_NumLogicalCPUCores = info.dwNumberOfProcessors;
         }
         }
+#endif
+#ifdef SDL_PLATFORM_3DS
+        if (SDL_NumLogicalCPUCores <= 0) {
+            bool isNew3DS = false;
+            APT_CheckNew3DS(&isNew3DS);
+            // 1 core is always dedicated to the OS
+            // Meaning that the New3DS has 3 available core, and the Old3DS only one.
+            SDL_NumLogicalCPUCores = isNew3DS ? 4 : 2;
+        }
 #endif
 #endif
         // There has to be at least 1, right? :)
         // There has to be at least 1, right? :)
         if (SDL_NumLogicalCPUCores <= 0) {
         if (SDL_NumLogicalCPUCores <= 0) {
@@ -1156,6 +1167,12 @@ int SDL_GetSystemRAM(void)
             }
             }
         }
         }
 #endif
 #endif
+#ifdef SDL_PLATFORM_3DS
+        if (SDL_SystemRAM <= 0) {
+            // The New3DS has 255MiB, the Old3DS 127MiB
+            SDL_SystemRAM = (int)(osGetMemRegionSize(MEMREGION_ALL) / (1024 * 1024));
+        }
+#endif
 #ifdef SDL_PLATFORM_VITA
 #ifdef SDL_PLATFORM_VITA
         if (SDL_SystemRAM <= 0) {
         if (SDL_SystemRAM <= 0) {
             /* Vita has 512MiB on SoC, that's split into 256MiB(+109MiB in extended memory mode) for app
             /* Vita has 512MiB on SoC, that's split into 256MiB(+109MiB in extended memory mode) for app

+ 6 - 4
libs/SDL3/src/events/SDL_events.c

@@ -110,7 +110,7 @@ typedef struct
 } SDL_DisabledEventBlock;
 } SDL_DisabledEventBlock;
 
 
 static SDL_DisabledEventBlock *SDL_disabled_events[256];
 static SDL_DisabledEventBlock *SDL_disabled_events[256];
-static Uint32 SDL_userevents = SDL_EVENT_USER;
+static SDL_AtomicInt SDL_userevents;
 
 
 typedef struct SDL_TemporaryMemory
 typedef struct SDL_TemporaryMemory
 {
 {
@@ -1893,9 +1893,11 @@ Uint32 SDL_RegisterEvents(int numevents)
 {
 {
     Uint32 event_base = 0;
     Uint32 event_base = 0;
 
 
-    if ((numevents > 0) && (SDL_userevents + numevents <= SDL_EVENT_LAST)) {
-        event_base = SDL_userevents;
-        SDL_userevents += numevents;
+    if (numevents > 0) {
+        int value = SDL_AddAtomicInt(&SDL_userevents, numevents);
+        if (value >= 0 && value <= (SDL_EVENT_LAST - SDL_EVENT_USER)) {
+            event_base = (Uint32)(SDL_EVENT_USER + value);
+        }
     }
     }
     return event_base;
     return event_base;
 }
 }

+ 3 - 3
libs/SDL3/src/events/SDL_keyboard.c

@@ -747,7 +747,7 @@ void SDL_SendKeyboardText(const char *text)
 {
 {
     SDL_Keyboard *keyboard = &SDL_keyboard;
     SDL_Keyboard *keyboard = &SDL_keyboard;
 
 
-    if (!SDL_TextInputActive(keyboard->focus)) {
+    if (!keyboard->focus || !SDL_TextInputActive(keyboard->focus)) {
         return;
         return;
     }
     }
 
 
@@ -778,7 +778,7 @@ void SDL_SendEditingText(const char *text, int start, int length)
 {
 {
     SDL_Keyboard *keyboard = &SDL_keyboard;
     SDL_Keyboard *keyboard = &SDL_keyboard;
 
 
-    if (!SDL_TextInputActive(keyboard->focus)) {
+    if (!keyboard->focus || !SDL_TextInputActive(keyboard->focus)) {
         return;
         return;
     }
     }
 
 
@@ -838,7 +838,7 @@ void SDL_SendEditingTextCandidates(char **candidates, int num_candidates, int se
 {
 {
     SDL_Keyboard *keyboard = &SDL_keyboard;
     SDL_Keyboard *keyboard = &SDL_keyboard;
 
 
-    if (!SDL_TextInputActive(keyboard->focus)) {
+    if (!keyboard->focus || !SDL_TextInputActive(keyboard->focus)) {
         return;
         return;
     }
     }
 
 

+ 33 - 0
libs/SDL3/src/events/SDL_mouse.c

@@ -234,6 +234,17 @@ static void SDLCALL SDL_MouseRelativeCursorVisibleChanged(void *userdata, const
     SDL_SetCursor(NULL); // Update cursor visibility
     SDL_SetCursor(NULL); // Update cursor visibility
 }
 }
 
 
+static void SDLCALL SDL_MouseIntegerModeChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+{
+    SDL_Mouse *mouse = (SDL_Mouse *)userdata;
+
+    if (hint && *hint) {
+        mouse->integer_mode_flags = (Uint8)SDL_atoi(hint);
+    } else {
+        mouse->integer_mode_flags = 0;
+    }
+}
+
 // Public functions
 // Public functions
 bool SDL_PreInitMouse(void)
 bool SDL_PreInitMouse(void)
 {
 {
@@ -288,6 +299,9 @@ bool SDL_PreInitMouse(void)
     SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE,
     SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE,
                         SDL_MouseRelativeCursorVisibleChanged, mouse);
                         SDL_MouseRelativeCursorVisibleChanged, mouse);
 
 
+    SDL_AddHintCallback("SDL_MOUSE_INTEGER_MODE",
+                        SDL_MouseIntegerModeChanged, mouse);
+
     mouse->was_touch_mouse_events = false; // no touch to mouse movement event pending
     mouse->was_touch_mouse_events = false; // no touch to mouse movement event pending
 
 
     mouse->cursor_shown = true;
     mouse->cursor_shown = true;
@@ -720,12 +734,22 @@ static void SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL
                 y *= mouse->normal_speed_scale;
                 y *= mouse->normal_speed_scale;
             }
             }
         }
         }
+        if (mouse->integer_mode_flags & 1) {
+            // Accumulate the fractional relative motion and only process the integer portion
+            mouse->integer_mode_residual_motion_x = SDL_modff(mouse->integer_mode_residual_motion_x + x, &x);
+            mouse->integer_mode_residual_motion_y = SDL_modff(mouse->integer_mode_residual_motion_y + y, &y);
+        }
         xrel = x;
         xrel = x;
         yrel = y;
         yrel = y;
         x = (mouse->last_x + xrel);
         x = (mouse->last_x + xrel);
         y = (mouse->last_y + yrel);
         y = (mouse->last_y + yrel);
         ConstrainMousePosition(mouse, window, &x, &y);
         ConstrainMousePosition(mouse, window, &x, &y);
     } else {
     } else {
+        if (mouse->integer_mode_flags & 1) {
+            // Discard the fractional component from absolute coordinates
+            x = SDL_truncf(x);
+            y = SDL_truncf(y);
+        }
         ConstrainMousePosition(mouse, window, &x, &y);
         ConstrainMousePosition(mouse, window, &x, &y);
         if (mouse->has_position) {
         if (mouse->has_position) {
             xrel = x - mouse->last_x;
             xrel = x - mouse->last_x;
@@ -998,6 +1022,12 @@ void SDL_SendMouseWheel(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseI
         SDL_SetMouseFocus(window);
         SDL_SetMouseFocus(window);
     }
     }
 
 
+    // Accumulate fractional wheel motion if integer mode is enabled
+    if (mouse->integer_mode_flags & 2) {
+        mouse->integer_mode_residual_scroll_x = SDL_modff(mouse->integer_mode_residual_scroll_x + x, &x);
+        mouse->integer_mode_residual_scroll_y = SDL_modff(mouse->integer_mode_residual_scroll_y + y, &y);
+    }
+
     if (x == 0.0f && y == 0.0f) {
     if (x == 0.0f && y == 0.0f) {
         return;
         return;
     }
     }
@@ -1108,6 +1138,9 @@ void SDL_QuitMouse(void)
     SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE,
     SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE,
                         SDL_MouseRelativeCursorVisibleChanged, mouse);
                         SDL_MouseRelativeCursorVisibleChanged, mouse);
 
 
+    SDL_RemoveHintCallback("SDL_MOUSE_INTEGER_MODE",
+                        SDL_MouseIntegerModeChanged, mouse);
+
     for (int i = SDL_mouse_count; i--; ) {
     for (int i = SDL_mouse_count; i--; ) {
         SDL_RemoveMouse(SDL_mice[i].instance_id, false);
         SDL_RemoveMouse(SDL_mice[i].instance_id, false);
     }
     }

+ 7 - 0
libs/SDL3/src/events/SDL_mouse_c.h

@@ -91,6 +91,13 @@ typedef struct
     void (*ApplySystemScale)(void *internal, Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, float *x, float *y);
     void (*ApplySystemScale)(void *internal, Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, float *x, float *y);
     void *system_scale_data;
     void *system_scale_data;
 
 
+    // integer mode data
+    Uint8 integer_mode_flags; // 1 to enable mouse quantization, 2 to enable wheel quantization
+    float integer_mode_residual_motion_x;
+    float integer_mode_residual_motion_y;
+    float integer_mode_residual_scroll_x;
+    float integer_mode_residual_scroll_y;
+
     // Data common to all mice
     // Data common to all mice
     SDL_Window *focus;
     SDL_Window *focus;
     float x;
     float x;

+ 5 - 2
libs/SDL3/src/gpu/SDL_gpu.c

@@ -2646,8 +2646,11 @@ bool SDL_ClaimWindowForGPUDevice(
 {
 {
     CHECK_DEVICE_MAGIC(device, false);
     CHECK_DEVICE_MAGIC(device, false);
     if (window == NULL) {
     if (window == NULL) {
-        SDL_InvalidParamError("window");
-        return false;
+        return SDL_InvalidParamError("window");
+    }
+
+    if ((window->flags & SDL_WINDOW_TRANSPARENT) != 0) {
+        return SDL_SetError("The GPU API doesn't support transparent windows");
     }
     }
 
 
     return device->ClaimWindow(
     return device->ClaimWindow(

+ 1 - 1
libs/SDL3/src/gpu/metal/SDL_gpu_metal.m

@@ -854,7 +854,7 @@ static MetalLibraryFunction METAL_INTERNAL_CompileShader(
             code,
             code,
             codeSize,
             codeSize,
             dispatch_get_global_queue(0, 0),
             dispatch_get_global_queue(0, 0),
-            ^{ /* do nothing */ });
+            DISPATCH_DATA_DESTRUCTOR_DEFAULT);
         library = [renderer->device newLibraryWithData:data error:&error];
         library = [renderer->device newLibraryWithData:data error:&error];
     } else {
     } else {
         SDL_assert(!"SDL_gpu.c should have already validated this!");
         SDL_assert(!"SDL_gpu.c should have already validated this!");

+ 26 - 11
libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c

@@ -1135,6 +1135,7 @@ struct VulkanRenderer
 
 
     VulkanMemoryAllocator *memoryAllocator;
     VulkanMemoryAllocator *memoryAllocator;
     VkPhysicalDeviceMemoryProperties memoryProperties;
     VkPhysicalDeviceMemoryProperties memoryProperties;
+    bool checkEmptyAllocations;
 
 
     WindowData **claimedWindows;
     WindowData **claimedWindows;
     Uint32 claimedWindowCount;
     Uint32 claimedWindowCount;
@@ -1203,6 +1204,7 @@ struct VulkanRenderer
     SDL_Mutex *submitLock;
     SDL_Mutex *submitLock;
     SDL_Mutex *acquireCommandBufferLock;
     SDL_Mutex *acquireCommandBufferLock;
     SDL_Mutex *acquireUniformBufferLock;
     SDL_Mutex *acquireUniformBufferLock;
+    SDL_Mutex *renderPassFetchLock;
     SDL_Mutex *framebufferFetchLock;
     SDL_Mutex *framebufferFetchLock;
     SDL_Mutex *windowLock;
     SDL_Mutex *windowLock;
 
 
@@ -1577,6 +1579,10 @@ static void VULKAN_INTERNAL_RemoveMemoryUsedRegion(
         usedRegion->offset,
         usedRegion->offset,
         usedRegion->size);
         usedRegion->size);
 
 
+    if (usedRegion->allocation->usedRegionCount == 0) {
+        renderer->checkEmptyAllocations = true;
+    }
+
     SDL_free(usedRegion);
     SDL_free(usedRegion);
 
 
     SDL_UnlockMutex(renderer->allocatorLock);
     SDL_UnlockMutex(renderer->allocatorLock);
@@ -4634,7 +4640,11 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain(
     swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
     swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
     swapchainCreateInfo.queueFamilyIndexCount = 0;
     swapchainCreateInfo.queueFamilyIndexCount = 0;
     swapchainCreateInfo.pQueueFamilyIndices = NULL;
     swapchainCreateInfo.pQueueFamilyIndices = NULL;
+#ifdef SDL_PLATFORM_ANDROID
     swapchainCreateInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
     swapchainCreateInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
+#else
+    swapchainCreateInfo.preTransform = swapchainSupportDetails.capabilities.currentTransform;
+#endif
     swapchainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
     swapchainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
     swapchainCreateInfo.presentMode = SDLToVK_PresentMode[windowData->presentMode];
     swapchainCreateInfo.presentMode = SDLToVK_PresentMode[windowData->presentMode];
     swapchainCreateInfo.clipped = VK_TRUE;
     swapchainCreateInfo.clipped = VK_TRUE;
@@ -4780,7 +4790,7 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain(
             CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false);
             CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false);
         }
         }
 
 
-        renderer->vkCreateSemaphore(
+        vulkanResult = renderer->vkCreateSemaphore(
             renderer->logicalDevice,
             renderer->logicalDevice,
             &semaphoreCreateInfo,
             &semaphoreCreateInfo,
             NULL,
             NULL,
@@ -4934,6 +4944,7 @@ static void VULKAN_DestroyDevice(
     SDL_DestroyMutex(renderer->submitLock);
     SDL_DestroyMutex(renderer->submitLock);
     SDL_DestroyMutex(renderer->acquireCommandBufferLock);
     SDL_DestroyMutex(renderer->acquireCommandBufferLock);
     SDL_DestroyMutex(renderer->acquireUniformBufferLock);
     SDL_DestroyMutex(renderer->acquireUniformBufferLock);
+    SDL_DestroyMutex(renderer->renderPassFetchLock);
     SDL_DestroyMutex(renderer->framebufferFetchLock);
     SDL_DestroyMutex(renderer->framebufferFetchLock);
     SDL_DestroyMutex(renderer->windowLock);
     SDL_DestroyMutex(renderer->windowLock);
 
 
@@ -7081,12 +7092,15 @@ static VkRenderPass VULKAN_INTERNAL_FetchRenderPass(
         key.depthStencilTargetDescription.stencilStoreOp = depthStencilTargetInfo->stencil_store_op;
         key.depthStencilTargetDescription.stencilStoreOp = depthStencilTargetInfo->stencil_store_op;
     }
     }
 
 
+    SDL_LockMutex(renderer->renderPassFetchLock);
+
     bool result = SDL_FindInHashTable(
     bool result = SDL_FindInHashTable(
         renderer->renderPassHashTable,
         renderer->renderPassHashTable,
         (const void *)&key,
         (const void *)&key,
         (const void **)&renderPassWrapper);
         (const void **)&renderPassWrapper);
 
 
     if (result) {
     if (result) {
+        SDL_UnlockMutex(renderer->renderPassFetchLock);
         return renderPassWrapper->handle;
         return renderPassWrapper->handle;
     }
     }
 
 
@@ -7098,6 +7112,7 @@ static VkRenderPass VULKAN_INTERNAL_FetchRenderPass(
         depthStencilTargetInfo);
         depthStencilTargetInfo);
 
 
     if (renderPassHandle == VK_NULL_HANDLE) {
     if (renderPassHandle == VK_NULL_HANDLE) {
+        SDL_UnlockMutex(renderer->renderPassFetchLock);
         return VK_NULL_HANDLE;
         return VK_NULL_HANDLE;
     }
     }
 
 
@@ -7113,6 +7128,8 @@ static VkRenderPass VULKAN_INTERNAL_FetchRenderPass(
         (const void *)allocedKey,
         (const void *)allocedKey,
         (const void *)renderPassWrapper, true);
         (const void *)renderPassWrapper, true);
 
 
+    SDL_UnlockMutex(renderer->renderPassFetchLock);
+
     return renderPassHandle;
     return renderPassHandle;
 }
 }
 
 
@@ -7181,9 +7198,8 @@ static VulkanFramebuffer *VULKAN_INTERNAL_FetchFramebuffer(
         (const void *)&key,
         (const void *)&key,
         (const void **)&vulkanFramebuffer);
         (const void **)&vulkanFramebuffer);
 
 
-    SDL_UnlockMutex(renderer->framebufferFetchLock);
-
     if (findResult) {
     if (findResult) {
+        SDL_UnlockMutex(renderer->framebufferFetchLock);
         return vulkanFramebuffer;
         return vulkanFramebuffer;
     }
     }
 
 
@@ -7251,19 +7267,18 @@ static VulkanFramebuffer *VULKAN_INTERNAL_FetchFramebuffer(
         FramebufferHashTableKey *allocedKey = SDL_malloc(sizeof(FramebufferHashTableKey));
         FramebufferHashTableKey *allocedKey = SDL_malloc(sizeof(FramebufferHashTableKey));
         SDL_memcpy(allocedKey, &key, sizeof(FramebufferHashTableKey));
         SDL_memcpy(allocedKey, &key, sizeof(FramebufferHashTableKey));
 
 
-        SDL_LockMutex(renderer->framebufferFetchLock);
-
         SDL_InsertIntoHashTable(
         SDL_InsertIntoHashTable(
             renderer->framebufferHashTable,
             renderer->framebufferHashTable,
             (const void *)allocedKey,
             (const void *)allocedKey,
             (const void *)vulkanFramebuffer, true);
             (const void *)vulkanFramebuffer, true);
 
 
-        SDL_UnlockMutex(renderer->framebufferFetchLock);
     } else {
     } else {
         SDL_free(vulkanFramebuffer);
         SDL_free(vulkanFramebuffer);
+        SDL_UnlockMutex(renderer->framebufferFetchLock);
         CHECK_VULKAN_ERROR_AND_RETURN(result, vkCreateFramebuffer, NULL);
         CHECK_VULKAN_ERROR_AND_RETURN(result, vkCreateFramebuffer, NULL);
     }
     }
 
 
+    SDL_UnlockMutex(renderer->framebufferFetchLock);
     return vulkanFramebuffer;
     return vulkanFramebuffer;
 }
 }
 
 
@@ -10429,7 +10444,6 @@ static bool VULKAN_Submit(
     VkPipelineStageFlags waitStages[MAX_PRESENT_COUNT];
     VkPipelineStageFlags waitStages[MAX_PRESENT_COUNT];
     Uint32 swapchainImageIndex;
     Uint32 swapchainImageIndex;
     VulkanTextureSubresource *swapchainTextureSubresource;
     VulkanTextureSubresource *swapchainTextureSubresource;
-    Uint8 commandBufferCleaned = 0;
     VulkanMemorySubAllocator *allocator;
     VulkanMemorySubAllocator *allocator;
     bool presenting = false;
     bool presenting = false;
 
 
@@ -10545,12 +10559,10 @@ static bool VULKAN_Submit(
                 renderer,
                 renderer,
                 renderer->submittedCommandBuffers[i],
                 renderer->submittedCommandBuffers[i],
                 false);
                 false);
-
-            commandBufferCleaned = 1;
         }
         }
     }
     }
 
 
-    if (commandBufferCleaned) {
+    if (renderer->checkEmptyAllocations) {
         SDL_LockMutex(renderer->allocatorLock);
         SDL_LockMutex(renderer->allocatorLock);
 
 
         for (Uint32 i = 0; i < VK_MAX_MEMORY_TYPES; i += 1) {
         for (Uint32 i = 0; i < VK_MAX_MEMORY_TYPES; i += 1) {
@@ -10566,6 +10578,8 @@ static bool VULKAN_Submit(
             }
             }
         }
         }
 
 
+        renderer->checkEmptyAllocations = false;
+
         SDL_UnlockMutex(renderer->allocatorLock);
         SDL_UnlockMutex(renderer->allocatorLock);
     }
     }
 
 
@@ -11657,6 +11671,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S
     renderer->submitLock = SDL_CreateMutex();
     renderer->submitLock = SDL_CreateMutex();
     renderer->acquireCommandBufferLock = SDL_CreateMutex();
     renderer->acquireCommandBufferLock = SDL_CreateMutex();
     renderer->acquireUniformBufferLock = SDL_CreateMutex();
     renderer->acquireUniformBufferLock = SDL_CreateMutex();
+    renderer->renderPassFetchLock = SDL_CreateMutex();
     renderer->framebufferFetchLock = SDL_CreateMutex();
     renderer->framebufferFetchLock = SDL_CreateMutex();
     renderer->windowLock = SDL_CreateMutex();
     renderer->windowLock = SDL_CreateMutex();
 
 
@@ -11718,7 +11733,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S
 
 
     renderer->renderPassHashTable = SDL_CreateHashTable(
     renderer->renderPassHashTable = SDL_CreateHashTable(
         0,  // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful.
         0,  // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful.
-        true,  // thread-safe
+        false,  // manually synchronized due to timing
         VULKAN_INTERNAL_RenderPassHashFunction,
         VULKAN_INTERNAL_RenderPassHashFunction,
         VULKAN_INTERNAL_RenderPassHashKeyMatch,
         VULKAN_INTERNAL_RenderPassHashKeyMatch,
         VULKAN_INTERNAL_RenderPassHashDestroy,
         VULKAN_INTERNAL_RenderPassHashDestroy,

+ 3 - 2
libs/SDL3/src/io/SDL_asyncio.c

@@ -309,12 +309,13 @@ bool SDL_LoadFileAsync(const char *file, SDL_AsyncIOQueue *queue, void *userdata
     if (asyncio) {
     if (asyncio) {
         asyncio->oneshot = true;
         asyncio->oneshot = true;
 
 
-        void *ptr = NULL;
+        Uint8 *ptr = NULL;
         const Sint64 flen = SDL_GetAsyncIOSize(asyncio);
         const Sint64 flen = SDL_GetAsyncIOSize(asyncio);
         if (flen >= 0) {
         if (flen >= 0) {
             // !!! FIXME: check if flen > address space, since it'll truncate and we'll just end up with an incomplete buffer or a crash.
             // !!! FIXME: check if flen > address space, since it'll truncate and we'll just end up with an incomplete buffer or a crash.
-            ptr = SDL_malloc((size_t) (flen + 1));  // over-allocate by one so we can add a null-terminator.
+            ptr = (Uint8 *) SDL_malloc((size_t) (flen + 1));  // over-allocate by one so we can add a null-terminator.
             if (ptr) {
             if (ptr) {
+                ptr[flen] = '\0';
                 retval = SDL_ReadAsyncIO(asyncio, ptr, 0, (Uint64) flen, queue, userdata);
                 retval = SDL_ReadAsyncIO(asyncio, ptr, 0, (Uint64) flen, queue, userdata);
                 if (!retval) {
                 if (!retval) {
                     SDL_free(ptr);
                     SDL_free(ptr);

+ 12 - 10
libs/SDL3/src/joystick/SDL_gamepad.c

@@ -405,15 +405,17 @@ static bool SDLCALL SDL_GamepadEventWatcher(void *userdata, SDL_Event *event)
     {
     {
         SDL_AssertJoysticksLocked();
         SDL_AssertJoysticksLocked();
 
 
-        for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {
-            if (gamepad->joystick->instance_id == event->jdevice.which) {
-                SDL_Event deviceevent;
-
-                deviceevent.type = SDL_EVENT_GAMEPAD_UPDATE_COMPLETE;
-                deviceevent.common.timestamp = event->jdevice.timestamp;
-                deviceevent.gdevice.which = event->jdevice.which;
-                SDL_PushEvent(&deviceevent);
-                break;
+        if (SDL_EventEnabled(SDL_EVENT_GAMEPAD_UPDATE_COMPLETE)) {
+            for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {
+                if (gamepad->joystick->instance_id == event->jdevice.which) {
+                    SDL_Event deviceevent;
+
+                    deviceevent.type = SDL_EVENT_GAMEPAD_UPDATE_COMPLETE;
+                    deviceevent.common.timestamp = event->jdevice.timestamp;
+                    deviceevent.gdevice.which = event->jdevice.which;
+                    SDL_PushEvent(&deviceevent);
+                    break;
+                }
             }
             }
         }
         }
     } break;
     } break;
@@ -729,7 +731,7 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
             SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string));
             SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string));
             break;
             break;
         case k_eSwitchDeviceInfoControllerType_SNES:
         case k_eSwitchDeviceInfoControllerType_SNES:
-            SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,", sizeof(mapping_string));
+            SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string));
             break;
             break;
         case k_eSwitchDeviceInfoControllerType_N64:
         case k_eSwitchDeviceInfoControllerType_N64:
             SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b11,", sizeof(mapping_string));
             SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b11,", sizeof(mapping_string));

+ 6 - 0
libs/SDL3/src/joystick/SDL_gamepad_db.h

@@ -641,6 +641,12 @@ static const char *s_GamepadMappings[] = {
     "030000004b120000014d000000010000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,",
     "030000004b120000014d000000010000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,",
     "03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
     "03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
     "03000000790000004318000010010000,Nintendo GameCube Controller,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
     "03000000790000004318000010010000,Nintendo GameCube Controller,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
+    "030000007e0500001920000011810000,Nintendo N64 Controller,crc:d670,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b7,righttrigger:b3,start:b11,x:b4,y:b10,",
+    "050000007e0500001920000001800000,Nintendo N64 Controller,crc:5e1c,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b7,righttrigger:b3,start:b11,x:b4,y:b10,",
+    "030000007e0500001e20000011810000,Nintendo SEGA Genesis Controller,crc:bb22,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,misc1:b3,rightshoulder:b2,righttrigger:b4,start:b5,",
+    "050000007e0500001720000001800000,Nintendo SEGA Genesis Controller,crc:c1bf,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,misc1:b3,rightshoulder:b2,righttrigger:b4,start:b5,",
+    "030000007e0500001720000011810000,Nintendo SNES Controller,crc:f648,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
+    "050000007e0500001720000001800000,Nintendo SNES Controller,crc:dbc0,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
     "050000007e0500000620000001800000,Nintendo Switch Joy-Con (L),a:b16,b:b15,guide:b4,leftshoulder:b6,leftstick:b12,leftx:a1,lefty:a0~,rightshoulder:b8,start:b9,x:b14,y:b17,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
     "050000007e0500000620000001800000,Nintendo Switch Joy-Con (L),a:b16,b:b15,guide:b4,leftshoulder:b6,leftstick:b12,leftx:a1,lefty:a0~,rightshoulder:b8,start:b9,x:b14,y:b17,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
     "060000007e0500000620000000000000,Nintendo Switch Joy-Con (L/R),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
     "060000007e0500000620000000000000,Nintendo Switch Joy-Con (L/R),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
     "060000007e0500000820000000000000,Nintendo Switch Joy-Con (L/R),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
     "060000007e0500000820000000000000,Nintendo Switch Joy-Con (L/R),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",

+ 57 - 0
libs/SDL3/src/joystick/SDL_joystick.c

@@ -427,6 +427,13 @@ static SDL_vidpid_list zero_centered_devices = {
         return result;                                          \
         return result;                                          \
     }
     }
 
 
+#define CHECK_JOYSTICK_VIRTUAL(joystick, result)                \
+    if (!joystick->is_virtual) {                                \
+        SDL_SetError("joystick isn't virtual");                 \
+        SDL_UnlockJoysticks();                                  \
+        return result;                                          \
+    }
+
 bool SDL_JoysticksInitialized(void)
 bool SDL_JoysticksInitialized(void)
 {
 {
     return SDL_joysticks_initialized;
     return SDL_joysticks_initialized;
@@ -1043,6 +1050,27 @@ static void CleanupSensorFusion(SDL_Joystick *joystick)
     }
     }
 }
 }
 
 
+static bool ShouldSwapFaceButtons(const SDL_SteamVirtualGamepadInfo *info)
+{
+    // When "Use Nintendo Button Layout" is enabled under Steam (the default)
+    // it will send button 0 for the A (east) button and button 1 for the
+    // B (south) button. This is done so that games that interpret the
+    // buttons as Xbox input will get button 0 for "A" as they expect.
+    //
+    // However, SDL reports positional buttons, so we need to swap
+    // the buttons so they show up in the correct position. This provides
+    // consistent behavior regardless of whether we're running under Steam,
+    // under the default settings.
+    if (info &&
+        (info->type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO ||
+         info->type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT ||
+         info->type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT ||
+         info->type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR)) {
+        return true;
+    }
+    return false;
+}
+
 /*
 /*
  * Open a joystick for use - the index passed as an argument refers to
  * Open a joystick for use - the index passed as an argument refers to
  * the N'th joystick on the system.  This index is the value which will
  * the N'th joystick on the system.  This index is the value which will
@@ -1094,6 +1122,7 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id)
     joystick->attached = true;
     joystick->attached = true;
     joystick->led_expiration = SDL_GetTicks();
     joystick->led_expiration = SDL_GetTicks();
     joystick->battery_percent = -1;
     joystick->battery_percent = -1;
+    joystick->is_virtual = (driver == &SDL_VIRTUAL_JoystickDriver);
 
 
     if (!driver->Open(joystick, device_index)) {
     if (!driver->Open(joystick, device_index)) {
         SDL_SetObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK, false);
         SDL_SetObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK, false);
@@ -1148,6 +1177,7 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id)
     info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id);
     info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id);
     if (info) {
     if (info) {
         joystick->steam_handle = info->handle;
         joystick->steam_handle = info->handle;
+        joystick->swap_face_buttons = ShouldSwapFaceButtons(info);
     }
     }
 
 
     // Use system gyro and accelerometer if the gamepad doesn't have built-in sensors
     // Use system gyro and accelerometer if the gamepad doesn't have built-in sensors
@@ -1225,6 +1255,7 @@ bool SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value)
     SDL_LockJoysticks();
     SDL_LockJoysticks();
     {
     {
         CHECK_JOYSTICK_MAGIC(joystick, false);
         CHECK_JOYSTICK_MAGIC(joystick, false);
+        CHECK_JOYSTICK_VIRTUAL(joystick, false);
 
 
 #ifdef SDL_JOYSTICK_VIRTUAL
 #ifdef SDL_JOYSTICK_VIRTUAL
         result = SDL_SetJoystickVirtualAxisInner(joystick, axis, value);
         result = SDL_SetJoystickVirtualAxisInner(joystick, axis, value);
@@ -1244,6 +1275,7 @@ bool SDL_SetJoystickVirtualBall(SDL_Joystick *joystick, int ball, Sint16 xrel, S
     SDL_LockJoysticks();
     SDL_LockJoysticks();
     {
     {
         CHECK_JOYSTICK_MAGIC(joystick, false);
         CHECK_JOYSTICK_MAGIC(joystick, false);
+        CHECK_JOYSTICK_VIRTUAL(joystick, false);
 
 
 #ifdef SDL_JOYSTICK_VIRTUAL
 #ifdef SDL_JOYSTICK_VIRTUAL
         result = SDL_SetJoystickVirtualBallInner(joystick, ball, xrel, yrel);
         result = SDL_SetJoystickVirtualBallInner(joystick, ball, xrel, yrel);
@@ -1263,6 +1295,7 @@ bool SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, int button, bool down)
     SDL_LockJoysticks();
     SDL_LockJoysticks();
     {
     {
         CHECK_JOYSTICK_MAGIC(joystick, false);
         CHECK_JOYSTICK_MAGIC(joystick, false);
+        CHECK_JOYSTICK_VIRTUAL(joystick, false);
 
 
 #ifdef SDL_JOYSTICK_VIRTUAL
 #ifdef SDL_JOYSTICK_VIRTUAL
         result = SDL_SetJoystickVirtualButtonInner(joystick, button, down);
         result = SDL_SetJoystickVirtualButtonInner(joystick, button, down);
@@ -1282,6 +1315,7 @@ bool SDL_SetJoystickVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value)
     SDL_LockJoysticks();
     SDL_LockJoysticks();
     {
     {
         CHECK_JOYSTICK_MAGIC(joystick, false);
         CHECK_JOYSTICK_MAGIC(joystick, false);
+        CHECK_JOYSTICK_VIRTUAL(joystick, false);
 
 
 #ifdef SDL_JOYSTICK_VIRTUAL
 #ifdef SDL_JOYSTICK_VIRTUAL
         result = SDL_SetJoystickVirtualHatInner(joystick, hat, value);
         result = SDL_SetJoystickVirtualHatInner(joystick, hat, value);
@@ -1301,6 +1335,7 @@ bool SDL_SetJoystickVirtualTouchpad(SDL_Joystick *joystick, int touchpad, int fi
     SDL_LockJoysticks();
     SDL_LockJoysticks();
     {
     {
         CHECK_JOYSTICK_MAGIC(joystick, false);
         CHECK_JOYSTICK_MAGIC(joystick, false);
+        CHECK_JOYSTICK_VIRTUAL(joystick, false);
 
 
 #ifdef SDL_JOYSTICK_VIRTUAL
 #ifdef SDL_JOYSTICK_VIRTUAL
         result = SDL_SetJoystickVirtualTouchpadInner(joystick, touchpad, finger, down, x, y, pressure);
         result = SDL_SetJoystickVirtualTouchpadInner(joystick, touchpad, finger, down, x, y, pressure);
@@ -1320,6 +1355,7 @@ bool SDL_SendJoystickVirtualSensorData(SDL_Joystick *joystick, SDL_SensorType ty
     SDL_LockJoysticks();
     SDL_LockJoysticks();
     {
     {
         CHECK_JOYSTICK_MAGIC(joystick, false);
         CHECK_JOYSTICK_MAGIC(joystick, false);
+        CHECK_JOYSTICK_VIRTUAL(joystick, false);
 
 
 #ifdef SDL_JOYSTICK_VIRTUAL
 #ifdef SDL_JOYSTICK_VIRTUAL
         result = SDL_SendJoystickVirtualSensorDataInner(joystick, type, sensor_timestamp, data, num_values);
         result = SDL_SendJoystickVirtualSensorDataInner(joystick, type, sensor_timestamp, data, num_values);
@@ -2305,6 +2341,25 @@ void SDL_SendJoystickButton(Uint64 timestamp, SDL_Joystick *joystick, Uint8 butt
         event.type = SDL_EVENT_JOYSTICK_BUTTON_UP;
         event.type = SDL_EVENT_JOYSTICK_BUTTON_UP;
     }
     }
 
 
+    if (joystick->swap_face_buttons) {
+        switch (button) {
+        case 0:
+            button = 1;
+            break;
+        case 1:
+            button = 0;
+            break;
+        case 2:
+            button = 3;
+            break;
+        case 3:
+            button = 2;
+            break;
+        default:
+            break;
+        }
+    }
+
     // Make sure we're not getting garbage or duplicate events
     // Make sure we're not getting garbage or duplicate events
     if (button >= joystick->nbuttons) {
     if (button >= joystick->nbuttons) {
         return;
         return;
@@ -2353,11 +2408,13 @@ static void SendSteamHandleUpdateEvents(void)
         if (info) {
         if (info) {
             if (joystick->steam_handle != info->handle) {
             if (joystick->steam_handle != info->handle) {
                 joystick->steam_handle = info->handle;
                 joystick->steam_handle = info->handle;
+                joystick->swap_face_buttons = ShouldSwapFaceButtons(info);
                 changed = true;
                 changed = true;
             }
             }
         } else {
         } else {
             if (joystick->steam_handle != 0) {
             if (joystick->steam_handle != 0) {
                 joystick->steam_handle = 0;
                 joystick->steam_handle = 0;
+                joystick->swap_face_buttons = false;
                 changed = true;
                 changed = true;
             }
             }
         }
         }

+ 2 - 0
libs/SDL3/src/joystick/SDL_sysjoystick.h

@@ -83,6 +83,8 @@ struct SDL_Joystick
     SDL_GUID guid _guarded;      // Joystick guid
     SDL_GUID guid _guarded;      // Joystick guid
     Uint16 firmware_version _guarded;    // Firmware version, if available
     Uint16 firmware_version _guarded;    // Firmware version, if available
     Uint64 steam_handle _guarded;        // Steam controller API handle
     Uint64 steam_handle _guarded;        // Steam controller API handle
+    bool swap_face_buttons _guarded;     // Whether we should swap face buttons
+    bool is_virtual _guarded;            // Whether this is a virtual joystick
 
 
     int naxes _guarded; // Number of axis controls on the joystick
     int naxes _guarded; // Number of axis controls on the joystick
     SDL_JoystickAxisInfo *axes _guarded;
     SDL_JoystickAxisInfo *axes _guarded;

+ 4 - 5
libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c

@@ -868,10 +868,8 @@ static SDL_HIDAPI_Device *HIDAPI_AddDevice(const struct SDL_hid_device_info *inf
         return NULL;
         return NULL;
     }
     }
     SDL_SetObjectValid(device, SDL_OBJECT_TYPE_HIDAPI_JOYSTICK, true);
     SDL_SetObjectValid(device, SDL_OBJECT_TYPE_HIDAPI_JOYSTICK, true);
-    device->path = SDL_strdup(info->path);
-    if (!device->path) {
-        SDL_free(device);
-        return NULL;
+    if (info->path) {
+        device->path = SDL_strdup(info->path);
     }
     }
     device->seen = true;
     device->seen = true;
     device->vendor_id = info->vendor_id;
     device->vendor_id = info->vendor_id;
@@ -1135,12 +1133,13 @@ check_removed:
                 goto check_removed;
                 goto check_removed;
             } else {
             } else {
                 HIDAPI_DelDevice(device);
                 HIDAPI_DelDevice(device);
+                device = NULL;
 
 
                 // Update the device list again in case this device comes back
                 // Update the device list again in case this device comes back
                 SDL_HIDAPI_change_count = 0;
                 SDL_HIDAPI_change_count = 0;
             }
             }
         }
         }
-        if (device->broken && device->parent) {
+        if (device && device->broken && device->parent) {
             HIDAPI_DelDevice(device->parent);
             HIDAPI_DelDevice(device->parent);
 
 
             // We deleted a different device here, restart the loop
             // We deleted a different device here, restart the loop

+ 11 - 5
libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c

@@ -160,6 +160,8 @@ struct joystick_hwdata
     WindowsGamingInputGamepadState *wgi_slot;
     WindowsGamingInputGamepadState *wgi_slot;
 #endif
 #endif
 
 
+    bool triggers_rumbling;
+
     SDL_RAWINPUT_Device *device;
     SDL_RAWINPUT_Device *device;
 };
 };
 typedef struct joystick_hwdata RAWINPUT_DeviceContext;
 typedef struct joystick_hwdata RAWINPUT_DeviceContext;
@@ -909,9 +911,11 @@ static void RAWINPUT_AddDevice(HANDLE hDevice)
         char *product_string = NULL;
         char *product_string = NULL;
         WCHAR string[128];
         WCHAR string[128];
 
 
+        string[0] = 0;
         if (SDL_HidD_GetManufacturerString(hFile, string, sizeof(string))) {
         if (SDL_HidD_GetManufacturerString(hFile, string, sizeof(string))) {
             manufacturer_string = WIN_StringToUTF8W(string);
             manufacturer_string = WIN_StringToUTF8W(string);
         }
         }
+        string[0] = 0;
         if (SDL_HidD_GetProductString(hFile, string, sizeof(string))) {
         if (SDL_HidD_GetProductString(hFile, string, sizeof(string))) {
             product_string = WIN_StringToUTF8W(string);
             product_string = WIN_StringToUTF8W(string);
         }
         }
@@ -1459,7 +1463,7 @@ static bool RAWINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency
 
 
 #ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
 #ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
     // Prefer XInput over WGI because it allows rumble in the background
     // Prefer XInput over WGI because it allows rumble in the background
-    if (!rumbled && ctx->xinput_correlated) {
+    if (!rumbled && ctx->xinput_correlated && !ctx->triggers_rumbling) {
         XINPUT_VIBRATION XVibration;
         XINPUT_VIBRATION XVibration;
 
 
         if (!XINPUTSETSTATE) {
         if (!XINPUTSETSTATE) {
@@ -1477,11 +1481,12 @@ static bool RAWINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency
 #endif // SDL_JOYSTICK_RAWINPUT_XINPUT
 #endif // SDL_JOYSTICK_RAWINPUT_XINPUT
 
 
 #ifdef SDL_JOYSTICK_RAWINPUT_WGI
 #ifdef SDL_JOYSTICK_RAWINPUT_WGI
+    // Save off the motor state in case trigger rumble is started
+    WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot;
+    HRESULT hr;
+    gamepad_state->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16;
+    gamepad_state->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16;
     if (!rumbled && ctx->wgi_correlated) {
     if (!rumbled && ctx->wgi_correlated) {
-        WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot;
-        HRESULT hr;
-        gamepad_state->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16;
-        gamepad_state->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16;
         hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration);
         hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration);
         if (SUCCEEDED(hr)) {
         if (SUCCEEDED(hr)) {
             rumbled = true;
             rumbled = true;
@@ -1513,6 +1518,7 @@ static bool RAWINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_
         if (!SUCCEEDED(hr)) {
         if (!SUCCEEDED(hr)) {
             return SDL_SetError("Setting vibration failed: 0x%lx", hr);
             return SDL_SetError("Setting vibration failed: 0x%lx", hr);
         }
         }
+        ctx->triggers_rumbling = (left_rumble > 0 || right_rumble > 0);
         return true;
         return true;
     } else {
     } else {
         return SDL_SetError("Controller isn't correlated yet, try hitting a button first");
         return SDL_SetError("Controller isn't correlated yet, try hitting a button first");

+ 88 - 97
libs/SDL3/src/render/SDL_render.c

@@ -213,93 +213,91 @@ static SDL_INLINE void DebugLogRenderCommands(const SDL_RenderCommand *cmd)
     SDL_Log("Render commands to flush:");
     SDL_Log("Render commands to flush:");
     while (cmd) {
     while (cmd) {
         switch (cmd->command) {
         switch (cmd->command) {
-            case SDL_RENDERCMD_NO_OP:
-                SDL_Log(" %u. no-op", i++);
-                break;
-
-            case SDL_RENDERCMD_SETVIEWPORT:
-                SDL_Log(" %u. set viewport (first=%u, rect={(%d, %d), %dx%d})", i++,
-                        (unsigned int) cmd->data.viewport.first,
-                        cmd->data.viewport.rect.x, cmd->data.viewport.rect.y,
-                        cmd->data.viewport.rect.w, cmd->data.viewport.rect.h);
-                break;
-
-            case SDL_RENDERCMD_SETCLIPRECT:
-                SDL_Log(" %u. set cliprect (enabled=%s, rect={(%d, %d), %dx%d})", i++,
-                        cmd->data.cliprect.enabled ? "true" : "false",
-                        cmd->data.cliprect.rect.x, cmd->data.cliprect.rect.y,
-                        cmd->data.cliprect.rect.w, cmd->data.cliprect.rect.h);
-                break;
+        case SDL_RENDERCMD_NO_OP:
+            SDL_Log(" %u. no-op", i++);
+            break;
 
 
-            case SDL_RENDERCMD_SETDRAWCOLOR:
-                SDL_Log(" %u. set draw color (first=%u, r=%d, g=%d, b=%d, a=%d, color_scale=%g)", i++,
-                        (unsigned int) cmd->data.color.first,
-                        (int) cmd->data.color.color.r, (int) cmd->data.color.color.g,
-                        (int) cmd->data.color.color.b, (int) cmd->data.color.color.a, cmd->data.color.color_scale);
-                break;
+        case SDL_RENDERCMD_SETVIEWPORT:
+            SDL_Log(" %u. set viewport (first=%u, rect={(%d, %d), %dx%d})", i++,
+                    (unsigned int)cmd->data.viewport.first,
+                    cmd->data.viewport.rect.x, cmd->data.viewport.rect.y,
+                    cmd->data.viewport.rect.w, cmd->data.viewport.rect.h);
+            break;
 
 
-            case SDL_RENDERCMD_CLEAR:
-                SDL_Log(" %u. clear (first=%u, r=%d, g=%d, b=%d, a=%d, color_scale=%g)", i++,
-                        (unsigned int) cmd->data.color.first,
-                        (int) cmd->data.color.color.r, (int) cmd->data.color.color.g,
-                        (int) cmd->data.color.color.b, (int) cmd->data.color.color.a, cmd->data.color.color_scale);
-                break;
+        case SDL_RENDERCMD_SETCLIPRECT:
+            SDL_Log(" %u. set cliprect (enabled=%s, rect={(%d, %d), %dx%d})", i++,
+                    cmd->data.cliprect.enabled ? "true" : "false",
+                    cmd->data.cliprect.rect.x, cmd->data.cliprect.rect.y,
+                    cmd->data.cliprect.rect.w, cmd->data.cliprect.rect.h);
+            break;
 
 
-            case SDL_RENDERCMD_DRAW_POINTS:
-                SDL_Log(" %u. draw points (first=%u, count=%u, r=%d, g=%d, b=%d, a=%d, blend=%d, color_scale=%g)", i++,
-                        (unsigned int) cmd->data.draw.first,
-                        (unsigned int) cmd->data.draw.count,
-                        (int) cmd->data.draw.color.r, (int) cmd->data.draw.color.g,
-                        (int) cmd->data.draw.color.b, (int) cmd->data.draw.color.a,
-                        (int) cmd->data.draw.blend, cmd->data.draw.color_scale);
-                break;
+        case SDL_RENDERCMD_SETDRAWCOLOR:
+            SDL_Log(" %u. set draw color (first=%u, r=%.2f, g=%.2f, b=%.2f, a=%.2f, color_scale=%g)", i++,
+                    (unsigned int)cmd->data.color.first,
+                    cmd->data.draw.color.r, cmd->data.draw.color.g,
+                    cmd->data.draw.color.b, cmd->data.draw.color.a, cmd->data.color.color_scale);
+            break;
 
 
-            case SDL_RENDERCMD_DRAW_LINES:
-                SDL_Log(" %u. draw lines (first=%u, count=%u, r=%d, g=%d, b=%d, a=%d, blend=%d, color_scale=%g)", i++,
-                        (unsigned int) cmd->data.draw.first,
-                        (unsigned int) cmd->data.draw.count,
-                        (int) cmd->data.draw.color.r, (int) cmd->data.draw.color.g,
-                        (int) cmd->data.draw.color.b, (int) cmd->data.draw.color.a,
-                        (int) cmd->data.draw.blend, cmd->data.draw.color_scale);
-                break;
+        case SDL_RENDERCMD_CLEAR:
+            SDL_Log(" %u. clear (first=%u, r=%.2f, g=%.2f, b=%.2f, a=%.2f, color_scale=%g)", i++,
+                    (unsigned int)cmd->data.color.first,
+                    cmd->data.draw.color.r, cmd->data.draw.color.g,
+                    cmd->data.draw.color.b, cmd->data.draw.color.a, cmd->data.color.color_scale);
+            break;
 
 
-            case SDL_RENDERCMD_FILL_RECTS:
-                SDL_Log(" %u. fill rects (first=%u, count=%u, r=%d, g=%d, b=%d, a=%d, blend=%d, color_scale=%g)", i++,
-                        (unsigned int) cmd->data.draw.first,
-                        (unsigned int) cmd->data.draw.count,
-                        (int) cmd->data.draw.color.r, (int) cmd->data.draw.color.g,
-                        (int) cmd->data.draw.color.b, (int) cmd->data.draw.color.a,
-                        (int) cmd->data.draw.blend, cmd->data.draw.color_scale);
-                break;
+        case SDL_RENDERCMD_DRAW_POINTS:
+            SDL_Log(" %u. draw points (first=%u, count=%u, r=%.2f, g=%.2f, b=%.2f, a=%.2f, blend=%d, color_scale=%g)", i++,
+                    (unsigned int)cmd->data.draw.first,
+                    (unsigned int)cmd->data.draw.count,
+                    cmd->data.draw.color.r, cmd->data.draw.color.g,
+                    cmd->data.draw.color.b, cmd->data.draw.color.a,
+                    (int)cmd->data.draw.blend, cmd->data.draw.color_scale);
+            break;
 
 
-            case SDL_RENDERCMD_COPY:
-                SDL_Log(" %u. copy (first=%u, count=%u, r=%d, g=%d, b=%d, a=%d, blend=%d, color_scale=%g, tex=%p)", i++,
-                        (unsigned int) cmd->data.draw.first,
-                        (unsigned int) cmd->data.draw.count,
-                        (int) cmd->data.draw.color.r, (int) cmd->data.draw.color.g,
-                        (int) cmd->data.draw.color.b, (int) cmd->data.draw.color.a,
-                        (int) cmd->data.draw.blend, cmd->data.draw.color_scale, cmd->data.draw.texture);
-                break;
+        case SDL_RENDERCMD_DRAW_LINES:
+            SDL_Log(" %u. draw lines (first=%u, count=%u, r=%.2f, g=%.2f, b=%.2f, a=%.2f, blend=%d, color_scale=%g)", i++,
+                    (unsigned int)cmd->data.draw.first,
+                    (unsigned int)cmd->data.draw.count,
+                    cmd->data.draw.color.r, cmd->data.draw.color.g,
+                    cmd->data.draw.color.b, cmd->data.draw.color.a,
+                    (int)cmd->data.draw.blend, cmd->data.draw.color_scale);
+            break;
 
 
+        case SDL_RENDERCMD_FILL_RECTS:
+            SDL_Log(" %u. fill rects (first=%u, count=%u, r=%.2f, g=%.2f, b=%.2f, a=%.2f, blend=%d, color_scale=%g)", i++,
+                    (unsigned int)cmd->data.draw.first,
+                    (unsigned int)cmd->data.draw.count,
+                    cmd->data.draw.color.r, cmd->data.draw.color.g,
+                    cmd->data.draw.color.b, cmd->data.draw.color.a,
+                    (int)cmd->data.draw.blend, cmd->data.draw.color_scale);
+            break;
 
 
-            case SDL_RENDERCMD_COPY_EX:
-                SDL_Log(" %u. copyex (first=%u, count=%u, r=%d, g=%d, b=%d, a=%d, blend=%d, color_scale=%g, tex=%p)", i++,
-                        (unsigned int) cmd->data.draw.first,
-                        (unsigned int) cmd->data.draw.count,
-                        (int) cmd->data.draw.color.r, (int) cmd->data.draw.color.g,
-                        (int) cmd->data.draw.color.b, (int) cmd->data.draw.color.a,
-                        (int) cmd->data.draw.blend, cmd->data.draw.color_scale, cmd->data.draw.texture);
-                break;
+        case SDL_RENDERCMD_COPY:
+            SDL_Log(" %u. copy (first=%u, count=%u, r=%.2f, g=%.2f, b=%.2f, a=%.2f, blend=%d, color_scale=%g, tex=%p)", i++,
+                    (unsigned int)cmd->data.draw.first,
+                    (unsigned int)cmd->data.draw.count,
+                    cmd->data.draw.color.r, cmd->data.draw.color.g,
+                    cmd->data.draw.color.b, cmd->data.draw.color.a,
+                    (int)cmd->data.draw.blend, cmd->data.draw.color_scale, cmd->data.draw.texture);
+            break;
 
 
-            case SDL_RENDERCMD_GEOMETRY:
-                SDL_Log(" %u. geometry (first=%u, count=%u, r=%d, g=%d, b=%d, a=%d, blend=%d, color_scale=%g, tex=%p)", i++,
-                        (unsigned int) cmd->data.draw.first,
-                        (unsigned int) cmd->data.draw.count,
-                        (int) cmd->data.draw.color.r, (int) cmd->data.draw.color.g,
-                        (int) cmd->data.draw.color.b, (int) cmd->data.draw.color.a,
-                        (int) cmd->data.draw.blend, cmd->data.draw.color_scale, cmd->data.draw.texture);
-                break;
+        case SDL_RENDERCMD_COPY_EX:
+            SDL_Log(" %u. copyex (first=%u, count=%u, r=%.2f, g=%.2f, b=%.2f, a=%.2f, blend=%d, color_scale=%g, tex=%p)", i++,
+                    (unsigned int)cmd->data.draw.first,
+                    (unsigned int)cmd->data.draw.count,
+                    cmd->data.draw.color.r, cmd->data.draw.color.g,
+                    cmd->data.draw.color.b, cmd->data.draw.color.a,
+                    (int)cmd->data.draw.blend, cmd->data.draw.color_scale, cmd->data.draw.texture);
+            break;
 
 
+        case SDL_RENDERCMD_GEOMETRY:
+            SDL_Log(" %u. geometry (first=%u, count=%u, r=%.2f, g=%.2f, b=%.2f, a=%.2f, blend=%d, color_scale=%g, tex=%p)", i++,
+                    (unsigned int)cmd->data.draw.first,
+                    (unsigned int)cmd->data.draw.count,
+                    cmd->data.draw.color.r, cmd->data.draw.color.g,
+                    cmd->data.draw.color.b, cmd->data.draw.color.a,
+                    (int)cmd->data.draw.blend, cmd->data.draw.color_scale, cmd->data.draw.texture);
+            break;
         }
         }
         cmd = cmd->next;
         cmd = cmd->next;
     }
     }
@@ -462,8 +460,8 @@ static void UpdatePixelClipRect(SDL_Renderer *renderer, SDL_RenderViewState *vie
 {
 {
     const float scale_x = view->current_scale.x;
     const float scale_x = view->current_scale.x;
     const float scale_y = view->current_scale.y;
     const float scale_y = view->current_scale.y;
-    view->pixel_clip_rect.x = (int)SDL_floorf((view->clip_rect.x * scale_x) + view->logical_offset.x);
-    view->pixel_clip_rect.y = (int)SDL_floorf((view->clip_rect.y * scale_y) + view->logical_offset.y);
+    view->pixel_clip_rect.x = (int)SDL_floorf(view->clip_rect.x * scale_x);
+    view->pixel_clip_rect.y = (int)SDL_floorf(view->clip_rect.y * scale_y);
     view->pixel_clip_rect.w = (int)SDL_ceilf(view->clip_rect.w * scale_x);
     view->pixel_clip_rect.w = (int)SDL_ceilf(view->clip_rect.w * scale_x);
     view->pixel_clip_rect.h = (int)SDL_ceilf(view->clip_rect.h * scale_y);
     view->pixel_clip_rect.h = (int)SDL_ceilf(view->clip_rect.h * scale_y);
 }
 }
@@ -573,6 +571,9 @@ static SDL_RenderCommand *PrepQueueCmdDraw(SDL_Renderer *renderer, const SDL_Ren
             cmd->data.draw.color = *color;
             cmd->data.draw.color = *color;
             cmd->data.draw.blend = blendMode;
             cmd->data.draw.blend = blendMode;
             cmd->data.draw.texture = texture;
             cmd->data.draw.texture = texture;
+            if (texture) {
+                cmd->data.draw.texture_scale_mode = texture->scaleMode;
+            }
             cmd->data.draw.texture_address_mode = SDL_TEXTURE_ADDRESS_CLAMP;
             cmd->data.draw.texture_address_mode = SDL_TEXTURE_ADDRESS_CLAMP;
         }
         }
     }
     }
@@ -827,6 +828,10 @@ static bool SDL_RendererEventWatch(void *userdata, SDL_Event *event)
     SDL_Renderer *renderer = (SDL_Renderer *)userdata;
     SDL_Renderer *renderer = (SDL_Renderer *)userdata;
     SDL_Window *window = renderer->window;
     SDL_Window *window = renderer->window;
 
 
+    if (event->window.windowID != SDL_GetWindowID(window)) {
+        return true;
+    }
+
     if (renderer->WindowEvent) {
     if (renderer->WindowEvent) {
         renderer->WindowEvent(renderer, &event->window);
         renderer->WindowEvent(renderer, &event->window);
     }
     }
@@ -1119,12 +1124,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
     }
     }
 
 
     int vsync = (int)SDL_GetNumberProperty(props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 0);
     int vsync = (int)SDL_GetNumberProperty(props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 0);
-    if (!SDL_SetRenderVSync(renderer, vsync)) {
-        if (vsync == 0) {
-            // Some renderers require vsync enabled
-            SDL_SetRenderVSync(renderer, 1);
-        }
-    }
+    SDL_SetRenderVSync(renderer, vsync);
     SDL_CalculateSimulatedVSyncInterval(renderer, window);
     SDL_CalculateSimulatedVSyncInterval(renderer, window);
 
 
     SDL_LogInfo(SDL_LOG_CATEGORY_RENDER,
     SDL_LogInfo(SDL_LOG_CATEGORY_RENDER,
@@ -1962,8 +1962,6 @@ bool SDL_GetTextureBlendMode(SDL_Texture *texture, SDL_BlendMode *blendMode)
 
 
 bool SDL_SetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode scaleMode)
 bool SDL_SetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode scaleMode)
 {
 {
-    SDL_Renderer *renderer;
-
     CHECK_TEXTURE_MAGIC(texture, false);
     CHECK_TEXTURE_MAGIC(texture, false);
 
 
     if (scaleMode != SDL_SCALEMODE_NEAREST &&
     if (scaleMode != SDL_SCALEMODE_NEAREST &&
@@ -1971,12 +1969,10 @@ bool SDL_SetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode scaleMode)
         return SDL_InvalidParamError("scaleMode");
         return SDL_InvalidParamError("scaleMode");
     }
     }
 
 
-    renderer = texture->renderer;
     texture->scaleMode = scaleMode;
     texture->scaleMode = scaleMode;
+
     if (texture->native) {
     if (texture->native) {
         return SDL_SetTextureScaleMode(texture->native, scaleMode);
         return SDL_SetTextureScaleMode(texture->native, scaleMode);
-    } else {
-        renderer->SetTextureScaleMode(renderer, texture, scaleMode);
     }
     }
     return true;
     return true;
 }
 }
@@ -5493,7 +5489,8 @@ bool SDL_SetRenderVSync(SDL_Renderer *renderer, int vsync)
     }
     }
 #endif
 #endif
 
 
-    if (!renderer->SetVSync) {
+    if (!renderer->SetVSync ||
+        !renderer->SetVSync(renderer, vsync)) {
         switch (vsync) {
         switch (vsync) {
         case 0:
         case 0:
             renderer->simulate_vsync = false;
             renderer->simulate_vsync = false;
@@ -5504,12 +5501,6 @@ bool SDL_SetRenderVSync(SDL_Renderer *renderer, int vsync)
         default:
         default:
             return SDL_Unsupported();
             return SDL_Unsupported();
         }
         }
-    } else if (!renderer->SetVSync(renderer, vsync)) {
-        if (vsync == 1) {
-            renderer->simulate_vsync = true;
-        } else {
-            return false;
-        }
     }
     }
     SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_VSYNC_NUMBER, vsync);
     SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_VSYNC_NUMBER, vsync);
     return true;
     return true;

+ 2 - 1
libs/SDL3/src/render/SDL_sysrender.h

@@ -34,6 +34,7 @@ extern "C" {
 
 
 typedef enum SDL_TextureAddressMode
 typedef enum SDL_TextureAddressMode
 {
 {
+    SDL_TEXTURE_ADDRESS_INVALID = -1,
     SDL_TEXTURE_ADDRESS_AUTO,
     SDL_TEXTURE_ADDRESS_AUTO,
     SDL_TEXTURE_ADDRESS_CLAMP,
     SDL_TEXTURE_ADDRESS_CLAMP,
     SDL_TEXTURE_ADDRESS_WRAP,
     SDL_TEXTURE_ADDRESS_WRAP,
@@ -155,6 +156,7 @@ typedef struct SDL_RenderCommand
             SDL_FColor color;
             SDL_FColor color;
             SDL_BlendMode blend;
             SDL_BlendMode blend;
             SDL_Texture *texture;
             SDL_Texture *texture;
+            SDL_ScaleMode texture_scale_mode;
             SDL_TextureAddressMode texture_address_mode;
             SDL_TextureAddressMode texture_address_mode;
         } draw;
         } draw;
         struct
         struct
@@ -224,7 +226,6 @@ struct SDL_Renderer
     bool (*LockTexture)(SDL_Renderer *renderer, SDL_Texture *texture,
     bool (*LockTexture)(SDL_Renderer *renderer, SDL_Texture *texture,
                        const SDL_Rect *rect, void **pixels, int *pitch);
                        const SDL_Rect *rect, void **pixels, int *pitch);
     void (*UnlockTexture)(SDL_Renderer *renderer, SDL_Texture *texture);
     void (*UnlockTexture)(SDL_Renderer *renderer, SDL_Texture *texture);
-    void (*SetTextureScaleMode)(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode);
     bool (*SetRenderTarget)(SDL_Renderer *renderer, SDL_Texture *texture);
     bool (*SetRenderTarget)(SDL_Renderer *renderer, SDL_Texture *texture);
     SDL_Surface *(*RenderReadPixels)(SDL_Renderer *renderer, const SDL_Rect *rect);
     SDL_Surface *(*RenderReadPixels)(SDL_Renderer *renderer, const SDL_Rect *rect);
     bool (*RenderPresent)(SDL_Renderer *renderer);
     bool (*RenderPresent)(SDL_Renderer *renderer);

+ 46 - 37
libs/SDL3/src/render/direct3d/SDL_render_d3d.c

@@ -60,7 +60,7 @@ typedef struct
     bool updateSize;
     bool updateSize;
     bool beginScene;
     bool beginScene;
     bool enableSeparateAlphaBlend;
     bool enableSeparateAlphaBlend;
-    D3DTEXTUREFILTERTYPE scaleMode[3];
+    SDL_ScaleMode scaleMode[3];
     SDL_TextureAddressMode addressMode[3];
     SDL_TextureAddressMode addressMode[3];
     IDirect3DSurface9 *defaultRenderTarget;
     IDirect3DSurface9 *defaultRenderTarget;
     IDirect3DSurface9 *currentRenderTarget;
     IDirect3DSurface9 *currentRenderTarget;
@@ -89,7 +89,6 @@ typedef struct
 typedef struct
 typedef struct
 {
 {
     D3D_TextureRep texture;
     D3D_TextureRep texture;
-    D3DTEXTUREFILTERTYPE scaleMode;
     D3D9_Shader shader;
     D3D9_Shader shader;
     const float *shader_params;
     const float *shader_params;
 
 
@@ -274,10 +273,14 @@ static void D3D_InitRenderState(D3D_RenderData *data)
     IDirect3DDevice9_SetTransform(device, D3DTS_VIEW, &matrix);
     IDirect3DDevice9_SetTransform(device, D3DTS_VIEW, &matrix);
 
 
     // Reset our current scale mode
     // Reset our current scale mode
-    SDL_memset(data->scaleMode, 0xFF, sizeof(data->scaleMode));
+    for (int i = 0; i < SDL_arraysize(data->scaleMode); ++i) {
+        data->scaleMode[i] = SDL_SCALEMODE_INVALID;
+    }
 
 
     // Reset our current address mode
     // Reset our current address mode
-    SDL_zeroa(data->addressMode);
+    for (int i = 0; i < SDL_arraysize(data->addressMode); ++i) {
+        data->addressMode[i] = SDL_TEXTURE_ADDRESS_INVALID;
+    }
 
 
     // Start the render with beginScene
     // Start the render with beginScene
     data->beginScene = true;
     data->beginScene = true;
@@ -533,7 +536,6 @@ static bool D3D_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_
     if (!texturedata) {
     if (!texturedata) {
         return false;
         return false;
     }
     }
-    texturedata->scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? D3DTEXF_POINT : D3DTEXF_LINEAR;
 
 
     texture->internal = texturedata;
     texture->internal = texturedata;
 
 
@@ -736,17 +738,6 @@ static void D3D_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
     }
     }
 }
 }
 
 
-static void D3D_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode)
-{
-    D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal;
-
-    if (!texturedata) {
-        return;
-    }
-
-    texturedata->scaleMode = (scaleMode == SDL_SCALEMODE_NEAREST) ? D3DTEXF_POINT : D3DTEXF_LINEAR;
-}
-
 static bool D3D_SetRenderTargetInternal(SDL_Renderer *renderer, SDL_Texture *texture)
 static bool D3D_SetRenderTargetInternal(SDL_Renderer *renderer, SDL_Texture *texture)
 {
 {
     D3D_RenderData *data = (D3D_RenderData *)renderer->internal;
     D3D_RenderData *data = (D3D_RenderData *)renderer->internal;
@@ -926,12 +917,22 @@ static bool BindTextureRep(IDirect3DDevice9 *device, D3D_TextureRep *texture, DW
     return true;
     return true;
 }
 }
 
 
-static void UpdateTextureScaleMode(D3D_RenderData *data, D3D_TextureData *texturedata, unsigned index)
+static void UpdateTextureScaleMode(D3D_RenderData *data, SDL_ScaleMode scaleMode, unsigned index)
 {
 {
-    if (texturedata->scaleMode != data->scaleMode[index]) {
-        IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_MINFILTER, texturedata->scaleMode);
-        IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_MAGFILTER, texturedata->scaleMode);
-        data->scaleMode[index] = texturedata->scaleMode;
+    if (scaleMode != data->scaleMode[index]) {
+        switch (scaleMode) {
+        case SDL_SCALEMODE_NEAREST:
+            IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_MINFILTER, D3DTEXF_POINT);
+            IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
+            break;
+        case SDL_SCALEMODE_LINEAR:
+            IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
+            IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
+            break;
+        default:
+            break;
+        }
+        data->scaleMode[index] = scaleMode;
     }
     }
 }
 }
 
 
@@ -954,7 +955,7 @@ static void UpdateTextureAddressMode(D3D_RenderData *data, SDL_TextureAddressMod
     }
     }
 }
 }
 
 
-static bool SetupTextureState(D3D_RenderData *data, SDL_Texture *texture, SDL_TextureAddressMode addressMode, D3D9_Shader *shader, const float **shader_params)
+static bool SetupTextureState(D3D_RenderData *data, SDL_Texture *texture, D3D9_Shader *shader, const float **shader_params)
 {
 {
     D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal;
     D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal;
 
 
@@ -962,9 +963,6 @@ static bool SetupTextureState(D3D_RenderData *data, SDL_Texture *texture, SDL_Te
         return SDL_SetError("Texture is not currently available");
         return SDL_SetError("Texture is not currently available");
     }
     }
 
 
-    UpdateTextureScaleMode(data, texturedata, 0);
-    UpdateTextureAddressMode(data, addressMode, 0);
-
     *shader = texturedata->shader;
     *shader = texturedata->shader;
     *shader_params = texturedata->shader_params;
     *shader_params = texturedata->shader_params;
 
 
@@ -973,11 +971,6 @@ static bool SetupTextureState(D3D_RenderData *data, SDL_Texture *texture, SDL_Te
     }
     }
 #ifdef SDL_HAVE_YUV
 #ifdef SDL_HAVE_YUV
     if (texturedata->yuv) {
     if (texturedata->yuv) {
-        UpdateTextureScaleMode(data, texturedata, 1);
-        UpdateTextureScaleMode(data, texturedata, 2);
-        UpdateTextureAddressMode(data, addressMode, 1);
-        UpdateTextureAddressMode(data, addressMode, 2);
-
         if (!BindTextureRep(data->device, &texturedata->utexture, 1)) {
         if (!BindTextureRep(data->device, &texturedata->utexture, 1)) {
             return false;
             return false;
         }
         }
@@ -1012,7 +1005,7 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd)
             IDirect3DDevice9_SetTexture(data->device, 2, NULL);
             IDirect3DDevice9_SetTexture(data->device, 2, NULL);
         }
         }
 #endif
 #endif
-        if (texture && !SetupTextureState(data, texture, cmd->data.draw.texture_address_mode, &shader, &shader_params)) {
+        if (texture && !SetupTextureState(data, texture, &shader, &shader_params)) {
             return false;
             return false;
         }
         }
 
 
@@ -1040,13 +1033,30 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd)
         data->drawstate.texture = texture;
         data->drawstate.texture = texture;
     } else if (texture) {
     } else if (texture) {
         D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal;
         D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal;
-        UpdateDirtyTexture(data->device, &texturedata->texture);
+        if (texturedata) {
+            UpdateDirtyTexture(data->device, &texturedata->texture);
 #ifdef SDL_HAVE_YUV
 #ifdef SDL_HAVE_YUV
-        if (texturedata->yuv) {
-            UpdateDirtyTexture(data->device, &texturedata->utexture);
-            UpdateDirtyTexture(data->device, &texturedata->vtexture);
+            if (texturedata->yuv) {
+                UpdateDirtyTexture(data->device, &texturedata->utexture);
+                UpdateDirtyTexture(data->device, &texturedata->vtexture);
+            }
+#endif // SDL_HAVE_YUV
         }
         }
-#endif
+    }
+
+    if (texture) {
+        UpdateTextureScaleMode(data, cmd->data.draw.texture_scale_mode, 0);
+        UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode, 0);
+
+#ifdef SDL_HAVE_YUV
+        D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal;
+        if (texturedata && texturedata->yuv) {
+            UpdateTextureScaleMode(data, cmd->data.draw.texture_scale_mode, 1);
+            UpdateTextureScaleMode(data, cmd->data.draw.texture_scale_mode, 2);
+            UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode, 1);
+            UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode, 2);
+        }
+#endif // SDL_HAVE_YUV
     }
     }
 
 
     if (blend != data->drawstate.blend) {
     if (blend != data->drawstate.blend) {
@@ -1653,7 +1663,6 @@ static bool D3D_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P
 #endif
 #endif
     renderer->LockTexture = D3D_LockTexture;
     renderer->LockTexture = D3D_LockTexture;
     renderer->UnlockTexture = D3D_UnlockTexture;
     renderer->UnlockTexture = D3D_UnlockTexture;
-    renderer->SetTextureScaleMode = D3D_SetTextureScaleMode;
     renderer->SetRenderTarget = D3D_SetRenderTarget;
     renderer->SetRenderTarget = D3D_SetRenderTarget;
     renderer->QueueSetViewport = D3D_QueueNoOp;
     renderer->QueueSetViewport = D3D_QueueNoOp;
     renderer->QueueSetDrawColor = D3D_QueueNoOp;
     renderer->QueueSetDrawColor = D3D_QueueNoOp;

+ 4 - 18
libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c

@@ -122,7 +122,6 @@ typedef struct
     ID3D11Texture2D *stagingTexture;
     ID3D11Texture2D *stagingTexture;
     int lockedTexturePositionX;
     int lockedTexturePositionX;
     int lockedTexturePositionY;
     int lockedTexturePositionY;
-    D3D11_FILTER scaleMode;
     D3D11_Shader shader;
     D3D11_Shader shader;
     const float *YCbCr_matrix;
     const float *YCbCr_matrix;
 #ifdef SDL_HAVE_YUV
 #ifdef SDL_HAVE_YUV
@@ -1172,7 +1171,6 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD
     if (!textureData) {
     if (!textureData) {
         return false;
         return false;
     }
     }
-    textureData->scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? D3D11_FILTER_MIN_MAG_MIP_POINT : D3D11_FILTER_MIN_MAG_MIP_LINEAR;
 
 
     texture->internal = textureData;
     texture->internal = textureData;
 
 
@@ -1796,17 +1794,6 @@ static void D3D11_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
     SAFE_RELEASE(textureData->stagingTexture);
     SAFE_RELEASE(textureData->stagingTexture);
 }
 }
 
 
-static void D3D11_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode)
-{
-    D3D11_TextureData *textureData = (D3D11_TextureData *)texture->internal;
-
-    if (!textureData) {
-        return;
-    }
-
-    textureData->scaleMode = (scaleMode == SDL_SCALEMODE_NEAREST) ? D3D11_FILTER_MIN_MAG_MIP_POINT : D3D11_FILTER_MIN_MAG_MIP_LINEAR;
-}
-
 static bool D3D11_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 static bool D3D11_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 {
 {
     D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->internal;
     D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->internal;
@@ -2318,8 +2305,8 @@ static bool D3D11_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *
 
 
     D3D11_SetupShaderConstants(renderer, cmd, texture, &constants);
     D3D11_SetupShaderConstants(renderer, cmd, texture, &constants);
 
 
-    switch (textureData->scaleMode) {
-    case D3D11_FILTER_MIN_MAG_MIP_POINT:
+    switch (cmd->data.draw.texture_scale_mode) {
+    case SDL_SCALEMODE_NEAREST:
         switch (cmd->data.draw.texture_address_mode) {
         switch (cmd->data.draw.texture_address_mode) {
         case SDL_TEXTURE_ADDRESS_CLAMP:
         case SDL_TEXTURE_ADDRESS_CLAMP:
             textureSampler = rendererData->samplers[D3D11_SAMPLER_NEAREST_CLAMP];
             textureSampler = rendererData->samplers[D3D11_SAMPLER_NEAREST_CLAMP];
@@ -2331,7 +2318,7 @@ static bool D3D11_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *
             return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode);
             return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode);
         }
         }
         break;
         break;
-    case D3D11_FILTER_MIN_MAG_MIP_LINEAR:
+    case SDL_SCALEMODE_LINEAR:
         switch (cmd->data.draw.texture_address_mode) {
         switch (cmd->data.draw.texture_address_mode) {
         case SDL_TEXTURE_ADDRESS_CLAMP:
         case SDL_TEXTURE_ADDRESS_CLAMP:
             textureSampler = rendererData->samplers[D3D11_SAMPLER_LINEAR_CLAMP];
             textureSampler = rendererData->samplers[D3D11_SAMPLER_LINEAR_CLAMP];
@@ -2344,7 +2331,7 @@ static bool D3D11_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *
         }
         }
         break;
         break;
     default:
     default:
-        return SDL_SetError("Unknown scale mode: %d", textureData->scaleMode);
+        return SDL_SetError("Unknown scale mode: %d", cmd->data.draw.texture_scale_mode);
     }
     }
 #ifdef SDL_HAVE_YUV
 #ifdef SDL_HAVE_YUV
     if (textureData->yuv) {
     if (textureData->yuv) {
@@ -2712,7 +2699,6 @@ static bool D3D11_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL
 #endif
 #endif
     renderer->LockTexture = D3D11_LockTexture;
     renderer->LockTexture = D3D11_LockTexture;
     renderer->UnlockTexture = D3D11_UnlockTexture;
     renderer->UnlockTexture = D3D11_UnlockTexture;
-    renderer->SetTextureScaleMode = D3D11_SetTextureScaleMode;
     renderer->SetRenderTarget = D3D11_SetRenderTarget;
     renderer->SetRenderTarget = D3D11_SetRenderTarget;
     renderer->QueueSetViewport = D3D11_QueueNoOp;
     renderer->QueueSetViewport = D3D11_QueueNoOp;
     renderer->QueueSetDrawColor = D3D11_QueueNoOp;
     renderer->QueueSetDrawColor = D3D11_QueueNoOp;

+ 16 - 19
libs/SDL3/src/render/direct3d12/SDL_render_d3d12.c

@@ -124,7 +124,6 @@ typedef struct
     DXGI_FORMAT mainTextureFormat;
     DXGI_FORMAT mainTextureFormat;
     ID3D12Resource *stagingBuffer;
     ID3D12Resource *stagingBuffer;
     D3D12_RESOURCE_STATES stagingResourceState;
     D3D12_RESOURCE_STATES stagingResourceState;
-    D3D12_FILTER scaleMode;
     D3D12_Shader shader;
     D3D12_Shader shader;
     const float *YCbCr_matrix;
     const float *YCbCr_matrix;
 #ifdef SDL_HAVE_YUV
 #ifdef SDL_HAVE_YUV
@@ -1574,7 +1573,6 @@ static bool D3D12_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD
     if (!textureData) {
     if (!textureData) {
         return false;
         return false;
     }
     }
-    textureData->scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? D3D12_FILTER_MIN_MAG_MIP_POINT : D3D12_FILTER_MIN_MAG_MIP_LINEAR;
 
 
     texture->internal = textureData;
     texture->internal = textureData;
     textureData->mainTextureFormat = textureFormat;
     textureData->mainTextureFormat = textureFormat;
@@ -1992,6 +1990,10 @@ static bool D3D12_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
         }
         }
     }
     }
 #endif // SDL_HAVE_YUV
 #endif // SDL_HAVE_YUV
+    if (textureData->mainTextureResourceView.ptr == rendererData->currentShaderResource.ptr) {
+        // We'll need to rebind this resource after updating it
+        rendererData->currentShaderResource.ptr = 0;
+    }
     return true;
     return true;
 }
 }
 
 
@@ -2018,6 +2020,10 @@ static bool D3D12_UpdateTextureYUV(SDL_Renderer *renderer, SDL_Texture *texture,
     if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTextureV, 0, rect->x / 2, rect->y / 2, rect->w / 2, rect->h / 2, Vplane, Vpitch, &textureData->mainResourceStateV)) {
     if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTextureV, 0, rect->x / 2, rect->y / 2, rect->w / 2, rect->h / 2, Vplane, Vpitch, &textureData->mainResourceStateV)) {
         return false;
         return false;
     }
     }
+    if (textureData->mainTextureResourceView.ptr == rendererData->currentShaderResource.ptr) {
+        // We'll need to rebind this resource after updating it
+        rendererData->currentShaderResource.ptr = 0;
+    }
     return true;
     return true;
 }
 }
 
 
@@ -2036,10 +2042,13 @@ static bool D3D12_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture,
     if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, 0, rect->x, rect->y, rect->w, rect->h, Yplane, Ypitch, &textureData->mainResourceState)) {
     if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, 0, rect->x, rect->y, rect->w, rect->h, Yplane, Ypitch, &textureData->mainResourceState)) {
         return false;
         return false;
     }
     }
-
     if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, 1, rect->x, rect->y, rect->w, rect->h, UVplane, UVpitch, &textureData->mainResourceState)) {
     if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, 1, rect->x, rect->y, rect->w, rect->h, UVplane, UVpitch, &textureData->mainResourceState)) {
         return false;
         return false;
     }
     }
+    if (textureData->mainTextureResourceView.ptr == rendererData->currentShaderResource.ptr) {
+        // We'll need to rebind this resource after updating it
+        rendererData->currentShaderResource.ptr = 0;
+    }
     return true;
     return true;
 }
 }
 #endif
 #endif
@@ -2244,17 +2253,6 @@ static void D3D12_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
     D3D_SAFE_RELEASE(textureData->stagingBuffer);
     D3D_SAFE_RELEASE(textureData->stagingBuffer);
 }
 }
 
 
-static void D3D12_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode)
-{
-    D3D12_TextureData *textureData = (D3D12_TextureData *)texture->internal;
-
-    if (!textureData) {
-        return;
-    }
-
-    textureData->scaleMode = (scaleMode == SDL_SCALEMODE_NEAREST) ? D3D12_FILTER_MIN_MAG_MIP_POINT : D3D12_FILTER_MIN_MAG_MIP_LINEAR;
-}
-
 static bool D3D12_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 static bool D3D12_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 {
 {
     D3D12_RenderData *rendererData = (D3D12_RenderData *)renderer->internal;
     D3D12_RenderData *rendererData = (D3D12_RenderData *)renderer->internal;
@@ -2745,8 +2743,8 @@ static bool D3D12_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *
 
 
     D3D12_SetupShaderConstants(renderer, cmd, texture, &constants);
     D3D12_SetupShaderConstants(renderer, cmd, texture, &constants);
 
 
-    switch (textureData->scaleMode) {
-    case D3D12_FILTER_MIN_MAG_MIP_POINT:
+    switch (cmd->data.draw.texture_scale_mode) {
+    case SDL_SCALEMODE_NEAREST:
         switch (cmd->data.draw.texture_address_mode) {
         switch (cmd->data.draw.texture_address_mode) {
         case SDL_TEXTURE_ADDRESS_CLAMP:
         case SDL_TEXTURE_ADDRESS_CLAMP:
             textureSampler = &rendererData->samplers[D3D12_SAMPLER_NEAREST_CLAMP];
             textureSampler = &rendererData->samplers[D3D12_SAMPLER_NEAREST_CLAMP];
@@ -2758,7 +2756,7 @@ static bool D3D12_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *
             return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode);
             return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode);
         }
         }
         break;
         break;
-    case D3D12_FILTER_MIN_MAG_MIP_LINEAR:
+    case SDL_SCALEMODE_LINEAR:
         switch (cmd->data.draw.texture_address_mode) {
         switch (cmd->data.draw.texture_address_mode) {
         case SDL_TEXTURE_ADDRESS_CLAMP:
         case SDL_TEXTURE_ADDRESS_CLAMP:
             textureSampler = &rendererData->samplers[D3D12_SAMPLER_LINEAR_CLAMP];
             textureSampler = &rendererData->samplers[D3D12_SAMPLER_LINEAR_CLAMP];
@@ -2771,7 +2769,7 @@ static bool D3D12_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *
         }
         }
         break;
         break;
     default:
     default:
-        return SDL_SetError("Unknown scale mode: %d", textureData->scaleMode);
+        return SDL_SetError("Unknown scale mode: %d", cmd->data.draw.texture_scale_mode);
     }
     }
 #ifdef SDL_HAVE_YUV
 #ifdef SDL_HAVE_YUV
     if (textureData->yuv) {
     if (textureData->yuv) {
@@ -3248,7 +3246,6 @@ bool D3D12_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_Proper
 #endif
 #endif
     renderer->LockTexture = D3D12_LockTexture;
     renderer->LockTexture = D3D12_LockTexture;
     renderer->UnlockTexture = D3D12_UnlockTexture;
     renderer->UnlockTexture = D3D12_UnlockTexture;
-    renderer->SetTextureScaleMode = D3D12_SetTextureScaleMode;
     renderer->SetRenderTarget = D3D12_SetRenderTarget;
     renderer->SetRenderTarget = D3D12_SetRenderTarget;
     renderer->QueueSetViewport = D3D12_QueueNoOp;
     renderer->QueueSetViewport = D3D12_QueueNoOp;
     renderer->QueueSetDrawColor = D3D12_QueueNoOp;
     renderer->QueueSetDrawColor = D3D12_QueueNoOp;

+ 8 - 10
libs/SDL3/src/render/gpu/SDL_render_gpu.c

@@ -335,11 +335,6 @@ static void GPU_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
     GPU_UpdateTexture(renderer, texture, rect, pixels, data->pitch);
     GPU_UpdateTexture(renderer, texture, rect, pixels, data->pitch);
 }
 }
 
 
-static void GPU_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scale_mode)
-{
-    // nothing to do in this backend.
-}
-
 static bool GPU_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 static bool GPU_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 {
 {
     GPU_RenderData *data = (GPU_RenderData *)renderer->internal;
     GPU_RenderData *data = (GPU_RenderData *)renderer->internal;
@@ -494,8 +489,7 @@ static void PushUniforms(GPU_RenderData *data, SDL_RenderCommand *cmd)
     SDL_PushGPUVertexUniformData(data->state.command_buffer, 0, &uniforms, sizeof(uniforms));
     SDL_PushGPUVertexUniformData(data->state.command_buffer, 0, &uniforms, sizeof(uniforms));
 }
 }
 
 
-static SDL_GPUSampler **SamplerPointer(
-    GPU_RenderData *data, SDL_TextureAddressMode address_mode, SDL_ScaleMode scale_mode)
+static SDL_GPUSampler **SamplerPointer(GPU_RenderData *data, SDL_TextureAddressMode address_mode, SDL_ScaleMode scale_mode)
 {
 {
     return &data->samplers[scale_mode][address_mode - 1];
     return &data->samplers[scale_mode][address_mode - 1];
 }
 }
@@ -575,7 +569,7 @@ static void Draw(
     if (tdata) {
     if (tdata) {
         SDL_GPUTextureSamplerBinding sampler_bind;
         SDL_GPUTextureSamplerBinding sampler_bind;
         SDL_zero(sampler_bind);
         SDL_zero(sampler_bind);
-        sampler_bind.sampler = *SamplerPointer(data, cmd->data.draw.texture_address_mode, cmd->data.draw.texture->scaleMode);
+        sampler_bind.sampler = *SamplerPointer(data, cmd->data.draw.texture_address_mode, cmd->data.draw.texture_scale_mode);
         sampler_bind.texture = tdata->texture;
         sampler_bind.texture = tdata->texture;
         SDL_BindGPUFragmentSamplers(pass, 0, &sampler_bind, 1);
         SDL_BindGPUFragmentSamplers(pass, 0, &sampler_bind, 1);
     }
     }
@@ -785,6 +779,8 @@ static bool GPU_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd,
                same texture, we can combine them all into a single draw call. */
                same texture, we can combine them all into a single draw call. */
             SDL_Texture *thistexture = cmd->data.draw.texture;
             SDL_Texture *thistexture = cmd->data.draw.texture;
             SDL_BlendMode thisblend = cmd->data.draw.blend;
             SDL_BlendMode thisblend = cmd->data.draw.blend;
+            SDL_ScaleMode thisscalemode = cmd->data.draw.texture_scale_mode;
+            SDL_TextureAddressMode thisaddressmode = cmd->data.draw.texture_address_mode;
             const SDL_RenderCommandType thiscmdtype = cmd->command;
             const SDL_RenderCommandType thiscmdtype = cmd->command;
             SDL_RenderCommand *finalcmd = cmd;
             SDL_RenderCommand *finalcmd = cmd;
             SDL_RenderCommand *nextcmd = cmd->next;
             SDL_RenderCommand *nextcmd = cmd->next;
@@ -795,7 +791,10 @@ static bool GPU_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd,
                 const SDL_RenderCommandType nextcmdtype = nextcmd->command;
                 const SDL_RenderCommandType nextcmdtype = nextcmd->command;
                 if (nextcmdtype != thiscmdtype) {
                 if (nextcmdtype != thiscmdtype) {
                     break; // can't go any further on this draw call, different render command up next.
                     break; // can't go any further on this draw call, different render command up next.
-                } else if (nextcmd->data.draw.texture != thistexture || nextcmd->data.draw.blend != thisblend) {
+                } else if (nextcmd->data.draw.texture != thistexture ||
+                           nextcmd->data.draw.texture_scale_mode != thisscalemode ||
+                           nextcmd->data.draw.texture_address_mode != thisaddressmode ||
+                           nextcmd->data.draw.blend != thisblend) {
                     // FIXME should we check address mode too?
                     // FIXME should we check address mode too?
                     break; // can't go any further on this draw call, different texture/blendmode copy up next.
                     break; // can't go any further on this draw call, different texture/blendmode copy up next.
                 } else {
                 } else {
@@ -1176,7 +1175,6 @@ static bool GPU_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P
     renderer->UpdateTexture = GPU_UpdateTexture;
     renderer->UpdateTexture = GPU_UpdateTexture;
     renderer->LockTexture = GPU_LockTexture;
     renderer->LockTexture = GPU_LockTexture;
     renderer->UnlockTexture = GPU_UnlockTexture;
     renderer->UnlockTexture = GPU_UnlockTexture;
-    renderer->SetTextureScaleMode = GPU_SetTextureScaleMode;
     renderer->SetRenderTarget = GPU_SetRenderTarget;
     renderer->SetRenderTarget = GPU_SetRenderTarget;
     renderer->QueueSetViewport = GPU_QueueNoOp;
     renderer->QueueSetViewport = GPU_QueueNoOp;
     renderer->QueueSetDrawColor = GPU_QueueNoOp;
     renderer->QueueSetDrawColor = GPU_QueueNoOp;

+ 1 - 6
libs/SDL3/src/render/metal/SDL_render_metal.m

@@ -1070,10 +1070,6 @@ static void METAL_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
     }
     }
 }
 }
 
 
-static void METAL_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode)
-{
-}
-
 static bool METAL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 static bool METAL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 {
 {
     @autoreleasepool {
     @autoreleasepool {
@@ -1473,7 +1469,7 @@ static bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, c
     if (texture != statecache->texture) {
     if (texture != statecache->texture) {
         id<MTLSamplerState> mtlsampler;
         id<MTLSamplerState> mtlsampler;
 
 
-        if (texture->scaleMode == SDL_SCALEMODE_NEAREST) {
+        if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_NEAREST) {
             switch (cmd->data.draw.texture_address_mode) {
             switch (cmd->data.draw.texture_address_mode) {
             case SDL_TEXTURE_ADDRESS_CLAMP:
             case SDL_TEXTURE_ADDRESS_CLAMP:
                 mtlsampler = data.mtlsamplers[SDL_METAL_SAMPLER_NEAREST_CLAMP];
                 mtlsampler = data.mtlsamplers[SDL_METAL_SAMPLER_NEAREST_CLAMP];
@@ -2129,7 +2125,6 @@ static bool METAL_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL
 #endif
 #endif
         renderer->LockTexture = METAL_LockTexture;
         renderer->LockTexture = METAL_LockTexture;
         renderer->UnlockTexture = METAL_UnlockTexture;
         renderer->UnlockTexture = METAL_UnlockTexture;
-        renderer->SetTextureScaleMode = METAL_SetTextureScaleMode;
         renderer->SetRenderTarget = METAL_SetRenderTarget;
         renderer->SetRenderTarget = METAL_SetRenderTarget;
         renderer->QueueSetViewport = METAL_QueueSetViewport;
         renderer->QueueSetViewport = METAL_QueueSetViewport;
         renderer->QueueSetDrawColor = METAL_QueueNoOp;
         renderer->QueueSetDrawColor = METAL_QueueNoOp;

+ 85 - 72
libs/SDL3/src/render/opengl/SDL_render_gl.c

@@ -137,7 +137,6 @@ typedef struct
     void *pixels;
     void *pixels;
     int pitch;
     int pitch;
     SDL_Rect locked_rect;
     SDL_Rect locked_rect;
-
 #ifdef SDL_HAVE_YUV
 #ifdef SDL_HAVE_YUV
     // YUV texture support
     // YUV texture support
     bool yuv;
     bool yuv;
@@ -147,7 +146,8 @@ typedef struct
     GLuint vtexture;
     GLuint vtexture;
     bool vtexture_external;
     bool vtexture_external;
 #endif
 #endif
-
+    SDL_ScaleMode texture_scale_mode;
+    SDL_TextureAddressMode texture_address_mode;
     GL_FBOList *fbo;
     GL_FBOList *fbo;
 } GL_TextureData;
 } GL_TextureData;
 
 
@@ -447,7 +447,6 @@ static bool GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_P
     GLint internalFormat;
     GLint internalFormat;
     GLenum format, type;
     GLenum format, type;
     int texture_w, texture_h;
     int texture_w, texture_h;
-    GLenum scaleMode;
 
 
     GL_ActivateRenderer(renderer);
     GL_ActivateRenderer(renderer);
 
 
@@ -536,11 +535,10 @@ static bool GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_P
 
 
     data->format = format;
     data->format = format;
     data->formattype = type;
     data->formattype = type;
-    scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? GL_NEAREST : GL_LINEAR;
+    data->texture_scale_mode = SDL_SCALEMODE_INVALID;
+    data->texture_address_mode = SDL_TEXTURE_ADDRESS_INVALID;
     renderdata->glEnable(textype);
     renderdata->glEnable(textype);
     renderdata->glBindTexture(textype, data->texture);
     renderdata->glBindTexture(textype, data->texture);
-    renderdata->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, scaleMode);
-    renderdata->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, scaleMode);
 #ifdef SDL_PLATFORM_MACOS
 #ifdef SDL_PLATFORM_MACOS
 #ifndef GL_TEXTURE_STORAGE_HINT_APPLE
 #ifndef GL_TEXTURE_STORAGE_HINT_APPLE
 #define GL_TEXTURE_STORAGE_HINT_APPLE 0x85BC
 #define GL_TEXTURE_STORAGE_HINT_APPLE 0x85BC
@@ -596,19 +594,11 @@ static bool GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_P
         }
         }
 
 
         renderdata->glBindTexture(textype, data->utexture);
         renderdata->glBindTexture(textype, data->utexture);
-        renderdata->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER,
-                                    scaleMode);
-        renderdata->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER,
-                                    scaleMode);
         renderdata->glTexImage2D(textype, 0, internalFormat, (texture_w + 1) / 2,
         renderdata->glTexImage2D(textype, 0, internalFormat, (texture_w + 1) / 2,
                                  (texture_h + 1) / 2, 0, format, type, NULL);
                                  (texture_h + 1) / 2, 0, format, type, NULL);
         SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_OPENGL_TEXTURE_U_NUMBER, data->utexture);
         SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_OPENGL_TEXTURE_U_NUMBER, data->utexture);
 
 
         renderdata->glBindTexture(textype, data->vtexture);
         renderdata->glBindTexture(textype, data->vtexture);
-        renderdata->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER,
-                                    scaleMode);
-        renderdata->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER,
-                                    scaleMode);
         renderdata->glTexImage2D(textype, 0, internalFormat, (texture_w + 1) / 2,
         renderdata->glTexImage2D(textype, 0, internalFormat, (texture_w + 1) / 2,
                                  (texture_h + 1) / 2, 0, format, type, NULL);
                                  (texture_h + 1) / 2, 0, format, type, NULL);
         SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_OPENGL_TEXTURE_V_NUMBER, data->vtexture);
         SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_OPENGL_TEXTURE_V_NUMBER, data->vtexture);
@@ -625,10 +615,6 @@ static bool GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_P
             renderdata->glGenTextures(1, &data->utexture);
             renderdata->glGenTextures(1, &data->utexture);
         }
         }
         renderdata->glBindTexture(textype, data->utexture);
         renderdata->glBindTexture(textype, data->utexture);
-        renderdata->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER,
-                                    scaleMode);
-        renderdata->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER,
-                                    scaleMode);
         renderdata->glTexImage2D(textype, 0, GL_LUMINANCE_ALPHA, (texture_w + 1) / 2,
         renderdata->glTexImage2D(textype, 0, GL_LUMINANCE_ALPHA, (texture_w + 1) / 2,
                                  (texture_h + 1) / 2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, NULL);
                                  (texture_h + 1) / 2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, NULL);
         SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_OPENGL_TEXTURE_UV_NUMBER, data->utexture);
         SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_OPENGL_TEXTURE_UV_NUMBER, data->utexture);
@@ -822,38 +808,6 @@ static void GL_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
     GL_UpdateTexture(renderer, texture, rect, pixels, data->pitch);
     GL_UpdateTexture(renderer, texture, rect, pixels, data->pitch);
 }
 }
 
 
-static void GL_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode)
-{
-    GL_RenderData *renderdata = (GL_RenderData *)renderer->internal;
-    const GLenum textype = renderdata->textype;
-    GL_TextureData *data = (GL_TextureData *)texture->internal;
-    GLenum glScaleMode = (scaleMode == SDL_SCALEMODE_NEAREST) ? GL_NEAREST : GL_LINEAR;
-
-    renderdata->glBindTexture(textype, data->texture);
-    renderdata->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, glScaleMode);
-    renderdata->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, glScaleMode);
-
-#ifdef SDL_HAVE_YUV
-    if (texture->format == SDL_PIXELFORMAT_YV12 ||
-        texture->format == SDL_PIXELFORMAT_IYUV) {
-        renderdata->glBindTexture(textype, data->utexture);
-        renderdata->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, glScaleMode);
-        renderdata->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, glScaleMode);
-
-        renderdata->glBindTexture(textype, data->vtexture);
-        renderdata->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, glScaleMode);
-        renderdata->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, glScaleMode);
-    }
-
-    if (texture->format == SDL_PIXELFORMAT_NV12 ||
-        texture->format == SDL_PIXELFORMAT_NV21) {
-        renderdata->glBindTexture(textype, data->utexture);
-        renderdata->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, glScaleMode);
-        renderdata->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, glScaleMode);
-    }
-#endif
-}
-
 static bool GL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 static bool GL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 {
 {
     GL_RenderData *data = (GL_RenderData *)renderer->internal;
     GL_RenderData *data = (GL_RenderData *)renderer->internal;
@@ -1120,6 +1074,23 @@ static bool SetDrawState(GL_RenderData *data, const SDL_RenderCommand *cmd, cons
     return true;
     return true;
 }
 }
 
 
+static bool SetTextureScaleMode(GL_RenderData *data, GLenum textype, SDL_ScaleMode scaleMode)
+{
+    switch (scaleMode) {
+    case SDL_SCALEMODE_NEAREST:
+        data->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        data->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        break;
+    case SDL_SCALEMODE_LINEAR:
+        data->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+        data->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+        break;
+    default:
+        return SDL_SetError("Unknown texture scale mode: %d", scaleMode);
+    }
+    return true;
+}
+
 static bool SetTextureAddressMode(GL_RenderData *data, GLenum textype, SDL_TextureAddressMode addressMode)
 static bool SetTextureAddressMode(GL_RenderData *data, GLenum textype, SDL_TextureAddressMode addressMode)
 {
 {
     switch (addressMode) {
     switch (addressMode) {
@@ -1140,53 +1111,91 @@ static bool SetTextureAddressMode(GL_RenderData *data, GLenum textype, SDL_Textu
 static bool SetCopyState(GL_RenderData *data, const SDL_RenderCommand *cmd)
 static bool SetCopyState(GL_RenderData *data, const SDL_RenderCommand *cmd)
 {
 {
     SDL_Texture *texture = cmd->data.draw.texture;
     SDL_Texture *texture = cmd->data.draw.texture;
-    const GL_TextureData *texturedata = (GL_TextureData *)texture->internal;
+    GL_TextureData *texturedata = (GL_TextureData *)texture->internal;
+    const GLenum textype = data->textype;
 
 
     SetDrawState(data, cmd, texturedata->shader, texturedata->shader_params);
     SetDrawState(data, cmd, texturedata->shader, texturedata->shader_params);
 
 
     if (texture != data->drawstate.texture) {
     if (texture != data->drawstate.texture) {
-        const GLenum textype = data->textype;
 #ifdef SDL_HAVE_YUV
 #ifdef SDL_HAVE_YUV
         if (texturedata->yuv) {
         if (texturedata->yuv) {
-            if (data->GL_ARB_multitexture_supported) {
-                data->glActiveTextureARB(GL_TEXTURE2_ARB);
-            }
+            data->glActiveTextureARB(GL_TEXTURE2_ARB);
             data->glBindTexture(textype, texturedata->vtexture);
             data->glBindTexture(textype, texturedata->vtexture);
 
 
-            if (!SetTextureAddressMode(data, textype, cmd->data.draw.texture_address_mode)) {
+            data->glActiveTextureARB(GL_TEXTURE1_ARB);
+            data->glBindTexture(textype, texturedata->utexture);
+        }
+        if (texturedata->nv12) {
+            data->glActiveTextureARB(GL_TEXTURE1_ARB);
+            data->glBindTexture(textype, texturedata->utexture);
+        }
+#endif
+        if (data->GL_ARB_multitexture_supported) {
+            data->glActiveTextureARB(GL_TEXTURE0_ARB);
+        }
+        data->glBindTexture(textype, texturedata->texture);
+
+        data->drawstate.texture = texture;
+    }
+
+    if (cmd->data.draw.texture_scale_mode != texturedata->texture_scale_mode) {
+#ifdef SDL_HAVE_YUV
+        if (texturedata->yuv) {
+            data->glActiveTextureARB(GL_TEXTURE2);
+            if (!SetTextureScaleMode(data, textype, cmd->data.draw.texture_scale_mode)) {
                 return false;
                 return false;
             }
             }
 
 
-            if (data->GL_ARB_multitexture_supported) {
-                data->glActiveTextureARB(GL_TEXTURE1_ARB);
+            data->glActiveTextureARB(GL_TEXTURE1);
+            if (!SetTextureScaleMode(data, textype, cmd->data.draw.texture_scale_mode)) {
+                return false;
             }
             }
-            data->glBindTexture(textype, texturedata->utexture);
 
 
-            if (!SetTextureAddressMode(data, textype, cmd->data.draw.texture_address_mode)) {
+            data->glActiveTextureARB(GL_TEXTURE0);
+        } else if (texturedata->nv12) {
+            data->glActiveTextureARB(GL_TEXTURE1);
+            if (!SetTextureScaleMode(data, textype, cmd->data.draw.texture_scale_mode)) {
                 return false;
                 return false;
             }
             }
+
+            data->glActiveTextureARB(GL_TEXTURE0);
         }
         }
-        if (texturedata->nv12) {
-            if (data->GL_ARB_multitexture_supported) {
-                data->glActiveTextureARB(GL_TEXTURE1_ARB);
+#endif
+        if (!SetTextureScaleMode(data, textype, cmd->data.draw.texture_scale_mode)) {
+            return false;
+        }
+
+        texturedata->texture_scale_mode = cmd->data.draw.texture_scale_mode;
+    }
+
+    if (cmd->data.draw.texture_address_mode != texturedata->texture_address_mode) {
+#ifdef SDL_HAVE_YUV
+        if (texturedata->yuv) {
+            data->glActiveTextureARB(GL_TEXTURE2);
+            if (!SetTextureAddressMode(data, textype, cmd->data.draw.texture_address_mode)) {
+                return false;
             }
             }
-            data->glBindTexture(textype, texturedata->utexture);
 
 
+            data->glActiveTextureARB(GL_TEXTURE1);
             if (!SetTextureAddressMode(data, textype, cmd->data.draw.texture_address_mode)) {
             if (!SetTextureAddressMode(data, textype, cmd->data.draw.texture_address_mode)) {
                 return false;
                 return false;
             }
             }
-        }
-#endif
-        if (data->GL_ARB_multitexture_supported) {
+
             data->glActiveTextureARB(GL_TEXTURE0_ARB);
             data->glActiveTextureARB(GL_TEXTURE0_ARB);
-        }
-        data->glBindTexture(textype, texturedata->texture);
+        } else if (texturedata->nv12) {
+            data->glActiveTextureARB(GL_TEXTURE1);
+            if (!SetTextureAddressMode(data, textype, cmd->data.draw.texture_address_mode)) {
+                return false;
+            }
 
 
+            data->glActiveTextureARB(GL_TEXTURE0);
+        }
+#endif
         if (!SetTextureAddressMode(data, textype, cmd->data.draw.texture_address_mode)) {
         if (!SetTextureAddressMode(data, textype, cmd->data.draw.texture_address_mode)) {
             return false;
             return false;
         }
         }
 
 
-        data->drawstate.texture = texture;
+        texturedata->texture_address_mode = cmd->data.draw.texture_address_mode;
     }
     }
 
 
     return true;
     return true;
@@ -1372,6 +1381,8 @@ static bool GL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, v
                same texture, we can combine them all into a single draw call. */
                same texture, we can combine them all into a single draw call. */
             SDL_Texture *thistexture = cmd->data.draw.texture;
             SDL_Texture *thistexture = cmd->data.draw.texture;
             SDL_BlendMode thisblend = cmd->data.draw.blend;
             SDL_BlendMode thisblend = cmd->data.draw.blend;
+            SDL_ScaleMode thisscalemode = cmd->data.draw.texture_scale_mode;
+            SDL_TextureAddressMode thisaddressmode = cmd->data.draw.texture_address_mode;
             const SDL_RenderCommandType thiscmdtype = cmd->command;
             const SDL_RenderCommandType thiscmdtype = cmd->command;
             SDL_RenderCommand *finalcmd = cmd;
             SDL_RenderCommand *finalcmd = cmd;
             SDL_RenderCommand *nextcmd = cmd->next;
             SDL_RenderCommand *nextcmd = cmd->next;
@@ -1381,7 +1392,10 @@ static bool GL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, v
                 const SDL_RenderCommandType nextcmdtype = nextcmd->command;
                 const SDL_RenderCommandType nextcmdtype = nextcmd->command;
                 if (nextcmdtype != thiscmdtype) {
                 if (nextcmdtype != thiscmdtype) {
                     break; // can't go any further on this draw call, different render command up next.
                     break; // can't go any further on this draw call, different render command up next.
-                } else if (nextcmd->data.draw.texture != thistexture || nextcmd->data.draw.blend != thisblend) {
+                } else if (nextcmd->data.draw.texture != thistexture ||
+                           nextcmd->data.draw.texture_scale_mode != thisscalemode ||
+                           nextcmd->data.draw.texture_address_mode != thisaddressmode ||
+                           nextcmd->data.draw.blend != thisblend) {
                     break; // can't go any further on this draw call, different texture/blendmode copy up next.
                     break; // can't go any further on this draw call, different texture/blendmode copy up next.
                 } else {
                 } else {
                     finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command.
                     finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command.
@@ -1656,7 +1670,6 @@ static bool GL_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_Pr
 #endif
 #endif
     renderer->LockTexture = GL_LockTexture;
     renderer->LockTexture = GL_LockTexture;
     renderer->UnlockTexture = GL_UnlockTexture;
     renderer->UnlockTexture = GL_UnlockTexture;
-    renderer->SetTextureScaleMode = GL_SetTextureScaleMode;
     renderer->SetRenderTarget = GL_SetRenderTarget;
     renderer->SetRenderTarget = GL_SetRenderTarget;
     renderer->QueueSetViewport = GL_QueueNoOp;
     renderer->QueueSetViewport = GL_QueueNoOp;
     renderer->QueueSetDrawColor = GL_QueueNoOp;
     renderer->QueueSetDrawColor = GL_QueueNoOp;

+ 98 - 61
libs/SDL3/src/render/opengles2/SDL_render_gles2.c

@@ -76,6 +76,8 @@ typedef struct GLES2_TextureData
     GLuint texture_u;
     GLuint texture_u;
     GLuint texture_u_external;
     GLuint texture_u_external;
 #endif
 #endif
+    SDL_ScaleMode texture_scale_mode;
+    SDL_TextureAddressMode texture_address_mode;
     GLES2_FBOList *fbo;
     GLES2_FBOList *fbo;
 } GLES2_TextureData;
 } GLES2_TextureData;
 
 
@@ -168,6 +170,7 @@ typedef struct GLES2_RenderData
 
 
     bool debug_enabled;
     bool debug_enabled;
 
 
+    bool GL_OES_EGL_image_external_supported;
     bool GL_EXT_blend_minmax_supported;
     bool GL_EXT_blend_minmax_supported;
 
 
 #define SDL_PROC(ret, func, params) ret (APIENTRY *func) params;
 #define SDL_PROC(ret, func, params) ret (APIENTRY *func) params;
@@ -987,8 +990,8 @@ static bool SetDrawState(GLES2_RenderData *data, const SDL_RenderCommand *cmd, c
     }
     }
 
 
     if (texture) {
     if (texture) {
-        SDL_Vertex *verts = (SDL_Vertex *)(((Uint8 *)vertices) + cmd->data.draw.first);
-        data->glVertexAttribPointer(GLES2_ATTRIBUTE_TEXCOORD, 2, GL_FLOAT, GL_FALSE, stride, (const GLvoid *)&verts->tex_coord);
+        uintptr_t base = (uintptr_t)vertices + cmd->data.draw.first; // address of first vertex, or base offset when using VBOs.
+        data->glVertexAttribPointer(GLES2_ATTRIBUTE_TEXCOORD, 2, GL_FLOAT, GL_FALSE, stride, (const GLvoid *)(base + offsetof(SDL_Vertex, tex_coord)));
     }
     }
 
 
     if (!GLES2_SelectProgram(data, imgsrc, texture ? texture->colorspace : SDL_COLORSPACE_SRGB)) {
     if (!GLES2_SelectProgram(data, imgsrc, texture ? texture->colorspace : SDL_COLORSPACE_SRGB)) {
@@ -1021,14 +1024,31 @@ static bool SetDrawState(GLES2_RenderData *data, const SDL_RenderCommand *cmd, c
 
 
     // all drawing commands use this
     // all drawing commands use this
     {
     {
-        SDL_VertexSolid *verts = (SDL_VertexSolid *)(((Uint8 *)vertices) + cmd->data.draw.first);
-        data->glVertexAttribPointer(GLES2_ATTRIBUTE_POSITION, 2, GL_FLOAT, GL_FALSE, stride, (const GLvoid *)&verts->position);
-        data->glVertexAttribPointer(GLES2_ATTRIBUTE_COLOR, 4, GL_FLOAT, GL_TRUE /* Normalized */, stride, (const GLvoid *)&verts->color);
+        uintptr_t base = (uintptr_t)vertices + cmd->data.draw.first; // address of first vertex, or base offset when using VBOs.
+        data->glVertexAttribPointer(GLES2_ATTRIBUTE_POSITION, 2, GL_FLOAT, GL_FALSE, stride, (const GLvoid *)(base + offsetof(SDL_VertexSolid, position)));
+        data->glVertexAttribPointer(GLES2_ATTRIBUTE_COLOR, 4, GL_FLOAT, GL_TRUE /* Normalized */, stride, (const GLvoid *)(base + offsetof(SDL_VertexSolid, color)));
     }
     }
 
 
     return true;
     return true;
 }
 }
 
 
+static bool SetTextureScaleMode(GLES2_RenderData *data, GLenum textype, SDL_ScaleMode scaleMode)
+{
+    switch (scaleMode) {
+    case SDL_SCALEMODE_NEAREST:
+        data->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        data->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        break;
+    case SDL_SCALEMODE_LINEAR:
+        data->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+        data->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+        break;
+    default:
+        return SDL_SetError("Unknown texture scale mode: %d", scaleMode);
+    }
+    return true;
+}
+
 static bool SetTextureAddressMode(GLES2_RenderData *data, GLenum textype, SDL_TextureAddressMode addressMode)
 static bool SetTextureAddressMode(GLES2_RenderData *data, GLenum textype, SDL_TextureAddressMode addressMode)
 {
 {
     switch (addressMode) {
     switch (addressMode) {
@@ -1051,6 +1071,7 @@ static bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, v
     GLES2_RenderData *data = (GLES2_RenderData *)renderer->internal;
     GLES2_RenderData *data = (GLES2_RenderData *)renderer->internal;
     GLES2_ImageSource sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR;
     GLES2_ImageSource sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR;
     SDL_Texture *texture = cmd->data.draw.texture;
     SDL_Texture *texture = cmd->data.draw.texture;
+    GLES2_TextureData *tdata = (GLES2_TextureData *)texture->internal;
     int ret;
     int ret;
 
 
     // Pick an appropriate shader
     // Pick an appropriate shader
@@ -1172,19 +1193,66 @@ static bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, v
     ret = SetDrawState(data, cmd, sourceType, vertices);
     ret = SetDrawState(data, cmd, sourceType, vertices);
 
 
     if (texture != data->drawstate.texture) {
     if (texture != data->drawstate.texture) {
-        GLES2_TextureData *tdata = (GLES2_TextureData *)texture->internal;
 #ifdef SDL_HAVE_YUV
 #ifdef SDL_HAVE_YUV
         if (tdata->yuv) {
         if (tdata->yuv) {
             data->glActiveTexture(GL_TEXTURE2);
             data->glActiveTexture(GL_TEXTURE2);
             data->glBindTexture(tdata->texture_type, tdata->texture_v);
             data->glBindTexture(tdata->texture_type, tdata->texture_v);
 
 
-            if (!SetTextureAddressMode(data, tdata->texture_type, cmd->data.draw.texture_address_mode)) {
+            data->glActiveTexture(GL_TEXTURE1);
+            data->glBindTexture(tdata->texture_type, tdata->texture_u);
+
+            data->glActiveTexture(GL_TEXTURE0);
+        } else if (tdata->nv12) {
+            data->glActiveTexture(GL_TEXTURE1);
+            data->glBindTexture(tdata->texture_type, tdata->texture_u);
+
+            data->glActiveTexture(GL_TEXTURE0);
+        }
+#endif
+        data->glBindTexture(tdata->texture_type, tdata->texture);
+
+        data->drawstate.texture = texture;
+    }
+
+    if (cmd->data.draw.texture_scale_mode != tdata->texture_scale_mode) {
+#ifdef SDL_HAVE_YUV
+        if (tdata->yuv) {
+            data->glActiveTexture(GL_TEXTURE2);
+            if (!SetTextureScaleMode(data, tdata->texture_type, cmd->data.draw.texture_scale_mode)) {
                 return false;
                 return false;
             }
             }
 
 
             data->glActiveTexture(GL_TEXTURE1);
             data->glActiveTexture(GL_TEXTURE1);
-            data->glBindTexture(tdata->texture_type, tdata->texture_u);
+            if (!SetTextureScaleMode(data, tdata->texture_type, cmd->data.draw.texture_scale_mode)) {
+                return false;
+            }
+
+            data->glActiveTexture(GL_TEXTURE0);
+        } else if (tdata->nv12) {
+            data->glActiveTexture(GL_TEXTURE1);
+            if (!SetTextureScaleMode(data, tdata->texture_type, cmd->data.draw.texture_scale_mode)) {
+                return false;
+            }
+
+            data->glActiveTexture(GL_TEXTURE0);
+        }
+#endif
+        if (!SetTextureScaleMode(data, tdata->texture_type, cmd->data.draw.texture_scale_mode)) {
+            return false;
+        }
+
+        tdata->texture_scale_mode = cmd->data.draw.texture_scale_mode;
+    }
 
 
+    if (cmd->data.draw.texture_address_mode != tdata->texture_address_mode) {
+#ifdef SDL_HAVE_YUV
+        if (tdata->yuv) {
+            data->glActiveTexture(GL_TEXTURE2);
+            if (!SetTextureAddressMode(data, tdata->texture_type, cmd->data.draw.texture_address_mode)) {
+                return false;
+            }
+
+            data->glActiveTexture(GL_TEXTURE1);
             if (!SetTextureAddressMode(data, tdata->texture_type, cmd->data.draw.texture_address_mode)) {
             if (!SetTextureAddressMode(data, tdata->texture_type, cmd->data.draw.texture_address_mode)) {
                 return false;
                 return false;
             }
             }
@@ -1192,8 +1260,6 @@ static bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, v
             data->glActiveTexture(GL_TEXTURE0);
             data->glActiveTexture(GL_TEXTURE0);
         } else if (tdata->nv12) {
         } else if (tdata->nv12) {
             data->glActiveTexture(GL_TEXTURE1);
             data->glActiveTexture(GL_TEXTURE1);
-            data->glBindTexture(tdata->texture_type, tdata->texture_u);
-
             if (!SetTextureAddressMode(data, tdata->texture_type, cmd->data.draw.texture_address_mode)) {
             if (!SetTextureAddressMode(data, tdata->texture_type, cmd->data.draw.texture_address_mode)) {
                 return false;
                 return false;
             }
             }
@@ -1201,13 +1267,11 @@ static bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, v
             data->glActiveTexture(GL_TEXTURE0);
             data->glActiveTexture(GL_TEXTURE0);
         }
         }
 #endif
 #endif
-        data->glBindTexture(tdata->texture_type, tdata->texture);
-
         if (!SetTextureAddressMode(data, tdata->texture_type, cmd->data.draw.texture_address_mode)) {
         if (!SetTextureAddressMode(data, tdata->texture_type, cmd->data.draw.texture_address_mode)) {
             return false;
             return false;
         }
         }
 
 
-        data->drawstate.texture = texture;
+        tdata->texture_address_mode = cmd->data.draw.texture_address_mode;
     }
     }
 
 
     return ret;
     return ret;
@@ -1269,7 +1333,8 @@ static bool GLES2_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd
     if (data->current_vertex_buffer >= SDL_arraysize(data->vertex_buffers)) {
     if (data->current_vertex_buffer >= SDL_arraysize(data->vertex_buffers)) {
         data->current_vertex_buffer = 0;
         data->current_vertex_buffer = 0;
     }
     }
-    vertices = NULL; // attrib pointers will be offsets into the VBO.
+    // attrib pointers will be offsets into the VBO.
+    vertices = (void *)(uintptr_t)0; // must be the exact value 0, not NULL (the representation of NULL is not guaranteed to be 0).
 #endif
 #endif
 
 
     while (cmd) {
     while (cmd) {
@@ -1384,6 +1449,8 @@ static bool GLES2_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd
                same texture, we can combine them all into a single draw call. */
                same texture, we can combine them all into a single draw call. */
             SDL_Texture *thistexture = cmd->data.draw.texture;
             SDL_Texture *thistexture = cmd->data.draw.texture;
             SDL_BlendMode thisblend = cmd->data.draw.blend;
             SDL_BlendMode thisblend = cmd->data.draw.blend;
+            SDL_ScaleMode thisscalemode = cmd->data.draw.texture_scale_mode;
+            SDL_TextureAddressMode thisaddressmode = cmd->data.draw.texture_address_mode;
             const SDL_RenderCommandType thiscmdtype = cmd->command;
             const SDL_RenderCommandType thiscmdtype = cmd->command;
             SDL_RenderCommand *finalcmd = cmd;
             SDL_RenderCommand *finalcmd = cmd;
             SDL_RenderCommand *nextcmd = cmd->next;
             SDL_RenderCommand *nextcmd = cmd->next;
@@ -1393,7 +1460,10 @@ static bool GLES2_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd
                 const SDL_RenderCommandType nextcmdtype = nextcmd->command;
                 const SDL_RenderCommandType nextcmdtype = nextcmd->command;
                 if (nextcmdtype != thiscmdtype) {
                 if (nextcmdtype != thiscmdtype) {
                     break; // can't go any further on this draw call, different render command up next.
                     break; // can't go any further on this draw call, different render command up next.
-                } else if (nextcmd->data.draw.texture != thistexture || nextcmd->data.draw.blend != thisblend) {
+                } else if (nextcmd->data.draw.texture != thistexture ||
+                           nextcmd->data.draw.texture_scale_mode != thisscalemode ||
+                           nextcmd->data.draw.texture_address_mode != thisaddressmode ||
+                           nextcmd->data.draw.blend != thisblend) {
                     break; // can't go any further on this draw call, different texture/blendmode copy up next.
                     break; // can't go any further on this draw call, different texture/blendmode copy up next.
                 } else {
                 } else {
                     finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command.
                     finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command.
@@ -1486,7 +1556,6 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD
     GLES2_TextureData *data;
     GLES2_TextureData *data;
     GLenum format;
     GLenum format;
     GLenum type;
     GLenum type;
-    GLenum scaleMode;
 
 
     GLES2_ActivateRenderer(renderer);
     GLES2_ActivateRenderer(renderer);
 
 
@@ -1512,9 +1581,12 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD
 #endif
 #endif
 #ifdef GL_TEXTURE_EXTERNAL_OES
 #ifdef GL_TEXTURE_EXTERNAL_OES
     case SDL_PIXELFORMAT_EXTERNAL_OES:
     case SDL_PIXELFORMAT_EXTERNAL_OES:
-        format = GL_NONE;
-        type = GL_NONE;
-        break;
+        if (renderdata->GL_OES_EGL_image_external_supported) {
+            format = GL_NONE;
+            type = GL_NONE;
+            break;
+        }
+        SDL_FALLTHROUGH;
 #endif
 #endif
     default:
     default:
         return SDL_SetError("Texture format not supported");
         return SDL_SetError("Texture format not supported");
@@ -1544,7 +1616,8 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD
     data->texture_u = 0;
     data->texture_u = 0;
     data->texture_v = 0;
     data->texture_v = 0;
 #endif
 #endif
-    scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? GL_NEAREST : GL_LINEAR;
+    data->texture_scale_mode = SDL_SCALEMODE_INVALID;
+    data->texture_address_mode = SDL_TEXTURE_ADDRESS_INVALID;
 
 
     // Allocate a blob for image renderdata
     // Allocate a blob for image renderdata
     if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
     if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
@@ -1583,8 +1656,6 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD
         }
         }
         renderdata->glActiveTexture(GL_TEXTURE2);
         renderdata->glActiveTexture(GL_TEXTURE2);
         renderdata->glBindTexture(data->texture_type, data->texture_v);
         renderdata->glBindTexture(data->texture_type, data->texture_v);
-        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MIN_FILTER, scaleMode);
-        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MAG_FILTER, scaleMode);
         renderdata->glTexImage2D(data->texture_type, 0, format, (texture->w + 1) / 2, (texture->h + 1) / 2, 0, format, type, NULL);
         renderdata->glTexImage2D(data->texture_type, 0, format, (texture->w + 1) / 2, (texture->h + 1) / 2, 0, format, type, NULL);
         SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_V_NUMBER, data->texture_v);
         SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_V_NUMBER, data->texture_v);
 
 
@@ -1599,8 +1670,6 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD
         }
         }
         renderdata->glActiveTexture(GL_TEXTURE1);
         renderdata->glActiveTexture(GL_TEXTURE1);
         renderdata->glBindTexture(data->texture_type, data->texture_u);
         renderdata->glBindTexture(data->texture_type, data->texture_u);
-        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MIN_FILTER, scaleMode);
-        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MAG_FILTER, scaleMode);
         renderdata->glTexImage2D(data->texture_type, 0, format, (texture->w + 1) / 2, (texture->h + 1) / 2, 0, format, type, NULL);
         renderdata->glTexImage2D(data->texture_type, 0, format, (texture->w + 1) / 2, (texture->h + 1) / 2, 0, format, type, NULL);
         if (!GL_CheckError("glTexImage2D()", renderer)) {
         if (!GL_CheckError("glTexImage2D()", renderer)) {
             return false;
             return false;
@@ -1622,8 +1691,6 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD
         }
         }
         renderdata->glActiveTexture(GL_TEXTURE1);
         renderdata->glActiveTexture(GL_TEXTURE1);
         renderdata->glBindTexture(data->texture_type, data->texture_u);
         renderdata->glBindTexture(data->texture_type, data->texture_u);
-        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MIN_FILTER, scaleMode);
-        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MAG_FILTER, scaleMode);
         renderdata->glTexImage2D(data->texture_type, 0, GL_LUMINANCE_ALPHA, (texture->w + 1) / 2, (texture->h + 1) / 2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, NULL);
         renderdata->glTexImage2D(data->texture_type, 0, GL_LUMINANCE_ALPHA, (texture->w + 1) / 2, (texture->h + 1) / 2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, NULL);
         if (!GL_CheckError("glTexImage2D()", renderer)) {
         if (!GL_CheckError("glTexImage2D()", renderer)) {
             return false;
             return false;
@@ -1648,8 +1715,6 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD
     texture->internal = data;
     texture->internal = data;
     renderdata->glActiveTexture(GL_TEXTURE0);
     renderdata->glActiveTexture(GL_TEXTURE0);
     renderdata->glBindTexture(data->texture_type, data->texture);
     renderdata->glBindTexture(data->texture_type, data->texture);
-    renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MIN_FILTER, scaleMode);
-    renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MAG_FILTER, scaleMode);
     if (texture->format != SDL_PIXELFORMAT_EXTERNAL_OES) {
     if (texture->format != SDL_PIXELFORMAT_EXTERNAL_OES) {
         renderdata->glTexImage2D(data->texture_type, 0, format, texture->w, texture->h, 0, format, type, NULL);
         renderdata->glTexImage2D(data->texture_type, 0, format, texture->w, texture->h, 0, format, type, NULL);
         if (!GL_CheckError("glTexImage2D()", renderer)) {
         if (!GL_CheckError("glTexImage2D()", renderer)) {
@@ -1900,37 +1965,6 @@ static void GLES2_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
     GLES2_UpdateTexture(renderer, texture, &rect, tdata->pixel_data, tdata->pitch);
     GLES2_UpdateTexture(renderer, texture, &rect, tdata->pixel_data, tdata->pitch);
 }
 }
 
 
-static void GLES2_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode)
-{
-    GLES2_RenderData *renderdata = (GLES2_RenderData *)renderer->internal;
-    GLES2_TextureData *data = (GLES2_TextureData *)texture->internal;
-    GLenum glScaleMode = (scaleMode == SDL_SCALEMODE_NEAREST) ? GL_NEAREST : GL_LINEAR;
-
-#ifdef SDL_HAVE_YUV
-    if (data->yuv) {
-        renderdata->glActiveTexture(GL_TEXTURE2);
-        renderdata->glBindTexture(data->texture_type, data->texture_v);
-        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MIN_FILTER, glScaleMode);
-        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MAG_FILTER, glScaleMode);
-
-        renderdata->glActiveTexture(GL_TEXTURE1);
-        renderdata->glBindTexture(data->texture_type, data->texture_u);
-        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MIN_FILTER, glScaleMode);
-        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MAG_FILTER, glScaleMode);
-    } else if (data->nv12) {
-        renderdata->glActiveTexture(GL_TEXTURE1);
-        renderdata->glBindTexture(data->texture_type, data->texture_u);
-        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MIN_FILTER, glScaleMode);
-        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MAG_FILTER, glScaleMode);
-    }
-#endif
-
-    renderdata->glActiveTexture(GL_TEXTURE0);
-    renderdata->glBindTexture(data->texture_type, data->texture);
-    renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MIN_FILTER, glScaleMode);
-    renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MAG_FILTER, glScaleMode);
-}
-
 static bool GLES2_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 static bool GLES2_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 {
 {
     GLES2_RenderData *data = (GLES2_RenderData *)renderer->internal;
     GLES2_RenderData *data = (GLES2_RenderData *)renderer->internal;
@@ -2151,7 +2185,6 @@ static bool GLES2_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL
 #endif
 #endif
     renderer->LockTexture = GLES2_LockTexture;
     renderer->LockTexture = GLES2_LockTexture;
     renderer->UnlockTexture = GLES2_UnlockTexture;
     renderer->UnlockTexture = GLES2_UnlockTexture;
-    renderer->SetTextureScaleMode = GLES2_SetTextureScaleMode;
     renderer->SetRenderTarget = GLES2_SetRenderTarget;
     renderer->SetRenderTarget = GLES2_SetRenderTarget;
     renderer->QueueSetViewport = GLES2_QueueNoOp;
     renderer->QueueSetViewport = GLES2_QueueNoOp;
     renderer->QueueSetDrawColor = GLES2_QueueNoOp;
     renderer->QueueSetDrawColor = GLES2_QueueNoOp;
@@ -2172,7 +2205,11 @@ static bool GLES2_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL
     SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV21);
     SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV21);
 #endif
 #endif
 #ifdef GL_TEXTURE_EXTERNAL_OES
 #ifdef GL_TEXTURE_EXTERNAL_OES
-    if (GLES2_CacheShader(data, GLES2_SHADER_FRAGMENT_TEXTURE_EXTERNAL_OES, GL_FRAGMENT_SHADER)) {
+    if (SDL_GL_ExtensionSupported("GL_OES_EGL_image_external")) {
+        data->GL_OES_EGL_image_external_supported = true;
+        if (!GLES2_CacheShader(data, GLES2_SHADER_FRAGMENT_TEXTURE_EXTERNAL_OES, GL_FRAGMENT_SHADER)) {
+            goto error;
+        }
         SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_EXTERNAL_OES);
         SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_EXTERNAL_OES);
     }
     }
 #endif
 #endif

+ 10 - 16
libs/SDL3/src/render/ps2/SDL_render_ps2.c

@@ -195,21 +195,6 @@ static bool PS2_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
     return true;
     return true;
 }
 }
 
 
-static void PS2_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode)
-{
-    GSTEXTURE *ps2_texture = (GSTEXTURE *)texture->internal;
-    /*
-     set texture filtering according to scaleMode
-     supported hint values are nearest (0, default) or linear (1)
-     gskit scale mode is either GS_FILTER_NEAREST (good for tile-map)
-     or GS_FILTER_LINEAR (good for scaling)
-     */
-    uint32_t gsKitScaleMode = (scaleMode == SDL_SCALEMODE_NEAREST
-                                   ? GS_FILTER_NEAREST
-                                   : GS_FILTER_LINEAR);
-    ps2_texture->Filter = gsKitScaleMode;
-}
-
 static bool PS2_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 static bool PS2_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 {
 {
     return true;
     return true;
@@ -458,6 +443,16 @@ static bool PS2_RenderGeometry(SDL_Renderer *renderer, void *vertices, SDL_Rende
         const GSPRIMUVPOINT *verts = (GSPRIMUVPOINT *) (vertices + cmd->data.draw.first);
         const GSPRIMUVPOINT *verts = (GSPRIMUVPOINT *) (vertices + cmd->data.draw.first);
         GSTEXTURE *ps2_tex = (GSTEXTURE *)cmd->data.draw.texture->internal;
         GSTEXTURE *ps2_tex = (GSTEXTURE *)cmd->data.draw.texture->internal;
 
 
+        switch (cmd->data.draw.texture_scale_mode) {
+        case SDL_SCALEMODE_NEAREST:
+            ps2_tex->Filter = GS_FILTER_NEAREST;
+            break;
+        case SDL_SCALEMODE_LINEAR:
+            ps2_tex->Filter = GS_FILTER_LINEAR;
+            break;
+        default:
+            break;
+        }
         gsKit_TexManager_bind(data->gsGlobal, ps2_tex);
         gsKit_TexManager_bind(data->gsGlobal, ps2_tex);
         gsKit_prim_list_triangle_goraud_texture_uv_3d(data->gsGlobal, ps2_tex, count, verts);
         gsKit_prim_list_triangle_goraud_texture_uv_3d(data->gsGlobal, ps2_tex, count, verts);
     } else {
     } else {
@@ -695,7 +690,6 @@ static bool PS2_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P
     renderer->UpdateTexture = PS2_UpdateTexture;
     renderer->UpdateTexture = PS2_UpdateTexture;
     renderer->LockTexture = PS2_LockTexture;
     renderer->LockTexture = PS2_LockTexture;
     renderer->UnlockTexture = PS2_UnlockTexture;
     renderer->UnlockTexture = PS2_UnlockTexture;
-    renderer->SetTextureScaleMode = PS2_SetTextureScaleMode;
     renderer->SetRenderTarget = PS2_SetRenderTarget;
     renderer->SetRenderTarget = PS2_SetRenderTarget;
     renderer->QueueSetViewport = PS2_QueueSetViewport;
     renderer->QueueSetViewport = PS2_QueueSetViewport;
     renderer->QueueSetDrawColor = PS2_QueueNoOp;
     renderer->QueueSetDrawColor = PS2_QueueNoOp;

+ 49 - 13
libs/SDL3/src/render/psp/SDL_render_psp.c

@@ -75,6 +75,8 @@ typedef struct
     unsigned int color;
     unsigned int color;
     int shadeModel;
     int shadeModel;
     SDL_Texture *texture;
     SDL_Texture *texture;
+    SDL_ScaleMode texture_scale_mode;
+    SDL_TextureAddressMode texture_address_mode;
 } PSP_BlendState;
 } PSP_BlendState;
 
 
 typedef struct
 typedef struct
@@ -538,20 +540,44 @@ static bool TextureShouldSwizzle(PSP_TextureData *psp_texture, SDL_Texture *text
     return !((texture->access == SDL_TEXTUREACCESS_TARGET) && InVram(psp_texture->data)) && texture->access != SDL_TEXTUREACCESS_STREAMING && (texture->w >= 16 || texture->h >= 16);
     return !((texture->access == SDL_TEXTUREACCESS_TARGET) && InVram(psp_texture->data)) && texture->access != SDL_TEXTUREACCESS_STREAMING && (texture->w >= 16 || texture->h >= 16);
 }
 }
 
 
+static void SetTextureAddressMode(SDL_TextureAddressMode addressMode)
+{
+    switch (addressMode) {
+    case SDL_TEXTURE_ADDRESS_CLAMP:
+        sceGuTexWrap(GU_CLAMP, GU_CLAMP);
+        break;
+    case SDL_TEXTURE_ADDRESS_WRAP:
+        sceGuTexWrap(GU_REPEAT, GU_REPEAT);
+        break;
+    default:
+        break;
+    }
+}
+
+static void SetTextureScaleMode(SDL_ScaleMode scaleMode)
+{
+    switch (scaleMode) {
+    case SDL_SCALEMODE_NEAREST:
+        sceGuTexFilter(GU_NEAREST, GU_NEAREST);
+        break;
+    case SDL_SCALEMODE_LINEAR:
+        sceGuTexFilter(GU_LINEAR, GU_LINEAR);
+        break;
+    default:
+        break;
+    }
+}
+
 static void TextureActivate(SDL_Texture *texture)
 static void TextureActivate(SDL_Texture *texture)
 {
 {
     PSP_TextureData *psp_texture = (PSP_TextureData *)texture->internal;
     PSP_TextureData *psp_texture = (PSP_TextureData *)texture->internal;
-    int scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? GU_NEAREST : GU_LINEAR;
 
 
     // Swizzling is useless with small textures.
     // Swizzling is useless with small textures.
     if (TextureShouldSwizzle(psp_texture, texture)) {
     if (TextureShouldSwizzle(psp_texture, texture)) {
         TextureSwizzle(psp_texture, NULL);
         TextureSwizzle(psp_texture, NULL);
     }
     }
 
 
-    sceGuTexWrap(GU_REPEAT, GU_REPEAT);
     sceGuTexMode(psp_texture->format, 0, 0, psp_texture->swizzled);
     sceGuTexMode(psp_texture->format, 0, 0, psp_texture->swizzled);
-    sceGuTexFilter(scaleMode, scaleMode); // GU_NEAREST good for tile-map
-    // GU_LINEAR good for scaling
     sceGuTexImage(0, psp_texture->textureWidth, psp_texture->textureHeight, psp_texture->textureWidth, psp_texture->data);
     sceGuTexImage(0, psp_texture->textureWidth, psp_texture->textureHeight, psp_texture->textureWidth, psp_texture->data);
 }
 }
 
 
@@ -608,11 +634,6 @@ static void PSP_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
     PSP_UpdateTexture(renderer, texture, &rect, psp_texture->data, psp_texture->pitch);
     PSP_UpdateTexture(renderer, texture, &rect, psp_texture->data, psp_texture->pitch);
 }
 }
 
 
-static void PSP_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode)
-{
-    // Nothing to do because TextureActivate takes care of it
-}
-
 static bool PSP_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 static bool PSP_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 {
 {
     return true;
     return true;
@@ -1034,6 +1055,11 @@ static void PSP_SetBlendState(PSP_RenderData *data, PSP_BlendState *state)
         }
         }
     }
     }
 
 
+    if (state->texture) {
+        SetTextureScaleMode(state->texture_scale_mode);
+        SetTextureAddressMode(state->texture_address_mode);
+    }
+
     *current = *state;
     *current = *state;
 }
 }
 
 
@@ -1117,6 +1143,8 @@ static bool PSP_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd,
             PSP_BlendState state = {
             PSP_BlendState state = {
                 .color = drawstate.color,
                 .color = drawstate.color,
                 .texture = NULL,
                 .texture = NULL,
+                .texture_scale_mode = SDL_SCALEMODE_INVALID,
+                .texture_address_mode = SDL_TEXTURE_ADDRESS_INVALID,
                 .mode = cmd->data.draw.blend,
                 .mode = cmd->data.draw.blend,
                 .shadeModel = GU_FLAT
                 .shadeModel = GU_FLAT
             };
             };
@@ -1132,6 +1160,8 @@ static bool PSP_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd,
             PSP_BlendState state = {
             PSP_BlendState state = {
                 .color = drawstate.color,
                 .color = drawstate.color,
                 .texture = NULL,
                 .texture = NULL,
+                .texture_scale_mode = SDL_SCALEMODE_INVALID,
+                .texture_address_mode = SDL_TEXTURE_ADDRESS_INVALID,
                 .mode = cmd->data.draw.blend,
                 .mode = cmd->data.draw.blend,
                 .shadeModel = GU_FLAT
                 .shadeModel = GU_FLAT
             };
             };
@@ -1147,6 +1177,8 @@ static bool PSP_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd,
             PSP_BlendState state = {
             PSP_BlendState state = {
                 .color = drawstate.color,
                 .color = drawstate.color,
                 .texture = NULL,
                 .texture = NULL,
+                .texture_scale_mode = SDL_SCALEMODE_INVALID,
+                .texture_address_mode = SDL_TEXTURE_ADDRESS_INVALID,
                 .mode = cmd->data.draw.blend,
                 .mode = cmd->data.draw.blend,
                 .shadeModel = GU_FLAT
                 .shadeModel = GU_FLAT
             };
             };
@@ -1162,6 +1194,8 @@ static bool PSP_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd,
             PSP_BlendState state = {
             PSP_BlendState state = {
                 .color = drawstate.color,
                 .color = drawstate.color,
                 .texture = cmd->data.draw.texture,
                 .texture = cmd->data.draw.texture,
+                .texture_scale_mode = cmd->data.draw.texture_scale_mode,
+                .texture_address_mode = cmd->data.draw.texture_address_mode,
                 .mode = cmd->data.draw.blend,
                 .mode = cmd->data.draw.blend,
                 .shadeModel = GU_SMOOTH
                 .shadeModel = GU_SMOOTH
             };
             };
@@ -1176,6 +1210,8 @@ static bool PSP_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd,
             PSP_BlendState state = {
             PSP_BlendState state = {
                 .color = drawstate.color,
                 .color = drawstate.color,
                 .texture = cmd->data.draw.texture,
                 .texture = cmd->data.draw.texture,
+                .texture_scale_mode = cmd->data.draw.texture_scale_mode,
+                .texture_address_mode = cmd->data.draw.texture_address_mode,
                 .mode = cmd->data.draw.blend,
                 .mode = cmd->data.draw.blend,
                 .shadeModel = GU_SMOOTH
                 .shadeModel = GU_SMOOTH
             };
             };
@@ -1197,11 +1233,12 @@ static bool PSP_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd,
                 const VertTCV *verts = (VertTCV *)(gpumem + cmd->data.draw.first);
                 const VertTCV *verts = (VertTCV *)(gpumem + cmd->data.draw.first);
                 PSP_BlendState state = {
                 PSP_BlendState state = {
                     .color = drawstate.color,
                     .color = drawstate.color,
-                    .texture = NULL,
+                    .texture = cmd->data.draw.texture,
+                    .texture_scale_mode = cmd->data.draw.texture_scale_mode,
+                    .texture_address_mode = cmd->data.draw.texture_address_mode,
                     .mode = cmd->data.draw.blend,
                     .mode = cmd->data.draw.blend,
-                    .shadeModel = GU_FLAT
+                    .shadeModel = GU_SMOOTH
                 };
                 };
-                TextureActivate(cmd->data.draw.texture);
                 PSP_SetBlendState(data, &state);
                 PSP_SetBlendState(data, &state);
                 sceGuDrawArray(GU_TRIANGLES, GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF | GU_TRANSFORM_2D, count, 0, verts);
                 sceGuDrawArray(GU_TRIANGLES, GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF | GU_TRANSFORM_2D, count, 0, verts);
             }
             }
@@ -1310,7 +1347,6 @@ static bool PSP_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P
     renderer->UpdateTexture = PSP_UpdateTexture;
     renderer->UpdateTexture = PSP_UpdateTexture;
     renderer->LockTexture = PSP_LockTexture;
     renderer->LockTexture = PSP_LockTexture;
     renderer->UnlockTexture = PSP_UnlockTexture;
     renderer->UnlockTexture = PSP_UnlockTexture;
-    renderer->SetTextureScaleMode = PSP_SetTextureScaleMode;
     renderer->SetRenderTarget = PSP_SetRenderTarget;
     renderer->SetRenderTarget = PSP_SetRenderTarget;
     renderer->QueueSetViewport = PSP_QueueNoOp;
     renderer->QueueSetViewport = PSP_QueueNoOp;
     renderer->QueueSetDrawColor = PSP_QueueNoOp;
     renderer->QueueSetDrawColor = PSP_QueueNoOp;

+ 10 - 15
libs/SDL3/src/render/software/SDL_render_sw.c

@@ -171,10 +171,6 @@ static void SW_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
 {
 {
 }
 }
 
 
-static void SW_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode)
-{
-}
-
 static bool SW_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 static bool SW_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 {
 {
     SW_RenderData *data = (SW_RenderData *)renderer->internal;
     SW_RenderData *data = (SW_RenderData *)renderer->internal;
@@ -317,7 +313,7 @@ static bool Blit_to_Screen(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *sur
 
 
 static bool SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Texture *texture,
 static bool SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Texture *texture,
                             const SDL_Rect *srcrect, const SDL_Rect *final_rect,
                             const SDL_Rect *srcrect, const SDL_Rect *final_rect,
-                            const double angle, const SDL_FPoint *center, const SDL_FlipMode flip, float scale_x, float scale_y)
+                            const double angle, const SDL_FPoint *center, const SDL_FlipMode flip, float scale_x, float scale_y, const SDL_ScaleMode scaleMode)
 {
 {
     SDL_Surface *src = (SDL_Surface *)texture->internal;
     SDL_Surface *src = (SDL_Surface *)texture->internal;
     SDL_Rect tmp_rect;
     SDL_Rect tmp_rect;
@@ -412,7 +408,7 @@ static bool SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Te
             result = false;
             result = false;
         } else {
         } else {
             SDL_SetSurfaceBlendMode(src_clone, SDL_BLENDMODE_NONE);
             SDL_SetSurfaceBlendMode(src_clone, SDL_BLENDMODE_NONE);
-            result = SDL_BlitSurfaceScaled(src_clone, srcrect, src_scaled, &scale_rect, texture->scaleMode);
+            result = SDL_BlitSurfaceScaled(src_clone, srcrect, src_scaled, &scale_rect, scaleMode);
             SDL_DestroySurface(src_clone);
             SDL_DestroySurface(src_clone);
             src_clone = src_scaled;
             src_clone = src_scaled;
             src_scaled = NULL;
             src_scaled = NULL;
@@ -429,7 +425,7 @@ static bool SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Te
         SDLgfx_rotozoomSurfaceSizeTrig(tmp_rect.w, tmp_rect.h, angle, center,
         SDLgfx_rotozoomSurfaceSizeTrig(tmp_rect.w, tmp_rect.h, angle, center,
                                        &rect_dest, &cangle, &sangle);
                                        &rect_dest, &cangle, &sangle);
         src_rotated = SDLgfx_rotateSurface(src_clone, angle,
         src_rotated = SDLgfx_rotateSurface(src_clone, angle,
-                                           (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? 0 : 1, flip & SDL_FLIP_HORIZONTAL, flip & SDL_FLIP_VERTICAL,
+                                           (scaleMode == SDL_SCALEMODE_NEAREST) ? 0 : 1, flip & SDL_FLIP_HORIZONTAL, flip & SDL_FLIP_VERTICAL,
                                            &rect_dest, cangle, sangle, center);
                                            &rect_dest, cangle, sangle, center);
         if (!src_rotated) {
         if (!src_rotated) {
             result = false;
             result = false;
@@ -460,7 +456,7 @@ static bool SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Te
                     SDL_SetSurfaceColorMod(src_rotated, rMod, gMod, bMod);
                     SDL_SetSurfaceColorMod(src_rotated, rMod, gMod, bMod);
                 }
                 }
                 // Renderer scaling, if needed
                 // Renderer scaling, if needed
-                result = Blit_to_Screen(src_rotated, NULL, surface, &tmp_rect, scale_x, scale_y, texture->scaleMode);
+                result = Blit_to_Screen(src_rotated, NULL, surface, &tmp_rect, scale_x, scale_y, scaleMode);
             } else {
             } else {
                 /* The NONE blend mode requires three steps to get the pixels onto the destination surface.
                 /* The NONE blend mode requires three steps to get the pixels onto the destination surface.
                  * First, the area where the rotated pixels will be blitted to get set to zero.
                  * First, the area where the rotated pixels will be blitted to get set to zero.
@@ -470,7 +466,7 @@ static bool SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Te
                 SDL_Rect mask_rect = tmp_rect;
                 SDL_Rect mask_rect = tmp_rect;
                 SDL_SetSurfaceBlendMode(mask_rotated, SDL_BLENDMODE_NONE);
                 SDL_SetSurfaceBlendMode(mask_rotated, SDL_BLENDMODE_NONE);
                 // Renderer scaling, if needed
                 // Renderer scaling, if needed
-                result = Blit_to_Screen(mask_rotated, NULL, surface, &mask_rect, scale_x, scale_y, texture->scaleMode);
+                result = Blit_to_Screen(mask_rotated, NULL, surface, &mask_rect, scale_x, scale_y, scaleMode);
                 if (result) {
                 if (result) {
                     /* The next step copies the alpha value. This is done with the BLEND blend mode and
                     /* The next step copies the alpha value. This is done with the BLEND blend mode and
                      * by modulating the source colors with 0. Since the destination is all zeros, this
                      * by modulating the source colors with 0. Since the destination is all zeros, this
@@ -479,7 +475,7 @@ static bool SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Te
                     SDL_SetSurfaceColorMod(src_rotated, 0, 0, 0);
                     SDL_SetSurfaceColorMod(src_rotated, 0, 0, 0);
                     mask_rect = tmp_rect;
                     mask_rect = tmp_rect;
                     // Renderer scaling, if needed
                     // Renderer scaling, if needed
-                    result = Blit_to_Screen(src_rotated, NULL, surface, &mask_rect, scale_x, scale_y, texture->scaleMode);
+                    result = Blit_to_Screen(src_rotated, NULL, surface, &mask_rect, scale_x, scale_y, scaleMode);
                     if (result) {
                     if (result) {
                         /* The last step gets the color values in place. The ADD blend mode simply adds them to
                         /* The last step gets the color values in place. The ADD blend mode simply adds them to
                          * the destination (where the color values are all zero). However, because the ADD blend
                          * the destination (where the color values are all zero). However, because the ADD blend
@@ -492,7 +488,7 @@ static bool SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Te
                         } else {
                         } else {
                             SDL_SetSurfaceBlendMode(src_rotated_rgb, SDL_BLENDMODE_ADD);
                             SDL_SetSurfaceBlendMode(src_rotated_rgb, SDL_BLENDMODE_ADD);
                             // Renderer scaling, if needed
                             // Renderer scaling, if needed
-                            result = Blit_to_Screen(src_rotated_rgb, NULL, surface, &tmp_rect, scale_x, scale_y, texture->scaleMode);
+                            result = Blit_to_Screen(src_rotated_rgb, NULL, surface, &tmp_rect, scale_x, scale_y, scaleMode);
                             SDL_DestroySurface(src_rotated_rgb);
                             SDL_DestroySurface(src_rotated_rgb);
                         }
                         }
                     }
                     }
@@ -858,7 +854,7 @@ static bool SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, v
                         SDL_SetSurfaceColorMod(src, 255, 255, 255);
                         SDL_SetSurfaceColorMod(src, 255, 255, 255);
                         SDL_SetSurfaceAlphaMod(src, 255);
                         SDL_SetSurfaceAlphaMod(src, 255);
 
 
-                        SDL_BlitSurfaceScaled(src, srcrect, tmp, &r, texture->scaleMode);
+                        SDL_BlitSurfaceScaled(src, srcrect, tmp, &r, cmd->data.draw.texture_scale_mode);
 
 
                         SDL_SetSurfaceColorMod(tmp, rMod, gMod, bMod);
                         SDL_SetSurfaceColorMod(tmp, rMod, gMod, bMod);
                         SDL_SetSurfaceAlphaMod(tmp, alphaMod);
                         SDL_SetSurfaceAlphaMod(tmp, alphaMod);
@@ -869,7 +865,7 @@ static bool SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, v
                         // No need to set back r/g/b/a/blendmode to 'src' since it's done in PrepTextureForCopy()
                         // No need to set back r/g/b/a/blendmode to 'src' since it's done in PrepTextureForCopy()
                     }
                     }
                 } else {
                 } else {
-                    SDL_BlitSurfaceScaled(src, srcrect, surface, dstrect, texture->scaleMode);
+                    SDL_BlitSurfaceScaled(src, srcrect, surface, dstrect, cmd->data.draw.texture_scale_mode);
                 }
                 }
             }
             }
             break;
             break;
@@ -889,7 +885,7 @@ static bool SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, v
 
 
             SW_RenderCopyEx(renderer, surface, cmd->data.draw.texture, &copydata->srcrect,
             SW_RenderCopyEx(renderer, surface, cmd->data.draw.texture, &copydata->srcrect,
                             &copydata->dstrect, copydata->angle, &copydata->center, copydata->flip,
                             &copydata->dstrect, copydata->angle, &copydata->center, copydata->flip,
-                            copydata->scale_x, copydata->scale_y);
+                            copydata->scale_x, copydata->scale_y, cmd->data.draw.texture_scale_mode);
             break;
             break;
         }
         }
 
 
@@ -1135,7 +1131,6 @@ bool SW_CreateRendererForSurface(SDL_Renderer *renderer, SDL_Surface *surface, S
     renderer->UpdateTexture = SW_UpdateTexture;
     renderer->UpdateTexture = SW_UpdateTexture;
     renderer->LockTexture = SW_LockTexture;
     renderer->LockTexture = SW_LockTexture;
     renderer->UnlockTexture = SW_UnlockTexture;
     renderer->UnlockTexture = SW_UnlockTexture;
-    renderer->SetTextureScaleMode = SW_SetTextureScaleMode;
     renderer->SetRenderTarget = SW_SetRenderTarget;
     renderer->SetRenderTarget = SW_SetRenderTarget;
     renderer->QueueSetViewport = SW_QueueNoOp;
     renderer->QueueSetViewport = SW_QueueNoOp;
     renderer->QueueSetDrawColor = SW_QueueNoOp;
     renderer->QueueSetDrawColor = SW_QueueNoOp;

+ 36 - 25
libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm.c

@@ -70,8 +70,6 @@ static bool VITA_GXM_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture,
 static void VITA_GXM_UnlockTexture(SDL_Renderer *renderer,
 static void VITA_GXM_UnlockTexture(SDL_Renderer *renderer,
                                    SDL_Texture *texture);
                                    SDL_Texture *texture);
 
 
-static void VITA_GXM_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode);
-
 static bool VITA_GXM_SetRenderTarget(SDL_Renderer *renderer,
 static bool VITA_GXM_SetRenderTarget(SDL_Renderer *renderer,
                                     SDL_Texture *texture);
                                     SDL_Texture *texture);
 
 
@@ -216,7 +214,6 @@ static bool VITA_GXM_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window,
 #endif
 #endif
     renderer->LockTexture = VITA_GXM_LockTexture;
     renderer->LockTexture = VITA_GXM_LockTexture;
     renderer->UnlockTexture = VITA_GXM_UnlockTexture;
     renderer->UnlockTexture = VITA_GXM_UnlockTexture;
-    renderer->SetTextureScaleMode = VITA_GXM_SetTextureScaleMode;
     renderer->SetRenderTarget = VITA_GXM_SetRenderTarget;
     renderer->SetRenderTarget = VITA_GXM_SetRenderTarget;
     renderer->QueueSetViewport = VITA_GXM_QueueNoOp;
     renderer->QueueSetViewport = VITA_GXM_QueueNoOp;
     renderer->QueueSetDrawColor = VITA_GXM_QueueSetDrawColor;
     renderer->QueueSetDrawColor = VITA_GXM_QueueSetDrawColor;
@@ -295,9 +292,10 @@ static bool VITA_GXM_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
         return SDL_OutOfMemory();
         return SDL_OutOfMemory();
     }
     }
 
 
-    texture->internal = vita_texture;
+    vita_texture->scale_mode = SDL_SCALEMODE_INVALID;
+    vita_texture->address_mode = SDL_TEXTURE_ADDRESS_INVALID;
 
 
-    VITA_GXM_SetTextureScaleMode(renderer, texture, texture->scaleMode);
+    texture->internal = vita_texture;
 
 
 #ifdef SDL_HAVE_YUV
 #ifdef SDL_HAVE_YUV
     vita_texture->yuv = ((texture->format == SDL_PIXELFORMAT_IYUV) || (texture->format == SDL_PIXELFORMAT_YV12));
     vita_texture->yuv = ((texture->format == SDL_PIXELFORMAT_IYUV) || (texture->format == SDL_PIXELFORMAT_YV12));
@@ -582,25 +580,6 @@ static void VITA_GXM_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
     // This really improves framerate when using lock/unlock.
     // This really improves framerate when using lock/unlock.
 }
 }
 
 
-static void VITA_GXM_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode)
-{
-    VITA_GXM_TextureData *vita_texture = (VITA_GXM_TextureData *)texture->internal;
-
-    /*
-     set texture filtering according to scaleMode
-     supported hint values are nearest (0, default) or linear (1)
-     vitaScaleMode is either SCE_GXM_TEXTURE_FILTER_POINT (good for tile-map)
-     or SCE_GXM_TEXTURE_FILTER_LINEAR (good for scaling)
-     */
-
-    int vitaScaleMode = (scaleMode == SDL_SCALEMODE_NEAREST
-                             ? SCE_GXM_TEXTURE_FILTER_POINT
-                             : SCE_GXM_TEXTURE_FILTER_LINEAR);
-    gxm_texture_set_filters(vita_texture->tex, vitaScaleMode, vitaScaleMode);
-
-    return;
-}
-
 static bool VITA_GXM_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 static bool VITA_GXM_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 {
 {
     return true;
     return true;
@@ -909,9 +888,41 @@ static bool SetDrawState(VITA_GXM_RenderData *data, const SDL_RenderCommand *cmd
         }
         }
     }
     }
 
 
+    if (texture) {
+        VITA_GXM_TextureData *vita_texture = (VITA_GXM_TextureData *)texture->internal;
+
+        if (cmd->data.draw.texture_scale_mode != vita_texture->scale_mode) {
+            switch (cmd->data.draw.texture_scale_mode) {
+            case SDL_SCALEMODE_NEAREST:
+                gxm_texture_set_filters(vita_texture->tex, SCE_GXM_TEXTURE_FILTER_POINT, SCE_GXM_TEXTURE_FILTER_POINT);
+                break;
+            case SDL_SCALEMODE_LINEAR:
+                gxm_texture_set_filters(vita_texture->tex, SCE_GXM_TEXTURE_FILTER_LINEAR, SCE_GXM_TEXTURE_FILTER_LINEAR);
+                break;
+            default:
+                break;
+            }
+            vita_texture->scale_mode = cmd->data.draw.texture_scale_mode;
+        }
+
+        if (cmd->data.draw.texture_address_mode != vita_texture->address_mode) {
+            switch (cmd->data.draw.texture_address_mode) {
+            case SDL_TEXTURE_ADDRESS_CLAMP:
+                gxm_texture_set_address_mode(vita_texture->tex, SCE_GXM_TEXTURE_ADDR_CLAMP, SCE_GXM_TEXTURE_ADDR_CLAMP);
+                break;
+            case SDL_TEXTURE_ADDRESS_WRAP:
+                gxm_texture_set_address_mode(vita_texture->tex, SCE_GXM_TEXTURE_ADDR_REPEAT, SCE_GXM_TEXTURE_ADDR_REPEAT);
+                break;
+            default:
+                break;
+            }
+            vita_texture->address_mode = cmd->data.draw.texture_address_mode;
+        }
+    }
+
     if (texture != data->drawstate.texture) {
     if (texture != data->drawstate.texture) {
         if (texture) {
         if (texture) {
-            VITA_GXM_TextureData *vita_texture = (VITA_GXM_TextureData *)cmd->data.draw.texture->internal;
+            VITA_GXM_TextureData *vita_texture = (VITA_GXM_TextureData *)texture->internal;
             sceGxmSetFragmentTexture(data->gxm_context, 0, &vita_texture->tex->gxm_tex);
             sceGxmSetFragmentTexture(data->gxm_context, 0, &vita_texture->tex->gxm_tex);
         }
         }
         data->drawstate.texture = texture;
         data->drawstate.texture = texture;

+ 6 - 0
libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_tools.c

@@ -1122,6 +1122,12 @@ gxm_texture *create_gxm_texture(VITA_GXM_RenderData *data, unsigned int w, unsig
     return texture;
     return texture;
 }
 }
 
 
+void gxm_texture_set_address_mode(gxm_texture *texture, SceGxmTextureAddrMode u_mode, SceGxmTextureAddrMode v_mode)
+{
+    sceGxmTextureSetUAddrMode(&texture->gxm_tex, u_mode);
+    sceGxmTextureSetVAddrMode(&texture->gxm_tex, v_mode);
+}
+
 void gxm_texture_set_filters(gxm_texture *texture, SceGxmTextureFilter min_filter, SceGxmTextureFilter mag_filter)
 void gxm_texture_set_filters(gxm_texture *texture, SceGxmTextureFilter min_filter, SceGxmTextureFilter mag_filter)
 {
 {
     sceGxmTextureSetMinFilter(&texture->gxm_tex, min_filter);
     sceGxmTextureSetMinFilter(&texture->gxm_tex, min_filter);

+ 1 - 0
libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_tools.h

@@ -49,6 +49,7 @@ void gxm_finish(SDL_Renderer *renderer);
 gxm_texture *create_gxm_texture(VITA_GXM_RenderData *data, unsigned int w, unsigned int h, SceGxmTextureFormat format, unsigned int isRenderTarget, unsigned int *return_w, unsigned int *return_h, unsigned int *return_pitch, float *return_wscale);
 gxm_texture *create_gxm_texture(VITA_GXM_RenderData *data, unsigned int w, unsigned int h, SceGxmTextureFormat format, unsigned int isRenderTarget, unsigned int *return_w, unsigned int *return_h, unsigned int *return_pitch, float *return_wscale);
 void free_gxm_texture(VITA_GXM_RenderData *data, gxm_texture *texture);
 void free_gxm_texture(VITA_GXM_RenderData *data, gxm_texture *texture);
 
 
+void gxm_texture_set_address_mode(gxm_texture *texture, SceGxmTextureAddrMode u_mode, SceGxmTextureAddrMode v_mode);
 void gxm_texture_set_filters(gxm_texture *texture, SceGxmTextureFilter min_filter, SceGxmTextureFilter mag_filter);
 void gxm_texture_set_filters(gxm_texture *texture, SceGxmTextureFilter min_filter, SceGxmTextureFilter mag_filter);
 SceGxmTextureFormat gxm_texture_get_format(const gxm_texture *texture);
 SceGxmTextureFormat gxm_texture_get_format(const gxm_texture *texture);
 
 

+ 2 - 0
libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_types.h

@@ -205,6 +205,8 @@ typedef struct
     float wscale;
     float wscale;
     bool yuv;
     bool yuv;
     bool nv12;
     bool nv12;
+    SDL_ScaleMode scale_mode;
+    SDL_TextureAddressMode address_mode;
 } VITA_GXM_TextureData;
 } VITA_GXM_TextureData;
 
 
 #endif // SDL_RENDER_VITA_GXM_TYPES_H
 #endif // SDL_RENDER_VITA_GXM_TYPES_H

+ 2 - 16
libs/SDL3/src/render/vulkan/SDL_render_vulkan.c

@@ -255,7 +255,6 @@ typedef struct
     VkRenderPass mainRenderpasses[VULKAN_RENDERPASS_COUNT];
     VkRenderPass mainRenderpasses[VULKAN_RENDERPASS_COUNT];
     VkFramebuffer mainFramebuffer;
     VkFramebuffer mainFramebuffer;
     VULKAN_Buffer stagingBuffer;
     VULKAN_Buffer stagingBuffer;
-    VkFilter scaleMode;
     SDL_Rect lockedRect;
     SDL_Rect lockedRect;
     int width;
     int width;
     int height;
     int height;
@@ -2600,7 +2599,6 @@ static bool VULKAN_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, S
     } else {
     } else {
         textureData->shader = SHADER_ADVANCED;
         textureData->shader = SHADER_ADVANCED;
     }
     }
-    textureData->scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
 
 
 #ifdef SDL_HAVE_YUV
 #ifdef SDL_HAVE_YUV
     // YUV textures must have even width and height.  Also create Ycbcr conversion
     // YUV textures must have even width and height.  Also create Ycbcr conversion
@@ -3093,17 +3091,6 @@ static void VULKAN_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
     VULKAN_DestroyBuffer(rendererData, &textureData->stagingBuffer);
     VULKAN_DestroyBuffer(rendererData, &textureData->stagingBuffer);
 }
 }
 
 
-static void VULKAN_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode)
-{
-    VULKAN_TextureData *textureData = (VULKAN_TextureData *)texture->internal;
-
-    if (!textureData) {
-        return;
-    }
-
-    textureData->scaleMode = (scaleMode == SDL_SCALEMODE_NEAREST) ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
-}
-
 static bool VULKAN_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 static bool VULKAN_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 {
 {
     VULKAN_RenderData *rendererData = (VULKAN_RenderData *)renderer->internal;
     VULKAN_RenderData *rendererData = (VULKAN_RenderData *)renderer->internal;
@@ -3775,7 +3762,7 @@ static bool VULKAN_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand
 
 
     VULKAN_SetupShaderConstants(renderer, cmd, texture, &constants);
     VULKAN_SetupShaderConstants(renderer, cmd, texture, &constants);
 
 
-    switch (textureData->scaleMode) {
+    switch (cmd->data.draw.texture_scale_mode) {
     case VK_FILTER_NEAREST:
     case VK_FILTER_NEAREST:
         switch (cmd->data.draw.texture_address_mode) {
         switch (cmd->data.draw.texture_address_mode) {
         case SDL_TEXTURE_ADDRESS_CLAMP:
         case SDL_TEXTURE_ADDRESS_CLAMP:
@@ -3801,7 +3788,7 @@ static bool VULKAN_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand
         }
         }
         break;
         break;
     default:
     default:
-        return SDL_SetError("Unknown scale mode: %d", textureData->scaleMode);
+        return SDL_SetError("Unknown scale mode: %d", cmd->data.draw.texture_scale_mode);
     }
     }
 
 
     if (textureData->mainImage.imageLayout != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
     if (textureData->mainImage.imageLayout != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
@@ -4290,7 +4277,6 @@ static bool VULKAN_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SD
 #endif
 #endif
     renderer->LockTexture = VULKAN_LockTexture;
     renderer->LockTexture = VULKAN_LockTexture;
     renderer->UnlockTexture = VULKAN_UnlockTexture;
     renderer->UnlockTexture = VULKAN_UnlockTexture;
-    renderer->SetTextureScaleMode = VULKAN_SetTextureScaleMode;
     renderer->SetRenderTarget = VULKAN_SetRenderTarget;
     renderer->SetRenderTarget = VULKAN_SetRenderTarget;
     renderer->QueueSetViewport = VULKAN_QueueNoOp;
     renderer->QueueSetViewport = VULKAN_QueueNoOp;
     renderer->QueueSetDrawColor = VULKAN_QueueNoOp;
     renderer->QueueSetDrawColor = VULKAN_QueueNoOp;

+ 6 - 0
libs/SDL3/src/stdlib/SDL_stdlib.c

@@ -533,6 +533,7 @@ void *SDL_aligned_alloc(size_t alignment, size_t size)
 {
 {
     size_t padding;
     size_t padding;
     Uint8 *result = NULL;
     Uint8 *result = NULL;
+    size_t requested_size = size;
 
 
     if (alignment < sizeof(void*)) {
     if (alignment < sizeof(void*)) {
         alignment = sizeof(void*);
         alignment = sizeof(void*);
@@ -552,6 +553,11 @@ void *SDL_aligned_alloc(size_t alignment, size_t size)
 
 
             // Store the original pointer right before the returned value
             // Store the original pointer right before the returned value
             SDL_memcpy(result - sizeof(original), &original, sizeof(original));
             SDL_memcpy(result - sizeof(original), &original, sizeof(original));
+
+            // Initialize the padding to zero
+            if (padding > 0) {
+                SDL_memset(result + requested_size, 0, padding);
+            }
         }
         }
     }
     }
     return result;
     return result;

+ 10 - 14
libs/SDL3/src/stdlib/SDL_string.c

@@ -368,14 +368,12 @@ static size_t SDL_ScanUnsignedLongLongInternal(const char *text, int count, int
             negative = *text == '-';
             negative = *text == '-';
             ++text;
             ++text;
         }
         }
-        if ((radix == 0 || radix == 16) && *text == '0' && text[1] != '\0') {
+        if ((radix == 0 || radix == 16) && *text == '0' && (text[1] == 'x' || text[1] == 'X')) {
+            text += 2;
+            radix = 16;
+        } else if (radix == 0 && *text == '0' && (text[1] >= '0' && text[1] <= '9')) {
             ++text;
             ++text;
-            if (*text == 'x' || *text == 'X') {
-                radix = 16;
-                ++text;
-            } else if (radix == 0) {
-                radix = 8;
-            }
+            radix = 8;
         } else if (radix == 0) {
         } else if (radix == 0) {
             radix = 10;
             radix = 10;
         }
         }
@@ -462,14 +460,12 @@ static size_t SDL_ScanUnsignedLongLongInternalW(const wchar_t *text, int count,
             negative = *text == '-';
             negative = *text == '-';
             ++text;
             ++text;
         }
         }
-        if ((radix == 0 || radix == 16) && *text == '0') {
+        if ((radix == 0 || radix == 16) && *text == '0' && (text[1] == 'x' || text[1] == 'X')) {
+            text += 2;
+            radix = 16;
+        } else if (radix == 0 && *text == '0' && (text[1] >= '0' && text[1] <= '9')) {
             ++text;
             ++text;
-            if (*text == 'x' || *text == 'X') {
-                radix = 16;
-                ++text;
-            } else if (radix == 0) {
-                radix = 8;
-            }
+            radix = 8;
         } else if (radix == 0) {
         } else if (radix == 0) {
             radix = 10;
             radix = 10;
         }
         }

+ 0 - 1
libs/SDL3/src/test/SDL_test_common.c

@@ -55,7 +55,6 @@ static const char *video_usage[] = {
     "[--input-focus]",
     "[--input-focus]",
     "[--keyboard-grab]",
     "[--keyboard-grab]",
     "[--logical-presentation disabled|match|stretch|letterbox|overscan|integer_scale]",
     "[--logical-presentation disabled|match|stretch|letterbox|overscan|integer_scale]",
-    "[--logical-scale-quality nearest|linear|best]",
     "[--logical WxH]",
     "[--logical WxH]",
     "[--max-geometry WxH]",
     "[--max-geometry WxH]",
     "[--maximize]",
     "[--maximize]",

+ 1 - 0
libs/SDL3/src/test/SDL_test_memory.c

@@ -454,4 +454,5 @@ void SDLTest_LogAllocations(void)
 #undef ADD_LINE
 #undef ADD_LINE
 
 
     SDL_Log("%s", message);
     SDL_Log("%s", message);
+    SDL_free_orig(message);
 }
 }

+ 1 - 1
libs/SDL3/src/thread/n3ds/SDL_syssem.c

@@ -102,7 +102,7 @@ Uint32 SDL_GetSemaphoreValue(SDL_Semaphore *sem)
 
 
 void SDL_SignalSemaphore(SDL_Semaphore *sem)
 void SDL_SignalSemaphore(SDL_Semaphore *sem)
 {
 {
-    if (sem) {
+    if (!sem) {
         return;
         return;
     }
     }
 
 

+ 17 - 0
libs/SDL3/src/time/unix/SDL_systime.c

@@ -27,6 +27,7 @@
 #include <langinfo.h>
 #include <langinfo.h>
 #include <sys/time.h>
 #include <sys/time.h>
 #include <time.h>
 #include <time.h>
+#include <unistd.h>
 
 
 #if !defined(HAVE_CLOCK_GETTIME) && defined(SDL_PLATFORM_APPLE)
 #if !defined(HAVE_CLOCK_GETTIME) && defined(SDL_PLATFORM_APPLE)
 #include <mach/clock.h>
 #include <mach/clock.h>
@@ -186,7 +187,23 @@ bool SDL_TimeToDateTime(SDL_Time ticks, SDL_DateTime *dt, bool localTime)
         dt->second = tm->tm_sec;
         dt->second = tm->tm_sec;
         dt->nanosecond = ticks % SDL_NS_PER_SECOND;
         dt->nanosecond = ticks % SDL_NS_PER_SECOND;
         dt->day_of_week = tm->tm_wday;
         dt->day_of_week = tm->tm_wday;
+
+        /* tm_gmtoff wasn't formally standardized until POSIX.1-2024, but practically it has been available on desktop
+         * *nix platforms such as Linux/glibc, FreeBSD, OpenBSD, NetBSD, OSX/macOS, and others since the 1990s.
+         *
+         * The notable exception is Solaris, where the timezone offset must still be retrieved in the strictly POSIX.1-2008
+         * compliant way.
+         */
+#if (_POSIX_VERSION >= 202405L) || (!defined(sun) && !defined(__sun))
         dt->utc_offset = (int)tm->tm_gmtoff;
         dt->utc_offset = (int)tm->tm_gmtoff;
+#else
+        if (localTime) {
+            tzset();
+            dt->utc_offset = (int)timezone;
+        } else {
+            dt->utc_offset = 0;
+        }
+#endif
 
 
         return true;
         return true;
     }
     }

+ 47 - 5
libs/SDL3/src/video/SDL_blit_A.c

@@ -22,6 +22,7 @@
 
 
 #ifdef SDL_HAVE_BLIT_A
 #ifdef SDL_HAVE_BLIT_A
 
 
+#include "SDL_pixels_c.h"
 #include "SDL_surface_c.h"
 #include "SDL_surface_c.h"
 
 
 // Functions to perform alpha blended blitting
 // Functions to perform alpha blended blitting
@@ -968,6 +969,10 @@ static void Blit8888to8888PixelAlphaSwizzle(SDL_BlitInfo *info)
     int dstskip = info->dst_skip;
     int dstskip = info->dst_skip;
     const SDL_PixelFormatDetails *srcfmt = info->src_fmt;
     const SDL_PixelFormatDetails *srcfmt = info->src_fmt;
     const SDL_PixelFormatDetails *dstfmt = info->dst_fmt;
     const SDL_PixelFormatDetails *dstfmt = info->dst_fmt;
+    bool fill_alpha = !dstfmt->Amask;
+    Uint32 dstAmask, dstAshift;
+
+    SDL_Get8888AlphaMaskAndShift(dstfmt, &dstAmask, &dstAshift);
 
 
     while (height--) {
     while (height--) {
         int i = 0;
         int i = 0;
@@ -976,6 +981,9 @@ static void Blit8888to8888PixelAlphaSwizzle(SDL_BlitInfo *info)
             Uint32 src32 = *(Uint32 *)src;
             Uint32 src32 = *(Uint32 *)src;
             Uint32 dst32 = *(Uint32 *)dst;
             Uint32 dst32 = *(Uint32 *)dst;
             ALPHA_BLEND_SWIZZLE_8888(src32, dst32, srcfmt, dstfmt);
             ALPHA_BLEND_SWIZZLE_8888(src32, dst32, srcfmt, dstfmt);
+            if (fill_alpha) {
+                dst32 |= dstAmask;
+            }
             *(Uint32 *)dst = dst32;
             *(Uint32 *)dst = dst32;
             src += 4;
             src += 4;
             dst += 4;
             dst += 4;
@@ -998,6 +1006,10 @@ static void SDL_TARGETING("sse4.1") Blit8888to8888PixelAlphaSwizzleSSE41(SDL_Bli
     int dstskip = info->dst_skip;
     int dstskip = info->dst_skip;
     const SDL_PixelFormatDetails *srcfmt = info->src_fmt;
     const SDL_PixelFormatDetails *srcfmt = info->src_fmt;
     const SDL_PixelFormatDetails *dstfmt = info->dst_fmt;
     const SDL_PixelFormatDetails *dstfmt = info->dst_fmt;
+    bool fill_alpha = !dstfmt->Amask;
+    Uint32 dstAmask, dstAshift;
+
+    SDL_Get8888AlphaMaskAndShift(dstfmt, &dstAmask, &dstAshift);
 
 
     // The byte offsets for the start of each pixel
     // The byte offsets for the start of each pixel
     const __m128i mask_offsets = _mm_set_epi8(
     const __m128i mask_offsets = _mm_set_epi8(
@@ -1011,7 +1023,7 @@ static void SDL_TARGETING("sse4.1") Blit8888to8888PixelAlphaSwizzleSSE41(SDL_Bli
         mask_offsets);
         mask_offsets);
 
 
     const __m128i alpha_splat_mask = _mm_add_epi8(_mm_set1_epi8(srcfmt->Ashift >> 3), mask_offsets);
     const __m128i alpha_splat_mask = _mm_add_epi8(_mm_set1_epi8(srcfmt->Ashift >> 3), mask_offsets);
-    const __m128i alpha_fill_mask = _mm_set1_epi32((int)dstfmt->Amask);
+    const __m128i alpha_fill_mask = _mm_set1_epi32((int)dstAmask);
 
 
     while (height--) {
     while (height--) {
         int i = 0;
         int i = 0;
@@ -1057,7 +1069,11 @@ static void SDL_TARGETING("sse4.1") Blit8888to8888PixelAlphaSwizzleSSE41(SDL_Bli
             dst_hi = _mm_mulhi_epu16(dst_hi, _mm_set1_epi16(257));
             dst_hi = _mm_mulhi_epu16(dst_hi, _mm_set1_epi16(257));
 
 
             // Blend the pixels together and save the result
             // Blend the pixels together and save the result
-            _mm_storeu_si128((__m128i *)dst, _mm_packus_epi16(dst_lo, dst_hi));
+            dst128 = _mm_packus_epi16(dst_lo, dst_hi);
+            if (fill_alpha) {
+                dst128 = _mm_or_si128(dst128, alpha_fill_mask);
+            }
+            _mm_storeu_si128((__m128i *)dst, dst128);
 
 
             src += 16;
             src += 16;
             dst += 16;
             dst += 16;
@@ -1067,6 +1083,9 @@ static void SDL_TARGETING("sse4.1") Blit8888to8888PixelAlphaSwizzleSSE41(SDL_Bli
             Uint32 src32 = *(Uint32 *)src;
             Uint32 src32 = *(Uint32 *)src;
             Uint32 dst32 = *(Uint32 *)dst;
             Uint32 dst32 = *(Uint32 *)dst;
             ALPHA_BLEND_SWIZZLE_8888(src32, dst32, srcfmt, dstfmt);
             ALPHA_BLEND_SWIZZLE_8888(src32, dst32, srcfmt, dstfmt);
+            if (fill_alpha) {
+                dst32 |= dstAmask;
+            }
             *(Uint32 *)dst = dst32;
             *(Uint32 *)dst = dst32;
             src += 4;
             src += 4;
             dst += 4;
             dst += 4;
@@ -1091,6 +1110,10 @@ static void SDL_TARGETING("avx2") Blit8888to8888PixelAlphaSwizzleAVX2(SDL_BlitIn
     int dstskip = info->dst_skip;
     int dstskip = info->dst_skip;
     const SDL_PixelFormatDetails *srcfmt = info->src_fmt;
     const SDL_PixelFormatDetails *srcfmt = info->src_fmt;
     const SDL_PixelFormatDetails *dstfmt = info->dst_fmt;
     const SDL_PixelFormatDetails *dstfmt = info->dst_fmt;
+    bool fill_alpha = !dstfmt->Amask;
+    Uint32 dstAmask, dstAshift;
+
+    SDL_Get8888AlphaMaskAndShift(dstfmt, &dstAmask, &dstAshift);
 
 
     // The byte offsets for the start of each pixel
     // The byte offsets for the start of each pixel
     const __m256i mask_offsets = _mm256_set_epi8(
     const __m256i mask_offsets = _mm256_set_epi8(
@@ -1104,7 +1127,7 @@ static void SDL_TARGETING("avx2") Blit8888to8888PixelAlphaSwizzleAVX2(SDL_BlitIn
         mask_offsets);
         mask_offsets);
 
 
     const __m256i alpha_splat_mask = _mm256_add_epi8(_mm256_set1_epi8(srcfmt->Ashift >> 3), mask_offsets);
     const __m256i alpha_splat_mask = _mm256_add_epi8(_mm256_set1_epi8(srcfmt->Ashift >> 3), mask_offsets);
-    const __m256i alpha_fill_mask = _mm256_set1_epi32((int)dstfmt->Amask);
+    const __m256i alpha_fill_mask = _mm256_set1_epi32((int)dstAmask);
 
 
     while (height--) {
     while (height--) {
         int i = 0;
         int i = 0;
@@ -1150,7 +1173,11 @@ static void SDL_TARGETING("avx2") Blit8888to8888PixelAlphaSwizzleAVX2(SDL_BlitIn
             dst_hi = _mm256_mulhi_epu16(dst_hi, _mm256_set1_epi16(257));
             dst_hi = _mm256_mulhi_epu16(dst_hi, _mm256_set1_epi16(257));
 
 
             // Blend the pixels together and save the result
             // Blend the pixels together and save the result
-            _mm256_storeu_si256((__m256i *)dst, _mm256_packus_epi16(dst_lo, dst_hi));
+            dst256 = _mm256_packus_epi16(dst_lo, dst_hi);
+            if (fill_alpha) {
+                dst256 = _mm256_or_si256(dst256, alpha_fill_mask);
+            }
+            _mm256_storeu_si256((__m256i *)dst, dst256);
 
 
             src += 32;
             src += 32;
             dst += 32;
             dst += 32;
@@ -1160,6 +1187,9 @@ static void SDL_TARGETING("avx2") Blit8888to8888PixelAlphaSwizzleAVX2(SDL_BlitIn
             Uint32 src32 = *(Uint32 *)src;
             Uint32 src32 = *(Uint32 *)src;
             Uint32 dst32 = *(Uint32 *)dst;
             Uint32 dst32 = *(Uint32 *)dst;
             ALPHA_BLEND_SWIZZLE_8888(src32, dst32, srcfmt, dstfmt);
             ALPHA_BLEND_SWIZZLE_8888(src32, dst32, srcfmt, dstfmt);
+            if (fill_alpha) {
+                dst32 |= dstAmask;
+            }
             *(Uint32 *)dst = dst32;
             *(Uint32 *)dst = dst32;
             src += 4;
             src += 4;
             dst += 4;
             dst += 4;
@@ -1184,6 +1214,10 @@ static void Blit8888to8888PixelAlphaSwizzleNEON(SDL_BlitInfo *info)
     int dstskip = info->dst_skip;
     int dstskip = info->dst_skip;
     const SDL_PixelFormatDetails *srcfmt = info->src_fmt;
     const SDL_PixelFormatDetails *srcfmt = info->src_fmt;
     const SDL_PixelFormatDetails *dstfmt = info->dst_fmt;
     const SDL_PixelFormatDetails *dstfmt = info->dst_fmt;
+    bool fill_alpha = !dstfmt->Amask;
+    Uint32 dstAmask, dstAshift;
+
+    SDL_Get8888AlphaMaskAndShift(dstfmt, &dstAmask, &dstAshift);
 
 
     // The byte offsets for the start of each pixel
     // The byte offsets for the start of each pixel
     const uint8x16_t mask_offsets = vreinterpretq_u8_u64(vcombine_u64(
     const uint8x16_t mask_offsets = vreinterpretq_u8_u64(vcombine_u64(
@@ -1197,7 +1231,7 @@ static void Blit8888to8888PixelAlphaSwizzleNEON(SDL_BlitInfo *info)
             ((srcfmt->Bshift >> 3) << dstfmt->Bshift))));
             ((srcfmt->Bshift >> 3) << dstfmt->Bshift))));
 
 
     const uint8x16_t alpha_splat_mask = vaddq_u8(vdupq_n_u8(srcfmt->Ashift >> 3), mask_offsets);
     const uint8x16_t alpha_splat_mask = vaddq_u8(vdupq_n_u8(srcfmt->Ashift >> 3), mask_offsets);
-    const uint8x16_t alpha_fill_mask = vreinterpretq_u8_u32(vdupq_n_u32(dstfmt->Amask));
+    const uint8x16_t alpha_fill_mask = vreinterpretq_u8_u32(vdupq_n_u32(dstAmask));
 
 
     while (height--) {
     while (height--) {
         int i = 0;
         int i = 0;
@@ -1242,6 +1276,10 @@ static void Blit8888to8888PixelAlphaSwizzleNEON(SDL_BlitInfo *info)
             // temp   = vraddhn_u16(res_lo, vrshrq_n_u16(res_lo, 8));
             // temp   = vraddhn_u16(res_lo, vrshrq_n_u16(res_lo, 8));
             // dst128 = vraddhn_high_u16(temp, res_hi, vrshrq_n_u16(res_hi, 8));
             // dst128 = vraddhn_high_u16(temp, res_hi, vrshrq_n_u16(res_hi, 8));
 
 
+            if (fill_alpha) {
+                dst128 = vorrq_u8(dst128, alpha_fill_mask);
+            }
+
             // Save the result
             // Save the result
             vst1q_u8(dst, dst128);
             vst1q_u8(dst, dst128);
 
 
@@ -1266,6 +1304,10 @@ static void Blit8888to8888PixelAlphaSwizzleNEON(SDL_BlitInfo *info)
 
 
             dst32 = vaddhn_u16(res, vshrq_n_u16(res, 8));
             dst32 = vaddhn_u16(res, vshrq_n_u16(res, 8));
 
 
+            if (fill_alpha) {
+                dst32 = vorr_u8(dst32, vget_low_u8(alpha_fill_mask));
+            }
+
             // Save the result, only low 32-bits
             // Save the result, only low 32-bits
             vst1_lane_u32((Uint32*)dst, vreinterpret_u32_u8(dst32), 0);
             vst1_lane_u32((Uint32*)dst, vreinterpret_u32_u8(dst32), 0);
 
 

+ 267 - 0
libs/SDL3/src/video/SDL_blit_N.c

@@ -22,6 +22,7 @@
 
 
 #ifdef SDL_HAVE_BLIT_N
 #ifdef SDL_HAVE_BLIT_N
 
 
+#include "SDL_pixels_c.h"
 #include "SDL_surface_c.h"
 #include "SDL_surface_c.h"
 #include "SDL_blit_copy.h"
 #include "SDL_blit_copy.h"
 
 
@@ -2551,6 +2552,255 @@ static void BlitNtoNKeyCopyAlpha(SDL_BlitInfo *info)
     }
     }
 }
 }
 
 
+// Convert between two 8888 pixels with differing formats.
+#define SWIZZLE_8888_SRC_ALPHA(src, dst, srcfmt, dstfmt)                \
+    do {                                                                \
+        dst = (((src >> srcfmt->Rshift) & 0xFF) << dstfmt->Rshift) |    \
+              (((src >> srcfmt->Gshift) & 0xFF) << dstfmt->Gshift) |    \
+              (((src >> srcfmt->Bshift) & 0xFF) << dstfmt->Bshift) |    \
+              (((src >> srcfmt->Ashift) & 0xFF) << dstfmt->Ashift);     \
+    } while (0)
+
+#define SWIZZLE_8888_DST_ALPHA(src, dst, srcfmt, dstfmt, dstAmask)      \
+    do {                                                                \
+        dst = (((src >> srcfmt->Rshift) & 0xFF) << dstfmt->Rshift) |    \
+              (((src >> srcfmt->Gshift) & 0xFF) << dstfmt->Gshift) |    \
+              (((src >> srcfmt->Bshift) & 0xFF) << dstfmt->Bshift) |    \
+              dstAmask;                                                 \
+    } while (0)
+
+#ifdef SDL_SSE4_1_INTRINSICS
+
+static void SDL_TARGETING("sse4.1") Blit8888to8888PixelSwizzleSSE41(SDL_BlitInfo *info)
+{
+    int width = info->dst_w;
+    int height = info->dst_h;
+    Uint8 *src = info->src;
+    int srcskip = info->src_skip;
+    Uint8 *dst = info->dst;
+    int dstskip = info->dst_skip;
+    const SDL_PixelFormatDetails *srcfmt = info->src_fmt;
+    const SDL_PixelFormatDetails *dstfmt = info->dst_fmt;
+    bool fill_alpha = (!srcfmt->Amask || !dstfmt->Amask);
+    Uint32 srcAmask, srcAshift;
+    Uint32 dstAmask, dstAshift;
+
+    SDL_Get8888AlphaMaskAndShift(srcfmt, &srcAmask, &srcAshift);
+    SDL_Get8888AlphaMaskAndShift(dstfmt, &dstAmask, &dstAshift);
+
+    // The byte offsets for the start of each pixel
+    const __m128i mask_offsets = _mm_set_epi8(
+        12, 12, 12, 12, 8, 8, 8, 8, 4, 4, 4, 4, 0, 0, 0, 0);
+
+    const __m128i convert_mask = _mm_add_epi32(
+        _mm_set1_epi32(
+            ((srcfmt->Rshift >> 3) << dstfmt->Rshift) |
+            ((srcfmt->Gshift >> 3) << dstfmt->Gshift) |
+            ((srcfmt->Bshift >> 3) << dstfmt->Bshift) |
+            ((srcAshift >> 3) << dstAshift)),
+        mask_offsets);
+
+    const __m128i alpha_fill_mask = _mm_set1_epi32((int)dstAmask);
+
+    while (height--) {
+        int i = 0;
+
+        for (; i + 4 <= width; i += 4) {
+            // Load 4 src pixels
+            __m128i src128 = _mm_loadu_si128((__m128i *)src);
+
+            // Convert to dst format
+            src128 = _mm_shuffle_epi8(src128, convert_mask);
+
+            if (fill_alpha) {
+                // Set the alpha channels of src to 255
+                src128 = _mm_or_si128(src128, alpha_fill_mask);
+            }
+
+            // Save the result
+            _mm_storeu_si128((__m128i *)dst, src128);
+
+            src += 16;
+            dst += 16;
+        }
+
+        for (; i < width; ++i) {
+            Uint32 src32 = *(Uint32 *)src;
+            Uint32 dst32;
+            if (fill_alpha) {
+                SWIZZLE_8888_DST_ALPHA(src32, dst32, srcfmt, dstfmt, dstAmask);
+            } else {
+                SWIZZLE_8888_SRC_ALPHA(src32, dst32, srcfmt, dstfmt);
+            }
+            *(Uint32 *)dst = dst32;
+            src += 4;
+            dst += 4;
+        }
+
+        src += srcskip;
+        dst += dstskip;
+    }
+}
+
+#endif
+
+#ifdef SDL_AVX2_INTRINSICS
+
+static void SDL_TARGETING("avx2") Blit8888to8888PixelSwizzleAVX2(SDL_BlitInfo *info)
+{
+    int width = info->dst_w;
+    int height = info->dst_h;
+    Uint8 *src = info->src;
+    int srcskip = info->src_skip;
+    Uint8 *dst = info->dst;
+    int dstskip = info->dst_skip;
+    const SDL_PixelFormatDetails *srcfmt = info->src_fmt;
+    const SDL_PixelFormatDetails *dstfmt = info->dst_fmt;
+    bool fill_alpha = (!srcfmt->Amask || !dstfmt->Amask);
+    Uint32 srcAmask, srcAshift;
+    Uint32 dstAmask, dstAshift;
+
+    SDL_Get8888AlphaMaskAndShift(srcfmt, &srcAmask, &srcAshift);
+    SDL_Get8888AlphaMaskAndShift(dstfmt, &dstAmask, &dstAshift);
+
+    // The byte offsets for the start of each pixel
+    const __m256i mask_offsets = _mm256_set_epi8(
+        28, 28, 28, 28, 24, 24, 24, 24, 20, 20, 20, 20, 16, 16, 16, 16, 12, 12, 12, 12, 8, 8, 8, 8, 4, 4, 4, 4, 0, 0, 0, 0);
+
+    const __m256i convert_mask = _mm256_add_epi32(
+        _mm256_set1_epi32(
+            ((srcfmt->Rshift >> 3) << dstfmt->Rshift) |
+            ((srcfmt->Gshift >> 3) << dstfmt->Gshift) |
+            ((srcfmt->Bshift >> 3) << dstfmt->Bshift) |
+            ((srcAshift >> 3) << dstAshift)),
+        mask_offsets);
+
+    const __m256i alpha_fill_mask = _mm256_set1_epi32((int)dstAmask);
+
+    while (height--) {
+        int i = 0;
+
+        for (; i + 8 <= width; i += 8) {
+            // Load 8 src pixels
+            __m256i src256 = _mm256_loadu_si256((__m256i *)src);
+
+            // Convert to dst format
+            src256 = _mm256_shuffle_epi8(src256, convert_mask);
+
+            if (fill_alpha) {
+                // Set the alpha channels of src to 255
+                src256 = _mm256_or_si256(src256, alpha_fill_mask);
+            }
+
+            // Save the result
+            _mm256_storeu_si256((__m256i *)dst, src256);
+
+            src += 32;
+            dst += 32;
+        }
+
+        for (; i < width; ++i) {
+            Uint32 src32 = *(Uint32 *)src;
+            Uint32 dst32;
+            if (fill_alpha) {
+                SWIZZLE_8888_DST_ALPHA(src32, dst32, srcfmt, dstfmt, dstAmask);
+            } else {
+                SWIZZLE_8888_SRC_ALPHA(src32, dst32, srcfmt, dstfmt);
+            }
+            *(Uint32 *)dst = dst32;
+            src += 4;
+            dst += 4;
+        }
+
+        src += srcskip;
+        dst += dstskip;
+    }
+}
+
+#endif
+
+#if defined(SDL_NEON_INTRINSICS) && (__ARM_ARCH >= 8)
+
+static void Blit8888to8888PixelSwizzleNEON(SDL_BlitInfo *info)
+{
+    int width = info->dst_w;
+    int height = info->dst_h;
+    Uint8 *src = info->src;
+    int srcskip = info->src_skip;
+    Uint8 *dst = info->dst;
+    int dstskip = info->dst_skip;
+    const SDL_PixelFormatDetails *srcfmt = info->src_fmt;
+    const SDL_PixelFormatDetails *dstfmt = info->dst_fmt;
+    bool fill_alpha = (!srcfmt->Amask || !dstfmt->Amask);
+    Uint32 srcAmask, srcAshift;
+    Uint32 dstAmask, dstAshift;
+
+    SDL_Get8888AlphaMaskAndShift(srcfmt, &srcAmask, &srcAshift);
+    SDL_Get8888AlphaMaskAndShift(dstfmt, &dstAmask, &dstAshift);
+
+    // The byte offsets for the start of each pixel
+    const uint8x16_t mask_offsets = vreinterpretq_u8_u64(vcombine_u64(
+        vcreate_u64(0x0404040400000000), vcreate_u64(0x0c0c0c0c08080808)));
+
+    const uint8x16_t convert_mask = vreinterpretq_u8_u32(vaddq_u32(
+        vreinterpretq_u32_u8(mask_offsets),
+        vdupq_n_u32(
+            ((srcfmt->Rshift >> 3) << dstfmt->Rshift) |
+            ((srcfmt->Gshift >> 3) << dstfmt->Gshift) |
+            ((srcfmt->Bshift >> 3) << dstfmt->Bshift) |
+            ((srcAshift >> 3) << dstAshift))));
+
+    const uint8x16_t alpha_fill_mask = vreinterpretq_u8_u32(vdupq_n_u32(dstAmask));
+
+    while (height--) {
+        int i = 0;
+
+        for (; i + 4 <= width; i += 4) {
+            // Load 4 src pixels
+            uint8x16_t src128 = vld1q_u8(src);
+
+            // Convert to dst format
+            src128 = vqtbl1q_u8(src128, convert_mask);
+
+            if (fill_alpha) {
+                // Set the alpha channels of src to 255
+                src128 = vorrq_u8(src128, alpha_fill_mask);
+            }
+
+            // Save the result
+            vst1q_u8(dst, src128);
+
+            src += 16;
+            dst += 16;
+        }
+
+        // Process 1 pixel per iteration, max 3 iterations, same calculations as above
+        for (; i < width; ++i) {
+            // Top 32-bits will be not used in src32
+            uint8x8_t src32 = vreinterpret_u8_u32(vld1_dup_u32((Uint32*)src));
+
+            // Convert to dst format
+            src32 = vtbl1_u8(src32, vget_low_u8(convert_mask));
+
+            if (fill_alpha) {
+                // Set the alpha channels of src to 255
+                src32 = vorr_u8(src32, vget_low_u8(alpha_fill_mask));
+            }
+
+            // Save the result, only low 32-bits
+            vst1_lane_u32((Uint32*)dst, vreinterpret_u32_u8(src32), 0);
+
+            src += 4;
+            dst += 4;
+        }
+
+        src += srcskip;
+        dst += dstskip;
+    }
+}
+
+#endif
+
 // Blit_3or4_to_3or4__same_rgb: 3 or 4 bpp, same RGB triplet
 // Blit_3or4_to_3or4__same_rgb: 3 or 4 bpp, same RGB triplet
 static void Blit_3or4_to_3or4__same_rgb(SDL_BlitInfo *info)
 static void Blit_3or4_to_3or4__same_rgb(SDL_BlitInfo *info)
 {
 {
@@ -2873,6 +3123,23 @@ SDL_BlitFunc SDL_CalculateBlitN(SDL_Surface *surface)
 
 
     switch (surface->map.info.flags & ~SDL_COPY_RLE_MASK) {
     switch (surface->map.info.flags & ~SDL_COPY_RLE_MASK) {
     case 0:
     case 0:
+        if (SDL_PIXELLAYOUT(srcfmt->format) == SDL_PACKEDLAYOUT_8888 &&
+            SDL_PIXELLAYOUT(dstfmt->format) == SDL_PACKEDLAYOUT_8888) {
+#ifdef SDL_AVX2_INTRINSICS
+            if (SDL_HasAVX2()) {
+                return Blit8888to8888PixelSwizzleAVX2;
+            }
+#endif
+#ifdef SDL_SSE4_1_INTRINSICS
+            if (SDL_HasSSE41()) {
+                return Blit8888to8888PixelSwizzleSSE41;
+            }
+#endif
+#if defined(SDL_NEON_INTRINSICS) && (__ARM_ARCH >= 8)
+            return Blit8888to8888PixelSwizzleNEON;
+#endif
+        }
+
         blitfun = NULL;
         blitfun = NULL;
         if (dstfmt->bits_per_pixel > 8) {
         if (dstfmt->bits_per_pixel > 8) {
             Uint32 a_need = NO_ALPHA;
             Uint32 a_need = NO_ALPHA;

+ 28 - 0
libs/SDL3/src/video/SDL_pixels.c

@@ -692,6 +692,34 @@ void SDL_QuitPixelFormatDetails(void)
     }
     }
 }
 }
 
 
+void SDL_Get8888AlphaMaskAndShift(const SDL_PixelFormatDetails *fmt, Uint32 *mask, Uint32 *shift)
+{
+    if (fmt->Amask) {
+        *mask = fmt->Amask;
+        *shift = fmt->Ashift;
+    } else {
+        *mask = ~(fmt->Rmask | fmt->Gmask | fmt->Bmask);
+        switch (*mask) {
+        case 0x000000FF:
+            *shift = 0;
+            break;
+        case 0x0000FF00:
+            *shift = 8;
+            break;
+        case 0x00FF0000:
+            *shift = 16;
+            break;
+        case 0xFF000000:
+            *shift = 24;
+            break;
+        default:
+            // Should never happen
+            *shift = 0;
+            break;
+        }
+    }
+}
+
 SDL_Colorspace SDL_GetDefaultColorspaceForFormat(SDL_PixelFormat format)
 SDL_Colorspace SDL_GetDefaultColorspaceForFormat(SDL_PixelFormat format)
 {
 {
     if (SDL_ISPIXELFORMAT_FOURCC(format)) {
     if (SDL_ISPIXELFORMAT_FOURCC(format)) {

+ 1 - 1
libs/SDL3/src/video/SDL_pixels_c.h

@@ -29,7 +29,7 @@
 
 
 
 
 // Pixel format functions
 // Pixel format functions
-extern bool SDL_CalculateSurfaceSize(SDL_PixelFormat format, int width, int height, size_t *size, size_t *pitch, bool minimalPitch);
+extern void SDL_Get8888AlphaMaskAndShift(const SDL_PixelFormatDetails *fmt, Uint32 *mask, Uint32 *shift);
 extern SDL_Colorspace SDL_GetDefaultColorspaceForFormat(SDL_PixelFormat pixel_format);
 extern SDL_Colorspace SDL_GetDefaultColorspaceForFormat(SDL_PixelFormat pixel_format);
 extern void SDL_QuitPixelFormatDetails(void);
 extern void SDL_QuitPixelFormatDetails(void);
 
 

+ 28 - 2
libs/SDL3/src/video/SDL_surface.c

@@ -104,6 +104,11 @@ bool SDL_CalculateSurfaceSize(SDL_PixelFormat format, int width, int height, siz
     }
     }
 
 
     if (SDL_ISPIXELFORMAT_FOURCC(format)) {
     if (SDL_ISPIXELFORMAT_FOURCC(format)) {
+        if (format == SDL_PIXELFORMAT_MJPG) {
+            // We don't know in advance what it will be, we'll figure it out later.
+            return true;
+        }
+
         if (!SDL_CalculateYUVSize(format, width, height, &sz, &p)) {
         if (!SDL_CalculateYUVSize(format, width, height, &sz, &p)) {
             // Overflow...
             // Overflow...
             return false;
             return false;
@@ -199,6 +204,11 @@ SDL_Surface *SDL_CreateSurface(int width, int height, SDL_PixelFormat format)
         return NULL;
         return NULL;
     }
     }
 
 
+    if (format == SDL_PIXELFORMAT_UNKNOWN) {
+        SDL_InvalidParamError("format");
+        return NULL;
+    }
+
     if (!SDL_CalculateSurfaceSize(format, width, height, &size, &pitch, false /* not minimal pitch */)) {
     if (!SDL_CalculateSurfaceSize(format, width, height, &size, &pitch, false /* not minimal pitch */)) {
         // Overflow...
         // Overflow...
         return NULL;
         return NULL;
@@ -214,7 +224,7 @@ SDL_Surface *SDL_CreateSurface(int width, int height, SDL_PixelFormat format)
         return NULL;
         return NULL;
     }
     }
 
 
-    if (surface->w && surface->h) {
+    if (surface->w && surface->h && format != SDL_PIXELFORMAT_MJPG) {
         surface->flags &= ~SDL_SURFACE_PREALLOCATED;
         surface->flags &= ~SDL_SURFACE_PREALLOCATED;
         surface->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), size);
         surface->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), size);
         if (!surface->pixels) {
         if (!surface->pixels) {
@@ -245,6 +255,11 @@ SDL_Surface *SDL_CreateSurfaceFrom(int width, int height, SDL_PixelFormat format
         return NULL;
         return NULL;
     }
     }
 
 
+    if (format == SDL_PIXELFORMAT_UNKNOWN) {
+        SDL_InvalidParamError("format");
+        return NULL;
+    }
+
     if (pitch == 0 && !pixels) {
     if (pitch == 0 && !pixels) {
         // The application will fill these in later with valid values
         // The application will fill these in later with valid values
     } else {
     } else {
@@ -1917,7 +1932,18 @@ SDL_Surface *SDL_ConvertSurfaceAndColorspace(SDL_Surface *surface, SDL_PixelForm
     SDL_SetSurfaceColorspace(convert, colorspace);
     SDL_SetSurfaceColorspace(convert, colorspace);
 
 
     if (SDL_ISPIXELFORMAT_FOURCC(format) || SDL_ISPIXELFORMAT_FOURCC(surface->format)) {
     if (SDL_ISPIXELFORMAT_FOURCC(format) || SDL_ISPIXELFORMAT_FOURCC(surface->format)) {
-        if (!SDL_ConvertPixelsAndColorspace(surface->w, surface->h, surface->format, src_colorspace, src_properties, surface->pixels, surface->pitch, convert->format, colorspace, props, convert->pixels, convert->pitch)) {
+        if (surface->format == SDL_PIXELFORMAT_MJPG && format == SDL_PIXELFORMAT_MJPG) {
+            // Just do a straight pixel copy of the JPEG image
+            size_t size = (size_t)surface->pitch;
+            convert->pixels = SDL_malloc(size);
+            if (!convert->pixels) {
+                goto error;
+            }
+            convert->flags &= ~SDL_SURFACE_PREALLOCATED;
+            convert->pitch = surface->pitch;
+            SDL_memcpy(convert->pixels, surface->pixels, size);
+
+        } else if (!SDL_ConvertPixelsAndColorspace(surface->w, surface->h, surface->format, src_colorspace, src_properties, surface->pixels, surface->pitch, convert->format, colorspace, props, convert->pixels, convert->pitch)) {
             goto error;
             goto error;
         }
         }
 
 

+ 1 - 0
libs/SDL3/src/video/SDL_surface_c.h

@@ -83,6 +83,7 @@ struct SDL_Surface
 // Surface functions
 // Surface functions
 extern bool SDL_SurfaceValid(SDL_Surface *surface);
 extern bool SDL_SurfaceValid(SDL_Surface *surface);
 extern void SDL_UpdateSurfaceLockFlag(SDL_Surface *surface);
 extern void SDL_UpdateSurfaceLockFlag(SDL_Surface *surface);
+extern bool SDL_CalculateSurfaceSize(SDL_PixelFormat format, int width, int height, size_t *size, size_t *pitch, bool minimalPitch);
 extern float SDL_GetDefaultSDRWhitePoint(SDL_Colorspace colorspace);
 extern float SDL_GetDefaultSDRWhitePoint(SDL_Colorspace colorspace);
 extern float SDL_GetSurfaceSDRWhitePoint(SDL_Surface *surface, SDL_Colorspace colorspace);
 extern float SDL_GetSurfaceSDRWhitePoint(SDL_Surface *surface, SDL_Colorspace colorspace);
 extern float SDL_GetDefaultHDRHeadroom(SDL_Colorspace colorspace);
 extern float SDL_GetDefaultHDRHeadroom(SDL_Colorspace colorspace);

+ 2 - 0
libs/SDL3/src/video/SDL_sysvideo.h

@@ -163,6 +163,8 @@ struct SDL_VideoDisplay
     float content_scale;
     float content_scale;
     SDL_HDROutputProperties HDR;
     SDL_HDROutputProperties HDR;
 
 
+    // This is true if we are fullscreen or fullscreen is pending
+    bool fullscreen_active;
     SDL_Window *fullscreen_window;
     SDL_Window *fullscreen_window;
 
 
     SDL_VideoDevice *device;
     SDL_VideoDevice *device;

+ 24 - 2
libs/SDL3/src/video/SDL_video.c

@@ -1435,6 +1435,11 @@ void SDL_SetDesktopDisplayMode(SDL_VideoDisplay *display, const SDL_DisplayMode
 {
 {
     SDL_DisplayMode last_mode;
     SDL_DisplayMode last_mode;
 
 
+    if (display->fullscreen_active) {
+        // This is a temporary mode change, don't save the desktop mode
+        return;
+    }
+
     SDL_copyp(&last_mode, &display->desktop_mode);
     SDL_copyp(&last_mode, &display->desktop_mode);
 
 
     if (display->desktop_mode.internal) {
     if (display->desktop_mode.internal) {
@@ -1944,6 +1949,8 @@ bool SDL_UpdateFullscreenMode(SDL_Window *window, SDL_FullscreenOp fullscreen, b
             SDL_MinimizeWindow(display->fullscreen_window);
             SDL_MinimizeWindow(display->fullscreen_window);
         }
         }
 
 
+        display->fullscreen_active = window->fullscreen_exclusive;
+
         if (!SDL_SetDisplayModeForDisplay(display, mode)) {
         if (!SDL_SetDisplayModeForDisplay(display, mode)) {
             goto error;
             goto error;
         }
         }
@@ -1961,6 +1968,7 @@ bool SDL_UpdateFullscreenMode(SDL_Window *window, SDL_FullscreenOp fullscreen, b
                     SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0);
                     SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0);
                 }
                 }
             } else if (ret == SDL_FULLSCREEN_FAILED) {
             } else if (ret == SDL_FULLSCREEN_FAILED) {
+                display->fullscreen_active = false;
                 goto error;
                 goto error;
             }
             }
         }
         }
@@ -1976,18 +1984,24 @@ bool SDL_UpdateFullscreenMode(SDL_Window *window, SDL_FullscreenOp fullscreen, b
              * This is also unnecessary on Cocoa, Wayland, Win32, and X11 (will send SDL_EVENT_WINDOW_RESIZED).
              * This is also unnecessary on Cocoa, Wayland, Win32, and X11 (will send SDL_EVENT_WINDOW_RESIZED).
              */
              */
             if (!SDL_SendsFullscreenDimensions(_this)) {
             if (!SDL_SendsFullscreenDimensions(_this)) {
+                SDL_Rect displayRect;
+
                 if (mode) {
                 if (mode) {
                     mode_w = mode->w;
                     mode_w = mode->w;
                     mode_h = mode->h;
                     mode_h = mode->h;
+                    SDL_GetDisplayBounds(mode->displayID, &displayRect);
                 } else {
                 } else {
                     mode_w = display->desktop_mode.w;
                     mode_w = display->desktop_mode.w;
                     mode_h = display->desktop_mode.h;
                     mode_h = display->desktop_mode.h;
+                    SDL_GetDisplayBounds(display->id, &displayRect);
                 }
                 }
 
 
                 if (window->w != mode_w || window->h != mode_h) {
                 if (window->w != mode_w || window->h != mode_h) {
                     resized = true;
                     resized = true;
                 }
                 }
 
 
+                SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, displayRect.x, displayRect.y);
+
                 if (resized) {
                 if (resized) {
                     SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, mode_w, mode_h);
                     SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, mode_w, mode_h);
                 } else {
                 } else {
@@ -2005,6 +2019,8 @@ bool SDL_UpdateFullscreenMode(SDL_Window *window, SDL_FullscreenOp fullscreen, b
 
 
         // Restore the desktop mode
         // Restore the desktop mode
         if (display) {
         if (display) {
+            display->fullscreen_active = false;
+
             SDL_SetDisplayModeForDisplay(display, NULL);
             SDL_SetDisplayModeForDisplay(display, NULL);
         }
         }
         if (commit) {
         if (commit) {
@@ -2034,6 +2050,7 @@ bool SDL_UpdateFullscreenMode(SDL_Window *window, SDL_FullscreenOp fullscreen, b
             }
             }
 
 
             if (!SDL_SendsFullscreenDimensions(_this)) {
             if (!SDL_SendsFullscreenDimensions(_this)) {
+                SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, window->windowed.x, window->windowed.y);
                 if (resized) {
                 if (resized) {
                     SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window->windowed.w, window->windowed.h);
                     SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window->windowed.w, window->windowed.h);
                 } else {
                 } else {
@@ -2907,11 +2924,12 @@ bool SDL_GetWindowPosition(SDL_Window *window, int *x, int *y)
             }
             }
         }
         }
     } else {
     } else {
+        const bool use_pending = (window->flags & SDL_WINDOW_HIDDEN) && window->last_position_pending;
         if (x) {
         if (x) {
-            *x = window->x;
+            *x = use_pending ? window->pending.x : window->x;
         }
         }
         if (y) {
         if (y) {
-            *y = window->y;
+            *y = use_pending ? window->pending.y : window->y;
         }
         }
     }
     }
     return true;
     return true;
@@ -5387,6 +5405,10 @@ bool SDL_GetTextInputMultiline(SDL_PropertiesID props)
 static bool AutoShowingScreenKeyboard(void)
 static bool AutoShowingScreenKeyboard(void)
 {
 {
     const char *hint = SDL_GetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD);
     const char *hint = SDL_GetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD);
+    if (!hint) {
+        // Steam will eventually have smarts about whether a keyboard is active, so always request the on-screen keyboard on Steam Deck
+        hint = SDL_GetHint("SteamDeck");
+    }
     if (((!hint || SDL_strcasecmp(hint, "auto") == 0) && !SDL_HasKeyboard()) ||
     if (((!hint || SDL_strcasecmp(hint, "auto") == 0) && !SDL_HasKeyboard()) ||
         SDL_GetStringBoolean(hint, false)) {
         SDL_GetStringBoolean(hint, false)) {
         return true;
         return true;

+ 31 - 13
libs/SDL3/src/video/SDL_yuv.c

@@ -1628,26 +1628,44 @@ static bool SDL_ConvertPixels_SwapNV_std(int width, int height, const void *src,
     const int UVwidth = (width + 1) / 2;
     const int UVwidth = (width + 1) / 2;
     const int UVheight = (height + 1) / 2;
     const int UVheight = (height + 1) / 2;
     const int srcUVPitch = ((src_pitch + 1) / 2) * 2;
     const int srcUVPitch = ((src_pitch + 1) / 2) * 2;
-    const int srcUVPitchLeft = (srcUVPitch - UVwidth * 2) / sizeof(Uint16);
     const int dstUVPitch = ((dst_pitch + 1) / 2) * 2;
     const int dstUVPitch = ((dst_pitch + 1) / 2) * 2;
-    const int dstUVPitchLeft = (dstUVPitch - UVwidth * 2) / sizeof(Uint16);
-    const Uint16 *srcUV;
-    Uint16 *dstUV;
 
 
     // Skip the Y plane
     // Skip the Y plane
     src = (const Uint8 *)src + height * src_pitch;
     src = (const Uint8 *)src + height * src_pitch;
     dst = (Uint8 *)dst + height * dst_pitch;
     dst = (Uint8 *)dst + height * dst_pitch;
 
 
-    srcUV = (const Uint16 *)src;
-    dstUV = (Uint16 *)dst;
-    y = UVheight;
-    while (y--) {
-        x = UVwidth;
-        while (x--) {
-            *dstUV++ = SDL_Swap16(*srcUV++);
+    bool aligned = (((uintptr_t)src | (uintptr_t)dst) & 1) == 0;
+    if (aligned) {
+        const int srcUVPitchLeft = (srcUVPitch - UVwidth * 2) / sizeof(Uint16);
+        const int dstUVPitchLeft = (dstUVPitch - UVwidth * 2) / sizeof(Uint16);
+        const Uint16 *srcUV = (const Uint16 *)src;
+        Uint16 *dstUV = (Uint16 *)dst;
+        y = UVheight;
+        while (y--) {
+            x = UVwidth;
+            while (x--) {
+                *dstUV++ = SDL_Swap16(*srcUV++);
+            }
+            srcUV += srcUVPitchLeft;
+            dstUV += dstUVPitchLeft;
+        }
+    } else {
+        const int srcUVPitchLeft = (srcUVPitch - UVwidth * 2);
+        const int dstUVPitchLeft = (dstUVPitch - UVwidth * 2);
+        const Uint8 *srcUV = (const Uint8 *)src;
+        Uint8 *dstUV = (Uint8 *)dst;
+        y = UVheight;
+        while (y--) {
+            x = UVwidth;
+            while (x--) {
+                Uint8 u = *srcUV++;
+                Uint8 v = *srcUV++;
+                *dstUV++ = v;
+                *dstUV++ = u;
+            }
+            srcUV += srcUVPitchLeft;
+            dstUV += dstUVPitchLeft;
         }
         }
-        srcUV += srcUVPitchLeft;
-        dstUV += dstUVPitchLeft;
     }
     }
     return true;
     return true;
 }
 }

+ 18 - 14
libs/SDL3/src/video/cocoa/SDL_cocoamodes.m

@@ -556,22 +556,24 @@ bool Cocoa_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, S
 
 
 bool Cocoa_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
 bool Cocoa_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
 {
 {
-    SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal;
-    NSScreen *screen = GetNSScreenForDisplayID(displaydata->display);
+    @autoreleasepool {
+        SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal;
+        NSScreen *screen = GetNSScreenForDisplayID(displaydata->display);
 
 
-    if (screen == nil) {
-        return SDL_SetError("Couldn't get NSScreen for display");
-    }
+        if (screen == nil) {
+            return SDL_SetError("Couldn't get NSScreen for display");
+        }
 
 
-    {
-        const NSRect frame = [screen visibleFrame];
-        rect->x = (int)frame.origin.x;
-        rect->y = (int)(CGDisplayPixelsHigh(kCGDirectMainDisplay) - frame.origin.y - frame.size.height);
-        rect->w = (int)frame.size.width;
-        rect->h = (int)frame.size.height;
-    }
+        {
+            const NSRect frame = [screen visibleFrame];
+            rect->x = (int)frame.origin.x;
+            rect->y = (int)(CGDisplayPixelsHigh(kCGDirectMainDisplay) - frame.origin.y - frame.size.height);
+            rect->w = (int)frame.size.width;
+            rect->h = (int)frame.size.height;
+        }
 
 
-    return true;
+        return true;
+    }
 }
 }
 
 
 bool Cocoa_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
 bool Cocoa_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
@@ -644,7 +646,9 @@ static CGError SetDisplayModeForDisplay(CGDirectDisplayID display, SDL_DisplayMo
         result = CGDisplaySetDisplayMode(display, moderef, NULL);
         result = CGDisplaySetDisplayMode(display, moderef, NULL);
         if (result == kCGErrorSuccess) {
         if (result == kCGErrorSuccess) {
             // If this mode works, try it first next time.
             // If this mode works, try it first next time.
-            CFArrayExchangeValuesAtIndices(data->modes, i, 0);
+            if (i > 0) {
+                CFArrayExchangeValuesAtIndices(data->modes, i, 0);
+            }
             break;
             break;
         }
         }
     }
     }

+ 42 - 47
libs/SDL3/src/video/cocoa/SDL_cocoawindow.m

@@ -868,14 +868,16 @@ static NSCursor *Cocoa_GetDesiredCursor(void)
         return NO; // we only allow you to make a Space on fullscreen desktop windows.
         return NO; // we only allow you to make a Space on fullscreen desktop windows.
     } else if (!state && window->last_fullscreen_exclusive_display) {
     } else if (!state && window->last_fullscreen_exclusive_display) {
         return NO; // we only handle leaving the Space on windows that were previously fullscreen desktop.
         return NO; // we only handle leaving the Space on windows that were previously fullscreen desktop.
-    } else if (state == isFullscreenSpace) {
+    } else if (state == isFullscreenSpace && !inFullscreenTransition) {
         return YES; // already there.
         return YES; // already there.
     }
     }
 
 
     if (inFullscreenTransition) {
     if (inFullscreenTransition) {
         if (state) {
         if (state) {
+            [self clearPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN];
             [self addPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN];
             [self addPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN];
         } else {
         } else {
+            [self clearPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN];
             [self addPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN];
             [self addPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN];
         }
         }
         return YES;
         return YES;
@@ -903,6 +905,11 @@ static NSCursor *Cocoa_GetDesiredCursor(void)
     pendingWindowOperation &= ~operation;
     pendingWindowOperation &= ~operation;
 }
 }
 
 
+- (void)clearAllPendingWindowOperations
+{
+    pendingWindowOperation = PENDING_OPERATION_NONE;
+}
+
 - (void)addPendingWindowOperation:(PendingWindowOperation)operation
 - (void)addPendingWindowOperation:(PendingWindowOperation)operation
 {
 {
     pendingWindowOperation |= operation;
     pendingWindowOperation |= operation;
@@ -915,7 +922,8 @@ static NSCursor *Cocoa_GetDesiredCursor(void)
 
 
 - (BOOL)hasPendingWindowOperation
 - (BOOL)hasPendingWindowOperation
 {
 {
-    return pendingWindowOperation != PENDING_OPERATION_NONE ||
+    // A pending zoom may be deferred until leaving fullscreen, so don't block on it.
+    return (pendingWindowOperation & ~PENDING_OPERATION_ZOOM) != PENDING_OPERATION_NONE ||
            isMiniaturizing || inFullscreenTransition;
            isMiniaturizing || inFullscreenTransition;
 }
 }
 
 
@@ -1349,21 +1357,14 @@ static NSCursor *Cocoa_GetDesiredCursor(void)
     inFullscreenTransition = YES;
     inFullscreenTransition = YES;
 }
 }
 
 
+/* This is usually sent after an unexpected windowDidExitFullscreen if the window
+ * failed to become fullscreen.
+ *
+ * Since something went wrong and the current state is unknown, dump any pending events.
+ */
 - (void)windowDidFailToEnterFullScreen:(NSNotification *)aNotification
 - (void)windowDidFailToEnterFullScreen:(NSNotification *)aNotification
 {
 {
-    SDL_Window *window = _data.window;
-
-    if (window->is_destroying) {
-        return;
-    }
-
-    SetWindowStyle(window, GetWindowStyle(window));
-
-    [self clearPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN];
-    isFullscreenSpace = NO;
-    inFullscreenTransition = NO;
-
-    [self windowDidExitFullScreen:nil];
+    [self clearAllPendingWindowOperations];
 }
 }
 
 
 - (void)windowDidEnterFullScreen:(NSNotification *)aNotification
 - (void)windowDidEnterFullScreen:(NSNotification *)aNotification
@@ -1371,6 +1372,7 @@ static NSCursor *Cocoa_GetDesiredCursor(void)
     SDL_Window *window = _data.window;
     SDL_Window *window = _data.window;
 
 
     inFullscreenTransition = NO;
     inFullscreenTransition = NO;
+    isFullscreenSpace = YES;
     [self clearPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN];
     [self clearPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN];
 
 
     if ([self windowOperationIsPending:PENDING_OPERATION_LEAVE_FULLSCREEN]) {
     if ([self windowOperationIsPending:PENDING_OPERATION_LEAVE_FULLSCREEN]) {
@@ -1421,26 +1423,14 @@ static NSCursor *Cocoa_GetDesiredCursor(void)
     inFullscreenTransition = YES;
     inFullscreenTransition = YES;
 }
 }
 
 
+/* This may be sent before windowDidExitFullscreen to signal that the window was
+ * dumped out of fullscreen with no animation.
+ *
+ * Since something went wrong and the state is unknown, dump any pending events.
+ */
 - (void)windowDidFailToExitFullScreen:(NSNotification *)aNotification
 - (void)windowDidFailToExitFullScreen:(NSNotification *)aNotification
 {
 {
-    SDL_Window *window = _data.window;
-    const NSUInteger flags = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
-
-    if (window->is_destroying) {
-        return;
-    }
-
-    _data.pending_position = NO;
-    _data.pending_size = NO;
-    window->last_position_pending = false;
-    window->last_size_pending = false;
-
-    SetWindowStyle(window, flags);
-
-    isFullscreenSpace = YES;
-    inFullscreenTransition = NO;
-
-    [self windowDidEnterFullScreen:nil];
+    [self clearAllPendingWindowOperations];
 }
 }
 
 
 - (void)windowDidExitFullScreen:(NSNotification *)aNotification
 - (void)windowDidExitFullScreen:(NSNotification *)aNotification
@@ -1449,16 +1439,24 @@ static NSCursor *Cocoa_GetDesiredCursor(void)
     NSWindow *nswindow = _data.nswindow;
     NSWindow *nswindow = _data.nswindow;
 
 
     inFullscreenTransition = NO;
     inFullscreenTransition = NO;
+    isFullscreenSpace = NO;
     _data.fullscreen_space_requested = NO;
     _data.fullscreen_space_requested = NO;
 
 
     /* As of macOS 10.15, the window decorations can go missing sometimes after
     /* As of macOS 10.15, the window decorations can go missing sometimes after
-       certain fullscreen-desktop->exlusive-fullscreen->windowed mode flows
+       certain fullscreen-desktop->exclusive-fullscreen->windowed mode flows
        sometimes. Making sure the style mask always uses the windowed mode style
        sometimes. Making sure the style mask always uses the windowed mode style
        when returning to windowed mode from a space (instead of using a pending
        when returning to windowed mode from a space (instead of using a pending
        fullscreen mode style mask) seems to work around that issue.
        fullscreen mode style mask) seems to work around that issue.
      */
      */
     SetWindowStyle(window, GetWindowWindowedStyle(window));
     SetWindowStyle(window, GetWindowWindowedStyle(window));
 
 
+    /* This can happen if the window failed to enter fullscreen, as this
+     * may be called *before* windowDidFailToEnterFullScreen in that case.
+     */
+    if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
+        [self clearAllPendingWindowOperations];
+    }
+
     /* Don't recurse back into UpdateFullscreenMode() if this was hit in
     /* Don't recurse back into UpdateFullscreenMode() if this was hit in
      * a blocking transition, as the caller is already waiting in
      * a blocking transition, as the caller is already waiting in
      * UpdateFullscreenMode().
      * UpdateFullscreenMode().
@@ -1503,9 +1501,10 @@ static NSCursor *Cocoa_GetDesiredCursor(void)
         if ([self windowOperationIsPending:PENDING_OPERATION_ZOOM]) {
         if ([self windowOperationIsPending:PENDING_OPERATION_ZOOM]) {
             [self clearPendingWindowOperation:PENDING_OPERATION_ZOOM];
             [self clearPendingWindowOperation:PENDING_OPERATION_ZOOM];
             [nswindow zoom:nil];
             [nswindow zoom:nil];
+            _data.was_zoomed = !_data.was_zoomed;
         }
         }
 
 
-        if (![nswindow isZoomed]) {
+        if (!_data.was_zoomed) {
             // Apply a pending window size, if not zoomed.
             // Apply a pending window size, if not zoomed.
             NSRect rect;
             NSRect rect;
             rect.origin.x = _data.pending_position ? window->pending.x : window->floating.x;
             rect.origin.x = _data.pending_position ? window->pending.x : window->floating.x;
@@ -2125,6 +2124,7 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow
         if (!data) {
         if (!data) {
             return SDL_OutOfMemory();
             return SDL_OutOfMemory();
         }
         }
+        window->internal = (SDL_WindowData *)CFBridgingRetain(data);
         data.window = window;
         data.window = window;
         data.nswindow = nswindow;
         data.nswindow = nswindow;
         data.videodata = videodata;
         data.videodata = videodata;
@@ -2245,7 +2245,6 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow
         SDL_SetNumberProperty(props, SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER, SDL_METALVIEW_TAG);
         SDL_SetNumberProperty(props, SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER, SDL_METALVIEW_TAG);
 
 
         // All done!
         // All done!
-        window->internal = (SDL_WindowData *)CFBridgingRetain(data);
         return true;
         return true;
     }
     }
 }
 }
@@ -3260,25 +3259,21 @@ bool Cocoa_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float op
 
 
 bool Cocoa_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window)
 bool Cocoa_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window)
 {
 {
-    bool result = true;
+    bool result = false;
 
 
     @autoreleasepool {
     @autoreleasepool {
-        /* The timeout needs to be high enough that animated fullscreen
-         * spaces transitions won't cause it to time out.
-         */
-        Uint64 timeout = SDL_GetTicksNS() + SDL_MS_TO_NS(2000);
+        const Uint64 timeout = SDL_GetTicksNS() + SDL_MS_TO_NS(2500);
         SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
         SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
-        while (true) {
+
+        for (;;) {
             SDL_PumpEvents();
             SDL_PumpEvents();
 
 
-            if (SDL_GetTicksNS() >= timeout) {
-                result = false;
-                break;
-            }
-            if (![data.listener hasPendingWindowOperation]) {
+            result = ![data.listener hasPendingWindowOperation];
+            if (result || SDL_GetTicksNS() >= timeout) {
                 break;
                 break;
             }
             }
 
 
+            // Small delay before going again.
             SDL_Delay(10);
             SDL_Delay(10);
         }
         }
     }
     }

+ 25 - 12
libs/SDL3/src/video/emscripten/SDL_emscriptenevents.c

@@ -1005,6 +1005,27 @@ static void Emscripten_unset_drag_event_callbacks(SDL_WindowData *data)
     }, data->canvas_id);
     }, data->canvas_id);
 }
 }
 
 
+static const char *Emscripten_GetKeyboardTargetElement()
+{
+    const char *target = SDL_GetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT);
+
+    if (!target || !*target) {
+        return EMSCRIPTEN_EVENT_TARGET_WINDOW;
+    }
+
+    if (SDL_strcmp(target, "#none") == 0) {
+        return NULL;
+    } else if (SDL_strcmp(target, "#window") == 0) {
+        return EMSCRIPTEN_EVENT_TARGET_WINDOW;
+    } else if (SDL_strcmp(target, "#document") == 0) {
+        return EMSCRIPTEN_EVENT_TARGET_DOCUMENT;
+    } else if (SDL_strcmp(target, "#screen") == 0) {
+        return EMSCRIPTEN_EVENT_TARGET_SCREEN;
+    }
+
+    return target;
+}
+
 void Emscripten_RegisterEventHandlers(SDL_WindowData *data)
 void Emscripten_RegisterEventHandlers(SDL_WindowData *data)
 {
 {
     const char *keyElement;
     const char *keyElement;
@@ -1033,12 +1054,8 @@ void Emscripten_RegisterEventHandlers(SDL_WindowData *data)
     emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, data, 0, Emscripten_HandlePointerLockChange);
     emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, data, 0, Emscripten_HandlePointerLockChange);
 
 
     // Keyboard events are awkward
     // Keyboard events are awkward
-    keyElement = SDL_GetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT);
-    if (!keyElement || !*keyElement) {
-        keyElement = EMSCRIPTEN_EVENT_TARGET_WINDOW;
-    }
-
-    if (SDL_strcmp(keyElement, "#none") != 0) {
+    keyElement = Emscripten_GetKeyboardTargetElement();
+    if (keyElement) {
         emscripten_set_keydown_callback(keyElement, data, 0, Emscripten_HandleKey);
         emscripten_set_keydown_callback(keyElement, data, 0, Emscripten_HandleKey);
         emscripten_set_keyup_callback(keyElement, data, 0, Emscripten_HandleKey);
         emscripten_set_keyup_callback(keyElement, data, 0, Emscripten_HandleKey);
         emscripten_set_keypress_callback(keyElement, data, 0, Emscripten_HandleKeyPress);
         emscripten_set_keypress_callback(keyElement, data, 0, Emscripten_HandleKeyPress);
@@ -1094,12 +1111,8 @@ void Emscripten_UnregisterEventHandlers(SDL_WindowData *data)
 
 
     emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, 0, NULL);
     emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, 0, NULL);
 
 
-    target = SDL_GetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT);
-    if (!target || !*target) {
-        target = EMSCRIPTEN_EVENT_TARGET_WINDOW;
-    }
-
-    if (SDL_strcmp(target, "#none") != 0) {
+    target = Emscripten_GetKeyboardTargetElement();
+    if (target) {
         emscripten_set_keydown_callback(target, NULL, 0, NULL);
         emscripten_set_keydown_callback(target, NULL, 0, NULL);
         emscripten_set_keyup_callback(target, NULL, 0, NULL);
         emscripten_set_keyup_callback(target, NULL, 0, NULL);
         emscripten_set_keypress_callback(target, NULL, 0, NULL);
         emscripten_set_keypress_callback(target, NULL, 0, NULL);

+ 2 - 0
libs/SDL3/src/video/uikit/SDL_uikitvideo.m

@@ -37,6 +37,7 @@
 #include "SDL_uikitvulkan.h"
 #include "SDL_uikitvulkan.h"
 #include "SDL_uikitmetalview.h"
 #include "SDL_uikitmetalview.h"
 #include "SDL_uikitmessagebox.h"
 #include "SDL_uikitmessagebox.h"
+#include "SDL_uikitpen.h"
 
 
 #define UIKITVID_DRIVER_NAME "uikit"
 #define UIKITVID_DRIVER_NAME "uikit"
 
 
@@ -170,6 +171,7 @@ static void UIKit_VideoQuit(SDL_VideoDevice *_this)
 
 
     SDL_QuitGCKeyboard();
     SDL_QuitGCKeyboard();
     SDL_QuitGCMouse();
     SDL_QuitGCMouse();
+    UIKit_QuitPen(_this);
 
 
     UIKit_QuitModes(_this);
     UIKit_QuitModes(_this);
 }
 }

+ 11 - 3
libs/SDL3/src/video/wayland/SDL_waylandevents.c

@@ -1023,8 +1023,8 @@ static void relative_pointer_handle_relative_motion(void *data,
             dx = wl_fixed_to_double(dx_unaccel_w);
             dx = wl_fixed_to_double(dx_unaccel_w);
             dy = wl_fixed_to_double(dy_unaccel_w);
             dy = wl_fixed_to_double(dy_unaccel_w);
         } else {
         } else {
-            dx = wl_fixed_to_double(dx_w);
-            dy = wl_fixed_to_double(dy_w);
+            dx = wl_fixed_to_double(dx_w) * window->pointer_scale.x;
+            dy = wl_fixed_to_double(dy_w) * window->pointer_scale.y;
         }
         }
 
 
         SDL_SendMouseMotion(timestamp, window->sdlwindow, input->pointer_id, true, (float)dx, (float)dy);
         SDL_SendMouseMotion(timestamp, window->sdlwindow, input->pointer_id, true, (float)dx, (float)dy);
@@ -1884,7 +1884,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
     SDL_SendKeyboardKeyIgnoreModifiers(timestamp, input->keyboard_id, key, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED);
     SDL_SendKeyboardKeyIgnoreModifiers(timestamp, input->keyboard_id, key, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED);
 
 
     if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
     if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
-        if (has_text && !(SDL_GetModState() & SDL_KMOD_CTRL)) {
+        if (has_text && !(SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_ALT))) {
             if (!handled_by_ime) {
             if (!handled_by_ime) {
                 SDL_SendKeyboardText(text);
                 SDL_SendKeyboardText(text);
             }
             }
@@ -3304,6 +3304,11 @@ bool Wayland_input_confine_pointer(struct SDL_WaylandInput *input, SDL_Window *w
         return SDL_SetError("No pointer to confine");
         return SDL_SetError("No pointer to confine");
     }
     }
 
 
+    // The confinement region will be created when the window is mapped.
+    if (w->shell_surface_status != WAYLAND_SHELL_SURFACE_STATUS_SHOWN) {
+        return true;
+    }
+
     /* A confine may already be active, in which case we should destroy it and
     /* A confine may already be active, in which case we should destroy it and
      * create a new one.
      * create a new one.
      */
      */
@@ -3353,6 +3358,9 @@ bool Wayland_input_confine_pointer(struct SDL_WaylandInput *input, SDL_Window *w
         wl_region_destroy(confine_rect);
         wl_region_destroy(confine_rect);
     }
     }
 
 
+    // Commit the double-buffered confinement region.
+    wl_surface_commit(w->surface);
+
     w->confined_pointer = confined_pointer;
     w->confined_pointer = confined_pointer;
     return true;
     return true;
 }
 }

+ 1 - 0
libs/SDL3/src/video/wayland/SDL_waylandmouse.c

@@ -903,6 +903,7 @@ static SDL_MouseButtonFlags SDLCALL Wayland_GetGlobalMouseState(float *x, float
         int off_x, off_y;
         int off_x, off_y;
 
 
         result = viddata->input->buttons_pressed;
         result = viddata->input->buttons_pressed;
+        SDL_GetMouseState(x, y);
         SDL_RelativeToGlobalForWindow(focus, focus->x, focus->y, &off_x, &off_y);
         SDL_RelativeToGlobalForWindow(focus, focus->x, focus->y, &off_x, &off_y);
         *x += off_x;
         *x += off_x;
         *y += off_y;
         *y += off_y;

+ 5 - 2
libs/SDL3/src/video/wayland/SDL_waylandwindow.c

@@ -707,6 +707,9 @@ static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time
             }
             }
         }
         }
 
 
+        // Create the pointer confinement region, if necessary.
+        Wayland_input_confine_pointer(wind->waylandData->input, wind->sdlwindow);
+
         /* If the window was initially set to the suspended state, send the occluded event now,
         /* If the window was initially set to the suspended state, send the occluded event now,
          * as we don't want to mark the window as occluded until at least one frame has been submitted.
          * as we don't want to mark the window as occluded until at least one frame has been submitted.
          */
          */
@@ -2299,8 +2302,8 @@ SDL_FullscreenResult Wayland_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Win
     }
     }
 
 
     // Don't send redundant fullscreen set/unset events.
     // Don't send redundant fullscreen set/unset events.
-    if (fullscreen != wind->is_fullscreen) {
-        wind->fullscreen_was_positioned = fullscreen;
+    if (!!fullscreen != wind->is_fullscreen) {
+        wind->fullscreen_was_positioned = !!fullscreen;
         SetFullscreen(window, fullscreen ? output : NULL);
         SetFullscreen(window, fullscreen ? output : NULL);
     } else if (wind->is_fullscreen) {
     } else if (wind->is_fullscreen) {
         /*
         /*

+ 14 - 5
libs/SDL3/src/video/windows/SDL_windowsevents.c

@@ -620,7 +620,7 @@ static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_VideoData *data, HANDL
 
 
     SDL_WindowData *windowdata = window->internal;
     SDL_WindowData *windowdata = window->internal;
 
 
-    if (haveMotion) {
+    if (haveMotion && !windowdata->in_modal_loop) {
         if (!isAbsolute) {
         if (!isAbsolute) {
             SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)dx, (float)dy);
             SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)dx, (float)dy);
         } else {
         } else {
@@ -1640,7 +1640,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
             POINT cursorPos;
             POINT cursorPos;
             GetCursorPos(&cursorPos);
             GetCursorPos(&cursorPos);
             ScreenToClient(hwnd, &cursorPos);
             ScreenToClient(hwnd, &cursorPos);
-            PostMessage(hwnd, WM_MOUSEMOVE, 0, cursorPos.x | cursorPos.y << 16);
+            PostMessage(hwnd, WM_MOUSEMOVE, 0, cursorPos.x | (((Uint32)((Sint16)cursorPos.y)) << 16));
         }
         }
     } break;
     } break;
 
 
@@ -1792,7 +1792,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
         }
         }
 
 
         if (!data->disable_move_size_events) {
         if (!data->disable_move_size_events) {
-            if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) {
+            if (GetClientRect(hwnd, &rect) && WIN_WindowRectValid(&rect)) {
                 ClientToScreen(hwnd, (LPPOINT) &rect);
                 ClientToScreen(hwnd, (LPPOINT) &rect);
                 ClientToScreen(hwnd, (LPPOINT) &rect + 1);
                 ClientToScreen(hwnd, (LPPOINT) &rect + 1);
 
 
@@ -1804,7 +1804,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
             }
             }
 
 
             // Moving the window from one display to another can change the size of the window (in the handling of SDL_EVENT_WINDOW_MOVED), so we need to re-query the bounds
             // Moving the window from one display to another can change the size of the window (in the handling of SDL_EVENT_WINDOW_MOVED), so we need to re-query the bounds
-            if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) {
+            if (GetClientRect(hwnd, &rect) && WIN_WindowRectValid(&rect)) {
                 w = rect.right;
                 w = rect.right;
                 h = rect.bottom;
                 h = rect.bottom;
 
 
@@ -1859,6 +1859,15 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
     {
     {
         if (wParam == (UINT_PTR)SDL_IterateMainCallbacks) {
         if (wParam == (UINT_PTR)SDL_IterateMainCallbacks) {
             SDL_OnWindowLiveResizeUpdate(data->window);
             SDL_OnWindowLiveResizeUpdate(data->window);
+
+#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
+#if 0 // This locks up the Windows compositor when called by Steam; disabling until we understand why
+            // Make sure graphics operations are complete for smooth refresh
+            if (data->videodata->DwmFlush) {
+                data->videodata->DwmFlush();
+            }
+#endif
+#endif
             return 0;
             return 0;
         }
         }
     } break;
     } break;
@@ -2084,7 +2093,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
                 RECT rect;
                 RECT rect;
                 float x, y;
                 float x, y;
 
 
-                if (!GetClientRect(hwnd, &rect) || WIN_IsRectEmpty(&rect)) {
+                if (!GetClientRect(hwnd, &rect) || !WIN_WindowRectValid(&rect)) {
                     if (inputs) {
                     if (inputs) {
                         SDL_small_free(inputs, isstack);
                         SDL_small_free(inputs, isstack);
                     }
                     }

+ 14 - 0
libs/SDL3/src/video/windows/SDL_windowsvideo.c

@@ -108,6 +108,9 @@ static void WIN_DeleteDevice(SDL_VideoDevice *device)
     if (data->shcoreDLL) {
     if (data->shcoreDLL) {
         SDL_UnloadObject(data->shcoreDLL);
         SDL_UnloadObject(data->shcoreDLL);
     }
     }
+    if (data->dwmapiDLL) {
+        SDL_UnloadObject(data->dwmapiDLL);
+    }
 #endif
 #endif
 #ifdef HAVE_DXGI_H
 #ifdef HAVE_DXGI_H
     if (data->pDXGIFactory) {
     if (data->pDXGIFactory) {
@@ -184,6 +187,17 @@ static SDL_VideoDevice *WIN_CreateDevice(void)
     } else {
     } else {
         SDL_ClearError();
         SDL_ClearError();
     }
     }
+
+    data->dwmapiDLL = SDL_LoadObject("DWMAPI.DLL");
+    if (data->dwmapiDLL) {
+        /* *INDENT-OFF* */ // clang-format off
+        data->DwmFlush = (HRESULT (WINAPI *)(void))SDL_LoadFunction(data->dwmapiDLL, "DwmFlush");
+        data->DwmEnableBlurBehindWindow = (HRESULT (WINAPI *)(HWND hwnd, const DWM_BLURBEHIND *pBlurBehind))SDL_LoadFunction(data->dwmapiDLL, "DwmEnableBlurBehindWindow");
+        data->DwmSetWindowAttribute = (HRESULT (WINAPI *)(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute))SDL_LoadFunction(data->dwmapiDLL, "DwmSetWindowAttribute");
+        /* *INDENT-ON* */ // clang-format on
+    } else {
+        SDL_ClearError();
+    }
 #endif // #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
 #endif // #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
 
 
 #ifdef HAVE_DXGI_H
 #ifdef HAVE_DXGI_H

+ 44 - 0
libs/SDL3/src/video/windows/SDL_windowsvideo.h

@@ -374,6 +374,45 @@ typedef struct tagINPUTCONTEXT2
 } INPUTCONTEXT2, *PINPUTCONTEXT2, NEAR *NPINPUTCONTEXT2, FAR *LPINPUTCONTEXT2;
 } INPUTCONTEXT2, *PINPUTCONTEXT2, NEAR *NPINPUTCONTEXT2, FAR *LPINPUTCONTEXT2;
 #endif
 #endif
 
 
+// Corner rounding support  (Win 11+)
+#ifndef DWMWA_WINDOW_CORNER_PREFERENCE
+#define DWMWA_WINDOW_CORNER_PREFERENCE 33
+#endif
+typedef enum {
+    DWMWCP_DEFAULT = 0,
+    DWMWCP_DONOTROUND = 1,
+    DWMWCP_ROUND = 2,
+    DWMWCP_ROUNDSMALL = 3
+} DWM_WINDOW_CORNER_PREFERENCE;
+
+// Border Color support (Win 11+)
+#ifndef DWMWA_BORDER_COLOR
+#define DWMWA_BORDER_COLOR 34
+#endif
+
+#ifndef DWMWA_COLOR_DEFAULT
+#define DWMWA_COLOR_DEFAULT 0xFFFFFFFF
+#endif
+
+#ifndef DWMWA_COLOR_NONE
+#define DWMWA_COLOR_NONE 0xFFFFFFFE
+#endif
+
+// Transparent window support
+#ifndef DWM_BB_ENABLE
+#define DWM_BB_ENABLE 0x00000001
+#endif
+#ifndef DWM_BB_BLURREGION
+#define DWM_BB_BLURREGION 0x00000002
+#endif
+typedef struct
+{
+    DWORD flags;
+    BOOL enable;
+    HRGN blur_region;
+    BOOL transition_on_maxed;
+} DWM_BLURBEHIND;
+
 // Private display data
 // Private display data
 
 
 struct SDL_VideoData
 struct SDL_VideoData
@@ -420,6 +459,11 @@ struct SDL_VideoData
     BOOL (WINAPI *GetPointerType)(UINT32 pointerId, POINTER_INPUT_TYPE *pointerType);
     BOOL (WINAPI *GetPointerType)(UINT32 pointerId, POINTER_INPUT_TYPE *pointerType);
     BOOL (WINAPI *GetPointerPenInfo)(UINT32 pointerId, POINTER_PEN_INFO *penInfo);
     BOOL (WINAPI *GetPointerPenInfo)(UINT32 pointerId, POINTER_PEN_INFO *penInfo);
 
 
+    SDL_SharedObject *dwmapiDLL;
+    /* *INDENT-OFF* */ // clang-format off
+    HRESULT (WINAPI *DwmFlush)(void);
+    HRESULT (WINAPI *DwmEnableBlurBehindWindow)(HWND hwnd, const DWM_BLURBEHIND *pBlurBehind);
+    HRESULT (WINAPI *DwmSetWindowAttribute)(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute);
     /* *INDENT-ON* */ // clang-format on
     /* *INDENT-ON* */ // clang-format on
 #endif                // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
 #endif                // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
 
 

+ 29 - 100
libs/SDL3/src/video/windows/SDL_windowswindow.c

@@ -38,10 +38,6 @@
 // Dropfile support
 // Dropfile support
 #include <shellapi.h>
 #include <shellapi.h>
 
 
-// DWM setting support
-typedef HRESULT (WINAPI *DwmSetWindowAttribute_t)(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute);
-typedef HRESULT (WINAPI *DwmGetWindowAttribute_t)(HWND hwnd, DWORD dwAttribute, PVOID pvAttribute, DWORD cbAttribute);
-
 // Dark mode support
 // Dark mode support
 typedef enum {
 typedef enum {
     UXTHEME_APPMODE_DEFAULT,
     UXTHEME_APPMODE_DEFAULT,
@@ -80,46 +76,6 @@ typedef UxthemePreferredAppMode (WINAPI *SetPreferredAppMode_t)(UxthemePreferred
 typedef BOOL (WINAPI *SetWindowCompositionAttribute_t)(HWND, const WINDOWCOMPOSITIONATTRIBDATA *);
 typedef BOOL (WINAPI *SetWindowCompositionAttribute_t)(HWND, const WINDOWCOMPOSITIONATTRIBDATA *);
 typedef void (NTAPI *RtlGetVersion_t)(NT_OSVERSIONINFOW *);
 typedef void (NTAPI *RtlGetVersion_t)(NT_OSVERSIONINFOW *);
 
 
-// Corner rounding support  (Win 11+)
-#ifndef DWMWA_WINDOW_CORNER_PREFERENCE
-#define DWMWA_WINDOW_CORNER_PREFERENCE 33
-#endif
-typedef enum {
-    DWMWCP_DEFAULT = 0,
-    DWMWCP_DONOTROUND = 1,
-    DWMWCP_ROUND = 2,
-    DWMWCP_ROUNDSMALL = 3
-} DWM_WINDOW_CORNER_PREFERENCE;
-
-// Border Color support (Win 11+)
-#ifndef DWMWA_BORDER_COLOR
-#define DWMWA_BORDER_COLOR 34
-#endif
-
-#ifndef DWMWA_COLOR_DEFAULT
-#define DWMWA_COLOR_DEFAULT 0xFFFFFFFF
-#endif
-
-#ifndef DWMWA_COLOR_NONE
-#define DWMWA_COLOR_NONE 0xFFFFFFFE
-#endif
-
-// Transparent window support
-#ifndef DWM_BB_ENABLE
-#define DWM_BB_ENABLE 0x00000001
-#endif
-#ifndef DWM_BB_BLURREGION
-#define DWM_BB_BLURREGION 0x00000002
-#endif
-typedef struct
-{
-    DWORD flags;
-    BOOL enable;
-    HRGN blur_region;
-    BOOL transition_on_maxed;
-} DWM_BLURBEHIND;
-typedef HRESULT(WINAPI *DwmEnableBlurBehindWindow_t)(HWND hwnd, const DWM_BLURBEHIND *pBlurBehind);
-
 // Windows CE compatibility
 // Windows CE compatibility
 #ifndef SWP_NOCOPYBITS
 #ifndef SWP_NOCOPYBITS
 #define SWP_NOCOPYBITS 0
 #define SWP_NOCOPYBITS 0
@@ -447,7 +403,6 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwn
     data->videodata = videodata;
     data->videodata = videodata;
     data->initializing = true;
     data->initializing = true;
     data->last_displayID = window->last_displayID;
     data->last_displayID = window->last_displayID;
-    data->dwma_border_color = DWMWA_COLOR_DEFAULT;
     data->hint_erase_background_mode = GetEraseBackgroundModeHint();
     data->hint_erase_background_mode = GetEraseBackgroundModeHint();
 
 
 
 
@@ -535,7 +490,7 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwn
     }
     }
     if (!(window->flags & SDL_WINDOW_MINIMIZED)) {
     if (!(window->flags & SDL_WINDOW_MINIMIZED)) {
         RECT rect;
         RECT rect;
-        if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) {
+        if (GetClientRect(hwnd, &rect) && WIN_WindowRectValid(&rect)) {
             int w = rect.right;
             int w = rect.right;
             int h = rect.bottom;
             int h = rect.bottom;
 
 
@@ -745,6 +700,7 @@ static void WIN_SetKeyboardFocus(SDL_Window *window, bool set_active_focus)
 
 
 bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
 bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
 {
 {
+    SDL_VideoData *videodata = _this->internal;
     HWND hwnd = (HWND)SDL_GetPointerProperty(create_props, SDL_PROP_WINDOW_CREATE_WIN32_HWND_POINTER, SDL_GetPointerProperty(create_props, "sdl2-compat.external_window", NULL));
     HWND hwnd = (HWND)SDL_GetPointerProperty(create_props, SDL_PROP_WINDOW_CREATE_WIN32_HWND_POINTER, SDL_GetPointerProperty(create_props, "sdl2-compat.external_window", NULL));
     HWND parent = NULL;
     HWND parent = NULL;
     if (hwnd) {
     if (hwnd) {
@@ -806,24 +762,19 @@ bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properties
 #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
 #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
     // FIXME: does not work on all hardware configurations with different renders (i.e. hybrid GPUs)
     // FIXME: does not work on all hardware configurations with different renders (i.e. hybrid GPUs)
     if (window->flags & SDL_WINDOW_TRANSPARENT) {
     if (window->flags & SDL_WINDOW_TRANSPARENT) {
-        SDL_SharedObject *handle = SDL_LoadObject("dwmapi.dll");
-        if (handle) {
-            DwmEnableBlurBehindWindow_t DwmEnableBlurBehindWindowFunc = (DwmEnableBlurBehindWindow_t)SDL_LoadFunction(handle, "DwmEnableBlurBehindWindow");
-            if (DwmEnableBlurBehindWindowFunc) {
-                /* The region indicates which part of the window will be blurred and rest will be transparent. This
-                   is because the alpha value of the window will be used for non-blurred areas
-                   We can use (-1, -1, 0, 0) boundary to make sure no pixels are being blurred
-                */
-                HRGN rgn = CreateRectRgn(-1, -1, 0, 0);
-                DWM_BLURBEHIND bb;
-                bb.flags = (DWM_BB_ENABLE | DWM_BB_BLURREGION);
-                bb.enable = TRUE;
-                bb.blur_region = rgn;
-                bb.transition_on_maxed = FALSE;
-                DwmEnableBlurBehindWindowFunc(hwnd, &bb);
-                DeleteObject(rgn);
-            }
-            SDL_UnloadObject(handle);
+        if (videodata->DwmEnableBlurBehindWindow) {
+            /* The region indicates which part of the window will be blurred and rest will be transparent. This
+               is because the alpha value of the window will be used for non-blurred areas
+               We can use (-1, -1, 0, 0) boundary to make sure no pixels are being blurred
+            */
+            HRGN rgn = CreateRectRgn(-1, -1, 0, 0);
+            DWM_BLURBEHIND bb;
+            bb.flags = (DWM_BB_ENABLE | DWM_BB_BLURREGION);
+            bb.enable = TRUE;
+            bb.blur_region = rgn;
+            bb.transition_on_maxed = FALSE;
+            videodata->DwmEnableBlurBehindWindow(hwnd, &bb);
+            DeleteObject(rgn);
         }
         }
     }
     }
 
 
@@ -1062,7 +1013,7 @@ void WIN_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *
     HWND hwnd = data->hwnd;
     HWND hwnd = data->hwnd;
     RECT rect;
     RECT rect;
 
 
-    if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) {
+    if (GetClientRect(hwnd, &rect) && WIN_WindowRectValid(&rect)) {
         *w = rect.right;
         *w = rect.right;
         *h = rect.bottom;
         *h = rect.bottom;
     } else if (window->last_pixel_w && window->last_pixel_h) {
     } else if (window->last_pixel_w && window->last_pixel_h) {
@@ -1265,42 +1216,20 @@ void WIN_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window)
     }
     }
 }
 }
 
 
-static DWM_WINDOW_CORNER_PREFERENCE WIN_UpdateCornerRoundingForHWND(HWND hwnd, DWM_WINDOW_CORNER_PREFERENCE cornerPref)
+static void WIN_UpdateCornerRoundingForHWND(SDL_VideoDevice *_this, HWND hwnd, DWM_WINDOW_CORNER_PREFERENCE cornerPref)
 {
 {
-    DWM_WINDOW_CORNER_PREFERENCE oldPref = DWMWCP_DEFAULT;
-
-    SDL_SharedObject *handle = SDL_LoadObject("dwmapi.dll");
-    if (handle) {
-        DwmGetWindowAttribute_t DwmGetWindowAttributeFunc = (DwmGetWindowAttribute_t)SDL_LoadFunction(handle, "DwmGetWindowAttribute");
-        DwmSetWindowAttribute_t DwmSetWindowAttributeFunc = (DwmSetWindowAttribute_t)SDL_LoadFunction(handle, "DwmSetWindowAttribute");
-        if (DwmGetWindowAttributeFunc && DwmSetWindowAttributeFunc) {
-            DwmGetWindowAttributeFunc(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &oldPref, sizeof(oldPref));
-            DwmSetWindowAttributeFunc(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPref, sizeof(cornerPref));
-        }
-
-        SDL_UnloadObject(handle);
+    SDL_VideoData *videodata = _this->internal;
+    if (videodata->DwmSetWindowAttribute) {
+        videodata->DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPref, sizeof(cornerPref));
     }
     }
-
-    return oldPref;
 }
 }
 
 
-static COLORREF WIN_UpdateBorderColorForHWND(HWND hwnd, COLORREF colorRef)
+static void WIN_UpdateBorderColorForHWND(SDL_VideoDevice *_this, HWND hwnd, COLORREF colorRef)
 {
 {
-    COLORREF oldPref = DWMWA_COLOR_DEFAULT;
-
-    SDL_SharedObject *handle = SDL_LoadObject("dwmapi.dll");
-    if (handle) {
-        DwmGetWindowAttribute_t DwmGetWindowAttributeFunc = (DwmGetWindowAttribute_t)SDL_LoadFunction(handle, "DwmGetWindowAttribute");
-        DwmSetWindowAttribute_t DwmSetWindowAttributeFunc = (DwmSetWindowAttribute_t)SDL_LoadFunction(handle, "DwmSetWindowAttribute");
-        if (DwmGetWindowAttributeFunc && DwmSetWindowAttributeFunc) {
-            DwmGetWindowAttributeFunc(hwnd, DWMWA_BORDER_COLOR, &oldPref, sizeof(oldPref));
-            DwmSetWindowAttributeFunc(hwnd, DWMWA_BORDER_COLOR, &colorRef, sizeof(colorRef));
-        }
-
-        SDL_UnloadObject(handle);
+    SDL_VideoData *videodata = _this->internal;
+    if (videodata->DwmSetWindowAttribute) {
+        videodata->DwmSetWindowAttribute(hwnd, DWMWA_BORDER_COLOR, &colorRef, sizeof(colorRef));
     }
     }
-
-    return oldPref;
 }
 }
 
 
 /**
 /**
@@ -1311,7 +1240,7 @@ SDL_FullscreenResult WIN_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window
 #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
 #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
     SDL_DisplayData *displaydata = display->internal;
     SDL_DisplayData *displaydata = display->internal;
     SDL_WindowData *data = window->internal;
     SDL_WindowData *data = window->internal;
-    HWND hwnd = data->hwnd;
+    HWND hwnd = data ? data->hwnd : NULL;
     MONITORINFO minfo;
     MONITORINFO minfo;
     DWORD style, styleEx;
     DWORD style, styleEx;
     HWND top;
     HWND top;
@@ -1366,13 +1295,13 @@ SDL_FullscreenResult WIN_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window
         }
         }
 
 
         // Disable corner rounding & border color (Windows 11+) so the window fills the full screen
         // Disable corner rounding & border color (Windows 11+) so the window fills the full screen
-        data->windowed_mode_corner_rounding = WIN_UpdateCornerRoundingForHWND(hwnd, DWMWCP_DONOTROUND);
-        data->dwma_border_color = WIN_UpdateBorderColorForHWND(hwnd, DWMWA_COLOR_NONE);
+        WIN_UpdateCornerRoundingForHWND(_this, hwnd, DWMWCP_DONOTROUND);
+        WIN_UpdateBorderColorForHWND(_this, hwnd, DWMWA_COLOR_NONE);
     } else {
     } else {
         BOOL menu;
         BOOL menu;
 
 
-        WIN_UpdateCornerRoundingForHWND(hwnd, (DWM_WINDOW_CORNER_PREFERENCE)data->windowed_mode_corner_rounding);
-        WIN_UpdateBorderColorForHWND(hwnd, data->dwma_border_color);
+        WIN_UpdateCornerRoundingForHWND(_this, hwnd, DWMWCP_DEFAULT);
+        WIN_UpdateBorderColorForHWND(_this, hwnd, DWMWA_COLOR_DEFAULT);
 
 
         /* Restore window-maximization state, as applicable.
         /* Restore window-maximization state, as applicable.
            Special care is taken to *not* do this if and when we're
            Special care is taken to *not* do this if and when we're

+ 0 - 2
libs/SDL3/src/video/windows/SDL_windowswindow.h

@@ -87,8 +87,6 @@ struct SDL_WindowData
     RECT initial_size_rect;
     RECT initial_size_rect;
     RECT cursor_clipped_rect; // last successfully committed clipping rect for this window
     RECT cursor_clipped_rect; // last successfully committed clipping rect for this window
     RECT cursor_ctrlock_rect; // this is Windows-specific, but probably does not need to be per-window
     RECT cursor_ctrlock_rect; // this is Windows-specific, but probably does not need to be per-window
-    UINT windowed_mode_corner_rounding;
-    COLORREF dwma_border_color;
     bool mouse_tracked;
     bool mouse_tracked;
     bool destroy_parent_with_window;
     bool destroy_parent_with_window;
     SDL_DisplayID last_displayID;
     SDL_DisplayID last_displayID;

+ 2 - 1
libs/SDL3/src/video/x11/SDL_x11events.c

@@ -977,7 +977,7 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_
             X11_HandleModifierKeys(videodata, scancode, true, true);
             X11_HandleModifierKeys(videodata, scancode, true, true);
             SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true);
             SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true);
 
 
-            if (*text) {
+            if (*text && !(SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_ALT))) {
                 text[text_length] = '\0';
                 text[text_length] = '\0';
                 X11_ClearComposition(windowdata);
                 X11_ClearComposition(windowdata);
                 SDL_SendKeyboardText(text);
                 SDL_SendKeyboardText(text);
@@ -2113,6 +2113,7 @@ void X11_PumpEvents(SDL_VideoDevice *_this)
                 SDL_LogError(SDL_LOG_CATEGORY_VIDEO,
                 SDL_LogError(SDL_LOG_CATEGORY_VIDEO,
                              "Time out elapsed after mode switch on display %" SDL_PRIu32 " with no window becoming fullscreen; reverting", _this->displays[i]->id);
                              "Time out elapsed after mode switch on display %" SDL_PRIu32 " with no window becoming fullscreen; reverting", _this->displays[i]->id);
                 SDL_SetDisplayModeForDisplay(_this->displays[i], NULL);
                 SDL_SetDisplayModeForDisplay(_this->displays[i], NULL);
+                _this->displays[i]->internal->mode_switch_deadline_ns = 0;
             }
             }
         }
         }
     }
     }

+ 22 - 12
libs/SDL3/src/video/x11/SDL_x11messagebox.c

@@ -48,13 +48,17 @@
 static const char g_MessageBoxFontLatin1[] =
 static const char g_MessageBoxFontLatin1[] =
     "-*-*-medium-r-normal--0-120-*-*-p-0-iso8859-1";
     "-*-*-medium-r-normal--0-120-*-*-p-0-iso8859-1";
 
 
-static const char g_MessageBoxFont[] =
-    "-*-*-medium-r-normal--*-120-*-*-*-*-iso10646-1,"  // explicitly unicode (iso10646-1)
-    "-*-*-medium-r-*--*-120-*-*-*-*-iso10646-1,"  // explicitly unicode (iso10646-1)
-    "-*-*-*-*-*--*-*-*-*-*-*-iso10646-1,"  // just give me anything Unicode.
-    "-*-*-medium-r-normal--*-120-*-*-*-*-iso8859-1,"  // explicitly latin1, in case low-ASCII works out.
-    "-*-*-medium-r-*--*-120-*-*-*-*-iso8859-1,"  // explicitly latin1, in case low-ASCII works out.
-    "-*-*-*-*-*--*-*-*-*-*-*-iso8859-1";  // just give me anything latin1.
+static const char* g_MessageBoxFont[] = {
+    "-*-*-medium-r-normal--*-120-*-*-*-*-iso10646-1",  // explicitly unicode (iso10646-1)
+    "-*-*-medium-r-*--*-120-*-*-*-*-iso10646-1",  // explicitly unicode (iso10646-1)
+    "-misc-*-*-*-*--*-*-*-*-*-*-iso10646-1",  // misc unicode (fix for some systems)
+    "-*-*-*-*-*--*-*-*-*-*-*-iso10646-1",  // just give me anything Unicode.
+    "-*-*-medium-r-normal--*-120-*-*-*-*-iso8859-1",  // explicitly latin1, in case low-ASCII works out.
+    "-*-*-medium-r-*--*-120-*-*-*-*-iso8859-1",  // explicitly latin1, in case low-ASCII works out.
+    "-misc-*-*-*-*--*-*-*-*-*-*-iso8859-1",  // misc latin1 (fix for some systems)
+    "-*-*-*-*-*--*-*-*-*-*-*-iso8859-1",  // just give me anything latin1.
+    NULL
+};
 
 
 static const SDL_MessageBoxColor g_default_colors[SDL_MESSAGEBOX_COLOR_COUNT] = {
 static const SDL_MessageBoxColor g_default_colors[SDL_MESSAGEBOX_COLOR_COUNT] = {
     { 56, 54, 53 },    // SDL_MESSAGEBOX_COLOR_BACKGROUND,
     { 56, 54, 53 },    // SDL_MESSAGEBOX_COLOR_BACKGROUND,
@@ -200,13 +204,19 @@ static bool X11_MessageBoxInit(SDL_MessageBoxDataX11 *data, const SDL_MessageBox
     if (SDL_X11_HAVE_UTF8) {
     if (SDL_X11_HAVE_UTF8) {
         char **missing = NULL;
         char **missing = NULL;
         int num_missing = 0;
         int num_missing = 0;
-        data->font_set = X11_XCreateFontSet(data->display, g_MessageBoxFont,
-                                            &missing, &num_missing, NULL);
-        if (missing) {
-            X11_XFreeStringList(missing);
+        int i_font;
+        for (i_font = 0; g_MessageBoxFont[i_font]; ++i_font) {
+            data->font_set = X11_XCreateFontSet(data->display, g_MessageBoxFont[i_font],
+                                                &missing, &num_missing, NULL);
+            if (missing) {
+                X11_XFreeStringList(missing);
+            }
+            if (data->font_set) {
+                break;
+            }
         }
         }
         if (!data->font_set) {
         if (!data->font_set) {
-            return SDL_SetError("Couldn't load font %s", g_MessageBoxFont);
+            return SDL_SetError("Couldn't load x11 message box font");
         }
         }
     } else
     } else
 #endif
 #endif

+ 63 - 9
libs/SDL3/src/video/x11/SDL_x11modes.c

@@ -555,22 +555,73 @@ static XRRScreenResources *X11_GetScreenResources(Display *dpy, int screen)
 
 
 static void X11_CheckDisplaysMoved(SDL_VideoDevice *_this, Display *dpy)
 static void X11_CheckDisplaysMoved(SDL_VideoDevice *_this, Display *dpy)
 {
 {
-    const int screen = DefaultScreen(dpy);
-    XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
-    if (!res) {
+    const int screencount = ScreenCount(dpy);
+
+    SDL_DisplayID *displays = SDL_GetDisplays(NULL);
+    if (!displays) {
         return;
         return;
     }
     }
 
 
-    SDL_DisplayID *displays = SDL_GetDisplays(NULL);
-    if (displays) {
+    for (int screen = 0; screen < screencount; ++screen) {
+        XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
+        if (!res) {
+            continue;
+        }
+
         for (int i = 0; displays[i]; ++i) {
         for (int i = 0; displays[i]; ++i) {
             SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]);
             SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]);
             const SDL_DisplayData *displaydata = display->internal;
             const SDL_DisplayData *displaydata = display->internal;
-            X11_UpdateXRandRDisplay(_this, dpy, screen, displaydata->xrandr_output, res, display);
+            if (displaydata->screen == screen) {
+                X11_UpdateXRandRDisplay(_this, dpy, screen, displaydata->xrandr_output, res, display);
+            }
         }
         }
-        SDL_free(displays);
+        X11_XRRFreeScreenResources(res);
     }
     }
-    X11_XRRFreeScreenResources(res);
+    SDL_free(displays);
+}
+
+static void X11_CheckDisplaysRemoved(SDL_VideoDevice *_this, Display *dpy)
+{
+    const int screencount = ScreenCount(dpy);
+    int num_displays = 0;
+
+    SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
+    if (!displays) {
+        return;
+    }
+
+    for (int screen = 0; screen < screencount; ++screen) {
+        XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
+        if (!res) {
+            continue;
+        }
+
+        for (int output = 0; output < res->noutput; output++) {
+            for (int i = 0; i < num_displays; ++i) {
+                if (!displays[i]) {
+                    // We already removed this display from the list
+                    continue;
+                }
+
+                SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]);
+                const SDL_DisplayData *displaydata = display->internal;
+                if (displaydata->xrandr_output == res->outputs[output]) {
+                    // This display is active, remove it from the list
+                    displays[i] = 0;
+                    break;
+                }
+            }
+        }
+        X11_XRRFreeScreenResources(res);
+    }
+
+    for (int i = 0; i < num_displays; ++i) {
+        if (displays[i]) {
+            // This display wasn't in the XRandR list
+            SDL_DelVideoDisplay(displays[i], true);
+        }
+    }
+    SDL_free(displays);
 }
 }
 
 
 static void X11_HandleXRandROutputChange(SDL_VideoDevice *_this, const XRROutputChangeNotifyEvent *ev)
 static void X11_HandleXRandROutputChange(SDL_VideoDevice *_this, const XRROutputChangeNotifyEvent *ev)
@@ -580,9 +631,12 @@ static void X11_HandleXRandROutputChange(SDL_VideoDevice *_this, const XRROutput
     int i;
     int i;
 
 
 #if 0
 #if 0
-    printf("XRROutputChangeNotifyEvent! [output=%u, crtc=%u, mode=%u, rotation=%u, connection=%u]", (unsigned int) ev->output, (unsigned int) ev->crtc, (unsigned int) ev->mode, (unsigned int) ev->rotation, (unsigned int) ev->connection);
+    printf("XRROutputChangeNotifyEvent! [output=%u, crtc=%u, mode=%u, rotation=%u, connection=%u]\n", (unsigned int) ev->output, (unsigned int) ev->crtc, (unsigned int) ev->mode, (unsigned int) ev->rotation, (unsigned int) ev->connection);
 #endif
 #endif
 
 
+    // XWayland doesn't always send output disconnected events
+    X11_CheckDisplaysRemoved(_this, ev->display);
+
     displays = SDL_GetDisplays(NULL);
     displays = SDL_GetDisplays(NULL);
     if (displays) {
     if (displays) {
         for (i = 0; displays[i]; ++i) {
         for (i = 0; displays[i]; ++i) {

+ 25 - 2
libs/SDL3/src/video/x11/SDL_x11video.c

@@ -78,6 +78,23 @@ static bool X11_IsXWayland(Display *d)
     return X11_XQueryExtension(d, "XWAYLAND", &opcode, &event, &error) == True;
     return X11_XQueryExtension(d, "XWAYLAND", &opcode, &event, &error) == True;
 }
 }
 
 
+static bool X11_CheckCurrentDesktop(const char *name)
+{
+    SDL_Environment *env = SDL_GetEnvironment();
+
+    const char *desktopVar = SDL_GetEnvironmentVariable(env, "DESKTOP_SESSION");
+    if (desktopVar && SDL_strcasecmp(desktopVar, name) == 0) {
+        return true;
+    }
+
+    desktopVar = SDL_GetEnvironmentVariable(env, "XDG_CURRENT_DESKTOP");
+    if (desktopVar && SDL_strcasestr(desktopVar, name)) {
+        return true;
+    }
+
+    return false;
+}
+
 static SDL_VideoDevice *X11_CreateDevice(void)
 static SDL_VideoDevice *X11_CreateDevice(void)
 {
 {
     SDL_VideoDevice *device;
     SDL_VideoDevice *device;
@@ -256,8 +273,14 @@ static SDL_VideoDevice *X11_CreateDevice(void)
         device->system_theme = SDL_SystemTheme_Get();
         device->system_theme = SDL_SystemTheme_Get();
 #endif
 #endif
 
 
-    device->device_caps = VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT |
-                          VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS;
+    device->device_caps = VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT;
+
+    /* Openbox doesn't send the new window dimensions when entering fullscreen, so the events must be synthesized.
+     * This is otherwise not wanted, as it can break fullscreen window positioning on multi-monitor configurations.
+     */
+    if (!X11_CheckCurrentDesktop("openbox")) {
+        device->device_caps |= VIDEO_DEVICE_CAPS_SENDS_DISPLAY_CHANGES;
+    }
 
 
     data->is_xwayland = X11_IsXWayland(x11_display);
     data->is_xwayland = X11_IsXWayland(x11_display);
     if (data->is_xwayland) {
     if (data->is_xwayland) {

Some files were not shown because too many files changed in this diff