diff --git a/CMakeLists.txt b/CMakeLists.txt index 1eeb75a9..c9321f0a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,7 +227,7 @@ set(MEGA_LIBVORBIS_VER "1.3.5") set(MEGA_LIBTHEORA_VER "1.1.1") set(MEGA_FREETYPE_VER "2.13.2") 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_MODPLUG_VER "0.8.8.4") diff --git a/libs/SDL3/CMakeLists.txt b/libs/SDL3/CMakeLists.txt index 62a6e460..c10eb8cf 100644 --- a/libs/SDL3/CMakeLists.txt +++ b/libs/SDL3/CMakeLists.txt @@ -5,7 +5,7 @@ if(NOT DEFINED CMAKE_BUILD_TYPE) endif() # 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) set(SDL3_MAINPROJECT ON) @@ -2188,6 +2188,7 @@ elseif(APPLE) set(SDL_CAMERA_DRIVER_COREMEDIA 1) set(HAVE_CAMERA TRUE) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/coremedia/*.m") + set(SDL_FRAMEWORK_AVFOUNDATION 1) endif() endif() diff --git a/libs/SDL3/INSTALL.md b/libs/SDL3/INSTALL.md index b33c2111..f2db667c 100644 --- a/libs/SDL3/INSTALL.md +++ b/libs/SDL3/INSTALL.md @@ -3,6 +3,7 @@ SDL supports a number of development environments: - [CMake](docs/INTRO-cmake.md) - [Visual Studio on Windows](docs/INTRO-visualstudio.md) +- [gcc on Windows](docs/INTRO-mingw.md) - [Xcode on Apple platforms](docs/INTRO-xcode.md) - [Android Studio](docs/INTRO-androidstudio.md) - [Emscripten for web](docs/INTRO-emscripten.md) diff --git a/libs/SDL3/WhatsNew.txt b/libs/SDL3/WhatsNew.txt index 22b1a0f8..0d10ee67 100644 --- a/libs/SDL3/WhatsNew.txt +++ b/libs/SDL3/WhatsNew.txt @@ -1,6 +1,16 @@ 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: --------------------------------------------------------------------------- diff --git a/libs/SDL3/Xcode/SDL/Info-Framework.plist b/libs/SDL3/Xcode/SDL/Info-Framework.plist index 02a79b59..eec80013 100644 --- a/libs/SDL3/Xcode/SDL/Info-Framework.plist +++ b/libs/SDL3/Xcode/SDL/Info-Framework.plist @@ -19,10 +19,10 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 3.2.6 + 3.2.10 CFBundleSignature SDLX CFBundleVersion - 3.2.6 + 3.2.10 diff --git a/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj index d5177a1c..5926329c 100644 --- a/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -3086,7 +3086,7 @@ CLANG_ENABLE_OBJC_ARC = YES; DEPLOYMENT_POSTPROCESSING = YES; DYLIB_COMPATIBILITY_VERSION = 201.0.0; - DYLIB_CURRENT_VERSION = 201.6.0; + DYLIB_CURRENT_VERSION = 201.10.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_ALTIVEC_EXTENSIONS = YES; @@ -3121,7 +3121,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 3.2.6; + MARKETING_VERSION = 3.2.10; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3; PRODUCT_NAME = SDL3; @@ -3150,7 +3150,7 @@ CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; DYLIB_COMPATIBILITY_VERSION = 201.0.0; - DYLIB_CURRENT_VERSION = 201.6.0; + DYLIB_CURRENT_VERSION = 201.10.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -3182,7 +3182,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 3.2.6; + MARKETING_VERSION = 3.2.10; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3; diff --git a/libs/SDL3/Xcode/SDL/pkg-support/SDL.info b/libs/SDL3/Xcode/SDL/pkg-support/SDL.info index cb68cd8d..2ef06d56 100644 --- a/libs/SDL3/Xcode/SDL/pkg-support/SDL.info +++ b/libs/SDL3/Xcode/SDL/pkg-support/SDL.info @@ -1,4 +1,4 @@ -Title SDL 3.2.6 +Title SDL 3.2.10 Version 1 Description SDL Library for macOS (http://www.libsdl.org) DefaultLocation /Library/Frameworks diff --git a/libs/SDL3/android-project/app/build.gradle b/libs/SDL3/android-project/app/build.gradle index 8946de66..989ef34c 100644 --- a/libs/SDL3/android-project/app/build.gradle +++ b/libs/SDL3/android-project/app/build.gradle @@ -5,7 +5,7 @@ plugins { def buildWithCMake = project.hasProperty('BUILD_WITH_CMAKE'); android { - namespace "org.libsdl.app" + namespace = "org.libsdl.app" compileSdkVersion 35 defaultConfig { minSdkVersion 21 @@ -14,12 +14,12 @@ android { versionName "1.0" externalNativeBuild { ndkBuild { - arguments "APP_PLATFORM=android-19" + arguments "APP_PLATFORM=android-21" // abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' abiFilters 'arm64-v8a' } 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 'arm64-v8a' } @@ -53,7 +53,7 @@ android { } lint { - abortOnError false + abortOnError = false } } diff --git a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java index 648db8be..6bfcc0bd 100644 --- a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/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 int SDL_MAJOR_VERSION = 3; 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 // diff --git a/libs/SDL3/cmake/sdlchecks.cmake b/libs/SDL3/cmake/sdlchecks.cmake index d95cbfa3..a444688a 100644 --- a/libs/SDL3/cmake/sdlchecks.cmake +++ b/libs/SDL3/cmake/sdlchecks.cmake @@ -823,7 +823,7 @@ macro(CheckPTHREAD) if(CMAKE_C_COMPILER_ID MATCHES "SunPro") set(PTHREAD_LDFLAGS "-mt -lpthread") else() - set(PTHREAD_LDFLAGS "-pthread -lposix4") + set(PTHREAD_LDFLAGS "-pthread") endif() elseif(SYSV5) set(PTHREAD_CFLAGS "-D_REENTRANT -Kthread") diff --git a/libs/SDL3/docs/INTRO-cmake.md b/libs/SDL3/docs/INTRO-cmake.md index e6ccace3..05990e4c 100644 --- a/libs/SDL3/docs/INTRO-cmake.md +++ b/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) -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_minimum_required(VERSION 3.16) project(hello) @@ -25,24 +30,26 @@ add_executable(hello WIN32 hello.c) target_link_libraries(hello PRIVATE SDL3::SDL3) ``` -Build: +# Configure and Build: ```sh cmake -S . -B build cmake --build build ``` -Run: -- On Windows the executable is in the build Debug directory: -```sh -cd build/Debug -./hello -``` -- On other platforms the executable is in the build directory: +# Run: +The executable should be in the `build` directory: + ```sh cd build ./hello ``` +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 +cd build/Debug +./hello +``` + A more complete example is available at: https://github.com/Ravbug/sdl3-sample diff --git a/libs/SDL3/docs/INTRO-mingw.md b/libs/SDL3/docs/INTRO-mingw.md new file mode 100644 index 00000000..d7c535dd --- /dev/null +++ b/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--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-` 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 +#include +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 ``. 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 `` with the version of the release of SDL3 you downloaded, as well as use the `` we learned in the previous section. + +```sh +gcc hello.c -o hello.exe -I SDL3-//include -L SDL3-//lib -lSDL3 -mwindows +cp SDL3-//bin/SDL3.dll SDL3.dll +./hello.exe +``` diff --git a/libs/SDL3/docs/INTRO-visualstudio.md b/libs/SDL3/docs/INTRO-visualstudio.md index 2cc6100c..4017f79e 100644 --- a/libs/SDL3/docs/INTRO-visualstudio.md +++ b/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) +- 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 - 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! diff --git a/libs/SDL3/docs/README-android.md b/libs/SDL3/docs/README-android.md index 37905c46..e57ca81d 100644 --- a/libs/SDL3/docs/README-android.md +++ b/libs/SDL3/docs/README-android.md @@ -242,7 +242,7 @@ not give you any processing time after the events are delivered. e.g. - int HandleAppEvents(void *userdata, SDL_Event *event) + bool HandleAppEvents(void *userdata, SDL_Event *event) { switch (event->type) { @@ -250,12 +250,12 @@ e.g. /* Terminate the app. Shut everything down before returning from this function. */ - return 0; + return false; case SDL_EVENT_LOW_MEMORY: /* You will get this when your app is paused and iOS wants more memory. Release as much memory as possible. */ - return 0; + return false; case SDL_EVENT_WILL_ENTER_BACKGROUND: /* 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. @@ -264,15 +264,15 @@ e.g. in addition, you should set the render target to NULL, if you're using it, e.g. call SDL_SetRenderTarget(renderer, NULL). */ - return 0; + return false; case SDL_EVENT_DID_ENTER_BACKGROUND: /* Your app is NOT active at this point. */ - return 0; + return false; case SDL_EVENT_WILL_ENTER_FOREGROUND: /* This call happens when your app is coming back to the foreground. Restore all your state here. */ - return 0; + return false; case SDL_EVENT_DID_ENTER_FOREGROUND: /* Restart your loops here. 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 restore your textures when you get it, or quit the app. */ - return 0; + return false; default: /* No special processing, add it to the event queue */ - return 1; + return true; } } diff --git a/libs/SDL3/docs/README-ios.md b/libs/SDL3/docs/README-ios.md index dbd70f5a..166d1826 100644 --- a/libs/SDL3/docs/README-ios.md +++ b/libs/SDL3/docs/README-ios.md @@ -65,7 +65,7 @@ not give you any processing time after the events are delivered. e.g. - int HandleAppEvents(void *userdata, SDL_Event *event) + bool HandleAppEvents(void *userdata, SDL_Event *event) { switch (event->type) { @@ -73,37 +73,37 @@ e.g. /* Terminate the app. Shut everything down before returning from this function. */ - return 0; + return false; case SDL_EVENT_LOW_MEMORY: /* You will get this when your app is paused and iOS wants more memory. Release as much memory as possible. */ - return 0; + return false; case SDL_EVENT_WILL_ENTER_BACKGROUND: /* 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. */ - return 0; + return false; case SDL_EVENT_DID_ENTER_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. 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. */ - return 0; + return false; case SDL_EVENT_WILL_ENTER_FOREGROUND: /* This call happens when your app is coming back to the foreground. Restore all your state here. */ - return 0; + return false; case SDL_EVENT_DID_ENTER_FOREGROUND: /* Restart your loops here. Your app is interactive and getting CPU again. */ - return 0; + return false; default: /* No special processing, add it to the event queue */ - return 1; + return true; } } diff --git a/libs/SDL3/docs/README-psp.md b/libs/SDL3/docs/README-psp.md index c010c609..65d2af29 100644 --- a/libs/SDL3/docs/README-psp.md +++ b/libs/SDL3/docs/README-psp.md @@ -29,7 +29,7 @@ cmake --install build ## 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 - PSP Screen Keyboard diff --git a/libs/SDL3/docs/README-wayland.md b/libs/SDL3/docs/README-wayland.md index 557a8139..75a9b906 100644 --- a/libs/SDL3/docs/README-wayland.md +++ b/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 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 Under normal operation, an `SDL_Window` corresponds to an XDG toplevel window, which provides a standard desktop window. diff --git a/libs/SDL3/include/SDL3/SDL.h b/libs/SDL3/include/SDL3/SDL.h index f8586ad4..9d216887 100644 --- a/libs/SDL3/include/SDL3/SDL.h +++ b/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 * picking out individual headers included here. There are exceptions to diff --git a/libs/SDL3/include/SDL3/SDL_audio.h b/libs/SDL3/include/SDL3/SDL_audio.h index 541c5dfb..c6acf885 100644 --- a/libs/SDL3/include/SDL3/SDL_audio.h +++ b/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 * 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, * 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 @@ -1762,13 +1762,13 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetAudioStreamGetCallback(SDL_AudioStream * * The callback can (optionally) call SDL_GetAudioStreamData() to obtain audio * 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 * currently available to read from the stream, instead of the total provided diff --git a/libs/SDL3/include/SDL3/SDL_gpu.h b/libs/SDL3/include/SDL3/SDL_gpu.h index 818ca7e7..9f516d73 100644 --- a/libs/SDL3/include/SDL3/SDL_gpu.h +++ b/libs/SDL3/include/SDL3/SDL_gpu.h @@ -4145,7 +4145,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GPUTextureSupportsFormat( * \param device a GPU context. * \param format the texture format 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. */ diff --git a/libs/SDL3/include/SDL3/SDL_hints.h b/libs/SDL3/include/SDL3/SDL_hints.h index 3206dc13..9c8ad3f8 100644 --- a/libs/SDL3/include/SDL3/SDL_hints.h +++ b/libs/SDL3/include/SDL3/SDL_hints.h @@ -3607,6 +3607,22 @@ extern "C" { */ #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 * should be used. diff --git a/libs/SDL3/include/SDL3/SDL_power.h b/libs/SDL3/include/SDL3/SDL_power.h index 4056ce3c..694fb092 100644 --- a/libs/SDL3/include/SDL3/SDL_power.h +++ b/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 * 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, * or NULL to ignore. This will be filled in with -1 if we * can't determine a value or there is no battery. diff --git a/libs/SDL3/include/SDL3/SDL_stdinc.h b/libs/SDL3/include/SDL3/SDL_stdinc.h index 9903a0b0..b2728da2 100644 --- a/libs/SDL3/include/SDL3/SDL_stdinc.h +++ b/libs/SDL3/include/SDL3/SDL_stdinc.h @@ -1299,8 +1299,11 @@ extern "C" { * * 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. * \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 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 size the size of each element of the array. * \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 * 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 size the new size of the memory. * \returns a pointer to the newly allocated memory, or NULL if allocation diff --git a/libs/SDL3/include/SDL3/SDL_surface.h b/libs/SDL3/include/SDL3/SDL_surface.h index 415da1b7..7bff7cfb 100644 --- a/libs/SDL3/include/SDL3/SDL_surface.h +++ b/libs/SDL3/include/SDL3/SDL_surface.h @@ -82,6 +82,7 @@ typedef Uint32 SDL_SurfaceFlags; */ typedef enum SDL_ScaleMode { + SDL_SCALEMODE_INVALID = -1, SDL_SCALEMODE_NEAREST, /**< nearest pixel sampling */ SDL_SCALEMODE_LINEAR /**< linear filtering */ } 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; * 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. * * \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; * 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. * * \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. * + * \threadsafety No other thread should be using the surface when it is freed. + * * \since This function is available since SDL 3.2.0. * * \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 * 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. */ 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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * the surface is NULL. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * more information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * no palette used. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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. * \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. * * \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 * 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. * * \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. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * 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. * * \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. * + * \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. * * \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 * 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. * * \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 * 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. * * \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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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. * \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. * * \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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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. * \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. * * \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 * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \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 * blits will be completely clipped. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. */ 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 * more information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * more information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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; * call SDL_GetError() for more information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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; * call SDL_GetError() for more information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * 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. * * \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 * 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. * * \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 * 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. */ 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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. */ 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 * information. * + * \threadsafety This function is not thread safe. + * * \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); @@ -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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \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 * 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. * @@ -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 * 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. * @@ -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 * 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. * @@ -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 * 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. * @@ -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 * 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. * @@ -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 * 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. * @@ -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 * 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. * @@ -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 * 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. * @@ -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. * \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. * * \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. * \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. * * \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 * information. * + * \threadsafety This function is not thread safe. + * * \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); @@ -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 * information. * + * \threadsafety This function is not thread safe. + * * \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); @@ -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 * information. * + * \threadsafety This function is not thread safe. + * * \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); @@ -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 * information. * + * \threadsafety This function is not thread safe. + * * \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); diff --git a/libs/SDL3/include/SDL3/SDL_version.h b/libs/SDL3/include/SDL3/SDL_version.h index 8b8dca6d..a3b6ae82 100644 --- a/libs/SDL3/include/SDL3/SDL_version.h +++ b/libs/SDL3/include/SDL3/SDL_version.h @@ -62,7 +62,7 @@ extern "C" { * * \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. diff --git a/libs/SDL3/src/SDL.c b/libs/SDL3/src/SDL.c index e9c9fb1a..502f6617 100644 --- a/libs/SDL3/src/SDL.c +++ b/libs/SDL3/src/SDL.c @@ -356,7 +356,9 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO); if (!SDL_VideoInit(NULL)) { SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO); + SDL_PushError(); SDL_QuitSubSystem(SDL_INIT_EVENTS); + SDL_PopError(); goto quit_and_error; } } else { @@ -381,7 +383,9 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) SDL_IncrementSubsystemRefCount(SDL_INIT_AUDIO); if (!SDL_InitAudio(NULL)) { SDL_DecrementSubsystemRefCount(SDL_INIT_AUDIO); + SDL_PushError(); SDL_QuitSubSystem(SDL_INIT_EVENTS); + SDL_PopError(); goto quit_and_error; } } else { @@ -406,7 +410,9 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) SDL_IncrementSubsystemRefCount(SDL_INIT_JOYSTICK); if (!SDL_InitJoysticks()) { SDL_DecrementSubsystemRefCount(SDL_INIT_JOYSTICK); + SDL_PushError(); SDL_QuitSubSystem(SDL_INIT_EVENTS); + SDL_PopError(); goto quit_and_error; } } else { @@ -430,7 +436,9 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) SDL_IncrementSubsystemRefCount(SDL_INIT_GAMEPAD); if (!SDL_InitGamepads()) { SDL_DecrementSubsystemRefCount(SDL_INIT_GAMEPAD); + SDL_PushError(); SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + SDL_PopError(); goto quit_and_error; } } else { @@ -493,7 +501,9 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) SDL_IncrementSubsystemRefCount(SDL_INIT_CAMERA); if (!SDL_CameraInit(NULL)) { SDL_DecrementSubsystemRefCount(SDL_INIT_CAMERA); + SDL_PushError(); SDL_QuitSubSystem(SDL_INIT_EVENTS); + SDL_PopError(); goto quit_and_error; } } else { @@ -511,7 +521,11 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) return SDL_ClearError(); quit_and_error: - SDL_QuitSubSystem(flags_initialized); + { + SDL_PushError(); + SDL_QuitSubSystem(flags_initialized); + SDL_PopError(); + } return false; } diff --git a/libs/SDL3/src/SDL_assert.c b/libs/SDL3/src/SDL_assert.c index 62a90ca2..2440d04b 100644 --- a/libs/SDL3/src/SDL_assert.c +++ b/libs/SDL3/src/SDL_assert.c @@ -34,15 +34,7 @@ #endif #ifdef SDL_PLATFORM_EMSCRIPTEN - #include - // 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 #endif // 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 (;;) { bool okay = true; /* *INDENT-OFF* */ // clang-format off - char *buf = (char *) MAIN_THREAD_EM_ASM_PTR({ + int reply = MAIN_THREAD_EM_ASM_INT({ var str = UTF8ToString($0) + '\n\n' + 'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :'; @@ -260,26 +252,32 @@ static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, v if (reply === null) { reply = "i"; } - return allocate(intArrayFromString(reply), 'i8', ALLOC_NORMAL); + return reply.length === 1 ? reply.charCodeAt(0) : -1; }, message); /* *INDENT-ON* */ // clang-format on - if (SDL_strcmp(buf, "a") == 0) { + switch (reply) { + case 'a': state = SDL_ASSERTION_ABORT; + break; #if 0 // (currently) no break functionality on Emscripten - } else if (SDL_strcmp(buf, "b") == 0) { + case 'b': state = SDL_ASSERTION_BREAK; + break; #endif - } else if (SDL_strcmp(buf, "r") == 0) { + case 'r': state = SDL_ASSERTION_RETRY; - } else if (SDL_strcmp(buf, "i") == 0) { + break; + case 'i': state = SDL_ASSERTION_IGNORE; - } else if (SDL_strcmp(buf, "A") == 0) { + break; + case 'A': state = SDL_ASSERTION_ALWAYS_IGNORE; - } else { + break; + default: okay = false; + break; } - free(buf); // This should NOT be SDL_free() if (okay) { break; diff --git a/libs/SDL3/src/SDL_error_c.h b/libs/SDL3/src/SDL_error_c.h index da9f8b5c..ba4550ed 100644 --- a/libs/SDL3/src/SDL_error_c.h +++ b/libs/SDL3/src/SDL_error_c.h @@ -46,4 +46,16 @@ typedef struct SDL_error // Defined in SDL_thread.c 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_ diff --git a/libs/SDL3/src/audio/SDL_audio.c b/libs/SDL3/src/audio/SDL_audio.c index 583a1591..cf817e4f 100644 --- a/libs/SDL3/src/audio/SDL_audio.c +++ b/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_PlaybackAudioThreadSetup(device); - do { + while (SDL_PlaybackAudioThreadIterate(device)) { if (!device->WaitDevice(device)) { 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); return 0; diff --git a/libs/SDL3/src/audio/SDL_audiotypecvt.c b/libs/SDL3/src/audio/SDL_audiotypecvt.c index d80a831f..a27575f2 100644 --- a/libs/SDL3/src/audio/SDL_audiotypecvt.c +++ b/libs/SDL3/src/audio/SDL_audiotypecvt.c @@ -22,6 +22,10 @@ #include "SDL_sysaudio.h" +#ifdef SDL_NEON_INTRINSICS +#include +#endif + #define DIVBY2147483648 0.0000000004656612873077392578125f // 0x1p-31f // start fallback scalar converters @@ -527,9 +531,27 @@ static void SDL_TARGETING("ssse3") SDL_Convert_Swap32_SSSE3(Uint32* dst, const U #endif #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) { LOG_DEBUG_AUDIO_CONVERT("S8", "F32 (using NEON)"); + fenv_t fenv; + feholdexcept(&fenv); CONVERT_16_REV({ 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 + 12], floats3); }) + fesetenv(&fenv); } static void SDL_Convert_U8_to_F32_NEON(float *dst, const Uint8 *src, int num_samples) { LOG_DEBUG_AUDIO_CONVERT("U8", "F32 (using NEON)"); + fenv_t fenv; + feholdexcept(&fenv); 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 + 12], floats3); }) + fesetenv(&fenv); } static void SDL_Convert_S16_to_F32_NEON(float *dst, const Sint16 *src, int num_samples) { LOG_DEBUG_AUDIO_CONVERT("S16", "F32 (using NEON)"); + fenv_t fenv; + feholdexcept(&fenv); CONVERT_16_REV({ 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 + 12], floats3); }) + fesetenv(&fenv); } static void SDL_Convert_S32_to_F32_NEON(float *dst, const Sint32 *src, int num_samples) { LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using NEON)"); + fenv_t fenv; + feholdexcept(&fenv); CONVERT_16_FWD({ 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 + 12], floats3); }) + fesetenv(&fenv); } static void SDL_Convert_F32_to_S8_NEON(Sint8 *dst, const float *src, int num_samples) { LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using NEON)"); + fenv_t fenv; + feholdexcept(&fenv); CONVERT_16_FWD({ 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); }) + fesetenv(&fenv); } static void SDL_Convert_F32_to_U8_NEON(Uint8 *dst, const float *src, int num_samples) { LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using NEON)"); + fenv_t fenv; + feholdexcept(&fenv); 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); }) + fesetenv(&fenv); } static void SDL_Convert_F32_to_S16_NEON(Sint16 *dst, const float *src, int num_samples) { LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using NEON)"); + fenv_t fenv; + feholdexcept(&fenv); CONVERT_16_FWD({ 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 + 8], shorts1); }) + fesetenv(&fenv); } static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_samples) { LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using NEON)"); + fenv_t fenv; + feholdexcept(&fenv); CONVERT_16_FWD({ 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 + 12], ints3); }) + fesetenv(&fenv); } 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); }) } + +#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 #undef CONVERT_16_FWD diff --git a/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c b/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c index 308eb235..badbc43e 100644 --- a/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c +++ b/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); } #endif - ALSA_snd_pcm_start(cfg_ctx.device->hidden->pcm); return true; // We're ready to rock and roll. :-) err_cleanup_ctx: @@ -1200,6 +1199,13 @@ err_free_device_hidden: 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 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->OpenDevice = ALSA_OpenDevice; + impl->ThreadInit = ALSA_ThreadInit; impl->WaitDevice = ALSA_WaitDevice; impl->GetDeviceBuf = ALSA_GetDeviceBuf; impl->PlayDevice = ALSA_PlayDevice; diff --git a/libs/SDL3/src/audio/pipewire/SDL_pipewire.c b/libs/SDL3/src/audio/pipewire/SDL_pipewire.c index e3f9439f..32c93f61 100644 --- a/libs/SDL3/src/audio/pipewire/SDL_pipewire.c +++ b/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_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_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) { PIPEWIRE_pw_thread_loop_lock(hotplug_loop); diff --git a/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c b/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c index 049a5ec6..69e8c1a8 100644 --- a/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/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; 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); @@ -701,7 +701,10 @@ static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) 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. - 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; if (recording) { diff --git a/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c b/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c index 4de2f234..5d868bfc 100644 --- a/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c +++ b/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c @@ -58,7 +58,9 @@ static bool pipewire_initialized = false; // Pipewire entry points 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); +#endif static void (*PIPEWIRE_pw_init)(int *, char ***); static void (*PIPEWIRE_pw_deinit)(void); 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) { SDL_PIPEWIRE_SYM(pw_get_library_version); +#if PW_CHECK_VERSION(0, 3, 75) SDL_PIPEWIRE_SYM(pw_check_library_version); +#endif SDL_PIPEWIRE_SYM(pw_init); SDL_PIPEWIRE_SYM(pw_deinit); SDL_PIPEWIRE_SYM(pw_main_loop_new); @@ -1024,7 +1028,11 @@ static bool hotplug_loop_init(void) 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); +#else + hotplug.have_1_0_5 = false; +#endif hotplug.loop = PIPEWIRE_pw_thread_loop_new("SDLPwCameraPlug", NULL); if (!hotplug.loop) { diff --git a/libs/SDL3/src/core/windows/SDL_windows.c b/libs/SDL3/src/core/windows/SDL_windows.c index 286e8e68..3259e787 100644 --- a/libs/SDL3/src/core/windows/SDL_windows.c +++ b/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; } -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. diff --git a/libs/SDL3/src/core/windows/SDL_windows.h b/libs/SDL3/src/core/windows/SDL_windows.h index b781b123..ef54fe37 100644 --- a/libs/SDL3/src/core/windows/SDL_windows.h +++ b/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 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); diff --git a/libs/SDL3/src/core/windows/version.rc b/libs/SDL3/src/core/windows/version.rc index cbe3a938..4f94933a 100644 --- a/libs/SDL3/src/core/windows/version.rc +++ b/libs/SDL3/src/core/windows/version.rc @@ -9,8 +9,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // 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 FILEFLAGS 0x0L FILEOS 0x40004L @@ -23,12 +23,12 @@ BEGIN BEGIN VALUE "CompanyName", "\0" VALUE "FileDescription", "SDL\0" - VALUE "FileVersion", "3, 2, 6, 0\0" + VALUE "FileVersion", "3, 2, 10, 0\0" VALUE "InternalName", "SDL\0" VALUE "LegalCopyright", "Copyright (C) 2025 Sam Lantinga\0" VALUE "OriginalFilename", "SDL3.dll\0" VALUE "ProductName", "Simple DirectMedia Layer\0" - VALUE "ProductVersion", "3, 2, 6, 0\0" + VALUE "ProductVersion", "3, 2, 10, 0\0" END END BLOCK "VarFileInfo" diff --git a/libs/SDL3/src/cpuinfo/SDL_cpuinfo.c b/libs/SDL3/src/cpuinfo/SDL_cpuinfo.c index bf425a38..81d1e914 100644 --- a/libs/SDL3/src/cpuinfo/SDL_cpuinfo.c +++ b/libs/SDL3/src/cpuinfo/SDL_cpuinfo.c @@ -85,7 +85,9 @@ #include #include #endif - +#ifdef SDL_PLATFORM_3DS +#include <3ds.h> +#endif #ifdef SDL_PLATFORM_PS2 #include #endif @@ -642,6 +644,15 @@ int SDL_GetNumLogicalCPUCores(void) GetSystemInfo(&info); 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 // There has to be at least 1, right? :) if (SDL_NumLogicalCPUCores <= 0) { @@ -1156,6 +1167,12 @@ int SDL_GetSystemRAM(void) } } #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 if (SDL_SystemRAM <= 0) { /* Vita has 512MiB on SoC, that's split into 256MiB(+109MiB in extended memory mode) for app diff --git a/libs/SDL3/src/events/SDL_events.c b/libs/SDL3/src/events/SDL_events.c index 157f0180..349d5750 100644 --- a/libs/SDL3/src/events/SDL_events.c +++ b/libs/SDL3/src/events/SDL_events.c @@ -110,7 +110,7 @@ typedef struct } SDL_DisabledEventBlock; static SDL_DisabledEventBlock *SDL_disabled_events[256]; -static Uint32 SDL_userevents = SDL_EVENT_USER; +static SDL_AtomicInt SDL_userevents; typedef struct SDL_TemporaryMemory { @@ -1893,9 +1893,11 @@ Uint32 SDL_RegisterEvents(int numevents) { 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; } diff --git a/libs/SDL3/src/events/SDL_keyboard.c b/libs/SDL3/src/events/SDL_keyboard.c index 066c6e6a..7f1ac7aa 100644 --- a/libs/SDL3/src/events/SDL_keyboard.c +++ b/libs/SDL3/src/events/SDL_keyboard.c @@ -747,7 +747,7 @@ void SDL_SendKeyboardText(const char *text) { SDL_Keyboard *keyboard = &SDL_keyboard; - if (!SDL_TextInputActive(keyboard->focus)) { + if (!keyboard->focus || !SDL_TextInputActive(keyboard->focus)) { return; } @@ -778,7 +778,7 @@ void SDL_SendEditingText(const char *text, int start, int length) { SDL_Keyboard *keyboard = &SDL_keyboard; - if (!SDL_TextInputActive(keyboard->focus)) { + if (!keyboard->focus || !SDL_TextInputActive(keyboard->focus)) { return; } @@ -838,7 +838,7 @@ void SDL_SendEditingTextCandidates(char **candidates, int num_candidates, int se { SDL_Keyboard *keyboard = &SDL_keyboard; - if (!SDL_TextInputActive(keyboard->focus)) { + if (!keyboard->focus || !SDL_TextInputActive(keyboard->focus)) { return; } diff --git a/libs/SDL3/src/events/SDL_mouse.c b/libs/SDL3/src/events/SDL_mouse.c index 2ea5995d..777733fe 100644 --- a/libs/SDL3/src/events/SDL_mouse.c +++ b/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 } +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 bool SDL_PreInitMouse(void) { @@ -288,6 +299,9 @@ bool SDL_PreInitMouse(void) SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE, 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->cursor_shown = true; @@ -720,12 +734,22 @@ static void SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL 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; yrel = y; x = (mouse->last_x + xrel); y = (mouse->last_y + yrel); ConstrainMousePosition(mouse, window, &x, &y); } 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); if (mouse->has_position) { xrel = x - mouse->last_x; @@ -998,6 +1022,12 @@ void SDL_SendMouseWheel(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseI 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) { return; } @@ -1108,6 +1138,9 @@ void SDL_QuitMouse(void) SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE, SDL_MouseRelativeCursorVisibleChanged, mouse); + SDL_RemoveHintCallback("SDL_MOUSE_INTEGER_MODE", + SDL_MouseIntegerModeChanged, mouse); + for (int i = SDL_mouse_count; i--; ) { SDL_RemoveMouse(SDL_mice[i].instance_id, false); } diff --git a/libs/SDL3/src/events/SDL_mouse_c.h b/libs/SDL3/src/events/SDL_mouse_c.h index 43cc5207..dd9a0c15 100644 --- a/libs/SDL3/src/events/SDL_mouse_c.h +++ b/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 *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 SDL_Window *focus; float x; diff --git a/libs/SDL3/src/gpu/SDL_gpu.c b/libs/SDL3/src/gpu/SDL_gpu.c index 0897d1c9..b7ca6962 100644 --- a/libs/SDL3/src/gpu/SDL_gpu.c +++ b/libs/SDL3/src/gpu/SDL_gpu.c @@ -2646,8 +2646,11 @@ bool SDL_ClaimWindowForGPUDevice( { CHECK_DEVICE_MAGIC(device, false); 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( diff --git a/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m b/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m index 365b3448..4ab10206 100644 --- a/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m +++ b/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m @@ -854,7 +854,7 @@ static MetalLibraryFunction METAL_INTERNAL_CompileShader( code, codeSize, dispatch_get_global_queue(0, 0), - ^{ /* do nothing */ }); + DISPATCH_DATA_DESTRUCTOR_DEFAULT); library = [renderer->device newLibraryWithData:data error:&error]; } else { SDL_assert(!"SDL_gpu.c should have already validated this!"); diff --git a/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c b/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c index 3014b4b3..7ea10367 100644 --- a/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -1135,6 +1135,7 @@ struct VulkanRenderer VulkanMemoryAllocator *memoryAllocator; VkPhysicalDeviceMemoryProperties memoryProperties; + bool checkEmptyAllocations; WindowData **claimedWindows; Uint32 claimedWindowCount; @@ -1203,6 +1204,7 @@ struct VulkanRenderer SDL_Mutex *submitLock; SDL_Mutex *acquireCommandBufferLock; SDL_Mutex *acquireUniformBufferLock; + SDL_Mutex *renderPassFetchLock; SDL_Mutex *framebufferFetchLock; SDL_Mutex *windowLock; @@ -1577,6 +1579,10 @@ static void VULKAN_INTERNAL_RemoveMemoryUsedRegion( usedRegion->offset, usedRegion->size); + if (usedRegion->allocation->usedRegionCount == 0) { + renderer->checkEmptyAllocations = true; + } + SDL_free(usedRegion); SDL_UnlockMutex(renderer->allocatorLock); @@ -4634,7 +4640,11 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; swapchainCreateInfo.queueFamilyIndexCount = 0; swapchainCreateInfo.pQueueFamilyIndices = NULL; +#ifdef SDL_PLATFORM_ANDROID swapchainCreateInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; +#else + swapchainCreateInfo.preTransform = swapchainSupportDetails.capabilities.currentTransform; +#endif swapchainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; swapchainCreateInfo.presentMode = SDLToVK_PresentMode[windowData->presentMode]; swapchainCreateInfo.clipped = VK_TRUE; @@ -4780,7 +4790,7 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false); } - renderer->vkCreateSemaphore( + vulkanResult = renderer->vkCreateSemaphore( renderer->logicalDevice, &semaphoreCreateInfo, NULL, @@ -4934,6 +4944,7 @@ static void VULKAN_DestroyDevice( SDL_DestroyMutex(renderer->submitLock); SDL_DestroyMutex(renderer->acquireCommandBufferLock); SDL_DestroyMutex(renderer->acquireUniformBufferLock); + SDL_DestroyMutex(renderer->renderPassFetchLock); SDL_DestroyMutex(renderer->framebufferFetchLock); SDL_DestroyMutex(renderer->windowLock); @@ -7081,12 +7092,15 @@ static VkRenderPass VULKAN_INTERNAL_FetchRenderPass( key.depthStencilTargetDescription.stencilStoreOp = depthStencilTargetInfo->stencil_store_op; } + SDL_LockMutex(renderer->renderPassFetchLock); + bool result = SDL_FindInHashTable( renderer->renderPassHashTable, (const void *)&key, (const void **)&renderPassWrapper); if (result) { + SDL_UnlockMutex(renderer->renderPassFetchLock); return renderPassWrapper->handle; } @@ -7098,6 +7112,7 @@ static VkRenderPass VULKAN_INTERNAL_FetchRenderPass( depthStencilTargetInfo); if (renderPassHandle == VK_NULL_HANDLE) { + SDL_UnlockMutex(renderer->renderPassFetchLock); return VK_NULL_HANDLE; } @@ -7113,6 +7128,8 @@ static VkRenderPass VULKAN_INTERNAL_FetchRenderPass( (const void *)allocedKey, (const void *)renderPassWrapper, true); + SDL_UnlockMutex(renderer->renderPassFetchLock); + return renderPassHandle; } @@ -7181,9 +7198,8 @@ static VulkanFramebuffer *VULKAN_INTERNAL_FetchFramebuffer( (const void *)&key, (const void **)&vulkanFramebuffer); - SDL_UnlockMutex(renderer->framebufferFetchLock); - if (findResult) { + SDL_UnlockMutex(renderer->framebufferFetchLock); return vulkanFramebuffer; } @@ -7251,19 +7267,18 @@ static VulkanFramebuffer *VULKAN_INTERNAL_FetchFramebuffer( FramebufferHashTableKey *allocedKey = SDL_malloc(sizeof(FramebufferHashTableKey)); SDL_memcpy(allocedKey, &key, sizeof(FramebufferHashTableKey)); - SDL_LockMutex(renderer->framebufferFetchLock); - SDL_InsertIntoHashTable( renderer->framebufferHashTable, (const void *)allocedKey, (const void *)vulkanFramebuffer, true); - SDL_UnlockMutex(renderer->framebufferFetchLock); } else { SDL_free(vulkanFramebuffer); + SDL_UnlockMutex(renderer->framebufferFetchLock); CHECK_VULKAN_ERROR_AND_RETURN(result, vkCreateFramebuffer, NULL); } + SDL_UnlockMutex(renderer->framebufferFetchLock); return vulkanFramebuffer; } @@ -10429,7 +10444,6 @@ static bool VULKAN_Submit( VkPipelineStageFlags waitStages[MAX_PRESENT_COUNT]; Uint32 swapchainImageIndex; VulkanTextureSubresource *swapchainTextureSubresource; - Uint8 commandBufferCleaned = 0; VulkanMemorySubAllocator *allocator; bool presenting = false; @@ -10545,12 +10559,10 @@ static bool VULKAN_Submit( renderer, renderer->submittedCommandBuffers[i], false); - - commandBufferCleaned = 1; } } - if (commandBufferCleaned) { + if (renderer->checkEmptyAllocations) { SDL_LockMutex(renderer->allocatorLock); 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); } @@ -11657,6 +11671,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->submitLock = SDL_CreateMutex(); renderer->acquireCommandBufferLock = SDL_CreateMutex(); renderer->acquireUniformBufferLock = SDL_CreateMutex(); + renderer->renderPassFetchLock = SDL_CreateMutex(); renderer->framebufferFetchLock = SDL_CreateMutex(); renderer->windowLock = SDL_CreateMutex(); @@ -11718,7 +11733,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->renderPassHashTable = SDL_CreateHashTable( 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_RenderPassHashKeyMatch, VULKAN_INTERNAL_RenderPassHashDestroy, diff --git a/libs/SDL3/src/io/SDL_asyncio.c b/libs/SDL3/src/io/SDL_asyncio.c index aa5ba871..51adf6b0 100644 --- a/libs/SDL3/src/io/SDL_asyncio.c +++ b/libs/SDL3/src/io/SDL_asyncio.c @@ -309,12 +309,13 @@ bool SDL_LoadFileAsync(const char *file, SDL_AsyncIOQueue *queue, void *userdata if (asyncio) { asyncio->oneshot = true; - void *ptr = NULL; + Uint8 *ptr = NULL; const Sint64 flen = SDL_GetAsyncIOSize(asyncio); 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. - 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) { + ptr[flen] = '\0'; retval = SDL_ReadAsyncIO(asyncio, ptr, 0, (Uint64) flen, queue, userdata); if (!retval) { SDL_free(ptr); diff --git a/libs/SDL3/src/joystick/SDL_gamepad.c b/libs/SDL3/src/joystick/SDL_gamepad.c index 8486865b..9e8659d6 100644 --- a/libs/SDL3/src/joystick/SDL_gamepad.c +++ b/libs/SDL3/src/joystick/SDL_gamepad.c @@ -405,15 +405,17 @@ static bool SDLCALL SDL_GamepadEventWatcher(void *userdata, SDL_Event *event) { SDL_AssertJoysticksLocked(); - for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) { - if (gamepad->joystick->instance_id == event->jdevice.which) { - SDL_Event deviceevent; + 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; + deviceevent.type = SDL_EVENT_GAMEPAD_UPDATE_COMPLETE; + deviceevent.common.timestamp = event->jdevice.timestamp; + deviceevent.gdevice.which = event->jdevice.which; + SDL_PushEvent(&deviceevent); + 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)); break; 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; 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)); diff --git a/libs/SDL3/src/joystick/SDL_gamepad_db.h b/libs/SDL3/src/joystick/SDL_gamepad_db.h index d72e4223..64e2dd78 100644 --- a/libs/SDL3/src/joystick/SDL_gamepad_db.h +++ b/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,", "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,", + "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,", "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,", diff --git a/libs/SDL3/src/joystick/SDL_joystick.c b/libs/SDL3/src/joystick/SDL_joystick.c index 7574adc6..78c64b7f 100644 --- a/libs/SDL3/src/joystick/SDL_joystick.c +++ b/libs/SDL3/src/joystick/SDL_joystick.c @@ -427,6 +427,13 @@ static SDL_vidpid_list zero_centered_devices = { 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) { 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 * 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->led_expiration = SDL_GetTicks(); joystick->battery_percent = -1; + joystick->is_virtual = (driver == &SDL_VIRTUAL_JoystickDriver); if (!driver->Open(joystick, device_index)) { 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); if (info) { 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 @@ -1225,6 +1255,7 @@ bool SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value) SDL_LockJoysticks(); { CHECK_JOYSTICK_MAGIC(joystick, false); + CHECK_JOYSTICK_VIRTUAL(joystick, false); #ifdef SDL_JOYSTICK_VIRTUAL result = SDL_SetJoystickVirtualAxisInner(joystick, axis, value); @@ -1244,6 +1275,7 @@ bool SDL_SetJoystickVirtualBall(SDL_Joystick *joystick, int ball, Sint16 xrel, S SDL_LockJoysticks(); { CHECK_JOYSTICK_MAGIC(joystick, false); + CHECK_JOYSTICK_VIRTUAL(joystick, false); #ifdef SDL_JOYSTICK_VIRTUAL result = SDL_SetJoystickVirtualBallInner(joystick, ball, xrel, yrel); @@ -1263,6 +1295,7 @@ bool SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, int button, bool down) SDL_LockJoysticks(); { CHECK_JOYSTICK_MAGIC(joystick, false); + CHECK_JOYSTICK_VIRTUAL(joystick, false); #ifdef SDL_JOYSTICK_VIRTUAL result = SDL_SetJoystickVirtualButtonInner(joystick, button, down); @@ -1282,6 +1315,7 @@ bool SDL_SetJoystickVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value) SDL_LockJoysticks(); { CHECK_JOYSTICK_MAGIC(joystick, false); + CHECK_JOYSTICK_VIRTUAL(joystick, false); #ifdef SDL_JOYSTICK_VIRTUAL result = SDL_SetJoystickVirtualHatInner(joystick, hat, value); @@ -1301,6 +1335,7 @@ bool SDL_SetJoystickVirtualTouchpad(SDL_Joystick *joystick, int touchpad, int fi SDL_LockJoysticks(); { CHECK_JOYSTICK_MAGIC(joystick, false); + CHECK_JOYSTICK_VIRTUAL(joystick, false); #ifdef SDL_JOYSTICK_VIRTUAL 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(); { CHECK_JOYSTICK_MAGIC(joystick, false); + CHECK_JOYSTICK_VIRTUAL(joystick, false); #ifdef SDL_JOYSTICK_VIRTUAL 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; } + 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 if (button >= joystick->nbuttons) { return; @@ -2353,11 +2408,13 @@ static void SendSteamHandleUpdateEvents(void) if (info) { if (joystick->steam_handle != info->handle) { joystick->steam_handle = info->handle; + joystick->swap_face_buttons = ShouldSwapFaceButtons(info); changed = true; } } else { if (joystick->steam_handle != 0) { joystick->steam_handle = 0; + joystick->swap_face_buttons = false; changed = true; } } diff --git a/libs/SDL3/src/joystick/SDL_sysjoystick.h b/libs/SDL3/src/joystick/SDL_sysjoystick.h index 343d2061..f8f2d1af 100644 --- a/libs/SDL3/src/joystick/SDL_sysjoystick.h +++ b/libs/SDL3/src/joystick/SDL_sysjoystick.h @@ -83,6 +83,8 @@ struct SDL_Joystick SDL_GUID guid _guarded; // Joystick guid Uint16 firmware_version _guarded; // Firmware version, if available 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 SDL_JoystickAxisInfo *axes _guarded; diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c index bec9c907..6293519c 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/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; } 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->vendor_id = info->vendor_id; @@ -1135,12 +1133,13 @@ check_removed: goto check_removed; } else { HIDAPI_DelDevice(device); + device = NULL; // Update the device list again in case this device comes back SDL_HIDAPI_change_count = 0; } } - if (device->broken && device->parent) { + if (device && device->broken && device->parent) { HIDAPI_DelDevice(device->parent); // We deleted a different device here, restart the loop diff --git a/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c b/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c index d5166def..3e2b270f 100644 --- a/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c +++ b/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c @@ -160,6 +160,8 @@ struct joystick_hwdata WindowsGamingInputGamepadState *wgi_slot; #endif + bool triggers_rumbling; + SDL_RAWINPUT_Device *device; }; typedef struct joystick_hwdata RAWINPUT_DeviceContext; @@ -909,9 +911,11 @@ static void RAWINPUT_AddDevice(HANDLE hDevice) char *product_string = NULL; WCHAR string[128]; + string[0] = 0; if (SDL_HidD_GetManufacturerString(hFile, string, sizeof(string))) { manufacturer_string = WIN_StringToUTF8W(string); } + string[0] = 0; if (SDL_HidD_GetProductString(hFile, string, sizeof(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 // 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; if (!XINPUTSETSTATE) { @@ -1477,11 +1481,12 @@ static bool RAWINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency #endif // SDL_JOYSTICK_RAWINPUT_XINPUT #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) { - 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); if (SUCCEEDED(hr)) { rumbled = true; @@ -1513,6 +1518,7 @@ static bool RAWINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_ if (!SUCCEEDED(hr)) { return SDL_SetError("Setting vibration failed: 0x%lx", hr); } + ctx->triggers_rumbling = (left_rumble > 0 || right_rumble > 0); return true; } else { return SDL_SetError("Controller isn't correlated yet, try hitting a button first"); diff --git a/libs/SDL3/src/render/SDL_render.c b/libs/SDL3/src/render/SDL_render.c index a66552f3..60024b85 100644 --- a/libs/SDL3/src/render/SDL_render.c +++ b/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:"); while (cmd) { switch (cmd->command) { - case SDL_RENDERCMD_NO_OP: - SDL_Log(" %u. no-op", i++); - break; + 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_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_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_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_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_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_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_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_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_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_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=%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_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: - 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_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_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_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_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_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; } @@ -462,8 +460,8 @@ static void UpdatePixelClipRect(SDL_Renderer *renderer, SDL_RenderViewState *vie { const float scale_x = view->current_scale.x; 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.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.blend = blendMode; 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; } } @@ -827,6 +828,10 @@ static bool SDL_RendererEventWatch(void *userdata, SDL_Event *event) SDL_Renderer *renderer = (SDL_Renderer *)userdata; SDL_Window *window = renderer->window; + if (event->window.windowID != SDL_GetWindowID(window)) { + return true; + } + if (renderer->WindowEvent) { 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); - 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_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) { - SDL_Renderer *renderer; - CHECK_TEXTURE_MAGIC(texture, false); if (scaleMode != SDL_SCALEMODE_NEAREST && @@ -1971,12 +1969,10 @@ bool SDL_SetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode scaleMode) return SDL_InvalidParamError("scaleMode"); } - renderer = texture->renderer; texture->scaleMode = scaleMode; + if (texture->native) { return SDL_SetTextureScaleMode(texture->native, scaleMode); - } else { - renderer->SetTextureScaleMode(renderer, texture, scaleMode); } return true; } @@ -5493,7 +5489,8 @@ bool SDL_SetRenderVSync(SDL_Renderer *renderer, int vsync) } #endif - if (!renderer->SetVSync) { + if (!renderer->SetVSync || + !renderer->SetVSync(renderer, vsync)) { switch (vsync) { case 0: renderer->simulate_vsync = false; @@ -5504,12 +5501,6 @@ bool SDL_SetRenderVSync(SDL_Renderer *renderer, int vsync) default: 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); return true; diff --git a/libs/SDL3/src/render/SDL_sysrender.h b/libs/SDL3/src/render/SDL_sysrender.h index 9d39dfd3..bbafefdc 100644 --- a/libs/SDL3/src/render/SDL_sysrender.h +++ b/libs/SDL3/src/render/SDL_sysrender.h @@ -34,6 +34,7 @@ extern "C" { typedef enum SDL_TextureAddressMode { + SDL_TEXTURE_ADDRESS_INVALID = -1, SDL_TEXTURE_ADDRESS_AUTO, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_WRAP, @@ -155,6 +156,7 @@ typedef struct SDL_RenderCommand SDL_FColor color; SDL_BlendMode blend; SDL_Texture *texture; + SDL_ScaleMode texture_scale_mode; SDL_TextureAddressMode texture_address_mode; } draw; struct @@ -224,7 +226,6 @@ struct SDL_Renderer bool (*LockTexture)(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, void **pixels, int *pitch); 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); SDL_Surface *(*RenderReadPixels)(SDL_Renderer *renderer, const SDL_Rect *rect); bool (*RenderPresent)(SDL_Renderer *renderer); diff --git a/libs/SDL3/src/render/direct3d/SDL_render_d3d.c b/libs/SDL3/src/render/direct3d/SDL_render_d3d.c index ccb5b3c3..1d395f13 100644 --- a/libs/SDL3/src/render/direct3d/SDL_render_d3d.c +++ b/libs/SDL3/src/render/direct3d/SDL_render_d3d.c @@ -60,7 +60,7 @@ typedef struct bool updateSize; bool beginScene; bool enableSeparateAlphaBlend; - D3DTEXTUREFILTERTYPE scaleMode[3]; + SDL_ScaleMode scaleMode[3]; SDL_TextureAddressMode addressMode[3]; IDirect3DSurface9 *defaultRenderTarget; IDirect3DSurface9 *currentRenderTarget; @@ -89,7 +89,6 @@ typedef struct typedef struct { D3D_TextureRep texture; - D3DTEXTUREFILTERTYPE scaleMode; D3D9_Shader shader; const float *shader_params; @@ -274,10 +273,14 @@ static void D3D_InitRenderState(D3D_RenderData *data) IDirect3DDevice9_SetTransform(device, D3DTS_VIEW, &matrix); // 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 - 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 data->beginScene = true; @@ -533,7 +536,6 @@ static bool D3D_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ if (!texturedata) { return false; } - texturedata->scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? D3DTEXF_POINT : D3DTEXF_LINEAR; 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) { D3D_RenderData *data = (D3D_RenderData *)renderer->internal; @@ -926,12 +917,22 @@ static bool BindTextureRep(IDirect3DDevice9 *device, D3D_TextureRep *texture, DW 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; @@ -962,9 +963,6 @@ static bool SetupTextureState(D3D_RenderData *data, SDL_Texture *texture, SDL_Te return SDL_SetError("Texture is not currently available"); } - UpdateTextureScaleMode(data, texturedata, 0); - UpdateTextureAddressMode(data, addressMode, 0); - *shader = texturedata->shader; *shader_params = texturedata->shader_params; @@ -973,11 +971,6 @@ static bool SetupTextureState(D3D_RenderData *data, SDL_Texture *texture, SDL_Te } #ifdef SDL_HAVE_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)) { return false; } @@ -1012,7 +1005,7 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd) IDirect3DDevice9_SetTexture(data->device, 2, NULL); } #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; } @@ -1040,13 +1033,30 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd) data->drawstate.texture = texture; } else if (texture) { D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal; - UpdateDirtyTexture(data->device, &texturedata->texture); + if (texturedata) { + UpdateDirtyTexture(data->device, &texturedata->texture); #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) { @@ -1653,7 +1663,6 @@ static bool D3D_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P #endif renderer->LockTexture = D3D_LockTexture; renderer->UnlockTexture = D3D_UnlockTexture; - renderer->SetTextureScaleMode = D3D_SetTextureScaleMode; renderer->SetRenderTarget = D3D_SetRenderTarget; renderer->QueueSetViewport = D3D_QueueNoOp; renderer->QueueSetDrawColor = D3D_QueueNoOp; diff --git a/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c b/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c index dc1a7229..60f646fd 100644 --- a/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c +++ b/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c @@ -122,7 +122,6 @@ typedef struct ID3D11Texture2D *stagingTexture; int lockedTexturePositionX; int lockedTexturePositionY; - D3D11_FILTER scaleMode; D3D11_Shader shader; const float *YCbCr_matrix; #ifdef SDL_HAVE_YUV @@ -1172,7 +1171,6 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD if (!textureData) { return false; } - textureData->scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? D3D11_FILTER_MIN_MAG_MIP_POINT : D3D11_FILTER_MIN_MAG_MIP_LINEAR; texture->internal = textureData; @@ -1796,17 +1794,6 @@ static void D3D11_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture) 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) { 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); - 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) { case SDL_TEXTURE_ADDRESS_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); } break; - case D3D11_FILTER_MIN_MAG_MIP_LINEAR: + case SDL_SCALEMODE_LINEAR: switch (cmd->data.draw.texture_address_mode) { case SDL_TEXTURE_ADDRESS_CLAMP: textureSampler = rendererData->samplers[D3D11_SAMPLER_LINEAR_CLAMP]; @@ -2344,7 +2331,7 @@ static bool D3D11_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand * } break; 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 if (textureData->yuv) { @@ -2712,7 +2699,6 @@ static bool D3D11_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL #endif renderer->LockTexture = D3D11_LockTexture; renderer->UnlockTexture = D3D11_UnlockTexture; - renderer->SetTextureScaleMode = D3D11_SetTextureScaleMode; renderer->SetRenderTarget = D3D11_SetRenderTarget; renderer->QueueSetViewport = D3D11_QueueNoOp; renderer->QueueSetDrawColor = D3D11_QueueNoOp; diff --git a/libs/SDL3/src/render/direct3d12/SDL_render_d3d12.c b/libs/SDL3/src/render/direct3d12/SDL_render_d3d12.c index 06535f0e..632e0f67 100644 --- a/libs/SDL3/src/render/direct3d12/SDL_render_d3d12.c +++ b/libs/SDL3/src/render/direct3d12/SDL_render_d3d12.c @@ -124,7 +124,6 @@ typedef struct DXGI_FORMAT mainTextureFormat; ID3D12Resource *stagingBuffer; D3D12_RESOURCE_STATES stagingResourceState; - D3D12_FILTER scaleMode; D3D12_Shader shader; const float *YCbCr_matrix; #ifdef SDL_HAVE_YUV @@ -1574,7 +1573,6 @@ static bool D3D12_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD if (!textureData) { return false; } - textureData->scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? D3D12_FILTER_MIN_MAG_MIP_POINT : D3D12_FILTER_MIN_MAG_MIP_LINEAR; texture->internal = textureData; textureData->mainTextureFormat = textureFormat; @@ -1992,6 +1990,10 @@ static bool D3D12_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, } } #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; } @@ -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)) { 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; } @@ -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)) { return false; } - if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, 1, rect->x, rect->y, rect->w, rect->h, UVplane, UVpitch, &textureData->mainResourceState)) { 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; } #endif @@ -2244,17 +2253,6 @@ static void D3D12_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture) 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) { 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); - 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) { case SDL_TEXTURE_ADDRESS_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); } break; - case D3D12_FILTER_MIN_MAG_MIP_LINEAR: + case SDL_SCALEMODE_LINEAR: switch (cmd->data.draw.texture_address_mode) { case SDL_TEXTURE_ADDRESS_CLAMP: textureSampler = &rendererData->samplers[D3D12_SAMPLER_LINEAR_CLAMP]; @@ -2771,7 +2769,7 @@ static bool D3D12_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand * } break; 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 if (textureData->yuv) { @@ -3248,7 +3246,6 @@ bool D3D12_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_Proper #endif renderer->LockTexture = D3D12_LockTexture; renderer->UnlockTexture = D3D12_UnlockTexture; - renderer->SetTextureScaleMode = D3D12_SetTextureScaleMode; renderer->SetRenderTarget = D3D12_SetRenderTarget; renderer->QueueSetViewport = D3D12_QueueNoOp; renderer->QueueSetDrawColor = D3D12_QueueNoOp; diff --git a/libs/SDL3/src/render/gpu/SDL_render_gpu.c b/libs/SDL3/src/render/gpu/SDL_render_gpu.c index 73c1dbe7..52694291 100644 --- a/libs/SDL3/src/render/gpu/SDL_render_gpu.c +++ b/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); } -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) { 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)); } -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]; } @@ -575,7 +569,7 @@ static void Draw( if (tdata) { SDL_GPUTextureSamplerBinding 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; 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. */ SDL_Texture *thistexture = cmd->data.draw.texture; 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; SDL_RenderCommand *finalcmd = cmd; 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; if (nextcmdtype != thiscmdtype) { 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? break; // can't go any further on this draw call, different texture/blendmode copy up next. } else { @@ -1176,7 +1175,6 @@ static bool GPU_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P renderer->UpdateTexture = GPU_UpdateTexture; renderer->LockTexture = GPU_LockTexture; renderer->UnlockTexture = GPU_UnlockTexture; - renderer->SetTextureScaleMode = GPU_SetTextureScaleMode; renderer->SetRenderTarget = GPU_SetRenderTarget; renderer->QueueSetViewport = GPU_QueueNoOp; renderer->QueueSetDrawColor = GPU_QueueNoOp; diff --git a/libs/SDL3/src/render/metal/SDL_render_metal.m b/libs/SDL3/src/render/metal/SDL_render_metal.m index 63d9770d..3bba129a 100644 --- a/libs/SDL3/src/render/metal/SDL_render_metal.m +++ b/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) { @autoreleasepool { @@ -1473,7 +1469,7 @@ static bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, c if (texture != statecache->texture) { id mtlsampler; - if (texture->scaleMode == SDL_SCALEMODE_NEAREST) { + if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_NEAREST) { switch (cmd->data.draw.texture_address_mode) { case SDL_TEXTURE_ADDRESS_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 renderer->LockTexture = METAL_LockTexture; renderer->UnlockTexture = METAL_UnlockTexture; - renderer->SetTextureScaleMode = METAL_SetTextureScaleMode; renderer->SetRenderTarget = METAL_SetRenderTarget; renderer->QueueSetViewport = METAL_QueueSetViewport; renderer->QueueSetDrawColor = METAL_QueueNoOp; diff --git a/libs/SDL3/src/render/opengl/SDL_render_gl.c b/libs/SDL3/src/render/opengl/SDL_render_gl.c index d5344382..fecd365d 100644 --- a/libs/SDL3/src/render/opengl/SDL_render_gl.c +++ b/libs/SDL3/src/render/opengl/SDL_render_gl.c @@ -137,7 +137,6 @@ typedef struct void *pixels; int pitch; SDL_Rect locked_rect; - #ifdef SDL_HAVE_YUV // YUV texture support bool yuv; @@ -147,7 +146,8 @@ typedef struct GLuint vtexture; bool vtexture_external; #endif - + SDL_ScaleMode texture_scale_mode; + SDL_TextureAddressMode texture_address_mode; GL_FBOList *fbo; } GL_TextureData; @@ -447,7 +447,6 @@ static bool GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_P GLint internalFormat; GLenum format, type; int texture_w, texture_h; - GLenum scaleMode; GL_ActivateRenderer(renderer); @@ -536,11 +535,10 @@ static bool GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_P data->format = format; 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->glBindTexture(textype, data->texture); - renderdata->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, scaleMode); - renderdata->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, scaleMode); #ifdef SDL_PLATFORM_MACOS #ifndef GL_TEXTURE_STORAGE_HINT_APPLE #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->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, - scaleMode); - renderdata->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, - scaleMode); renderdata->glTexImage2D(textype, 0, internalFormat, (texture_w + 1) / 2, (texture_h + 1) / 2, 0, format, type, NULL); SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_OPENGL_TEXTURE_U_NUMBER, data->utexture); 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, (texture_h + 1) / 2, 0, format, type, NULL); 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->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, (texture_h + 1) / 2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, NULL); 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); } -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) { GL_RenderData *data = (GL_RenderData *)renderer->internal; @@ -1120,6 +1074,23 @@ static bool SetDrawState(GL_RenderData *data, const SDL_RenderCommand *cmd, cons 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) { switch (addressMode) { @@ -1140,41 +1111,23 @@ static bool SetTextureAddressMode(GL_RenderData *data, GLenum textype, SDL_Textu static bool SetCopyState(GL_RenderData *data, const SDL_RenderCommand *cmd) { 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); if (texture != data->drawstate.texture) { - const GLenum textype = data->textype; #ifdef SDL_HAVE_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); - if (!SetTextureAddressMode(data, textype, cmd->data.draw.texture_address_mode)) { - return false; - } - - if (data->GL_ARB_multitexture_supported) { - data->glActiveTextureARB(GL_TEXTURE1_ARB); - } + data->glActiveTextureARB(GL_TEXTURE1_ARB); data->glBindTexture(textype, texturedata->utexture); - - if (!SetTextureAddressMode(data, textype, cmd->data.draw.texture_address_mode)) { - return false; - } } if (texturedata->nv12) { - if (data->GL_ARB_multitexture_supported) { - data->glActiveTextureARB(GL_TEXTURE1_ARB); - } + data->glActiveTextureARB(GL_TEXTURE1_ARB); data->glBindTexture(textype, texturedata->utexture); - - if (!SetTextureAddressMode(data, textype, cmd->data.draw.texture_address_mode)) { - return false; - } } #endif if (data->GL_ARB_multitexture_supported) { @@ -1182,11 +1135,67 @@ static bool SetCopyState(GL_RenderData *data, const SDL_RenderCommand *cmd) } 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; + } + + data->glActiveTextureARB(GL_TEXTURE1); + if (!SetTextureScaleMode(data, textype, cmd->data.draw.texture_scale_mode)) { + return false; + } + + data->glActiveTextureARB(GL_TEXTURE0); + } else if (texturedata->nv12) { + data->glActiveTextureARB(GL_TEXTURE1); + if (!SetTextureScaleMode(data, textype, cmd->data.draw.texture_scale_mode)) { + return false; + } + + data->glActiveTextureARB(GL_TEXTURE0); + } +#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->glActiveTextureARB(GL_TEXTURE1); + if (!SetTextureAddressMode(data, textype, cmd->data.draw.texture_address_mode)) { + return false; + } + + data->glActiveTextureARB(GL_TEXTURE0_ARB); + } 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)) { return false; } - data->drawstate.texture = texture; + texturedata->texture_address_mode = cmd->data.draw.texture_address_mode; } 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. */ SDL_Texture *thistexture = cmd->data.draw.texture; 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; SDL_RenderCommand *finalcmd = cmd; 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; if (nextcmdtype != thiscmdtype) { 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. } else { 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 renderer->LockTexture = GL_LockTexture; renderer->UnlockTexture = GL_UnlockTexture; - renderer->SetTextureScaleMode = GL_SetTextureScaleMode; renderer->SetRenderTarget = GL_SetRenderTarget; renderer->QueueSetViewport = GL_QueueNoOp; renderer->QueueSetDrawColor = GL_QueueNoOp; diff --git a/libs/SDL3/src/render/opengles2/SDL_render_gles2.c b/libs/SDL3/src/render/opengles2/SDL_render_gles2.c index 0e142a78..d15f4f02 100644 --- a/libs/SDL3/src/render/opengles2/SDL_render_gles2.c +++ b/libs/SDL3/src/render/opengles2/SDL_render_gles2.c @@ -76,6 +76,8 @@ typedef struct GLES2_TextureData GLuint texture_u; GLuint texture_u_external; #endif + SDL_ScaleMode texture_scale_mode; + SDL_TextureAddressMode texture_address_mode; GLES2_FBOList *fbo; } GLES2_TextureData; @@ -168,6 +170,7 @@ typedef struct GLES2_RenderData bool debug_enabled; + bool GL_OES_EGL_image_external_supported; bool GL_EXT_blend_minmax_supported; #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) { - 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)) { @@ -1021,14 +1024,31 @@ static bool SetDrawState(GLES2_RenderData *data, const SDL_RenderCommand *cmd, c // 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; } +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) { 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_ImageSource sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR; SDL_Texture *texture = cmd->data.draw.texture; + GLES2_TextureData *tdata = (GLES2_TextureData *)texture->internal; int ret; // 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); if (texture != data->drawstate.texture) { - GLES2_TextureData *tdata = (GLES2_TextureData *)texture->internal; #ifdef SDL_HAVE_YUV if (tdata->yuv) { data->glActiveTexture(GL_TEXTURE2); data->glBindTexture(tdata->texture_type, tdata->texture_v); + 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; + } + + data->glActiveTexture(GL_TEXTURE1); + 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); - data->glBindTexture(tdata->texture_type, tdata->texture_u); - if (!SetTextureAddressMode(data, tdata->texture_type, cmd->data.draw.texture_address_mode)) { return false; } @@ -1192,8 +1260,6 @@ static bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, v data->glActiveTexture(GL_TEXTURE0); } else if (tdata->nv12) { data->glActiveTexture(GL_TEXTURE1); - data->glBindTexture(tdata->texture_type, tdata->texture_u); - if (!SetTextureAddressMode(data, tdata->texture_type, cmd->data.draw.texture_address_mode)) { return false; } @@ -1201,13 +1267,11 @@ static bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, v data->glActiveTexture(GL_TEXTURE0); } #endif - data->glBindTexture(tdata->texture_type, tdata->texture); - if (!SetTextureAddressMode(data, tdata->texture_type, cmd->data.draw.texture_address_mode)) { return false; } - data->drawstate.texture = texture; + tdata->texture_address_mode = cmd->data.draw.texture_address_mode; } 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)) { 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 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. */ SDL_Texture *thistexture = cmd->data.draw.texture; 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; SDL_RenderCommand *finalcmd = cmd; 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; if (nextcmdtype != thiscmdtype) { 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. } else { 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; GLenum format; GLenum type; - GLenum scaleMode; GLES2_ActivateRenderer(renderer); @@ -1512,9 +1581,12 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD #endif #ifdef GL_TEXTURE_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 default: 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_v = 0; #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 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->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); 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->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); if (!GL_CheckError("glTexImage2D()", renderer)) { return false; @@ -1622,8 +1691,6 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD } renderdata->glActiveTexture(GL_TEXTURE1); 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); if (!GL_CheckError("glTexImage2D()", renderer)) { return false; @@ -1648,8 +1715,6 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD texture->internal = data; renderdata->glActiveTexture(GL_TEXTURE0); 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) { renderdata->glTexImage2D(data->texture_type, 0, format, texture->w, texture->h, 0, format, type, NULL); 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); } -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) { GLES2_RenderData *data = (GLES2_RenderData *)renderer->internal; @@ -2151,7 +2185,6 @@ static bool GLES2_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL #endif renderer->LockTexture = GLES2_LockTexture; renderer->UnlockTexture = GLES2_UnlockTexture; - renderer->SetTextureScaleMode = GLES2_SetTextureScaleMode; renderer->SetRenderTarget = GLES2_SetRenderTarget; renderer->QueueSetViewport = 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); #endif #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); } #endif diff --git a/libs/SDL3/src/render/ps2/SDL_render_ps2.c b/libs/SDL3/src/render/ps2/SDL_render_ps2.c index ddcab9e2..713192c9 100644 --- a/libs/SDL3/src/render/ps2/SDL_render_ps2.c +++ b/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; } -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) { 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); 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_prim_list_triangle_goraud_texture_uv_3d(data->gsGlobal, ps2_tex, count, verts); } else { @@ -695,7 +690,6 @@ static bool PS2_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P renderer->UpdateTexture = PS2_UpdateTexture; renderer->LockTexture = PS2_LockTexture; renderer->UnlockTexture = PS2_UnlockTexture; - renderer->SetTextureScaleMode = PS2_SetTextureScaleMode; renderer->SetRenderTarget = PS2_SetRenderTarget; renderer->QueueSetViewport = PS2_QueueSetViewport; renderer->QueueSetDrawColor = PS2_QueueNoOp; diff --git a/libs/SDL3/src/render/psp/SDL_render_psp.c b/libs/SDL3/src/render/psp/SDL_render_psp.c index 3ce5036f..14f43ba8 100644 --- a/libs/SDL3/src/render/psp/SDL_render_psp.c +++ b/libs/SDL3/src/render/psp/SDL_render_psp.c @@ -75,6 +75,8 @@ typedef struct unsigned int color; int shadeModel; SDL_Texture *texture; + SDL_ScaleMode texture_scale_mode; + SDL_TextureAddressMode texture_address_mode; } PSP_BlendState; 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); } +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) { 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. if (TextureShouldSwizzle(psp_texture, texture)) { TextureSwizzle(psp_texture, NULL); } - sceGuTexWrap(GU_REPEAT, GU_REPEAT); 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); } @@ -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); } -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) { 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; } @@ -1117,6 +1143,8 @@ static bool PSP_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, PSP_BlendState state = { .color = drawstate.color, .texture = NULL, + .texture_scale_mode = SDL_SCALEMODE_INVALID, + .texture_address_mode = SDL_TEXTURE_ADDRESS_INVALID, .mode = cmd->data.draw.blend, .shadeModel = GU_FLAT }; @@ -1132,6 +1160,8 @@ static bool PSP_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, PSP_BlendState state = { .color = drawstate.color, .texture = NULL, + .texture_scale_mode = SDL_SCALEMODE_INVALID, + .texture_address_mode = SDL_TEXTURE_ADDRESS_INVALID, .mode = cmd->data.draw.blend, .shadeModel = GU_FLAT }; @@ -1147,6 +1177,8 @@ static bool PSP_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, PSP_BlendState state = { .color = drawstate.color, .texture = NULL, + .texture_scale_mode = SDL_SCALEMODE_INVALID, + .texture_address_mode = SDL_TEXTURE_ADDRESS_INVALID, .mode = cmd->data.draw.blend, .shadeModel = GU_FLAT }; @@ -1162,6 +1194,8 @@ static bool PSP_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, PSP_BlendState state = { .color = drawstate.color, .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, .shadeModel = GU_SMOOTH }; @@ -1176,6 +1210,8 @@ static bool PSP_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, PSP_BlendState state = { .color = drawstate.color, .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, .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); PSP_BlendState state = { .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, - .shadeModel = GU_FLAT + .shadeModel = GU_SMOOTH }; - TextureActivate(cmd->data.draw.texture); PSP_SetBlendState(data, &state); 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->LockTexture = PSP_LockTexture; renderer->UnlockTexture = PSP_UnlockTexture; - renderer->SetTextureScaleMode = PSP_SetTextureScaleMode; renderer->SetRenderTarget = PSP_SetRenderTarget; renderer->QueueSetViewport = PSP_QueueNoOp; renderer->QueueSetDrawColor = PSP_QueueNoOp; diff --git a/libs/SDL3/src/render/software/SDL_render_sw.c b/libs/SDL3/src/render/software/SDL_render_sw.c index 2231724a..37359be5 100644 --- a/libs/SDL3/src/render/software/SDL_render_sw.c +++ b/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) { 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, 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_Rect tmp_rect; @@ -412,7 +408,7 @@ static bool SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Te result = false; } else { 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); src_clone = src_scaled; 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, &rect_dest, &cangle, &sangle); 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); if (!src_rotated) { 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); } // 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 { /* 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. @@ -470,7 +466,7 @@ static bool SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Te SDL_Rect mask_rect = tmp_rect; SDL_SetSurfaceBlendMode(mask_rotated, SDL_BLENDMODE_NONE); // 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) { /* 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 @@ -479,7 +475,7 @@ static bool SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Te SDL_SetSurfaceColorMod(src_rotated, 0, 0, 0); mask_rect = tmp_rect; // 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) { /* 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 @@ -492,7 +488,7 @@ static bool SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Te } else { SDL_SetSurfaceBlendMode(src_rotated_rgb, SDL_BLENDMODE_ADD); // 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); } } @@ -858,7 +854,7 @@ static bool SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, v SDL_SetSurfaceColorMod(src, 255, 255, 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_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() } } else { - SDL_BlitSurfaceScaled(src, srcrect, surface, dstrect, texture->scaleMode); + SDL_BlitSurfaceScaled(src, srcrect, surface, dstrect, cmd->data.draw.texture_scale_mode); } } break; @@ -889,7 +885,7 @@ static bool SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, v SW_RenderCopyEx(renderer, surface, cmd->data.draw.texture, ©data->srcrect, ©data->dstrect, copydata->angle, ©data->center, copydata->flip, - copydata->scale_x, copydata->scale_y); + copydata->scale_x, copydata->scale_y, cmd->data.draw.texture_scale_mode); break; } @@ -1135,7 +1131,6 @@ bool SW_CreateRendererForSurface(SDL_Renderer *renderer, SDL_Surface *surface, S renderer->UpdateTexture = SW_UpdateTexture; renderer->LockTexture = SW_LockTexture; renderer->UnlockTexture = SW_UnlockTexture; - renderer->SetTextureScaleMode = SW_SetTextureScaleMode; renderer->SetRenderTarget = SW_SetRenderTarget; renderer->QueueSetViewport = SW_QueueNoOp; renderer->QueueSetDrawColor = SW_QueueNoOp; diff --git a/libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm.c b/libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm.c index acf31c3d..c992135f 100644 --- a/libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm.c +++ b/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, 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, SDL_Texture *texture); @@ -216,7 +214,6 @@ static bool VITA_GXM_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, #endif renderer->LockTexture = VITA_GXM_LockTexture; renderer->UnlockTexture = VITA_GXM_UnlockTexture; - renderer->SetTextureScaleMode = VITA_GXM_SetTextureScaleMode; renderer->SetRenderTarget = VITA_GXM_SetRenderTarget; renderer->QueueSetViewport = VITA_GXM_QueueNoOp; renderer->QueueSetDrawColor = VITA_GXM_QueueSetDrawColor; @@ -295,9 +292,10 @@ static bool VITA_GXM_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, 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 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. } -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) { 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) { - 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); } data->drawstate.texture = texture; diff --git a/libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_tools.c b/libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_tools.c index 9140a62c..48af7b8f 100644 --- a/libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_tools.c +++ b/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; } +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) { sceGxmTextureSetMinFilter(&texture->gxm_tex, min_filter); diff --git a/libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_tools.h b/libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_tools.h index 023f8598..36c6f400 100644 --- a/libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_tools.h +++ b/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); 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); SceGxmTextureFormat gxm_texture_get_format(const gxm_texture *texture); diff --git a/libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_types.h b/libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_types.h index 19962c73..e23137f6 100644 --- a/libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_types.h +++ b/libs/SDL3/src/render/vitagxm/SDL_render_vita_gxm_types.h @@ -205,6 +205,8 @@ typedef struct float wscale; bool yuv; bool nv12; + SDL_ScaleMode scale_mode; + SDL_TextureAddressMode address_mode; } VITA_GXM_TextureData; #endif // SDL_RENDER_VITA_GXM_TYPES_H diff --git a/libs/SDL3/src/render/vulkan/SDL_render_vulkan.c b/libs/SDL3/src/render/vulkan/SDL_render_vulkan.c index d2157c30..65af0a52 100644 --- a/libs/SDL3/src/render/vulkan/SDL_render_vulkan.c +++ b/libs/SDL3/src/render/vulkan/SDL_render_vulkan.c @@ -255,7 +255,6 @@ typedef struct VkRenderPass mainRenderpasses[VULKAN_RENDERPASS_COUNT]; VkFramebuffer mainFramebuffer; VULKAN_Buffer stagingBuffer; - VkFilter scaleMode; SDL_Rect lockedRect; int width; int height; @@ -2600,7 +2599,6 @@ static bool VULKAN_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, S } else { textureData->shader = SHADER_ADVANCED; } - textureData->scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? VK_FILTER_NEAREST : VK_FILTER_LINEAR; #ifdef SDL_HAVE_YUV // 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); } -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) { 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); - switch (textureData->scaleMode) { + switch (cmd->data.draw.texture_scale_mode) { case VK_FILTER_NEAREST: switch (cmd->data.draw.texture_address_mode) { case SDL_TEXTURE_ADDRESS_CLAMP: @@ -3801,7 +3788,7 @@ static bool VULKAN_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand } break; 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) { @@ -4290,7 +4277,6 @@ static bool VULKAN_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SD #endif renderer->LockTexture = VULKAN_LockTexture; renderer->UnlockTexture = VULKAN_UnlockTexture; - renderer->SetTextureScaleMode = VULKAN_SetTextureScaleMode; renderer->SetRenderTarget = VULKAN_SetRenderTarget; renderer->QueueSetViewport = VULKAN_QueueNoOp; renderer->QueueSetDrawColor = VULKAN_QueueNoOp; diff --git a/libs/SDL3/src/stdlib/SDL_stdlib.c b/libs/SDL3/src/stdlib/SDL_stdlib.c index 98faab95..093b2bec 100644 --- a/libs/SDL3/src/stdlib/SDL_stdlib.c +++ b/libs/SDL3/src/stdlib/SDL_stdlib.c @@ -533,6 +533,7 @@ void *SDL_aligned_alloc(size_t alignment, size_t size) { size_t padding; Uint8 *result = NULL; + size_t requested_size = size; if (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 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; diff --git a/libs/SDL3/src/stdlib/SDL_string.c b/libs/SDL3/src/stdlib/SDL_string.c index 007719ef..79679a1f 100644 --- a/libs/SDL3/src/stdlib/SDL_string.c +++ b/libs/SDL3/src/stdlib/SDL_string.c @@ -368,14 +368,12 @@ static size_t SDL_ScanUnsignedLongLongInternal(const char *text, int count, int negative = *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; - if (*text == 'x' || *text == 'X') { - radix = 16; - ++text; - } else if (radix == 0) { - radix = 8; - } + radix = 8; } else if (radix == 0) { radix = 10; } @@ -462,14 +460,12 @@ static size_t SDL_ScanUnsignedLongLongInternalW(const wchar_t *text, int count, negative = *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; - if (*text == 'x' || *text == 'X') { - radix = 16; - ++text; - } else if (radix == 0) { - radix = 8; - } + radix = 8; } else if (radix == 0) { radix = 10; } diff --git a/libs/SDL3/src/test/SDL_test_common.c b/libs/SDL3/src/test/SDL_test_common.c index 3203ddea..315fec06 100644 --- a/libs/SDL3/src/test/SDL_test_common.c +++ b/libs/SDL3/src/test/SDL_test_common.c @@ -55,7 +55,6 @@ static const char *video_usage[] = { "[--input-focus]", "[--keyboard-grab]", "[--logical-presentation disabled|match|stretch|letterbox|overscan|integer_scale]", - "[--logical-scale-quality nearest|linear|best]", "[--logical WxH]", "[--max-geometry WxH]", "[--maximize]", diff --git a/libs/SDL3/src/test/SDL_test_memory.c b/libs/SDL3/src/test/SDL_test_memory.c index b90c4a5d..e1bec17f 100644 --- a/libs/SDL3/src/test/SDL_test_memory.c +++ b/libs/SDL3/src/test/SDL_test_memory.c @@ -454,4 +454,5 @@ void SDLTest_LogAllocations(void) #undef ADD_LINE SDL_Log("%s", message); + SDL_free_orig(message); } diff --git a/libs/SDL3/src/thread/n3ds/SDL_syssem.c b/libs/SDL3/src/thread/n3ds/SDL_syssem.c index 3207a2df..c03030b2 100644 --- a/libs/SDL3/src/thread/n3ds/SDL_syssem.c +++ b/libs/SDL3/src/thread/n3ds/SDL_syssem.c @@ -102,7 +102,7 @@ Uint32 SDL_GetSemaphoreValue(SDL_Semaphore *sem) void SDL_SignalSemaphore(SDL_Semaphore *sem) { - if (sem) { + if (!sem) { return; } diff --git a/libs/SDL3/src/time/unix/SDL_systime.c b/libs/SDL3/src/time/unix/SDL_systime.c index 296175a5..bb3aa4b8 100644 --- a/libs/SDL3/src/time/unix/SDL_systime.c +++ b/libs/SDL3/src/time/unix/SDL_systime.c @@ -27,6 +27,7 @@ #include #include #include +#include #if !defined(HAVE_CLOCK_GETTIME) && defined(SDL_PLATFORM_APPLE) #include @@ -186,7 +187,23 @@ bool SDL_TimeToDateTime(SDL_Time ticks, SDL_DateTime *dt, bool localTime) dt->second = tm->tm_sec; dt->nanosecond = ticks % SDL_NS_PER_SECOND; 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; +#else + if (localTime) { + tzset(); + dt->utc_offset = (int)timezone; + } else { + dt->utc_offset = 0; + } +#endif return true; } diff --git a/libs/SDL3/src/video/SDL_blit_A.c b/libs/SDL3/src/video/SDL_blit_A.c index 5d6d92da..1cefd456 100644 --- a/libs/SDL3/src/video/SDL_blit_A.c +++ b/libs/SDL3/src/video/SDL_blit_A.c @@ -22,6 +22,7 @@ #ifdef SDL_HAVE_BLIT_A +#include "SDL_pixels_c.h" #include "SDL_surface_c.h" // Functions to perform alpha blended blitting @@ -968,6 +969,10 @@ static void Blit8888to8888PixelAlphaSwizzle(SDL_BlitInfo *info) int dstskip = info->dst_skip; const SDL_PixelFormatDetails *srcfmt = info->src_fmt; const SDL_PixelFormatDetails *dstfmt = info->dst_fmt; + bool fill_alpha = !dstfmt->Amask; + Uint32 dstAmask, dstAshift; + + SDL_Get8888AlphaMaskAndShift(dstfmt, &dstAmask, &dstAshift); while (height--) { int i = 0; @@ -976,6 +981,9 @@ static void Blit8888to8888PixelAlphaSwizzle(SDL_BlitInfo *info) Uint32 src32 = *(Uint32 *)src; Uint32 dst32 = *(Uint32 *)dst; ALPHA_BLEND_SWIZZLE_8888(src32, dst32, srcfmt, dstfmt); + if (fill_alpha) { + dst32 |= dstAmask; + } *(Uint32 *)dst = dst32; src += 4; dst += 4; @@ -998,6 +1006,10 @@ static void SDL_TARGETING("sse4.1") Blit8888to8888PixelAlphaSwizzleSSE41(SDL_Bli int dstskip = info->dst_skip; const SDL_PixelFormatDetails *srcfmt = info->src_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 const __m128i mask_offsets = _mm_set_epi8( @@ -1011,7 +1023,7 @@ static void SDL_TARGETING("sse4.1") Blit8888to8888PixelAlphaSwizzleSSE41(SDL_Bli 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--) { 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)); // 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; dst += 16; @@ -1067,6 +1083,9 @@ static void SDL_TARGETING("sse4.1") Blit8888to8888PixelAlphaSwizzleSSE41(SDL_Bli Uint32 src32 = *(Uint32 *)src; Uint32 dst32 = *(Uint32 *)dst; ALPHA_BLEND_SWIZZLE_8888(src32, dst32, srcfmt, dstfmt); + if (fill_alpha) { + dst32 |= dstAmask; + } *(Uint32 *)dst = dst32; src += 4; dst += 4; @@ -1091,6 +1110,10 @@ static void SDL_TARGETING("avx2") Blit8888to8888PixelAlphaSwizzleAVX2(SDL_BlitIn int dstskip = info->dst_skip; const SDL_PixelFormatDetails *srcfmt = info->src_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 const __m256i mask_offsets = _mm256_set_epi8( @@ -1104,7 +1127,7 @@ static void SDL_TARGETING("avx2") Blit8888to8888PixelAlphaSwizzleAVX2(SDL_BlitIn 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--) { 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)); // 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; dst += 32; @@ -1160,6 +1187,9 @@ static void SDL_TARGETING("avx2") Blit8888to8888PixelAlphaSwizzleAVX2(SDL_BlitIn Uint32 src32 = *(Uint32 *)src; Uint32 dst32 = *(Uint32 *)dst; ALPHA_BLEND_SWIZZLE_8888(src32, dst32, srcfmt, dstfmt); + if (fill_alpha) { + dst32 |= dstAmask; + } *(Uint32 *)dst = dst32; src += 4; dst += 4; @@ -1184,6 +1214,10 @@ static void Blit8888to8888PixelAlphaSwizzleNEON(SDL_BlitInfo *info) int dstskip = info->dst_skip; const SDL_PixelFormatDetails *srcfmt = info->src_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 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)))); 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--) { int i = 0; @@ -1242,6 +1276,10 @@ static void Blit8888to8888PixelAlphaSwizzleNEON(SDL_BlitInfo *info) // temp = vraddhn_u16(res_lo, vrshrq_n_u16(res_lo, 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 vst1q_u8(dst, dst128); @@ -1266,6 +1304,10 @@ static void Blit8888to8888PixelAlphaSwizzleNEON(SDL_BlitInfo *info) 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 vst1_lane_u32((Uint32*)dst, vreinterpret_u32_u8(dst32), 0); diff --git a/libs/SDL3/src/video/SDL_blit_N.c b/libs/SDL3/src/video/SDL_blit_N.c index c710e468..dd157901 100644 --- a/libs/SDL3/src/video/SDL_blit_N.c +++ b/libs/SDL3/src/video/SDL_blit_N.c @@ -22,6 +22,7 @@ #ifdef SDL_HAVE_BLIT_N +#include "SDL_pixels_c.h" #include "SDL_surface_c.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 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) { 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; if (dstfmt->bits_per_pixel > 8) { Uint32 a_need = NO_ALPHA; diff --git a/libs/SDL3/src/video/SDL_pixels.c b/libs/SDL3/src/video/SDL_pixels.c index 5fede225..5e341f43 100644 --- a/libs/SDL3/src/video/SDL_pixels.c +++ b/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) { if (SDL_ISPIXELFORMAT_FOURCC(format)) { diff --git a/libs/SDL3/src/video/SDL_pixels_c.h b/libs/SDL3/src/video/SDL_pixels_c.h index 92c34016..11be7f3f 100644 --- a/libs/SDL3/src/video/SDL_pixels_c.h +++ b/libs/SDL3/src/video/SDL_pixels_c.h @@ -29,7 +29,7 @@ // 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 void SDL_QuitPixelFormatDetails(void); diff --git a/libs/SDL3/src/video/SDL_surface.c b/libs/SDL3/src/video/SDL_surface.c index 945fda60..e6f13b81 100644 --- a/libs/SDL3/src/video/SDL_surface.c +++ b/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 (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)) { // Overflow... return false; @@ -199,6 +204,11 @@ SDL_Surface *SDL_CreateSurface(int width, int height, SDL_PixelFormat format) return NULL; } + if (format == SDL_PIXELFORMAT_UNKNOWN) { + SDL_InvalidParamError("format"); + return NULL; + } + if (!SDL_CalculateSurfaceSize(format, width, height, &size, &pitch, false /* not minimal pitch */)) { // Overflow... return NULL; @@ -214,7 +224,7 @@ SDL_Surface *SDL_CreateSurface(int width, int height, SDL_PixelFormat format) return NULL; } - if (surface->w && surface->h) { + if (surface->w && surface->h && format != SDL_PIXELFORMAT_MJPG) { surface->flags &= ~SDL_SURFACE_PREALLOCATED; surface->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), size); if (!surface->pixels) { @@ -245,6 +255,11 @@ SDL_Surface *SDL_CreateSurfaceFrom(int width, int height, SDL_PixelFormat format return NULL; } + if (format == SDL_PIXELFORMAT_UNKNOWN) { + SDL_InvalidParamError("format"); + return NULL; + } + if (pitch == 0 && !pixels) { // The application will fill these in later with valid values } else { @@ -1917,7 +1932,18 @@ SDL_Surface *SDL_ConvertSurfaceAndColorspace(SDL_Surface *surface, SDL_PixelForm SDL_SetSurfaceColorspace(convert, colorspace); 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; } diff --git a/libs/SDL3/src/video/SDL_surface_c.h b/libs/SDL3/src/video/SDL_surface_c.h index 27dc88a6..9e73a4bc 100644 --- a/libs/SDL3/src/video/SDL_surface_c.h +++ b/libs/SDL3/src/video/SDL_surface_c.h @@ -83,6 +83,7 @@ struct SDL_Surface // Surface functions extern bool SDL_SurfaceValid(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_GetSurfaceSDRWhitePoint(SDL_Surface *surface, SDL_Colorspace colorspace); extern float SDL_GetDefaultHDRHeadroom(SDL_Colorspace colorspace); diff --git a/libs/SDL3/src/video/SDL_sysvideo.h b/libs/SDL3/src/video/SDL_sysvideo.h index 6951d456..6da8bd2e 100644 --- a/libs/SDL3/src/video/SDL_sysvideo.h +++ b/libs/SDL3/src/video/SDL_sysvideo.h @@ -163,6 +163,8 @@ struct SDL_VideoDisplay float content_scale; SDL_HDROutputProperties HDR; + // This is true if we are fullscreen or fullscreen is pending + bool fullscreen_active; SDL_Window *fullscreen_window; SDL_VideoDevice *device; diff --git a/libs/SDL3/src/video/SDL_video.c b/libs/SDL3/src/video/SDL_video.c index 3a400d81..725bbc80 100644 --- a/libs/SDL3/src/video/SDL_video.c +++ b/libs/SDL3/src/video/SDL_video.c @@ -1435,6 +1435,11 @@ void SDL_SetDesktopDisplayMode(SDL_VideoDisplay *display, const SDL_DisplayMode { 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); if (display->desktop_mode.internal) { @@ -1944,6 +1949,8 @@ bool SDL_UpdateFullscreenMode(SDL_Window *window, SDL_FullscreenOp fullscreen, b SDL_MinimizeWindow(display->fullscreen_window); } + display->fullscreen_active = window->fullscreen_exclusive; + if (!SDL_SetDisplayModeForDisplay(display, mode)) { 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); } } else if (ret == SDL_FULLSCREEN_FAILED) { + display->fullscreen_active = false; 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). */ if (!SDL_SendsFullscreenDimensions(_this)) { + SDL_Rect displayRect; + if (mode) { mode_w = mode->w; mode_h = mode->h; + SDL_GetDisplayBounds(mode->displayID, &displayRect); } else { mode_w = display->desktop_mode.w; mode_h = display->desktop_mode.h; + SDL_GetDisplayBounds(display->id, &displayRect); } if (window->w != mode_w || window->h != mode_h) { resized = true; } + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, displayRect.x, displayRect.y); + if (resized) { SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, mode_w, mode_h); } else { @@ -2005,6 +2019,8 @@ bool SDL_UpdateFullscreenMode(SDL_Window *window, SDL_FullscreenOp fullscreen, b // Restore the desktop mode if (display) { + display->fullscreen_active = false; + SDL_SetDisplayModeForDisplay(display, NULL); } if (commit) { @@ -2034,6 +2050,7 @@ bool SDL_UpdateFullscreenMode(SDL_Window *window, SDL_FullscreenOp fullscreen, b } if (!SDL_SendsFullscreenDimensions(_this)) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, window->windowed.x, window->windowed.y); if (resized) { SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window->windowed.w, window->windowed.h); } else { @@ -2907,11 +2924,12 @@ bool SDL_GetWindowPosition(SDL_Window *window, int *x, int *y) } } } else { + const bool use_pending = (window->flags & SDL_WINDOW_HIDDEN) && window->last_position_pending; if (x) { - *x = window->x; + *x = use_pending ? window->pending.x : window->x; } if (y) { - *y = window->y; + *y = use_pending ? window->pending.y : window->y; } } return true; @@ -5387,6 +5405,10 @@ bool SDL_GetTextInputMultiline(SDL_PropertiesID props) static bool AutoShowingScreenKeyboard(void) { 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()) || SDL_GetStringBoolean(hint, false)) { return true; diff --git a/libs/SDL3/src/video/SDL_yuv.c b/libs/SDL3/src/video/SDL_yuv.c index ccacfcf4..cffc9502 100644 --- a/libs/SDL3/src/video/SDL_yuv.c +++ b/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 UVheight = (height + 1) / 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 dstUVPitchLeft = (dstUVPitch - UVwidth * 2) / sizeof(Uint16); - const Uint16 *srcUV; - Uint16 *dstUV; // Skip the Y plane src = (const Uint8 *)src + height * src_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; } diff --git a/libs/SDL3/src/video/cocoa/SDL_cocoamodes.m b/libs/SDL3/src/video/cocoa/SDL_cocoamodes.m index b4e151b9..4c168de8 100644 --- a/libs/SDL3/src/video/cocoa/SDL_cocoamodes.m +++ b/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) { - 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; + } + + return true; } - - { - 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; } 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); if (result == kCGErrorSuccess) { // If this mode works, try it first next time. - CFArrayExchangeValuesAtIndices(data->modes, i, 0); + if (i > 0) { + CFArrayExchangeValuesAtIndices(data->modes, i, 0); + } break; } } diff --git a/libs/SDL3/src/video/cocoa/SDL_cocoawindow.m b/libs/SDL3/src/video/cocoa/SDL_cocoawindow.m index 8ed3bcc9..c5d88b10 100644 --- a/libs/SDL3/src/video/cocoa/SDL_cocoawindow.m +++ b/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. } else if (!state && window->last_fullscreen_exclusive_display) { 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. } if (inFullscreenTransition) { if (state) { + [self clearPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN]; [self addPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN]; } else { + [self clearPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN]; [self addPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN]; } return YES; @@ -903,6 +905,11 @@ static NSCursor *Cocoa_GetDesiredCursor(void) pendingWindowOperation &= ~operation; } +- (void)clearAllPendingWindowOperations +{ + pendingWindowOperation = PENDING_OPERATION_NONE; +} + - (void)addPendingWindowOperation:(PendingWindowOperation)operation { pendingWindowOperation |= operation; @@ -915,7 +922,8 @@ static NSCursor *Cocoa_GetDesiredCursor(void) - (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; } @@ -1349,21 +1357,14 @@ static NSCursor *Cocoa_GetDesiredCursor(void) 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 { - 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 @@ -1371,6 +1372,7 @@ static NSCursor *Cocoa_GetDesiredCursor(void) SDL_Window *window = _data.window; inFullscreenTransition = NO; + isFullscreenSpace = YES; [self clearPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN]; if ([self windowOperationIsPending:PENDING_OPERATION_LEAVE_FULLSCREEN]) { @@ -1421,26 +1423,14 @@ static NSCursor *Cocoa_GetDesiredCursor(void) 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 { - 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 @@ -1449,16 +1439,24 @@ static NSCursor *Cocoa_GetDesiredCursor(void) NSWindow *nswindow = _data.nswindow; inFullscreenTransition = NO; + isFullscreenSpace = NO; _data.fullscreen_space_requested = NO; /* 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 when returning to windowed mode from a space (instead of using a pending fullscreen mode style mask) seems to work around that issue. */ 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 * a blocking transition, as the caller is already waiting in * UpdateFullscreenMode(). @@ -1503,9 +1501,10 @@ static NSCursor *Cocoa_GetDesiredCursor(void) if ([self windowOperationIsPending:PENDING_OPERATION_ZOOM]) { [self clearPendingWindowOperation:PENDING_OPERATION_ZOOM]; [nswindow zoom:nil]; + _data.was_zoomed = !_data.was_zoomed; } - if (![nswindow isZoomed]) { + if (!_data.was_zoomed) { // Apply a pending window size, if not zoomed. NSRect rect; 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) { return SDL_OutOfMemory(); } + window->internal = (SDL_WindowData *)CFBridgingRetain(data); data.window = window; data.nswindow = nswindow; 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); // All done! - window->internal = (SDL_WindowData *)CFBridgingRetain(data); 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 result = true; + bool result = false; @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; - while (true) { + + for (;;) { SDL_PumpEvents(); - if (SDL_GetTicksNS() >= timeout) { - result = false; - break; - } - if (![data.listener hasPendingWindowOperation]) { + result = ![data.listener hasPendingWindowOperation]; + if (result || SDL_GetTicksNS() >= timeout) { break; } + // Small delay before going again. SDL_Delay(10); } } diff --git a/libs/SDL3/src/video/emscripten/SDL_emscriptenevents.c b/libs/SDL3/src/video/emscripten/SDL_emscriptenevents.c index 49a140f3..d9fa3c8f 100644 --- a/libs/SDL3/src/video/emscripten/SDL_emscriptenevents.c +++ b/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); } +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) { 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); // 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_keyup_callback(keyElement, data, 0, Emscripten_HandleKey); 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); - 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_keyup_callback(target, NULL, 0, NULL); emscripten_set_keypress_callback(target, NULL, 0, NULL); diff --git a/libs/SDL3/src/video/uikit/SDL_uikitvideo.m b/libs/SDL3/src/video/uikit/SDL_uikitvideo.m index 5c3987d1..f5326d83 100644 --- a/libs/SDL3/src/video/uikit/SDL_uikitvideo.m +++ b/libs/SDL3/src/video/uikit/SDL_uikitvideo.m @@ -37,6 +37,7 @@ #include "SDL_uikitvulkan.h" #include "SDL_uikitmetalview.h" #include "SDL_uikitmessagebox.h" +#include "SDL_uikitpen.h" #define UIKITVID_DRIVER_NAME "uikit" @@ -170,6 +171,7 @@ static void UIKit_VideoQuit(SDL_VideoDevice *_this) SDL_QuitGCKeyboard(); SDL_QuitGCMouse(); + UIKit_QuitPen(_this); UIKit_QuitModes(_this); } diff --git a/libs/SDL3/src/video/wayland/SDL_waylandevents.c b/libs/SDL3/src/video/wayland/SDL_waylandevents.c index 83c414fb..06d1aebc 100644 --- a/libs/SDL3/src/video/wayland/SDL_waylandevents.c +++ b/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); dy = wl_fixed_to_double(dy_unaccel_w); } 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); @@ -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); 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) { 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"); } + // 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 * 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); } + // Commit the double-buffered confinement region. + wl_surface_commit(w->surface); + w->confined_pointer = confined_pointer; return true; } diff --git a/libs/SDL3/src/video/wayland/SDL_waylandmouse.c b/libs/SDL3/src/video/wayland/SDL_waylandmouse.c index 8075c3a2..d464cf3f 100644 --- a/libs/SDL3/src/video/wayland/SDL_waylandmouse.c +++ b/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; result = viddata->input->buttons_pressed; + SDL_GetMouseState(x, y); SDL_RelativeToGlobalForWindow(focus, focus->x, focus->y, &off_x, &off_y); *x += off_x; *y += off_y; diff --git a/libs/SDL3/src/video/wayland/SDL_waylandwindow.c b/libs/SDL3/src/video/wayland/SDL_waylandwindow.c index 212fd54f..523d2e4b 100644 --- a/libs/SDL3/src/video/wayland/SDL_waylandwindow.c +++ b/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, * 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. - 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); } else if (wind->is_fullscreen) { /* diff --git a/libs/SDL3/src/video/windows/SDL_windowsevents.c b/libs/SDL3/src/video/windows/SDL_windowsevents.c index f271db85..175e2211 100644 --- a/libs/SDL3/src/video/windows/SDL_windowsevents.c +++ b/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; - if (haveMotion) { + if (haveMotion && !windowdata->in_modal_loop) { if (!isAbsolute) { SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)dx, (float)dy); } else { @@ -1640,7 +1640,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara POINT cursorPos; GetCursorPos(&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; @@ -1792,7 +1792,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara } 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 + 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 - if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) { + if (GetClientRect(hwnd, &rect) && WIN_WindowRectValid(&rect)) { w = rect.right; 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) { 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; } } break; @@ -2084,7 +2093,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara RECT rect; float x, y; - if (!GetClientRect(hwnd, &rect) || WIN_IsRectEmpty(&rect)) { + if (!GetClientRect(hwnd, &rect) || !WIN_WindowRectValid(&rect)) { if (inputs) { SDL_small_free(inputs, isstack); } diff --git a/libs/SDL3/src/video/windows/SDL_windowsvideo.c b/libs/SDL3/src/video/windows/SDL_windowsvideo.c index 04415b54..6103d523 100644 --- a/libs/SDL3/src/video/windows/SDL_windowsvideo.c +++ b/libs/SDL3/src/video/windows/SDL_windowsvideo.c @@ -108,6 +108,9 @@ static void WIN_DeleteDevice(SDL_VideoDevice *device) if (data->shcoreDLL) { SDL_UnloadObject(data->shcoreDLL); } + if (data->dwmapiDLL) { + SDL_UnloadObject(data->dwmapiDLL); + } #endif #ifdef HAVE_DXGI_H if (data->pDXGIFactory) { @@ -184,6 +187,17 @@ static SDL_VideoDevice *WIN_CreateDevice(void) } else { 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) #ifdef HAVE_DXGI_H diff --git a/libs/SDL3/src/video/windows/SDL_windowsvideo.h b/libs/SDL3/src/video/windows/SDL_windowsvideo.h index 53cfbadd..c80a721a 100644 --- a/libs/SDL3/src/video/windows/SDL_windowsvideo.h +++ b/libs/SDL3/src/video/windows/SDL_windowsvideo.h @@ -374,6 +374,45 @@ typedef struct tagINPUTCONTEXT2 } INPUTCONTEXT2, *PINPUTCONTEXT2, NEAR *NPINPUTCONTEXT2, FAR *LPINPUTCONTEXT2; #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 struct SDL_VideoData @@ -420,6 +459,11 @@ struct SDL_VideoData BOOL (WINAPI *GetPointerType)(UINT32 pointerId, POINTER_INPUT_TYPE *pointerType); 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 #endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) diff --git a/libs/SDL3/src/video/windows/SDL_windowswindow.c b/libs/SDL3/src/video/windows/SDL_windowswindow.c index 36c8de7f..18820f1d 100644 --- a/libs/SDL3/src/video/windows/SDL_windowswindow.c +++ b/libs/SDL3/src/video/windows/SDL_windowswindow.c @@ -38,10 +38,6 @@ // Dropfile support #include -// 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 typedef enum { UXTHEME_APPMODE_DEFAULT, @@ -80,46 +76,6 @@ typedef UxthemePreferredAppMode (WINAPI *SetPreferredAppMode_t)(UxthemePreferred typedef BOOL (WINAPI *SetWindowCompositionAttribute_t)(HWND, const WINDOWCOMPOSITIONATTRIBDATA *); 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 #ifndef SWP_NOCOPYBITS #define SWP_NOCOPYBITS 0 @@ -447,7 +403,6 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwn data->videodata = videodata; data->initializing = true; data->last_displayID = window->last_displayID; - data->dwma_border_color = DWMWA_COLOR_DEFAULT; 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)) { RECT rect; - if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) { + if (GetClientRect(hwnd, &rect) && WIN_WindowRectValid(&rect)) { int w = rect.right; 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) { + 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 parent = NULL; 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) // FIXME: does not work on all hardware configurations with different renders (i.e. hybrid GPUs) 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; RECT rect; - if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) { + if (GetClientRect(hwnd, &rect) && WIN_WindowRectValid(&rect)) { *w = rect.right; *h = rect.bottom; } 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) SDL_DisplayData *displaydata = display->internal; SDL_WindowData *data = window->internal; - HWND hwnd = data->hwnd; + HWND hwnd = data ? data->hwnd : NULL; MONITORINFO minfo; DWORD style, styleEx; 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 - 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 { 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. Special care is taken to *not* do this if and when we're diff --git a/libs/SDL3/src/video/windows/SDL_windowswindow.h b/libs/SDL3/src/video/windows/SDL_windowswindow.h index a2c9a211..3d1aaed0 100644 --- a/libs/SDL3/src/video/windows/SDL_windowswindow.h +++ b/libs/SDL3/src/video/windows/SDL_windowswindow.h @@ -87,8 +87,6 @@ struct SDL_WindowData RECT initial_size_rect; 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 - UINT windowed_mode_corner_rounding; - COLORREF dwma_border_color; bool mouse_tracked; bool destroy_parent_with_window; SDL_DisplayID last_displayID; diff --git a/libs/SDL3/src/video/x11/SDL_x11events.c b/libs/SDL3/src/video/x11/SDL_x11events.c index 02c2d905..e674b9ab 100644 --- a/libs/SDL3/src/video/x11/SDL_x11events.c +++ b/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); SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true); - if (*text) { + if (*text && !(SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_ALT))) { text[text_length] = '\0'; X11_ClearComposition(windowdata); SDL_SendKeyboardText(text); @@ -2113,6 +2113,7 @@ void X11_PumpEvents(SDL_VideoDevice *_this) 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); SDL_SetDisplayModeForDisplay(_this->displays[i], NULL); + _this->displays[i]->internal->mode_switch_deadline_ns = 0; } } } diff --git a/libs/SDL3/src/video/x11/SDL_x11messagebox.c b/libs/SDL3/src/video/x11/SDL_x11messagebox.c index a2fdba48..8aa1c6a8 100644 --- a/libs/SDL3/src/video/x11/SDL_x11messagebox.c +++ b/libs/SDL3/src/video/x11/SDL_x11messagebox.c @@ -48,13 +48,17 @@ static const char g_MessageBoxFontLatin1[] = "-*-*-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] = { { 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) { char **missing = NULL; 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) { - return SDL_SetError("Couldn't load font %s", g_MessageBoxFont); + return SDL_SetError("Couldn't load x11 message box font"); } } else #endif diff --git a/libs/SDL3/src/video/x11/SDL_x11modes.c b/libs/SDL3/src/video/x11/SDL_x11modes.c index e17a2d10..37198a72 100644 --- a/libs/SDL3/src/video/x11/SDL_x11modes.c +++ b/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) { - 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; } - 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) { SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]); 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) @@ -580,9 +631,12 @@ static void X11_HandleXRandROutputChange(SDL_VideoDevice *_this, const XRROutput int i; #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 + // XWayland doesn't always send output disconnected events + X11_CheckDisplaysRemoved(_this, ev->display); + displays = SDL_GetDisplays(NULL); if (displays) { for (i = 0; displays[i]; ++i) { diff --git a/libs/SDL3/src/video/x11/SDL_x11video.c b/libs/SDL3/src/video/x11/SDL_x11video.c index 62b4330e..75862db2 100644 --- a/libs/SDL3/src/video/x11/SDL_x11video.c +++ b/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; } +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) { SDL_VideoDevice *device; @@ -256,8 +273,14 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->system_theme = SDL_SystemTheme_Get(); #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); if (data->is_xwayland) { diff --git a/libs/SDL3/src/video/x11/SDL_x11window.c b/libs/SDL3/src/video/x11/SDL_x11window.c index 55620533..e96a5aca 100644 --- a/libs/SDL3/src/video/x11/SDL_x11window.c +++ b/libs/SDL3/src/video/x11/SDL_x11window.c @@ -521,7 +521,9 @@ bool X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properties return false; } - SetupWindowInput(_this, window); + if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_EXTERNAL_WINDOW_INPUT, true)) { + SetupWindowInput(_this, window); + } return true; } @@ -532,7 +534,7 @@ bool X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properties } const bool force_override_redirect = SDL_GetHintBoolean(SDL_HINT_X11_FORCE_OVERRIDE_REDIRECT, false); - const bool use_resize_sync = (window->flags & SDL_WINDOW_VULKAN); /* doesn't work well with Vulkan */ + const bool use_resize_sync = !(window->flags & SDL_WINDOW_VULKAN); /* doesn't work well with Vulkan */ SDL_WindowData *windowdata; Display *display = data->display; int screen = displaydata->screen; @@ -1436,7 +1438,6 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) SDL_WindowData *data = window->internal; Display *display = data->videodata->display; bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, true); - bool position_is_absolute = false; bool set_position = false; XEvent event; @@ -1444,9 +1445,6 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) // Update the position in case the parent moved while we were hidden X11_ConstrainPopup(window, true); data->pending_position = true; - - // Coordinates after X11_ConstrainPopup() are already in the global space. - position_is_absolute = true; set_position = true; } @@ -1489,14 +1487,11 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) const int tx = data->pending_position ? window->pending.x : window->x; const int ty = data->pending_position ? window->pending.y : window->y; int x, y; - if (position_is_absolute) { - x = tx; - y = ty; - } else { - SDL_RelativeToGlobalForWindow(window, - tx - data->border_left, ty - data->border_top, - &x, &y); - } + + SDL_RelativeToGlobalForWindow(window, + tx - data->border_left, ty - data->border_top, + &x, &y); + data->pending_position = false; X11_XMoveWindow(display, data->xwindow, x, y); } @@ -2017,6 +2012,33 @@ bool X11_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool return true; } + /* GNOME needs the _XWAYLAND_MAY_GRAB_KEYBOARD message on XWayland: + * + * - message_type set to "_XWAYLAND_MAY_GRAB_KEYBOARD" + * - window set to the xid of the window on which the grab is to be issued + * - data.l[0] to a non-zero value + * + * The dconf setting `org/gnome/mutter/wayland/xwayland-allow-grabs` must be enabled as well. + * + * https://gitlab.gnome.org/GNOME/mutter/-/commit/5f132f39750f684c3732b4346dec810cd218d609 + */ + if (_this->internal->is_xwayland) { + Atom _XWAYLAND_MAY_GRAB_ATOM = X11_XInternAtom(display, "_XWAYLAND_MAY_GRAB_KEYBOARD", False); + + if (_XWAYLAND_MAY_GRAB_ATOM != None) { + XClientMessageEvent client_message; + client_message.type = ClientMessage; + client_message.window = data->xwindow; + client_message.format = 32; + client_message.message_type = _XWAYLAND_MAY_GRAB_ATOM; + client_message.data.l[0] = 1; + client_message.data.l[1] = CurrentTime; + + X11_XSendEvent(display, DefaultRootWindow(display), False, SubstructureNotifyMask | SubstructureRedirectMask, (XEvent *)&client_message); + X11_XFlush(display); + } + } + X11_XGrabKeyboard(display, data->xwindow, True, GrabModeAsync, GrabModeAsync, CurrentTime); } else { diff --git a/libs/SDL3/test/testautomation_render.c b/libs/SDL3/test/testautomation_render.c index 8310e92a..9a0503da 100644 --- a/libs/SDL3/test/testautomation_render.c +++ b/libs/SDL3/test/testautomation_render.c @@ -1512,6 +1512,132 @@ static int SDLCALL render_testUVWrapping(void *arg) return TEST_COMPLETED; } +/** + * Tests texture state changes + */ +static int SDLCALL render_testTextureState(void *arg) +{ + const Uint8 pixels[8] = { + 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF + }; + const SDL_Color expected[] = { + /* Step 0: plain copy */ + { 0x00, 0x00, 0x00, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF }, + /* Step 1: color mod to red */ + { 0x00, 0x00, 0x00, 0xFF }, + { 0xFF, 0x00, 0x00, 0xFF }, + /* Step 2: alpha mod to 128 (cleared to green) */ + { 0x00, 0x7F, 0x00, 0xFF }, + { 0x80, 0xFF, 0x80, 0xFF }, + /* Step 3: nearest stretch */ + { 0xFF, 0xFF, 0xFF, 0xFF }, + { 0x00, 0xFF, 0x00, 0xFF }, + /* Step 4: linear stretch */ + { 0x80, 0x80, 0x80, 0xFF }, + { 0x00, 0xFF, 0x00, 0xFF }, + }; + SDL_Texture *texture; + SDL_Rect rect; + SDL_FRect dst; + int i; + + /* Clear surface to green */ + SDL_SetRenderDrawColor(renderer, 0, 255, 0, SDL_ALPHA_OPAQUE); + SDL_RenderClear(renderer); + + /* Create 2-pixel surface. */ + texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, 2, 1); + SDLTest_AssertCheck(texture != NULL, "Verify SDL_CreateTexture() result"); + if (texture == NULL) { + return TEST_ABORTED; + } + SDL_UpdateTexture(texture, NULL, pixels, sizeof(pixels)); + + dst.x = 0.0f; + dst.y = 0.0f; + dst.w = 2.0f; + dst.h = 1.0f; + + /* Step 0: plain copy */ + SDL_RenderTexture(renderer, texture, NULL, &dst); + dst.y += 1; + + /* Step 1: color mod to red */ + SDL_SetTextureColorMod(texture, 0xFF, 0x00, 0x00); + SDL_RenderTexture(renderer, texture, NULL, &dst); + SDL_SetTextureColorMod(texture, 0xFF, 0xFF, 0xFF); + dst.y += 1; + + /* Step 2: alpha mod to 128 */ + SDL_SetTextureAlphaMod(texture, 0x80); + SDL_RenderTexture(renderer, texture, NULL, &dst); + SDL_SetTextureAlphaMod(texture, 0xFF); + dst.y += 1; + + /* Step 3: nearest stretch */ + dst.w = 1; + SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); + SDL_RenderTexture(renderer, texture, NULL, &dst); + dst.y += 1; + + /* Step 4: linear stretch */ + dst.w = 1; + SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_LINEAR); + SDL_RenderTexture(renderer, texture, NULL, &dst); + dst.y += 1; + + /* Verify results */ + rect.x = 0; + rect.y = 0; + rect.w = 2; + rect.h = 1; + for (i = 0; i < SDL_arraysize(expected); ) { + const int MAX_DELTA = 1; + SDL_Color actual; + int deltaR, deltaG, deltaB, deltaA; + SDL_Surface *surface = SDL_RenderReadPixels(renderer, &rect); + + SDL_ReadSurfacePixel(surface, 0, 0, &actual.r, &actual.g, &actual.b, &actual.a); + deltaR = (actual.r - expected[i].r); + deltaG = (actual.g - expected[i].g); + deltaB = (actual.b - expected[i].b); + deltaA = (actual.a - expected[i].a); + SDLTest_AssertCheck(SDL_abs(deltaR) <= MAX_DELTA && + SDL_abs(deltaG) <= MAX_DELTA && + SDL_abs(deltaB) <= MAX_DELTA && + SDL_abs(deltaA) <= MAX_DELTA, + "Validate left pixel at step %d, expected %d,%d,%d,%d, got %d,%d,%d,%d", i/2, + expected[i].r, expected[i].g, expected[i].b, expected[i].a, + actual.r, actual.g, actual.b, actual.a); + ++i; + + SDL_ReadSurfacePixel(surface, 1, 0, &actual.r, &actual.g, &actual.b, &actual.a); + deltaR = (actual.r - expected[i].r); + deltaG = (actual.g - expected[i].g); + deltaB = (actual.b - expected[i].b); + deltaA = (actual.a - expected[i].a); + SDLTest_AssertCheck(SDL_abs(deltaR) <= MAX_DELTA && + SDL_abs(deltaG) <= MAX_DELTA && + SDL_abs(deltaB) <= MAX_DELTA && + SDL_abs(deltaA) <= MAX_DELTA, + "Validate right pixel at step %d, expected %d,%d,%d,%d, got %d,%d,%d,%d", i/2, + expected[i].r, expected[i].g, expected[i].b, expected[i].a, + actual.r, actual.g, actual.b, actual.a); + ++i; + + SDL_DestroySurface(surface); + + rect.y += 1; + } + + /* Clean up. */ + SDL_DestroyTexture(texture); + + return TEST_COMPLETED; +} + /* ================= Test References ================== */ /* Render test cases */ @@ -1563,6 +1689,10 @@ static const SDLTest_TestCaseReference renderTestUVWrapping = { render_testUVWrapping, "render_testUVWrapping", "Tests geometry UV wrapping", TEST_ENABLED }; +static const SDLTest_TestCaseReference renderTestTextureState = { + render_testTextureState, "render_testTextureState", "Tests texture state changes", TEST_ENABLED +}; + /* Sequence of Render test cases */ static const SDLTest_TestCaseReference *renderTests[] = { &renderTestGetNumRenderDrivers, @@ -1577,6 +1707,7 @@ static const SDLTest_TestCaseReference *renderTests[] = { &renderTestClipRect, &renderTestLogicalSize, &renderTestUVWrapping, + &renderTestTextureState, NULL }; diff --git a/libs/SDL3/test/testautomation_stdlib.c b/libs/SDL3/test/testautomation_stdlib.c index bd7e17a5..e3f2d47a 100644 --- a/libs/SDL3/test/testautomation_stdlib.c +++ b/libs/SDL3/test/testautomation_stdlib.c @@ -750,6 +750,7 @@ static int SDLCALL stdlib_sscanf(void *arg) size_t size_output, expected_size_output; void *ptr_output, *expected_ptr_output; char text[128], text2[128]; + unsigned int r = 0, g = 0, b = 0; expected_output = output = 123; expected_result = -1; @@ -785,6 +786,17 @@ static int SDLCALL stdlib_sscanf(void *arg) SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); SDLTest_AssertCheck(length == 1, "Check length, expected: 1, got: %i", length); + expected_result = 3; + result = SDL_sscanf("#026", "#%1x%1x%1x", &r, &g, &b); + SDLTest_AssertPass("Call to SDL_sscanf(\"#026\", \"#%%1x%%1x%%1x\", &r, &g, &b)"); + expected_output = 0; + SDLTest_AssertCheck(r == expected_output, "Check output for r, expected: %i, got: %i", expected_output, r); + expected_output = 2; + SDLTest_AssertCheck(g == expected_output, "Check output for g, expected: %i, got: %i", expected_output, g); + expected_output = 6; + SDLTest_AssertCheck(b == expected_output, "Check output for b, expected: %i, got: %i", expected_output, b); + SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); + #define SIZED_TEST_CASE(type, var, printf_specifier, scanf_specifier) \ var##_output = 123; \ var##_length = 0; \ diff --git a/libs/SDL3/test/testautomation_surface.c b/libs/SDL3/test/testautomation_surface.c index c1d641ee..014fca79 100644 --- a/libs/SDL3/test/testautomation_surface.c +++ b/libs/SDL3/test/testautomation_surface.c @@ -112,7 +112,7 @@ static void testBlitBlendModeWithFormats(int mode, SDL_PixelFormat src_format, S int deltaR, deltaG, deltaB, deltaA; /* Create dst surface */ - dst = SDL_CreateSurface(1, 1, dst_format); + dst = SDL_CreateSurface(9, 1, dst_format); SDLTest_AssertCheck(dst != NULL, "Verify dst surface is not NULL"); if (dst == NULL) { return; @@ -137,7 +137,7 @@ static void testBlitBlendModeWithFormats(int mode, SDL_PixelFormat src_format, S SDL_GetRGBA(color, SDL_GetPixelFormatDetails(dst->format), SDL_GetSurfacePalette(dst), &dstR, &dstG, &dstB, &dstA); /* Create src surface */ - src = SDL_CreateSurface(1, 1, src_format); + src = SDL_CreateSurface(9, 1, src_format); SDLTest_AssertCheck(src != NULL, "Verify src surface is not NULL"); if (src == NULL) { return; @@ -313,6 +313,24 @@ static void AssertFileExist(const char *filename) /* Test case functions */ +/** + * Tests creating surface with invalid format + */ +static int SDLCALL surface_testInvalidFormat(void *arg) +{ + SDL_Surface *surface; + + surface = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_UNKNOWN); + SDLTest_AssertCheck(surface == NULL, "Verify SDL_CreateSurface(SDL_PIXELFORMAT_UNKNOWN) returned NULL"); + SDL_DestroySurface(surface); + + surface = SDL_CreateSurfaceFrom(32, 32, SDL_PIXELFORMAT_UNKNOWN, NULL, 0); + SDLTest_AssertCheck(surface == NULL, "Verify SDL_CreateSurfaceFrom(SDL_PIXELFORMAT_UNKNOWN) returned NULL"); + SDL_DestroySurface(surface); + + return TEST_COMPLETED; +} + /** * Tests sprite saving and loading */ @@ -1540,6 +1558,10 @@ static int SDLCALL surface_testScale(void *arg) /* ================= Test References ================== */ /* Surface test cases */ +static const SDLTest_TestCaseReference surfaceTestInvalidFormat = { + surface_testInvalidFormat, "surface_testInvalidFormat", "Tests creating surface with invalid format", TEST_ENABLED +}; + static const SDLTest_TestCaseReference surfaceTestSaveLoadBitmap = { surface_testSaveLoadBitmap, "surface_testSaveLoadBitmap", "Tests sprite saving and loading.", TEST_ENABLED }; @@ -1638,6 +1660,7 @@ static const SDLTest_TestCaseReference surfaceTestScale = { /* Sequence of Surface test cases */ static const SDLTest_TestCaseReference *surfaceTests[] = { + &surfaceTestInvalidFormat, &surfaceTestSaveLoadBitmap, &surfaceTestBlitZeroSource, &surfaceTestBlit,