diff --git a/libs/SDL3/.git-hash b/libs/SDL3/.git-hash index 3a026d3..5c68109 100644 --- a/libs/SDL3/.git-hash +++ b/libs/SDL3/.git-hash @@ -1 +1 @@ -b5c3eab6b447111d3c7879bb547b80fb4abd9063 +96292a5b464258a2b926e0a3d72f8b98c2a81aa6 diff --git a/libs/SDL3/Android.mk b/libs/SDL3/Android.mk index b5fe895..413967c 100644 --- a/libs/SDL3/Android.mk +++ b/libs/SDL3/Android.mk @@ -107,6 +107,10 @@ LOCAL_LDLIBS := -ldl -lGLESv1_CM -lGLESv2 -lOpenSLES -llog -landroid LOCAL_LDFLAGS := -Wl,--no-undefined -Wl,--no-undefined-version -Wl,--version-script=$(LOCAL_PATH)/src/dynapi/SDL_dynapi.sym +# https://developer.android.com/guide/practices/page-sizes +LOCAL_LDFLAGS += "-Wl,-z,max-page-size=16384" +LOCAL_LDFLAGS += "-Wl,-z,common-page-size=16384" + ifeq ($(NDK_DEBUG),1) cmd-strip := endif diff --git a/libs/SDL3/CMakeLists.txt b/libs/SDL3/CMakeLists.txt index d8e5667..5873957 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.4") +project(SDL3 LANGUAGES C VERSION "3.2.20") if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) set(SDL3_MAINPROJECT ON) @@ -188,9 +188,12 @@ if(MSVC) # Make sure /RTC1 is disabled, otherwise it will use functions from the CRT foreach(flag_var CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE - CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) + CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) string(REGEX REPLACE "/RTC(su|[1su])" "" ${flag_var} "${${flag_var}}") endforeach(flag_var) + set(CMAKE_MSVC_RUNTIME_CHECKS "") endif() if(MSVC_CLANG) @@ -243,6 +246,8 @@ if(SDL_SHARED_DEFAULT AND SDL_STATIC_DEFAULT AND SDL_SHARED_AVAILABLE) endif() endif() +dep_option(SDL_DEPS_SHARED "Load dependencies dynamically" ON SDL_SHARED_AVAILABLE OFF) + set(SDL_SUBSYSTEMS ) macro(define_sdl_subsystem _name) @@ -334,19 +339,19 @@ set_option(SDL_PTHREADS "Use POSIX threads for multi-threading" ${SDL dep_option(SDL_PTHREADS_SEM "Use pthread semaphores" ON "SDL_PTHREADS" OFF) dep_option(SDL_OSS "Support the OSS audio API" ${SDL_OSS_DEFAULT} "UNIX_SYS OR RISCOS;SDL_AUDIO" OFF) dep_option(SDL_ALSA "Support the ALSA audio API" ${UNIX_SYS} "SDL_AUDIO" OFF) -dep_option(SDL_ALSA_SHARED "Dynamically load ALSA audio support" ON "SDL_ALSA" OFF) +dep_option(SDL_ALSA_SHARED "Dynamically load ALSA audio support" ON "SDL_ALSA;SDL_DEPS_SHARED" OFF) dep_option(SDL_JACK "Support the JACK audio API" ${UNIX_SYS} "SDL_AUDIO" OFF) -dep_option(SDL_JACK_SHARED "Dynamically load JACK audio support" ON "SDL_JACK" OFF) +dep_option(SDL_JACK_SHARED "Dynamically load JACK audio support" ON "SDL_JACK;SDL_DEPS_SHARED" OFF) set_option(SDL_PIPEWIRE "Use Pipewire audio" ${UNIX_SYS}) -dep_option(SDL_PIPEWIRE_SHARED "Dynamically load Pipewire support" ON "SDL_PIPEWIRE" OFF) +dep_option(SDL_PIPEWIRE_SHARED "Dynamically load Pipewire support" ON "SDL_PIPEWIRE;SDL_DEPS_SHARED" OFF) dep_option(SDL_PULSEAUDIO "Use PulseAudio" ${UNIX_SYS} "SDL_AUDIO" OFF) -dep_option(SDL_PULSEAUDIO_SHARED "Dynamically load PulseAudio support" ON "SDL_PULSEAUDIO" OFF) +dep_option(SDL_PULSEAUDIO_SHARED "Dynamically load PulseAudio support" ON "SDL_PULSEAUDIO;SDL_DEPS_SHARED" OFF) dep_option(SDL_SNDIO "Support the sndio audio API" ${UNIX_SYS} "SDL_AUDIO" OFF) -dep_option(SDL_SNDIO_SHARED "Dynamically load the sndio audio API" ON "SDL_SNDIO" OFF) +dep_option(SDL_SNDIO_SHARED "Dynamically load the sndio audio API" ON "SDL_SNDIO;SDL_DEPS_SHARED" OFF) set_option(SDL_RPATH "Use an rpath when linking SDL" ${SDL_RPATH_DEFAULT}) set_option(SDL_CLOCK_GETTIME "Use clock_gettime() instead of gettimeofday()" ${SDL_CLOCK_GETTIME_DEFAULT}) dep_option(SDL_X11 "Use X11 video driver" ${UNIX_SYS} "SDL_VIDEO" OFF) -dep_option(SDL_X11_SHARED "Dynamically load X11 support" ON "SDL_X11" OFF) +dep_option(SDL_X11_SHARED "Dynamically load X11 support" ON "SDL_X11;SDL_DEPS_SHARED" OFF) dep_option(SDL_X11_XCURSOR "Enable Xcursor support" ON SDL_X11 OFF) dep_option(SDL_X11_XDBE "Enable Xdbe support" ON SDL_X11 OFF) dep_option(SDL_X11_XINPUT "Enable XInput support" ON SDL_X11 OFF) @@ -356,9 +361,9 @@ dep_option(SDL_X11_XSCRNSAVER "Enable Xscrnsaver support" ON SDL_X11 OFF) dep_option(SDL_X11_XSHAPE "Enable XShape support" ON SDL_X11 OFF) dep_option(SDL_X11_XSYNC "Enable Xsync support" ON SDL_X11 OFF) dep_option(SDL_WAYLAND "Use Wayland video driver" ${UNIX_SYS} "SDL_VIDEO" OFF) -dep_option(SDL_WAYLAND_SHARED "Dynamically load Wayland support" ON "SDL_WAYLAND" OFF) +dep_option(SDL_WAYLAND_SHARED "Dynamically load Wayland support" ON "SDL_WAYLAND;SDL_DEPS_SHARED" OFF) dep_option(SDL_WAYLAND_LIBDECOR "Use client-side window decorations on Wayland" ON "SDL_WAYLAND" OFF) -dep_option(SDL_WAYLAND_LIBDECOR_SHARED "Dynamically load libdecor support" ON "SDL_WAYLAND_LIBDECOR;SDL_WAYLAND_SHARED" OFF) +dep_option(SDL_WAYLAND_LIBDECOR_SHARED "Dynamically load libdecor support" ON "SDL_WAYLAND_LIBDECOR;SDL_WAYLAND_SHARED;SDL_DEPS_SHARED" OFF) dep_option(SDL_RPI "Use Raspberry Pi video driver" ON "SDL_VIDEO;UNIX_SYS;SDL_CPU_ARM32 OR SDL_CPU_ARM64" OFF) dep_option(SDL_ROCKCHIP "Use ROCKCHIP Hardware Acceleration video driver" ON "SDL_VIDEO;UNIX_SYS;SDL_CPU_ARM32 OR SDL_CPU_ARM64" OFF) dep_option(SDL_COCOA "Use Cocoa video driver" ON "APPLE" OFF) @@ -376,14 +381,14 @@ dep_option(SDL_RENDER_VULKAN "Enable the Vulkan render driver" ON "SDL_REN dep_option(SDL_METAL "Enable Metal support" ON "APPLE" OFF) set_option(SDL_OPENVR "Use OpenVR video driver" OFF) dep_option(SDL_KMSDRM "Use KMS DRM video driver" ${UNIX_SYS} "SDL_VIDEO" OFF) -dep_option(SDL_KMSDRM_SHARED "Dynamically load KMS DRM support" ON "SDL_KMSDRM" OFF) +dep_option(SDL_KMSDRM_SHARED "Dynamically load KMS DRM support" ON "SDL_KMSDRM;SDL_DEPS_SHARED" OFF) set_option(SDL_OFFSCREEN "Use offscreen video driver" ON) dep_option(SDL_DUMMYCAMERA "Support the dummy camera driver" ON SDL_CAMERA OFF) option_string(SDL_BACKGROUNDING_SIGNAL "number to use for magic backgrounding signal or 'OFF'" OFF) option_string(SDL_FOREGROUNDING_SIGNAL "number to use for magic foregrounding signal or 'OFF'" OFF) dep_option(SDL_HIDAPI "Enable the HIDAPI subsystem" ON "NOT VISIONOS" OFF) dep_option(SDL_HIDAPI_LIBUSB "Use libusb for low level joystick drivers" ON SDL_HIDAPI_LIBUSB_AVAILABLE OFF) -dep_option(SDL_HIDAPI_LIBUSB_SHARED "Dynamically load libusb support" ON SDL_HIDAPI_LIBUSB OFF) +dep_option(SDL_HIDAPI_LIBUSB_SHARED "Dynamically load libusb support" ON "SDL_HIDAPI_LIBUSB;SDL_DEPS_SHARED" OFF) dep_option(SDL_HIDAPI_JOYSTICK "Use HIDAPI for low level joystick drivers" ON SDL_HIDAPI OFF) dep_option(SDL_VIRTUAL_JOYSTICK "Enable the virtual-joystick driver" ON SDL_HIDAPI OFF) set_option(SDL_LIBUDEV "Enable libudev support" ON) @@ -632,6 +637,11 @@ if(MSVC) # Mark SDL3.dll as compatible with Control-flow Enforcement Technology (CET) sdl_shared_link_options("-CETCOMPAT") endif() + + # for VS >= 17.14 targeting ARM64: inline the Interlocked funcs + if(MSVC_VERSION GREATER 1943 AND SDL_CPU_ARM64 AND NOT SDL_LIBC) + sdl_compile_options(PRIVATE "/forceInterlockedFunctions-") + endif() endif() if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") @@ -751,7 +761,7 @@ if(SDL_ASSEMBLY) if(SDL_SSE4_2) cmake_push_check_state() if(USE_GCC OR USE_CLANG OR USE_INTELCC) - string(APPEND CMAKE_REQUIRED_FLAGS " -msse4.2") + string(APPEND CMAKE_REQUIRED_FLAGS " -msse4.2 -mcrc32") endif() check_c_source_compiles(" #include @@ -1289,8 +1299,8 @@ if(ANDROID) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/android") sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/android/*.c") - sdl_sources("${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c") - set_property(SOURCE "${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c" APPEND_STRING PROPERTY COMPILE_FLAGS " -Wno-declaration-after-statement") + sdl_sources("${CMAKE_ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c") + set_property(SOURCE "${CMAKE_ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c" APPEND_STRING PROPERTY COMPILE_FLAGS " -Wno-declaration-after-statement") sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/android/*.c") set(HAVE_SDL_MISC TRUE) @@ -1334,9 +1344,7 @@ if(ANDROID) set(HAVE_SDL_HAPTIC TRUE) endif() - if(SDL_HIDAPI) - CheckHIDAPI() - endif() + CheckHIDAPI() if(SDL_JOYSTICK) set(SDL_JOYSTICK_ANDROID 1) @@ -1479,6 +1487,11 @@ if(ANDROID) endif() endif() +if(TARGET SDL3-shared) + target_link_options(SDL3-shared PRIVATE "-Wl,-z,max-page-size=16384") + target_link_options(SDL3-shared PRIVATE "-Wl,-z,common-page-size=16384") +endif() + elseif(EMSCRIPTEN) # Hide noisy warnings that intend to aid mostly during initial stages of porting a new # project. Uncomment at will for verbose cross-compiling -I/../ path info. @@ -2186,6 +2199,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() @@ -2887,6 +2901,7 @@ elseif(N3DS) set(SDL_THREAD_N3DS 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/thread/n3ds/*.c") sdl_sources( + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c" ) @@ -3013,7 +3028,7 @@ if(SDL_GPU) set(SDL_GPU_D3D11 1) set(HAVE_SDL_GPU TRUE) endif() - if(SDL_RENDER_D3D12) + if(WINDOWS) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/gpu/d3d12/*.c") set(SDL_GPU_D3D12 1) set(HAVE_SDL_GPU TRUE) @@ -3284,7 +3299,7 @@ else() endif() if(ANDROID) - sdl_include_directories(PRIVATE SYSTEM "${ANDROID_NDK}/sources/android/cpufeatures") + sdl_include_directories(PRIVATE SYSTEM "${CMAKE_ANDROID_NDK}/sources/android/cpufeatures") endif() if(APPLE) diff --git a/libs/SDL3/INSTALL.md b/libs/SDL3/INSTALL.md index b33c211..f2db667 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/REVISION.txt b/libs/SDL3/REVISION.txt index 0c3fdeb..b520912 100644 --- a/libs/SDL3/REVISION.txt +++ b/libs/SDL3/REVISION.txt @@ -1 +1 @@ -release-3.2.4-0-gb5c3eab6b +release-3.2.20-0-g96292a5b4 diff --git a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj index 7376967..96ca804 100644 --- a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj +++ b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj @@ -168,8 +168,8 @@ - call $(ProjectDir)..\..\src\render\direct3d12\compile_shaders_xbox.bat $(ProjectDir)..\ - call $(ProjectDir)..\..\src\gpu\d3d12\compile_shaders_xbox.bat $(ProjectDir)..\ + call "$(ProjectDir)..\..\src\render\direct3d12\compile_shaders_xbox.bat" "$(ProjectDir)..\" + call "$(ProjectDir)..\..\src\gpu\d3d12\compile_shaders_xbox.bat" "$(ProjectDir)..\" @@ -425,6 +425,7 @@ + @@ -441,6 +442,7 @@ + @@ -594,6 +596,7 @@ + @@ -640,6 +643,7 @@ + @@ -673,6 +677,7 @@ + @@ -868,6 +873,7 @@ + diff --git a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters index 247bbdc..f760877 100644 --- a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -27,6 +27,7 @@ + @@ -38,6 +39,7 @@ + @@ -175,6 +177,7 @@ + @@ -314,6 +317,7 @@ + @@ -330,6 +334,7 @@ + @@ -434,6 +439,7 @@ + diff --git a/libs/SDL3/VisualC/SDL/SDL.vcxproj b/libs/SDL3/VisualC/SDL/SDL.vcxproj index 79d5c46..a15978a 100644 --- a/libs/SDL3/VisualC/SDL/SDL.vcxproj +++ b/libs/SDL3/VisualC/SDL/SDL.vcxproj @@ -336,6 +336,7 @@ + @@ -353,6 +354,7 @@ + @@ -492,6 +494,7 @@ + @@ -538,6 +541,7 @@ + @@ -555,6 +559,7 @@ + @@ -703,6 +708,7 @@ + diff --git a/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters b/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters index 176bea6..d653ee0 100644 --- a/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters +++ b/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters @@ -486,6 +486,12 @@ audio + + core\windows + + + core\windows + core\windows @@ -528,6 +534,9 @@ events + + events + events @@ -666,6 +675,9 @@ video + + video + video @@ -1028,6 +1040,9 @@ core + + core\windows + core\windows @@ -1064,6 +1079,9 @@ events + + events + events @@ -1283,6 +1301,9 @@ video + + video + video diff --git a/libs/SDL3/WhatsNew.txt b/libs/SDL3/WhatsNew.txt index 22b1a0f..0d10ee6 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 13d9c51..5e724fc 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.4 + 3.2.20 CFBundleSignature SDLX CFBundleVersion - 3.2.4 + 3.2.20 diff --git a/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj index 2d97149..ae8a61b 100644 --- a/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -512,10 +512,16 @@ F3D46B122D20625800D9CBDF /* SDL_egl.h in Headers */ = {isa = PBXBuildFile; fileRef = F3D46A8E2D20625800D9CBDF /* SDL_egl.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3D46B132D20625800D9CBDF /* SDL_filesystem.h in Headers */ = {isa = PBXBuildFile; fileRef = F3D46A922D20625800D9CBDF /* SDL_filesystem.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3D60A8328C16A1900788A3A /* SDL_hidapi_wii.c in Sources */ = {isa = PBXBuildFile; fileRef = F3D60A8228C16A1800788A3A /* SDL_hidapi_wii.c */; }; + F3D8BDFC2D6D2C7000B22FA1 /* SDL_eventwatch_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3D8BDFB2D6D2C7000B22FA1 /* SDL_eventwatch_c.h */; }; + F3D8BDFD2D6D2C7000B22FA1 /* SDL_eventwatch.c in Sources */ = {isa = PBXBuildFile; fileRef = F3D8BDFA2D6D2C7000B22FA1 /* SDL_eventwatch.c */; }; F3DDCC562AFD42B600B0842B /* SDL_clipboard_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC4D2AFD42B500B0842B /* SDL_clipboard_c.h */; }; F3DDCC5B2AFD42B600B0842B /* SDL_video_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC522AFD42B600B0842B /* SDL_video_c.h */; }; F3DDCC5D2AFD42B600B0842B /* SDL_rect_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */; }; F3E5A6EB2AD5E0E600293D83 /* SDL_properties.c in Sources */ = {isa = PBXBuildFile; fileRef = F3E5A6EA2AD5E0E600293D83 /* SDL_properties.c */; }; + F3EFA5ED2D5AB97300BCF22F /* SDL_stb_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3EFA5EA2D5AB97300BCF22F /* SDL_stb_c.h */; }; + F3EFA5EE2D5AB97300BCF22F /* stb_image.h in Headers */ = {isa = PBXBuildFile; fileRef = F3EFA5EC2D5AB97300BCF22F /* stb_image.h */; }; + F3EFA5EF2D5AB97300BCF22F /* SDL_surface_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3EFA5EB2D5AB97300BCF22F /* SDL_surface_c.h */; }; + F3EFA5F02D5AB97300BCF22F /* SDL_stb.c in Sources */ = {isa = PBXBuildFile; fileRef = F3EFA5E92D5AB97300BCF22F /* SDL_stb.c */; }; F3F07D5A269640160074468B /* SDL_hidapi_luna.c in Sources */ = {isa = PBXBuildFile; fileRef = F3F07D59269640160074468B /* SDL_hidapi_luna.c */; }; F3F15D7F2D011912007AE210 /* SDL_dialog.c in Sources */ = {isa = PBXBuildFile; fileRef = F3F15D7D2D011912007AE210 /* SDL_dialog.c */; }; F3F15D802D011912007AE210 /* SDL_dialog_utils.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F15D7E2D011912007AE210 /* SDL_dialog_utils.h */; }; @@ -1075,10 +1081,16 @@ F3D46AC82D20625800D9CBDF /* SDL_video.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_video.h; sourceTree = ""; }; F3D46AC92D20625800D9CBDF /* SDL_vulkan.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_vulkan.h; sourceTree = ""; }; F3D60A8228C16A1800788A3A /* SDL_hidapi_wii.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_wii.c; sourceTree = ""; }; + F3D8BDFA2D6D2C7000B22FA1 /* SDL_eventwatch.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_eventwatch.c; sourceTree = ""; }; + F3D8BDFB2D6D2C7000B22FA1 /* SDL_eventwatch_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_eventwatch_c.h; sourceTree = ""; }; F3DDCC4D2AFD42B500B0842B /* SDL_clipboard_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_clipboard_c.h; sourceTree = ""; }; F3DDCC522AFD42B600B0842B /* SDL_video_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_video_c.h; sourceTree = ""; }; F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_rect_impl.h; sourceTree = ""; }; F3E5A6EA2AD5E0E600293D83 /* SDL_properties.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_properties.c; sourceTree = ""; }; + F3EFA5E92D5AB97300BCF22F /* SDL_stb.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_stb.c; sourceTree = ""; }; + F3EFA5EA2D5AB97300BCF22F /* SDL_stb_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_stb_c.h; sourceTree = ""; }; + F3EFA5EB2D5AB97300BCF22F /* SDL_surface_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_surface_c.h; sourceTree = ""; }; + F3EFA5EC2D5AB97300BCF22F /* stb_image.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = stb_image.h; sourceTree = ""; }; F3F07D59269640160074468B /* SDL_hidapi_luna.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_luna.c; sourceTree = ""; }; F3F15D7C2D011912007AE210 /* SDL_dialog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_dialog.h; sourceTree = ""; }; F3F15D7D2D011912007AE210 /* SDL_dialog.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_dialog.c; sourceTree = ""; }; @@ -1586,43 +1598,47 @@ A7D8A60523E2513D00DCD162 /* dummy */, A7D8A72123E2513E00DCD162 /* khronos */, A7D8A5EC23E2513D00DCD162 /* offscreen */, - A7D8A61823E2513D00DCD162 /* uikit */, - A7D8A76C23E2513E00DCD162 /* yuv2rgb */, + A7D8A76B23E2513E00DCD162 /* SDL_blit.h */, + A7D8A64C23E2513D00DCD162 /* SDL_blit.c */, A7D8A66223E2513E00DCD162 /* SDL_blit_0.c */, A7D8A6FA23E2513E00DCD162 /* SDL_blit_1.c */, A7D8A66423E2513E00DCD162 /* SDL_blit_A.c */, - A7D8A63F23E2513D00DCD162 /* SDL_blit_auto.c */, A7D8A73F23E2513E00DCD162 /* SDL_blit_auto.h */, - A7D8A61623E2513D00DCD162 /* SDL_blit_copy.c */, + A7D8A63F23E2513D00DCD162 /* SDL_blit_auto.c */, A7D8A76623E2513E00DCD162 /* SDL_blit_copy.h */, + A7D8A61623E2513D00DCD162 /* SDL_blit_copy.c */, A7D8A64223E2513D00DCD162 /* SDL_blit_N.c */, - A7D8A60223E2513D00DCD162 /* SDL_blit_slow.c */, A7D8A66323E2513E00DCD162 /* SDL_blit_slow.h */, - A7D8A64C23E2513D00DCD162 /* SDL_blit.c */, - A7D8A76B23E2513E00DCD162 /* SDL_blit.h */, + A7D8A60223E2513D00DCD162 /* SDL_blit_slow.c */, A7D8A77323E2513E00DCD162 /* SDL_bmp.c */, - F3DDCC4D2AFD42B500B0842B /* SDL_clipboard_c.h */, A7D8A67B23E2513E00DCD162 /* SDL_clipboard.c */, - A7D8A60423E2513D00DCD162 /* SDL_egl_c.h */, + F3DDCC4D2AFD42B500B0842B /* SDL_clipboard_c.h */, A7D8A6B623E2513E00DCD162 /* SDL_egl.c */, + A7D8A60423E2513D00DCD162 /* SDL_egl_c.h */, A7D8A76823E2513E00DCD162 /* SDL_fillrect.c */, - A7D8A74023E2513E00DCD162 /* SDL_pixels_c.h */, A7D8A64D23E2513D00DCD162 /* SDL_pixels.c */, + A7D8A74023E2513E00DCD162 /* SDL_pixels_c.h */, + A7D8A63423E2513D00DCD162 /* SDL_rect.c */, A7D8A60C23E2513D00DCD162 /* SDL_rect_c.h */, F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */, - A7D8A63423E2513D00DCD162 /* SDL_rect.c */, - A7D8A76723E2513E00DCD162 /* SDL_RLEaccel_c.h */, A7D8A61523E2513D00DCD162 /* SDL_RLEaccel.c */, + A7D8A76723E2513E00DCD162 /* SDL_RLEaccel_c.h */, + F3EFA5E92D5AB97300BCF22F /* SDL_stb.c */, + F3EFA5EA2D5AB97300BCF22F /* SDL_stb_c.h */, A7D8A60323E2513D00DCD162 /* SDL_stretch.c */, A7D8A61423E2513D00DCD162 /* SDL_surface.c */, + F3EFA5EB2D5AB97300BCF22F /* SDL_surface_c.h */, A7D8A61723E2513D00DCD162 /* SDL_sysvideo.h */, + A7D8A60E23E2513D00DCD162 /* SDL_video.c */, F3DDCC522AFD42B600B0842B /* SDL_video_c.h */, E4F7981F2AD8D87F00669F54 /* SDL_video_unsupported.c */, - A7D8A60E23E2513D00DCD162 /* SDL_video.c */, A7D8A63E23E2513D00DCD162 /* SDL_vulkan_internal.h */, A7D8A64023E2513D00DCD162 /* SDL_vulkan_utils.c */, - A7D8A76A23E2513E00DCD162 /* SDL_yuv_c.h */, A7D8A67C23E2513E00DCD162 /* SDL_yuv.c */, + A7D8A76A23E2513E00DCD162 /* SDL_yuv_c.h */, + F3EFA5EC2D5AB97300BCF22F /* stb_image.h */, + A7D8A61823E2513D00DCD162 /* uikit */, + A7D8A76C23E2513E00DCD162 /* yuv2rgb */, ); path = video; sourceTree = ""; @@ -2197,29 +2213,31 @@ A7D8A93623E2514000DCD162 /* scancodes_linux.h */, A7D8A92C23E2514000DCD162 /* scancodes_windows.h */, A7D8A94123E2514000DCD162 /* scancodes_xfree86.h */, - F3C2CB202C5DDDB2004D7998 /* SDL_categories_c.h */, F3C2CB212C5DDDB2004D7998 /* SDL_categories.c */, - A7D8A93923E2514000DCD162 /* SDL_clipboardevents_c.h */, + F3C2CB202C5DDDB2004D7998 /* SDL_categories_c.h */, A7D8A93A23E2514000DCD162 /* SDL_clipboardevents.c */, - A7D8A93123E2514000DCD162 /* SDL_displayevents_c.h */, + A7D8A93923E2514000DCD162 /* SDL_clipboardevents_c.h */, A7D8A92D23E2514000DCD162 /* SDL_displayevents.c */, - A7D8A92E23E2514000DCD162 /* SDL_dropevents_c.h */, + A7D8A93123E2514000DCD162 /* SDL_displayevents_c.h */, A7D8A93B23E2514000DCD162 /* SDL_dropevents.c */, - A7D8A94223E2514000DCD162 /* SDL_events_c.h */, + A7D8A92E23E2514000DCD162 /* SDL_dropevents_c.h */, A7D8A93523E2514000DCD162 /* SDL_events.c */, - A7D8A93D23E2514000DCD162 /* SDL_keyboard_c.h */, + A7D8A94223E2514000DCD162 /* SDL_events_c.h */, + F3D8BDFA2D6D2C7000B22FA1 /* SDL_eventwatch.c */, + F3D8BDFB2D6D2C7000B22FA1 /* SDL_eventwatch_c.h */, A7D8A93823E2514000DCD162 /* SDL_keyboard.c */, - F31013C62C24E98200FBE946 /* SDL_keymap_c.h */, + A7D8A93D23E2514000DCD162 /* SDL_keyboard_c.h */, F31013C52C24E98200FBE946 /* SDL_keymap.c */, - A7D8A92B23E2514000DCD162 /* SDL_mouse_c.h */, + F31013C62C24E98200FBE946 /* SDL_keymap_c.h */, A7D8A92A23E2514000DCD162 /* SDL_mouse.c */, - 63134A232A7902FD0021E9A6 /* SDL_pen_c.h */, + A7D8A92B23E2514000DCD162 /* SDL_mouse_c.h */, 63134A242A7902FD0021E9A6 /* SDL_pen.c */, + 63134A232A7902FD0021E9A6 /* SDL_pen_c.h */, A7D8A93C23E2514000DCD162 /* SDL_quit.c */, - A7D8A93723E2514000DCD162 /* SDL_touch_c.h */, A7D8A93E23E2514000DCD162 /* SDL_touch.c */, - A7D8A94323E2514000DCD162 /* SDL_windowevents_c.h */, + A7D8A93723E2514000DCD162 /* SDL_touch_c.h */, A7D8A92F23E2514000DCD162 /* SDL_windowevents.c */, + A7D8A94323E2514000DCD162 /* SDL_windowevents_c.h */, ); path = events; sourceTree = ""; @@ -2458,6 +2476,9 @@ A7D8BB6F23E2514500DCD162 /* SDL_clipboardevents_c.h in Headers */, A7D8AECA23E2514100DCD162 /* SDL_cocoaclipboard.h in Headers */, A7D8AF1223E2514100DCD162 /* SDL_cocoaevents.h in Headers */, + F3EFA5ED2D5AB97300BCF22F /* SDL_stb_c.h in Headers */, + F3EFA5EE2D5AB97300BCF22F /* stb_image.h in Headers */, + F3EFA5EF2D5AB97300BCF22F /* SDL_surface_c.h in Headers */, A7D8AE8E23E2514100DCD162 /* SDL_cocoakeyboard.h in Headers */, A7D8AF0623E2514100DCD162 /* SDL_cocoamessagebox.h in Headers */, A7D8AEB223E2514100DCD162 /* SDL_cocoametalview.h in Headers */, @@ -2692,6 +2713,7 @@ A7D8B3D423E2514300DCD162 /* yuv_rgb.h in Headers */, F3FA5A252B59ACE000FEAD97 /* yuv_rgb_common.h in Headers */, F3FA5A1D2B59ACE000FEAD97 /* yuv_rgb_internal.h in Headers */, + F3D8BDFC2D6D2C7000B22FA1 /* SDL_eventwatch_c.h in Headers */, F3FA5A242B59ACE000FEAD97 /* yuv_rgb_lsx.h in Headers */, F3FA5A1E2B59ACE000FEAD97 /* yuv_rgb_lsx_func.h in Headers */, F3FA5A1F2B59ACE000FEAD97 /* yuv_rgb_sse.h in Headers */, @@ -2950,6 +2972,8 @@ 566E26CF246274CC00718109 /* SDL_syslocale.m in Sources */, A7D8AFC023E2514200DCD162 /* SDL_egl.c in Sources */, A7D8AC3323E2514100DCD162 /* SDL_RLEaccel.c in Sources */, + F3D8BDFD2D6D2C7000B22FA1 /* SDL_eventwatch.c in Sources */, + F3EFA5F02D5AB97300BCF22F /* SDL_stb.c in Sources */, A7D8BBB123E2514500DCD162 /* SDL_assert.c in Sources */, A7D8B3DA23E2514300DCD162 /* SDL_bmp.c in Sources */, A7D8B96E23E2514400DCD162 /* SDL_stdlib.c in Sources */, @@ -3062,7 +3086,7 @@ CLANG_ENABLE_OBJC_ARC = YES; DEPLOYMENT_POSTPROCESSING = YES; DYLIB_COMPATIBILITY_VERSION = 201.0.0; - DYLIB_CURRENT_VERSION = 201.4.0; + DYLIB_CURRENT_VERSION = 201.20.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_ALTIVEC_EXTENSIONS = YES; @@ -3097,7 +3121,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 3.2.4; + MARKETING_VERSION = 3.2.20; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3; PRODUCT_NAME = SDL3; @@ -3126,7 +3150,7 @@ CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; DYLIB_COMPATIBILITY_VERSION = 201.0.0; - DYLIB_CURRENT_VERSION = 201.4.0; + DYLIB_CURRENT_VERSION = 201.20.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -3158,7 +3182,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 3.2.4; + MARKETING_VERSION = 3.2.20; 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 92f6585..475a7cc 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.4 +Title SDL 3.2.20 Version 1 Description SDL Library for macOS (http://www.libsdl.org) DefaultLocation /Library/Frameworks diff --git a/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3Config.cmake b/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3Config.cmake index 9d29aae..b60ef0a 100644 --- a/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3Config.cmake +++ b/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3Config.cmake @@ -92,7 +92,7 @@ if(NOT TARGET SDL3::Headers) add_library(SDL3::Headers INTERFACE IMPORTED) set_target_properties(SDL3::Headers PROPERTIES - INTERFACE_COMPILE_OPTIONS "SHELL:-F \"${_sdl3_framework_parent_path}\"" + INTERFACE_COMPILE_OPTIONS "-F${_sdl3_framework_parent_path}" ) endif() set(SDL3_Headers_FOUND TRUE) diff --git a/libs/SDL3/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj b/libs/SDL3/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj index bfeae4e..3a2e59d 100644 --- a/libs/SDL3/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj +++ b/libs/SDL3/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj @@ -167,6 +167,9 @@ F399C6512A7892D800C86979 /* testautomation_intrinsics.c in Sources */ = {isa = PBXBuildFile; fileRef = F399C6502A7892D800C86979 /* testautomation_intrinsics.c */; }; F399C6522A7892D800C86979 /* testautomation_intrinsics.c in Sources */ = {isa = PBXBuildFile; fileRef = F399C6502A7892D800C86979 /* testautomation_intrinsics.c */; }; F399C6552A78933100C86979 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F399C6542A78933000C86979 /* Cocoa.framework */; }; + F3B7FD642D73FC630086D1D0 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; + F3B7FD662D73FC630086D1D0 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + F3B7FD6C2D73FC9E0086D1D0 /* testpen.c in Sources */ = {isa = PBXBuildFile; fileRef = F3B7FD6B2D73FC9E0086D1D0 /* testpen.c */; }; F3C17C7728E40BC800E1A26D /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; F3C17C7928E40C6E00E1A26D /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; F3C17C7B28E40D4E00E1A26D /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; @@ -717,6 +720,17 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + F3B7FD652D73FC630086D1D0 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + F3B7FD662D73FC630086D1D0 /* SDL3.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; F3CB568B2A7895F800766177 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1356,6 +1370,8 @@ F399C6492A78929400C86979 /* gamepadutils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gamepadutils.c; sourceTree = ""; }; F399C6502A7892D800C86979 /* testautomation_intrinsics.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_intrinsics.c; sourceTree = ""; }; F399C6542A78933000C86979 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + F3B7FD6A2D73FC630086D1D0 /* testpen.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testpen.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F3B7FD6B2D73FC9E0086D1D0 /* testpen.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = testpen.c; sourceTree = ""; }; F3C17C6A28E3FD4400E1A26D /* config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = config.xcconfig; sourceTree = ""; }; F3C17C7328E40ADE00E1A26D /* testutils.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = testutils.c; sourceTree = ""; }; F3C17CD628E416AC00E1A26D /* testgeometry.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testgeometry.c; sourceTree = ""; }; @@ -1732,6 +1748,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F3B7FD632D73FC630086D1D0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F3B7FD642D73FC630086D1D0 /* SDL3.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F3C17CD928E416CF00E1A26D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1789,6 +1813,7 @@ 083E4872006D84C97F000001 /* loopwave.c */, 0017958F1074216E00F5D044 /* testatomic.c */, 001795B01074222D00F5D044 /* testaudioinfo.c */, + F35E56CC2983130F00A43A5F /* testautomation.c */, F35E56C42983130D00A43A5F /* testautomation_audio.c */, F35E56BC2983130B00A43A5F /* testautomation_clipboard.c */, F35E56BB2983130B00A43A5F /* testautomation_events.c */, @@ -1815,7 +1840,6 @@ A1A8594B2BC72FC20045DD6C /* testautomation_time.c */, F35E56BD2983130B00A43A5F /* testautomation_timer.c */, F35E56C12983130C00A43A5F /* testautomation_video.c */, - F35E56CC2983130F00A43A5F /* testautomation.c */, F36C342C2C0F869B00991150 /* testcamera.c */, BBFC088E164C6820003E6A99 /* testcontroller.c */, 001797711074320D00F5D044 /* testdraw.c */, @@ -1837,11 +1861,12 @@ 092D6D75FFB313BB7F000001 /* testlock.c */, DB166CBD16A1C74100A1396C /* testmessage.c */, 001798151074359B00F5D044 /* testmultiaudio.c */, - 0017985A107436ED00F5D044 /* testnative.c */, 0017985B107436ED00F5D044 /* testnative.h */, + 0017985A107436ED00F5D044 /* testnative.c */, 0017985C107436ED00F5D044 /* testnativecocoa.m */, 00179872107438D000F5D044 /* testnativex11.c */, 002F345209CA201C00EBEB88 /* testoverlay.c */, + F3B7FD6B2D73FC9E0086D1D0 /* testpen.c */, 002F346F09CA20A600EBEB88 /* testplatform.c */, 001798B910743A4900F5D044 /* testpower.c */, DB166CBF16A1C74100A1396C /* testrelative.c */, @@ -1918,6 +1943,7 @@ F3C17CDC28E416CF00E1A26D /* testgeometry.app */, F35E56AA298312CB00A43A5F /* testautomation.app */, F36C34272C0F85DB00991150 /* testcamera.app */, + F3B7FD6A2D73FC630086D1D0 /* testpen.app */, ); name = Products; sourceTree = ""; @@ -2756,6 +2782,23 @@ productReference = F36C34272C0F85DB00991150 /* testcamera.app */; productType = "com.apple.product-type.application"; }; + F3B7FD602D73FC630086D1D0 /* testpen */ = { + isa = PBXNativeTarget; + buildConfigurationList = F3B7FD672D73FC630086D1D0 /* Build configuration list for PBXNativeTarget "testpen" */; + buildPhases = ( + F3B7FD612D73FC630086D1D0 /* Sources */, + F3B7FD632D73FC630086D1D0 /* Frameworks */, + F3B7FD652D73FC630086D1D0 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = testpen; + productName = testalpha; + productReference = F3B7FD6A2D73FC630086D1D0 /* testpen.app */; + productType = "com.apple.product-type.application"; + }; F3C17CDB28E416CF00E1A26D /* testgeometry */ = { isa = PBXNativeTarget; buildConfigurationList = F3C17CE828E416D000E1A26D /* Build configuration list for PBXNativeTarget "testgeometry" */; @@ -2972,6 +3015,7 @@ 001798781074392D00F5D044 /* testnative */, 002F343C09CA1FB300EBEB88 /* testoverlay */, 002F345909CA204F00EBEB88 /* testplatform */, + F3B7FD602D73FC630086D1D0 /* testpen */, 0017989D107439DF00F5D044 /* testpower */, DB166DDC16A1D50C00A1396C /* testrelative */, DB166DF316A1D57C00A1396C /* testrendercopyex */, @@ -3455,6 +3499,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F3B7FD612D73FC630086D1D0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F3B7FD6C2D73FC9E0086D1D0 /* testpen.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F3C17CD828E416CF00E1A26D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -4704,6 +4756,26 @@ }; name = Release; }; + F3B7FD682D73FC630086D1D0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + }; + name = Debug; + }; + F3B7FD692D73FC630086D1D0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + }; + name = Release; + }; F3C17CE928E416D000E1A26D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -5159,6 +5231,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; + F3B7FD672D73FC630086D1D0 /* Build configuration list for PBXNativeTarget "testpen" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F3B7FD682D73FC630086D1D0 /* Debug */, + F3B7FD692D73FC630086D1D0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; F3C17CE828E416D000E1A26D /* Build configuration list for PBXNativeTarget "testgeometry" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/libs/SDL3/android-project/app/build.gradle b/libs/SDL3/android-project/app/build.gradle index 8946de6..f44cf26 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", "-DAPP_SUPPORT_FLEXIBLE_PAGE_SIZES=true" // 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/jni/Application.mk b/libs/SDL3/android-project/app/jni/Application.mk index 023bc20..80b73fd 100644 --- a/libs/SDL3/android-project/app/jni/Application.mk +++ b/libs/SDL3/android-project/app/jni/Application.mk @@ -7,4 +7,7 @@ APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 # Min runtime API level -APP_PLATFORM=android-16 +APP_PLATFORM=android-21 + +# https://developer.android.com/guide/practices/page-sizes#update-packaging +APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true \ No newline at end of file diff --git a/libs/SDL3/android-project/app/jni/src/CMakeLists.txt b/libs/SDL3/android-project/app/jni/src/CMakeLists.txt index 41a82f2..df0a4d0 100644 --- a/libs/SDL3/android-project/app/jni/src/CMakeLists.txt +++ b/libs/SDL3/android-project/app/jni/src/CMakeLists.txt @@ -26,4 +26,9 @@ endif() add_library(main SHARED YourSourceHere.c ) + +#https://developer.android.com/guide/practices/page-sizes#update-packaging +target_link_options(main PRIVATE "-Wl,-z,max-page-size=16384") +target_link_options(main PRIVATE "-Wl,-z,common-page-size=16384") + target_link_libraries(main PRIVATE SDL3::SDL3) diff --git a/libs/SDL3/android-project/app/proguard-rules.pro b/libs/SDL3/android-project/app/proguard-rules.pro index 1eeb90e..5f8ee6a 100644 --- a/libs/SDL3/android-project/app/proguard-rules.pro +++ b/libs/SDL3/android-project/app/proguard-rules.pro @@ -51,6 +51,8 @@ boolean supportsRelativeMouse(); int openFileDescriptor(java.lang.String, java.lang.String); boolean showFileDialog(java.lang.String[], boolean, boolean, int); + java.lang.String getPreferredLocales(); + java.lang.String formatLocale(java.util.Locale); } -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager { 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 0684e0a..0aa1ef3 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 @@ -23,6 +23,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.LocaleList; import android.os.Message; import android.os.ParcelFileDescriptor; import android.util.DisplayMetrics; @@ -60,7 +61,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 = 4; + private static final int SDL_MICRO_VERSION = 20; /* // Display InputType.SOURCE/CLASS of events and devices // @@ -2116,6 +2117,44 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh int requestCode; boolean multipleChoice; } + + /** + * This method is called by SDL using JNI. + */ + public static String getPreferredLocales() { + String result = ""; + if (Build.VERSION.SDK_INT >= 24 /* Android 7 (N) */) { + LocaleList locales = LocaleList.getAdjustedDefault(); + for (int i = 0; i < locales.size(); i++) { + if (i != 0) result += ","; + result += formatLocale(locales.get(i)); + } + } else if (mCurrentLocale != null) { + result = formatLocale(mCurrentLocale); + } + return result; + } + + public static String formatLocale(Locale locale) { + String result = ""; + String lang = ""; + if (locale.getLanguage() == "in") { + // Indonesian is "id" according to ISO 639.2, but on Android is "in" because of Java backwards compatibility + lang = "id"; + } else if (locale.getLanguage() == "") { + // Make sure language is never empty + lang = "und"; + } else { + lang = locale.getLanguage(); + } + + if (locale.getCountry() == "") { + result = lang; + } else { + result = lang + "_" + locale.getCountry(); + } + return result; + } } /** diff --git a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java index 1658821..080501c 100644 --- a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java +++ b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java @@ -276,7 +276,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, int buttonState = (event.getButtonState() >> 4) | (1 << (toolType == MotionEvent.TOOL_TYPE_STYLUS ? 0 : 30)); SDLActivity.onNativePen(pointerId, buttonState, action, x, y, p); - } else if (toolType == MotionEvent.TOOL_TYPE_FINGER) { + } else { // MotionEvent.TOOL_TYPE_FINGER or MotionEvent.TOOL_TYPE_UNKNOWN pointerId = event.getPointerId(i); x = getNormalizedX(event.getX(i)); y = getNormalizedY(event.getY(i)); diff --git a/libs/SDL3/build-scripts/androidbuildlibs.sh b/libs/SDL3/build-scripts/androidbuildlibs.sh index a903f36..1004a98 100644 --- a/libs/SDL3/build-scripts/androidbuildlibs.sh +++ b/libs/SDL3/build-scripts/androidbuildlibs.sh @@ -30,6 +30,7 @@ abi="arm64-v8a" # "armeabi-v7a arm64-v8a x86 x86_64" obj= lib= ndk_args= +flexpage=true # Allow an external caller to specify locations and platform. while [ $# -gt 0 ]; do @@ -42,6 +43,8 @@ while [ $# -gt 0 ]; do platform=${arg#APP_PLATFORM=} elif [ "${arg:0:8}" == "APP_ABI=" ]; then abi=${arg#APP_ABI=} + elif [ "${arg:0:32}" == "APP_SUPPORT_FLEXIBLE_PAGE_SIZES=" ]; then + flexpage=${arg#APP_SUPPORT_FLEXIBLE_PAGE_SIZES=} else ndk_args="$ndk_args $arg" fi @@ -67,6 +70,9 @@ done # APP_* variables set in the environment here will not be seen by the # ndk-build makefile segments that use them, e.g., default-application.mk. # For consistency, pass all values on the command line. +# +# Add support for Google Play 16 KB Page size requirement: +# https://developer.android.com/guide/practices/page-sizes#ndk-build ndk-build \ NDK_PROJECT_PATH=null \ NDK_OUT=$obj \ @@ -75,4 +81,5 @@ ndk-build \ APP_ABI="$abi" \ APP_PLATFORM="$platform" \ APP_MODULES="SDL3" \ + APP_SUPPORT_FLEXIBLE_PAGE_SIZES="$flexpage" \ $ndk_args diff --git a/libs/SDL3/build-scripts/check_stdlib_usage.py b/libs/SDL3/build-scripts/check_stdlib_usage.py index 9ab65e0..0994130 100644 --- a/libs/SDL3/build-scripts/check_stdlib_usage.py +++ b/libs/SDL3/build-scripts/check_stdlib_usage.py @@ -161,6 +161,7 @@ def find_symbols_in_file(file: pathlib.Path) -> int: "src/libm", "src/hidapi", "src/video/khronos", + "src/video/stb_image.h", "include/SDL3", "build-scripts/gen_audio_resampler_filter.c", "build-scripts/gen_audio_channel_conversion.c", diff --git a/libs/SDL3/build-scripts/wikiheaders.pl b/libs/SDL3/build-scripts/wikiheaders.pl index 9859149..d4205b8 100644 --- a/libs/SDL3/build-scripts/wikiheaders.pl +++ b/libs/SDL3/build-scripts/wikiheaders.pl @@ -424,7 +424,11 @@ sub dewikify_chunk { $str .= "\n```$codelang\n$code\n```\n"; } } elsif ($dewikify_mode eq 'manpage') { - $str =~ s/\./\\[char46]/gms; # make sure these can't become control codes. + # make sure these can't become part of roff syntax. + $str =~ s/\./\\[char46]/gms; + $str =~ s/"/\\(dq/gms; + $str =~ s/'/\\(aq/gms; + if ($wikitype eq 'mediawiki') { # Dump obvious wikilinks. if (defined $apiprefixregex) { @@ -449,33 +453,52 @@ sub dewikify_chunk { # bullets $str =~ s/^\* /\n\\\(bu /gm; } elsif ($wikitype eq 'md') { + # bullets + $str =~ s/^\- /\n\\(bu /gm; + # merge paragraphs + $str =~ s/^[ \t]+//gm; + $str =~ s/([^\-\n])\n([^\-\n])/$1 $2/g; + $str =~ s/\n\n/\n.PP\n/g; + # Dump obvious wikilinks. if (defined $apiprefixregex) { - $str =~ s/\[(\`?$apiprefixregex[a-zA-Z0-9_]+\`?)\]\($apiprefixregex[a-zA-Z0-9_]+\)/\n.BR $1\n/gms; + my $apr = $apiprefixregex; + if(!($apr =~ /\A\(.*\)\Z/s)) { + # we're relying on the apiprefixregex having a capturing group. + $apr = "(" . $apr . ")"; + } + $str =~ s/(\S*?)\[\`?($apr[a-zA-Z0-9_]+)\`?\]\($apr[a-zA-Z0-9_]+\)(\S*)\s*/\n.BR "" "$1" "$2" "$5"\n/gm; + # handle cases like "[x](x), [y](y), [z](z)" being separated. + while($str =~ s/(\.BR[^\n]*)\n\n\.BR/$1\n.BR/gm) {} } # links $str =~ s/\[(.*?)]\((https?\:\/\/.*?)\)/\n.URL "$2" "$1"\n/g; # is also popular. :/ - $str =~ s/\s*\`(.*?)\`\s*/\n.BR $1\n/gms; + $str =~ s/\s*(\S*?)\`([^\n]*?)\`(\S*)\s*/\n.BR "" "$1" "$2" "$3"\n/gms; # bold+italic (this looks bad, just make it bold). - $str =~ s/\s*\*\*\*(.*?)\*\*\*\s*/\n.B $1\n/gms; + $str =~ s/\s*(\S*?)\*\*\*([^\n]*?)\*\*\*(\S*)\s*/\n.BR "" "$1" "$2" "$3"\n/gms; # bold - $str =~ s/\s*\*\*(.*?)\*\*\s*/\n.B $1\n/gms; + $str =~ s/\s*(\S*?)\*\*([^\n]*?)\*\*(\S*)\s*/\n.BR "" "$1" "$2" "$3"\n/gms; # italic - $str =~ s/\s*\*(.*?)\*\s*/\n.I $1\n/gms; - - # bullets - $str =~ s/^\- /\n\\\(bu /gm; + $str =~ s/\s*(\S*?)\*([^\n]*?)\*(\S*)\s*/\n.IR "" "$1" "$2" "$3"\n/gms; } + # cleanup unnecessary quotes + $str =~ s/(\.[IB]R?)(.*?) ""\n/$1$2\n/gm; + $str =~ s/(\.[IB]R?) "" ""(.*?)\n/$1$2\n/gm; + $str =~ s/"(\S+)"/$1/gm; + # cleanup unnecessary whitespace + $str =~ s/ +\n/\n/gm; + if (defined $code) { $code =~ s/\A\n+//gms; $code =~ s/\n+\Z//gms; + $code =~ s/\\/\\(rs/gms; if ($dewikify_manpage_code_indent) { $str .= "\n.IP\n" } else { @@ -580,7 +603,7 @@ sub dewikify { $retval .= dewikify_chunk($wikitype, $1, $2, $3); } } elsif ($wikitype eq 'md') { - while ($str =~ s/\A(.*?)\n```(.*?)\n(.*?)\n```\n//ms) { + while ($str =~ s/\A(.*?)\n?```(.*?)\n(.*?)\n```\n//ms) { $retval .= dewikify_chunk($wikitype, $1, $2, $3); } } @@ -2765,7 +2788,6 @@ __EOF__ my $wikitype = $wikitypes{$sym}; my $sectionsref = $wikisyms{$sym}; my $remarks = $sectionsref->{'Remarks'}; - my $params = $sectionsref->{'Function Parameters'}; my $returns = $sectionsref->{'Return Value'}; my $version = $sectionsref->{'Version'}; my $threadsafety = $sectionsref->{'Thread Safety'}; @@ -2773,6 +2795,23 @@ __EOF__ my $examples = $sectionsref->{'Code Examples'}; my $deprecated = $sectionsref->{'Deprecated'}; my $headerfile = $manpageheaderfiletext; + + my $params = undef; + + if ($symtype == -1) { # category documentation block. + # nothing to be done here. + } elsif (($symtype == 1) || (($symtype == 5))) { # we'll assume a typedef (5) with a \param is a function pointer typedef. + $params = $sectionsref->{'Function Parameters'}; + } elsif ($symtype == 2) { + $params = $sectionsref->{'Macro Parameters'}; + } elsif ($symtype == 3) { + $params = $sectionsref->{'Fields'}; + } elsif ($symtype == 4) { + $params = $sectionsref->{'Values'}; + } else { + die("Unexpected symtype $symtype"); + } + $headerfile =~ s/\%fname\%/$headersymslocation{$sym}/g; $headerfile .= "\n"; @@ -2839,18 +2878,22 @@ __EOF__ $str .= dewikify($wikitype, $deprecated) . "\n"; } + my $incfile = $mainincludefname; if (defined $headerfile) { - $str .= ".SH HEADER FILE\n"; - $str .= dewikify($wikitype, $headerfile) . "\n"; + if($headerfile =~ /Defined in (.*)/) { + $incfile = $1; + } } $str .= ".SH SYNOPSIS\n"; $str .= ".nf\n"; - $str .= ".B #include \\(dq$mainincludefname\\(dq\n"; + $str .= ".B #include <$incfile>\n"; $str .= ".PP\n"; my @decllines = split /\n/, $decl; foreach (@decllines) { + $_ =~ s/\\/\\(rs/g; # fix multiline macro defs + $_ =~ s/"/\\(dq/g; $str .= ".BI \"$_\n"; } $str .= ".fi\n"; @@ -2938,8 +2981,11 @@ __EOF__ } if (defined $returns) { + # Chop datatype in parentheses off the front. + if(!($returns =~ s/\A\([^\[]*\[[^\]]*\]\([^\)]*\)[^\)]*\) //ms)) { + $returns =~ s/\A\([^\)]*\) //ms; + } $returns = dewikify($wikitype, $returns); - $returns =~ s/\A\(.*?\)\s*//; # Chop datatype in parentheses off the front. $str .= ".SH RETURN VALUE\n"; $str .= "$returns\n"; } @@ -2975,6 +3021,8 @@ __EOF__ s/\A\/*//; s/\A\.BR\s+//; # dewikify added this, but we want to handle it. s/\A\.I\s+//; # dewikify added this, but we want to handle it. + s/\A\.PP\s*//; # dewikify added this, but we want to handle it. + s/\\\(bu//; # dewikify added this, but we want to handle it. s/\A\s*[\:\*\-]\s*//; s/\A\s+//; s/\s+\Z//; diff --git a/libs/SDL3/cmake/SDL3Config.cmake.in b/libs/SDL3/cmake/SDL3Config.cmake.in index e2ca50e..4c6f1b6 100644 --- a/libs/SDL3/cmake/SDL3Config.cmake.in +++ b/libs/SDL3/cmake/SDL3Config.cmake.in @@ -77,6 +77,9 @@ else() endif() endif() +if(NOT SDL3_COMPONENTS AND NOT TARGET SDL3::Headers AND NOT TARGET SDL3::SDL3-shared AND NOT TARGET SDL3::SDL3-static) + set(SDL3_FOUND FALSE) +endif() check_required_components(SDL3) function(_sdl_create_target_alias_compat NEW_TARGET TARGET) @@ -93,7 +96,7 @@ endfunction() if(NOT TARGET SDL3::SDL3) if(TARGET SDL3::SDL3-shared) _sdl_create_target_alias_compat(SDL3::SDL3 SDL3::SDL3-shared) - else() + elseif(TARGET SDL3::SDL3-static) _sdl_create_target_alias_compat(SDL3::SDL3 SDL3::SDL3-static) endif() endif() diff --git a/libs/SDL3/cmake/sdlchecks.cmake b/libs/SDL3/cmake/sdlchecks.cmake index d95cbfa..d321e71 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") @@ -1077,6 +1077,14 @@ endmacro() # Check for HIDAPI support macro(CheckHIDAPI) + if(ANDROID) + enable_language(CXX) + sdl_sources("${SDL3_SOURCE_DIR}/src/hidapi/android/hid.cpp") + endif() + if(IOS OR TVOS) + sdl_sources("${SDL3_SOURCE_DIR}/src/hidapi/ios/hid.m") + set(SDL_FRAMEWORK_COREBLUETOOTH 1) + endif() if(SDL_HIDAPI) set(HAVE_HIDAPI ON) if(SDL_HIDAPI_LIBUSB) @@ -1109,14 +1117,6 @@ macro(CheckHIDAPI) endif() if(HAVE_HIDAPI) - if(ANDROID) - enable_language(CXX) - sdl_sources("${SDL3_SOURCE_DIR}/src/hidapi/android/hid.cpp") - endif() - if(IOS OR TVOS) - sdl_sources("${SDL3_SOURCE_DIR}/src/hidapi/ios/hid.m") - set(SDL_FRAMEWORK_COREBLUETOOTH 1) - endif() set(HAVE_SDL_HIDAPI TRUE) if(SDL_JOYSTICK AND SDL_HIDAPI_JOYSTICK) diff --git a/libs/SDL3/docs/INTRO-cmake.md b/libs/SDL3/docs/INTRO-cmake.md index e6ccace..05990e4 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 0000000..d7c535d --- /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 2cc6100..4017f79 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 37905c4..e57ca81 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-highdpi.md b/libs/SDL3/docs/README-highdpi.md index bec0125..dd3a2fa 100644 --- a/libs/SDL3/docs/README-highdpi.md +++ b/libs/SDL3/docs/README-highdpi.md @@ -1,32 +1,6 @@ SDL 3.0 has new support for high DPI displays. Interfaces provided by SDL uses the platform's native coordinates unless otherwise specified. -To reconcile platform differences in their approach to high-density scaling, SDL provides the following interfaces: -- `SDL_GetWindowSize()` retrieves the window dimensions in native coordinates. -- `SDL_GetWindowSizeInPixels()` retrieves the window dimensions in pixels-addressable. -- `SDL_GetDisplayContentScale()` retrieves the suggested amplification factor when drawing in native coordinates. -- `SDL_GetWindowDisplayScale()` retrieves the suggested amplification factor when drawing in pixels-addressable. -- `SDL_GetWindowPixelDensity()` retrieves how many addressable pixels correspond to one unit of native coordinates. -- `SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED` is emitted when the value retrievable from `SDL_GetWindowSizeInPixels()` changes. -- `SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED` is emitted when the value retrievable from `SDL_GetWindowDisplayScale()` changes. -- Windows created with `SDL_WINDOW_HIGH_PIXEL_DENSITY` will ask the platform to display addressable pixels at their natural scale. - -## Numeric example - -Given a fullscreen window spanning a 3840x2160 monitor set to 2x display or 200% scaling, the following tabulates the effect of creating a window with or without `SDL_WINDOW_HIGH_PIXEL_DENSITY` on MacOS and Win32: - -| Value | MacOS (Default) | MacOS (HD) | Win32 (Default & HD) | -|--------------------------------|-----------------|------------|----------------------| -| `SDL_GetWindowSize()` | 1920x1080 | 1920x1080 | 3840x2160 | -| `SDL_GetWindowSizeInPixels()` | 1920x1080 | 3840x2160 | 3840x2160 | -| `SDL_GetDisplayContentScale()` | 1.0 | 1.0 | 2.0 | -| `SDL_GetWindowDisplayScale()` | 1.0 | 2.0 | 2.0 | -| `SDL_GetWindowPixelDensity()` | 1.0 | 2.0 | 1.0 | - -Observe the philosophical difference between the approaches taken by MacOS and Win32: -- Win32 coordinate system always deals in physical device pixels, high DPI support is achieved by providing an advisory hint for the developer to enlarge drawn objects. Ignoring the advisory scale factor results in graphics appearing tiny. -- MacOS coordinate system always deals in physical content sizes, high DPI support is achieved by providing an optional flag for the developer to request finer granularity. Omitting the granularity request results in graphics appearing coarse. - ## Explanation Displays now have a content display scale, which is the expected scale for content based on the DPI settings of the display. For example, a 4K display might have a 2.0 (200%) display scale, which means that the user expects UI elements to be twice as big on this display, to aid in readability. You can query the display content scale using `SDL_GetDisplayContentScale()`, and when this changes you get an `SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED` event. @@ -34,3 +8,33 @@ Displays now have a content display scale, which is the expected scale for conte The window size is now distinct from the window pixel size, and the ratio between the two is the window pixel density. If the window is created with the `SDL_WINDOW_HIGH_PIXEL_DENSITY` flag, SDL will try to match the native pixel density for the display, otherwise it will try to have the pixel size match the window size. You can query the window pixel density using `SDL_GetWindowPixelDensity()`. You can query the window pixel size using `SDL_GetWindowSizeInPixels()`, and when this changes you get an `SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED` event. You are guaranteed to get a `SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED` event when a window is created and resized, and you can use this event to create and resize your graphics context for the window. The window has a display scale, which is the scale from the pixel resolution to the desired content size, e.g. the combination of the pixel density and the content scale. For example, a 3840x2160 window displayed at 200% on Windows, and a 1920x1080 window with the high density flag on a 2x display on macOS will both have a pixel size of 3840x2160 and a display scale of 2.0. You can query the window display scale using `SDL_GetWindowDisplayScale()`, and when this changes you get an `SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED` event. + +## Numeric example + +Given a window spanning a 3840x2160 monitor set to 2x display or 200% scaling, the following tabulates the effect of creating a window with or without `SDL_WINDOW_HIGH_PIXEL_DENSITY` on macOS and Windows: + +| Value | macOS (Default) | macOS (HD) | Windows (Default & HD) | +|--------------------------------|-----------------|------------|------------------------| +| `SDL_GetWindowSize()` | 1920x1080 | 1920x1080 | 3840x2160 | +| `SDL_GetWindowSizeInPixels()` | 1920x1080 | 3840x2160 | 3840x2160 | +| `SDL_GetDisplayContentScale()` | 1.0 | 1.0 | 2.0 | +| `SDL_GetWindowDisplayScale()` | 1.0 | 2.0 | 2.0 | +| `SDL_GetWindowPixelDensity()` | 1.0 | 2.0 | 1.0 | + +Observe the difference between the approaches taken by macOS and Windows: +- The Windows and Android coordinate system always deals in physical device pixels, high DPI support is achieved by providing a content scale that tells the developer to draw objects larger. Ignoring this scale factor results in graphics appearing tiny. +- The macOS and iOS coordinate system always deals in window coordinates, high DPI support is achieved by providing an optional flag for the developer to request more pixels. Omitting this flag results in graphics having low detail. +- On Linux, X11 uses a similar approach to Windows and Wayland uses a similar approach to macOS. + +## Solution + +Proper high DPI support takes into account both the content scale and the pixel density. + +First, you'd create your window with the `SDL_WINDOW_HIGH_PIXEL_DENSITY` flag, assuming you want the highest detail possible. Then you'd get the window display scale to see how much your UI elements should be enlarged to be readable. + +If you're using the SDL 2D renderer, SDL provides the function `SDL_ConvertEventToRenderCoordinates()` to convert mouse coordinates between window coordinates and rendering coordinates, and the more general functions `SDL_RenderCoordinatesFromWindow()` and `SDL_RenderCoordinatesToWindow()` to do other conversion between them. + +If you're not using the 2D renderer, you can implement this yourself using `SDL_GetWindowPixelDensity()` as scale factor to convert from window coordinates to pixels. + +Finally you'll want to test on both Windows and macOS if possible to make sure your high DPI support works in all environments. + diff --git a/libs/SDL3/docs/README-ios.md b/libs/SDL3/docs/README-ios.md index dbd70f5..166d182 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-linux.md b/libs/SDL3/docs/README-linux.md index ce740ea..3f2d2c0 100644 --- a/libs/SDL3/docs/README-linux.md +++ b/libs/SDL3/docs/README-linux.md @@ -19,7 +19,7 @@ Ubuntu 18.04, all available features enabled: libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \ libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \ libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \ - libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev + libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev Ubuntu 22.04+ can also add `libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev` to that command line. @@ -28,7 +28,7 @@ Fedora 35, all available features enabled: sudo yum install gcc git-core make cmake \ alsa-lib-devel pulseaudio-libs-devel nas-devel pipewire-devel \ libX11-devel libXext-devel libXrandr-devel libXcursor-devel libXfixes-devel \ - libXi-devel libXScrnSaver-devel dbus-devel ibus-devel fcitx-devel \ + libXi-devel libXScrnSaver-devel dbus-devel ibus-devel \ systemd-devel mesa-libGL-devel libxkbcommon-devel mesa-libGLES-devel \ mesa-libEGL-devel vulkan-devel wayland-devel wayland-protocols-devel \ libdrm-devel mesa-libgbm-devel libusb-devel libdecor-devel \ @@ -45,6 +45,10 @@ openSUSE Tumbleweed: sudo zypper in libunwind-devel libusb-1_0-devel Mesa-libGL-devel libxkbcommon-devel libdrm-devel \ libgbm-devel pipewire-devel libpulse-devel sndio-devel Mesa-libEGL-devel +Arch: + sudo pacman -S alsa-lib cmake hidapi ibus jack libdecor libgl libpulse libusb libx11 libxcursor libxext libxinerama libxkbcommon libxrandr libxrender libxss mesa ninja pipewire sndio vulkan-driver vulkan-headers wayland wayland-protocols + + Joystick does not work -------------------------------------------------------------------------------- diff --git a/libs/SDL3/docs/README-macos.md b/libs/SDL3/docs/README-macos.md index fb0639b..e5c75c1 100644 --- a/libs/SDL3/docs/README-macos.md +++ b/libs/SDL3/docs/README-macos.md @@ -49,7 +49,7 @@ NSApplicationDelegate implementation: ```objc - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { - if (SDL_GetEventState(SDL_EVENT_QUIT) == SDL_ENABLE) { + if (SDL_EventEnabled(SDL_EVENT_QUIT)) { SDL_Event event; SDL_zero(event); event.type = SDL_EVENT_QUIT; @@ -61,7 +61,7 @@ NSApplicationDelegate implementation: - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { - if (SDL_GetEventState(SDL_EVENT_DROP_FILE) == SDL_ENABLE) { + if (SDL_EventEnabled(SDL_EVENT_DROP_FILE)) { SDL_Event event; SDL_zero(event); event.type = SDL_EVENT_DROP_FILE; diff --git a/libs/SDL3/docs/README-migration.md b/libs/SDL3/docs/README-migration.md index 0cbe9fe..deca6d0 100644 --- a/libs/SDL3/docs/README-migration.md +++ b/libs/SDL3/docs/README-migration.md @@ -411,7 +411,7 @@ The iscapture field of SDL_AudioDeviceEvent has been renamed recording. SDL_QUERY, SDL_IGNORE, SDL_ENABLE, and SDL_DISABLE have been removed. You can use the functions SDL_SetEventEnabled() and SDL_EventEnabled() to set and query event processing state. -SDL_AddEventWatch() now returns SDL_FALSE_ if it fails because it ran out of memory and couldn't add the event watch callback. +SDL_AddEventWatch() now returns false if it fails because it ran out of memory and couldn't add the event watch callback. SDL_RegisterEvents() now returns 0 if it couldn't allocate any user events. diff --git a/libs/SDL3/docs/README-platforms.md b/libs/SDL3/docs/README-platforms.md index 77ae411..46a054a 100644 --- a/libs/SDL3/docs/README-platforms.md +++ b/libs/SDL3/docs/README-platforms.md @@ -11,7 +11,7 @@ - [macOS](README-macos.md) - [NetBSD](README-bsd.md) - [Nintendo Switch](README-switch.md) -- [Nintendo 3DS](README-3ds.md) +- [Nintendo 3DS](README-n3ds.md) - [OpenBSD](README-bsd.md) - [PlayStation 2](README-ps2.md) - [PlayStation 4](README-ps4.md) diff --git a/libs/SDL3/docs/README-psp.md b/libs/SDL3/docs/README-psp.md index c010c60..65d2af2 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-versions.md b/libs/SDL3/docs/README-versions.md index 097dba1..6c2d920 100644 --- a/libs/SDL3/docs/README-versions.md +++ b/libs/SDL3/docs/README-versions.md @@ -1,60 +1,48 @@ # Versioning -## Since 2.23.0 +## Since 3.2.0 SDL follows an "odd/even" versioning policy, similar to GLib, GTK, Flatpak and older versions of the Linux kernel: -* The major version (first part) increases when backwards compatibility - is broken, which will happen infrequently. - -* If the minor version (second part) is divisible by 2 - (for example 2.24.x, 2.26.x), this indicates a version of SDL that - is believed to be stable and suitable for production use. +* If the minor version (second part) and the patch version (third part) is + divisible by 2 (for example 3.2.6, 3.4.0), this indicates a version of + SDL that is believed to be stable and suitable for production use. * In stable releases, the patchlevel or micro version (third part) - indicates bugfix releases. Bugfix releases should not add or - remove ABI, so the ".0" release (for example 2.24.0) should be - forwards-compatible with all the bugfix releases from the - same cycle (for example 2.24.1). + indicates bugfix releases. Bugfix releases may add small changes + to the ABI, so newer patch versions are backwards-compatible but + not fully forwards-compatible. For example, programs built against + SDL 3.2.0 should work fine with SDL 3.2.8, but programs built against + SDL 3.2.8 may not work with 3.2.0. - * The minor version increases when new API or ABI is added, or when - other significant changes are made. Newer minor versions are - backwards-compatible, but not fully forwards-compatible. - For example, programs built against SDL 2.24.x should work fine - with SDL 2.26.x, but programs built against SDL 2.26.x will not - necessarily work with 2.24.x. + * The minor version increases when significant changes are made that + require longer development or testing time, e.g. major new functionality, + or revamping support for a platform. Newer minor versions are + backwards-compatible, but not fully forwards-compatible. For example, + programs built against SDL 3.2.x should work fine with SDL 3.4.x, + but programs built against SDL 3.4.x may not work with 3.2.x. -* If the minor version (second part) is not divisible by 2 - (for example 2.23.x, 2.25.x), this indicates a development prerelease - of SDL that is not suitable for stable software distributions. +* If the minor version (second part) or patch version (third part) is not + divisible by 2 (for example 3.2.9, 3.3.x), this indicates a development + prerelease of SDL that is not suitable for stable software distributions. Use with caution. - * The patchlevel or micro version (third part) increases with - each prerelease. - - * Each prerelease might add new API and/or ABI. + * The patchlevel or micro version (third part) increases with each prerelease. * Prereleases are backwards-compatible with older stable branches. - For example, 2.25.x will be backwards-compatible with 2.24.x. + For example, programs built against SDL 3.2.x should work fine with + SDL 3.3.x, but programs built against SDL 3.3.x may not work with 3.2.x. - * Prereleases are not guaranteed to be backwards-compatible with - each other. For example, new API or ABI added in 2.25.1 - might be removed or changed in 2.25.2. - If this would be a problem for you, please do not use prereleases. + * Prereleases are not guaranteed to be backwards-compatible with each other. + For example, new API or ABI added in 3.3.0 might be removed or changed in + 3.3.1. If this would be a problem for you, please do not use prereleases. - * Only upgrade to a prerelease if you can guarantee that you will - promptly upgrade to the stable release that follows it. - For example, do not upgrade to 2.23.x unless you will be able to - upgrade to 2.24.0 when it becomes available. + * Only use a prerelease if you can guarantee that you will promptly upgrade + to the stable release that follows it. For example, do not use 3.3.x + unless you will be able to upgrade to 3.4.0 when it becomes available. * Software distributions that have a freeze policy (in particular Linux distributions with a release cycle, such as Debian and Fedora) - should usually only package stable releases, and not prereleases. + should only package stable releases, and not prereleases. -## Before 2.23.0 - -Older versions of SDL followed a similar policy, but instead of the -odd/even rule applying to the minor version, it applied to the patchlevel -(micro version, third part). For example, 2.0.22 was a stable release -and 2.0.21 was a prerelease. diff --git a/libs/SDL3/docs/README-wayland.md b/libs/SDL3/docs/README-wayland.md index 557a813..75a9b90 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/examples/audio/01-simple-playback/simple-playback.c b/libs/SDL3/examples/audio/01-simple-playback/simple-playback.c index 330df80..15126e5 100644 --- a/libs/SDL3/examples/audio/01-simple-playback/simple-playback.c +++ b/libs/SDL3/examples/audio/01-simple-playback/simple-playback.c @@ -69,7 +69,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) A sine wave is unchanging audio--easy to stream--but for video games, you'll want to generate significantly _less_ audio ahead of time! */ const int minimum_audio = (8000 * sizeof (float)) / 2; /* 8000 float samples per second. Half of that. */ - if (SDL_GetAudioStreamAvailable(stream) < minimum_audio) { + if (SDL_GetAudioStreamQueued(stream) < minimum_audio) { static float samples[512]; /* this will feed 512 samples each frame until we get to our maximum. */ int i; diff --git a/libs/SDL3/examples/audio/03-load-wav/load-wav.c b/libs/SDL3/examples/audio/03-load-wav/load-wav.c index 966fccb..c517e5d 100644 --- a/libs/SDL3/examples/audio/03-load-wav/load-wav.c +++ b/libs/SDL3/examples/audio/03-load-wav/load-wav.c @@ -82,7 +82,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) We're being lazy here, but if there's less than the entire wav file left to play, just shove a whole copy of it into the queue, so we always have _tons_ of data queued for playback. */ - if (SDL_GetAudioStreamAvailable(stream) < (int)wav_data_len) { + if (SDL_GetAudioStreamQueued(stream) < (int)wav_data_len) { /* feed more data to the stream. It will queue at the end, and trickle out as the hardware needs more data. */ SDL_PutAudioStreamData(stream, wav_data, wav_data_len); } diff --git a/libs/SDL3/examples/audio/04-multiple-streams/multiple-streams.c b/libs/SDL3/examples/audio/04-multiple-streams/multiple-streams.c index 57f3cd0..8d3bfaa 100644 --- a/libs/SDL3/examples/audio/04-multiple-streams/multiple-streams.c +++ b/libs/SDL3/examples/audio/04-multiple-streams/multiple-streams.c @@ -104,7 +104,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) /* If less than a full copy of the audio is queued for playback, put another copy in there. This is overkill, but easy when lots of RAM is cheap. One could be more careful and queue less at a time, as long as the stream doesn't run dry. */ - if (SDL_GetAudioStreamAvailable(sounds[i].stream) < ((int) sounds[i].wav_data_len)) { + if (SDL_GetAudioStreamQueued(sounds[i].stream) < ((int) sounds[i].wav_data_len)) { SDL_PutAudioStreamData(sounds[i].stream, sounds[i].wav_data, (int) sounds[i].wav_data_len); } } diff --git a/libs/SDL3/examples/demo/01-snake/snake.c b/libs/SDL3/examples/demo/01-snake/snake.c index a7f667d..0aca862 100644 --- a/libs/SDL3/examples/demo/01-snake/snake.c +++ b/libs/SDL3/examples/demo/01-snake/snake.c @@ -208,7 +208,7 @@ void snake_step(SnakeContext *ctx) } } -static int handle_key_event_(SnakeContext *ctx, SDL_Scancode key_code) +static SDL_AppResult handle_key_event_(SnakeContext *ctx, SDL_Scancode key_code) { switch (key_code) { /* Quit. */ @@ -309,7 +309,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - AppState *as = SDL_calloc(1, sizeof(AppState)); + AppState *as = (AppState *)SDL_calloc(1, sizeof(AppState)); if (!as) { return SDL_APP_FAILURE; } diff --git a/libs/SDL3/examples/demo/04-bytepusher/bytepusher.c b/libs/SDL3/examples/demo/04-bytepusher/bytepusher.c index 01b0e1a..acb2ea4 100644 --- a/libs/SDL3/examples/demo/04-bytepusher/bytepusher.c +++ b/libs/SDL3/examples/demo/04-bytepusher/bytepusher.c @@ -156,7 +156,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { return SDL_APP_FAILURE; } - if (!(vm = SDL_calloc(1, sizeof(*vm)))) { + if (!(vm = (BytePusher *)SDL_calloc(1, sizeof(*vm)))) { return SDL_APP_FAILURE; } *(BytePusher**)appstate = vm; @@ -199,7 +199,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { for (r = 0; r < 6; ++r) { for (g = 0; g < 6; ++g) { for (b = 0; b < 6; ++b, ++i) { - SDL_Color color = { r * 0x33, g * 0x33, b * 0x33, SDL_ALPHA_OPAQUE }; + SDL_Color color = { (Uint8)(r * 0x33), (Uint8)(g * 0x33), (Uint8)(b * 0x33), SDL_ALPHA_OPAQUE }; palette->colors[i] = color; } } diff --git a/libs/SDL3/examples/input/01-joystick-polling/joystick-polling.c b/libs/SDL3/examples/input/01-joystick-polling/joystick-polling.c index 8f7a315..6eb23b8 100644 --- a/libs/SDL3/examples/input/01-joystick-polling/joystick-polling.c +++ b/libs/SDL3/examples/input/01-joystick-polling/joystick-polling.c @@ -105,7 +105,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) /* draw axes as bars going across middle of screen. We don't know if it's an X or Y or whatever axis, so we can't do more than this. */ total = SDL_GetNumJoystickAxes(joystick); - y = (float) ((winh - (total * size)) / 2); + y = (winh - (total * size)) / 2; x = ((float) winw) / 2.0f; for (i = 0; i < total; i++) { const SDL_Color *color = &colors[i % SDL_arraysize(colors)]; @@ -119,7 +119,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) /* draw buttons as blocks across top of window. We only know the button numbers, but not where they are on the device. */ total = SDL_GetNumJoystickButtons(joystick); - x = (float) ((winw - (total * size)) / 2); + x = (winw - (total * size)) / 2; for (i = 0; i < total; i++) { const SDL_Color *color = &colors[i % SDL_arraysize(colors)]; const SDL_FRect dst = { x, 0.0f, size, size }; @@ -136,7 +136,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) /* draw hats across the bottom of the screen. */ total = SDL_GetNumJoystickHats(joystick); - x = ((float) ((winw - (total * (size * 2.0f))) / 2.0f)) + (size / 2.0f); + x = ((winw - (total * (size * 2.0f))) / 2.0f) + (size / 2.0f); y = ((float) winh) - size; for (i = 0; i < total; i++) { const SDL_Color *color = &colors[i % SDL_arraysize(colors)]; diff --git a/libs/SDL3/examples/renderer/09-scaling-textures/scaling-textures.c b/libs/SDL3/examples/renderer/09-scaling-textures/scaling-textures.c index 71a94c6..66060ed 100644 --- a/libs/SDL3/examples/renderer/09-scaling-textures/scaling-textures.c +++ b/libs/SDL3/examples/renderer/09-scaling-textures/scaling-textures.c @@ -92,8 +92,8 @@ SDL_AppResult SDL_AppIterate(void *appstate) /* center this one and make it grow and shrink. */ dst_rect.w = (float) texture_width + (texture_width * scale); dst_rect.h = (float) texture_height + (texture_height * scale); - dst_rect.x = ((float) (WINDOW_WIDTH - dst_rect.w)) / 2.0f; - dst_rect.y = ((float) (WINDOW_HEIGHT - dst_rect.h)) / 2.0f; + dst_rect.x = (WINDOW_WIDTH - dst_rect.w) / 2.0f; + dst_rect.y = (WINDOW_HEIGHT - dst_rect.h) / 2.0f; SDL_RenderTexture(renderer, texture, NULL, &dst_rect); SDL_RenderPresent(renderer); /* put it all on the screen! */ diff --git a/libs/SDL3/examples/renderer/10-geometry/geometry.c b/libs/SDL3/examples/renderer/10-geometry/geometry.c index ffe7111..77ff863 100644 --- a/libs/SDL3/examples/renderer/10-geometry/geometry.c +++ b/libs/SDL3/examples/renderer/10-geometry/geometry.c @@ -141,7 +141,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) /* we need one more vertex, since the two triangles can share two of them. */ vertices[3].position.x = 600.0f; vertices[3].position.y = 150.0f; - vertices[3].color.r = vertices[0].color.g = vertices[0].color.b = vertices[0].color.a = 1.0f; + vertices[3].color.r = vertices[3].color.g = vertices[3].color.b = vertices[3].color.a = 1.0f; vertices[3].tex_coord.x = 1.0f; vertices[3].tex_coord.y = 1.0f; diff --git a/libs/SDL3/include/SDL3/SDL.h b/libs/SDL3/include/SDL3/SDL.h index 4fd4083..587b541 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.4 + * Main include header for the SDL library, version 3.2.20 * * 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_assert.h b/libs/SDL3/include/SDL3/SDL_assert.h index 09b3b47..053af13 100644 --- a/libs/SDL3/include/SDL3/SDL_assert.h +++ b/libs/SDL3/include/SDL3/SDL_assert.h @@ -149,6 +149,8 @@ extern "C" { #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "bkpt #22\n\t" ) #elif defined(_WIN32) && ((defined(__GNUC__) || defined(__clang__)) && (defined(__arm64__) || defined(__aarch64__)) ) #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "brk #0xF000\n\t" ) +#elif defined(__GNUC__) || defined(__clang__) + #define SDL_TriggerBreakpoint() __builtin_trap() /* older gcc may not support SDL_HAS_BUILTIN(__builtin_trap) above */ #elif defined(__386__) && defined(__WATCOMC__) #define SDL_TriggerBreakpoint() { _asm { int 0x03 } } #elif defined(HAVE_SIGNAL_H) && !defined(__WATCOMC__) @@ -360,7 +362,7 @@ extern SDL_DECLSPEC SDL_AssertState SDLCALL SDL_ReportAssertion(SDL_AssertData * #define SDL_enabled_assert(condition) \ do { \ while ( !(condition) ) { \ - static struct SDL_AssertData sdl_assert_data = { 0, 0, #condition, 0, 0, 0, 0 }; \ + static struct SDL_AssertData sdl_assert_data = { false, 0, #condition, NULL, 0, NULL, NULL }; \ const SDL_AssertState sdl_assert_state = SDL_ReportAssertion(&sdl_assert_data, SDL_FUNCTION, SDL_FILE, SDL_LINE); \ if (sdl_assert_state == SDL_ASSERTION_RETRY) { \ continue; /* go again. */ \ diff --git a/libs/SDL3/include/SDL3/SDL_audio.h b/libs/SDL3/include/SDL3/SDL_audio.h index 541c5df..51af40e 100644 --- a/libs/SDL3/include/SDL3/SDL_audio.h +++ b/libs/SDL3/include/SDL3/SDL_audio.h @@ -942,7 +942,10 @@ extern SDL_DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID devid); * Binding a stream to a device will set its output format for playback * devices, and its input format for recording devices, so they match the * device's settings. The caller is welcome to change the other end of the - * stream's format at any time with SDL_SetAudioStreamFormat(). + * stream's format at any time with SDL_SetAudioStreamFormat(). If the other + * end of the stream's format has never been set (the audio stream was created + * with a NULL audio spec), this function will set it to match the device + * end's format. * * \param devid an audio device to bind a stream to. * \param streams an array of audio streams to bind. @@ -1021,7 +1024,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnbindAudioStream(SDL_AudioStream *stream); /** * Query an audio stream for its currently-bound device. * - * This reports the audio device that an audio stream is currently bound to. + * This reports the logical audio device that an audio stream is currently bound to. * * If not bound, or invalid, this returns zero, which is not a valid device * ID. @@ -1717,7 +1720,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 +1765,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_clipboard.h b/libs/SDL3/include/SDL3/SDL_clipboard.h index 0d3cbb4..3ca56a4 100644 --- a/libs/SDL3/include/SDL3/SDL_clipboard.h +++ b/libs/SDL3/include/SDL3/SDL_clipboard.h @@ -106,7 +106,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetClipboardText(const char *text); /** * Get UTF-8 text from the clipboard. * - * This functions returns an empty string if there was not enough memory left + * This function returns an empty string if there is not enough memory left * for a copy of the clipboard's content. * * \returns the clipboard text on success or an empty string on failure; call @@ -155,7 +155,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetPrimarySelectionText(const char *text); /** * Get UTF-8 text from the primary selection. * - * This functions returns an empty string if there was not enough memory left + * This function returns an empty string if there is not enough memory left * for a copy of the primary selection's content. * * \returns the primary selection text on success or an empty string on @@ -194,15 +194,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasPrimarySelectionText(void); * clipboard is cleared or new data is set. The clipboard is automatically * cleared in SDL_Quit(). * - * \param userdata a pointer to provided user data. + * \param userdata a pointer to the provided user data. * \param mime_type the requested mime-type. * \param size a pointer filled in with the length of the returned data. * \returns a pointer to the data for the provided mime-type. Returning NULL - * or setting length to 0 will cause no data to be sent to the + * or setting the length to 0 will cause no data to be sent to the * "receiver". It is up to the receiver to handle this. Essentially * returning no data is more or less undefined behavior and may cause * breakage in receiving applications. The returned data will not be - * freed so it needs to be retained and dealt with internally. + * freed, so it needs to be retained and dealt with internally. * * \since This function is available since SDL 3.2.0. * @@ -211,10 +211,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasPrimarySelectionText(void); typedef const void *(SDLCALL *SDL_ClipboardDataCallback)(void *userdata, const char *mime_type, size_t *size); /** - * Callback function that will be called when the clipboard is cleared, or new + * Callback function that will be called when the clipboard is cleared, or when new * data is set. * - * \param userdata a pointer to provided user data. + * \param userdata a pointer to the provided user data. * * \since This function is available since SDL 3.2.0. * @@ -231,7 +231,7 @@ typedef void (SDLCALL *SDL_ClipboardCleanupCallback)(void *userdata); * respond with the data for the requested mime-type. * * The size of text data does not include any terminator, and the text does - * not need to be null terminated (e.g. you can directly copy a portion of a + * not need to be null-terminated (e.g., you can directly copy a portion of a * document). * * \param callback a function pointer to the function that provides the @@ -239,7 +239,7 @@ typedef void (SDLCALL *SDL_ClipboardCleanupCallback)(void *userdata); * \param cleanup a function pointer to the function that cleans up the * clipboard data. * \param userdata an opaque pointer that will be forwarded to the callbacks. - * \param mime_types a list of mime-types that are being offered. + * \param mime_types a list of mime-types that are being offered. SDL copies the given list. * \param num_mime_types the number of mime-types in the mime_types list. * \returns true on success or false on failure; call SDL_GetError() for more * information. @@ -269,10 +269,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetClipboardData(SDL_ClipboardDataCallback extern SDL_DECLSPEC bool SDLCALL SDL_ClearClipboardData(void); /** - * Get the data from clipboard for a given mime type. + * Get the data from the clipboard for a given mime type. * * The size of text data does not include the terminator, but the text is - * guaranteed to be null terminated. + * guaranteed to be null-terminated. * * \param mime_type the mime type to read from the clipboard. * \param size a pointer filled in with the length of the returned data. @@ -292,8 +292,8 @@ extern SDL_DECLSPEC void * SDLCALL SDL_GetClipboardData(const char *mime_type, s /** * Query whether there is data in the clipboard for the provided mime type. * - * \param mime_type the mime type to check for data for. - * \returns true if there exists data in clipboard for the provided mime type, + * \param mime_type the mime type to check for data. + * \returns true if data exists in the clipboard for the provided mime type, * false if it does not. * * \threadsafety This function should only be called on the main thread. @@ -310,7 +310,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasClipboardData(const char *mime_type); * * \param num_mime_types a pointer filled with the number of mime types, may * be NULL. - * \returns a null terminated array of strings with mime types, or NULL on + * \returns a null-terminated array of strings with mime types, or NULL on * failure; call SDL_GetError() for more information. This should be * freed with SDL_free() when it is no longer needed. * diff --git a/libs/SDL3/include/SDL3/SDL_dialog.h b/libs/SDL3/include/SDL3/SDL_dialog.h index 460038f..ddb9e24 100644 --- a/libs/SDL3/include/SDL3/SDL_dialog.h +++ b/libs/SDL3/include/SDL3/SDL_dialog.h @@ -84,8 +84,8 @@ typedef struct SDL_DialogFileFilter * - A pointer to NULL, the user either didn't choose any file or canceled the * dialog. * - A pointer to non-`NULL`, the user chose one or more files. The argument - * is a null-terminated list of pointers to C strings, each containing a - * path. + * is a null-terminated array of pointers to UTF-8 encoded strings, each + * containing a path. * * The filelist argument should not be freed; it will automatically be freed * when the callback returns. diff --git a/libs/SDL3/include/SDL3/SDL_endian.h b/libs/SDL3/include/SDL3/SDL_endian.h index a34e9d4..2a9b8a3 100644 --- a/libs/SDL3/include/SDL3/SDL_endian.h +++ b/libs/SDL3/include/SDL3/SDL_endian.h @@ -486,7 +486,7 @@ SDL_FORCE_INLINE Uint32 SDL_Swap32(Uint32 x) { return x_but_byteswapped; } * * \since This function is available since SDL 3.2.0. */ -SDL_FORCE_INLINE Uint32 SDL_Swap64(Uint64 x) { return x_but_byteswapped; } +SDL_FORCE_INLINE Uint64 SDL_Swap64(Uint64 x) { return x_but_byteswapped; } /** * Swap a 16-bit value from littleendian to native byte order. diff --git a/libs/SDL3/include/SDL3/SDL_events.h b/libs/SDL3/include/SDL3/SDL_events.h index 1323e9f..d267f05 100644 --- a/libs/SDL3/include/SDL3/SDL_events.h +++ b/libs/SDL3/include/SDL3/SDL_events.h @@ -132,7 +132,7 @@ typedef enum SDL_EventType /* Window events */ /* 0x200 was SDL_WINDOWEVENT, reserve the number for sdl2-compat */ - /* 0x201 was SDL_EVENT_SYSWM, reserve the number for sdl2-compat */ + /* 0x201 was SDL_SYSWMEVENT, reserve the number for sdl2-compat */ SDL_EVENT_WINDOW_SHOWN = 0x202, /**< Window has been shown */ SDL_EVENT_WINDOW_HIDDEN, /**< Window has been hidden */ SDL_EVENT_WINDOW_EXPOSED, /**< Window has been exposed and should be redrawn, and can be redrawn directly from event watchers for this event */ @@ -492,6 +492,8 @@ typedef struct SDL_MouseWheelEvent SDL_MouseWheelDirection direction; /**< Set to one of the SDL_MOUSEWHEEL_* defines. When FLIPPED the values in X and Y will be opposite. Multiply by -1 to change them back */ float mouse_x; /**< X coordinate, relative to window */ float mouse_y; /**< Y coordinate, relative to window */ + Sint32 integer_x; /**< The amount scrolled horizontally, accumulated to whole scroll "ticks" (added in 3.2.12) */ + Sint32 integer_y; /**< The amount scrolled vertically, accumulated to whole scroll "ticks" (added in 3.2.12) */ } SDL_MouseWheelEvent; /** @@ -1108,7 +1110,7 @@ typedef enum SDL_EventAction * \param numevents if action is SDL_ADDEVENT, the number of events to add * back to the event queue; if action is SDL_PEEKEVENT or * SDL_GETEVENT, the maximum number of events to retrieve. - * \param action action to take; see [[#action|Remarks]] for details. + * \param action action to take; see [Remarks](#remarks) for details. * \param minType minimum value of the event type to be considered; * SDL_EVENT_FIRST is a safe choice. * \param maxType maximum value of the event type to be considered; diff --git a/libs/SDL3/include/SDL3/SDL_gpu.h b/libs/SDL3/include/SDL3/SDL_gpu.h index c3a3db7..4a5e32f 100644 --- a/libs/SDL3/include/SDL3/SDL_gpu.h +++ b/libs/SDL3/include/SDL3/SDL_gpu.h @@ -35,13 +35,14 @@ * can render offscreen entirely, perhaps for image processing, and not use a * window at all. * - * Next the app prepares static data (things that are created once and used + * Next, the app prepares static data (things that are created once and used * over and over). For example: * * - Shaders (programs that run on the GPU): use SDL_CreateGPUShader(). - * - Vertex buffers (arrays of geometry data) and other data rendering will - * need: use SDL_UploadToGPUBuffer(). - * - Textures (images): use SDL_UploadToGPUTexture(). + * - Vertex buffers (arrays of geometry data) and other rendering data: use + * SDL_CreateGPUBuffer() and SDL_UploadToGPUBuffer(). + * - Textures (images): use SDL_CreateGPUTexture() and + * SDL_UploadToGPUTexture(). * - Samplers (how textures should be read from): use SDL_CreateGPUSampler(). * - Render pipelines (precalculated rendering state): use * SDL_CreateGPUGraphicsPipeline() @@ -1495,9 +1496,16 @@ typedef struct SDL_GPUIndirectDispatchCommand /** * A structure specifying the parameters of a sampler. * + * Note that mip_lod_bias is a no-op for the Metal driver. For Metal, LOD bias + * must be applied via shader instead. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CreateGPUSampler + * \sa SDL_GPUFilter + * \sa SDL_GPUSamplerMipmapMode + * \sa SDL_GPUSamplerAddressMode + * \sa SDL_GPUCompareOp */ typedef struct SDL_GPUSamplerCreateInfo { @@ -1536,14 +1544,14 @@ typedef struct SDL_GPUSamplerCreateInfo * \since This struct is available since SDL 3.2.0. * * \sa SDL_GPUVertexAttribute - * \sa SDL_GPUVertexInputState + * \sa SDL_GPUVertexInputRate */ typedef struct SDL_GPUVertexBufferDescription { Uint32 slot; /**< The binding slot of the vertex buffer. */ - Uint32 pitch; /**< The byte pitch between consecutive elements of the vertex buffer. */ + Uint32 pitch; /**< The size of a single element + the offset between elements. */ SDL_GPUVertexInputRate input_rate; /**< Whether attribute addressing is a function of the vertex index or instance index. */ - Uint32 instance_step_rate; /**< The number of instances to draw using the same per-instance data before advancing in the instance buffer by one element. Ignored unless input_rate is SDL_GPU_VERTEXINPUTRATE_INSTANCE */ + Uint32 instance_step_rate; /**< Reserved for future use. Must be set to 0. */ } SDL_GPUVertexBufferDescription; /** @@ -1713,10 +1721,13 @@ typedef struct SDL_GPUTransferBufferCreateInfo * A structure specifying the parameters of the graphics pipeline rasterizer * state. * - * NOTE: Some backend APIs (D3D11/12) will enable depth clamping even if - * enable_depth_clip is true. If you rely on this clamp+clip behavior, - * consider enabling depth clip and then manually clamping depth in your - * fragment shaders on Metal and Vulkan. + * Note that SDL_GPU_FILLMODE_LINE is not supported on many Android devices. + * For those devices, the fill mode will automatically fall back to FILL. + * + * Also note that the D3D12 driver will enable depth clamping even if + * enable_depth_clip is true. If you need this clamp+clip behavior, consider + * enabling depth clip and then manually clamping depth in your fragment + * shaders on Metal and Vulkan. * * \since This struct is available since SDL 3.2.0. * @@ -1747,8 +1758,8 @@ typedef struct SDL_GPURasterizerState typedef struct SDL_GPUMultisampleState { SDL_GPUSampleCount sample_count; /**< The number of samples to be used in rasterization. */ - Uint32 sample_mask; /**< Determines which samples get updated in the render targets. Treated as 0xFFFFFFFF if enable_mask is false. */ - bool enable_mask; /**< Enables sample masking. */ + Uint32 sample_mask; /**< Reserved for future use. Must be set to 0. */ + bool enable_mask; /**< Reserved for future use. Must be set to false. */ Uint8 padding1; Uint8 padding2; Uint8 padding3; @@ -1798,6 +1809,8 @@ typedef struct SDL_GPUColorTargetDescription * \since This struct is available since SDL 3.2.0. * * \sa SDL_GPUGraphicsPipelineCreateInfo + * \sa SDL_GPUColorTargetDescription + * \sa SDL_GPUTextureFormat */ typedef struct SDL_GPUGraphicsPipelineTargetInfo { @@ -2454,9 +2467,9 @@ extern SDL_DECLSPEC SDL_GPUShader * SDLCALL SDL_CreateGPUShader( * - `SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT`: (Direct3D 12 only) * if the texture usage is SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, clear * the texture to a depth of this value. Defaults to zero. - * - `SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_UINT8`: (Direct3D 12 + * - `SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_NUMBER`: (Direct3D 12 * only) if the texture usage is SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, - * clear the texture to a stencil of this value. Defaults to zero. + * clear the texture to a stencil of this Uint8 value. Defaults to zero. * - `SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING`: a name that can be displayed * in debugging tools. * @@ -2482,13 +2495,13 @@ extern SDL_DECLSPEC SDL_GPUTexture * SDLCALL SDL_CreateGPUTexture( SDL_GPUDevice *device, const SDL_GPUTextureCreateInfo *createinfo); -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_R_FLOAT "SDL.gpu.texture.create.d3d12.clear.r" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_G_FLOAT "SDL.gpu.texture.create.d3d12.clear.g" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_B_FLOAT "SDL.gpu.texture.create.d3d12.clear.b" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_A_FLOAT "SDL.gpu.texture.create.d3d12.clear.a" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT "SDL.gpu.texture.create.d3d12.clear.depth" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_UINT8 "SDL.gpu.texture.create.d3d12.clear.stencil" -#define SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING "SDL.gpu.texture.create.name" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_R_FLOAT "SDL.gpu.texture.create.d3d12.clear.r" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_G_FLOAT "SDL.gpu.texture.create.d3d12.clear.g" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_B_FLOAT "SDL.gpu.texture.create.d3d12.clear.b" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_A_FLOAT "SDL.gpu.texture.create.d3d12.clear.a" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT "SDL.gpu.texture.create.d3d12.clear.depth" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_NUMBER "SDL.gpu.texture.create.d3d12.clear.stencil" +#define SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING "SDL.gpu.texture.create.name" /** * Creates a buffer object to be used in graphics or compute workflows. @@ -3762,7 +3775,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_ReleaseWindowFromGPUDevice( * supported via SDL_WindowSupportsGPUPresentMode / * SDL_WindowSupportsGPUSwapchainComposition prior to calling this function. * - * SDL_GPU_PRESENTMODE_VSYNC and SDL_GPU_SWAPCHAINCOMPOSITION_SDR are always + * SDL_GPU_PRESENTMODE_VSYNC with SDL_GPU_SWAPCHAINCOMPOSITION_SDR are always * supported. * * \param device a GPU context. @@ -3920,6 +3933,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WaitForGPUSwapchain( * freed by the user. You MUST NOT call this function from any thread other * than the one that created the window. * + * The swapchain texture is write-only and cannot be used as a sampler or for + * another reading operation. + * * \param command_buffer a command buffer. * \param window a window that has been claimed. * \param swapchain_texture a pointer filled in with a swapchain texture @@ -3938,6 +3954,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WaitForGPUSwapchain( * * \sa SDL_SubmitGPUCommandBuffer * \sa SDL_SubmitGPUCommandBufferAndAcquireFence + * \sa SDL_AcquireGPUSwapchainTexture */ extern SDL_DECLSPEC bool SDLCALL SDL_WaitAndAcquireGPUSwapchainTexture( SDL_GPUCommandBuffer *command_buffer, @@ -4128,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 ba98e74..a081535 100644 --- a/libs/SDL3/include/SDL3/SDL_hints.h +++ b/libs/SDL3/include/SDL3/SDL_hints.h @@ -1074,8 +1074,8 @@ extern "C" { * * By default, SDL will try all available GPU backends in a reasonable order * until it finds one that can work, but this hint allows the app or user to - * force a specific target, such as "direct3d11" if, say, your hardware - * supports D3D12 but want to try using D3D11 instead. + * force a specific target, such as "direct3d12" if, say, your hardware + * supports Vulkan but you want to try using D3D12 instead. * * This hint should be set before any GPU functions are called. * @@ -2026,8 +2026,8 @@ extern "C" { * * The variable can be set to the following values: * - * - "0": RAWINPUT drivers are not used. - * - "1": RAWINPUT drivers are used. (default) + * - "0": RAWINPUT drivers are not used. (default) + * - "1": RAWINPUT drivers are used. * * This hint should be set before SDL is initialized. * @@ -2191,6 +2191,28 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_ZERO_CENTERED_DEVICES "SDL_JOYSTICK_ZERO_CENTERED_DEVICES" +/** + * A variable containing a list of devices and their desired number of haptic + * (force feedback) enabled axis. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form plus the number of desired axes, e.g. + * + * `0xAAAA/0xBBBB/1,0xCCCC/0xDDDD/3` + * + * This hint supports a "wildcard" device that will set the number of haptic + * axes on all initialized haptic devices which were not defined explicitly in + * this hint. + * + * `0xFFFF/0xFFFF/1` + * + * This hint should be set before a controller is opened. The number of haptic + * axes won't exceed the number of real axes found on the device. + * + * \since This hint is available since SDL 3.2.5. + */ +#define SDL_HINT_JOYSTICK_HAPTIC_AXES "SDL_JOYSTICK_HAPTIC_AXES" + /** * A variable that controls keycode representation in keyboard events. * @@ -3585,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_init.h b/libs/SDL3/include/SDL3/SDL_init.h index adf0de8..27ebe4b 100644 --- a/libs/SDL3/include/SDL3/SDL_init.h +++ b/libs/SDL3/include/SDL3/SDL_init.h @@ -79,7 +79,7 @@ typedef Uint32 SDL_InitFlags; #define SDL_INIT_AUDIO 0x00000010u /**< `SDL_INIT_AUDIO` implies `SDL_INIT_EVENTS` */ #define SDL_INIT_VIDEO 0x00000020u /**< `SDL_INIT_VIDEO` implies `SDL_INIT_EVENTS`, should be initialized on the main thread */ -#define SDL_INIT_JOYSTICK 0x00000200u /**< `SDL_INIT_JOYSTICK` implies `SDL_INIT_EVENTS`, should be initialized on the same thread as SDL_INIT_VIDEO on Windows if you don't set SDL_HINT_JOYSTICK_THREAD */ +#define SDL_INIT_JOYSTICK 0x00000200u /**< `SDL_INIT_JOYSTICK` implies `SDL_INIT_EVENTS` */ #define SDL_INIT_HAPTIC 0x00001000u #define SDL_INIT_GAMEPAD 0x00002000u /**< `SDL_INIT_GAMEPAD` implies `SDL_INIT_JOYSTICK` */ #define SDL_INIT_EVENTS 0x00004000u diff --git a/libs/SDL3/include/SDL3/SDL_log.h b/libs/SDL3/include/SDL3/SDL_log.h index 5610420..3fd7ec2 100644 --- a/libs/SDL3/include/SDL3/SDL_log.h +++ b/libs/SDL3/include/SDL3/SDL_log.h @@ -41,8 +41,8 @@ * "system", "audio", "video", "render", "input", "test", or `*` for any * unspecified category. * - * The level can be a numeric level, one of "verbose", "debug", "info", - * "warn", "error", "critical", or "quiet" to disable that category. + * The level can be a numeric level, one of "trace", "verbose", "debug", + * "info", "warn", "error", "critical", or "quiet" to disable that category. * * You can omit the category if you want to set the logging level for all * categories. diff --git a/libs/SDL3/include/SDL3/SDL_main.h b/libs/SDL3/include/SDL3/SDL_main.h index 2e7a2eb..905d78e 100644 --- a/libs/SDL3/include/SDL3/SDL_main.h +++ b/libs/SDL3/include/SDL3/SDL_main.h @@ -28,6 +28,9 @@ * should look like this: * * ```c + * #include + * #include + * * int main(int argc, char *argv[]) * { * } @@ -38,9 +41,9 @@ * This is also where an app can be configured to use the main callbacks, via * the SDL_MAIN_USE_CALLBACKS macro. * - * This is a "single-header library," which is to say that including this - * header inserts code into your program, and you should only include it once - * in most cases. SDL.h does not include this header automatically. + * SDL_main.h is a "single-header library," which is to say that including + * this header inserts code into your program, and you should only include it + * once in most cases. SDL.h does not include this header automatically. * * For more information, see: * diff --git a/libs/SDL3/include/SDL3/SDL_pixels.h b/libs/SDL3/include/SDL3/SDL_pixels.h index b6f38ac..39596c1 100644 --- a/libs/SDL3/include/SDL3/SDL_pixels.h +++ b/libs/SDL3/include/SDL3/SDL_pixels.h @@ -517,7 +517,7 @@ typedef enum SDL_PackedLayout * ABGR32, define a platform-independent encoding into bytes in the order * specified. For example, in RGB24 data, each pixel is encoded in 3 bytes * (red, green, blue) in that order, and in ABGR32 data, each pixel is - * encoded in 4 bytes alpha, blue, green, red) in that order. Use these + * encoded in 4 bytes (alpha, blue, green, red) in that order. Use these * names if the property of a format that is important to you is the order * of the bytes in memory or on disk. * - Names with a bit count per component, such as ARGB8888 and XRGB1555, are @@ -676,6 +676,9 @@ typedef enum SDL_PixelFormat SDL_PIXELFORMAT_EXTERNAL_OES = 0x2053454fu, /**< Android video texture format */ /* SDL_DEFINE_PIXELFOURCC('O', 'E', 'S', ' ') */ + SDL_PIXELFORMAT_MJPG = 0x47504a4du, /**< Motion JPEG */ + /* SDL_DEFINE_PIXELFOURCC('M', 'J', 'P', 'G') */ + /* Aliases for RGBA byte arrays of color data, for the current platform */ #if SDL_BYTEORDER == SDL_BIG_ENDIAN SDL_PIXELFORMAT_RGBA32 = SDL_PIXELFORMAT_RGBA8888, diff --git a/libs/SDL3/include/SDL3/SDL_power.h b/libs/SDL3/include/SDL3/SDL_power.h index 4056ce3..694fb09 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_render.h b/libs/SDL3/include/SDL3/SDL_render.h index 891e994..c9d184c 100644 --- a/libs/SDL3/include/SDL3/SDL_render.h +++ b/libs/SDL3/include/SDL3/SDL_render.h @@ -490,6 +490,9 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetRendererProperties(SDL_Rende * This returns the true output size in pixels, ignoring any render targets or * logical size and presentation. * + * For the output size of the current rendering target, with logical size + * adjustments, use SDL_GetCurrentRenderOutputSize() instead. + * * \param renderer the rendering context. * \param w a pointer filled in with the width in pixels. * \param h a pointer filled in with the height in pixels. @@ -508,9 +511,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderOutputSize(SDL_Renderer *renderer, * Get the current output size in pixels of a rendering context. * * If a rendering target is active, this will return the size of the rendering - * target in pixels, otherwise if a logical size is set, it will return the - * logical size, otherwise it will return the value of - * SDL_GetRenderOutputSize(). + * target in pixels, otherwise return the value of SDL_GetRenderOutputSize(). + * + * Rendering target or not, the output will be adjusted by the current logical + * presentation state, dictated by SDL_SetRenderLogicalPresentation(). * * \param renderer the rendering context. * \param w a pointer filled in with the current width. @@ -1318,6 +1322,11 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnlockTexture(SDL_Texture *texture); * To stop rendering to a texture and render to the window again, call this * function with a NULL `texture`. * + * Viewport, cliprect, scale, and logical presentation are unique to each + * render target. Get and set functions for these states apply to the current + * render target set by this function, and those states persist on each target + * when the current render target changes. + * * \param renderer the rendering context. * \param texture the targeted texture, which must be created with the * `SDL_TEXTUREACCESS_TARGET` flag, or NULL to render to the @@ -1351,25 +1360,39 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderTarget(SDL_Renderer *renderer, SDL extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_GetRenderTarget(SDL_Renderer *renderer); /** - * Set a device independent resolution and presentation mode for rendering. + * Set a device-independent resolution and presentation mode for rendering. * * This function sets the width and height of the logical rendering output. - * The renderer will act as if the window is always the requested dimensions, - * scaling to the actual window resolution as necessary. + * The renderer will act as if the current render target is always the + * requested dimensions, scaling to the actual resolution as necessary. * * This can be useful for games that expect a fixed size, but would like to * scale the output to whatever is available, regardless of how a user resizes * a window, or if the display is high DPI. * + * Logical presentation can be used with both render target textures and the + * renderer's window; the state is unique to each render target, and this + * function sets the state for the current render target. It might be useful + * to draw to a texture that matches the window dimensions with logical + * presentation enabled, and then draw that texture across the entire window + * with logical presentation disabled. Be careful not to render both with + * logical presentation enabled, however, as this could produce + * double-letterboxing, etc. + * * You can disable logical coordinates by setting the mode to * SDL_LOGICAL_PRESENTATION_DISABLED, and in that case you get the full pixel - * resolution of the output window; it is safe to toggle logical presentation + * resolution of the render target; it is safe to toggle logical presentation * during the rendering of a frame: perhaps most of the rendering is done to * specific dimensions but to make fonts look sharp, the app turns off logical - * presentation while drawing text. + * presentation while drawing text, for example. * - * Letterboxing will only happen if logical presentation is enabled during - * SDL_RenderPresent; be sure to reenable it first if you were using it. + * For the renderer's window, letterboxing is drawn into the framebuffer if + * logical presentation is enabled during SDL_RenderPresent; be sure to + * reenable it before presenting if you were toggling it, otherwise the + * letterbox areas might have artifacts from previous frames (or artifacts + * from external overlays, etc). Letterboxing is never drawn into texture + * render targets; be sure to call SDL_RenderClear() before drawing into the + * texture so the letterboxing areas are cleared, if appropriate. * * You can convert coordinates in an event into rendering coordinates using * SDL_ConvertEventToRenderCoordinates(). @@ -1397,6 +1420,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderLogicalPresentation(SDL_Renderer * * This function gets the width and height of the logical rendering output, or * the output size in pixels if a logical resolution is not enabled. * + * Each render target has its own logical presentation state. This function + * gets the state for the current render target. + * * \param renderer the rendering context. * \param w an int to be filled with the width. * \param h an int to be filled with the height. @@ -1420,6 +1446,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderLogicalPresentation(SDL_Renderer * * presentation is disabled, it will fill the rectangle with the output size, * in pixels. * + * Each render target has its own logical presentation state. This function + * gets the rectangle for the current render target. + * * \param renderer the rendering context. * \param rect a pointer filled in with the final presentation rectangle, may * be NULL. @@ -1536,6 +1565,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ConvertEventToRenderCoordinates(SDL_Rendere * * The area's width and height must be >= 0. * + * Each render target has its own viewport. This function sets the viewport + * for the current render target. + * * \param renderer the rendering context. * \param rect the SDL_Rect structure representing the drawing area, or NULL * to set the viewport to the entire target. @@ -1554,6 +1586,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderViewport(SDL_Renderer *renderer, c /** * Get the drawing area for the current target. * + * Each render target has its own viewport. This function gets the viewport + * for the current render target. + * * \param renderer the rendering context. * \param rect an SDL_Rect structure filled in with the current drawing area. * \returns true on success or false on failure; call SDL_GetError() for more @@ -1572,8 +1607,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderViewport(SDL_Renderer *renderer, S * Return whether an explicit rectangle was set as the viewport. * * This is useful if you're saving and restoring the viewport and want to know - * whether you should restore a specific rectangle or NULL. Note that the - * viewport is always reset when changing rendering targets. + * whether you should restore a specific rectangle or NULL. + * + * Each render target has its own viewport. This function checks the viewport + * for the current render target. * * \param renderer the rendering context. * \returns true if the viewport was set to a specific rectangle, or false if @@ -1613,6 +1650,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderSafeArea(SDL_Renderer *renderer, S /** * Set the clip rectangle for rendering on the specified target. * + * Each render target has its own clip rectangle. This function sets the + * cliprect for the current render target. + * * \param renderer the rendering context. * \param rect an SDL_Rect structure representing the clip area, relative to * the viewport, or NULL to disable clipping. @@ -1631,6 +1671,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderClipRect(SDL_Renderer *renderer, c /** * Get the clip rectangle for the current target. * + * Each render target has its own clip rectangle. This function gets the + * cliprect for the current render target. + * * \param renderer the rendering context. * \param rect an SDL_Rect structure filled in with the current clipping area * or an empty rectangle if clipping is disabled. @@ -1647,7 +1690,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderClipRect(SDL_Renderer *renderer, c extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderClipRect(SDL_Renderer *renderer, SDL_Rect *rect); /** - * Get whether clipping is enabled on the given renderer. + * Get whether clipping is enabled on the given render target. + * + * Each render target has its own clip rectangle. This function checks the + * cliprect for the current render target. * * \param renderer the rendering context. * \returns true if clipping is enabled or false if not; call SDL_GetError() @@ -1673,6 +1719,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderClipEnabled(SDL_Renderer *renderer); * will be handled using the appropriate quality hints. For best results use * integer scaling factors. * + * Each render target has its own scale. This function sets the scale for the + * current render target. + * * \param renderer the rendering context. * \param scaleX the horizontal scaling factor. * \param scaleY the vertical scaling factor. @@ -1690,6 +1739,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderScale(SDL_Renderer *renderer, floa /** * Get the drawing scale for the current target. * + * Each render target has its own scale. This function gets the scale for the + * current render target. + * * \param renderer the rendering context. * \param scaleX a pointer filled in with the horizontal scaling factor. * \param scaleY a pointer filled in with the vertical scaling factor. @@ -2247,15 +2299,21 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderGeometryRaw(SDL_Renderer *renderer, /** * Read pixels from the current rendering target. * - * The returned surface should be freed with SDL_DestroySurface() + * The returned surface contains pixels inside the desired area clipped to the + * current viewport, and should be freed with SDL_DestroySurface(). + * + * Note that this returns the actual pixels on the screen, so if you are using + * logical presentation you should use SDL_GetRenderLogicalPresentationRect() + * to get the area containing your content. * * **WARNING**: This is a very slow operation, and should not be used * frequently. If you're using this on the main rendering target, it should be * called after rendering and before SDL_RenderPresent(). * * \param renderer the rendering context. - * \param rect an SDL_Rect structure representing the area in pixels relative - * to the to current viewport, or NULL for the entire viewport. + * \param rect an SDL_Rect structure representing the area to read, which will + * be clipped to the current viewport, or NULL for the entire + * viewport. * \returns a new SDL_Surface on success or NULL on failure; call * SDL_GetError() for more information. * diff --git a/libs/SDL3/include/SDL3/SDL_revision.h b/libs/SDL3/include/SDL3/SDL_revision.h index f3ce18b..bcccca1 100644 --- a/libs/SDL3/include/SDL3/SDL_revision.h +++ b/libs/SDL3/include/SDL3/SDL_revision.h @@ -48,9 +48,9 @@ */ #define SDL_REVISION "Some arbitrary string decided at SDL build time" #elif defined(SDL_VENDOR_INFO) -#define SDL_REVISION "release-3.2.4-0-gb5c3eab6b (" SDL_VENDOR_INFO ")" +#define SDL_REVISION "release-3.2.20-0-g96292a5b4 (" SDL_VENDOR_INFO ")" #else -#define SDL_REVISION "release-3.2.4-0-gb5c3eab6b" +#define SDL_REVISION "release-3.2.20-0-g96292a5b4" #endif #endif /* SDL_revision_h_ */ diff --git a/libs/SDL3/include/SDL3/SDL_scancode.h b/libs/SDL3/include/SDL3/SDL_scancode.h index 9650a6c..6e9be47 100644 --- a/libs/SDL3/include/SDL3/SDL_scancode.h +++ b/libs/SDL3/include/SDL3/SDL_scancode.h @@ -208,7 +208,7 @@ typedef enum SDL_Scancode SDL_SCANCODE_NONUSBACKSLASH = 100, /**< This is the additional key that ISO * keyboards have over ANSI ones, - * located between left shift and Y. + * located between left shift and Z. * Produces GRAVE ACCENT and TILDE in a * US or UK Mac layout, REVERSE SOLIDUS * (backslash) and VERTICAL LINE in a diff --git a/libs/SDL3/include/SDL3/SDL_stdinc.h b/libs/SDL3/include/SDL3/SDL_stdinc.h index c163928..7df253f 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 @@ -4243,14 +4255,14 @@ extern SDL_DECLSPEC int SDLCALL SDL_vasprintf(char **strp, SDL_PRINTF_FORMAT_STR /** * Seeds the pseudo-random number generator. * - * Reusing the seed number will cause SDL_rand_*() to repeat the same stream - * of 'random' numbers. + * Reusing the seed number will cause SDL_rand() to repeat the same stream of + * 'random' numbers. * * \param seed the value to use as a random number seed, or 0 to use * SDL_GetPerformanceCounter(). * * \threadsafety This should be called on the same thread that calls - * SDL_rand*() + * SDL_rand() * * \since This function is available since SDL 3.2.0. * @@ -4644,7 +4656,7 @@ extern SDL_DECLSPEC float SDLCALL SDL_atanf(float x); * * Domain: `-INF <= x <= INF`, `-INF <= y <= INF` * - * Range: `-Pi/2 <= y <= Pi/2` + * Range: `-Pi <= y <= Pi` * * This function operates on double-precision floating point values, use * SDL_atan2f for single-precision floats. @@ -4680,7 +4692,7 @@ extern SDL_DECLSPEC double SDLCALL SDL_atan2(double y, double x); * * Domain: `-INF <= x <= INF`, `-INF <= y <= INF` * - * Range: `-Pi/2 <= y <= Pi/2` + * Range: `-Pi <= y <= Pi` * * This function operates on single-precision floating point values, use * SDL_atan2 for double-precision floats. @@ -5962,14 +5974,17 @@ size_t wcslcpy(wchar_t *dst, const wchar_t *src, size_t size); size_t wcslcat(wchar_t *dst, const wchar_t *src, size_t size); #endif +#ifndef _WIN32 /* strdup is not ANSI but POSIX, and its prototype might be hidden... */ +/* not for windows: might conflict with string.h where strdup may have + * dllimport attribute: https://github.com/libsdl-org/SDL/issues/12948 */ char *strdup(const char *str); +#endif /* Starting LLVM 16, the analyser errors out if these functions do not have their prototype defined (clang-diagnostic-implicit-function-declaration) */ #include #include -#include #define SDL_malloc malloc #define SDL_calloc calloc diff --git a/libs/SDL3/include/SDL3/SDL_storage.h b/libs/SDL3/include/SDL3/SDL_storage.h index d3599b9..6837eba 100644 --- a/libs/SDL3/include/SDL3/SDL_storage.h +++ b/libs/SDL3/include/SDL3/SDL_storage.h @@ -450,7 +450,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_CloseStorage(SDL_Storage *storage); * * This function should be called in regular intervals until it returns true - * however, it is not recommended to spinwait on this call, as the backend may - * depend on a synchronous message loop. + * depend on a synchronous message loop. You might instead poll this in your + * game's main loop while processing events and drawing a loading screen. * * \param storage a storage container to query. * \returns true if the container is ready, false otherwise. diff --git a/libs/SDL3/include/SDL3/SDL_surface.h b/libs/SDL3/include/SDL3/SDL_surface.h index f2d2065..15fce04 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; @@ -120,6 +121,9 @@ typedef enum SDL_FlipMode * format with a pitch of 32 would consist of 32x32 bytes of Y plane followed * by 32x16 bytes of UV plane. * + * When a surface holds MJPG format data, pixels points at the compressed JPEG + * image and pitch is the length of that data. + * * \since This struct is available since SDL 3.2.0. * * \sa SDL_CreateSurface @@ -153,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 @@ -181,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 @@ -195,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 @@ -221,11 +231,17 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroySurface(SDL_Surface *surface); * the same tone mapping that Chrome uses for HDR content, the form "*=N", * where N is a floating point scale factor applied in linear space, and * "none", which disables tone mapping. This defaults to "chrome". + * - `SDL_PROP_SURFACE_HOTSPOT_X_NUMBER`: the hotspot pixel offset from the + * left edge of the image, if this surface is being used as a cursor. + * - `SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER`: the hotspot pixel offset from the + * top edge of the image, if this surface is being used as a cursor. * * \param surface the SDL_Surface structure to query. * \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); @@ -233,6 +249,8 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetSurfaceProperties(SDL_Surfac #define SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT "SDL.surface.SDR_white_point" #define SDL_PROP_SURFACE_HDR_HEADROOM_FLOAT "SDL.surface.HDR_headroom" #define SDL_PROP_SURFACE_TONEMAP_OPERATOR_STRING "SDL.surface.tonemap" +#define SDL_PROP_SURFACE_HOTSPOT_X_NUMBER "SDL.surface.hotspot.x" +#define SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER "SDL.surface.hotspot.y" /** * Set the colorspace used by a surface. @@ -246,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 @@ -263,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 @@ -291,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 @@ -307,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 @@ -321,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 @@ -344,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 @@ -358,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 @@ -383,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 @@ -399,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 @@ -423,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 @@ -435,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 @@ -453,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 @@ -471,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 @@ -495,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 @@ -516,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 @@ -534,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 @@ -550,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 @@ -572,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 @@ -588,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 @@ -608,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 @@ -631,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 @@ -649,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 @@ -669,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 @@ -684,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 @@ -703,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 @@ -717,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 @@ -738,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 @@ -757,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 @@ -771,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); @@ -787,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 @@ -806,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 @@ -831,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 @@ -857,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 @@ -878,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 @@ -907,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 @@ -931,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); @@ -946,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); @@ -966,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); @@ -989,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 @@ -1014,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 @@ -1027,9 +1135,6 @@ extern SDL_DECLSPEC bool SDLCALL SDL_FillSurfaceRects(SDL_Surface *dst, const SD * If either `srcrect` or `dstrect` are NULL, the entire surface (`src` or * `dst`) is copied while ensuring clipping to `dst->clip_rect`. * - * The final blit rectangles are saved in `srcrect` and `dstrect` after all - * clipping is performed. - * * The blit function should not be called on a locked surface. * * The blit semantics for surfaces with and without blending and colorkey are @@ -1087,9 +1192,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. * @@ -1112,9 +1216,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. * @@ -1137,9 +1240,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. * @@ -1163,9 +1265,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. * @@ -1178,17 +1279,17 @@ extern SDL_DECLSPEC bool SDLCALL SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src * * \param src the SDL_Surface structure to be copied from. * \param srcrect the SDL_Rect structure representing the rectangle to be - * copied, may not be NULL. + * copied, or NULL to copy the entire surface. * \param dst the SDL_Surface structure that is the blit target. * \param dstrect the SDL_Rect structure representing the target rectangle in - * the destination surface, may not be NULL. + * the destination surface, or NULL to fill the entire + * destination surface. * \param scaleMode the SDL_ScaleMode to be used. * \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. * @@ -1212,9 +1313,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. * @@ -1242,9 +1342,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. * @@ -1279,9 +1378,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. * @@ -1313,6 +1411,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 @@ -1344,6 +1444,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 @@ -1373,6 +1475,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); @@ -1397,6 +1501,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); @@ -1420,6 +1526,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); @@ -1440,6 +1548,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 2f7e955..7443d52 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 4 +#define SDL_MICRO_VERSION 20 /** * This macro turns the version numbers into a numeric value. diff --git a/libs/SDL3/include/SDL3/SDL_video.h b/libs/SDL3/include/SDL3/SDL_video.h index a7afc32..3b3676d 100644 --- a/libs/SDL3/include/SDL3/SDL_video.h +++ b/libs/SDL3/include/SDL3/SDL_video.h @@ -426,10 +426,10 @@ typedef SDL_EGLint *(SDLCALL *SDL_EGLIntArrayCallback)(void *userdata, SDL_EGLDi */ typedef enum SDL_GLAttr { - SDL_GL_RED_SIZE, /**< the minimum number of bits for the red channel of the color buffer; defaults to 3. */ - SDL_GL_GREEN_SIZE, /**< the minimum number of bits for the green channel of the color buffer; defaults to 3. */ - SDL_GL_BLUE_SIZE, /**< the minimum number of bits for the blue channel of the color buffer; defaults to 2. */ - SDL_GL_ALPHA_SIZE, /**< the minimum number of bits for the alpha channel of the color buffer; defaults to 0. */ + SDL_GL_RED_SIZE, /**< the minimum number of bits for the red channel of the color buffer; defaults to 8. */ + SDL_GL_GREEN_SIZE, /**< the minimum number of bits for the green channel of the color buffer; defaults to 8. */ + SDL_GL_BLUE_SIZE, /**< the minimum number of bits for the blue channel of the color buffer; defaults to 8. */ + SDL_GL_ALPHA_SIZE, /**< the minimum number of bits for the alpha channel of the color buffer; defaults to 8. */ SDL_GL_BUFFER_SIZE, /**< the minimum number of bits for frame buffer size; defaults to 0. */ SDL_GL_DOUBLEBUFFER, /**< whether the output is single or double buffered; defaults to double buffering on. */ SDL_GL_DEPTH_SIZE, /**< the minimum number of bits in the depth buffer; defaults to 16. */ @@ -1041,6 +1041,10 @@ extern SDL_DECLSPEC SDL_Window ** SDLCALL SDL_GetWindows(int *count); /** * Create a window with the specified dimensions and flags. * + * The window size is a request and may be different than expected based on + * the desktop layout and window manager policies. Your application should be + * prepared to handle a window of any size. + * * `flags` may be any of the following OR'd together: * * - `SDL_WINDOW_FULLSCREEN`: fullscreen window at desktop resolution @@ -1127,6 +1131,10 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindow(const char *title, int /** * Create a child popup window of the specified parent window. * + * The window size is a request and may be different than expected based on + * the desktop layout and window manager policies. Your application should be + * prepared to handle a window of any size. + * * The flags parameter **must** contain at least one of the following: * * - `SDL_WINDOW_TOOLTIP`: The popup window is a tooltip and will not pass any @@ -1159,6 +1167,15 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindow(const char *title, int * Popup windows implicitly do not have a border/decorations and do not appear * on the taskbar/dock or in lists of windows such as alt-tab menus. * + * By default, popup window positions will automatically be constrained to keep + * the entire window within display bounds. This can be overridden with the + * `SDL_PROP_WINDOW_CREATE_CONSTRAIN_POPUP_BOOLEAN` property. + * + * By default, popup menus will automatically grab keyboard focus from the parent + * when shown. This behavior can be overridden by setting the `SDL_WINDOW_NOT_FOCUSABLE` + * flag, setting the `SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN` property to false, or + * toggling it after creation via the `SDL_SetWindowFocusable()` function. + * * If a parent window is hidden or destroyed, any child popup windows will be * recursively hidden or destroyed as well. Child popup windows not explicitly * hidden will be restored when the parent is shown. @@ -1189,12 +1206,19 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren /** * Create a window with the specified properties. * + * The window size is a request and may be different than expected based on + * the desktop layout and window manager policies. Your application should be + * prepared to handle a window of any size. + * * These are the supported properties: * * - `SDL_PROP_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN`: true if the window should * be always on top * - `SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN`: true if the window has no * window decoration + * - `SDL_PROP_WINDOW_CREATE_CONSTRAIN_POPUP_BOOLEAN`: true if the "tooltip" and + * "menu" window types should be automatically constrained to be entirely within + * display bounds (default), false if no constraints on the position are desired. * - `SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN`: true if the * window will be used with an externally managed graphics context. * - `SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN`: true if the window should @@ -1309,6 +1333,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowWithProperties(SDL_Prop #define SDL_PROP_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN "SDL.window.create.always_on_top" #define SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN "SDL.window.create.borderless" +#define SDL_PROP_WINDOW_CREATE_CONSTRAIN_POPUP_BOOLEAN "SDL.window.create.constrain_popup" #define SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN "SDL.window.create.focusable" #define SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN "SDL.window.create.external_graphics_context" #define SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER "SDL.window.create.flags" diff --git a/libs/SDL3/include/SDL3/SDL_vulkan.h b/libs/SDL3/include/SDL3/SDL_vulkan.h index 710afbe..e91e148 100644 --- a/libs/SDL3/include/SDL3/SDL_vulkan.h +++ b/libs/SDL3/include/SDL3/SDL_vulkan.h @@ -51,14 +51,14 @@ extern "C" { #endif -/* Avoid including vulkan.h, don't define VkInstance if it's already included */ -#ifdef VULKAN_H_ +/* Avoid including vulkan_core.h, don't define VkInstance if it's already included */ +#ifdef VULKAN_CORE_H_ #define NO_SDL_VULKAN_TYPEDEFS #endif #ifndef NO_SDL_VULKAN_TYPEDEFS #define VK_DEFINE_HANDLE(object) typedef struct object##_T* object; -#if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) || (defined(__riscv) && __riscv_xlen == 64) #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object; #else #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object; diff --git a/libs/SDL3/include/build_config/SDL_build_config_windows.h b/libs/SDL3/include/build_config/SDL_build_config_windows.h index 4427517..98079cb 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_windows.h +++ b/libs/SDL3/include/build_config/SDL_build_config_windows.h @@ -111,6 +111,9 @@ typedef unsigned int uintptr_t; # define SDL_DISABLE_AVX 1 #endif +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 + /* This can be disabled to avoid C runtime dependencies and manifest requirements */ #ifndef HAVE_LIBC #define HAVE_LIBC 1 @@ -122,8 +125,6 @@ typedef unsigned int uintptr_t; #define HAVE_LIMITS_H 1 #define HAVE_MATH_H 1 #define HAVE_SIGNAL_H 1 -#define HAVE_STDARG_H 1 -#define HAVE_STDDEF_H 1 #define HAVE_STDIO_H 1 #define HAVE_STDLIB_H 1 #define HAVE_STRING_H 1 @@ -153,7 +154,6 @@ typedef unsigned int uintptr_t; #define HAVE_STRCMP 1 #define HAVE_STRNCMP 1 #define HAVE_STRPBRK 1 -#define HAVE_VSSCANF 1 #define HAVE_VSNPRINTF 1 #define HAVE_ACOS 1 #define HAVE_ASIN 1 @@ -211,10 +211,7 @@ typedef unsigned int uintptr_t; #if _MSC_VER >= 1400 #define HAVE__FSEEKI64 1 #endif -#endif /* _MSC_VER */ -#else -#define HAVE_STDARG_H 1 -#define HAVE_STDDEF_H 1 +#endif /* _MSC_VER */ #endif /* Enable various audio drivers */ diff --git a/libs/SDL3/include/build_config/SDL_revision.h.cmake b/libs/SDL3/include/build_config/SDL_revision.h.cmake index ea40f43..a7e6a29 100644 --- a/libs/SDL3/include/build_config/SDL_revision.h.cmake +++ b/libs/SDL3/include/build_config/SDL_revision.h.cmake @@ -33,9 +33,9 @@ #cmakedefine SDL_VENDOR_INFO "@SDL_VENDOR_INFO@" #if defined(SDL_VENDOR_INFO) -#define SDL_REVISION "release-3.2.4-0-gb5c3eab6b (" SDL_VENDOR_INFO ")" +#define SDL_REVISION "release-3.2.20-0-g96292a5b4 (" SDL_VENDOR_INFO ")" #else -#define SDL_REVISION "release-3.2.4-0-gb5c3eab6b" +#define SDL_REVISION "release-3.2.20-0-g96292a5b4" #endif #endif /* SDL_revision_h_ */ diff --git a/libs/SDL3/src/SDL.c b/libs/SDL3/src/SDL.c index e9c9fb1..46a74aa 100644 --- a/libs/SDL3/src/SDL.c +++ b/libs/SDL3/src/SDL.c @@ -65,7 +65,7 @@ // Initialization/Cleanup routines #include "timer/SDL_timer_c.h" -#ifdef SDL_VIDEO_DRIVER_WINDOWS +#ifdef SDL_PLATFORM_WINDOWS extern bool SDL_HelperWindowCreate(void); extern void SDL_HelperWindowDestroy(void); #endif @@ -317,7 +317,7 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) SDL_DBus_Init(); #endif -#ifdef SDL_VIDEO_DRIVER_WINDOWS +#ifdef SDL_PLATFORM_WINDOWS if (flags & (SDL_INIT_HAPTIC | SDL_INIT_JOYSTICK)) { if (!SDL_HelperWindowCreate()) { goto quit_and_error; @@ -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; } @@ -639,7 +653,7 @@ void SDL_Quit(void) SDL_bInMainQuit = true; // Quit all subsystems -#ifdef SDL_VIDEO_DRIVER_WINDOWS +#ifdef SDL_PLATFORM_WINDOWS SDL_HelperWindowDestroy(); #endif SDL_QuitSubSystem(SDL_INIT_EVERYTHING); diff --git a/libs/SDL3/src/SDL_assert.c b/libs/SDL3/src/SDL_assert.c index 62a90ca..2440d04 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 da9f8b5..ba4550e 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/SDL_hashtable.c b/libs/SDL3/src/SDL_hashtable.c index 571e376..3124b41 100644 --- a/libs/SDL3/src/SDL_hashtable.c +++ b/libs/SDL3/src/SDL_hashtable.c @@ -19,34 +19,6 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "SDL_internal.h" -#include "SDL_hashtable.h" - -// XXX: We can't use SDL_assert here because it's going to call into hashtable code -#ifdef NDEBUG -#define HT_ASSERT(x) (void)(0) -#else -#if (defined(_WIN32) || defined(SDL_PLATFORM_CYGWIN)) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) -#include -#endif -/* This is not declared in any header, although it is shared between some - parts of SDL, because we don't want anything calling it without an - extremely good reason. */ -extern SDL_NORETURN void SDL_ExitProcess(int exitcode); -static void HT_ASSERT_FAIL(const char *msg) -{ - const char *caption = "SDL_HashTable Assertion Failure!"; - (void)caption; -#if (defined(_WIN32) || defined(SDL_PLATFORM_CYGWIN)) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) - MessageBoxA(NULL, msg, caption, MB_OK | MB_ICONERROR); -#elif defined(HAVE_STDIO_H) - fprintf(stderr, "\n\n%s\n%s\n\n", caption, msg); - fflush(stderr); -#endif - SDL_TriggerBreakpoint(); - SDL_ExitProcess(-1); -} -#define HT_ASSERT(x) if (!(x)) HT_ASSERT_FAIL("SDL_HashTable Assertion Failure: " #x) -#endif typedef struct SDL_HashItem { @@ -67,47 +39,49 @@ SDL_COMPILE_TIME_ASSERT(sizeof_SDL_HashItem, sizeof(SDL_HashItem) <= MAX_HASHITE struct SDL_HashTable { - SDL_RWLock *lock; + SDL_RWLock *lock; // NULL if not created threadsafe SDL_HashItem *table; - SDL_HashTable_HashFn hash; - SDL_HashTable_KeyMatchFn keymatch; - SDL_HashTable_NukeFn nuke; - void *data; + SDL_HashCallback hash; + SDL_HashKeyMatchCallback keymatch; + SDL_HashDestroyCallback destroy; + void *userdata; Uint32 hash_mask; Uint32 max_probe_len; Uint32 num_occupied_slots; - bool stackable; }; -SDL_HashTable *SDL_CreateHashTable(void *data, - Uint32 num_buckets, - SDL_HashTable_HashFn hashfn, - SDL_HashTable_KeyMatchFn keymatchfn, - SDL_HashTable_NukeFn nukefn, - bool threadsafe, - bool stackable) + +static Uint32 CalculateHashBucketsFromEstimate(int estimated_capacity) { - SDL_HashTable *table; - - // num_buckets must be a power of two so we can derive the bucket index with just a bit-and. - if ((num_buckets < 1) || !SDL_HasExactlyOneBitSet32(num_buckets)) { - SDL_SetError("num_buckets must be a power of two"); - return NULL; + if (estimated_capacity <= 0) { + return 4; // start small, grow as necessary. } - if (num_buckets > MAX_HASHTABLE_SIZE) { - SDL_SetError("num_buckets is too large"); - return NULL; + const Uint32 estimated32 = (Uint32) estimated_capacity; + Uint32 buckets = ((Uint32) 1) << SDL_MostSignificantBitIndex32(estimated32); + if (!SDL_HasExactlyOneBitSet32(estimated32)) { + buckets <<= 1; // need next power of two up to fit overflow capacity bits. } - table = (SDL_HashTable *)SDL_calloc(1, sizeof(SDL_HashTable)); + return SDL_min(buckets, MAX_HASHTABLE_SIZE); +} + +SDL_HashTable *SDL_CreateHashTable(int estimated_capacity, bool threadsafe, SDL_HashCallback hash, + SDL_HashKeyMatchCallback keymatch, + SDL_HashDestroyCallback destroy, void *userdata) +{ + const Uint32 num_buckets = CalculateHashBucketsFromEstimate(estimated_capacity); + SDL_HashTable *table = (SDL_HashTable *)SDL_calloc(1, sizeof(SDL_HashTable)); if (!table) { return NULL; } if (threadsafe) { - // Don't fail if we can't create a lock (single threaded environment?) table->lock = SDL_CreateRWLock(); + if (!table->lock) { + SDL_DestroyHashTable(table); + return NULL; + } } table->table = (SDL_HashItem *)SDL_calloc(num_buckets, sizeof(SDL_HashItem)); @@ -117,24 +91,22 @@ SDL_HashTable *SDL_CreateHashTable(void *data, } table->hash_mask = num_buckets - 1; - table->stackable = stackable; - table->data = data; - table->hash = hashfn; - table->keymatch = keymatchfn; - table->nuke = nukefn; + table->userdata = userdata; + table->hash = hash; + table->keymatch = keymatch; + table->destroy = destroy; return table; } static SDL_INLINE Uint32 calc_hash(const SDL_HashTable *table, const void *key) { const Uint32 BitMixer = 0x9E3779B1u; - return table->hash(key, table->data) * BitMixer; + return table->hash(table->userdata, key) * BitMixer; } static SDL_INLINE Uint32 get_probe_length(Uint32 zero_idx, Uint32 actual_idx, Uint32 num_buckets) { // returns the probe sequence length from zero_idx to actual_idx - if (actual_idx < zero_idx) { return num_buckets - zero_idx + actual_idx; } @@ -149,7 +121,7 @@ static SDL_HashItem *find_item(const SDL_HashTable *ht, const void *key, Uint32 SDL_HashItem *table = ht->table; - for (;;) { + while (true) { SDL_HashItem *item = table + *i; Uint32 item_hash = item->hash; @@ -157,12 +129,12 @@ static SDL_HashItem *find_item(const SDL_HashTable *ht, const void *key, Uint32 return NULL; } - if (item_hash == hash && ht->keymatch(item->key, key, ht->data)) { + if (item_hash == hash && ht->keymatch(ht->userdata, item->key, key)) { return item; } Uint32 item_probe_len = item->probe_len; - HT_ASSERT(item_probe_len == get_probe_length(item_hash & hash_mask, (Uint32)(item - table), hash_mask + 1)); + SDL_assert(item_probe_len == get_probe_length(item_hash & hash_mask, (Uint32)(item - table), hash_mask + 1)); if (*probe_len > item_probe_len) { return NULL; @@ -185,23 +157,23 @@ static SDL_HashItem *find_first_item(const SDL_HashTable *ht, const void *key, U static SDL_HashItem *insert_item(SDL_HashItem *item_to_insert, SDL_HashItem *table, Uint32 hash_mask, Uint32 *max_probe_len_ptr) { + const Uint32 num_buckets = hash_mask + 1; Uint32 idx = item_to_insert->hash & hash_mask; - SDL_HashItem temp_item, *target = NULL; - Uint32 num_buckets = hash_mask + 1; + SDL_HashItem *target = NULL; + SDL_HashItem temp_item; - for (;;) { + while (true) { SDL_HashItem *candidate = table + idx; if (!candidate->live) { // Found an empty slot. Put it here and we're done. - *candidate = *item_to_insert; if (target == NULL) { target = candidate; } - Uint32 probe_len = get_probe_length(candidate->hash & hash_mask, idx, num_buckets); + const Uint32 probe_len = get_probe_length(candidate->hash & hash_mask, idx, num_buckets); candidate->probe_len = probe_len; if (*max_probe_len_ptr < probe_len) { @@ -211,9 +183,9 @@ static SDL_HashItem *insert_item(SDL_HashItem *item_to_insert, SDL_HashItem *tab break; } - Uint32 candidate_probe_len = candidate->probe_len; - HT_ASSERT(candidate_probe_len == get_probe_length(candidate->hash & hash_mask, idx, num_buckets)); - Uint32 new_probe_len = get_probe_length(item_to_insert->hash & hash_mask, idx, num_buckets); + const Uint32 candidate_probe_len = candidate->probe_len; + SDL_assert(candidate_probe_len == get_probe_length(candidate->hash & hash_mask, idx, num_buckets)); + const Uint32 new_probe_len = get_probe_length(item_to_insert->hash & hash_mask, idx, num_buckets); if (candidate_probe_len < new_probe_len) { // Robin Hood hashing: the item at idx has a better probe length than our item would at this position. @@ -229,7 +201,7 @@ static SDL_HashItem *insert_item(SDL_HashItem *item_to_insert, SDL_HashItem *tab *item_to_insert = temp_item; - HT_ASSERT(new_probe_len == get_probe_length(candidate->hash & hash_mask, idx, num_buckets)); + SDL_assert(new_probe_len == get_probe_length(candidate->hash & hash_mask, idx, num_buckets)); candidate->probe_len = new_probe_len; if (*max_probe_len_ptr < new_probe_len) { @@ -245,17 +217,19 @@ static SDL_HashItem *insert_item(SDL_HashItem *item_to_insert, SDL_HashItem *tab static void delete_item(SDL_HashTable *ht, SDL_HashItem *item) { - Uint32 hash_mask = ht->hash_mask; + const Uint32 hash_mask = ht->hash_mask; SDL_HashItem *table = ht->table; - if (ht->nuke) { - ht->nuke(item->key, item->value, ht->data); + if (ht->destroy) { + ht->destroy(ht->userdata, item->key, item->value); } + + SDL_assert(ht->num_occupied_slots > 0); ht->num_occupied_slots--; Uint32 idx = (Uint32)(item - ht->table); - for (;;) { + while (true) { idx = (idx + 1) & hash_mask; SDL_HashItem *next_item = table + idx; @@ -266,22 +240,23 @@ static void delete_item(SDL_HashTable *ht, SDL_HashItem *item) *item = *next_item; item->probe_len -= 1; - HT_ASSERT(item->probe_len < ht->max_probe_len); + SDL_assert(item->probe_len < ht->max_probe_len); item = next_item; } } static bool resize(SDL_HashTable *ht, Uint32 new_size) { - SDL_HashItem *old_table = ht->table; - Uint32 old_size = ht->hash_mask + 1; - Uint32 new_hash_mask = new_size - 1; + const Uint32 new_hash_mask = new_size - 1; SDL_HashItem *new_table = SDL_calloc(new_size, sizeof(*new_table)); if (!new_table) { return false; } + SDL_HashItem *old_table = ht->table; + const Uint32 old_size = ht->hash_mask + 1; + ht->max_probe_len = 0; ht->hash_mask = new_hash_mask; ht->table = new_table; @@ -299,14 +274,14 @@ static bool resize(SDL_HashTable *ht, Uint32 new_size) static bool maybe_resize(SDL_HashTable *ht) { - Uint32 capacity = ht->hash_mask + 1; + const Uint32 capacity = ht->hash_mask + 1; if (capacity >= MAX_HASHTABLE_SIZE) { return false; } - Uint32 max_load_factor = 217; // range: 0-255; 217 is roughly 85% - Uint32 resize_threshold = (Uint32)((max_load_factor * (Uint64)capacity) >> 8); + const Uint32 max_load_factor = 217; // range: 0-255; 217 is roughly 85% + const Uint32 resize_threshold = (Uint32)((max_load_factor * (Uint64)capacity) >> 8); if (ht->num_occupied_slots > resize_threshold) { return resize(ht, capacity * 2); @@ -315,72 +290,66 @@ static bool maybe_resize(SDL_HashTable *ht) return true; } -bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value) +bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value, bool replace) { - SDL_HashItem *item; - Uint32 hash; + if (!table) { + return SDL_InvalidParamError("table"); + } + bool result = false; - if (!table) { - return false; + SDL_LockRWLockForWriting(table->lock); + + const Uint32 hash = calc_hash(table, key); + SDL_HashItem *item = find_first_item(table, key, hash); + bool do_insert = true; + + if (item) { + if (replace) { + delete_item(table, item); + } else { + SDL_SetError("key already exists and replace is disabled"); + do_insert = false; + } } - if (table->lock) { - SDL_LockRWLockForWriting(table->lock); + if (do_insert) { + SDL_HashItem new_item; + new_item.key = key; + new_item.value = value; + new_item.hash = hash; + new_item.live = true; + new_item.probe_len = 0; + + table->num_occupied_slots++; + + if (!maybe_resize(table)) { + table->num_occupied_slots--; + } else { + // This never returns NULL + insert_item(&new_item, table->table, table->hash_mask, &table->max_probe_len); + result = true; + } } - hash = calc_hash(table, key); - item = find_first_item(table, key, hash); - - if (item && !table->stackable) { - // Allow overwrites, this might have been inserted on another thread - delete_item(table, item); - } - - SDL_HashItem new_item; - new_item.key = key; - new_item.value = value; - new_item.hash = hash; - new_item.live = true; - new_item.probe_len = 0; - - table->num_occupied_slots++; - - if (!maybe_resize(table)) { - table->num_occupied_slots--; - goto done; - } - - // This never returns NULL - insert_item(&new_item, table->table, table->hash_mask, &table->max_probe_len); - result = true; - -done: - if (table->lock) { - SDL_UnlockRWLock(table->lock); - } + SDL_UnlockRWLock(table->lock); return result; } bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **value) { - Uint32 hash; - SDL_HashItem *i; - bool result = false; - if (!table) { if (value) { *value = NULL; } - return false; + return SDL_InvalidParamError("table"); } - if (table->lock) { - SDL_LockRWLockForReading(table->lock); - } + SDL_LockRWLockForReading(table->lock); - hash = calc_hash(table, key); - i = find_first_item(table, key, hash); + bool result = false; + const Uint32 hash = calc_hash(table, key); + SDL_HashItem *i = find_first_item(table, key, hash); if (i) { if (value) { *value = i->value; @@ -388,156 +357,91 @@ bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void result = true; } - if (table->lock) { - SDL_UnlockRWLock(table->lock); - } + SDL_UnlockRWLock(table->lock); + return result; } bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key) { - Uint32 hash; - SDL_HashItem *item; - bool result = false; - if (!table) { - return false; + return SDL_InvalidParamError("table"); } - if (table->lock) { - SDL_LockRWLockForWriting(table->lock); + SDL_LockRWLockForWriting(table->lock); + + bool result = false; + const Uint32 hash = calc_hash(table, key); + SDL_HashItem *item = find_first_item(table, key, hash); + if (item) { + delete_item(table, item); + result = true; } - // FIXME: what to do for stacking hashtables? - // The original code removes just one item. - // This hashtable happens to preserve the insertion order of multi-value keys, - // so deleting the first one will always delete the least-recently inserted one. - // But maybe it makes more sense to remove all matching items? - - hash = calc_hash(table, key); - item = find_first_item(table, key, hash); - if (!item) { - goto done; - } - - delete_item(table, item); - result = true; - -done: - if (table->lock) { - SDL_UnlockRWLock(table->lock); - } + SDL_UnlockRWLock(table->lock); return result; } -bool SDL_IterateHashTableKey(const SDL_HashTable *table, const void *key, const void **_value, void **iter) +bool SDL_IterateHashTable(const SDL_HashTable *table, SDL_HashTableIterateCallback callback, void *userdata) { - SDL_HashItem *item = (SDL_HashItem *)*iter; - if (!table) { - return false; + return SDL_InvalidParamError("table"); + } else if (!callback) { + return SDL_InvalidParamError("callback"); } - Uint32 i, probe_len, hash; - - if (item) { - HT_ASSERT(item >= table->table); - HT_ASSERT(item < table->table + (table->hash_mask + 1)); - - hash = item->hash; - probe_len = item->probe_len + 1; - i = ((Uint32)(item - table->table) + 1) & table->hash_mask; - item = table->table + i; - } else { - hash = calc_hash(table, key); - i = hash & table->hash_mask; - probe_len = 0; - } - - item = find_item(table, key, hash, &i, &probe_len); - - if (!item) { - *_value = NULL; - return false; - } - - *_value = item->value; - *iter = item; - - return true; -} - -bool SDL_IterateHashTable(const SDL_HashTable *table, const void **_key, const void **_value, void **iter) -{ - SDL_HashItem *item = (SDL_HashItem *)*iter; - - if (!table) { - return false; - } - - if (!item) { - item = table->table; - } else { - item++; - } - - HT_ASSERT(item >= table->table); + SDL_LockRWLockForReading(table->lock); SDL_HashItem *end = table->table + (table->hash_mask + 1); + Uint32 num_iterated = 0; - while (item < end && !item->live) { - ++item; - } - - HT_ASSERT(item <= end); - - if (item == end) { - if (_key) { - *_key = NULL; + for (SDL_HashItem *item = table->table; item < end; item++) { + if (item->live) { + if (!callback(userdata, table, item->key, item->value)) { + break; // callback requested iteration stop. + } else if (++num_iterated >= table->num_occupied_slots) { + break; // we can drop out early because we've seen all the live items. + } } - if (_value) { - *_value = NULL; - } - return false; } - if (_key) { - *_key = item->key; - } - if (_value) { - *_value = item->value; - } - *iter = item; - + SDL_UnlockRWLock(table->lock); return true; } bool SDL_HashTableEmpty(SDL_HashTable *table) { - return !(table && table->num_occupied_slots); + if (!table) { + return SDL_InvalidParamError("table"); + } + + SDL_LockRWLockForReading(table->lock); + const bool retval = (table->num_occupied_slots == 0); + SDL_UnlockRWLock(table->lock); + return retval; } -static void nuke_all(SDL_HashTable *table) -{ - void *data = table->data; - SDL_HashItem *end = table->table + (table->hash_mask + 1); - SDL_HashItem *i; - for (i = table->table; i < end; ++i) { - if (i->live) { - table->nuke(i->key, i->value, data); +static void destroy_all(SDL_HashTable *table) +{ + SDL_HashDestroyCallback destroy = table->destroy; + if (destroy) { + void *userdata = table->userdata; + SDL_HashItem *end = table->table + (table->hash_mask + 1); + for (SDL_HashItem *i = table->table; i < end; ++i) { + if (i->live) { + i->live = false; + destroy(userdata, i->key, i->value); + } } } } -void SDL_EmptyHashTable(SDL_HashTable *table) +void SDL_ClearHashTable(SDL_HashTable *table) { if (table) { SDL_LockRWLockForWriting(table->lock); { - if (table->nuke) { - nuke_all(table); - } - + destroy_all(table); SDL_memset(table->table, 0, sizeof(*table->table) * (table->hash_mask + 1)); table->num_occupied_slots = 0; } @@ -548,9 +452,10 @@ void SDL_EmptyHashTable(SDL_HashTable *table) void SDL_DestroyHashTable(SDL_HashTable *table) { if (table) { - SDL_EmptyHashTable(table); - - SDL_DestroyRWLock(table->lock); + destroy_all(table); + if (table->lock) { + SDL_DestroyRWLock(table->lock); + } SDL_free(table->table); SDL_free(table); } @@ -566,26 +471,26 @@ static SDL_INLINE Uint32 hash_string_djbxor(const char *str, size_t len) return hash; } -Uint32 SDL_HashPointer(const void *key, void *unused) +Uint32 SDL_HashPointer(void *unused, const void *key) { (void)unused; return SDL_murmur3_32(&key, sizeof(key), 0); } -bool SDL_KeyMatchPointer(const void *a, const void *b, void *unused) +bool SDL_KeyMatchPointer(void *unused, const void *a, const void *b) { (void)unused; return (a == b); } -Uint32 SDL_HashString(const void *key, void *unused) +Uint32 SDL_HashString(void *unused, const void *key) { (void)unused; const char *str = (const char *)key; return hash_string_djbxor(str, SDL_strlen(str)); } -bool SDL_KeyMatchString(const void *a, const void *b, void *unused) +bool SDL_KeyMatchString(void *unused, const void *a, const void *b) { const char *a_string = (const char *)a; const char *b_string = (const char *)b; @@ -604,26 +509,33 @@ bool SDL_KeyMatchString(const void *a, const void *b, void *unused) // We assume we can fit the ID in the key directly SDL_COMPILE_TIME_ASSERT(SDL_HashID_KeySize, sizeof(Uint32) <= sizeof(const void *)); -Uint32 SDL_HashID(const void *key, void *unused) +Uint32 SDL_HashID(void *unused, const void *key) { (void)unused; return (Uint32)(uintptr_t)key; } -bool SDL_KeyMatchID(const void *a, const void *b, void *unused) +bool SDL_KeyMatchID(void *unused, const void *a, const void *b) { (void)unused; return (a == b); } -void SDL_NukeFreeKey(const void *key, const void *value, void *unused) +void SDL_DestroyHashKeyAndValue(void *unused, const void *key, const void *value) +{ + (void)unused; + SDL_free((void *)key); + SDL_free((void *)value); +} + +void SDL_DestroyHashKey(void *unused, const void *key, const void *value) { (void)value; (void)unused; SDL_free((void *)key); } -void SDL_NukeFreeValue(const void *key, const void *value, void *unused) +void SDL_DestroyHashValue(void *unused, const void *key, const void *value) { (void)key; (void)unused; diff --git a/libs/SDL3/src/SDL_hashtable.h b/libs/SDL3/src/SDL_hashtable.h index 5983b8d..598a6d6 100644 --- a/libs/SDL3/src/SDL_hashtable.h +++ b/libs/SDL3/src/SDL_hashtable.h @@ -18,61 +18,616 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ + +/* this is over-documented because it was almost a public API. Leaving the + full docs here in case it _does_ become public some day. */ + +/* WIKI CATEGORY: HashTable */ + +/** + * # CategoryHashTable + * + * SDL offers a hash table implementation, as a convenience for C code that + * needs efficient organization and access of arbitrary data. + * + * Hash tables are a popular data structure, designed to make it quick to + * store and look up arbitrary data. Data is stored with an associated "key." + * While one would look up an element of an array with an index, a hash table + * uses a unique key to find an element later. + * + * A key can be anything, as long as its unique and in a format that the table + * understands. For example, it's popular to use strings as keys: the key + * might be a username, and it is used to lookup account information for that + * user, etc. + * + * Hash tables are named because they "hash" their keys down into simple + * integers that can be used to efficiently organize and access the associated + * data. + * + * As this is a C API, there is one generic interface that is intended to work + * with different data types. This can be a little awkward to set up, but is + * easy to use after that. + * + * Hashtables are generated by a call to SDL_CreateHashTable(). This function + * requires several callbacks to be provided (for hashing keys, comparing + * entries, and cleaning up entries when removed). These are necessary to + * allow the hash to manage any arbitrary data type. + * + * Once a hash table is created, the common tasks are inserting data into the + * table, (SDL_InsertIntoHashTable), looking up previously inserted data + * (SDL_FindInHashTable), and removing data (SDL_RemoveFromHashTable and + * SDL_ClearHashTable). Less common but still useful is the ability to + * iterate through all the items in the table (SDL_IterateHashTable). + * + * The underlying hash table implementation is always subject to change, but + * at the time of writing, it uses open addressing and Robin Hood hashing. + * The technical details are explained [here](https://github.com/libsdl-org/SDL/pull/10897). + * + * Hashtables keep an SDL_RWLock internally, so multiple threads can perform + * hash lookups in parallel, while changes to the table will safely serialize + * access between threads. + * + * SDL provides a layer on top of this hash table implementation that might be + * more pleasant to use. SDL_PropertiesID maps a string to arbitrary data of + * various types in the same table, which could be both easier to use and more + * flexible. Refer to [CategoryProperties](CategoryProperties) for details. + */ + #ifndef SDL_hashtable_h_ #define SDL_hashtable_h_ -// this is not (currently) a public API. But maybe it should be! +#include -struct SDL_HashTable; +#include +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The opaque type that represents a hash table. + * + * This is hidden behind an opaque pointer because not only does the table + * need to store arbitrary data types, but the hash table implementation may + * change in the future. + * + * \since This struct is available since SDL 3.4.0. + * + * \sa SDL_CreateHashTable + */ typedef struct SDL_HashTable SDL_HashTable; -typedef Uint32 (*SDL_HashTable_HashFn)(const void *key, void *data); -typedef bool (*SDL_HashTable_KeyMatchFn)(const void *a, const void *b, void *data); -typedef void (*SDL_HashTable_NukeFn)(const void *key, const void *value, void *data); -extern SDL_HashTable *SDL_CreateHashTable(void *data, - Uint32 num_buckets, - SDL_HashTable_HashFn hashfn, - SDL_HashTable_KeyMatchFn keymatchfn, - SDL_HashTable_NukeFn nukefn, - bool threadsafe, - bool stackable); +/** + * A function pointer representing a hash table hashing callback. + * + * This is called by SDL_HashTable when it needs to look up a key in + * its dataset. It generates a hash value from that key, and then uses that + * value as a basis for an index into an internal array. + * + * There are no rules on what hashing algorithm is used, so long as it + * can produce a reliable 32-bit value from `key`, and ideally distributes + * those values well across the 32-bit value space. The quality of a + * hashing algorithm is directly related to how well a hash table performs. + * + * Hashing can be a complicated subject, and often times what works best + * for one dataset will be suboptimal for another. There is a good discussion + * of the field [on Wikipedia](https://en.wikipedia.org/wiki/Hash_function). + * + * Also: do you _need_ to write a hashing function? SDL provides generic + * functions for strings (SDL_HashString), generic integer IDs (SDL_HashID), + * and generic pointers (SDL_HashPointer). Often you should use one of these + * before writing your own. + * + * \param userdata what was passed as `userdata` to SDL_CreateHashTable(). + * \param key the key to be hashed. + * \returns a 32-bit value that represents a hash of `key`. + * + * \threadsafety This function must be thread safe if the hash table is used + * from multiple threads at the same time. + * + * \since This datatype is available since SDL 3.4.0. + * + * \sa SDL_CreateHashTable + * \sa SDL_HashString + * \sa SDL_HashID + * \sa SDL_HashPointer + */ +typedef Uint32 (SDLCALL *SDL_HashCallback)(void *userdata, const void *key); -// This function is thread-safe if the hashtable was created with threadsafe = true -extern void SDL_EmptyHashTable(SDL_HashTable *table); -// This function is not thread-safe. +/** + * A function pointer representing a hash table matching callback. + * + * This is called by SDL_HashTable when it needs to look up a key in its + * dataset. After hashing the key, it looks for items stored in relation to + * that hash value. Since there can be more than one item found through the + * same hash value, this function verifies a specific value is actually + * correct before choosing it. + * + * So this function needs to compare the keys at `a` and `b` and decide if + * they are actually the same. + * + * For example, if the keys are C strings, this function might just be: + * + * ```c + * return (SDL_strcmp((const char *) a, const char *b) == 0);` + * ``` + * + * Also: do you _need_ to write a matching function? SDL provides generic + * functions for strings (SDL_KeyMatchString), generic integer IDs + * (SDL_KeyMatchID), and generic pointers (SDL_KeyMatchPointer). Often you + * should use one of these before writing your own. + * + * \param userdata what was passed as `userdata` to SDL_CreateHashTable(). + * \param a the first key to be compared. + * \param b the second key to be compared. + * \returns true if two keys are identical, false otherwise. + * + * \threadsafety This function must be thread safe if the hash table is used + * from multiple threads at the same time. + * + * \since This datatype is available since SDL 3.4.0. + * + * \sa SDL_CreateHashTable + */ +typedef bool (SDLCALL *SDL_HashKeyMatchCallback)(void *userdata, const void *a, const void *b); + + +/** + * A function pointer representing a hash table cleanup callback. + * + * This is called by SDL_HashTable when removing items from the hash, or + * destroying the hash table. It is used to optionally deallocate the + * key/value pairs. + * + * This is not required to do anything, if all the data in the table is + * static or POD data, but it can also do more than a simple free: for + * example, if the hash table is storing open files, it can close them here. + * It can also free only the key or only the value; it depends on what the + * hash table contains. + * + * \param userdata what was passed as `userdata` to SDL_CreateHashTable(). + * \param key the key to deallocate. + * \param value the value to deallocate. + * + * \threadsafety This function must be thread safe if the hash table is used + * from multiple threads at the same time. + * + * \since This datatype is available since SDL 3.4.0. + * + * \sa SDL_CreateHashTable + */ +typedef void (SDLCALL *SDL_HashDestroyCallback)(void *userdata, const void *key, const void *value); + + +/** + * A function pointer representing a hash table iterator callback. + * + * This function is called once for each key/value pair to be considered + * when iterating a hash table. + * + * Iteration continues as long as there are more items to examine and this + * callback continues to return true. + * + * Do not attempt to modify the hash table during this callback, as it will + * cause incorrect behavior and possibly crashes. + * + * \param userdata what was passed as `userdata` to an iterator function. + * \param table the hash table being iterated. + * \param key the current key being iterated. + * \param value the current value being iterated. + * \returns true to keep iterating, false to stop iteration. + * + * \threadsafety A read lock is held during iteration, so other threads can + * still access the the hash table, but threads attempting to + * make changes will be blocked until iteration completes. If + * this is a concern, do as little in the callback as possible + * and finish iteration quickly. + * + * \since This datatype is available since SDL 3.4.0. + * + * \sa SDL_IterateHashTable + */ +typedef bool (SDLCALL *SDL_HashTableIterateCallback)(void *userdata, const SDL_HashTable *table, const void *key, const void *value); + + +/** + * Create a new hash table. + * + * To deal with different datatypes and needs of the caller, hash tables + * require several callbacks that deal with some specifics: how to hash a key, + * how to compare a key for equality, and how to clean up keys and values. + * SDL provides a few generic functions that can be used for these callbacks: + * + * - SDL_HashString and SDL_KeyMatchString for C strings. + * - SDL_HashPointer and SDL_KeyMatchPointer for generic pointers. + * - SDL_HashID and SDL_KeyMatchID for generic (possibly small) integers. + * + * Oftentimes, these are all you need for any hash table, but depending on + * your dataset, custom implementations might make more sense. + * + * You can specify an estimate of the number of items expected to be stored + * in the table, which can help make the table run more efficiently. The table + * will preallocate resources to accomodate this number of items, which is + * most useful if you intend to fill the table with a lot of data right after + * creating it. Otherwise, it might make more sense to specify the _minimum_ + * you expect the table to hold and let it grow as necessary from there. This + * number is only a hint, and the table will be able to handle any amount of + * data--as long as the system doesn't run out of resources--so a perfect + * answer is not required. A value of 0 signifies no guess at all, and the + * table will start small and reallocate as necessary; often this is the + * correct thing to do. + * + * !!! FIXME: add note about `threadsafe` here. And update `threadsafety` tags. + * !!! FIXME: note that `threadsafe` tables can't be recursively locked, so + * !!! FIXME: you can't use `destroy` callbacks that might end up relocking. + * + * Note that SDL provides a higher-level option built on its hash tables: + * SDL_PropertiesID lets you map strings to various datatypes, and this + * might be easier to use. It only allows strings for keys, however. Those are + * created with SDL_CreateProperties(). + * + * The returned hash table should be destroyed with SDL_DestroyHashTable() + * when no longer needed. + * + * \param estimated_capacity the approximate maximum number of items to be held + * in the hash table, or 0 for no estimate. + * \param threadsafe true to create an internal rwlock for this table. + * \param hash the function to use to hash keys. + * \param keymatch the function to use to compare keys. + * \param destroy the function to use to clean up keys and values, may be NULL. + * \param userdata a pointer that is passed to the callbacks. + * \returns a newly-created hash table, or NULL if there was an error; 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.4.0. + * + * \sa SDL_DestroyHashTable + */ +extern SDL_HashTable * SDL_CreateHashTable(int estimated_capacity, + bool threadsafe, + SDL_HashCallback hash, + SDL_HashKeyMatchCallback keymatch, + SDL_HashDestroyCallback destroy, + void *userdata); + + +/** + * Destroy a hash table. + * + * This will call the hash table's SDL_HashDestroyCallback for each item in + * the table, removing all inserted items, before deallocating the table + * itself. + * + * The table becomes invalid once this function is called, and no other thread + * should be accessing this table once this function has started. + * + * \param table the hash table to destroy. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ extern void SDL_DestroyHashTable(SDL_HashTable *table); -// This function is thread-safe if the hashtable was created with threadsafe = true -extern bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value); +/** + * Add an item to a hash table. + * + * All keys in the table must be unique. If attempting to insert a key that + * already exists in the hash table, what will be done depends on the + * `replace` value: + * + * - If `replace` is false, this function will return false without modifying + * the table. + * - If `replace` is true, SDL will remove the previous item first, so the new + * value is the only one associated with that key. This will call the hash + * table's SDL_HashDestroyCallback for the previous item. + * + * \param table the hash table to insert into. + * \param key the key of the new item to insert. + * \param value the value of the new item to insert. + * \param replace true if a duplicate key should replace the previous value. + * \returns true if the new item was inserted, false otherwise. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value, bool replace); -// This function is thread-safe if the hashtable was created with threadsafe = true +/** + * Look up an item in a hash table. + * + * On return, the value associated with `key` is stored to `*value`. + * If the key does not exist in the table, `*value` will be set to NULL. + * + * It is legal for `value` to be NULL, to not retrieve the key's value. In + * this case, the return value is still useful for reporting if the key exists + * in the table at all. + * + * \param table the hash table to search. + * \param key the key to search for in the table. + * \param value the found value will be stored here. Can be NULL. + * \returns true if key exists in the table, false otherwise. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_InsertIntoHashTable + */ +extern bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **value); + +/** + * Remove an item from a hash table. + * + * If there is an item that matches `key`, it is removed from the table. This + * will call the hash table's SDL_HashDestroyCallback for the item to be + * removed. + * + * \param table the hash table to remove from. + * \param key the key of the item to remove from the table. + * \returns true if a key was removed, false if the key was not found. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ extern bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key); -// This function is thread-safe if the hashtable was created with threadsafe = true -extern bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **_value); +/** + * Remove all items in a hash table. + * + * This will call the hash table's SDL_HashDestroyCallback for each item in + * the table, removing all inserted items. + * + * When this function returns, the hash table will be empty. + * + * \param table the hash table to clear. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern void SDL_ClearHashTable(SDL_HashTable *table); -// This function is thread-safe if the hashtable was created with threadsafe = true +/** + * Check if any items are currently stored in a hash table. + * + * If there are no items stored (the table is completely empty), this will + * return true. + * + * \param table the hash table to check. + * \returns true if the table is completely empty, false otherwise. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_ClearHashTable + */ extern bool SDL_HashTableEmpty(SDL_HashTable *table); -// iterate all values for a specific key. This only makes sense if the hash is stackable. If not-stackable, just use SDL_FindInHashTable(). -// This function is not thread-safe, you should use external locking if you use this function -extern bool SDL_IterateHashTableKey(const SDL_HashTable *table, const void *key, const void **_value, void **iter); +/** + * Iterate all key/value pairs in a hash table. + * + * This function will call `callback` once for each key/value pair in the + * table, until either all pairs have been presented to the callback, or the + * callback has returned false to signal it is done. + * + * There is no guarantee what order results will be returned in. + * + * \param table the hash table to iterate. + * \param callback the function pointer to call for each value. + * \param userdata a pointer that is passed to `callback`. + * \returns true if iteration happened, false if not (bogus parameter, etc). + * + * \since This function is available since SDL 3.4.0. + */ +extern bool SDL_IterateHashTable(const SDL_HashTable *table, SDL_HashTableIterateCallback callback, void *userdata); -// iterate all key/value pairs in a hash (stackable hashes can have duplicate keys with multiple values). -// This function is not thread-safe, you should use external locking if you use this function -extern bool SDL_IterateHashTable(const SDL_HashTable *table, const void **_key, const void **_value, void **iter); -extern Uint32 SDL_HashPointer(const void *key, void *unused); -extern bool SDL_KeyMatchPointer(const void *a, const void *b, void *unused); +/* Helper functions for SDL_CreateHashTable callbacks... */ -extern Uint32 SDL_HashString(const void *key, void *unused); -extern bool SDL_KeyMatchString(const void *a, const void *b, void *unused); +/** + * Generate a hash from a generic pointer. + * + * The key is intended to be a unique pointer to any datatype. + * + * This is intended to be used as one of the callbacks to SDL_CreateHashTable, + * if this is useful to the type of keys to be used with the hash table. + * + * Note that the implementation may change in the future; do not expect + * the results to be stable vs future SDL releases. Use this in a hash table + * in the current process and don't store them to disk for the future. + * + * \param unused this parameter is ignored. + * \param key the key to hash as a generic pointer. + * \returns a 32-bit hash of the key. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreateHashTable + */ +extern Uint32 SDL_HashPointer(void *unused, const void *key); -extern Uint32 SDL_HashID(const void *key, void *unused); -extern bool SDL_KeyMatchID(const void *a, const void *b, void *unused); +/** + * Compare two generic pointers as hash table keys. + * + * This is intended to be used as one of the callbacks to SDL_CreateHashTable, + * if this is useful to the type of keys to be used with the hash table. + * + * \param unused this parameter is ignored. + * \param a the first generic pointer to compare. + * \param b the second generic pointer to compare. + * \returns true if the pointers are the same, false otherwise. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreateHashTable + */ +extern bool SDL_KeyMatchPointer(void *unused, const void *a, const void *b); -extern void SDL_NukeFreeKey(const void *key, const void *value, void *unused); -extern void SDL_NukeFreeValue(const void *key, const void *value, void *unused); +/** + * Generate a hash from a C string. + * + * The key is intended to be a NULL-terminated string, in UTF-8 format. + * + * This is intended to be used as one of the callbacks to SDL_CreateHashTable, + * if this is useful to the type of keys to be used with the hash table. + * + * Note that the implementation may change in the future; do not expect + * the results to be stable vs future SDL releases. Use this in a hash table + * in the current process and don't store them to disk for the future. + * + * \param unused this parameter is ignored. + * \param key the key to hash as a generic pointer. + * \returns a 32-bit hash of the key. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreateHashTable + */ +extern Uint32 SDL_HashString(void *unused, const void *key); -#endif // SDL_hashtable_h_ +/** + * Compare two C strings as hash table keys. + * + * Strings will be compared in a case-sensitive manner. More specifically, + * they'll be compared as NULL-terminated arrays of bytes. + * + * This is intended to be used as one of the callbacks to SDL_CreateHashTable, + * if this is useful to the type of keys to be used with the hash table. + * + * \param unused this parameter is ignored. + * \param a the first string to compare. + * \param b the second string to compare. + * \returns true if the strings are the same, false otherwise. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreateHashTable + */ +extern bool SDL_KeyMatchString(void *unused, const void *a, const void *b); + +/** + * Generate a hash from an integer ID. + * + * The key is intended to a unique integer, possibly within a small range. + * + * This is intended to be used as one of the callbacks to SDL_CreateHashTable, + * if this is useful to the type of keys to be used with the hash table. + * + * Note that the implementation may change in the future; do not expect + * the results to be stable vs future SDL releases. Use this in a hash table + * in the current process and don't store them to disk for the future. + * + * \param unused this parameter is ignored. + * \param key the key to hash as a generic pointer. + * \returns a 32-bit hash of the key. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreateHashTable + */ +extern Uint32 SDL_HashID(void *unused, const void *key); + +/** + * Compare two integer IDs as hash table keys. + * + * This is intended to be used as one of the callbacks to SDL_CreateHashTable, + * if this is useful to the type of keys to be used with the hash table. + * + * \param unused this parameter is ignored. + * \param a the first ID to compare. + * \param b the second ID to compare. + * \returns true if the IDs are the same, false otherwise. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreateHashTable + */ +extern bool SDL_KeyMatchID(void *unused, const void *a, const void *b); + +/** + * Free both the key and value pointers of a hash table item. + * + * This is intended to be used as one of the callbacks to SDL_CreateHashTable, + * if this is useful to the type of data to be used with the hash table. + * + * This literally calls `SDL_free(key);` and `SDL_free(value);`. + * + * \param unused this parameter is ignored. + * \param key the key to be destroyed. + * \param value the value to be destroyed. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreateHashTable + */ +extern void SDL_DestroyHashKeyAndValue(void *unused, const void *key, const void *value); + +/** + * Free just the value pointer of a hash table item. + * + * This is intended to be used as one of the callbacks to SDL_CreateHashTable, + * if this is useful to the type of data to be used with the hash table. + * + * This literally calls `SDL_free(key);` and leaves `value` alone. + * + * \param unused this parameter is ignored. + * \param key the key to be destroyed. + * \param value the value to be destroyed. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreateHashTable + */ +extern void SDL_DestroyHashKey(void *unused, const void *key, const void *value); + +/** + * Free just the value pointer of a hash table item. + * + * This is intended to be used as one of the callbacks to SDL_CreateHashTable, + * if this is useful to the type of data to be used with the hash table. + * + * This literally calls `SDL_free(value);` and leaves `key` alone. + * + * \param unused this parameter is ignored. + * \param key the key to be destroyed. + * \param value the value to be destroyed. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreateHashTable + */ +extern void SDL_DestroyHashValue(void *unused, const void *key, const void *value); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include + +#endif /* SDL_hashtable_h_ */ diff --git a/libs/SDL3/src/SDL_hints.c b/libs/SDL3/src/SDL_hints.c index 9c4ccd4..a10598f 100644 --- a/libs/SDL3/src/SDL_hints.c +++ b/libs/SDL3/src/SDL_hints.c @@ -22,6 +22,10 @@ #include "SDL_hints_c.h" +#ifdef SDL_PLATFORM_ANDROID +#include "core/android/SDL_android.h" +#endif + typedef struct SDL_HintWatch { SDL_HintCallback callback; @@ -147,6 +151,13 @@ bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriori } } +#ifdef SDL_PLATFORM_ANDROID + if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) { + // Special handling for this hint, which needs to persist outside the normal application flow + Android_SetAllowRecreateActivity(SDL_GetStringBoolean(value, false)); + } +#endif // SDL_PLATFORM_ANDROID + SDL_UnlockProperties(hints); return result; @@ -185,6 +196,17 @@ bool SDL_ResetHint(const char *name) result = true; } +#ifdef SDL_PLATFORM_ANDROID + if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) { + // Special handling for this hint, which needs to persist outside the normal application flow + if (env) { + Android_SetAllowRecreateActivity(SDL_GetStringBoolean(env, false)); + } else { + Android_SetAllowRecreateActivity(false); + } + } +#endif // SDL_PLATFORM_ANDROID + SDL_UnlockProperties(hints); return result; @@ -210,6 +232,17 @@ static void SDLCALL ResetHintsCallback(void *userdata, SDL_PropertiesID hints, c SDL_free(hint->value); hint->value = NULL; hint->priority = SDL_HINT_DEFAULT; + +#ifdef SDL_PLATFORM_ANDROID + if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) { + // Special handling for this hint, which needs to persist outside the normal application flow + if (env) { + Android_SetAllowRecreateActivity(SDL_GetStringBoolean(env, false)); + } else { + Android_SetAllowRecreateActivity(false); + } + } +#endif // SDL_PLATFORM_ANDROID } void SDL_ResetHints(void) diff --git a/libs/SDL3/src/SDL_internal.h b/libs/SDL3/src/SDL_internal.h index ac018f9..8fcd96a 100644 --- a/libs/SDL3/src/SDL_internal.h +++ b/libs/SDL3/src/SDL_internal.h @@ -191,6 +191,11 @@ #define SDL_VIDEO_RENDER_SW 1 #endif +/* STB image conversion */ +#if !defined(SDL_HAVE_STB) && !defined(SDL_LEAN_AND_MEAN) +#define SDL_HAVE_STB 1 +#endif + /* YUV formats - handling of YUV surfaces - blitting and conversion functions */ @@ -198,6 +203,17 @@ #define SDL_HAVE_YUV 1 #endif +#ifdef SDL_CAMERA_DISABLED +#undef SDL_CAMERA_DRIVER_ANDROID +#undef SDL_CAMERA_DRIVER_COREMEDIA +#undef SDL_CAMERA_DRIVER_DUMMY +#undef SDL_CAMERA_DRIVER_EMSCRIPTEN +#undef SDL_CAMERA_DRIVER_MEDIAFOUNDATION +#undef SDL_CAMERA_DRIVER_PIPEWIRE +#undef SDL_CAMERA_DRIVER_V4L2 +#undef SDL_CAMERA_DRIVER_VITA +#endif + #ifdef SDL_RENDER_DISABLED #undef SDL_VIDEO_RENDER_SW #undef SDL_VIDEO_RENDER_D3D @@ -249,6 +265,12 @@ extern "C" { #include "SDL_utils_c.h" #include "SDL_hashtable.h" +#define PUSH_SDL_ERROR() \ + { char *_error = SDL_strdup(SDL_GetError()); + +#define POP_SDL_ERROR() \ + SDL_SetError("%s", _error); SDL_free(_error); } + // Do any initialization that needs to happen before threads are started extern void SDL_InitMainThread(void); diff --git a/libs/SDL3/src/SDL_properties.c b/libs/SDL3/src/SDL_properties.c index 41c81e9..166ea0f 100644 --- a/libs/SDL3/src/SDL_properties.c +++ b/libs/SDL3/src/SDL_properties.c @@ -76,7 +76,7 @@ static void SDL_FreePropertyWithCleanup(const void *key, const void *value, void SDL_free((void *)value); } -static void SDL_FreeProperty(const void *key, const void *value, void *data) +static void SDLCALL SDL_FreeProperty(void *data, const void *key, const void *value) { SDL_FreePropertyWithCleanup(key, value, data, true); } @@ -84,14 +84,8 @@ static void SDL_FreeProperty(const void *key, const void *value, void *data) static void SDL_FreeProperties(SDL_Properties *properties) { if (properties) { - if (properties->props) { - SDL_DestroyHashTable(properties->props); - properties->props = NULL; - } - if (properties->lock) { - SDL_DestroyMutex(properties->lock); - properties->lock = NULL; - } + SDL_DestroyHashTable(properties->props); + SDL_DestroyMutex(properties->lock); SDL_free(properties); } } @@ -102,18 +96,16 @@ bool SDL_InitProperties(void) return true; } - SDL_properties = SDL_CreateHashTable(NULL, 16, SDL_HashID, SDL_KeyMatchID, NULL, true, false); - if (!SDL_properties) { - goto error; - } + SDL_properties = SDL_CreateHashTable(0, true, SDL_HashID, SDL_KeyMatchID, NULL, NULL); + const bool initialized = (SDL_properties != NULL); + SDL_SetInitialized(&SDL_properties_init, initialized); + return initialized; +} - SDL_SetInitialized(&SDL_properties_init, true); - return true; - -error: - SDL_SetInitialized(&SDL_properties_init, true); - SDL_QuitProperties(); - return false; +static bool SDLCALL FreeOneProperties(void *userdata, const SDL_HashTable *table, const void *key, const void *value) +{ + SDL_FreeProperties((SDL_Properties *)value); + return true; // keep iterating. } void SDL_QuitProperties(void) @@ -131,17 +123,13 @@ void SDL_QuitProperties(void) SDL_DestroyProperties(props); } - if (SDL_properties) { - void *iter; - const void *key, *value; - - iter = NULL; - while (SDL_IterateHashTable(SDL_properties, &key, &value, &iter)) { - SDL_FreeProperties((SDL_Properties *)value); - } - SDL_DestroyHashTable(SDL_properties); - SDL_properties = NULL; - } + // this can't just DestroyHashTable with SDL_FreeProperties as the destructor, because + // other destructors under this might cause use to attempt a recursive lock on SDL_properties, + // which isn't allowed with rwlocks. So manually iterate and free everything. + SDL_HashTable *properties = SDL_properties; + SDL_properties = NULL; + SDL_IterateHashTable(properties, FreeOneProperties, NULL); + SDL_DestroyHashTable(properties); SDL_SetInitialized(&SDL_properties_init, false); } @@ -167,55 +155,101 @@ SDL_PropertiesID SDL_GetGlobalProperties(void) SDL_PropertiesID SDL_CreateProperties(void) { - SDL_PropertiesID props = 0; - SDL_Properties *properties = NULL; - bool inserted = false; - if (!SDL_CheckInitProperties()) { return 0; } - properties = (SDL_Properties *)SDL_calloc(1, sizeof(*properties)); + SDL_Properties *properties = (SDL_Properties *)SDL_calloc(1, sizeof(*properties)); if (!properties) { - goto error; - } - properties->props = SDL_CreateHashTable(NULL, 4, SDL_HashString, SDL_KeyMatchString, SDL_FreeProperty, false, false); - if (!properties->props) { - goto error; + return 0; } - // If this fails we'll continue without it. properties->lock = SDL_CreateMutex(); + if (!properties->lock) { + SDL_free(properties); + return 0; + } - for ( ; ; ) { + properties->props = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_FreeProperty, NULL); + if (!properties->props) { + SDL_DestroyMutex(properties->lock); + SDL_free(properties); + return 0; + } + + SDL_PropertiesID props = 0; + while (true) { props = (SDL_GetAtomicU32(&SDL_last_properties_id) + 1); if (props == 0) { continue; - } - if (SDL_CompareAndSwapAtomicU32(&SDL_last_properties_id, props - 1, props)) { + } else if (SDL_CompareAndSwapAtomicU32(&SDL_last_properties_id, props - 1, props)) { break; } } - if (SDL_InsertIntoHashTable(SDL_properties, (const void *)(uintptr_t)props, properties)) { - inserted = true; + + SDL_assert(!SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, NULL)); // should NOT be in the hash table already. + + if (!SDL_InsertIntoHashTable(SDL_properties, (const void *)(uintptr_t)props, properties, false)) { + SDL_FreeProperties(properties); + return 0; } - if (inserted) { - // All done! - return props; + return props; // All done! +} + +typedef struct CopyOnePropertyData +{ + SDL_Properties *dst_properties; + bool result; +} CopyOnePropertyData; + +static bool SDLCALL CopyOneProperty(void *userdata, const SDL_HashTable *table, const void *key, const void *value) +{ + const SDL_Property *src_property = (const SDL_Property *)value; + if (src_property->cleanup) { + // Can't copy properties with cleanup functions, we don't know how to duplicate the data + return true; // keep iterating. } -error: - SDL_FreeProperties(properties); - return 0; + CopyOnePropertyData *data = (CopyOnePropertyData *) userdata; + SDL_Properties *dst_properties = data->dst_properties; + const char *src_name = (const char *)key; + SDL_Property *dst_property; + + char *dst_name = SDL_strdup(src_name); + if (!dst_name) { + data->result = false; + return true; // keep iterating (I guess...?) + } + + dst_property = (SDL_Property *)SDL_malloc(sizeof(*dst_property)); + if (!dst_property) { + SDL_free(dst_name); + data->result = false; + return true; // keep iterating (I guess...?) + } + + SDL_copyp(dst_property, src_property); + if (src_property->type == SDL_PROPERTY_TYPE_STRING) { + dst_property->value.string_value = SDL_strdup(src_property->value.string_value); + if (!dst_property->value.string_value) { + SDL_free(dst_name); + SDL_free(dst_property); + data->result = false; + return true; // keep iterating (I guess...?) + } + } + + if (!SDL_InsertIntoHashTable(dst_properties->props, dst_name, dst_property, true)) { + SDL_FreePropertyWithCleanup(dst_name, dst_property, NULL, false); + data->result = false; + } + + return true; // keep iterating. } bool SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst) { - SDL_Properties *src_properties = NULL; - SDL_Properties *dst_properties = NULL; - bool result = true; - if (!src) { return SDL_InvalidParamError("src"); } @@ -223,55 +257,25 @@ bool SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst) return SDL_InvalidParamError("dst"); } + SDL_Properties *src_properties = NULL; + SDL_Properties *dst_properties = NULL; + SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)src, (const void **)&src_properties); - SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)dst, (const void **)&dst_properties); if (!src_properties) { return SDL_InvalidParamError("src"); } + SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)dst, (const void **)&dst_properties); if (!dst_properties) { return SDL_InvalidParamError("dst"); } + bool result = true; SDL_LockMutex(src_properties->lock); SDL_LockMutex(dst_properties->lock); { - void *iter; - const void *key, *value; - - iter = NULL; - while (SDL_IterateHashTable(src_properties->props, &key, &value, &iter)) { - const char *src_name = (const char *)key; - const SDL_Property *src_property = (const SDL_Property *)value; - char *dst_name; - SDL_Property *dst_property; - - if (src_property->cleanup) { - // Can't copy properties with cleanup functions, we don't know how to duplicate the data - continue; - } - - SDL_RemoveFromHashTable(dst_properties->props, src_name); - - dst_name = SDL_strdup(src_name); - if (!dst_name) { - result = false; - continue; - } - dst_property = (SDL_Property *)SDL_malloc(sizeof(*dst_property)); - if (!dst_property) { - SDL_free(dst_name); - result = false; - continue; - } - SDL_copyp(dst_property, src_property); - if (src_property->type == SDL_PROPERTY_TYPE_STRING) { - dst_property->value.string_value = SDL_strdup(src_property->value.string_value); - } - if (!SDL_InsertIntoHashTable(dst_properties->props, dst_name, dst_property)) { - SDL_FreePropertyWithCleanup(dst_name, dst_property, NULL, false); - result = false; - } - } + CopyOnePropertyData data = { dst_properties, true }; + SDL_IterateHashTable(src_properties->props, CopyOneProperty, &data); + result = data.result; } SDL_UnlockMutex(dst_properties->lock); SDL_UnlockMutex(src_properties->lock); @@ -337,7 +341,7 @@ static bool SDL_PrivateSetProperty(SDL_PropertiesID props, const char *name, SDL SDL_RemoveFromHashTable(properties->props, name); if (property) { char *key = SDL_strdup(name); - if (!SDL_InsertIntoHashTable(properties->props, key, property)) { + if (!key || !SDL_InsertIntoHashTable(properties->props, key, property, false)) { SDL_FreePropertyWithCleanup(key, property, NULL, true); result = false; } @@ -518,10 +522,9 @@ void *SDL_GetPointerProperty(SDL_PropertiesID props, const char *name, void *def return value; } - /* Note that taking the lock here only guarantees that we won't read the - * hashtable while it's being modified. The value itself can easily be - * freed from another thread after it is returned here. - */ + // Note that taking the lock here only guarantees that we won't read the + // hashtable while it's being modified. The value itself can easily be + // freed from another thread after it is returned here. SDL_LockMutex(properties->lock); { SDL_Property *property = NULL; @@ -731,6 +734,23 @@ bool SDL_ClearProperty(SDL_PropertiesID props, const char *name) return SDL_PrivateSetProperty(props, name, NULL); } +typedef struct EnumerateOnePropertyData +{ + SDL_EnumeratePropertiesCallback callback; + void *userdata; + SDL_PropertiesID props; +} EnumerateOnePropertyData; + + +static bool SDLCALL EnumerateOneProperty(void *userdata, const SDL_HashTable *table, const void *key, const void *value) +{ + (void) table; + (void) value; + const EnumerateOnePropertyData *data = (const EnumerateOnePropertyData *) userdata; + data->callback(data->userdata, data->props, (const char *)key); + return true; // keep iterating. +} + bool SDL_EnumerateProperties(SDL_PropertiesID props, SDL_EnumeratePropertiesCallback callback, void *userdata) { SDL_Properties *properties = NULL; @@ -749,13 +769,8 @@ bool SDL_EnumerateProperties(SDL_PropertiesID props, SDL_EnumeratePropertiesCall SDL_LockMutex(properties->lock); { - void *iter; - const void *key, *value; - - iter = NULL; - while (SDL_IterateHashTable(properties->props, &key, &value, &iter)) { - callback(userdata, props, (const char *)key); - } + EnumerateOnePropertyData data = { callback, userdata, props }; + SDL_IterateHashTable(properties->props, EnumerateOneProperty, &data); } SDL_UnlockMutex(properties->lock); @@ -796,14 +811,14 @@ bool SDL_DumpProperties(SDL_PropertiesID props) void SDL_DestroyProperties(SDL_PropertiesID props) { - SDL_Properties *properties = NULL; - - if (!props) { - return; - } - - if (SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties)) { - SDL_FreeProperties(properties); - SDL_RemoveFromHashTable(SDL_properties, (const void *)(uintptr_t)props); + if (props) { + // this can't just use RemoveFromHashTable with SDL_FreeProperties as the destructor, because + // other destructors under this might cause use to attempt a recursive lock on SDL_properties, + // which isn't allowed with rwlocks. So manually look it up and remove/free it. + SDL_Properties *properties = NULL; + if (SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties)) { + SDL_FreeProperties(properties); + SDL_RemoveFromHashTable(SDL_properties, (const void *)(uintptr_t)props); + } } } diff --git a/libs/SDL3/src/SDL_utils.c b/libs/SDL3/src/SDL_utils.c index fb82aac..f209074 100644 --- a/libs/SDL3/src/SDL_utils.c +++ b/libs/SDL3/src/SDL_utils.c @@ -24,6 +24,9 @@ #include #endif +#include "joystick/SDL_joystick_c.h" // For SDL_GetGamepadTypeFromVIDPID() + + // Common utility functions that aren't in the public API int SDL_powerof2(int x) @@ -135,12 +138,12 @@ Uint32 SDL_GetNextObjectID(void) static SDL_InitState SDL_objects_init; static SDL_HashTable *SDL_objects; -static Uint32 SDL_HashObject(const void *key, void *unused) +static Uint32 SDLCALL SDL_HashObject(void *unused, const void *key) { return (Uint32)(uintptr_t)key; } -static bool SDL_KeyMatchObject(const void *a, const void *b, void *unused) +static bool SDL_KeyMatchObject(void *unused, const void *a, const void *b) { return (a == b); } @@ -149,16 +152,17 @@ void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid) { SDL_assert(object != NULL); - if (valid && SDL_ShouldInit(&SDL_objects_init)) { - SDL_objects = SDL_CreateHashTable(NULL, 32, SDL_HashObject, SDL_KeyMatchObject, NULL, true, false); - if (!SDL_objects) { - SDL_SetInitialized(&SDL_objects_init, false); + if (SDL_ShouldInit(&SDL_objects_init)) { + SDL_objects = SDL_CreateHashTable(0, true, SDL_HashObject, SDL_KeyMatchObject, NULL, NULL); + const bool initialized = (SDL_objects != NULL); + SDL_SetInitialized(&SDL_objects_init, initialized); + if (!initialized) { + return; } - SDL_SetInitialized(&SDL_objects_init, true); } if (valid) { - SDL_InsertIntoHashTable(SDL_objects, object, (void *)(uintptr_t)type); + SDL_InsertIntoHashTable(SDL_objects, object, (void *)(uintptr_t)type, true); } else { SDL_RemoveFromHashTable(SDL_objects, object); } @@ -178,75 +182,65 @@ bool SDL_ObjectValid(void *object, SDL_ObjectType type) return (((SDL_ObjectType)(uintptr_t)object_type) == type); } +typedef struct GetOneObjectData +{ + const SDL_ObjectType type; + void **objects; + const int count; + int num_objects; +} GetOneObjectData; + +static bool SDLCALL GetOneObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type) +{ + GetOneObjectData *data = (GetOneObjectData *) userdata; + if ((SDL_ObjectType)(uintptr_t)object_type == data->type) { + if (data->num_objects < data->count) { + data->objects[data->num_objects] = (void *)object; + } + ++data->num_objects; + } + return true; // keep iterating. +} + + int SDL_GetObjects(SDL_ObjectType type, void **objects, int count) { - const void *object, *object_type; - void *iter = NULL; - int num_objects = 0; - while (SDL_IterateHashTable(SDL_objects, &object, &object_type, &iter)) { - if ((SDL_ObjectType)(uintptr_t)object_type == type) { - if (num_objects < count) { - objects[num_objects] = (void *)object; - } - ++num_objects; - } + GetOneObjectData data = { type, objects, count, 0 }; + SDL_IterateHashTable(SDL_objects, GetOneObject, &data); + return data.num_objects; +} + +static bool SDLCALL LogOneLeakedObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type) +{ + const char *type = "unknown object"; + switch ((SDL_ObjectType)(uintptr_t)object_type) { + #define SDLOBJTYPECASE(typ, name) case SDL_OBJECT_TYPE_##typ: type = name; break + SDLOBJTYPECASE(WINDOW, "SDL_Window"); + SDLOBJTYPECASE(RENDERER, "SDL_Renderer"); + SDLOBJTYPECASE(TEXTURE, "SDL_Texture"); + SDLOBJTYPECASE(JOYSTICK, "SDL_Joystick"); + SDLOBJTYPECASE(GAMEPAD, "SDL_Gamepad"); + SDLOBJTYPECASE(HAPTIC, "SDL_Haptic"); + SDLOBJTYPECASE(SENSOR, "SDL_Sensor"); + SDLOBJTYPECASE(HIDAPI_DEVICE, "hidapi device"); + SDLOBJTYPECASE(HIDAPI_JOYSTICK, "hidapi joystick"); + SDLOBJTYPECASE(THREAD, "thread"); + SDLOBJTYPECASE(TRAY, "SDL_Tray"); + #undef SDLOBJTYPECASE + default: break; } - return num_objects; + SDL_Log("Leaked %s (%p)", type, object); + return true; // keep iterating. } void SDL_SetObjectsInvalid(void) { if (SDL_ShouldQuit(&SDL_objects_init)) { // Log any leaked objects - const void *object, *object_type; - void *iter = NULL; - while (SDL_IterateHashTable(SDL_objects, &object, &object_type, &iter)) { - const char *type; - switch ((SDL_ObjectType)(uintptr_t)object_type) { - case SDL_OBJECT_TYPE_WINDOW: - type = "SDL_Window"; - break; - case SDL_OBJECT_TYPE_RENDERER: - type = "SDL_Renderer"; - break; - case SDL_OBJECT_TYPE_TEXTURE: - type = "SDL_Texture"; - break; - case SDL_OBJECT_TYPE_JOYSTICK: - type = "SDL_Joystick"; - break; - case SDL_OBJECT_TYPE_GAMEPAD: - type = "SDL_Gamepad"; - break; - case SDL_OBJECT_TYPE_HAPTIC: - type = "SDL_Haptic"; - break; - case SDL_OBJECT_TYPE_SENSOR: - type = "SDL_Sensor"; - break; - case SDL_OBJECT_TYPE_HIDAPI_DEVICE: - type = "hidapi device"; - break; - case SDL_OBJECT_TYPE_HIDAPI_JOYSTICK: - type = "hidapi joystick"; - break; - case SDL_OBJECT_TYPE_THREAD: - type = "thread"; - break; - case SDL_OBJECT_TYPE_TRAY: - type = "SDL_Tray"; - break; - default: - type = "unknown object"; - break; - } - SDL_Log("Leaked %s (%p)", type, object); - } + SDL_IterateHashTable(SDL_objects, LogOneLeakedObject, NULL); SDL_assert(SDL_HashTableEmpty(SDL_objects)); - SDL_DestroyHashTable(SDL_objects); SDL_objects = NULL; - SDL_SetInitialized(&SDL_objects_init, false); } } @@ -384,7 +378,7 @@ const char *SDL_GetPersistentString(const char *string) SDL_HashTable *strings = (SDL_HashTable *)SDL_GetTLS(&SDL_string_storage); if (!strings) { - strings = SDL_CreateHashTable(NULL, 32, SDL_HashString, SDL_KeyMatchString, SDL_NukeFreeValue, false, false); + strings = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_DestroyHashValue, NULL); if (!strings) { return NULL; } @@ -400,8 +394,161 @@ const char *SDL_GetPersistentString(const char *string) } // If the hash table insert fails, at least we can return the string we allocated - SDL_InsertIntoHashTable(strings, new_string, new_string); + SDL_InsertIntoHashTable(strings, new_string, new_string, false); result = new_string; } return result; } + +static int PrefixMatch(const char *a, const char *b) +{ + int matchlen = 0; + // Fixes the "HORI HORl Taiko No Tatsujin Drum Controller" + if (SDL_strncmp(a, "HORI ", 5) == 0 && SDL_strncmp(b, "HORl ", 5) == 0) { + return 5; + } + while (*a && *b) { + if (SDL_tolower((unsigned char)*a++) == SDL_tolower((unsigned char)*b++)) { + ++matchlen; + } else { + break; + } + } + return matchlen; +} + +char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name, const char *default_name) +{ + static struct + { + const char *prefix; + const char *replacement; + } replacements[] = { + { "8BitDo Tech Ltd", "8BitDo" }, + { "ASTRO Gaming", "ASTRO" }, + { "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" }, + { "Guangzhou Chicken Run Network Technology Co., Ltd.", "GameSir" }, + { "HORI CO.,LTD.", "HORI" }, + { "HORI CO.,LTD", "HORI" }, + { "Mad Catz Inc.", "Mad Catz" }, + { "Nintendo Co., Ltd.", "Nintendo" }, + { "NVIDIA Corporation ", "" }, + { "Performance Designed Products", "PDP" }, + { "QANBA USA, LLC", "Qanba" }, + { "QANBA USA,LLC", "Qanba" }, + { "Unknown ", "" }, + }; + char *name = NULL; + size_t i, len; + + if (!vendor_name) { + vendor_name = ""; + } + if (!product_name) { + product_name = ""; + } + + while (*vendor_name == ' ') { + ++vendor_name; + } + while (*product_name == ' ') { + ++product_name; + } + + if (*vendor_name && *product_name) { + len = (SDL_strlen(vendor_name) + 1 + SDL_strlen(product_name) + 1); + name = (char *)SDL_malloc(len); + if (name) { + (void)SDL_snprintf(name, len, "%s %s", vendor_name, product_name); + } + } else if (*product_name) { + name = SDL_strdup(product_name); + } else if (vendor || product) { + // Couldn't find a controller name, try to give it one based on device type + switch (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, true)) { + case SDL_GAMEPAD_TYPE_XBOX360: + name = SDL_strdup("Xbox 360 Controller"); + break; + case SDL_GAMEPAD_TYPE_XBOXONE: + name = SDL_strdup("Xbox One Controller"); + break; + case SDL_GAMEPAD_TYPE_PS3: + name = SDL_strdup("PS3 Controller"); + break; + case SDL_GAMEPAD_TYPE_PS4: + name = SDL_strdup("PS4 Controller"); + break; + case SDL_GAMEPAD_TYPE_PS5: + name = SDL_strdup("DualSense Wireless Controller"); + break; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: + name = SDL_strdup("Nintendo Switch Pro Controller"); + break; + default: + len = (6 + 1 + 6 + 1); + name = (char *)SDL_malloc(len); + if (name) { + (void)SDL_snprintf(name, len, "0x%.4x/0x%.4x", vendor, product); + } + break; + } + } else if (default_name) { + name = SDL_strdup(default_name); + } + + if (!name) { + return NULL; + } + + // Trim trailing whitespace + for (len = SDL_strlen(name); (len > 0 && name[len - 1] == ' '); --len) { + // continue + } + name[len] = '\0'; + + // Compress duplicate spaces + for (i = 0; i < (len - 1);) { + if (name[i] == ' ' && name[i + 1] == ' ') { + SDL_memmove(&name[i], &name[i + 1], (len - i)); + --len; + } else { + ++i; + } + } + + // Perform any manufacturer replacements + for (i = 0; i < SDL_arraysize(replacements); ++i) { + size_t prefixlen = SDL_strlen(replacements[i].prefix); + if (SDL_strncasecmp(name, replacements[i].prefix, prefixlen) == 0) { + size_t replacementlen = SDL_strlen(replacements[i].replacement); + if (replacementlen <= prefixlen) { + SDL_memcpy(name, replacements[i].replacement, replacementlen); + SDL_memmove(name + replacementlen, name + prefixlen, (len - prefixlen) + 1); + len -= (prefixlen - replacementlen); + } else { + // FIXME: Need to handle the expand case by reallocating the string + } + break; + } + } + + /* Remove duplicate manufacturer or product in the name + * e.g. Razer Razer Raiju Tournament Edition Wired + */ + for (i = 1; i < (len - 1); ++i) { + int matchlen = PrefixMatch(name, &name[i]); + while (matchlen > 0) { + if (name[matchlen] == ' ' || name[matchlen] == '-') { + SDL_memmove(name, name + matchlen + 1, len - matchlen); + break; + } + --matchlen; + } + if (matchlen > 0) { + // We matched the manufacturer's name and removed it + break; + } + } + + return name; +} diff --git a/libs/SDL3/src/SDL_utils_c.h b/libs/SDL3/src/SDL_utils_c.h index 557dad4..5e0e8f5 100644 --- a/libs/SDL3/src/SDL_utils_c.h +++ b/libs/SDL3/src/SDL_utils_c.h @@ -73,4 +73,6 @@ extern void SDL_SetObjectsInvalid(void); extern const char *SDL_GetPersistentString(const char *string); +extern char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name, const char *default_name); + #endif // SDL_utils_h_ diff --git a/libs/SDL3/src/audio/SDL_audio.c b/libs/SDL3/src/audio/SDL_audio.c index db4c55f..5e3e1fb 100644 --- a/libs/SDL3/src/audio/SDL_audio.c +++ b/libs/SDL3/src/audio/SDL_audio.c @@ -136,6 +136,7 @@ const char *SDL_GetAudioDriver(int index) if (index >= 0 && index < SDL_GetNumAudioDrivers()) { return deduped_bootstrap[index]->name; } + SDL_InvalidParamError("index"); return NULL; } @@ -409,6 +410,7 @@ static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid, SDL_LockRWLockForReading(current_audio.device_hash_lock); SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &logdev); if (logdev) { + SDL_assert(logdev->instance_id == devid); device = logdev->physical_device; SDL_assert(device != NULL); RefPhysicalAudioDevice(device); // reference it, in case the logical device migrates to a new default. @@ -458,6 +460,7 @@ static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) // ! } else { SDL_LockRWLockForReading(current_audio.device_hash_lock); SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &device); + SDL_assert(device->instance_id == devid); SDL_UnlockRWLock(current_audio.device_hash_lock); if (!device) { @@ -650,7 +653,7 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, bool recordi device->instance_id = AssignAudioDeviceInstanceId(recording, /*islogical=*/false); SDL_LockRWLockForWriting(current_audio.device_hash_lock); - if (SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) device->instance_id, device)) { + if (SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) device->instance_id, device, false)) { SDL_AddAtomicInt(device_count, 1); } else { SDL_DestroyCondition(device->close_cond); @@ -864,50 +867,48 @@ static void CompleteAudioEntryPoints(void) #undef FILL_STUB } -static SDL_AudioDevice *GetFirstAddedAudioDevice(const bool recording) +typedef struct FindLowestDeviceIDData { - SDL_AudioDeviceID highest = (SDL_AudioDeviceID) SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; // According to AssignAudioDeviceInstanceId, nothing can have a value this large. - SDL_AudioDevice *result = NULL; + const bool recording; + SDL_AudioDeviceID highest; + SDL_AudioDevice *result; +} FindLowestDeviceIDData; - // (Device IDs increase as new devices are added, so the first device added has the lowest SDL_AudioDeviceID value.) - SDL_LockRWLockForReading(current_audio.device_hash_lock); - - const void *key; - const void *value; - void *iter = NULL; - while (SDL_IterateHashTable(current_audio.device_hash, &key, &value, &iter)) { - const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - // bit #0 of devid is set for playback devices and unset for recording. - // bit #1 of devid is set for physical devices and unset for logical. - const bool devid_recording = !(devid & (1 << 0)); - const bool isphysical = !!(devid & (1 << 1)); - if (isphysical && (devid_recording == recording) && (devid < highest)) { - highest = devid; - result = (SDL_AudioDevice *) value; - } +static bool SDLCALL FindLowestDeviceID(void *userdata, const SDL_HashTable *table, const void *key, const void *value) +{ + FindLowestDeviceIDData *data = (FindLowestDeviceIDData *) userdata; + const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; + // bit #0 of devid is set for playback devices and unset for recording. + // bit #1 of devid is set for physical devices and unset for logical. + const bool devid_recording = !(devid & (1 << 0)); + const bool isphysical = !!(devid & (1 << 1)); + if (isphysical && (devid_recording == data->recording) && (devid < data->highest)) { + data->highest = devid; + data->result = (SDL_AudioDevice *) value; + SDL_assert(data->result->instance_id == devid); } - - SDL_UnlockRWLock(current_audio.device_hash_lock); - return result; + return true; // keep iterating. } -static Uint32 HashAudioDeviceID(const void *key, void *data) +static SDL_AudioDevice *GetFirstAddedAudioDevice(const bool recording) +{ + const SDL_AudioDeviceID highest = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; // According to AssignAudioDeviceInstanceId, nothing can have a value this large. + + // (Device IDs increase as new devices are added, so the first device added has the lowest SDL_AudioDeviceID value.) + FindLowestDeviceIDData data = { recording, highest, NULL }; + SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_IterateHashTable(current_audio.device_hash, FindLowestDeviceID, &data); + SDL_UnlockRWLock(current_audio.device_hash_lock); + return data.result; +} + +static Uint32 SDLCALL HashAudioDeviceID(void *userdata, const void *key) { // shift right 2, to dump the first two bits, since these are flags // (recording vs playback, logical vs physical) and the rest are unique incrementing integers. return ((Uint32) ((uintptr_t) key)) >> 2; } -static bool MatchAudioDeviceID(const void *a, const void *b, void *data) -{ - return (a == b); -} - -static void NukeAudioDeviceHashItem(const void *key, const void *value, void *data) -{ - // no-op, keys and values in this hashtable are treated as Plain Old Data and don't get freed here. -} - // !!! FIXME: the video subsystem does SDL_VideoInit, not SDL_InitVideo. Make this match. bool SDL_InitAudio(const char *driver_name) { @@ -926,7 +927,7 @@ bool SDL_InitAudio(const char *driver_name) return false; } - SDL_HashTable *device_hash = SDL_CreateHashTable(NULL, 8, HashAudioDeviceID, MatchAudioDeviceID, NukeAudioDeviceHashItem, false, false); + SDL_HashTable *device_hash = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL); if (!device_hash) { SDL_DestroyRWLock(device_hash_lock); return false; @@ -964,7 +965,7 @@ bool SDL_InitAudio(const char *driver_name) } for (int i = 0; bootstrap[i]; ++i) { - if (SDL_strcasecmp(bootstrap[i]->name, driver_attempt) == 0) { + if (!bootstrap[i]->is_preferred && SDL_strcasecmp(bootstrap[i]->name, driver_attempt) == 0) { tried_to_init = true; SDL_zero(current_audio); current_audio.pending_events_tail = ¤t_audio.pending_events; @@ -1047,6 +1048,20 @@ bool SDL_InitAudio(const char *driver_name) return true; } +static bool SDLCALL DestroyOnePhysicalAudioDevice(void *userdata, const SDL_HashTable *table, const void *key, const void *value) +{ + // bit #1 of devid is set for physical devices and unset for logical. + const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; + const bool isphysical = !!(devid & (1<<1)); + if (isphysical) { + SDL_AudioDevice *dev = (SDL_AudioDevice *) value; + + SDL_assert(dev->instance_id == devid); + DestroyPhysicalAudioDevice(dev); + } + return true; // keep iterating. +} + void SDL_QuitAudio(void) { if (!current_audio.name) { // not initialized?! @@ -1076,17 +1091,7 @@ void SDL_QuitAudio(void) SDL_free(i); } - const void *key; - const void *value; - void *iter = NULL; - while (SDL_IterateHashTable(device_hash, &key, &value, &iter)) { - // bit #1 of devid is set for physical devices and unset for logical. - const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - const bool isphysical = !!(devid & (1<<1)); - if (isphysical) { - DestroyPhysicalAudioDevice((SDL_AudioDevice *) value); - } - } + SDL_IterateHashTable(device_hash, DestroyOnePhysicalAudioDevice, NULL); // Free the driver data current_audio.impl.Deinitialize(); @@ -1148,7 +1153,10 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) // We should have updated this elsewhere if the format changed! SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &device->spec, NULL, NULL)); + SDL_assert(stream->src_spec.format != SDL_AUDIO_UNKNOWN); + const int br = SDL_GetAtomicInt(&logdev->paused) ? 0 : SDL_GetAudioStreamDataAdjustGain(stream, device_buffer, buffer_size, logdev->gain); + if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow. failed = true; SDL_memset(device_buffer, device->silence_value, buffer_size); // just supply silence to the device before we die. @@ -1190,6 +1198,8 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) // We should have updated this elsewhere if the format changed! SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &outspec, NULL, NULL)); + SDL_assert(stream->src_spec.format != SDL_AUDIO_UNKNOWN); + /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams for iterating here because the binding linked list can only change while the device lock is held. (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind @@ -1257,11 +1267,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; @@ -1325,6 +1335,7 @@ bool SDL_RecordingAudioThreadIterate(SDL_AudioDevice *device) SDL_assert(stream->src_spec.format == ((logdev->postmix || (logdev->gain != 1.0f)) ? SDL_AUDIO_F32 : device->spec.format)); SDL_assert(stream->src_spec.channels == device->spec.channels); SDL_assert(stream->src_spec.freq == device->spec.freq); + SDL_assert(stream->dst_spec.format != SDL_AUDIO_UNKNOWN); void *final_buf = output_buffer; @@ -1383,6 +1394,35 @@ static int SDLCALL RecordingAudioThread(void *devicep) // thread entry point return 0; } +typedef struct CountAudioDevicesData +{ + int devs_seen; + int devs_skipped; + const int num_devices; + SDL_AudioDeviceID *result; + const bool recording; +} CountAudioDevicesData; + +static bool SDLCALL CountAudioDevices(void *userdata, const SDL_HashTable *table, const void *key, const void *value) +{ + CountAudioDevicesData *data = (CountAudioDevicesData *) userdata; + const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; + // bit #0 of devid is set for playback devices and unset for recording. + // bit #1 of devid is set for physical devices and unset for logical. + const bool devid_recording = !(devid & (1<<0)); + const bool isphysical = !!(devid & (1<<1)); + if (isphysical && (devid_recording == data->recording)) { + SDL_assert(data->devs_seen < data->num_devices); + SDL_AudioDevice *device = (SDL_AudioDevice *) value; // this is normally risky, but we hold the device_hash_lock here. + const bool zombie = SDL_GetAtomicInt(&device->zombie) != 0; + if (zombie) { + data->devs_skipped++; + } else { + data->result[data->devs_seen++] = devid; + } + } + return true; // keep iterating. +} static SDL_AudioDeviceID *GetAudioDevices(int *count, bool recording) { @@ -1395,24 +1435,11 @@ static SDL_AudioDeviceID *GetAudioDevices(int *count, bool recording) num_devices = SDL_GetAtomicInt(recording ? ¤t_audio.recording_device_count : ¤t_audio.playback_device_count); result = (SDL_AudioDeviceID *) SDL_malloc((num_devices + 1) * sizeof (SDL_AudioDeviceID)); if (result) { - int devs_seen = 0; - const void *key; - const void *value; - void *iter = NULL; - while (SDL_IterateHashTable(current_audio.device_hash, &key, &value, &iter)) { - const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - // bit #0 of devid is set for playback devices and unset for recording. - // bit #1 of devid is set for physical devices and unset for logical. - const bool devid_recording = !(devid & (1<<0)); - const bool isphysical = !!(devid & (1<<1)); - if (isphysical && (devid_recording == recording)) { - SDL_assert(devs_seen < num_devices); - result[devs_seen++] = devid; - } - } - - SDL_assert(devs_seen == num_devices); - result[devs_seen] = 0; // null-terminated. + CountAudioDevicesData data = { 0, 0, num_devices, result, recording }; + SDL_IterateHashTable(current_audio.device_hash, CountAudioDevices, &data); + SDL_assert((data.devs_seen + data.devs_skipped) == num_devices); + num_devices = data.devs_seen; // might be less if we skipped any. + result[num_devices] = 0; // null-terminated. } } SDL_UnlockRWLock(current_audio.device_hash_lock); @@ -1440,7 +1467,31 @@ SDL_AudioDeviceID *SDL_GetAudioRecordingDevices(int *count) return GetAudioDevices(count, true); } +typedef struct FindAudioDeviceByCallbackData +{ + bool (*callback)(SDL_AudioDevice *device, void *userdata); + void *userdata; + SDL_AudioDevice *retval; +} FindAudioDeviceByCallbackData; +static bool SDLCALL FindAudioDeviceByCallback(void *userdata, const SDL_HashTable *table, const void *key, const void *value) +{ + FindAudioDeviceByCallbackData *data = (FindAudioDeviceByCallbackData *) userdata; + const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; + // bit #1 of devid is set for physical devices and unset for logical. + const bool isphysical = !!(devid & (1<<1)); + if (isphysical) { + SDL_AudioDevice *device = (SDL_AudioDevice *) value; + if (data->callback(device, data->userdata)) { // found it? + data->retval = device; + SDL_assert(data->retval->instance_id == devid); + return false; // stop iterating, we found it. + } + } + return true; // keep iterating. +} + +// !!! FIXME: SDL convention is for userdata to come first in the callback's params. Fix this at some point. SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(bool (*callback)(SDL_AudioDevice *device, void *userdata), void *userdata) { if (!SDL_GetCurrentAudioDriver()) { @@ -1448,27 +1499,16 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(bool (*callback)(SDL_Audi return NULL; } - const void *key; - const void *value; - void *iter = NULL; - + FindAudioDeviceByCallbackData data = { callback, userdata, NULL }; SDL_LockRWLockForReading(current_audio.device_hash_lock); - while (SDL_IterateHashTable(current_audio.device_hash, &key, &value, &iter)) { - const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - // bit #1 of devid is set for physical devices and unset for logical. - const bool isphysical = !!(devid & (1<<1)); - if (isphysical) { - SDL_AudioDevice *device = (SDL_AudioDevice *) value; - if (callback(device, userdata)) { // found it? - SDL_UnlockRWLock(current_audio.device_hash_lock); - return device; - } - } - } + SDL_IterateHashTable(current_audio.device_hash, FindAudioDeviceByCallback, &data); SDL_UnlockRWLock(current_audio.device_hash_lock); - SDL_SetError("Device not found"); - return NULL; + if (!data.retval) { + SDL_SetError("Device not found"); + } + + return data.retval; } static bool TestDeviceHandleCallback(SDL_AudioDevice *device, void *handle) @@ -1483,12 +1523,33 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) { + // bit #1 of devid is set for physical devices and unset for logical. + const bool islogical = !(devid & (1<<1)); const char *result = NULL; - SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); - if (device) { - result = SDL_GetPersistentString(device->name); + const void *vdev = NULL; + + if (!SDL_GetCurrentAudioDriver()) { + SDL_SetError("Audio subsystem is not initialized"); + } else { + // This does not call ObtainPhysicalAudioDevice() because the device's name never changes, so + // it doesn't have to lock the whole device. However, just to make sure the device pointer itself + // remains valid (in case the device is unplugged at the wrong moment), we hold the + // device_hash_lock while we copy the string. + SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, &vdev); + if (!vdev) { + SDL_SetError("Invalid audio device instance ID"); + } else if (islogical) { + const SDL_LogicalAudioDevice *logdev = (const SDL_LogicalAudioDevice *) vdev; + SDL_assert(logdev->instance_id == devid); + result = SDL_GetPersistentString(logdev->physical_device->name); + } else { + const SDL_AudioDevice *device = (const SDL_AudioDevice *) vdev; + SDL_assert(device->instance_id == devid); + result = SDL_GetPersistentString(device->name); + } + SDL_UnlockRWLock(current_audio.device_hash_lock); } - ReleaseAudioDevice(device); return result; } @@ -1520,7 +1581,9 @@ int *SDL_GetAudioDeviceChannelMap(SDL_AudioDeviceID devid, int *count) SDL_AudioDevice *device = ObtainPhysicalAudioDeviceDefaultAllowed(devid); if (device) { channels = device->spec.channels; - result = SDL_ChannelMapDup(device->chmap, channels); + if (channels > 0 && device->chmap) { + result = SDL_ChannelMapDup(device->chmap, channels); + } } ReleaseAudioDevice(device); @@ -1696,13 +1759,18 @@ static bool OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec SDL_copyp(&spec, inspec ? inspec : &device->default_spec); PrepareAudioFormat(device->recording, &spec); - /* We allow the device format to change if it's better than the current settings (by various definitions of "better"). This prevents - something low quality, like an old game using S8/8000Hz audio, from ruining a music thing playing at CD quality that tries to open later. - (or some VoIP library that opens for mono output ruining your surround-sound game because it got there first). + /* We impose a simple minimum on device formats. This prevents something low quality, like an old game using S8/8000Hz audio, + from ruining a music thing playing at CD quality that tries to open later, or some VoIP library that opens for mono output + ruining your surround-sound game because it got there first. These are just requests! The backend may change any of these values during OpenDevice method! */ - device->spec.format = (SDL_AUDIO_BITSIZE(device->default_spec.format) >= SDL_AUDIO_BITSIZE(spec.format)) ? device->default_spec.format : spec.format; - device->spec.freq = SDL_max(device->default_spec.freq, spec.freq); - device->spec.channels = SDL_max(device->default_spec.channels, spec.channels); + + const SDL_AudioFormat minimum_format = device->recording ? DEFAULT_AUDIO_RECORDING_FORMAT : DEFAULT_AUDIO_PLAYBACK_FORMAT; + const int minimum_channels = device->recording ? DEFAULT_AUDIO_RECORDING_CHANNELS : DEFAULT_AUDIO_PLAYBACK_CHANNELS; + const int minimum_freq = device->recording ? DEFAULT_AUDIO_RECORDING_FREQUENCY : DEFAULT_AUDIO_PLAYBACK_FREQUENCY; + + device->spec.format = (SDL_AUDIO_BITSIZE(minimum_format) >= SDL_AUDIO_BITSIZE(spec.format)) ? minimum_format : spec.format; + device->spec.channels = SDL_max(minimum_channels, spec.channels); + device->spec.freq = SDL_max(minimum_freq, spec.freq); device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq); SDL_UpdatedAudioDeviceFormat(device); // start this off sane. @@ -1794,7 +1862,7 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp if (result) { SDL_LockRWLockForWriting(current_audio.device_hash_lock); - const bool inserted = SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) result, logdev); + const bool inserted = SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) result, logdev, false); SDL_UnlockRWLock(current_audio.device_hash_lock); if (!inserted) { SDL_CloseAudioDevice(result); @@ -1913,10 +1981,6 @@ bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *stre } else if (logdev->simplified) { result = SDL_SetError("Cannot change stream bindings on device opened with SDL_OpenAudioDeviceStream"); } else { - - // !!! FIXME: We'll set the device's side's format below, but maybe we should refuse to bind a stream if the app's side doesn't have a format set yet. - // !!! FIXME: Actually, why do we allow there to be an invalid format, again? - // make sure start of list is sane. SDL_assert(!logdev->bound_streams || (logdev->bound_streams->prev_binding == NULL)); @@ -1951,9 +2015,17 @@ bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *stre if (result) { // Now that everything is verified, chain everything together. + const bool recording = device->recording; for (int i = 0; i < num_streams; i++) { SDL_AudioStream *stream = streams[i]; if (stream) { // shouldn't be NULL, but just in case... + // if the stream never had its non-device-end format set, just set it to the device end's format. + if (recording && (stream->dst_spec.format == SDL_AUDIO_UNKNOWN)) { + SDL_copyp(&stream->dst_spec, &device->spec); + } else if (!recording && (stream->src_spec.format == SDL_AUDIO_UNKNOWN)) { + SDL_copyp(&stream->src_spec, &device->spec); + } + stream->bound_device = logdev; stream->prev_binding = NULL; stream->next_binding = logdev->bound_streams; diff --git a/libs/SDL3/src/audio/SDL_audioresample.c b/libs/SDL3/src/audio/SDL_audioresample.c index dd2451b..371002e 100644 --- a/libs/SDL3/src/audio/SDL_audioresample.c +++ b/libs/SDL3/src/audio/SDL_audioresample.c @@ -444,7 +444,7 @@ static void SincTable(float *table, int len) } // Calculate Sinc(x/y), using a lookup table -static float Sinc(float *table, int x, int y) +static float Sinc(const float *table, int x, int y) { float s = table[x % y]; s = ((x / y) & 1) ? -s : s; @@ -587,7 +587,18 @@ Sint64 SDL_GetResampleRate(int src_rate, int dst_rate) SDL_assert(src_rate > 0); SDL_assert(dst_rate > 0); - Sint64 sample_rate = ((Sint64)src_rate << 32) / (Sint64)dst_rate; + Sint64 numerator = (Sint64)src_rate << 32; + Sint64 denominator = (Sint64)dst_rate; + + // Generally it's expected that `dst_frames = (src_frames * dst_rate) / src_rate` + // To match this as closely as possible without infinite precision, always round up the resample rate. + // For example, without rounding up, a sample ratio of 2:3 would have `sample_rate = 0xAAAAAAAA` + // After 3 frames, the position would be 0x1.FFFFFFFE, meaning we haven't fully consumed the second input frame. + // By rounding up to 0xAAAAAAAB, we would instead reach 0x2.00000001, fulling consuming the second frame. + // Technically you could say this is kicking the can 0x100000000 steps down the road, but I'm fine with that :) + // sample_rate = div_ceil(numerator, denominator) + Sint64 sample_rate = ((numerator - 1) / denominator) + 1; + SDL_assert(sample_rate > 0); return sample_rate; @@ -657,7 +668,7 @@ Sint64 SDL_GetResamplerOutputFrames(Sint64 input_frames, Sint64 resample_rate, S } // output_frames = div_ceil(input_offset, resample_rate) - Sint64 output_frames = (input_offset > 0) ? ((input_offset + resample_rate * 3 / 4) / resample_rate) : 0; + Sint64 output_frames = (input_offset > 0) ? ((input_offset - 1) / resample_rate) + 1 : 0; *inout_resample_offset = (output_frames * resample_rate) - input_offset; diff --git a/libs/SDL3/src/audio/SDL_audiotypecvt.c b/libs/SDL3/src/audio/SDL_audiotypecvt.c index d80a831..b3a7bbc 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,29 @@ 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 +#if defined(__aarch64__) +#pragma STDC FENV_ACCESS ON +#endif +#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 +573,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 +602,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 +627,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 +654,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 +683,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 +718,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 +746,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 +773,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 +813,21 @@ 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 +#if defined(__aarch64__) +#pragma STDC FENV_ACCESS DEFAULT +#endif +#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/SDL_sysaudio.h b/libs/SDL3/src/audio/SDL_sysaudio.h index c8af244..4a88bd2 100644 --- a/libs/SDL3/src/audio/SDL_sysaudio.h +++ b/libs/SDL3/src/audio/SDL_sysaudio.h @@ -360,6 +360,7 @@ typedef struct AudioBootStrap const char *desc; bool (*init)(SDL_AudioDriverImpl *impl); bool demand_only; // if true: request explicitly, or it won't be available. + bool is_preferred; } AudioBootStrap; // Not all of these are available in a given build. Use #ifdefs, etc. diff --git a/libs/SDL3/src/audio/SDL_wave.c b/libs/SDL3/src/audio/SDL_wave.c index 1d53e79..826b652 100644 --- a/libs/SDL3/src/audio/SDL_wave.c +++ b/libs/SDL3/src/audio/SDL_wave.c @@ -2114,8 +2114,8 @@ bool SDL_LoadWAV_IO(SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 result = WaveLoad(src, &file, spec, audio_buf, audio_len); if (!result) { SDL_free(*audio_buf); - audio_buf = NULL; - audio_len = 0; + *audio_buf = NULL; + *audio_len = 0; } // Cleanup diff --git a/libs/SDL3/src/audio/aaudio/SDL_aaudio.c b/libs/SDL3/src/audio/aaudio/SDL_aaudio.c index 0339a61..5436be0 100644 --- a/libs/SDL3/src/audio/aaudio/SDL_aaudio.c +++ b/libs/SDL3/src/audio/aaudio/SDL_aaudio.c @@ -308,6 +308,12 @@ static bool BuildAAudioStream(SDL_AudioDevice *device) ctx.AAudioStreamBuilder_setFormat(builder, format); ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq); ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels); + + // If no specific buffer size has been requested, the device will pick the optimal + if(SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES)) { + ctx.AAudioStreamBuilder_setBufferCapacityInFrames(builder, 2 * device->sample_frames); // AAudio requires that the buffer capacity is at least + ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, device->sample_frames); // twice the size of the data callback buffer size + } const aaudio_direction_t direction = (recording ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT); ctx.AAudioStreamBuilder_setDirection(builder, direction); @@ -366,7 +372,7 @@ static bool BuildAAudioStream(SDL_AudioDevice *device) hidden->processed_bytes = 0; hidden->callback_bytes = 0; - hidden->semaphore = SDL_CreateSemaphore(recording ? 0 : hidden->num_buffers); + hidden->semaphore = SDL_CreateSemaphore(recording ? 0 : hidden->num_buffers - 1); if (!hidden->semaphore) { LOGI("SDL Failed SDL_CreateSemaphore %s recording:%d", SDL_GetError(), recording); return false; @@ -545,7 +551,7 @@ static bool AAUDIO_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap AAUDIO_bootstrap = { - "AAudio", "AAudio audio driver", AAUDIO_Init, false + "AAudio", "AAudio audio driver", AAUDIO_Init, false, false }; #endif // SDL_AUDIO_DRIVER_AAUDIO diff --git a/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h b/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h index 0298821..1d9f710 100644 --- a/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h +++ b/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h @@ -31,7 +31,7 @@ SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSamplesPerFrame, (AAudioStreamBuild SDL_PROC(void, AAudioStreamBuilder_setFormat, (AAudioStreamBuilder * builder, aaudio_format_t format)) SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * builder, aaudio_sharing_mode_t sharingMode)) SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction)) -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames)) +SDL_PROC(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames)) SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode)) SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) // API 28 SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) // API 28 diff --git a/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c b/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c index 633e364..cb8c907 100644 --- a/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c +++ b/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c @@ -566,7 +566,7 @@ static enum snd_pcm_chmap_position sdl_channel_maps[SDL_AUDIO_ALSA__SDL_CHMAPS_N }; // Helper for the function right below. -static bool has_pos(unsigned int *chmap, unsigned int pos) +static bool has_pos(const unsigned int *chmap, unsigned int pos) { for (unsigned int chan_idx = 0; ; chan_idx++) { if (chan_idx == 6) { @@ -586,7 +586,7 @@ static bool has_pos(unsigned int *chmap, unsigned int pos) #define HAVE_REAR 1 #define HAVE_SIDE 2 #define HAVE_BOTH 3 -static void sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(unsigned int *sdl_6chans, unsigned int *alsa_6chans) +static void sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(unsigned int *sdl_6chans, const unsigned int *alsa_6chans) { // For alsa channel maps with 6 channels and with SND_CHMAP_FL,SND_CHMAP_FR,SND_CHMAP_FC, // SND_CHMAP_LFE, reduce our 6 channels maps to a uniq one. @@ -638,7 +638,7 @@ static void sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(unsigned int * #undef HAVE_SIDE #undef HAVE_BOTH -static void swizzle_map_compute_alsa_subscan(struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, unsigned int sdl_pos_idx) +static void swizzle_map_compute_alsa_subscan(const struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, unsigned int sdl_pos_idx) { swizzle_map[sdl_pos_idx] = -1; for (unsigned int alsa_pos_idx = 0; ; alsa_pos_idx++) { @@ -652,7 +652,7 @@ static void swizzle_map_compute_alsa_subscan(struct ALSA_pcm_cfg_ctx *ctx, int * } // XXX: this must stay playback/recording symetric. -static void swizzle_map_compute(struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, bool *needs_swizzle) +static void swizzle_map_compute(const struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, bool *needs_swizzle) { *needs_swizzle = false; for (unsigned int sdl_pos_idx = 0; sdl_pos_idx != ctx->chans_n; sdl_pos_idx++) { @@ -668,7 +668,7 @@ static void swizzle_map_compute(struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, #define CHMAP_NOT_FOUND 2 // Should always be a queried alsa channel map unless the queried alsa channel map was of type VAR, // namely we can program the channel positions directly from the SDL channel map. -static int alsa_chmap_install(struct ALSA_pcm_cfg_ctx *ctx, unsigned int *chmap) +static int alsa_chmap_install(struct ALSA_pcm_cfg_ctx *ctx, const unsigned int *chmap) { bool isstack; snd_pcm_chmap_t *chmap_to_install = (snd_pcm_chmap_t*)SDL_small_alloc(unsigned int, 1 + ctx->chans_n, &isstack); @@ -698,7 +698,7 @@ static int alsa_chmap_install(struct ALSA_pcm_cfg_ctx *ctx, unsigned int *chmap) // We restrict the alsa channel maps because in the unordered matches we do only simple accounting. // In the end, this will handle mostly alsa channel maps with more than one SND_CHMAP_NA position fillers. -static bool alsa_chmap_has_duplicate_position(struct ALSA_pcm_cfg_ctx *ctx, unsigned int *pos) +static bool alsa_chmap_has_duplicate_position(const struct ALSA_pcm_cfg_ctx *ctx, const unsigned int *pos) { if (ctx->chans_n < 2) {// we need at least 2 positions LOGDEBUG("channel map:no duplicate"); @@ -1156,7 +1156,7 @@ static bool ALSA_OpenDevice(SDL_AudioDevice *device) #if SDL_ALSA_DEBUG snd_pcm_uframes_t bufsize; ALSA_snd_pcm_hw_params_get_buffer_size(cfg_ctx.hwparams, &bufsize); - SDL_LogError(SDL_LOG_CATEGORY_AUDIO, + SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, "ALSA: period size = %ld, periods = %u, buffer size = %lu", cfg_ctx.persize, cfg_ctx.periods, bufsize); #endif @@ -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; @@ -1513,7 +1520,7 @@ static bool ALSA_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap ALSA_bootstrap = { - "alsa", "ALSA PCM audio", ALSA_Init, false + "alsa", "ALSA PCM audio", ALSA_Init, false, false }; #endif // SDL_AUDIO_DRIVER_ALSA diff --git a/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m b/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m index cd0446e..57b19c7 100644 --- a/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m +++ b/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m @@ -1034,7 +1034,7 @@ static bool COREAUDIO_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap COREAUDIO_bootstrap = { - "coreaudio", "CoreAudio", COREAUDIO_Init, false + "coreaudio", "CoreAudio", COREAUDIO_Init, false, false }; #endif // SDL_AUDIO_DRIVER_COREAUDIO diff --git a/libs/SDL3/src/audio/directsound/SDL_directsound.c b/libs/SDL3/src/audio/directsound/SDL_directsound.c index da4390d..ab4dd0c 100644 --- a/libs/SDL3/src/audio/directsound/SDL_directsound.c +++ b/libs/SDL3/src/audio/directsound/SDL_directsound.c @@ -206,7 +206,7 @@ static void DSOUND_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDe { #ifdef HAVE_MMDEVICEAPI_H if (SupportsIMMDevice) { - SDL_IMMDevice_EnumerateEndpoints(default_playback, default_recording); + SDL_IMMDevice_EnumerateEndpoints(default_playback, default_recording, SDL_AUDIO_UNKNOWN); } else #endif { @@ -674,7 +674,7 @@ static bool DSOUND_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap DSOUND_bootstrap = { - "directsound", "DirectSound", DSOUND_Init, false + "directsound", "DirectSound", DSOUND_Init, false, false }; #endif // SDL_AUDIO_DRIVER_DSOUND diff --git a/libs/SDL3/src/audio/disk/SDL_diskaudio.c b/libs/SDL3/src/audio/disk/SDL_diskaudio.c index 99145c9..9e05478 100644 --- a/libs/SDL3/src/audio/disk/SDL_diskaudio.c +++ b/libs/SDL3/src/audio/disk/SDL_diskaudio.c @@ -165,7 +165,7 @@ static bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap DISKAUDIO_bootstrap = { - "disk", "direct-to-disk audio", DISKAUDIO_Init, true + "disk", "direct-to-disk audio", DISKAUDIO_Init, true, false }; #endif // SDL_AUDIO_DRIVER_DISK diff --git a/libs/SDL3/src/audio/dsp/SDL_dspaudio.c b/libs/SDL3/src/audio/dsp/SDL_dspaudio.c index 111caeb..62b8990 100644 --- a/libs/SDL3/src/audio/dsp/SDL_dspaudio.c +++ b/libs/SDL3/src/audio/dsp/SDL_dspaudio.c @@ -297,7 +297,7 @@ static bool DSP_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap DSP_bootstrap = { - "dsp", "Open Sound System (/dev/dsp)", DSP_Init, false + "dsp", "Open Sound System (/dev/dsp)", DSP_Init, false, false }; #endif // SDL_AUDIO_DRIVER_OSS diff --git a/libs/SDL3/src/audio/dummy/SDL_dummyaudio.c b/libs/SDL3/src/audio/dummy/SDL_dummyaudio.c index c5999fb..d0f1a1a 100644 --- a/libs/SDL3/src/audio/dummy/SDL_dummyaudio.c +++ b/libs/SDL3/src/audio/dummy/SDL_dummyaudio.c @@ -131,5 +131,5 @@ static bool DUMMYAUDIO_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap DUMMYAUDIO_bootstrap = { - "dummy", "SDL dummy audio driver", DUMMYAUDIO_Init, true + "dummy", "SDL dummy audio driver", DUMMYAUDIO_Init, true, false }; diff --git a/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c b/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c index 84ba63b..46b8b76 100644 --- a/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c +++ b/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c @@ -189,7 +189,7 @@ static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device) } // limit to native freq - device->spec.freq = EM_ASM_INT({ return Module['SDL3'].audioContext.sampleRate; }); + device->spec.freq = MAIN_THREAD_EM_ASM_INT({ return Module['SDL3'].audioContext.sampleRate; }); device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq) * 2; // double the buffer size, some browsers need more, and we'll just have to live with the latency. SDL_UpdatedAudioDeviceFormat(device); @@ -234,7 +234,7 @@ static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device) if ((SDL3 === undefined) || (SDL3.audio_recording === undefined)) { return; } audioProcessingEvent.outputBuffer.getChannelData(0).fill(0.0); SDL3.audio_recording.currentRecordingBuffer = audioProcessingEvent.inputBuffer; - dynCall('ii', $2, [$3]); + dynCall('ip', $2, [$3]); }; SDL3.audio_recording.mediaStreamNode.connect(SDL3.audio_recording.scriptProcessorNode); SDL3.audio_recording.scriptProcessorNode.connect(SDL3.audioContext.destination); @@ -250,7 +250,7 @@ static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device) SDL3.audio_recording.silenceBuffer.getChannelData(0).fill(0.0); var silence_callback = function() { SDL3.audio_recording.currentRecordingBuffer = SDL3.audio_recording.silenceBuffer; - dynCall('ii', $2, [$3]); + dynCall('ip', $2, [$3]); }; SDL3.audio_recording.silenceTimer = setInterval(silence_callback, ($1 / SDL3.audioContext.sampleRate) * 1000); @@ -275,7 +275,7 @@ static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device) SDL3.audio_playback.silenceBuffer = undefined; } SDL3.audio_playback.currentPlaybackBuffer = e['outputBuffer']; - dynCall('ii', $2, [$3]); + dynCall('ip', $2, [$3]); }; SDL3.audio_playback.scriptProcessorNode['connect'](SDL3.audioContext['destination']); @@ -293,7 +293,7 @@ static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device) // the buffer that gets filled here just gets ignored, so the app can make progress // and/or avoid flooding audio queues until we can actually play audio. SDL3.audio_playback.currentPlaybackBuffer = SDL3.audio_playback.silenceBuffer; - dynCall('ii', $2, [$3]); + dynCall('ip', $2, [$3]); SDL3.audio_playback.currentPlaybackBuffer = undefined; }; @@ -351,7 +351,7 @@ static bool EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap EMSCRIPTENAUDIO_bootstrap = { - "emscripten", "SDL emscripten audio driver", EMSCRIPTENAUDIO_Init, false + "emscripten", "SDL emscripten audio driver", EMSCRIPTENAUDIO_Init, false, false }; /* *INDENT-ON* */ // clang-format on diff --git a/libs/SDL3/src/audio/haiku/SDL_haikuaudio.cc b/libs/SDL3/src/audio/haiku/SDL_haikuaudio.cc index 9401d88..730b107 100644 --- a/libs/SDL3/src/audio/haiku/SDL_haikuaudio.cc +++ b/libs/SDL3/src/audio/haiku/SDL_haikuaudio.cc @@ -216,7 +216,7 @@ static bool HAIKUAUDIO_Init(SDL_AudioDriverImpl *impl) extern "C" { extern AudioBootStrap HAIKUAUDIO_bootstrap; } AudioBootStrap HAIKUAUDIO_bootstrap = { - "haiku", "Haiku BSoundPlayer", HAIKUAUDIO_Init, false + "haiku", "Haiku BSoundPlayer", HAIKUAUDIO_Init, false, false }; #endif // SDL_AUDIO_DRIVER_HAIKU diff --git a/libs/SDL3/src/audio/jack/SDL_jackaudio.c b/libs/SDL3/src/audio/jack/SDL_jackaudio.c index 0ed79f5..3ae5137 100644 --- a/libs/SDL3/src/audio/jack/SDL_jackaudio.c +++ b/libs/SDL3/src/audio/jack/SDL_jackaudio.c @@ -429,7 +429,7 @@ static bool JACK_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap JACK_bootstrap = { - "jack", "JACK Audio Connection Kit", JACK_Init, false + "jack", "JACK Audio Connection Kit", JACK_Init, false, false }; #endif // SDL_AUDIO_DRIVER_JACK diff --git a/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.c b/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.c index 8144d90..780b06c 100644 --- a/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.c +++ b/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.c @@ -280,7 +280,8 @@ AudioBootStrap N3DSAUDIO_bootstrap = { N3DSAUDIO_DRIVER_NAME, "SDL N3DS audio driver", N3DSAUDIO_Init, - 0 + false, + false }; #endif // SDL_AUDIO_DRIVER_N3DS diff --git a/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.c b/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.c index cc0840b..26060d3 100644 --- a/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.c +++ b/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.c @@ -322,7 +322,7 @@ static bool NETBSDAUDIO_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap NETBSDAUDIO_bootstrap = { - "netbsd", "NetBSD audio", NETBSDAUDIO_Init, false + "netbsd", "NetBSD audio", NETBSDAUDIO_Init, false, false }; #endif // SDL_AUDIO_DRIVER_NETBSD diff --git a/libs/SDL3/src/audio/openslES/SDL_openslES.c b/libs/SDL3/src/audio/openslES/SDL_openslES.c index 08fd678..4d5b3bd 100644 --- a/libs/SDL3/src/audio/openslES/SDL_openslES.c +++ b/libs/SDL3/src/audio/openslES/SDL_openslES.c @@ -779,7 +779,7 @@ static bool OPENSLES_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap OPENSLES_bootstrap = { - "openslES", "OpenSL ES audio driver", OPENSLES_Init, false + "openslES", "OpenSL ES audio driver", OPENSLES_Init, false, false }; void OPENSLES_ResumeDevices(void) diff --git a/libs/SDL3/src/audio/pipewire/SDL_pipewire.c b/libs/SDL3/src/audio/pipewire/SDL_pipewire.c index dfb5d6c..6004feb 100644 --- a/libs/SDL3/src/audio/pipewire/SDL_pipewire.c +++ b/libs/SDL3/src/audio/pipewire/SDL_pipewire.c @@ -44,7 +44,9 @@ enum PW_READY_FLAGS { PW_READY_FLAG_BUFFER_ADDED = 0x1, PW_READY_FLAG_STREAM_READY = 0x2, - PW_READY_FLAG_ALL_BITS = 0x3 + PW_READY_FLAG_ALL_PREOPEN_BITS = 0x3, + PW_READY_FLAG_OPEN_COMPLETE = 0x4, + PW_READY_FLAG_ALL_BITS = 0x7 }; #define PW_ID_TO_HANDLE(x) (void *)((uintptr_t)x) @@ -1116,7 +1118,13 @@ static bool PIPEWIRE_OpenDevice(SDL_AudioDevice *device) stream_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME); if (!stream_name || *stream_name == '\0') { - stream_name = "Audio Stream"; + if (app_name) { + stream_name = app_name; + } else if (app_id) { + stream_name = app_id; + } else { + stream_name = "SDL Audio Stream"; + } } /* @@ -1185,7 +1193,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); @@ -1215,12 +1227,13 @@ static bool PIPEWIRE_OpenDevice(SDL_AudioDevice *device) return SDL_SetError("Pipewire: Failed to start stream loop"); } - // Wait until all init flags are set or the stream has failed. + // Wait until all pre-open init flags are set or the stream has failed. PIPEWIRE_pw_thread_loop_lock(priv->loop); - while (priv->stream_init_status != PW_READY_FLAG_ALL_BITS && + while (priv->stream_init_status != PW_READY_FLAG_ALL_PREOPEN_BITS && PIPEWIRE_pw_stream_get_state(priv->stream, NULL) != PW_STREAM_STATE_ERROR) { PIPEWIRE_pw_thread_loop_wait(priv->loop); } + priv->stream_init_status |= PW_READY_FLAG_OPEN_COMPLETE; PIPEWIRE_pw_thread_loop_unlock(priv->loop); if (PIPEWIRE_pw_stream_get_state(priv->stream, &error) == PW_STREAM_STATE_ERROR) { @@ -1337,10 +1350,10 @@ static bool PIPEWIRE_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap PIPEWIRE_PREFERRED_bootstrap = { - "pipewire", "Pipewire", PIPEWIRE_PREFERRED_Init, false + "pipewire", "Pipewire", PIPEWIRE_PREFERRED_Init, false, true }; AudioBootStrap PIPEWIRE_bootstrap = { - "pipewire", "Pipewire", PIPEWIRE_Init, false + "pipewire", "Pipewire", PIPEWIRE_Init, false, false }; #endif // SDL_AUDIO_DRIVER_PIPEWIRE diff --git a/libs/SDL3/src/audio/ps2/SDL_ps2audio.c b/libs/SDL3/src/audio/ps2/SDL_ps2audio.c index 2995eed..6579f1b 100644 --- a/libs/SDL3/src/audio/ps2/SDL_ps2audio.c +++ b/libs/SDL3/src/audio/ps2/SDL_ps2audio.c @@ -155,5 +155,5 @@ static bool PS2AUDIO_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap PS2AUDIO_bootstrap = { - "ps2", "PS2 audio driver", PS2AUDIO_Init, false + "ps2", "PS2 audio driver", PS2AUDIO_Init, false, false }; diff --git a/libs/SDL3/src/audio/psp/SDL_pspaudio.c b/libs/SDL3/src/audio/psp/SDL_pspaudio.c index 328387e..1b095e9 100644 --- a/libs/SDL3/src/audio/psp/SDL_pspaudio.c +++ b/libs/SDL3/src/audio/psp/SDL_pspaudio.c @@ -114,7 +114,7 @@ static bool PSPAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, in } else { rc = sceAudioOutputPannedBlocking(device->hidden->channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, (void *) buffer); } - return (rc == 0); + return (rc >= 0); } static bool PSPAUDIO_WaitDevice(SDL_AudioDevice *device) @@ -177,7 +177,7 @@ static bool PSPAUDIO_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap PSPAUDIO_bootstrap = { - "psp", "PSP audio driver", PSPAUDIO_Init, false + "psp", "PSP audio driver", PSPAUDIO_Init, false, false }; #endif // SDL_AUDIO_DRIVER_PSP diff --git a/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c b/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c index 7536514..4d618a6 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); @@ -543,7 +543,7 @@ static void PULSEAUDIO_FlushRecording(SDL_AudioDevice *device) { struct SDL_PrivateAudioData *h = device->hidden; const void *data = NULL; - size_t nbytes = 0; + size_t nbytes = 0, buflen = 0; PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); @@ -553,7 +553,8 @@ static void PULSEAUDIO_FlushRecording(SDL_AudioDevice *device) h->recordinglen = 0; } - while (!SDL_GetAtomicInt(&device->shutdown) && (PULSEAUDIO_pa_stream_readable_size(h->stream) > 0)) { + buflen = PULSEAUDIO_pa_stream_readable_size(h->stream); + while (!SDL_GetAtomicInt(&device->shutdown) && (buflen > 0)) { PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) { //SDL_Log("PULSEAUDIO DEVICE FAILURE IN FLUSHRECORDING!"); @@ -561,11 +562,11 @@ static void PULSEAUDIO_FlushRecording(SDL_AudioDevice *device) break; } - if (PULSEAUDIO_pa_stream_readable_size(h->stream) > 0) { - // a new fragment is available! Just dump it. - PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes); - PULSEAUDIO_pa_stream_drop(h->stream); // drop this fragment. - } + // a fragment of audio present before FlushCapture was call is + // still available! Just drop it. + PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes); + PULSEAUDIO_pa_stream_drop(h->stream); + buflen -= nbytes; } PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); @@ -671,7 +672,8 @@ static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) paspec.rate = device->spec.freq; // Reduced prebuffering compared to the defaults. - paattr.fragsize = device->buffer_size; // despite the name, this is only used for recording devices, according to PulseAudio docs! + + paattr.fragsize = device->buffer_size * 2; // despite the name, this is only used for recording devices, according to PulseAudio docs! (times 2 because we want _more_ than our buffer size sent from the server at a time, which helps some drivers). paattr.tlength = device->buffer_size; paattr.prebuf = -1; paattr.maxlength = -1; @@ -700,7 +702,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) { @@ -778,8 +783,8 @@ static void AddPulseAudioDevice(const bool recording, const char *description, c SDL_free(handle); } else { handle->device_index = index; + SDL_AddAudioDevice(recording, description, &spec, handle); } - SDL_AddAudioDevice(recording, description, &spec, handle); } } @@ -1030,7 +1035,7 @@ static bool PULSEAUDIO_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap PULSEAUDIO_bootstrap = { - "pulseaudio", "PulseAudio", PULSEAUDIO_Init, false + "pulseaudio", "PulseAudio", PULSEAUDIO_Init, false, false }; #endif // SDL_AUDIO_DRIVER_PULSEAUDIO diff --git a/libs/SDL3/src/audio/qnx/SDL_qsa_audio.c b/libs/SDL3/src/audio/qnx/SDL_qsa_audio.c index 715ee32..a31bea4 100644 --- a/libs/SDL3/src/audio/qnx/SDL_qsa_audio.c +++ b/libs/SDL3/src/audio/qnx/SDL_qsa_audio.c @@ -444,7 +444,7 @@ static bool QSA_Init(SDL_AudioDriverImpl * impl) } AudioBootStrap QSAAUDIO_bootstrap = { - "qsa", "QNX QSA Audio", QSA_Init, 0 + "qsa", "QNX QSA Audio", QSA_Init, false, false }; #endif // SDL_AUDIO_DRIVER_QNX diff --git a/libs/SDL3/src/audio/sndio/SDL_sndioaudio.c b/libs/SDL3/src/audio/sndio/SDL_sndioaudio.c index 0056291..a0d2020 100644 --- a/libs/SDL3/src/audio/sndio/SDL_sndioaudio.c +++ b/libs/SDL3/src/audio/sndio/SDL_sndioaudio.c @@ -350,7 +350,7 @@ static bool SNDIO_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap SNDIO_bootstrap = { - "sndio", "OpenBSD sndio", SNDIO_Init, false + "sndio", "OpenBSD sndio", SNDIO_Init, false, false }; #endif // SDL_AUDIO_DRIVER_SNDIO diff --git a/libs/SDL3/src/audio/vita/SDL_vitaaudio.c b/libs/SDL3/src/audio/vita/SDL_vitaaudio.c index c02c9fa..86e8a69 100644 --- a/libs/SDL3/src/audio/vita/SDL_vitaaudio.c +++ b/libs/SDL3/src/audio/vita/SDL_vitaaudio.c @@ -130,7 +130,8 @@ static bool VITAAUD_OpenDevice(SDL_AudioDevice *device) static bool VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { - return (sceAudioOutOutput(device->hidden->port, buffer) == 0); + // sceAudioOutOutput returns amount of samples queued or < 0 on error + return (sceAudioOutOutput(device->hidden->port, buffer) >= 0); } // This function waits until it is possible to write a full sound buffer @@ -232,7 +233,7 @@ static bool VITAAUD_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap VITAAUD_bootstrap = { - "vita", "VITA audio driver", VITAAUD_Init, false + "vita", "VITA audio driver", VITAAUD_Init, false, false }; #endif // SDL_AUDIO_DRIVER_VITA diff --git a/libs/SDL3/src/audio/wasapi/SDL_wasapi.c b/libs/SDL3/src/audio/wasapi/SDL_wasapi.c index 71eb329..2ab2dc3 100644 --- a/libs/SDL3/src/audio/wasapi/SDL_wasapi.c +++ b/libs/SDL3/src/audio/wasapi/SDL_wasapi.c @@ -337,7 +337,7 @@ typedef struct static bool mgmtthrtask_DetectDevices(void *userdata) { mgmtthrtask_DetectDevicesData *data = (mgmtthrtask_DetectDevicesData *)userdata; - SDL_IMMDevice_EnumerateEndpoints(data->default_playback, data->default_recording); + SDL_IMMDevice_EnumerateEndpoints(data->default_playback, data->default_recording, SDL_AUDIO_F32); return true; } @@ -957,7 +957,7 @@ static bool WASAPI_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap WASAPI_bootstrap = { - "wasapi", "WASAPI", WASAPI_Init, false + "wasapi", "WASAPI", WASAPI_Init, false, false }; #endif // SDL_AUDIO_DRIVER_WASAPI diff --git a/libs/SDL3/src/camera/SDL_camera.c b/libs/SDL3/src/camera/SDL_camera.c index 1849d04..9f71cea 100644 --- a/libs/SDL3/src/camera/SDL_camera.c +++ b/libs/SDL3/src/camera/SDL_camera.c @@ -32,7 +32,6 @@ // Available camera drivers static const CameraBootStrap *const bootstrap[] = { -#ifndef SDL_CAMERA_DISABLED #ifdef SDL_CAMERA_DRIVER_V4L2 &V4L2_bootstrap, #endif @@ -56,7 +55,6 @@ static const CameraBootStrap *const bootstrap[] = { #endif #ifdef SDL_CAMERA_DRIVER_DUMMY &DUMMYCAMERA_bootstrap, -#endif #endif NULL }; @@ -74,6 +72,7 @@ const char *SDL_GetCameraDriver(int index) if (index >= 0 && index < SDL_GetNumCameraDrivers()) { return bootstrap[index]->name; } + SDL_InvalidParamError("index"); return NULL; } @@ -279,23 +278,10 @@ static void ClosePhysicalCamera(SDL_Camera *device) device->base_timestamp = 0; device->adjust_timestamp = 0; -} -// this must not be called while `device` is still in a device list, or while a device's camera thread is still running. -static void DestroyPhysicalCamera(SDL_Camera *device) -{ - if (device) { - // Destroy any logical devices that still exist... - ClosePhysicalCamera(device); - camera_driver.impl.FreeDeviceHandle(device); - SDL_DestroyMutex(device->lock); - SDL_free(device->all_specs); - SDL_free(device->name); - SDL_free(device); - } + SDL_zero(device->spec); } - // Don't hold the device lock when calling this, as we may destroy the device! void UnrefPhysicalCamera(SDL_Camera *device) { @@ -306,7 +292,6 @@ void UnrefPhysicalCamera(SDL_Camera *device) SDL_AddAtomicInt(&camera_driver.device_count, -1); } SDL_UnlockRWLock(camera_driver.device_hash_lock); - DestroyPhysicalCamera(device); // ...and nuke it. } } @@ -499,7 +484,7 @@ SDL_Camera *SDL_AddCamera(const char *name, SDL_CameraPosition position, int num RefPhysicalCamera(device); SDL_LockRWLockForWriting(camera_driver.device_hash_lock); - if (SDL_InsertIntoHashTable(camera_driver.device_hash, (const void *) (uintptr_t) device->instance_id, device)) { + if (SDL_InsertIntoHashTable(camera_driver.device_hash, (const void *) (uintptr_t) device->instance_id, device, false)) { SDL_AddAtomicInt(&camera_driver.device_count, 1); } else { SDL_DestroyMutex(device->lock); @@ -623,7 +608,25 @@ void SDL_CameraPermissionOutcome(SDL_Camera *device, bool approved) } } +typedef struct FindOnePhysicalCameraByCallbackData +{ + bool (*callback)(SDL_Camera *device, void *userdata); + void *userdata; + SDL_Camera *device; +} FindOnePhysicalCameraByCallbackData; +static bool SDLCALL FindOnePhysicalCameraByCallback(void *userdata, const SDL_HashTable *table, const void *key, const void *value) +{ + FindOnePhysicalCameraByCallbackData *data = (FindOnePhysicalCameraByCallbackData *) userdata; + SDL_Camera *device = (SDL_Camera *) value; + if (data->callback(device, data->userdata)) { + data->device = device; + return false; // stop iterating. + } + return true; // keep iterating. +} + +// !!! FIXME: this doesn't follow SDL convention of `userdata` being the first param of the callback. SDL_Camera *SDL_FindPhysicalCameraByCallback(bool (*callback)(SDL_Camera *device, void *userdata), void *userdata) { if (!SDL_GetCurrentCameraDriver()) { @@ -631,28 +634,22 @@ SDL_Camera *SDL_FindPhysicalCameraByCallback(bool (*callback)(SDL_Camera *device return NULL; } - const void *key; - const void *value; - void *iter = NULL; + FindOnePhysicalCameraByCallbackData data = { callback, userdata, NULL }; SDL_LockRWLockForReading(camera_driver.device_hash_lock); - while (SDL_IterateHashTable(camera_driver.device_hash, &key, &value, &iter)) { - SDL_Camera *device = (SDL_Camera *) value; - if (callback(device, userdata)) { // found it? - SDL_UnlockRWLock(camera_driver.device_hash_lock); - return device; - } - } - + SDL_IterateHashTable(camera_driver.device_hash, FindOnePhysicalCameraByCallback, &data); SDL_UnlockRWLock(camera_driver.device_hash_lock); - SDL_SetError("Device not found"); - return NULL; + if (!data.device) { + SDL_SetError("Device not found"); + } + + return data.device; } void SDL_CloseCamera(SDL_Camera *camera) { - SDL_Camera *device = (SDL_Camera *) camera; // currently there's no separation between physical and logical device. + SDL_Camera *device = camera; // currently there's no separation between physical and logical device. ClosePhysicalCamera(device); } @@ -666,7 +663,7 @@ bool SDL_GetCameraFormat(SDL_Camera *camera, SDL_CameraSpec *spec) return SDL_InvalidParamError("spec"); } - SDL_Camera *device = (SDL_Camera *) camera; // currently there's no separation between physical and logical device. + SDL_Camera *device = camera; // currently there's no separation between physical and logical device. ObtainPhysicalCameraObj(device); if (device->permission > 0) { SDL_copyp(spec, &device->spec); @@ -703,6 +700,19 @@ SDL_CameraPosition SDL_GetCameraPosition(SDL_CameraID instance_id) } +typedef struct GetOneCameraData +{ + SDL_CameraID *result; + int devs_seen; +} GetOneCameraData; + +static bool SDLCALL GetOneCamera(void *userdata, const SDL_HashTable *table, const void *key, const void *value) +{ + GetOneCameraData *data = (GetOneCameraData *) userdata; + data->result[data->devs_seen++] = (SDL_CameraID) (uintptr_t) key; + return true; // keep iterating. +} + SDL_CameraID *SDL_GetCameras(int *count) { int dummy_count; @@ -724,16 +734,10 @@ SDL_CameraID *SDL_GetCameras(int *count) if (!result) { num_devices = 0; } else { - int devs_seen = 0; - const void *key; - const void *value; - void *iter = NULL; - while (SDL_IterateHashTable(camera_driver.device_hash, &key, &value, &iter)) { - result[devs_seen++] = (SDL_CameraID) (uintptr_t) key; - } - - SDL_assert(devs_seen == num_devices); - result[devs_seen] = 0; // null-terminated. + GetOneCameraData data = { result, 0 }; + SDL_IterateHashTable(camera_driver.device_hash, GetOneCamera, &data); + SDL_assert(data.devs_seen == num_devices); + result[num_devices] = 0; // null-terminated. } SDL_UnlockRWLock(camera_driver.device_hash_lock); @@ -954,6 +958,110 @@ static int SDLCALL CameraThread(void *devicep) return 0; } +bool SDL_PrepareCameraSurfaces(SDL_Camera *device) +{ + SDL_CameraSpec *appspec = &device->spec; // the app wants this format. + const SDL_CameraSpec *devspec = &device->actual_spec; // the hardware is set to this format. + + SDL_assert(device->acquire_surface == NULL); // shouldn't call this function twice on an opened camera! + SDL_assert(devspec->format != SDL_PIXELFORMAT_UNKNOWN); // fix the backend, it should have an actual format by now. + SDL_assert(devspec->width >= 0); // fix the backend, it should have an actual format by now. + SDL_assert(devspec->height >= 0); // fix the backend, it should have an actual format by now. + + if (appspec->width <= 0 || appspec->height <= 0) { + appspec->width = devspec->width; + appspec->height = devspec->height; + } + + if (appspec->format == SDL_PIXELFORMAT_UNKNOWN) { + appspec->format = devspec->format; + } + + if (appspec->framerate_denominator == 0) { + appspec->framerate_numerator = devspec->framerate_numerator; + appspec->framerate_denominator = devspec->framerate_denominator; + } + + if ((devspec->width == appspec->width) && (devspec->height == appspec->height)) { + device->needs_scaling = 0; + } else { + const Uint64 srcarea = ((Uint64) devspec->width) * ((Uint64) devspec->height); + const Uint64 dstarea = ((Uint64) appspec->width) * ((Uint64) appspec->height); + if (dstarea <= srcarea) { + device->needs_scaling = -1; // downscaling (or changing to new aspect ratio with same area) + } else { + device->needs_scaling = 1; // upscaling + } + } + + device->needs_conversion = (devspec->format != appspec->format); + + device->acquire_surface = SDL_CreateSurfaceFrom(devspec->width, devspec->height, devspec->format, NULL, 0); + if (!device->acquire_surface) { + goto failed; + } + SDL_SetSurfaceColorspace(device->acquire_surface, devspec->colorspace); + + // if we have to scale _and_ convert, we need a middleman surface, since we can't do both changes at once. + if (device->needs_scaling && device->needs_conversion) { + const bool downscaling_first = (device->needs_scaling < 0); + const SDL_CameraSpec *s = downscaling_first ? appspec : devspec; + const SDL_PixelFormat fmt = downscaling_first ? devspec->format : appspec->format; + device->conversion_surface = SDL_CreateSurface(s->width, s->height, fmt); + if (!device->conversion_surface) { + goto failed; + } + SDL_SetSurfaceColorspace(device->conversion_surface, devspec->colorspace); + } + + // output surfaces are in the app-requested format. If no conversion is necessary, we'll just use the pointers + // the backend fills into acquired_surface, and you can get all the way from DMA access in the camera hardware + // to the app without a single copy. Otherwise, these will be full surfaces that hold converted/scaled copies. + + for (int i = 0; i < (SDL_arraysize(device->output_surfaces) - 1); i++) { + device->output_surfaces[i].next = &device->output_surfaces[i + 1]; + } + device->empty_output_surfaces.next = device->output_surfaces; + + for (int i = 0; i < SDL_arraysize(device->output_surfaces); i++) { + SDL_Surface *surf; + if (device->needs_scaling || device->needs_conversion) { + surf = SDL_CreateSurface(appspec->width, appspec->height, appspec->format); + } else { + surf = SDL_CreateSurfaceFrom(appspec->width, appspec->height, appspec->format, NULL, 0); + } + if (!surf) { + goto failed; + } + SDL_SetSurfaceColorspace(surf, devspec->colorspace); + + device->output_surfaces[i].surface = surf; + } + + return true; + +failed: + if (device->acquire_surface) { + SDL_DestroySurface(device->acquire_surface); + device->acquire_surface = NULL; + } + + if (device->conversion_surface) { + SDL_DestroySurface(device->conversion_surface); + device->conversion_surface = NULL; + } + + for (int i = 0; i < SDL_arraysize(device->output_surfaces); i++) { + SDL_Surface *surf = device->output_surfaces[i].surface; + if (surf) { + SDL_DestroySurface(surf); + } + } + SDL_zeroa(device->output_surfaces); + + return false; +} + static void ChooseBestCameraSpec(SDL_Camera *device, const SDL_CameraSpec *spec, SDL_CameraSpec *closest) { // Find the closest available native format/size... @@ -1106,85 +1214,19 @@ SDL_Camera *SDL_OpenCamera(SDL_CameraID instance_id, const SDL_CameraSpec *spec) return NULL; } - if (spec) { - SDL_copyp(&device->spec, spec); - if (spec->width <= 0 || spec->height <= 0) { - device->spec.width = closest.width; - device->spec.height = closest.height; - } - if (spec->format == SDL_PIXELFORMAT_UNKNOWN) { - device->spec.format = closest.format; - } - if (spec->framerate_denominator == 0) { - device->spec.framerate_numerator = closest.framerate_numerator; - device->spec.framerate_denominator = closest.framerate_denominator; - } - } else { - SDL_copyp(&device->spec, &closest); - } - + SDL_copyp(&device->spec, spec ? spec : &closest); SDL_copyp(&device->actual_spec, &closest); - if ((closest.width == device->spec.width) && (closest.height == device->spec.height)) { - device->needs_scaling = 0; - } else { - const Uint64 srcarea = ((Uint64) closest.width) * ((Uint64) closest.height); - const Uint64 dstarea = ((Uint64) device->spec.width) * ((Uint64) device->spec.height); - if (dstarea <= srcarea) { - device->needs_scaling = -1; // downscaling (or changing to new aspect ratio with same area) - } else { - device->needs_scaling = 1; // upscaling - } - } - - device->needs_conversion = (closest.format != device->spec.format); - - device->acquire_surface = SDL_CreateSurfaceFrom(closest.width, closest.height, closest.format, NULL, 0); - if (!device->acquire_surface) { - ClosePhysicalCamera(device); - ReleaseCamera(device); - return NULL; - } - SDL_SetSurfaceColorspace(device->acquire_surface, closest.colorspace); - - // if we have to scale _and_ convert, we need a middleman surface, since we can't do both changes at once. - if (device->needs_scaling && device->needs_conversion) { - const bool downsampling_first = (device->needs_scaling < 0); - const SDL_CameraSpec *s = downsampling_first ? &device->spec : &closest; - const SDL_PixelFormat fmt = downsampling_first ? closest.format : device->spec.format; - device->conversion_surface = SDL_CreateSurface(s->width, s->height, fmt); - if (!device->conversion_surface) { + // SDL_PIXELFORMAT_UNKNOWN here is taken as a signal that the backend + // doesn't know its format yet (Emscripten waiting for user permission, + // in this case), and the backend will call SDL_PrepareCameraSurfaces() + // itself, later but before the app is allowed to acquire images. + if (closest.format != SDL_PIXELFORMAT_UNKNOWN) { + if (!SDL_PrepareCameraSurfaces(device)) { ClosePhysicalCamera(device); ReleaseCamera(device); return NULL; } - SDL_SetSurfaceColorspace(device->conversion_surface, closest.colorspace); - } - - // output surfaces are in the app-requested format. If no conversion is necessary, we'll just use the pointers - // the backend fills into acquired_surface, and you can get all the way from DMA access in the camera hardware - // to the app without a single copy. Otherwise, these will be full surfaces that hold converted/scaled copies. - - for (int i = 0; i < (SDL_arraysize(device->output_surfaces) - 1); i++) { - device->output_surfaces[i].next = &device->output_surfaces[i + 1]; - } - device->empty_output_surfaces.next = device->output_surfaces; - - for (int i = 0; i < SDL_arraysize(device->output_surfaces); i++) { - SDL_Surface *surf; - if (device->needs_scaling || device->needs_conversion) { - surf = SDL_CreateSurface(device->spec.width, device->spec.height, device->spec.format); - } else { - surf = SDL_CreateSurfaceFrom(device->spec.width, device->spec.height, device->spec.format, NULL, 0); - } - if (!surf) { - ClosePhysicalCamera(device); - ReleaseCamera(device); - return NULL; - } - SDL_SetSurfaceColorspace(surf, closest.colorspace); - - device->output_surfaces[i].surface = surf; } device->drop_frames = 1; @@ -1204,7 +1246,7 @@ SDL_Camera *SDL_OpenCamera(SDL_CameraID instance_id, const SDL_CameraSpec *spec) ReleaseCamera(device); // unlock, we're good to go! - return (SDL_Camera *) device; // currently there's no separation between physical and logical device. + return device; // currently there's no separation between physical and logical device. } SDL_Surface *SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS) @@ -1218,7 +1260,7 @@ SDL_Surface *SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS) return NULL; } - SDL_Camera *device = (SDL_Camera *) camera; // currently there's no separation between physical and logical device. + SDL_Camera *device = camera; // currently there's no separation between physical and logical device. ObtainPhysicalCameraObj(device); @@ -1260,7 +1302,7 @@ void SDL_ReleaseCameraFrame(SDL_Camera *camera, SDL_Surface *frame) return; } - SDL_Camera *device = (SDL_Camera *) camera; // currently there's no separation between physical and logical device. + SDL_Camera *device = camera; // currently there's no separation between physical and logical device. ObtainPhysicalCameraObj(device); SurfaceList *slistprev = &device->app_held_output_surfaces; @@ -1302,7 +1344,7 @@ SDL_CameraID SDL_GetCameraID(SDL_Camera *camera) if (!camera) { SDL_InvalidParamError("camera"); } else { - SDL_Camera *device = (SDL_Camera *) camera; // currently there's no separation between physical and logical device. + SDL_Camera *device = camera; // currently there's no separation between physical and logical device. ObtainPhysicalCameraObj(device); result = device->instance_id; ReleaseCamera(device); @@ -1317,7 +1359,7 @@ SDL_PropertiesID SDL_GetCameraProperties(SDL_Camera *camera) if (!camera) { SDL_InvalidParamError("camera"); } else { - SDL_Camera *device = (SDL_Camera *) camera; // currently there's no separation between physical and logical device. + SDL_Camera *device = camera; // currently there's no separation between physical and logical device. ObtainPhysicalCameraObj(device); if (device->props == 0) { device->props = SDL_CreateProperties(); @@ -1336,7 +1378,7 @@ int SDL_GetCameraPermissionState(SDL_Camera *camera) SDL_InvalidParamError("camera"); result = -1; } else { - SDL_Camera *device = (SDL_Camera *) camera; // currently there's no separation between physical and logical device. + SDL_Camera *device = camera; // currently there's no separation between physical and logical device. ObtainPhysicalCameraObj(device); result = device->permission; ReleaseCamera(device); @@ -1380,37 +1422,26 @@ void SDL_QuitCamera(void) SDL_free(i); } - const void *key; - const void *value; - void *iter = NULL; - while (SDL_IterateHashTable(device_hash, &key, &value, &iter)) { - DestroyPhysicalCamera((SDL_Camera *) value); - } + SDL_DestroyHashTable(device_hash); // Free the driver data camera_driver.impl.Deinitialize(); SDL_DestroyRWLock(camera_driver.device_hash_lock); - SDL_DestroyHashTable(device_hash); SDL_zero(camera_driver); } - -static Uint32 HashCameraID(const void *key, void *data) +// Physical camera objects are only destroyed when removed from the device hash. +static void SDLCALL DestroyCameraHashItem(void *userdata, const void *key, const void *value) { - // The values are unique incrementing integers, starting at 1, so just return minus 1 to start with bucket zero. - return ((Uint32) ((uintptr_t) key)) - 1; -} - -static bool MatchCameraID(const void *a, const void *b, void *data) -{ - return (a == b); // simple integers, just compare them as pointer values. -} - -static void NukeCameraHashItem(const void *key, const void *value, void *data) -{ - // no-op, keys and values in this hashtable are treated as Plain Old Data and don't get freed here. + SDL_Camera *device = (SDL_Camera *) value; + ClosePhysicalCamera(device); + camera_driver.impl.FreeDeviceHandle(device); + SDL_DestroyMutex(device->lock); + SDL_free(device->all_specs); + SDL_free(device->name); + SDL_free(device); } bool SDL_CameraInit(const char *driver_name) @@ -1424,7 +1455,7 @@ bool SDL_CameraInit(const char *driver_name) return false; } - SDL_HashTable *device_hash = SDL_CreateHashTable(NULL, 8, HashCameraID, MatchCameraID, NukeCameraHashItem, false, false); + SDL_HashTable *device_hash = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, DestroyCameraHashItem, NULL); if (!device_hash) { SDL_DestroyRWLock(device_hash_lock); return false; diff --git a/libs/SDL3/src/camera/SDL_syscamera.h b/libs/SDL3/src/camera/SDL_syscamera.h index 7af115a..30a02f3 100644 --- a/libs/SDL3/src/camera/SDL_syscamera.h +++ b/libs/SDL3/src/camera/SDL_syscamera.h @@ -54,6 +54,10 @@ extern void SDL_CameraThreadSetup(SDL_Camera *device); extern bool SDL_CameraThreadIterate(SDL_Camera *device); extern void SDL_CameraThreadShutdown(SDL_Camera *device); +// Backends can call this if they have to finish initializing later, like Emscripten. Most backends should _not_ call this directly! +extern bool SDL_PrepareCameraSurfaces(SDL_Camera *device); + + // common utility functionality to gather up camera specs. Not required! typedef struct CameraFormatAddData { @@ -190,7 +194,7 @@ typedef struct SDL_CameraDriver const char *desc; // The description of this camera driver SDL_CameraDriverImpl impl; // the backend's interface - SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash` + SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash` // !!! FIXME: device_hash _also_ has a rwlock, see if we still need this one. SDL_HashTable *device_hash; // the collection of currently-available camera devices SDL_PendingCameraEvent pending_events; SDL_PendingCameraEvent *pending_events_tail; diff --git a/libs/SDL3/src/camera/android/SDL_camera_android.c b/libs/SDL3/src/camera/android/SDL_camera_android.c index 54b539a..97c7c4d 100644 --- a/libs/SDL3/src/camera/android/SDL_camera_android.c +++ b/libs/SDL3/src/camera/android/SDL_camera_android.c @@ -814,7 +814,7 @@ static void ANDROIDCAMERA_Deinitialize(void) static bool ANDROIDCAMERA_Init(SDL_CameraDriverImpl *impl) { // !!! FIXME: slide this off into a subroutine - // system libraries are in android-24 and later; we currently target android-16 and later, so check if they exist at runtime. + // system libraries are in android-24 and later; we currently target android-21 and later, so check if they exist at runtime. void *libcamera2 = dlopen("libcamera2ndk.so", RTLD_NOW | RTLD_LOCAL); if (!libcamera2) { SDL_Log("CAMERA: libcamera2ndk.so can't be loaded: %s", dlerror()); diff --git a/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c b/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c index 986c899..36418c5 100644 --- a/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c +++ b/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c @@ -61,7 +61,7 @@ static SDL_CameraFrameResult EMSCRIPTENCAMERA_AcquireFrame(SDL_Camera *device, S SDL3.camera.ctx2d.drawImage(SDL3.camera.video, 0, 0, w, h); const imgrgba = SDL3.camera.ctx2d.getImageData(0, 0, w, h).data; - Module.HEAPU8.set(imgrgba, rgba); + HEAPU8.set(imgrgba, rgba); return 1; }, device->actual_spec.width, device->actual_spec.height, rgba); @@ -98,17 +98,24 @@ static void EMSCRIPTENCAMERA_CloseDevice(SDL_Camera *device) } } -static void SDLEmscriptenCameraPermissionOutcome(SDL_Camera *device, int approved, int w, int h, int fps) +static int SDLEmscriptenCameraPermissionOutcome(SDL_Camera *device, int approved, int w, int h, int fps) { - device->spec.width = device->actual_spec.width = w; - device->spec.height = device->actual_spec.height = h; - device->spec.framerate_numerator = device->actual_spec.framerate_numerator = fps; - device->spec.framerate_denominator = device->actual_spec.framerate_denominator = 1; - if (device->acquire_surface) { - device->acquire_surface->w = w; - device->acquire_surface->h = h; + if (approved) { + device->actual_spec.format = SDL_PIXELFORMAT_RGBA32; + device->actual_spec.width = w; + device->actual_spec.height = h; + device->actual_spec.framerate_numerator = fps; + device->actual_spec.framerate_denominator = 1; + + if (!SDL_PrepareCameraSurfaces(device)) { + // uhoh, we're in trouble. Probably ran out of memory. + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Camera could not prepare surfaces: %s ... revoking approval!", SDL_GetError()); + approved = 0; // disconnecting the SDL camera might not be safe here, just mark it as denied by user. + } } + SDL_CameraPermissionOutcome(device, approved ? true : false); + return approved; } static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) @@ -167,40 +174,40 @@ static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec const actualfps = settings.frameRate; console.log("Camera is opened! Actual spec: (" + actualw + "x" + actualh + "), fps=" + actualfps); - dynCall('viiiii', outcome, [device, 1, actualw, actualh, actualfps]); + if (dynCall('iiiiii', outcome, [device, 1, actualw, actualh, actualfps])) { + const video = document.createElement("video"); + video.width = actualw; + video.height = actualh; + video.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels. + video.srcObject = stream; - const video = document.createElement("video"); - video.width = actualw; - video.height = actualh; - video.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels. - video.srcObject = stream; + const canvas = document.createElement("canvas"); + canvas.width = actualw; + canvas.height = actualh; + canvas.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels. - const canvas = document.createElement("canvas"); - canvas.width = actualw; - canvas.height = actualh; - canvas.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels. + const ctx2d = canvas.getContext('2d'); - const ctx2d = canvas.getContext('2d'); + const SDL3 = Module['SDL3']; + SDL3.camera.width = actualw; + SDL3.camera.height = actualh; + SDL3.camera.fps = actualfps; + SDL3.camera.fpsincrms = 1000.0 / actualfps; + SDL3.camera.stream = stream; + SDL3.camera.video = video; + SDL3.camera.canvas = canvas; + SDL3.camera.ctx2d = ctx2d; + SDL3.camera.next_frame_time = performance.now(); - const SDL3 = Module['SDL3']; - SDL3.camera.width = actualw; - SDL3.camera.height = actualh; - SDL3.camera.fps = actualfps; - SDL3.camera.fpsincrms = 1000.0 / actualfps; - SDL3.camera.stream = stream; - SDL3.camera.video = video; - SDL3.camera.canvas = canvas; - SDL3.camera.ctx2d = ctx2d; - SDL3.camera.next_frame_time = performance.now(); - - video.play(); - video.addEventListener('loadedmetadata', () => { - grabNextCameraFrame(); // start this loop going. - }); + video.play(); + video.addEventListener('loadedmetadata', () => { + grabNextCameraFrame(); // start this loop going. + }); + } }) .catch((err) => { console.error("Tried to open camera but it threw an error! " + err.name + ": " + err.message); - dynCall('viiiii', outcome, [device, 0, 0, 0, 0]); // we call this a permission error, because it probably is. + dynCall('iiiiii', outcome, [device, 0, 0, 0, 0]); // we call this a permission error, because it probably is. }); }, device, spec->width, spec->height, spec->framerate_numerator, spec->framerate_denominator, SDLEmscriptenCameraPermissionOutcome, SDL_CameraThreadIterate); diff --git a/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c b/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c index 086868f..d9d627d 100644 --- a/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c +++ b/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c @@ -76,6 +76,7 @@ SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_UYVY, FCC('UYVY')); SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YVYU, FCC('YVYU')); SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_NV12, FCC('NV12')); SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_NV21, FCC('NV21')); +SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_MJPG, FCC('MJPG')); #undef SDL_DEFINE_MEDIATYPE_GUID #ifdef __GNUC__ @@ -102,7 +103,8 @@ static const struct { &SDL_MFVideoFormat_UYVY, SDL_PIXELFORMAT_UYVY, SDL_COLORSPACE_BT709_LIMITED }, { &SDL_MFVideoFormat_YVYU, SDL_PIXELFORMAT_YVYU, SDL_COLORSPACE_BT709_LIMITED }, { &SDL_MFVideoFormat_NV12, SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_LIMITED }, - { &SDL_MFVideoFormat_NV21, SDL_PIXELFORMAT_NV21, SDL_COLORSPACE_BT709_LIMITED } + { &SDL_MFVideoFormat_NV21, SDL_PIXELFORMAT_NV21, SDL_COLORSPACE_BT709_LIMITED }, + { &SDL_MFVideoFormat_MJPG, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_SRGB } }; static SDL_Colorspace GetMediaTypeColorspace(IMFMediaType *mediatype, SDL_Colorspace default_colorspace) @@ -296,6 +298,13 @@ static void MediaTypeToSDLFmt(IMFMediaType *mediatype, SDL_PixelFormat *format, } } } +#if DEBUG_CAMERA + SDL_Log("Unknown media type: 0x%x (%c%c%c%c)", type.Data1, + (char)(Uint8)(type.Data1 >> 0), + (char)(Uint8)(type.Data1 >> 8), + (char)(Uint8)(type.Data1 >> 16), + (char)(Uint8)(type.Data1 >> 24)); +#endif *format = SDL_PIXELFORMAT_UNKNOWN; *colorspace = SDL_COLORSPACE_UNKNOWN; } @@ -424,7 +433,7 @@ static void SDLCALL CleanupIMFMediaBuffer(void *userdata, void *value) static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) { SDL_assert(device->hidden->current_sample != NULL); - + SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY; HRESULT ret; LONGLONG timestamp100NS = 0; @@ -457,46 +466,60 @@ static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SD } else { BYTE *pixels = NULL; LONG pitch = 0; + DWORD buflen = 0; if (SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer2, (void **)&objs->buffer2d2))) { BYTE *bufstart = NULL; - DWORD buflen = 0; ret = IMF2DBuffer2_Lock2DSize(objs->buffer2d2, MF2DBuffer_LockFlags_Read, &pixels, &pitch, &bufstart, &buflen); if (FAILED(ret)) { result = SDL_CAMERA_FRAME_ERROR; CleanupIMF2DBuffer2(NULL, objs); } else { + if (frame->format == SDL_PIXELFORMAT_MJPG) { + pitch = (LONG)buflen; + } + if (pitch < 0) { // image rows are reversed. + pixels += -pitch * (frame->h - 1); + } frame->pixels = pixels; - frame->pitch = (int) pitch; + frame->pitch = (int)pitch; if (!SDL_SetPointerPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer2, NULL)) { result = SDL_CAMERA_FRAME_ERROR; } } - } else if (SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer, (void **)&objs->buffer2d))) { + } else if (frame->format != SDL_PIXELFORMAT_MJPG && + SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer, (void **)&objs->buffer2d))) { ret = IMF2DBuffer_Lock2D(objs->buffer2d, &pixels, &pitch); if (FAILED(ret)) { CleanupIMF2DBuffer(NULL, objs); result = SDL_CAMERA_FRAME_ERROR; } else { + if (pitch < 0) { // image rows are reversed. + pixels += -pitch * (frame->h - 1); + } frame->pixels = pixels; - frame->pitch = (int) pitch; + frame->pitch = (int)pitch; if (!SDL_SetPointerPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer, NULL)) { result = SDL_CAMERA_FRAME_ERROR; } } } else { - DWORD maxlen = 0, currentlen = 0; - ret = IMFMediaBuffer_Lock(objs->buffer, &pixels, &maxlen, ¤tlen); + DWORD maxlen = 0; + ret = IMFMediaBuffer_Lock(objs->buffer, &pixels, &maxlen, &buflen); if (FAILED(ret)) { CleanupIMFMediaBuffer(NULL, objs); result = SDL_CAMERA_FRAME_ERROR; } else { - pitch = (LONG) device->hidden->pitch; - if (pitch < 0) { // image rows are reversed. + if (frame->format == SDL_PIXELFORMAT_MJPG) { + pitch = (LONG)buflen; + } else { + pitch = (LONG)device->hidden->pitch; + } + if (pitch < 0) { // image rows are reversed. pixels += -pitch * (frame->h - 1); } frame->pixels = pixels; - frame->pitch = (int) pitch; + frame->pitch = (int)pitch; if (!SDL_SetPointerPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMFMediaBuffer, NULL)) { result = SDL_CAMERA_FRAME_ERROR; } @@ -522,6 +545,23 @@ static void MEDIAFOUNDATION_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame) #else +static SDL_CameraFrameResult MEDIAFOUNDATION_CopyFrame(SDL_Surface *frame, const BYTE *pixels, LONG pitch, DWORD buflen) +{ + frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen); + if (!frame->pixels) { + return SDL_CAMERA_FRAME_ERROR; + } + + const BYTE *start = pixels; + if (pitch < 0) { // image rows are reversed. + start += -pitch * (frame->h - 1); + } + SDL_memcpy(frame->pixels, start, buflen); + frame->pitch = (int)pitch; + + return SDL_CAMERA_FRAME_READY; +} + static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) { SDL_assert(device->hidden->current_sample != NULL); @@ -555,63 +595,44 @@ static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SD IMF2DBuffer2 *buffer2d2 = NULL; BYTE *pixels = NULL; LONG pitch = 0; + DWORD buflen = 0; if (SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer2, (void **)&buffer2d2))) { BYTE *bufstart = NULL; - DWORD buflen = 0; ret = IMF2DBuffer2_Lock2DSize(buffer2d2, MF2DBuffer_LockFlags_Read, &pixels, &pitch, &bufstart, &buflen); if (FAILED(ret)) { result = SDL_CAMERA_FRAME_ERROR; } else { - frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen); - if (frame->pixels == NULL) { - result = SDL_CAMERA_FRAME_ERROR; - } else { - SDL_memcpy(frame->pixels, pixels, buflen); - frame->pitch = (int)pitch; + if (frame->format == SDL_PIXELFORMAT_MJPG) { + pitch = (LONG)buflen; } + result = MEDIAFOUNDATION_CopyFrame(frame, pixels, pitch, buflen); IMF2DBuffer2_Unlock2D(buffer2d2); } IMF2DBuffer2_Release(buffer2d2); - } else if (SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer, (void **)&buffer2d))) { + } else if (frame->format != SDL_PIXELFORMAT_MJPG && + SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer, (void **)&buffer2d))) { ret = IMF2DBuffer_Lock2D(buffer2d, &pixels, &pitch); if (FAILED(ret)) { result = SDL_CAMERA_FRAME_ERROR; } else { - BYTE *bufstart = pixels; - const DWORD buflen = (SDL_abs((int)pitch) * frame->w) * frame->h; - if (pitch < 0) { // image rows are reversed. - bufstart += -pitch * (frame->h - 1); - } - frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen); - if (frame->pixels == NULL) { - result = SDL_CAMERA_FRAME_ERROR; - } else { - SDL_memcpy(frame->pixels, bufstart, buflen); - frame->pitch = (int)pitch; - } + buflen = SDL_abs((int)pitch) * frame->h; + result = MEDIAFOUNDATION_CopyFrame(frame, pixels, pitch, buflen); IMF2DBuffer_Unlock2D(buffer2d); } IMF2DBuffer_Release(buffer2d); } else { - DWORD maxlen = 0, currentlen = 0; - ret = IMFMediaBuffer_Lock(buffer, &pixels, &maxlen, ¤tlen); + DWORD maxlen = 0; + ret = IMFMediaBuffer_Lock(buffer, &pixels, &maxlen, &buflen); if (FAILED(ret)) { result = SDL_CAMERA_FRAME_ERROR; } else { - BYTE *bufstart = pixels; - pitch = (LONG)device->hidden->pitch; - const DWORD buflen = (SDL_abs((int)pitch) * frame->w) * frame->h; - if (pitch < 0) { // image rows are reversed. - bufstart += -pitch * (frame->h - 1); - } - frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen); - if (frame->pixels == NULL) { - result = SDL_CAMERA_FRAME_ERROR; + if (frame->format == SDL_PIXELFORMAT_MJPG) { + pitch = (LONG)buflen; } else { - SDL_memcpy(frame->pixels, bufstart, buflen); - frame->pitch = (int)pitch; + pitch = (LONG)device->hidden->pitch; } + result = MEDIAFOUNDATION_CopyFrame(frame, pixels, pitch, buflen); IMFMediaBuffer_Unlock(buffer); } } diff --git a/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c b/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c index 43e54d2..5d868bf 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); @@ -374,10 +378,8 @@ static struct sdl_video_format { { SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_YUY2 }, { SDL_PIXELFORMAT_UYVY, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_UYVY }, { SDL_PIXELFORMAT_YVYU, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_YVYU }, -#if SDL_VERSION_ATLEAST(2,0,4) { SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_NV12 }, - { SDL_PIXELFORMAT_NV21, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_NV21 }, -#endif + { SDL_PIXELFORMAT_NV21, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_NV21 } }; static uint32_t sdl_format_to_id(SDL_PixelFormat format) @@ -500,14 +502,24 @@ static bool PIPEWIRECAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec * &device->hidden->stream_listener, &stream_events, device); - params[n_params++] = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_VIDEO_format, SPA_POD_Id(sdl_format_to_id(spec->format)), - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(spec->width, spec->height)), - SPA_FORMAT_VIDEO_framerate, - SPA_POD_Fraction(&SPA_FRACTION(spec->framerate_numerator, spec->framerate_denominator))); + if (spec->format == SDL_PIXELFORMAT_MJPG) { + params[n_params++] = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mjpg), + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(spec->width, spec->height)), + SPA_FORMAT_VIDEO_framerate, + SPA_POD_Fraction(&SPA_FRACTION(spec->framerate_numerator, spec->framerate_denominator))); + } else { + params[n_params++] = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_VIDEO_format, SPA_POD_Id(sdl_format_to_id(spec->format)), + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(spec->width, spec->height)), + SPA_FORMAT_VIDEO_framerate, + SPA_POD_Fraction(&SPA_FRACTION(spec->framerate_numerator, spec->framerate_denominator))); + } if ((res = PIPEWIRE_pw_stream_connect(device->hidden->stream, PW_DIRECTION_INPUT, @@ -573,7 +585,11 @@ static SDL_CameraFrameResult PIPEWIRECAMERA_AcquireFrame(SDL_Camera *device, SDL *timestampNS = SDL_GetTicksNS(); #endif frame->pixels = b->buffer->datas[0].data; - frame->pitch = b->buffer->datas[0].chunk->stride; + if (frame->format == SDL_PIXELFORMAT_MJPG) { + frame->pitch = b->buffer->datas[0].chunk->size; + } else { + frame->pitch = b->buffer->datas[0].chunk->stride; + } PIPEWIRE_pw_thread_loop_unlock(hotplug.loop); @@ -612,7 +628,7 @@ static void collect_rates(CameraFormatAddData *data, struct param *p, SDL_PixelF switch (choice) { case SPA_CHOICE_None: n_vals = 1; - SPA_FALLTHROUGH; + SDL_FALLTHROUGH; case SPA_CHOICE_Enum: for (i = 0; i < n_vals; i++) { if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, size->width, size->height, rates[i].num, rates[i].denom)) { @@ -645,7 +661,7 @@ static void collect_size(CameraFormatAddData *data, struct param *p, SDL_PixelFo switch (choice) { case SPA_CHOICE_None: n_vals = 1; - SPA_FALLTHROUGH; + SDL_FALLTHROUGH; case SPA_CHOICE_Enum: for (i = 0; i < n_vals; i++) { collect_rates(data, p, sdlfmt, colorspace, &rectangles[i]); @@ -657,7 +673,7 @@ static void collect_size(CameraFormatAddData *data, struct param *p, SDL_PixelFo } } -static void collect_format(CameraFormatAddData *data, struct param *p) +static void collect_raw(CameraFormatAddData *data, struct param *p) { const struct spa_pod_prop *prop; SDL_PixelFormat sdlfmt; @@ -677,7 +693,7 @@ static void collect_format(CameraFormatAddData *data, struct param *p) switch (choice) { case SPA_CHOICE_None: n_vals = 1; - SPA_FALLTHROUGH; + SDL_FALLTHROUGH; case SPA_CHOICE_Enum: for (i = 0; i < n_vals; i++) { id_to_sdl_format(ids[i], &sdlfmt, &colorspace); @@ -688,7 +704,47 @@ static void collect_format(CameraFormatAddData *data, struct param *p) } break; default: - SDL_Log("CAMERA: unimplemented choice:%d", choice); + SDL_Log("CAMERA: unimplemented choice: %d", choice); + break; + } +} + +static void collect_format(CameraFormatAddData *data, struct param *p) +{ + const struct spa_pod_prop *prop; + struct spa_pod * values; + uint32_t i, n_vals, choice, *ids; + + prop = spa_pod_find_prop(p->param, NULL, SPA_FORMAT_mediaSubtype); + if (prop == NULL) + return; + + values = spa_pod_get_values(&prop->value, &n_vals, &choice); + if (values->type != SPA_TYPE_Id || n_vals == 0) + return; + + ids = SPA_POD_BODY(values); + switch (choice) { + case SPA_CHOICE_None: + n_vals = 1; + SDL_FALLTHROUGH; + case SPA_CHOICE_Enum: + for (i = 0; i < n_vals; i++) { + switch (ids[i]) { + case SPA_MEDIA_SUBTYPE_raw: + collect_raw(data, p); + break; + case SPA_MEDIA_SUBTYPE_mjpg: + collect_size(data, p, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_JPEG); + break; + default: + // Unsupported format + break; + } + } + break; + default: + SDL_Log("CAMERA: unimplemented choice: %d", choice); break; } } @@ -972,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/camera/v4l2/SDL_camera_v4l2.c b/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c index a4d4df6..9cdb54b 100644 --- a/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c +++ b/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c @@ -128,10 +128,11 @@ static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface * const io_method io = device->hidden->io; size_t size = device->hidden->buffers[0].length; struct v4l2_buffer buf; + ssize_t amount; switch (io) { case IO_METHOD_READ: - if (read(fd, device->hidden->buffers[0].start, size) == -1) { + if ((amount = read(fd, device->hidden->buffers[0].start, size)) == -1) { switch (errno) { case EAGAIN: return SDL_CAMERA_FRAME_SKIP; @@ -148,7 +149,11 @@ static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface * *timestampNS = SDL_GetTicksNS(); // oh well, close enough. frame->pixels = device->hidden->buffers[0].start; - frame->pitch = device->hidden->driver_pitch; + if (device->hidden->driver_pitch) { + frame->pitch = device->hidden->driver_pitch; + } else { + frame->pitch = (int)amount; + } break; case IO_METHOD_MMAP: @@ -178,7 +183,11 @@ static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface * } frame->pixels = device->hidden->buffers[buf.index].start; - frame->pitch = device->hidden->driver_pitch; + if (device->hidden->driver_pitch) { + frame->pitch = device->hidden->driver_pitch; + } else { + frame->pitch = buf.bytesused; + } device->hidden->buffers[buf.index].available = 1; *timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec); @@ -222,7 +231,11 @@ static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface * } frame->pixels = (void*)buf.m.userptr; - frame->pitch = device->hidden->driver_pitch; + if (device->hidden->driver_pitch) { + frame->pitch = device->hidden->driver_pitch; + } else { + frame->pitch = buf.bytesused; + } device->hidden->buffers[i].available = 1; *timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec); @@ -404,10 +417,15 @@ static void format_v4l2_to_sdl(Uint32 fmt, SDL_PixelFormat *format, SDL_Colorspa switch (fmt) { #define CASE(x, y, z) case x: *format = y; *colorspace = z; return CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED); + CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_SRGB); #undef CASE default: #if DEBUG_CAMERA - SDL_Log("CAMERA: Unknown format V4L2_PIX_FORMAT '%d'", fmt); + SDL_Log("CAMERA: Unknown format V4L2_PIX_FORMAT '%c%c%c%c' (0x%x)", + (char)(Uint8)(fmt >> 0), + (char)(Uint8)(fmt >> 8), + (char)(Uint8)(fmt >> 16), + (char)(Uint8)(fmt >> 24), fmt); #endif break; } @@ -420,10 +438,10 @@ static Uint32 format_sdl_to_v4l2(SDL_PixelFormat fmt) switch (fmt) { #define CASE(y, x) case x: return y CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2); - CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_UNKNOWN); + CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_MJPG); #undef CASE default: - return true; + return 0; } } diff --git a/libs/SDL3/src/core/android/SDL_android.c b/libs/SDL3/src/core/android/SDL_android.c index daf0f29..1cc9ccd 100644 --- a/libs/SDL3/src/core/android/SDL_android.c +++ b/libs/SDL3/src/core/android/SDL_android.c @@ -371,6 +371,7 @@ static jmethodID midShowTextInput; static jmethodID midSupportsRelativeMouse; static jmethodID midOpenFileDescriptor; static jmethodID midShowFileDialog; +static jmethodID midGetPreferredLocales; // audio manager static jclass mAudioManagerClass; @@ -660,6 +661,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl midSupportsRelativeMouse = (*env)->GetStaticMethodID(env, mActivityClass, "supportsRelativeMouse", "()Z"); midOpenFileDescriptor = (*env)->GetStaticMethodID(env, mActivityClass, "openFileDescriptor", "(Ljava/lang/String;Ljava/lang/String;)I"); midShowFileDialog = (*env)->GetStaticMethodID(env, mActivityClass, "showFileDialog", "([Ljava/lang/String;ZZI)Z"); + midGetPreferredLocales = (*env)->GetStaticMethodID(env, mActivityClass, "getPreferredLocales", "()Ljava/lang/String;"); if (!midClipboardGetText || !midClipboardHasText || @@ -691,7 +693,8 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl !midShowTextInput || !midSupportsRelativeMouse || !midOpenFileDescriptor || - !midShowFileDialog) { + !midShowFileDialog || + !midGetPreferredLocales) { __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?"); } @@ -751,6 +754,8 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env typedef int (*SDL_main_func)(int argc, char *argv[]); static int run_count = 0; +static bool allow_recreate_activity; +static bool allow_recreate_activity_set; JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter)( JNIEnv *env, jclass jcls) @@ -760,10 +765,16 @@ JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter)( return tmp; } +void Android_SetAllowRecreateActivity(bool enabled) +{ + allow_recreate_activity = enabled; + allow_recreate_activity_set = true; +} + JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeAllowRecreateActivity)( JNIEnv *env, jclass jcls) { - return SDL_GetHintBoolean(SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY, false); + return allow_recreate_activity; } JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeInitMainThread)( @@ -1526,6 +1537,14 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)( // Note that we call setenv() directly to avoid affecting SDL environments setenv(utfname, utfvalue, 1); // This should NOT be SDL_setenv() + if (SDL_strcmp(utfname, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) { + // Special handling for this hint, which needs to persist outside the normal application flow + // Only set this the first time we run, in case it's been set by the application via SDL_SetHint() + if (!allow_recreate_activity_set) { + Android_SetAllowRecreateActivity(SDL_GetStringBoolean(utfvalue, false)); + } + } + (*env)->ReleaseStringUTFChars(env, name, utfname); (*env)->ReleaseStringUTFChars(env, value, utfvalue); } @@ -2569,65 +2588,22 @@ bool Android_JNI_ShowToast(const char *message, int duration, int gravity, int x bool Android_JNI_GetLocale(char *buf, size_t buflen) { - AConfiguration *cfg; - - SDL_assert(buflen > 6); - - // Need to re-create the asset manager if locale has changed (SDL_EVENT_LOCALE_CHANGED) - Internal_Android_Destroy_AssetManager(); - - if (!asset_manager) { - Internal_Android_Create_AssetManager(); - } - - if (!asset_manager) { - return false; - } - - cfg = AConfiguration_new(); - if (!cfg) { - return false; - } - - { - char language[2] = {}; - char country[2] = {}; - size_t id = 0; - - AConfiguration_fromAssetManager(cfg, asset_manager); - AConfiguration_getLanguage(cfg, language); - AConfiguration_getCountry(cfg, country); - - // Indonesian is "id" according to ISO 639.2, but on Android is "in" because of Java backwards compatibility - if (language[0] == 'i' && language[1] == 'n') { - language[1] = 'd'; - } - - // copy language (not null terminated) - if (language[0]) { - buf[id++] = language[0]; - if (language[1]) { - buf[id++] = language[1]; + bool result = false; + if (buf && buflen > 0) { + *buf = '\0'; + JNIEnv *env = Android_JNI_GetEnv(); + jstring string = (jstring)(*env)->CallStaticObjectMethod(env, mActivityClass, midGetPreferredLocales); + if (string) { + const char *utf8string = (*env)->GetStringUTFChars(env, string, NULL); + if (utf8string) { + result = true; + SDL_strlcpy(buf, utf8string, buflen); + (*env)->ReleaseStringUTFChars(env, string, utf8string); } + (*env)->DeleteLocalRef(env, string); } - - buf[id++] = '_'; - - // copy country (not null terminated) - if (country[0]) { - buf[id++] = country[0]; - if (country[1]) { - buf[id++] = country[1]; - } - } - - buf[id++] = '\0'; - SDL_assert(id <= buflen); } - - AConfiguration_delete(cfg); - - return true; + return result; } bool Android_JNI_OpenURL(const char *url) diff --git a/libs/SDL3/src/core/android/SDL_android.h b/libs/SDL3/src/core/android/SDL_android.h index 3541c2a..620639c 100644 --- a/libs/SDL3/src/core/android/SDL_android.h +++ b/libs/SDL3/src/core/android/SDL_android.h @@ -55,6 +55,8 @@ bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeout void Android_LockActivityMutex(void); void Android_UnlockActivityMutex(void); +void Android_SetAllowRecreateActivity(bool enabled); + // Interface from the SDL library into the Android Java activity extern void Android_JNI_SetActivityTitle(const char *title); extern void Android_JNI_SetWindowStyle(bool fullscreen); diff --git a/libs/SDL3/src/core/gdk/SDL_gdk.cpp b/libs/SDL3/src/core/gdk/SDL_gdk.cpp index 738daa5..4e792ef 100644 --- a/libs/SDL3/src/core/gdk/SDL_gdk.cpp +++ b/libs/SDL3/src/core/gdk/SDL_gdk.cpp @@ -105,7 +105,7 @@ bool GDK_RegisterChangeNotifications(void) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "[GDK] in RegisterAppConstrainedChangeNotification handler"); SDL_VideoDevice *_this = SDL_GetVideoDevice(); if (_this) { - if (constrained) { + if (constrained && !((_this->windows) && _this->windows->text_input_active)) { SDL_SetKeyboardFocus(NULL); } else { SDL_SetKeyboardFocus(_this->windows); diff --git a/libs/SDL3/src/core/haiku/SDL_BApp.h b/libs/SDL3/src/core/haiku/SDL_BApp.h index b13b9e6..cbd3e3a 100644 --- a/libs/SDL3/src/core/haiku/SDL_BApp.h +++ b/libs/SDL3/src/core/haiku/SDL_BApp.h @@ -293,12 +293,9 @@ class SDL_BLooper : public BLooper void _HandleKey(BMessage *msg) { - SDL_Window *win; - int32 winID; int32 scancode; - bool down; + bool down; if ( - !_GetWinID(msg, &winID) || msg->FindInt32("key-scancode", &scancode) != B_OK || msg->FindBool("key-down", &down) != B_OK) { return; @@ -306,15 +303,17 @@ class SDL_BLooper : public BLooper SDL_SendKeyboardKey(0, SDL_DEFAULT_KEYBOARD_ID, scancode, HAIKU_GetScancodeFromBeKey(scancode), down); - win = GetSDLWindow(winID); - if (down && SDL_TextInputActive(win)) { - const int8 *keyUtf8; - ssize_t count; - if (msg->FindData("key-utf8", B_INT8_TYPE, (const void **)&keyUtf8, &count) == B_OK) { - char text[64]; - SDL_zeroa(text); - SDL_memcpy(text, keyUtf8, count); - SDL_SendKeyboardText(text); + if (down) { + SDL_Window *win = SDL_GetKeyboardFocus(); + if (win && SDL_TextInputActive(win)) { + const int8 *keyUtf8; + ssize_t count; + if (msg->FindData("key-utf8", B_INT8_TYPE, (const void **)&keyUtf8, &count) == B_OK) { + char text[64]; + SDL_zeroa(text); + SDL_memcpy(text, keyUtf8, count); + SDL_SendKeyboardText(text); + } } } } diff --git a/libs/SDL3/src/core/unix/SDL_poll.c b/libs/SDL3/src/core/unix/SDL_poll.c index 9440bff..572ed5a 100644 --- a/libs/SDL3/src/core/unix/SDL_poll.c +++ b/libs/SDL3/src/core/unix/SDL_poll.c @@ -54,7 +54,7 @@ int SDL_IOReady(int fd, int flags, Sint64 timeoutNS) } // FIXME: Add support for ppoll() for nanosecond precision if (timeoutNS > 0) { - timeoutMS = (int)SDL_NS_TO_MS(timeoutNS); + timeoutMS = (int)SDL_NS_TO_MS(timeoutNS + (SDL_NS_PER_MS - 1)); } else if (timeoutNS == 0) { timeoutMS = 0; } else { @@ -82,7 +82,7 @@ int SDL_IOReady(int fd, int flags, Sint64 timeoutNS) if (timeoutNS >= 0) { tv.tv_sec = (timeoutNS / SDL_NS_PER_SECOND); - tv.tv_usec = SDL_NS_TO_US(timeoutNS % SDL_NS_PER_SECOND); + tv.tv_usec = SDL_NS_TO_US((timeoutNS % SDL_NS_PER_SECOND) + (SDL_NS_PER_US - 1)); tvp = &tv; } diff --git a/libs/SDL3/src/core/windows/SDL_gameinput.c b/libs/SDL3/src/core/windows/SDL_gameinput.c new file mode 100644 index 0000000..9ac5912 --- /dev/null +++ b/libs/SDL3/src/core/windows/SDL_gameinput.c @@ -0,0 +1,98 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef HAVE_GAMEINPUT_H + +#include "SDL_windows.h" +#include "SDL_gameinput.h" + +#ifdef SDL_PLATFORM_WIN32 +#include +// {11BE2A7E-4254-445A-9C09-FFC40F006918} +DEFINE_GUID(SDL_IID_GameInput, 0x11BE2A7E, 0x4254, 0x445A, 0x9C, 0x09, 0xFF, 0xC4, 0x0F, 0x00, 0x69, 0x18); +#endif + +static SDL_SharedObject *g_hGameInputDLL; +static IGameInput *g_pGameInput; +static int g_nGameInputRefCount; + +bool SDL_InitGameInput(IGameInput **ppGameInput) +{ + if (g_nGameInputRefCount == 0) { + g_hGameInputDLL = SDL_LoadObject("gameinput.dll"); + if (!g_hGameInputDLL) { + return false; + } + + typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput * *gameInput); + GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate"); + if (!GameInputCreateFunc) { + SDL_UnloadObject(g_hGameInputDLL); + return false; + } + + IGameInput *pGameInput = NULL; + HRESULT hr = GameInputCreateFunc(&pGameInput); + if (FAILED(hr)) { + SDL_UnloadObject(g_hGameInputDLL); + return WIN_SetErrorFromHRESULT("GameInputCreate failed", hr); + } + +#ifdef SDL_PLATFORM_WIN32 + hr = IGameInput_QueryInterface(pGameInput, &SDL_IID_GameInput, (void **)&g_pGameInput); + IGameInput_Release(pGameInput); + if (FAILED(hr)) { + SDL_UnloadObject(g_hGameInputDLL); + return WIN_SetErrorFromHRESULT("GameInput QueryInterface failed", hr); + } +#else + // Assume that the version we get is compatible with the current SDK + // If that isn't the case, define the correct GUID for SDL_IID_GameInput above + g_pGameInput = pGameInput; +#endif + } + ++g_nGameInputRefCount; + + if (ppGameInput) { + *ppGameInput = g_pGameInput; + } + return true; +} + +void SDL_QuitGameInput(void) +{ + SDL_assert(g_nGameInputRefCount > 0); + + --g_nGameInputRefCount; + if (g_nGameInputRefCount == 0) { + if (g_pGameInput) { + IGameInput_Release(g_pGameInput); + g_pGameInput = NULL; + } + if (g_hGameInputDLL) { + SDL_UnloadObject(g_hGameInputDLL); + g_hGameInputDLL = NULL; + } + } +} + +#endif // HAVE_GAMEINPUT_H diff --git a/libs/SDL3/src/core/windows/SDL_gameinput.h b/libs/SDL3/src/core/windows/SDL_gameinput.h new file mode 100644 index 0000000..0022c0b --- /dev/null +++ b/libs/SDL3/src/core/windows/SDL_gameinput.h @@ -0,0 +1,36 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_gameinput_h_ +#define SDL_gameinput_h_ + +#ifdef HAVE_GAMEINPUT_H + +#define COBJMACROS +#include + +extern bool SDL_InitGameInput(IGameInput **ppGameInput); +extern void SDL_QuitGameInput(void); + +#endif // HAVE_GAMEINPUT_H + +#endif // SDL_gameinput_h_ diff --git a/libs/SDL3/src/core/windows/SDL_hid.c b/libs/SDL3/src/core/windows/SDL_hid.c index 9874815..87e8735 100644 --- a/libs/SDL3/src/core/windows/SDL_hid.c +++ b/libs/SDL3/src/core/windows/SDL_hid.c @@ -22,6 +22,7 @@ #include "SDL_hid.h" +HidD_GetAttributes_t SDL_HidD_GetAttributes; HidD_GetString_t SDL_HidD_GetManufacturerString; HidD_GetString_t SDL_HidD_GetProductString; HidP_GetCaps_t SDL_HidP_GetCaps; @@ -50,6 +51,7 @@ bool WIN_LoadHIDDLL(void) SDL_assert(s_HIDDLLRefCount == 0); s_HIDDLLRefCount = 1; + SDL_HidD_GetAttributes = (HidD_GetAttributes_t)GetProcAddress(s_pHIDDLL, "HidD_GetAttributes"); SDL_HidD_GetManufacturerString = (HidD_GetString_t)GetProcAddress(s_pHIDDLL, "HidD_GetManufacturerString"); SDL_HidD_GetProductString = (HidD_GetString_t)GetProcAddress(s_pHIDDLL, "HidD_GetProductString"); SDL_HidP_GetCaps = (HidP_GetCaps_t)GetProcAddress(s_pHIDDLL, "HidP_GetCaps"); diff --git a/libs/SDL3/src/core/windows/SDL_hid.h b/libs/SDL3/src/core/windows/SDL_hid.h index 8b20df1..46c22f2 100644 --- a/libs/SDL3/src/core/windows/SDL_hid.h +++ b/libs/SDL3/src/core/windows/SDL_hid.h @@ -191,6 +191,7 @@ typedef struct extern bool WIN_LoadHIDDLL(void); extern void WIN_UnloadHIDDLL(void); +typedef BOOLEAN (WINAPI *HidD_GetAttributes_t)(HANDLE HidDeviceObject, PHIDD_ATTRIBUTES Attributes); typedef BOOLEAN (WINAPI *HidD_GetString_t)(HANDLE HidDeviceObject, PVOID Buffer, ULONG BufferLength); typedef NTSTATUS (WINAPI *HidP_GetCaps_t)(PHIDP_PREPARSED_DATA PreparsedData, PHIDP_CAPS Capabilities); typedef NTSTATUS (WINAPI *HidP_GetButtonCaps_t)(HIDP_REPORT_TYPE ReportType, PHIDP_BUTTON_CAPS ButtonCaps, PUSHORT ButtonCapsLength, PHIDP_PREPARSED_DATA PreparsedData); @@ -198,6 +199,7 @@ typedef NTSTATUS (WINAPI *HidP_GetValueCaps_t)(HIDP_REPORT_TYPE ReportType, PHID typedef ULONG (WINAPI *HidP_MaxDataListLength_t)(HIDP_REPORT_TYPE ReportType, PHIDP_PREPARSED_DATA PreparsedData); typedef NTSTATUS (WINAPI *HidP_GetData_t)(HIDP_REPORT_TYPE ReportType, PHIDP_DATA DataList, PULONG DataLength, PHIDP_PREPARSED_DATA PreparsedData, PCHAR Report, ULONG ReportLength); +extern HidD_GetAttributes_t SDL_HidD_GetAttributes; extern HidD_GetString_t SDL_HidD_GetManufacturerString; extern HidD_GetString_t SDL_HidD_GetProductString; extern HidP_GetCaps_t SDL_HidP_GetCaps; diff --git a/libs/SDL3/src/core/windows/SDL_immdevice.c b/libs/SDL3/src/core/windows/SDL_immdevice.c index 802a412..cc6945b 100644 --- a/libs/SDL3/src/core/windows/SDL_immdevice.c +++ b/libs/SDL3/src/core/windows/SDL_immdevice.c @@ -120,7 +120,7 @@ void SDL_IMMDevice_FreeDeviceHandle(SDL_AudioDevice *device) } } -static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid, GUID *dsoundguid) +static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid, GUID *dsoundguid, SDL_AudioFormat force_format) { /* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever). In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for @@ -162,7 +162,7 @@ static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devn SDL_zero(spec); spec.channels = (Uint8)fmt->Format.nChannels; spec.freq = fmt->Format.nSamplesPerSec; - spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt); + spec.format = (force_format != SDL_AUDIO_UNKNOWN) ? force_format : SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt); device = SDL_AddAudioDevice(recording, devname, &spec, handle); if (!device) { @@ -183,6 +183,7 @@ typedef struct SDLMMNotificationClient { const IMMNotificationClientVtbl *lpVtbl; SDL_AtomicInt refcount; + SDL_AudioFormat force_format; } SDLMMNotificationClient; static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_QueryInterface(IMMNotificationClient *client, REFIID iid, void **ppv) @@ -241,6 +242,7 @@ static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceRemoved(IMMNoti static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *iclient, LPCWSTR pwstrDeviceId, DWORD dwNewState) { + SDLMMNotificationClient *client = (SDLMMNotificationClient *)iclient; IMMDevice *device = NULL; if (SUCCEEDED(IMMDeviceEnumerator_GetDevice(enumerator, pwstrDeviceId, &device))) { @@ -255,7 +257,7 @@ static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceStateChanged(IM GUID dsoundguid; GetMMDeviceInfo(device, &utf8dev, &fmt, &dsoundguid); if (utf8dev) { - SDL_IMMDevice_Add(recording, utf8dev, &fmt, pwstrDeviceId, &dsoundguid); + SDL_IMMDevice_Add(recording, utf8dev, &fmt, pwstrDeviceId, &dsoundguid, client->force_format); SDL_free(utf8dev); } } else { @@ -286,7 +288,7 @@ static const IMMNotificationClientVtbl notification_client_vtbl = { SDLMMNotificationClient_OnPropertyValueChanged }; -static SDLMMNotificationClient notification_client = { ¬ification_client_vtbl, { 1 } }; +static SDLMMNotificationClient notification_client = { ¬ification_client_vtbl, { 1 }, SDL_AUDIO_UNKNOWN }; bool SDL_IMMDevice_Init(const SDL_IMMDevice_callbacks *callbacks) { @@ -363,7 +365,7 @@ bool SDL_IMMDevice_Get(SDL_AudioDevice *device, IMMDevice **immdevice, bool reco return true; } -static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **default_device) +static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **default_device, SDL_AudioFormat force_format) { /* Note that WASAPI separates "adapter devices" from "audio endpoint devices" ...one adapter device ("SoundBlaster Pro") might have multiple endpoint devices ("Speakers", "Line-Out"). */ @@ -405,7 +407,7 @@ static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **de SDL_zero(dsoundguid); GetMMDeviceInfo(immdevice, &devname, &fmt, &dsoundguid); if (devname) { - SDL_AudioDevice *sdldevice = SDL_IMMDevice_Add(recording, devname, &fmt, devid, &dsoundguid); + SDL_AudioDevice *sdldevice = SDL_IMMDevice_Add(recording, devname, &fmt, devid, &dsoundguid, force_format); if (default_device && default_devid && SDL_wcscmp(default_devid, devid) == 0) { *default_device = sdldevice; } @@ -422,10 +424,12 @@ static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **de IMMDeviceCollection_Release(collection); } -void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) +void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording, SDL_AudioFormat force_format) { - EnumerateEndpointsForFlow(false, default_playback); - EnumerateEndpointsForFlow(true, default_recording); + EnumerateEndpointsForFlow(false, default_playback, force_format); + EnumerateEndpointsForFlow(true, default_recording, force_format); + + notification_client.force_format = force_format; // if this fails, we just won't get hotplug events. Carry on anyhow. IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)¬ification_client); diff --git a/libs/SDL3/src/core/windows/SDL_immdevice.h b/libs/SDL3/src/core/windows/SDL_immdevice.h index 66fdf13..0582bc0 100644 --- a/libs/SDL3/src/core/windows/SDL_immdevice.h +++ b/libs/SDL3/src/core/windows/SDL_immdevice.h @@ -37,7 +37,7 @@ typedef struct SDL_IMMDevice_callbacks bool SDL_IMMDevice_Init(const SDL_IMMDevice_callbacks *callbacks); void SDL_IMMDevice_Quit(void); bool SDL_IMMDevice_Get(struct SDL_AudioDevice *device, IMMDevice **immdevice, bool recording); -void SDL_IMMDevice_EnumerateEndpoints(struct SDL_AudioDevice **default_playback, struct SDL_AudioDevice **default_recording); +void SDL_IMMDevice_EnumerateEndpoints(struct SDL_AudioDevice **default_playback, struct SDL_AudioDevice **default_recording, SDL_AudioFormat force_format); LPGUID SDL_IMMDevice_GetDirectSoundGUID(struct SDL_AudioDevice *device); LPCWSTR SDL_IMMDevice_GetDevID(struct SDL_AudioDevice *device); void SDL_IMMDevice_FreeDeviceHandle(struct SDL_AudioDevice *device); diff --git a/libs/SDL3/src/core/windows/SDL_windows.c b/libs/SDL3/src/core/windows/SDL_windows.c index 286e8e6..163635a 100644 --- a/libs/SDL3/src/core/windows/SDL_windows.c +++ b/libs/SDL3/src/core/windows/SDL_windows.c @@ -53,6 +53,78 @@ typedef enum RO_INIT_TYPE #define WC_ERR_INVALID_CHARS 0x00000080 #endif +// Fake window to help with DirectInput events. +HWND SDL_HelperWindow = NULL; +static const TCHAR *SDL_HelperWindowClassName = TEXT("SDLHelperWindowInputCatcher"); +static const TCHAR *SDL_HelperWindowName = TEXT("SDLHelperWindowInputMsgWindow"); +static ATOM SDL_HelperWindowClass = 0; + +/* + * Creates a HelperWindow used for DirectInput. + */ +bool SDL_HelperWindowCreate(void) +{ + HINSTANCE hInstance = GetModuleHandle(NULL); + WNDCLASS wce; + + // Make sure window isn't created twice. + if (SDL_HelperWindow != NULL) { + return true; + } + + // Create the class. + SDL_zero(wce); + wce.lpfnWndProc = DefWindowProc; + wce.lpszClassName = SDL_HelperWindowClassName; + wce.hInstance = hInstance; + + // Register the class. + SDL_HelperWindowClass = RegisterClass(&wce); + if (SDL_HelperWindowClass == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) { + return WIN_SetError("Unable to create Helper Window Class"); + } + + // Create the window. + SDL_HelperWindow = CreateWindowEx(0, SDL_HelperWindowClassName, + SDL_HelperWindowName, + WS_OVERLAPPED, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, HWND_MESSAGE, NULL, + hInstance, NULL); + if (!SDL_HelperWindow) { + UnregisterClass(SDL_HelperWindowClassName, hInstance); + return WIN_SetError("Unable to create Helper Window"); + } + + return true; +} + +/* + * Destroys the HelperWindow previously created with SDL_HelperWindowCreate. + */ +void SDL_HelperWindowDestroy(void) +{ + HINSTANCE hInstance = GetModuleHandle(NULL); + + // Destroy the window. + if (SDL_HelperWindow != NULL) { + if (DestroyWindow(SDL_HelperWindow) == 0) { + WIN_SetError("Unable to destroy Helper Window"); + return; + } + SDL_HelperWindow = NULL; + } + + // Unregister the class. + if (SDL_HelperWindowClass != 0) { + if ((UnregisterClass(SDL_HelperWindowClassName, hInstance)) == 0) { + WIN_SetError("Unable to destroy Helper Window Class"); + return; + } + SDL_HelperWindowClass = 0; + } +} + // Sets an error message based on an HRESULT bool WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr) { @@ -330,10 +402,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 b781b12..ef54fe3 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 4a84a43..568e832 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,4,0 - PRODUCTVERSION 3,2,4,0 + FILEVERSION 3,2,20,0 + PRODUCTVERSION 3,2,20,0 FILEFLAGSMASK 0x3fL FILEFLAGS 0x0L FILEOS 0x40004L @@ -23,12 +23,12 @@ BEGIN BEGIN VALUE "CompanyName", "\0" VALUE "FileDescription", "SDL\0" - VALUE "FileVersion", "3, 2, 4, 0\0" + VALUE "FileVersion", "3, 2, 20, 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, 4, 0\0" + VALUE "ProductVersion", "3, 2, 20, 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 bf425a3..b836532 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 @@ -113,7 +115,11 @@ #define CPU_CFG2_LSX (1 << 6) #define CPU_CFG2_LASX (1 << 7) -#if defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) && !defined(SDL_PLATFORM_MACOS) && !defined(SDL_PLATFORM_OPENBSD) && !defined(SDL_PLATFORM_FREEBSD) +#if !defined(SDL_CPUINFO_DISABLED) && \ + !((defined(SDL_PLATFORM_MACOS) && (defined(__ppc__) || defined(__ppc64__))) || (defined(SDL_PLATFORM_OPENBSD) && defined(__powerpc__))) && \ + !(defined(SDL_PLATFORM_FREEBSD) && defined(__powerpc__)) && \ + !(defined(SDL_PLATFORM_LINUX) && defined(__powerpc__) && defined(HAVE_GETAUXVAL)) && \ + defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) /* This is the brute force way of detecting instruction sets... the idea is borrowed from the libmpeg2 library - thanks! */ @@ -342,6 +348,8 @@ static int CPU_haveAltiVec(void) elf_aux_info(AT_HWCAP, &cpufeatures, sizeof(cpufeatures)); altivec = cpufeatures & PPC_FEATURE_HAS_ALTIVEC; return altivec; +#elif defined(SDL_PLATFORM_LINUX) && defined(__powerpc__) && defined(HAVE_GETAUXVAL) + altivec = getauxval(AT_HWCAP) & PPC_FEATURE_HAS_ALTIVEC; #elif defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) void (*handler)(int sig); handler = signal(SIGILL, illegal_instruction); @@ -642,6 +650,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 +1173,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/dialog/cocoa/SDL_cocoadialog.m b/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m index fb9c5ad..bc806f5 100644 --- a/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m +++ b/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m @@ -27,6 +27,37 @@ #import #import +extern void Cocoa_SetWindowHasModalDialog(SDL_Window *window, bool has_modal); + +static void AddFileExtensionType(NSMutableArray *types, const char *pattern_ptr) +{ + if (!*pattern_ptr) { + return; // in case the string had an extra ';' at the end. + } + + // -[UTType typeWithFilenameExtension] will return nil if there's a period in the string. It's better to + // allow too many files than not allow the one the user actually needs, so just take the part after the '.' + const char *dot = SDL_strrchr(pattern_ptr, '.'); + NSString *extstr = [NSString stringWithFormat: @"%s", dot ? (dot + 1) : pattern_ptr]; + if (@available(macOS 11.0, *)) { + UTType *uttype = [UTType typeWithFilenameExtension:extstr]; + if (uttype) { // still failed? Don't add the pattern. This is what the pre-macOS11 path does internally anyhow. + [types addObject:uttype]; + } + } else { + [types addObject:extstr]; + } +} + +static void ReactivateAfterDialog(void) +{ + for (NSRunningApplication *i in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) { + [i activateWithOptions:0]; + break; + } + [NSApp activateIgnoringOtherApps:YES]; +} + void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) { SDL_Window* window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); @@ -87,7 +118,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil if (filters) { // On macOS 11.0 and up, this is an array of UTType. Prior to that, it's an array of NSString - NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:nfilters ]; + NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:nfilters]; int has_all_files = 0; for (int i = 0; i < nfilters; i++) { @@ -102,21 +133,14 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil for (char *c = pattern; *c; c++) { if (*c == ';') { *c = '\0'; - if(@available(macOS 11.0, *)) { - [types addObject: [UTType typeWithFilenameExtension:[NSString stringWithFormat: @"%s", pattern_ptr]]]; - } else { - [types addObject: [NSString stringWithFormat: @"%s", pattern_ptr]]; - } + AddFileExtensionType(types, pattern_ptr); pattern_ptr = c + 1; } else if (*c == '*') { has_all_files = 1; } } - if(@available(macOS 11.0, *)) { - [types addObject: [UTType typeWithFilenameExtension:[NSString stringWithFormat: @"%s", pattern_ptr]]]; - } else { - [types addObject: [NSString stringWithFormat: @"%s", pattern_ptr]]; - } + + AddFileExtensionType(types, pattern_ptr); // get the last piece of the string. SDL_free(pattern); } @@ -141,6 +165,9 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil if (window) { w = (__bridge NSWindow *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL); + if (w) { + Cocoa_SetWindowHasModalDialog(window, true); + } } if (w) { @@ -163,6 +190,9 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil const char *files[1] = { NULL }; callback(userdata, files, -1); } + + Cocoa_SetWindowHasModalDialog(window, false); + ReactivateAfterDialog(); }]; } else { if ([dialog runModal] == NSModalResponseOK) { @@ -182,6 +212,8 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil const char *files[1] = { NULL }; callback(userdata, files, -1); } + + ReactivateAfterDialog(); } } diff --git a/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c b/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c index 2de224f..364753c 100644 --- a/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c +++ b/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c @@ -261,7 +261,7 @@ void windows_ShowFileDialog(void *ptr) chosen_files_list[nfiles] = NULL; - if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_folder, MAX_PATH, NULL, NULL) >= MAX_PATH) { + if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_folder, MAX_PATH, NULL, NULL) == 0) { SDL_SetError("Path too long or invalid character in path"); SDL_free(chosen_files_list); callback(userdata, NULL, -1); @@ -273,7 +273,7 @@ void windows_ShowFileDialog(void *ptr) SDL_strlcpy(chosen_file, chosen_folder, MAX_PATH); chosen_file[chosen_folder_size] = '\\'; - file_ptr += SDL_strlen(chosen_folder) + 1; + file_ptr += SDL_wcslen(file_ptr) + 1; while (*file_ptr) { nfiles++; @@ -295,7 +295,7 @@ void windows_ShowFileDialog(void *ptr) int diff = ((int) chosen_folder_size) + 1; - if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_file + diff, MAX_PATH - diff, NULL, NULL) >= MAX_PATH - diff) { + if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_file + diff, MAX_PATH - diff, NULL, NULL) == 0) { SDL_SetError("Path too long or invalid character in path"); for (size_t i = 0; i < nfiles - 1; i++) { @@ -308,7 +308,7 @@ void windows_ShowFileDialog(void *ptr) return; } - file_ptr += SDL_strlen(chosen_file) + 1 - diff; + file_ptr += SDL_wcslen(file_ptr) + 1; chosen_files_list[nfiles - 1] = SDL_strdup(chosen_file); @@ -516,6 +516,14 @@ static void ShowFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_ filters_str = win_get_filters(filters, nfilters); + DWORD flags = 0; + if (allow_many) { + flags |= OFN_ALLOWMULTISELECT; + } + if (is_save) { + flags |= OFN_OVERWRITEPROMPT; + } + if (!filters_str && filters) { callback(userdata, NULL, -1); SDL_free(args); @@ -526,7 +534,7 @@ static void ShowFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_ args->filters_str = filters_str; args->default_file = default_location ? SDL_strdup(default_location) : NULL; args->parent = window; - args->flags = allow_many ? OFN_ALLOWMULTISELECT : 0; + args->flags = flags; args->callback = callback; args->userdata = userdata; args->title = title ? SDL_strdup(title) : NULL; diff --git a/libs/SDL3/src/events/SDL_clipboardevents.c b/libs/SDL3/src/events/SDL_clipboardevents.c index 96bfd04..d5cf8ad 100644 --- a/libs/SDL3/src/events/SDL_clipboardevents.c +++ b/libs/SDL3/src/events/SDL_clipboardevents.c @@ -29,8 +29,17 @@ void SDL_SendClipboardUpdate(bool owner, char **mime_types, size_t num_mime_types) { if (!owner) { - // Clear our internal clipboard contents when external clipboard is set - SDL_CancelClipboardData(0); + /* Clear our internal clipboard contents when external clipboard is set. + * + * Wayland recursively sends a data offer to the client from which the clipboard data originated, + * and as the client can't determine the origin of the offer, the clipboard must not be cleared, + * or the original data may be destroyed. Cleanup will be done in the backend when an offer + * cancellation event arrives. + */ + if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") != 0) { + SDL_CancelClipboardData(0); + } + SDL_SaveClipboardMimeTypes((const char **)mime_types, num_mime_types); } diff --git a/libs/SDL3/src/events/SDL_events.c b/libs/SDL3/src/events/SDL_events.c index 7404234..a151740 100644 --- a/libs/SDL3/src/events/SDL_events.c +++ b/libs/SDL3/src/events/SDL_events.c @@ -23,6 +23,8 @@ // General event handling code for SDL #include "SDL_events_c.h" +#include "SDL_eventwatch_c.h" +#include "SDL_windowevents_c.h" #include "../SDL_hints_c.h" #include "../audio/SDL_audio_c.h" #include "../camera/SDL_camera_c.h" @@ -52,19 +54,53 @@ // Make sure the type in the SDL_Event aligns properly across the union SDL_COMPILE_TIME_ASSERT(SDL_Event_type, sizeof(Uint32) == sizeof(SDL_EventType)); -typedef struct SDL_EventWatcher -{ - SDL_EventFilter callback; - void *userdata; - bool removed; -} SDL_EventWatcher; +#define SDL2_SYSWMEVENT 0x201 -static SDL_Mutex *SDL_event_watchers_lock; -static SDL_EventWatcher SDL_EventOK; -static SDL_EventWatcher *SDL_event_watchers = NULL; -static int SDL_event_watchers_count = 0; -static bool SDL_event_watchers_dispatching = false; -static bool SDL_event_watchers_removed = false; +#ifdef SDL_VIDEO_DRIVER_WINDOWS +#include "../core/windows/SDL_windows.h" +#endif + +#ifdef SDL_VIDEO_DRIVER_X11 +#include +#endif + +typedef struct SDL2_version +{ + Uint8 major; + Uint8 minor; + Uint8 patch; +} SDL2_version; + +typedef enum +{ + SDL2_SYSWM_UNKNOWN +} SDL2_SYSWM_TYPE; + +typedef struct SDL2_SysWMmsg +{ + SDL2_version version; + SDL2_SYSWM_TYPE subsystem; + union + { +#ifdef SDL_VIDEO_DRIVER_WINDOWS + struct { + HWND hwnd; /**< The window for the message */ + UINT msg; /**< The type of message */ + WPARAM wParam; /**< WORD message parameter */ + LPARAM lParam; /**< LONG message parameter */ + } win; +#endif +#ifdef SDL_VIDEO_DRIVER_X11 + struct { + XEvent event; + } x11; +#endif + /* Can't have an empty union */ + int dummy; + } msg; +} SDL2_SysWMmsg; + +static SDL_EventWatchList SDL_event_watchers; static SDL_AtomicInt SDL_sentinel_pending; static Uint32 SDL_last_event_id = 0; @@ -74,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 { @@ -214,6 +250,17 @@ static void SDL_LinkTemporaryMemoryToEvent(SDL_EventEntry *event, const void *me } } +static void SDL_TransferSysWMMemoryToEvent(SDL_EventEntry *event) +{ + SDL2_SysWMmsg **wmmsg = (SDL2_SysWMmsg **)((&event->event.common)+1); + SDL2_SysWMmsg *mem = SDL_AllocateTemporaryMemory(sizeof(*mem)); + if (mem) { + SDL_copyp(mem, *wmmsg); + *wmmsg = mem; + SDL_LinkTemporaryMemoryToEvent(event, mem); + } +} + // Transfer the event memory from the thread-local event memory list to the event static void SDL_TransferTemporaryMemoryToEvent(SDL_EventEntry *event) { @@ -238,6 +285,10 @@ static void SDL_TransferTemporaryMemoryToEvent(SDL_EventEntry *event) case SDL_EVENT_CLIPBOARD_UPDATE: SDL_LinkTemporaryMemoryToEvent(event, event->event.clipboard.mime_types); break; + case SDL2_SYSWMEVENT: + // We need to copy the stack pointer into temporary memory + SDL_TransferSysWMMemoryToEvent(event); + break; default: break; } @@ -582,9 +633,10 @@ static void SDL_LogEvent(const SDL_Event *event) #undef PRINT_MBUTTON_EVENT SDL_EVENT_CASE(SDL_EVENT_MOUSE_WHEEL) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u x=%g y=%g direction=%s)", + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u x=%g y=%g integer_x=%d integer_y=%d direction=%s)", (uint)event->wheel.timestamp, (uint)event->wheel.windowID, (uint)event->wheel.which, event->wheel.x, event->wheel.y, + (int)event->wheel.integer_x, (int)event->wheel.integer_y, event->wheel.direction == SDL_MOUSEWHEEL_NORMAL ? "normal" : "flipped"); break; @@ -877,16 +929,8 @@ void SDL_StopEventLoop(void) SDL_disabled_events[i] = NULL; } - if (SDL_event_watchers_lock) { - SDL_DestroyMutex(SDL_event_watchers_lock); - SDL_event_watchers_lock = NULL; - } - if (SDL_event_watchers) { - SDL_free(SDL_event_watchers); - SDL_event_watchers = NULL; - SDL_event_watchers_count = 0; - } - SDL_zero(SDL_EventOK); + SDL_QuitEventWatchList(&SDL_event_watchers); + SDL_QuitWindowEventWatch(); SDL_Mutex *lock = NULL; if (SDL_EventQ.lock) { @@ -920,17 +964,19 @@ bool SDL_StartEventLoop(void) } SDL_LockMutex(SDL_EventQ.lock); - if (SDL_event_watchers_lock == NULL) { - SDL_event_watchers_lock = SDL_CreateMutex(); - if (SDL_event_watchers_lock == NULL) { - SDL_UnlockMutex(SDL_EventQ.lock); - return false; - } + if (!SDL_InitEventWatchList(&SDL_event_watchers)) { + SDL_UnlockMutex(SDL_EventQ.lock); + return false; } #endif // !SDL_THREADS_DISABLED + SDL_InitWindowEventWatch(); + SDL_EventQ.active = true; + +#ifndef SDL_THREADS_DISABLED SDL_UnlockMutex(SDL_EventQ.lock); +#endif return true; } @@ -1031,16 +1077,11 @@ static void SDL_SendWakeupEvent(void) return; } - SDL_LockMutex(_this->wakeup_lock); - { - if (_this->wakeup_window) { - _this->SendWakeupEvent(_this, _this->wakeup_window); - - // No more wakeup events needed until we enter a new wait - _this->wakeup_window = NULL; - } + // We only want to do this once while waiting for an event, so set it to NULL atomically here + SDL_Window *wakeup_window = (SDL_Window *)SDL_SetAtomicPointer(&_this->wakeup_window, NULL); + if (wakeup_window) { + _this->SendWakeupEvent(_this, wakeup_window); } - SDL_UnlockMutex(_this->wakeup_lock); #endif } @@ -1326,14 +1367,15 @@ bool SDL_RunOnMainThread(SDL_MainThreadCallback callback, void *userdata, bool w } SDL_UnlockMutex(SDL_main_callbacks_lock); + // If the main thread is waiting for events, wake it up + SDL_SendWakeupEvent(); + if (!wait_complete) { // Queued for execution, wait not requested return true; } - // Maximum wait of 30 seconds to prevent deadlocking forever - const Sint32 MAX_CALLBACK_WAIT = 30 * 1000; - SDL_WaitSemaphoreTimeout(entry->semaphore, MAX_CALLBACK_WAIT); + SDL_WaitSemaphore(entry->semaphore); switch (SDL_GetAtomicInt(&entry->state)) { case SDL_MAIN_CALLBACK_COMPLETE: @@ -1477,18 +1519,7 @@ static int SDL_WaitEventTimeout_Device(SDL_VideoDevice *_this, SDL_Window *wakeu */ SDL_PumpEventsInternal(true); - SDL_LockMutex(_this->wakeup_lock); - { - status = SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST); - // If status == 0 we are going to block so wakeup will be needed. - if (status == 0) { - _this->wakeup_window = wakeup_window; - } else { - _this->wakeup_window = NULL; - } - } - SDL_UnlockMutex(_this->wakeup_lock); - + status = SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST); if (status < 0) { // Got an error: return break; @@ -1501,8 +1532,6 @@ static int SDL_WaitEventTimeout_Device(SDL_VideoDevice *_this, SDL_Window *wakeu if (timeoutNS > 0) { Sint64 elapsed = SDL_GetTicksNS() - start; if (elapsed >= timeoutNS) { - // Set wakeup_window to NULL without holding the lock. - _this->wakeup_window = NULL; return 0; } loop_timeoutNS = (timeoutNS - elapsed); @@ -1515,9 +1544,9 @@ static int SDL_WaitEventTimeout_Device(SDL_VideoDevice *_this, SDL_Window *wakeu loop_timeoutNS = poll_intervalNS; } } + SDL_SetAtomicPointer(&_this->wakeup_window, wakeup_window); status = _this->WaitEventTimeout(_this, loop_timeoutNS); - // Set wakeup_window to NULL without holding the lock. - _this->wakeup_window = NULL; + SDL_SetAtomicPointer(&_this->wakeup_window, NULL); if (status == 0 && poll_intervalNS != SDL_MAX_SINT64 && loop_timeoutNS == poll_intervalNS) { // We may have woken up to poll. Try again continue; @@ -1674,44 +1703,11 @@ bool SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS) static bool SDL_CallEventWatchers(SDL_Event *event) { - if ((SDL_EventOK.callback || SDL_event_watchers_count > 0) && - (event->common.type != SDL_EVENT_POLL_SENTINEL)) { - SDL_LockMutex(SDL_event_watchers_lock); - { - if (SDL_EventOK.callback && !SDL_EventOK.callback(SDL_EventOK.userdata, event)) { - SDL_UnlockMutex(SDL_event_watchers_lock); - return false; - } - - if (SDL_event_watchers_count > 0) { - // Make sure we only dispatch the current watcher list - int i, event_watchers_count = SDL_event_watchers_count; - - SDL_event_watchers_dispatching = true; - for (i = 0; i < event_watchers_count; ++i) { - if (!SDL_event_watchers[i].removed) { - SDL_event_watchers[i].callback(SDL_event_watchers[i].userdata, event); - } - } - SDL_event_watchers_dispatching = false; - - if (SDL_event_watchers_removed) { - for (i = SDL_event_watchers_count; i--;) { - if (SDL_event_watchers[i].removed) { - --SDL_event_watchers_count; - if (i < SDL_event_watchers_count) { - SDL_memmove(&SDL_event_watchers[i], &SDL_event_watchers[i + 1], (SDL_event_watchers_count - i) * sizeof(SDL_event_watchers[i])); - } - } - } - SDL_event_watchers_removed = false; - } - } - } - SDL_UnlockMutex(SDL_event_watchers_lock); + if (event->common.type == SDL_EVENT_POLL_SENTINEL) { + return true; } - return true; + return SDL_DispatchEventWatchList(&SDL_event_watchers, event); } bool SDL_PushEvent(SDL_Event *event) @@ -1735,11 +1731,11 @@ bool SDL_PushEvent(SDL_Event *event) void SDL_SetEventFilter(SDL_EventFilter filter, void *userdata) { SDL_EventEntry *event, *next; - SDL_LockMutex(SDL_event_watchers_lock); + SDL_LockMutex(SDL_event_watchers.lock); { // Set filter and discard pending events - SDL_EventOK.callback = filter; - SDL_EventOK.userdata = userdata; + SDL_event_watchers.filter.callback = filter; + SDL_event_watchers.filter.userdata = userdata; if (filter) { // Cut all events not accepted by the filter SDL_LockMutex(SDL_EventQ.lock); @@ -1754,18 +1750,18 @@ void SDL_SetEventFilter(SDL_EventFilter filter, void *userdata) SDL_UnlockMutex(SDL_EventQ.lock); } } - SDL_UnlockMutex(SDL_event_watchers_lock); + SDL_UnlockMutex(SDL_event_watchers.lock); } bool SDL_GetEventFilter(SDL_EventFilter *filter, void **userdata) { SDL_EventWatcher event_ok; - SDL_LockMutex(SDL_event_watchers_lock); + SDL_LockMutex(SDL_event_watchers.lock); { - event_ok = SDL_EventOK; + event_ok = SDL_event_watchers.filter; } - SDL_UnlockMutex(SDL_event_watchers_lock); + SDL_UnlockMutex(SDL_event_watchers.lock); if (filter) { *filter = event_ok.callback; @@ -1778,53 +1774,12 @@ bool SDL_GetEventFilter(SDL_EventFilter *filter, void **userdata) bool SDL_AddEventWatch(SDL_EventFilter filter, void *userdata) { - bool result = true; - - SDL_LockMutex(SDL_event_watchers_lock); - { - SDL_EventWatcher *event_watchers; - - event_watchers = (SDL_EventWatcher *)SDL_realloc(SDL_event_watchers, (SDL_event_watchers_count + 1) * sizeof(*event_watchers)); - if (event_watchers) { - SDL_EventWatcher *watcher; - - SDL_event_watchers = event_watchers; - watcher = &SDL_event_watchers[SDL_event_watchers_count]; - watcher->callback = filter; - watcher->userdata = userdata; - watcher->removed = false; - ++SDL_event_watchers_count; - } else { - result = false; - } - } - SDL_UnlockMutex(SDL_event_watchers_lock); - - return result; + return SDL_AddEventWatchList(&SDL_event_watchers, filter, userdata); } void SDL_RemoveEventWatch(SDL_EventFilter filter, void *userdata) { - SDL_LockMutex(SDL_event_watchers_lock); - { - int i; - - for (i = 0; i < SDL_event_watchers_count; ++i) { - if (SDL_event_watchers[i].callback == filter && SDL_event_watchers[i].userdata == userdata) { - if (SDL_event_watchers_dispatching) { - SDL_event_watchers[i].removed = true; - SDL_event_watchers_removed = true; - } else { - --SDL_event_watchers_count; - if (i < SDL_event_watchers_count) { - SDL_memmove(&SDL_event_watchers[i], &SDL_event_watchers[i + 1], (SDL_event_watchers_count - i) * sizeof(SDL_event_watchers[i])); - } - } - break; - } - } - } - SDL_UnlockMutex(SDL_event_watchers_lock); + SDL_RemoveEventWatchList(&SDL_event_watchers, filter, userdata); } void SDL_FilterEvents(SDL_EventFilter filter, void *userdata) @@ -1849,7 +1804,7 @@ void SDL_SetEventEnabled(Uint32 type, bool enabled) Uint8 lo = (type & 0xff); if (SDL_disabled_events[hi] && - (SDL_disabled_events[hi]->bits[lo / 32] & (1 << (lo & 31)))) { + (SDL_disabled_events[hi]->bits[lo / 32] & (1U << (lo & 31)))) { current_state = false; } else { current_state = true; @@ -1858,7 +1813,7 @@ void SDL_SetEventEnabled(Uint32 type, bool enabled) if ((enabled != false) != current_state) { if (enabled) { SDL_assert(SDL_disabled_events[hi] != NULL); - SDL_disabled_events[hi]->bits[lo / 32] &= ~(1 << (lo & 31)); + SDL_disabled_events[hi]->bits[lo / 32] &= ~(1U << (lo & 31)); // Gamepad events depend on joystick events switch (type) { @@ -1889,7 +1844,7 @@ void SDL_SetEventEnabled(Uint32 type, bool enabled) } // Out of memory, nothing we can do... if (SDL_disabled_events[hi]) { - SDL_disabled_events[hi]->bits[lo / 32] |= (1 << (lo & 31)); + SDL_disabled_events[hi]->bits[lo / 32] |= (1U << (lo & 31)); SDL_FlushEvent(type); } } @@ -1908,7 +1863,7 @@ bool SDL_EventEnabled(Uint32 type) Uint8 lo = (type & 0xff); if (SDL_disabled_events[hi] && - (SDL_disabled_events[hi]->bits[lo / 32] & (1 << (lo & 31)))) { + (SDL_disabled_events[hi]->bits[lo / 32] & (1U << (lo & 31)))) { return false; } else { return true; @@ -1919,9 +1874,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_eventwatch.c b/libs/SDL3/src/events/SDL_eventwatch.c new file mode 100644 index 0000000..08e7248 --- /dev/null +++ b/libs/SDL3/src/events/SDL_eventwatch.c @@ -0,0 +1,143 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include "SDL_eventwatch_c.h" + + +bool SDL_InitEventWatchList(SDL_EventWatchList *list) +{ + if (list->lock == NULL) { + list->lock = SDL_CreateMutex(); + if (list->lock == NULL) { + return false; + } + } + return true; +} + +void SDL_QuitEventWatchList(SDL_EventWatchList *list) +{ + if (list->lock) { + SDL_DestroyMutex(list->lock); + list->lock = NULL; + } + if (list->watchers) { + SDL_free(list->watchers); + list->watchers = NULL; + list->count = 0; + } + SDL_zero(list->filter); +} + +bool SDL_DispatchEventWatchList(SDL_EventWatchList *list, SDL_Event *event) +{ + SDL_EventWatcher *filter = &list->filter; + + if (!filter->callback && list->count == 0) { + return true; + } + + SDL_LockMutex(list->lock); + { + // Make sure we only dispatch the current watcher list + int i, count = list->count; + + if (filter->callback && !filter->callback(filter->userdata, event)) { + SDL_UnlockMutex(list->lock); + return false; + } + + list->dispatching = true; + for (i = 0; i < count; ++i) { + if (!list->watchers[i].removed) { + list->watchers[i].callback(list->watchers[i].userdata, event); + } + } + list->dispatching = false; + + if (list->removed) { + for (i = list->count; i--;) { + if (list->watchers[i].removed) { + --list->count; + if (i < list->count) { + SDL_memmove(&list->watchers[i], &list->watchers[i + 1], (list->count - i) * sizeof(list->watchers[i])); + } + } + } + list->removed = false; + } + } + SDL_UnlockMutex(list->lock); + + return true; +} + +bool SDL_AddEventWatchList(SDL_EventWatchList *list, SDL_EventFilter filter, void *userdata) +{ + bool result = true; + + SDL_LockMutex(list->lock); + { + SDL_EventWatcher *watchers; + + watchers = (SDL_EventWatcher *)SDL_realloc(list->watchers, (list->count + 1) * sizeof(*watchers)); + if (watchers) { + SDL_EventWatcher *watcher; + + list->watchers = watchers; + watcher = &list->watchers[list->count]; + watcher->callback = filter; + watcher->userdata = userdata; + watcher->removed = false; + ++list->count; + } else { + result = false; + } + } + SDL_UnlockMutex(list->lock); + + return result; +} + +void SDL_RemoveEventWatchList(SDL_EventWatchList *list, SDL_EventFilter filter, void *userdata) +{ + SDL_LockMutex(list->lock); + { + int i; + + for (i = 0; i < list->count; ++i) { + if (list->watchers[i].callback == filter && list->watchers[i].userdata == userdata) { + if (list->dispatching) { + list->watchers[i].removed = true; + list->removed = true; + } else { + --list->count; + if (i < list->count) { + SDL_memmove(&list->watchers[i], &list->watchers[i + 1], (list->count - i) * sizeof(list->watchers[i])); + } + } + break; + } + } + } + SDL_UnlockMutex(list->lock); +} diff --git a/libs/SDL3/src/events/SDL_eventwatch_c.h b/libs/SDL3/src/events/SDL_eventwatch_c.h new file mode 100644 index 0000000..c9aea38 --- /dev/null +++ b/libs/SDL3/src/events/SDL_eventwatch_c.h @@ -0,0 +1,45 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +typedef struct SDL_EventWatcher +{ + SDL_EventFilter callback; + void *userdata; + bool removed; +} SDL_EventWatcher; + +typedef struct SDL_EventWatchList +{ + SDL_Mutex *lock; + SDL_EventWatcher filter; + SDL_EventWatcher *watchers; + int count; + bool dispatching; + bool removed; +} SDL_EventWatchList; + + +extern bool SDL_InitEventWatchList(SDL_EventWatchList *list); +extern void SDL_QuitEventWatchList(SDL_EventWatchList *list); +extern bool SDL_DispatchEventWatchList(SDL_EventWatchList *list, SDL_Event *event); +extern bool SDL_AddEventWatchList(SDL_EventWatchList *list, SDL_EventFilter filter, void *userdata); +extern void SDL_RemoveEventWatchList(SDL_EventWatchList *list, SDL_EventFilter filter, void *userdata); diff --git a/libs/SDL3/src/events/SDL_keyboard.c b/libs/SDL3/src/events/SDL_keyboard.c index 066c6e6..7f1ac7a 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_keymap.c b/libs/SDL3/src/events/SDL_keymap.c index bc21c33..bd08786 100644 --- a/libs/SDL3/src/events/SDL_keymap.c +++ b/libs/SDL3/src/events/SDL_keymap.c @@ -39,8 +39,8 @@ SDL_Keymap *SDL_CreateKeymap(void) return NULL; } - keymap->scancode_to_keycode = SDL_CreateHashTable(NULL, 64, SDL_HashID, SDL_KeyMatchID, NULL, false, false); - keymap->keycode_to_scancode = SDL_CreateHashTable(NULL, 64, SDL_HashID, SDL_KeyMatchID, NULL, false, false); + keymap->scancode_to_keycode = SDL_CreateHashTable(256, false, SDL_HashID, SDL_KeyMatchID, NULL, NULL); + keymap->keycode_to_scancode = SDL_CreateHashTable(256, false, SDL_HashID, SDL_KeyMatchID, NULL, NULL); if (!keymap->scancode_to_keycode || !keymap->keycode_to_scancode) { SDL_DestroyKeymap(keymap); return NULL; @@ -76,31 +76,27 @@ void SDL_SetKeymapEntry(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod mo Uint32 key = ((Uint32)modstate << 16) | scancode; const void *value; if (SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { - SDL_Keycode existing_keycode = (SDL_Keycode)(uintptr_t)value; + const SDL_Keycode existing_keycode = (SDL_Keycode)(uintptr_t)value; if (existing_keycode == keycode) { // We already have this mapping return; } - - // Changing the mapping, need to remove the existing entry from the keymap - SDL_RemoveFromHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key); + // InsertIntoHashTable will replace the existing entry in the keymap atomically. } - SDL_InsertIntoHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, (void *)(uintptr_t)keycode); + SDL_InsertIntoHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, (void *)(uintptr_t)keycode, true); bool update_keycode = true; if (SDL_FindInHashTable(keymap->keycode_to_scancode, (void *)(uintptr_t)keycode, &value)) { - Uint32 existing_value = (Uint32)(uintptr_t)value; - SDL_Keymod existing_modstate = (SDL_Keymod)(existing_value >> 16); + const Uint32 existing_value = (Uint32)(uintptr_t)value; + const SDL_Keymod existing_modstate = (SDL_Keymod)(existing_value >> 16); // Keep the simplest combination of scancode and modifiers to generate this keycode if (existing_modstate <= modstate) { update_keycode = false; - } else { - SDL_RemoveFromHashTable(keymap->keycode_to_scancode, (void *)(uintptr_t)keycode); } } if (update_keycode) { - SDL_InsertIntoHashTable(keymap->keycode_to_scancode, (void *)(uintptr_t)keycode, (void *)(uintptr_t)key); + SDL_InsertIntoHashTable(keymap->keycode_to_scancode, (void *)(uintptr_t)keycode, (void *)(uintptr_t)key, true); } } @@ -108,7 +104,7 @@ SDL_Keycode SDL_GetKeymapKeycode(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_ { SDL_Keycode keycode; - Uint32 key = ((Uint32)NormalizeModifierStateForKeymap(modstate) << 16) | scancode; + const Uint32 key = ((Uint32)NormalizeModifierStateForKeymap(modstate) << 16) | scancode; const void *value; if (keymap && SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { keycode = (SDL_Keycode)(uintptr_t)value; @@ -777,7 +773,7 @@ static const char *SDL_scancode_names[SDL_SCANCODE_COUNT] = /* 97 */ "Keypad 9", /* 98 */ "Keypad 0", /* 99 */ "Keypad .", - /* 100 */ NULL, + /* 100 */ "NonUSBackslash", /* 101 */ "Application", /* 102 */ "Power", /* 103 */ "Keypad =", @@ -812,24 +808,24 @@ static const char *SDL_scancode_names[SDL_SCANCODE_COUNT] = /* 132 */ NULL, /* 133 */ "Keypad ,", /* 134 */ "Keypad = (AS400)", - /* 135 */ NULL, - /* 136 */ NULL, - /* 137 */ NULL, - /* 138 */ NULL, - /* 139 */ NULL, - /* 140 */ NULL, - /* 141 */ NULL, - /* 142 */ NULL, - /* 143 */ NULL, - /* 144 */ NULL, - /* 145 */ NULL, - /* 146 */ NULL, - /* 147 */ NULL, - /* 148 */ NULL, - /* 149 */ NULL, - /* 150 */ NULL, - /* 151 */ NULL, - /* 152 */ NULL, + /* 135 */ "International 1", + /* 136 */ "International 2", + /* 137 */ "International 3", + /* 138 */ "International 4", + /* 139 */ "International 5", + /* 140 */ "International 6", + /* 141 */ "International 7", + /* 142 */ "International 8", + /* 143 */ "International 9", + /* 144 */ "Language 1", + /* 145 */ "Language 2", + /* 146 */ "Language 3", + /* 147 */ "Language 4", + /* 148 */ "Language 5", + /* 149 */ "Language 6", + /* 150 */ "Language 7", + /* 151 */ "Language 8", + /* 152 */ "Language 9", /* 153 */ "AltErase", /* 154 */ "SysReq", /* 155 */ "Cancel", diff --git a/libs/SDL3/src/events/SDL_mouse.c b/libs/SDL3/src/events/SDL_mouse.c index 42933af..92f9913 100644 --- a/libs/SDL3/src/events/SDL_mouse.c +++ b/libs/SDL3/src/events/SDL_mouse.c @@ -138,21 +138,25 @@ static void SDLCALL SDL_TouchMouseEventsChanged(void *userdata, const char *name #ifdef SDL_PLATFORM_VITA static void SDLCALL SDL_VitaTouchMouseDeviceChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { + Uint8 vita_touch_mouse_device = 1; + SDL_Mouse *mouse = (SDL_Mouse *)userdata; if (hint) { switch (*hint) { - default: case '0': - mouse->vita_touch_mouse_device = 1; + vita_touch_mouse_device = 1; break; case '1': - mouse->vita_touch_mouse_device = 2; + vita_touch_mouse_device = 2; break; case '2': - mouse->vita_touch_mouse_device = 3; + vita_touch_mouse_device = 3; + break; + default: break; } } + mouse->vita_touch_mouse_device = vita_touch_mouse_device; } #endif @@ -234,6 +238,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 +303,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; @@ -484,7 +502,7 @@ SDL_SystemCursor SDL_GetDefaultSystemCursor(void) const char *value = SDL_GetHint(SDL_HINT_MOUSE_DEFAULT_SYSTEM_CURSOR); if (value) { int index = SDL_atoi(value); - if (0 <= index && index < (int)SDL_SYSTEM_CURSOR_COUNT) { + if (0 <= index && index < SDL_SYSTEM_CURSOR_COUNT) { id = (SDL_SystemCursor)index; } } @@ -631,7 +649,7 @@ void SDL_SendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouse { if (window && !relative) { SDL_Mouse *mouse = SDL_GetMouse(); - if (!SDL_UpdateMouseFocus(window, x, y, SDL_GetMouseButtonState(mouse, mouseID, true), (mouseID != SDL_TOUCH_MOUSEID))) { + if (!SDL_UpdateMouseFocus(window, x, y, SDL_GetMouseButtonState(mouse, mouseID, true), (mouseID != SDL_TOUCH_MOUSEID && mouseID != SDL_PEN_MOUSEID))) { return; } } @@ -689,7 +707,7 @@ static void SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL // SDL_HINT_MOUSE_TOUCH_EVENTS: controlling whether mouse events should generate synthetic touch events if (mouse->mouse_touch_events) { - if (mouseID != SDL_TOUCH_MOUSEID && !relative && track_mouse_down) { + if (mouseID != SDL_TOUCH_MOUSEID && mouseID != SDL_PEN_MOUSEID && !relative && track_mouse_down) { if (window) { float normalized_x = x / (float)window->w; float normalized_y = y / (float)window->h; @@ -720,12 +738,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; @@ -748,10 +776,8 @@ static void SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL // modify internal state { - if (relative) { - mouse->x_accu += xrel; - mouse->y_accu += yrel; - } + mouse->x_accu += xrel; + mouse->y_accu += yrel; if (relative && mouse->has_position) { mouse->x += xrel; @@ -779,7 +805,7 @@ static void SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL // Post the event, if desired if (SDL_EventEnabled(SDL_EVENT_MOUSE_MOTION)) { - if ((!mouse->relative_mode || mouse->warp_emulation_active) && mouseID != SDL_TOUCH_MOUSEID) { + if ((!mouse->relative_mode || mouse->warp_emulation_active) && mouseID != SDL_TOUCH_MOUSEID && mouseID != SDL_PEN_MOUSEID) { // We're not in relative mode, so all mouse events are global mouse events mouseID = SDL_GLOBAL_MOUSE_ID; } @@ -882,7 +908,7 @@ static void SDL_PrivateSendMouseButton(Uint64 timestamp, SDL_Window *window, SDL // SDL_HINT_MOUSE_TOUCH_EVENTS: controlling whether mouse events should generate synthetic touch events if (mouse->mouse_touch_events) { - if (mouseID != SDL_TOUCH_MOUSEID && button == SDL_BUTTON_LEFT) { + if (mouseID != SDL_TOUCH_MOUSEID && mouseID != SDL_PEN_MOUSEID && button == SDL_BUTTON_LEFT) { if (down) { track_mouse_down = true; } else { @@ -950,7 +976,7 @@ static void SDL_PrivateSendMouseButton(Uint64 timestamp, SDL_Window *window, SDL // Post the event, if desired if (SDL_EventEnabled(type)) { - if ((!mouse->relative_mode || mouse->warp_emulation_active) && mouseID != SDL_TOUCH_MOUSEID) { + if ((!mouse->relative_mode || mouse->warp_emulation_active) && mouseID != SDL_TOUCH_MOUSEID && mouseID != SDL_PEN_MOUSEID) { // We're not in relative mode, so all mouse events are global mouse events mouseID = SDL_GLOBAL_MOUSE_ID; } else { @@ -1006,6 +1032,8 @@ void SDL_SendMouseWheel(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseI // Post the event, if desired if (SDL_EventEnabled(SDL_EVENT_MOUSE_WHEEL)) { + float integer_x, integer_y; + if (!mouse->relative_mode || mouse->warp_emulation_active) { // We're not in relative mode, so all mouse events are global mouse events mouseID = SDL_GLOBAL_MOUSE_ID; @@ -1016,11 +1044,26 @@ void SDL_SendMouseWheel(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseI event.common.timestamp = timestamp; event.wheel.windowID = mouse->focus ? mouse->focus->id : 0; event.wheel.which = mouseID; - event.wheel.x = x; - event.wheel.y = y; event.wheel.direction = direction; event.wheel.mouse_x = mouse->x; event.wheel.mouse_y = mouse->y; + + mouse->residual_scroll_x = SDL_modff(mouse->residual_scroll_x + x, &integer_x); + event.wheel.integer_x = (Sint32)integer_x; + + mouse->residual_scroll_y = SDL_modff(mouse->residual_scroll_y + y, &integer_y); + event.wheel.integer_y = (Sint32)integer_y; + + // Return the accumulated values in x/y when integer wheel mode is enabled. + // This is necessary for compatibility with sdl2-compat 2.32.54. + if (mouse->integer_mode_flags & 2) { + event.wheel.x = integer_x; + event.wheel.y = integer_y; + } else { + event.wheel.x = x; + event.wheel.y = y; + } + SDL_PushEvent(&event); } } @@ -1110,6 +1153,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); } @@ -1483,6 +1529,11 @@ SDL_Cursor *SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y) return NULL; } + // Allow specifying the hot spot via properties on the surface + SDL_PropertiesID props = SDL_GetSurfaceProperties(surface); + hot_x = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, hot_x); + hot_y = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, hot_y); + // Sanity check the hot spot if ((hot_x < 0) || (hot_y < 0) || (hot_x >= surface->w) || (hot_y >= surface->h)) { diff --git a/libs/SDL3/src/events/SDL_mouse_c.h b/libs/SDL3/src/events/SDL_mouse_c.h index 43cc520..5927b8b 100644 --- a/libs/SDL3/src/events/SDL_mouse_c.h +++ b/libs/SDL3/src/events/SDL_mouse_c.h @@ -91,6 +91,11 @@ 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; + // Data common to all mice SDL_Window *focus; float x; @@ -98,6 +103,8 @@ typedef struct float x_accu; float y_accu; float last_x, last_y; // the last reported x and y coordinates + float residual_scroll_x; + float residual_scroll_y; double click_motion_x; double click_motion_y; bool has_position; diff --git a/libs/SDL3/src/events/SDL_pen.c b/libs/SDL3/src/events/SDL_pen.c index 71ddd4c..cd3730c 100644 --- a/libs/SDL3/src/events/SDL_pen.c +++ b/libs/SDL3/src/events/SDL_pen.c @@ -346,7 +346,7 @@ void SDL_SendPenTouch(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *windo if (eraser && ((input_state & SDL_PEN_INPUT_ERASER_TIP) == 0)) { input_state |= SDL_PEN_INPUT_ERASER_TIP; send_event = true; - } else if (!down && (input_state & SDL_PEN_INPUT_ERASER_TIP)) { + } else if (!eraser && (input_state & SDL_PEN_INPUT_ERASER_TIP)) { input_state &= ~SDL_PEN_INPUT_ERASER_TIP; send_event = true; } @@ -565,10 +565,19 @@ void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *wind event.pbutton.down = down; SDL_PushEvent(&event); - if (window && (pen_touching == instance_id)) { + if (window && (!pen_touching || (pen_touching == instance_id))) { SDL_Mouse *mouse = SDL_GetMouse(); if (mouse && mouse->pen_mouse_events) { - SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, button + 1, down); + static const Uint8 mouse_buttons[] = { + SDL_BUTTON_LEFT, + SDL_BUTTON_RIGHT, + SDL_BUTTON_MIDDLE, + SDL_BUTTON_X1, + SDL_BUTTON_X2 + }; + if (button < SDL_arraysize(mouse_buttons)) { + SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, mouse_buttons[button], down); + } } } } diff --git a/libs/SDL3/src/events/SDL_windowevents.c b/libs/SDL3/src/events/SDL_windowevents.c index e0364f8..e20cd3a 100644 --- a/libs/SDL3/src/events/SDL_windowevents.c +++ b/libs/SDL3/src/events/SDL_windowevents.c @@ -23,10 +23,39 @@ // Window event handling code for SDL #include "SDL_events_c.h" +#include "SDL_eventwatch_c.h" #include "SDL_mouse_c.h" -#include "../render/SDL_sysrender.h" #include "../tray/SDL_tray_utils.h" + +#define NUM_WINDOW_EVENT_WATCH_PRIORITIES (SDL_WINDOW_EVENT_WATCH_NORMAL + 1) + +static SDL_EventWatchList SDL_window_event_watchers[NUM_WINDOW_EVENT_WATCH_PRIORITIES]; + +void SDL_InitWindowEventWatch(void) +{ + for (int i = 0; i < SDL_arraysize(SDL_window_event_watchers); ++i) { + SDL_InitEventWatchList(&SDL_window_event_watchers[i]); + } +} + +void SDL_QuitWindowEventWatch(void) +{ + for (int i = 0; i < SDL_arraysize(SDL_window_event_watchers); ++i) { + SDL_QuitEventWatchList(&SDL_window_event_watchers[i]); + } +} + +void SDL_AddWindowEventWatch(SDL_WindowEventWatchPriority priority, SDL_EventFilter filter, void *userdata) +{ + SDL_AddEventWatchList(&SDL_window_event_watchers[priority], filter, userdata); +} + +void SDL_RemoveWindowEventWatch(SDL_WindowEventWatchPriority priority, SDL_EventFilter filter, void *userdata) +{ + SDL_RemoveEventWatchList(&SDL_window_event_watchers[priority], filter, userdata); +} + static bool SDLCALL RemoveSupercededWindowEvents(void *userdata, SDL_Event *event) { SDL_Event *new_event = (SDL_Event *)userdata; @@ -191,10 +220,8 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data event.window.data2 = data2; event.window.windowID = window->id; - for (int i = 0; i < window->num_renderers; ++i) { - SDL_Renderer *renderer = window->renderers[i]; - SDL_RendererEventWatch(renderer, &event); - } + SDL_DispatchEventWatchList(&SDL_window_event_watchers[SDL_WINDOW_EVENT_WATCH_EARLY], &event); + SDL_DispatchEventWatchList(&SDL_window_event_watchers[SDL_WINDOW_EVENT_WATCH_NORMAL], &event); if (SDL_EventEnabled(windowevent)) { // Fixes queue overflow with move/resize events that aren't processed diff --git a/libs/SDL3/src/events/SDL_windowevents_c.h b/libs/SDL3/src/events/SDL_windowevents_c.h index b7f0681..7204305 100644 --- a/libs/SDL3/src/events/SDL_windowevents_c.h +++ b/libs/SDL3/src/events/SDL_windowevents_c.h @@ -23,6 +23,17 @@ #ifndef SDL_windowevents_c_h_ #define SDL_windowevents_c_h_ +typedef enum +{ + SDL_WINDOW_EVENT_WATCH_EARLY, + SDL_WINDOW_EVENT_WATCH_NORMAL +} SDL_WindowEventWatchPriority; + +extern void SDL_InitWindowEventWatch(void); +extern void SDL_QuitWindowEventWatch(void); +extern void SDL_AddWindowEventWatch(SDL_WindowEventWatchPriority priority, SDL_EventFilter filter, void *userdata); +extern void SDL_RemoveWindowEventWatch(SDL_WindowEventWatchPriority priority, SDL_EventFilter filter, void *userdata); + extern bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data1, int data2); #endif // SDL_windowevents_c_h_ diff --git a/libs/SDL3/src/events/scancodes_linux.h b/libs/SDL3/src/events/scancodes_linux.h index fc26e49..2deeb9a 100644 --- a/libs/SDL3/src/events/scancodes_linux.h +++ b/libs/SDL3/src/events/scancodes_linux.h @@ -169,7 +169,7 @@ static SDL_Scancode const linux_scancode_table[] = { /* 140, 0x08c */ SDL_SCANCODE_UNKNOWN, // KEY_CALC /* 141, 0x08d */ SDL_SCANCODE_UNKNOWN, // KEY_SETUP /* 142, 0x08e */ SDL_SCANCODE_SLEEP, // KEY_SLEEP - /* 143, 0x08f */ SDL_SCANCODE_UNKNOWN, // KEY_WAKEUP + /* 143, 0x08f */ SDL_SCANCODE_WAKE, // KEY_WAKEUP /* 144, 0x090 */ SDL_SCANCODE_UNKNOWN, // KEY_FILE /* 145, 0x091 */ SDL_SCANCODE_UNKNOWN, // KEY_SENDFILE /* 146, 0x092 */ SDL_SCANCODE_UNKNOWN, // KEY_DELETEFILE @@ -282,7 +282,6 @@ static SDL_Scancode const linux_scancode_table[] = { /* 253, 0x0fd */ SDL_SCANCODE_UNKNOWN, /* 254, 0x0fe */ SDL_SCANCODE_UNKNOWN, /* 255, 0x0ff */ SDL_SCANCODE_UNKNOWN, -#if 0 // We don't have any mapped scancodes after this point (yet) /* 256, 0x100 */ SDL_SCANCODE_UNKNOWN, /* 257, 0x101 */ SDL_SCANCODE_UNKNOWN, /* 258, 0x102 */ SDL_SCANCODE_UNKNOWN, @@ -380,9 +379,9 @@ static SDL_Scancode const linux_scancode_table[] = { /* 350, 0x15e */ SDL_SCANCODE_UNKNOWN, /* 351, 0x15f */ SDL_SCANCODE_UNKNOWN, /* 352, 0x160 */ SDL_SCANCODE_UNKNOWN, // KEY_OK - /* 353, 0x161 */ SDL_SCANCODE_UNKNOWN, // KEY_SELECT + /* 353, 0x161 */ SDL_SCANCODE_SELECT, // KEY_SELECT /* 354, 0x162 */ SDL_SCANCODE_UNKNOWN, // KEY_GOTO - /* 355, 0x163 */ SDL_SCANCODE_UNKNOWN, // KEY_CLEAR + /* 355, 0x163 */ SDL_SCANCODE_CLEAR, // KEY_CLEAR /* 356, 0x164 */ SDL_SCANCODE_UNKNOWN, // KEY_POWER2 /* 357, 0x165 */ SDL_SCANCODE_UNKNOWN, // KEY_OPTION /* 358, 0x166 */ SDL_SCANCODE_UNKNOWN, // KEY_INFO @@ -400,7 +399,7 @@ static SDL_Scancode const linux_scancode_table[] = { /* 370, 0x172 */ SDL_SCANCODE_UNKNOWN, // KEY_SUBTITLE /* 371, 0x173 */ SDL_SCANCODE_UNKNOWN, // KEY_ANGLE /* 372, 0x174 */ SDL_SCANCODE_UNKNOWN, // KEY_FULL_SCREEN - /* 373, 0x175 */ SDL_SCANCODE_UNKNOWN, // KEY_MODE + /* 373, 0x175 */ SDL_SCANCODE_MODE, // KEY_MODE /* 374, 0x176 */ SDL_SCANCODE_UNKNOWN, // KEY_KEYBOARD /* 375, 0x177 */ SDL_SCANCODE_UNKNOWN, // KEY_ASPECT_RATIO /* 376, 0x178 */ SDL_SCANCODE_UNKNOWN, // KEY_PC @@ -429,8 +428,9 @@ static SDL_Scancode const linux_scancode_table[] = { /* 399, 0x18f */ SDL_SCANCODE_UNKNOWN, // KEY_GREEN /* 400, 0x190 */ SDL_SCANCODE_UNKNOWN, // KEY_YELLOW /* 401, 0x191 */ SDL_SCANCODE_UNKNOWN, // KEY_BLUE - /* 402, 0x192 */ SDL_SCANCODE_UNKNOWN, // KEY_CHANNELUP - /* 403, 0x193 */ SDL_SCANCODE_UNKNOWN, // KEY_CHANNELDOWN + /* 402, 0x192 */ SDL_SCANCODE_CHANNEL_INCREMENT, // KEY_CHANNELUP + /* 403, 0x193 */ SDL_SCANCODE_CHANNEL_DECREMENT, // KEY_CHANNELDOWN +#if 0 // We don't have any mapped scancodes after this point (yet) /* 404, 0x194 */ SDL_SCANCODE_UNKNOWN, // KEY_FIRST /* 405, 0x195 */ SDL_SCANCODE_UNKNOWN, // KEY_LAST /* 406, 0x196 */ SDL_SCANCODE_UNKNOWN, // KEY_AB diff --git a/libs/SDL3/src/events/scancodes_xfree86.h b/libs/SDL3/src/events/scancodes_xfree86.h index 5e51bb1..81d625e 100644 --- a/libs/SDL3/src/events/scancodes_xfree86.h +++ b/libs/SDL3/src/events/scancodes_xfree86.h @@ -371,10 +371,10 @@ static const SDL_Scancode xfree86_scancode_table2[] = { /* 188, 0x0bc */ SDL_SCANCODE_F18, // XF86Launch9 /* 189, 0x0bd */ SDL_SCANCODE_F19, // NoSymbol /* 190, 0x0be */ SDL_SCANCODE_F20, // XF86AudioMicMute - /* 191, 0x0bf */ SDL_SCANCODE_UNKNOWN, // XF86TouchpadToggle - /* 192, 0x0c0 */ SDL_SCANCODE_UNKNOWN, // XF86TouchpadOn - /* 193, 0x0c1 */ SDL_SCANCODE_UNKNOWN, // XF86TouchpadOff - /* 194, 0x0c2 */ SDL_SCANCODE_UNKNOWN, // NoSymbol + /* 191, 0x0bf */ SDL_SCANCODE_F21, // XF86TouchpadToggle + /* 192, 0x0c0 */ SDL_SCANCODE_F22, // XF86TouchpadOn + /* 193, 0x0c1 */ SDL_SCANCODE_F23, // XF86TouchpadOff + /* 194, 0x0c2 */ SDL_SCANCODE_F24, // NoSymbol /* 195, 0x0c3 */ SDL_SCANCODE_MODE, // Mode_switch /* 196, 0x0c4 */ SDL_SCANCODE_UNKNOWN, // NoSymbol /* 197, 0x0c5 */ SDL_SCANCODE_UNKNOWN, // NoSymbol diff --git a/libs/SDL3/src/filesystem/SDL_filesystem.c b/libs/SDL3/src/filesystem/SDL_filesystem.c index 7555652..b115019 100644 --- a/libs/SDL3/src/filesystem/SDL_filesystem.c +++ b/libs/SDL3/src/filesystem/SDL_filesystem.c @@ -188,6 +188,12 @@ static bool WildcardMatch(const char *pattern, const char *str, bool *matched_to sch = *str; pch = *pattern; } + + #ifdef SDL_PLATFORM_WINDOWS + if (sch == '\\') { + sch = '/'; + } + #endif } // '*' at the end can be ignored, they are allowed to match nothing. diff --git a/libs/SDL3/src/gpu/SDL_gpu.c b/libs/SDL3/src/gpu/SDL_gpu.c index b3148c2..aea7ed5 100644 --- a/libs/SDL3/src/gpu/SDL_gpu.c +++ b/libs/SDL3/src/gpu/SDL_gpu.c @@ -56,15 +56,47 @@ } #define CHECK_RENDERPASS \ - if (!((Pass *)render_pass)->in_progress) { \ + if (!((RenderPass *)render_pass)->in_progress) { \ SDL_assert_release(!"Render pass not in progress!"); \ return; \ } -#define CHECK_GRAPHICS_PIPELINE_BOUND \ - if (!((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->graphics_pipeline_bound) { \ - SDL_assert_release(!"Graphics pipeline not bound!"); \ - return; \ +#define CHECK_SAMPLER_TEXTURES \ + RenderPass *rp = (RenderPass *)render_pass; \ + for (Uint32 color_target_index = 0; color_target_index < rp->num_color_targets; color_target_index += 1) { \ + for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \ + if (rp->color_targets[color_target_index] == texture_sampler_bindings[texture_sampler_index].texture) { \ + SDL_assert_release(!"Texture cannot be simultaneously bound as a color target and a sampler!"); \ + } \ + } \ + } \ + \ + for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \ + if (rp->depth_stencil_target != NULL && rp->depth_stencil_target == texture_sampler_bindings[texture_sampler_index].texture) { \ + SDL_assert_release(!"Texture cannot be simultaneously bound as a depth stencil target and a sampler!"); \ + } \ + } + +#define CHECK_STORAGE_TEXTURES \ + RenderPass *rp = (RenderPass *)render_pass; \ + for (Uint32 color_target_index = 0; color_target_index < rp->num_color_targets; color_target_index += 1) { \ + for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \ + if (rp->color_targets[color_target_index] == storage_textures[texture_sampler_index]) { \ + SDL_assert_release(!"Texture cannot be simultaneously bound as a color target and a storage texture!"); \ + } \ + } \ + } \ + \ + for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \ + if (rp->depth_stencil_target != NULL && rp->depth_stencil_target == storage_textures[texture_sampler_index]) { \ + SDL_assert_release(!"Texture cannot be simultaneously bound as a depth stencil target and a storage texture!"); \ + } \ + } + +#define CHECK_GRAPHICS_PIPELINE_BOUND \ + if (!((RenderPass *)render_pass)->graphics_pipeline) { \ + SDL_assert_release(!"Graphics pipeline not bound!"); \ + return; \ } #define CHECK_COMPUTEPASS \ @@ -74,7 +106,7 @@ } #define CHECK_COMPUTE_PIPELINE_BOUND \ - if (!((CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER)->compute_pipeline_bound) { \ + if (!((ComputePass *)compute_pass)->compute_pipeline) { \ SDL_assert_release(!"Compute pipeline not bound!"); \ return; \ } @@ -137,27 +169,144 @@ ((CommandBufferCommonHeader *)command_buffer)->device #define RENDERPASS_COMMAND_BUFFER \ - ((Pass *)render_pass)->command_buffer + ((RenderPass *)render_pass)->command_buffer #define RENDERPASS_DEVICE \ ((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->device +#define RENDERPASS_BOUND_PIPELINE \ + ((RenderPass *)render_pass)->graphics_pipeline + #define COMPUTEPASS_COMMAND_BUFFER \ ((Pass *)compute_pass)->command_buffer #define COMPUTEPASS_DEVICE \ ((CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER)->device +#define COMPUTEPASS_BOUND_PIPELINE \ + ((ComputePass *)compute_pass)->compute_pipeline + #define COPYPASS_COMMAND_BUFFER \ ((Pass *)copy_pass)->command_buffer #define COPYPASS_DEVICE \ ((CommandBufferCommonHeader *)COPYPASS_COMMAND_BUFFER)->device +static bool TextureFormatIsComputeWritable[] = { + false, // INVALID + false, // A8_UNORM + true, // R8_UNORM + true, // R8G8_UNORM + true, // R8G8B8A8_UNORM + true, // R16_UNORM + true, // R16G16_UNORM + true, // R16G16B16A16_UNORM + true, // R10G10B10A2_UNORM + false, // B5G6R5_UNORM + false, // B5G5R5A1_UNORM + false, // B4G4R4A4_UNORM + false, // B8G8R8A8_UNORM + false, // BC1_UNORM + false, // BC2_UNORM + false, // BC3_UNORM + false, // BC4_UNORM + false, // BC5_UNORM + false, // BC7_UNORM + false, // BC6H_FLOAT + false, // BC6H_UFLOAT + true, // R8_SNORM + true, // R8G8_SNORM + true, // R8G8B8A8_SNORM + true, // R16_SNORM + true, // R16G16_SNORM + true, // R16G16B16A16_SNORM + true, // R16_FLOAT + true, // R16G16_FLOAT + true, // R16G16B16A16_FLOAT + true, // R32_FLOAT + true, // R32G32_FLOAT + true, // R32G32B32A32_FLOAT + true, // R11G11B10_UFLOAT + true, // R8_UINT + true, // R8G8_UINT + true, // R8G8B8A8_UINT + true, // R16_UINT + true, // R16G16_UINT + true, // R16G16B16A16_UINT + true, // R32_UINT + true, // R32G32_UINT + true, // R32G32B32A32_UINT + true, // R8_INT + true, // R8G8_INT + true, // R8G8B8A8_INT + true, // R16_INT + true, // R16G16_INT + true, // R16G16B16A16_INT + true, // R32_INT + true, // R32G32_INT + true, // R32G32B32A32_INT + false, // R8G8B8A8_UNORM_SRGB + false, // B8G8R8A8_UNORM_SRGB + false, // BC1_UNORM_SRGB + false, // BC3_UNORM_SRGB + false, // BC3_UNORM_SRGB + false, // BC7_UNORM_SRGB + false, // D16_UNORM + false, // D24_UNORM + false, // D32_FLOAT + false, // D24_UNORM_S8_UINT + false, // D32_FLOAT_S8_UINT + false, // ASTC_4x4_UNORM + false, // ASTC_5x4_UNORM + false, // ASTC_5x5_UNORM + false, // ASTC_6x5_UNORM + false, // ASTC_6x6_UNORM + false, // ASTC_8x5_UNORM + false, // ASTC_8x6_UNORM + false, // ASTC_8x8_UNORM + false, // ASTC_10x5_UNORM + false, // ASTC_10x6_UNORM + false, // ASTC_10x8_UNORM + false, // ASTC_10x10_UNORM + false, // ASTC_12x10_UNORM + false, // ASTC_12x12_UNORM + false, // ASTC_4x4_UNORM_SRGB + false, // ASTC_5x4_UNORM_SRGB + false, // ASTC_5x5_UNORM_SRGB + false, // ASTC_6x5_UNORM_SRGB + false, // ASTC_6x6_UNORM_SRGB + false, // ASTC_8x5_UNORM_SRGB + false, // ASTC_8x6_UNORM_SRGB + false, // ASTC_8x8_UNORM_SRGB + false, // ASTC_10x5_UNORM_SRGB + false, // ASTC_10x6_UNORM_SRGB + false, // ASTC_10x8_UNORM_SRGB + false, // ASTC_10x10_UNORM_SRGB + false, // ASTC_12x10_UNORM_SRGB + false, // ASTC_12x12_UNORM_SRGB + false, // ASTC_4x4_FLOAT + false, // ASTC_5x4_FLOAT + false, // ASTC_5x5_FLOAT + false, // ASTC_6x5_FLOAT + false, // ASTC_6x6_FLOAT + false, // ASTC_8x5_FLOAT + false, // ASTC_8x6_FLOAT + false, // ASTC_8x8_FLOAT + false, // ASTC_10x5_FLOAT + false, // ASTC_10x6_FLOAT + false, // ASTC_10x8_FLOAT + false, // ASTC_10x10_FLOAT + false, // ASTC_12x10_FLOAT + false // ASTC_12x12_FLOAT +}; + // Drivers #ifndef SDL_GPU_DISABLED static const SDL_GPUBootstrap *backends[] = { +#ifdef SDL_GPU_PRIVATE + &PrivateGPUDriver, +#endif #ifdef SDL_GPU_METAL &MetalDriver, #endif @@ -368,6 +517,73 @@ void SDL_GPU_BlitCommon( SDL_EndGPURenderPass(render_pass); } +static void SDL_GPU_CheckGraphicsBindings(SDL_GPURenderPass *render_pass) +{ + RenderPass *rp = (RenderPass *)render_pass; + GraphicsPipelineCommonHeader *pipeline = (GraphicsPipelineCommonHeader *)RENDERPASS_BOUND_PIPELINE; + for (Uint32 i = 0; i < pipeline->num_vertex_samplers; i += 1) { + if (!rp->vertex_sampler_bound[i]) { + SDL_assert_release(!"Missing vertex sampler binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_vertex_storage_textures; i += 1) { + if (!rp->vertex_storage_texture_bound[i]) { + SDL_assert_release(!"Missing vertex storage texture binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_vertex_storage_buffers; i += 1) { + if (!rp->vertex_storage_buffer_bound[i]) { + SDL_assert_release(!"Missing vertex storage buffer binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_fragment_samplers; i += 1) { + if (!rp->fragment_sampler_bound[i]) { + SDL_assert_release(!"Missing fragment sampler binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_fragment_storage_textures; i += 1) { + if (!rp->fragment_storage_texture_bound[i]) { + SDL_assert_release(!"Missing fragment storage texture binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_fragment_storage_buffers; i += 1) { + if (!rp->fragment_storage_buffer_bound[i]) { + SDL_assert_release(!"Missing fragment storage buffer binding!"); + } + } +} + +static void SDL_GPU_CheckComputeBindings(SDL_GPUComputePass *compute_pass) +{ + ComputePass *cp = (ComputePass *)compute_pass; + ComputePipelineCommonHeader *pipeline = (ComputePipelineCommonHeader *)COMPUTEPASS_BOUND_PIPELINE; + for (Uint32 i = 0; i < pipeline->numSamplers; i += 1) { + if (!cp->sampler_bound[i]) { + SDL_assert_release(!"Missing compute sampler binding!"); + } + } + for (Uint32 i = 0; i < pipeline->numReadonlyStorageTextures; i += 1) { + if (!cp->read_only_storage_texture_bound[i]) { + SDL_assert_release(!"Missing compute readonly storage texture binding!"); + } + } + for (Uint32 i = 0; i < pipeline->numReadonlyStorageBuffers; i += 1) { + if (!cp->read_only_storage_buffer_bound[i]) { + SDL_assert_release(!"Missing compute readonly storage buffer binding!"); + } + } + for (Uint32 i = 0; i < pipeline->numReadWriteStorageTextures; i += 1) { + if (!cp->read_write_storage_texture_bound[i]) { + SDL_assert_release(!"Missing compute read-write storage texture binding!"); + } + } + for (Uint32 i = 0; i < pipeline->numReadWriteStorageBuffers; i += 1) { + if (!cp->read_write_storage_buffer_bound[i]) { + SDL_assert_release(!"Missing compute read-write storage buffer bbinding!"); + } + } +} + // Driver Functions #ifndef SDL_GPU_DISABLED @@ -529,7 +745,6 @@ SDL_GPUDevice *SDL_CreateGPUDeviceWithProperties(SDL_PropertiesID props) result = selectedBackend->CreateDevice(debug_mode, preferLowPower, props); if (result != NULL) { result->backend = selectedBackend->name; - result->shader_formats = selectedBackend->shader_formats; result->debug_mode = debug_mode; } } @@ -605,6 +820,7 @@ Uint32 SDL_GPUTextureFormatTexelBlockSize( case SDL_GPU_TEXTUREFORMAT_R8_SNORM: case SDL_GPU_TEXTUREFORMAT_A8_UNORM: case SDL_GPU_TEXTUREFORMAT_R8_UINT: + case SDL_GPU_TEXTUREFORMAT_R8_INT: return 1; case SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM: case SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM: @@ -613,9 +829,11 @@ Uint32 SDL_GPUTextureFormatTexelBlockSize( case SDL_GPU_TEXTUREFORMAT_R8G8_SNORM: case SDL_GPU_TEXTUREFORMAT_R8G8_UNORM: case SDL_GPU_TEXTUREFORMAT_R8G8_UINT: + case SDL_GPU_TEXTUREFORMAT_R8G8_INT: case SDL_GPU_TEXTUREFORMAT_R16_UNORM: case SDL_GPU_TEXTUREFORMAT_R16_SNORM: case SDL_GPU_TEXTUREFORMAT_R16_UINT: + case SDL_GPU_TEXTUREFORMAT_R16_INT: case SDL_GPU_TEXTUREFORMAT_D16_UNORM: return 2; case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM: @@ -628,11 +846,15 @@ Uint32 SDL_GPUTextureFormatTexelBlockSize( case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM: case SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM: case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT: + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT: case SDL_GPU_TEXTUREFORMAT_R16G16_UINT: + case SDL_GPU_TEXTUREFORMAT_R16G16_INT: case SDL_GPU_TEXTUREFORMAT_R16G16_UNORM: case SDL_GPU_TEXTUREFORMAT_R16G16_SNORM: case SDL_GPU_TEXTUREFORMAT_D24_UNORM: case SDL_GPU_TEXTUREFORMAT_D32_FLOAT: + case SDL_GPU_TEXTUREFORMAT_R32_UINT: + case SDL_GPU_TEXTUREFORMAT_R32_INT: case SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT: return 4; case SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT: @@ -640,10 +862,15 @@ Uint32 SDL_GPUTextureFormatTexelBlockSize( case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT: case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM: case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM: - case SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT: case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT: + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT: + case SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT: + case SDL_GPU_TEXTUREFORMAT_R32G32_UINT: + case SDL_GPU_TEXTUREFORMAT_R32G32_INT: return 8; case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT: + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT: + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT: return 16; case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM: case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM: @@ -706,6 +933,13 @@ bool SDL_GPUTextureSupportsFormat( CHECK_TEXTUREFORMAT_ENUM_INVALID(format, false) } + if ((usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE) || + (usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE)) { + if (!TextureFormatIsComputeWritable[format]) { + return false; + } + } + return device->SupportsTextureFormat( device->driverData, format, @@ -842,6 +1076,12 @@ SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline( SDL_assert_release(!"The number of vertex attributes in a vertex input state must not exceed 16!"); return NULL; } + for (Uint32 i = 0; i < graphicsPipelineCreateInfo->vertex_input_state.num_vertex_buffers; i += 1) { + if (graphicsPipelineCreateInfo->vertex_input_state.vertex_buffer_descriptions[i].instance_step_rate != 0) { + SDL_assert_release(!"For all vertex buffer descriptions, instance_step_rate must be 0!"); + return NULL; + } + } Uint32 locations[MAX_VERTEX_ATTRIBUTES]; for (Uint32 i = 0; i < graphicsPipelineCreateInfo->vertex_input_state.num_vertex_attributes; i += 1) { CHECK_VERTEXELEMENTFORMAT_ENUM_INVALID(graphicsPipelineCreateInfo->vertex_input_state.vertex_attributes[i].format, NULL); @@ -850,9 +1090,18 @@ SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline( for (Uint32 j = 0; j < i; j += 1) { if (locations[j] == locations[i]) { SDL_assert_release(!"Each vertex attribute location in a vertex input state must be unique!"); + return NULL; } } } + if (graphicsPipelineCreateInfo->multisample_state.enable_mask) { + SDL_assert_release(!"For multisample states, enable_mask must be false!"); + return NULL; + } + if (graphicsPipelineCreateInfo->multisample_state.sample_mask != 0) { + SDL_assert_release(!"For multisample states, sample_mask must be 0!"); + return NULL; + } if (graphicsPipelineCreateInfo->depth_stencil_state.enable_depth_test) { CHECK_COMPAREOP_ENUM_INVALID(graphicsPipelineCreateInfo->depth_stencil_state.compare_op, NULL) } @@ -1313,15 +1562,30 @@ SDL_GPUCommandBuffer *SDL_AcquireGPUCommandBuffer( commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; commandBufferHeader->device = device; commandBufferHeader->render_pass.command_buffer = command_buffer; - commandBufferHeader->render_pass.in_progress = false; - commandBufferHeader->graphics_pipeline_bound = false; commandBufferHeader->compute_pass.command_buffer = command_buffer; - commandBufferHeader->compute_pass.in_progress = false; - commandBufferHeader->compute_pipeline_bound = false; commandBufferHeader->copy_pass.command_buffer = command_buffer; - commandBufferHeader->copy_pass.in_progress = false; - commandBufferHeader->swapchain_texture_acquired = false; - commandBufferHeader->submitted = false; + + if (device->debug_mode) { + commandBufferHeader->render_pass.in_progress = false; + commandBufferHeader->render_pass.graphics_pipeline = NULL; + commandBufferHeader->compute_pass.in_progress = false; + commandBufferHeader->compute_pass.compute_pipeline = NULL; + commandBufferHeader->copy_pass.in_progress = false; + commandBufferHeader->swapchain_texture_acquired = false; + commandBufferHeader->submitted = false; + commandBufferHeader->ignore_render_pass_texture_validation = false; + SDL_zeroa(commandBufferHeader->render_pass.vertex_sampler_bound); + SDL_zeroa(commandBufferHeader->render_pass.vertex_storage_texture_bound); + SDL_zeroa(commandBufferHeader->render_pass.vertex_storage_buffer_bound); + SDL_zeroa(commandBufferHeader->render_pass.fragment_sampler_bound); + SDL_zeroa(commandBufferHeader->render_pass.fragment_storage_texture_bound); + SDL_zeroa(commandBufferHeader->render_pass.fragment_storage_buffer_bound); + SDL_zeroa(commandBufferHeader->compute_pass.sampler_bound); + SDL_zeroa(commandBufferHeader->compute_pass.read_only_storage_texture_bound); + SDL_zeroa(commandBufferHeader->compute_pass.read_only_storage_buffer_bound); + SDL_zeroa(commandBufferHeader->compute_pass.read_write_storage_texture_bound); + SDL_zeroa(commandBufferHeader->compute_pass.read_write_storage_buffer_bound); + } return command_buffer; } @@ -1439,30 +1703,47 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( if (color_target_infos[i].cycle && color_target_infos[i].load_op == SDL_GPU_LOADOP_LOAD) { SDL_assert_release(!"Cannot cycle color target when load op is LOAD!"); + return NULL; } if (color_target_infos[i].store_op == SDL_GPU_STOREOP_RESOLVE || color_target_infos[i].store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) { if (color_target_infos[i].resolve_texture == NULL) { SDL_assert_release(!"Store op is RESOLVE or RESOLVE_AND_STORE but resolve_texture is NULL!"); + return NULL; } else { TextureCommonHeader *resolveTextureHeader = (TextureCommonHeader *)color_target_infos[i].resolve_texture; if (textureHeader->info.sample_count == SDL_GPU_SAMPLECOUNT_1) { SDL_assert_release(!"Store op is RESOLVE or RESOLVE_AND_STORE but texture is not multisample!"); + return NULL; } if (resolveTextureHeader->info.sample_count != SDL_GPU_SAMPLECOUNT_1) { SDL_assert_release(!"Resolve texture must have a sample count of 1!"); + return NULL; } if (resolveTextureHeader->info.format != textureHeader->info.format) { SDL_assert_release(!"Resolve texture must have the same format as its corresponding color target!"); + return NULL; } if (resolveTextureHeader->info.type == SDL_GPU_TEXTURETYPE_3D) { SDL_assert_release(!"Resolve texture must not be of TEXTURETYPE_3D!"); + return NULL; } if (!(resolveTextureHeader->info.usage & SDL_GPU_TEXTUREUSAGE_COLOR_TARGET)) { SDL_assert_release(!"Resolve texture usage must include COLOR_TARGET!"); + return NULL; } } } + + if (color_target_infos[i].layer_or_depth_plane >= textureHeader->info.layer_count_or_depth) { + SDL_assert_release(!"Color target layer index must be less than the texture's layer count!"); + return NULL; + } + + if (color_target_infos[i].mip_level >= textureHeader->info.num_levels) { + SDL_assert_release(!"Color target mip level must be less than the texture's level count!"); + return NULL; + } } if (depth_stencil_target_info != NULL) { @@ -1470,10 +1751,12 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( TextureCommonHeader *textureHeader = (TextureCommonHeader *)depth_stencil_target_info->texture; if (!(textureHeader->info.usage & SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)) { SDL_assert_release(!"Depth target must have been created with the DEPTH_STENCIL_TARGET usage flag!"); + return NULL; } if (depth_stencil_target_info->cycle && (depth_stencil_target_info->load_op == SDL_GPU_LOADOP_LOAD || depth_stencil_target_info->stencil_load_op == SDL_GPU_LOADOP_LOAD)) { SDL_assert_release(!"Cannot cycle depth target when load op or stencil load op is LOAD!"); + return NULL; } if (depth_stencil_target_info->store_op == SDL_GPU_STOREOP_RESOLVE || @@ -1481,6 +1764,7 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( depth_stencil_target_info->store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE || depth_stencil_target_info->stencil_store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) { SDL_assert_release(!"RESOLVE store ops are not supported for depth-stencil targets!"); + return NULL; } } } @@ -1492,7 +1776,18 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( depth_stencil_target_info); commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - commandBufferHeader->render_pass.in_progress = true; + + if (COMMAND_BUFFER_DEVICE->debug_mode) { + commandBufferHeader->render_pass.in_progress = true; + for (Uint32 i = 0; i < num_color_targets; i += 1) { + commandBufferHeader->render_pass.color_targets[i] = color_target_infos[i].texture; + } + commandBufferHeader->render_pass.num_color_targets = num_color_targets; + if (depth_stencil_target_info != NULL) { + commandBufferHeader->render_pass.depth_stencil_target = depth_stencil_target_info->texture; + } + } + return (SDL_GPURenderPass *)&(commandBufferHeader->render_pass); } @@ -1500,8 +1795,6 @@ void SDL_BindGPUGraphicsPipeline( SDL_GPURenderPass *render_pass, SDL_GPUGraphicsPipeline *graphics_pipeline) { - CommandBufferCommonHeader *commandBufferHeader; - if (render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; @@ -1515,8 +1808,10 @@ void SDL_BindGPUGraphicsPipeline( RENDERPASS_COMMAND_BUFFER, graphics_pipeline); - commandBufferHeader = (CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER; - commandBufferHeader->graphics_pipeline_bound = true; + + if (RENDERPASS_DEVICE->debug_mode) { + RENDERPASS_BOUND_PIPELINE = graphics_pipeline; + } } void SDL_SetGPUViewport( @@ -1666,6 +1961,15 @@ void SDL_BindGPUVertexSamplers( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + + if (!((CommandBufferCommonHeader*)RENDERPASS_COMMAND_BUFFER)->ignore_render_pass_texture_validation) + { + CHECK_SAMPLER_TEXTURES + } + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->vertex_sampler_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindVertexSamplers( @@ -1692,6 +1996,11 @@ void SDL_BindGPUVertexStorageTextures( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + CHECK_STORAGE_TEXTURES + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->vertex_storage_texture_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindVertexStorageTextures( @@ -1718,6 +2027,10 @@ void SDL_BindGPUVertexStorageBuffers( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->vertex_storage_buffer_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindVertexStorageBuffers( @@ -1744,6 +2057,14 @@ void SDL_BindGPUFragmentSamplers( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + + if (!((CommandBufferCommonHeader*)RENDERPASS_COMMAND_BUFFER)->ignore_render_pass_texture_validation) { + CHECK_SAMPLER_TEXTURES + } + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->fragment_sampler_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindFragmentSamplers( @@ -1770,6 +2091,11 @@ void SDL_BindGPUFragmentStorageTextures( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + CHECK_STORAGE_TEXTURES + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->fragment_storage_texture_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindFragmentStorageTextures( @@ -1796,6 +2122,10 @@ void SDL_BindGPUFragmentStorageBuffers( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->fragment_storage_buffer_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindFragmentStorageBuffers( @@ -1821,6 +2151,7 @@ void SDL_DrawGPUIndexedPrimitives( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS CHECK_GRAPHICS_PIPELINE_BOUND + SDL_GPU_CheckGraphicsBindings(render_pass); } RENDERPASS_DEVICE->DrawIndexedPrimitives( @@ -1847,6 +2178,7 @@ void SDL_DrawGPUPrimitives( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS CHECK_GRAPHICS_PIPELINE_BOUND + SDL_GPU_CheckGraphicsBindings(render_pass); } RENDERPASS_DEVICE->DrawPrimitives( @@ -1875,6 +2207,7 @@ void SDL_DrawGPUPrimitivesIndirect( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS CHECK_GRAPHICS_PIPELINE_BOUND + SDL_GPU_CheckGraphicsBindings(render_pass); } RENDERPASS_DEVICE->DrawPrimitivesIndirect( @@ -1902,6 +2235,7 @@ void SDL_DrawGPUIndexedPrimitivesIndirect( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS CHECK_GRAPHICS_PIPELINE_BOUND + SDL_GPU_CheckGraphicsBindings(render_pass); } RENDERPASS_DEVICE->DrawIndexedPrimitivesIndirect( @@ -1914,13 +2248,14 @@ void SDL_DrawGPUIndexedPrimitivesIndirect( void SDL_EndGPURenderPass( SDL_GPURenderPass *render_pass) { - CommandBufferCommonHeader *commandBufferCommonHeader; - if (render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } + CommandBufferCommonHeader *commandBufferCommonHeader; + commandBufferCommonHeader = (CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER; + if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS } @@ -1928,9 +2263,22 @@ void SDL_EndGPURenderPass( RENDERPASS_DEVICE->EndRenderPass( RENDERPASS_COMMAND_BUFFER); - commandBufferCommonHeader = (CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER; - commandBufferCommonHeader->render_pass.in_progress = false; - commandBufferCommonHeader->graphics_pipeline_bound = false; + if (RENDERPASS_DEVICE->debug_mode) { + commandBufferCommonHeader->render_pass.in_progress = false; + for (Uint32 i = 0; i < MAX_COLOR_TARGET_BINDINGS; i += 1) + { + commandBufferCommonHeader->render_pass.color_targets[i] = NULL; + } + commandBufferCommonHeader->render_pass.num_color_targets = 0; + commandBufferCommonHeader->render_pass.depth_stencil_target = NULL; + commandBufferCommonHeader->render_pass.graphics_pipeline = NULL; + SDL_zeroa(commandBufferCommonHeader->render_pass.vertex_sampler_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.vertex_storage_texture_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.vertex_storage_buffer_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.fragment_sampler_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.fragment_storage_texture_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.fragment_storage_buffer_bound); + } } // Compute Pass @@ -1974,6 +2322,16 @@ SDL_GPUComputePass *SDL_BeginGPUComputePass( SDL_assert_release(!"Texture must be created with COMPUTE_STORAGE_WRITE or COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE flag"); return NULL; } + + if (storage_texture_bindings[i].layer >= header->info.layer_count_or_depth) { + SDL_assert_release(!"Storage texture layer index must be less than the texture's layer count!"); + return NULL; + } + + if (storage_texture_bindings[i].mip_level >= header->info.num_levels) { + SDL_assert_release(!"Storage texture mip level must be less than the texture's level count!"); + return NULL; + } } // TODO: validate buffer usage? @@ -1987,7 +2345,19 @@ SDL_GPUComputePass *SDL_BeginGPUComputePass( num_storage_buffer_bindings); commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - commandBufferHeader->compute_pass.in_progress = true; + + if (COMMAND_BUFFER_DEVICE->debug_mode) { + commandBufferHeader->compute_pass.in_progress = true; + + for (Uint32 i = 0; i < num_storage_texture_bindings; i += 1) { + commandBufferHeader->compute_pass.read_write_storage_texture_bound[i] = true; + } + + for (Uint32 i = 0; i < num_storage_buffer_bindings; i += 1) { + commandBufferHeader->compute_pass.read_write_storage_buffer_bound[i] = true; + } + } + return (SDL_GPUComputePass *)&(commandBufferHeader->compute_pass); } @@ -1995,8 +2365,6 @@ void SDL_BindGPUComputePipeline( SDL_GPUComputePass *compute_pass, SDL_GPUComputePipeline *compute_pipeline) { - CommandBufferCommonHeader *commandBufferHeader; - if (compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; @@ -2014,8 +2382,10 @@ void SDL_BindGPUComputePipeline( COMPUTEPASS_COMMAND_BUFFER, compute_pipeline); - commandBufferHeader = (CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER; - commandBufferHeader->compute_pipeline_bound = true; + + if (COMPUTEPASS_DEVICE->debug_mode) { + COMPUTEPASS_BOUND_PIPELINE = compute_pipeline; + } } void SDL_BindGPUComputeSamplers( @@ -2035,6 +2405,10 @@ void SDL_BindGPUComputeSamplers( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((ComputePass *)compute_pass)->sampler_bound[first_slot + i] = true; + } } COMPUTEPASS_DEVICE->BindComputeSamplers( @@ -2061,6 +2435,10 @@ void SDL_BindGPUComputeStorageTextures( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((ComputePass *)compute_pass)->read_only_storage_texture_bound[first_slot + i] = true; + } } COMPUTEPASS_DEVICE->BindComputeStorageTextures( @@ -2087,6 +2465,10 @@ void SDL_BindGPUComputeStorageBuffers( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((ComputePass *)compute_pass)->read_only_storage_buffer_bound[first_slot + i] = true; + } } COMPUTEPASS_DEVICE->BindComputeStorageBuffers( @@ -2110,6 +2492,7 @@ void SDL_DispatchGPUCompute( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS CHECK_COMPUTE_PIPELINE_BOUND + SDL_GPU_CheckComputeBindings(compute_pass); } COMPUTEPASS_DEVICE->DispatchCompute( @@ -2132,6 +2515,7 @@ void SDL_DispatchGPUComputeIndirect( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS CHECK_COMPUTE_PIPELINE_BOUND + SDL_GPU_CheckComputeBindings(compute_pass); } COMPUTEPASS_DEVICE->DispatchComputeIndirect( @@ -2157,9 +2541,16 @@ void SDL_EndGPUComputePass( COMPUTEPASS_DEVICE->EndComputePass( COMPUTEPASS_COMMAND_BUFFER); - commandBufferCommonHeader = (CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER; - commandBufferCommonHeader->compute_pass.in_progress = false; - commandBufferCommonHeader->compute_pipeline_bound = false; + if (COMPUTEPASS_DEVICE->debug_mode) { + commandBufferCommonHeader = (CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER; + commandBufferCommonHeader->compute_pass.in_progress = false; + commandBufferCommonHeader->compute_pass.compute_pipeline = NULL; + SDL_zeroa(commandBufferCommonHeader->compute_pass.sampler_bound); + SDL_zeroa(commandBufferCommonHeader->compute_pass.read_only_storage_texture_bound); + SDL_zeroa(commandBufferCommonHeader->compute_pass.read_only_storage_buffer_bound); + SDL_zeroa(commandBufferCommonHeader->compute_pass.read_write_storage_texture_bound); + SDL_zeroa(commandBufferCommonHeader->compute_pass.read_write_storage_buffer_bound); + } } // TransferBuffer Data @@ -2217,7 +2608,11 @@ SDL_GPUCopyPass *SDL_BeginGPUCopyPass( command_buffer); commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - commandBufferHeader->copy_pass.in_progress = true; + + if (COMMAND_BUFFER_DEVICE->debug_mode) { + commandBufferHeader->copy_pass.in_progress = true; + } + return (SDL_GPUCopyPass *)&(commandBufferHeader->copy_pass); } @@ -2468,7 +2863,9 @@ void SDL_EndGPUCopyPass( COPYPASS_DEVICE->EndCopyPass( COPYPASS_COMMAND_BUFFER); - ((CommandBufferCommonHeader *)COPYPASS_COMMAND_BUFFER)->copy_pass.in_progress = false; + if (COPYPASS_DEVICE->debug_mode) { + ((CommandBufferCommonHeader *)COPYPASS_COMMAND_BUFFER)->copy_pass.in_progress = false; + } } void SDL_GenerateMipmapsForGPUTexture( @@ -2498,11 +2895,19 @@ void SDL_GenerateMipmapsForGPUTexture( SDL_assert_release(!"GenerateMipmaps texture must be created with SAMPLER and COLOR_TARGET usage flags!"); return; } + + CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; + commandBufferHeader->ignore_render_pass_texture_validation = true; } COMMAND_BUFFER_DEVICE->GenerateMipmaps( command_buffer, texture); + + if (COMMAND_BUFFER_DEVICE->debug_mode) { + CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; + commandBufferHeader->ignore_render_pass_texture_validation = false; + } } void SDL_BlitGPUTexture( @@ -2616,8 +3021,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/SDL_sysgpu.h b/libs/SDL3/src/gpu/SDL_sysgpu.h index 98f4be9..8469d94 100644 --- a/libs/SDL3/src/gpu/SDL_sysgpu.h +++ b/libs/SDL3/src/gpu/SDL_sysgpu.h @@ -24,6 +24,21 @@ #ifndef SDL_GPU_DRIVER_H #define SDL_GPU_DRIVER_H +// GraphicsDevice Limits + +#define MAX_TEXTURE_SAMPLERS_PER_STAGE 16 +#define MAX_STORAGE_TEXTURES_PER_STAGE 8 +#define MAX_STORAGE_BUFFERS_PER_STAGE 8 +#define MAX_UNIFORM_BUFFERS_PER_STAGE 4 +#define MAX_COMPUTE_WRITE_TEXTURES 8 +#define MAX_COMPUTE_WRITE_BUFFERS 8 +#define UNIFORM_BUFFER_SIZE 32768 +#define MAX_VERTEX_BUFFERS 16 +#define MAX_VERTEX_ATTRIBUTES 16 +#define MAX_COLOR_TARGET_BINDINGS 4 +#define MAX_PRESENT_COUNT 16 +#define MAX_FRAMES_IN_FLIGHT 3 + // Common Structs typedef struct Pass @@ -32,16 +47,51 @@ typedef struct Pass bool in_progress; } Pass; +typedef struct ComputePass +{ + SDL_GPUCommandBuffer *command_buffer; + bool in_progress; + + SDL_GPUComputePipeline *compute_pipeline; + + bool sampler_bound[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + bool read_only_storage_texture_bound[MAX_STORAGE_TEXTURES_PER_STAGE]; + bool read_only_storage_buffer_bound[MAX_STORAGE_BUFFERS_PER_STAGE]; + bool read_write_storage_texture_bound[MAX_COMPUTE_WRITE_TEXTURES]; + bool read_write_storage_buffer_bound[MAX_COMPUTE_WRITE_BUFFERS]; +} ComputePass; + +typedef struct RenderPass +{ + SDL_GPUCommandBuffer *command_buffer; + bool in_progress; + SDL_GPUTexture *color_targets[MAX_COLOR_TARGET_BINDINGS]; + Uint32 num_color_targets; + SDL_GPUTexture *depth_stencil_target; + + SDL_GPUGraphicsPipeline *graphics_pipeline; + + bool vertex_sampler_bound[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + bool vertex_storage_texture_bound[MAX_STORAGE_TEXTURES_PER_STAGE]; + bool vertex_storage_buffer_bound[MAX_STORAGE_BUFFERS_PER_STAGE]; + + bool fragment_sampler_bound[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + bool fragment_storage_texture_bound[MAX_STORAGE_TEXTURES_PER_STAGE]; + bool fragment_storage_buffer_bound[MAX_STORAGE_BUFFERS_PER_STAGE]; +} RenderPass; + typedef struct CommandBufferCommonHeader { SDL_GPUDevice *device; - Pass render_pass; - bool graphics_pipeline_bound; - Pass compute_pass; - bool compute_pipeline_bound; + + RenderPass render_pass; + ComputePass compute_pass; + Pass copy_pass; bool swapchain_texture_acquired; bool submitted; + // used to avoid tripping assert on GenerateMipmaps + bool ignore_render_pass_texture_validation; } CommandBufferCommonHeader; typedef struct TextureCommonHeader @@ -49,6 +99,29 @@ typedef struct TextureCommonHeader SDL_GPUTextureCreateInfo info; } TextureCommonHeader; +typedef struct GraphicsPipelineCommonHeader +{ + Uint32 num_vertex_samplers; + Uint32 num_vertex_storage_textures; + Uint32 num_vertex_storage_buffers; + Uint32 num_vertex_uniform_buffers; + + Uint32 num_fragment_samplers; + Uint32 num_fragment_storage_textures; + Uint32 num_fragment_storage_buffers; + Uint32 num_fragment_uniform_buffers; +} GraphicsPipelineCommonHeader; + +typedef struct ComputePipelineCommonHeader +{ + Uint32 numSamplers; + Uint32 numReadonlyStorageTextures; + Uint32 numReadonlyStorageBuffers; + Uint32 numReadWriteStorageTextures; + Uint32 numReadWriteStorageBuffers; + Uint32 numUniformBuffers; +} ComputePipelineCommonHeader; + typedef struct BlitFragmentUniforms { // texcoord space @@ -136,6 +209,7 @@ static inline Sint32 Texture_GetBlockWidth( case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT: case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT: case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM: @@ -173,12 +247,18 @@ static inline Sint32 Texture_GetBlockWidth( case SDL_GPU_TEXTUREFORMAT_R16_UINT: case SDL_GPU_TEXTUREFORMAT_R16G16_UINT: case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT: + case SDL_GPU_TEXTUREFORMAT_R32_UINT: + case SDL_GPU_TEXTUREFORMAT_R32G32_UINT: + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT: case SDL_GPU_TEXTUREFORMAT_R8_INT: case SDL_GPU_TEXTUREFORMAT_R8G8_INT: case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT: case SDL_GPU_TEXTUREFORMAT_R16_INT: case SDL_GPU_TEXTUREFORMAT_R16G16_INT: case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT: + case SDL_GPU_TEXTUREFORMAT_R32_INT: + case SDL_GPU_TEXTUREFORMAT_R32G32_INT: + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT: case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_D16_UNORM: @@ -247,6 +327,7 @@ static inline Sint32 Texture_GetBlockHeight( case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT: case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT: case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM: @@ -287,12 +368,18 @@ static inline Sint32 Texture_GetBlockHeight( case SDL_GPU_TEXTUREFORMAT_R16_UINT: case SDL_GPU_TEXTUREFORMAT_R16G16_UINT: case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT: + case SDL_GPU_TEXTUREFORMAT_R32_UINT: + case SDL_GPU_TEXTUREFORMAT_R32G32_UINT: + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT: case SDL_GPU_TEXTUREFORMAT_R8_INT: case SDL_GPU_TEXTUREFORMAT_R8G8_INT: case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT: case SDL_GPU_TEXTUREFORMAT_R16_INT: case SDL_GPU_TEXTUREFORMAT_R16G16_INT: case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT: + case SDL_GPU_TEXTUREFORMAT_R32_INT: + case SDL_GPU_TEXTUREFORMAT_R32G32_INT: + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT: case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_D16_UNORM: @@ -373,21 +460,6 @@ static inline Uint32 BytesPerRow( return blocksPerRow * SDL_GPUTextureFormatTexelBlockSize(format); } -// GraphicsDevice Limits - -#define MAX_TEXTURE_SAMPLERS_PER_STAGE 16 -#define MAX_STORAGE_TEXTURES_PER_STAGE 8 -#define MAX_STORAGE_BUFFERS_PER_STAGE 8 -#define MAX_UNIFORM_BUFFERS_PER_STAGE 4 -#define MAX_COMPUTE_WRITE_TEXTURES 8 -#define MAX_COMPUTE_WRITE_BUFFERS 8 -#define UNIFORM_BUFFER_SIZE 32768 -#define MAX_VERTEX_BUFFERS 16 -#define MAX_VERTEX_ATTRIBUTES 16 -#define MAX_COLOR_TARGET_BINDINGS 4 -#define MAX_PRESENT_COUNT 16 -#define MAX_FRAMES_IN_FLIGHT 3 - // Internal Macros #define EXPAND_ARRAY_IF_NEEDED(arr, elementType, newCount, capacity, newCapacity) \ @@ -977,7 +1049,7 @@ extern "C" { extern SDL_GPUBootstrap VulkanDriver; extern SDL_GPUBootstrap D3D12Driver; extern SDL_GPUBootstrap MetalDriver; -extern SDL_GPUBootstrap PS5Driver; +extern SDL_GPUBootstrap PrivateGPUDriver; #ifdef __cplusplus } diff --git a/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c b/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c index aa5dc6c..1ad359b 100644 --- a/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c +++ b/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c @@ -475,6 +475,115 @@ static DXGI_FORMAT SDLToD3D12_DepthFormat[] = { }; SDL_COMPILE_TIME_ASSERT(SDLToD3D12_DepthFormat, SDL_arraysize(SDLToD3D12_DepthFormat) == SDL_GPU_TEXTUREFORMAT_MAX_ENUM_VALUE); +static DXGI_FORMAT SDLToD3D12_TypelessFormat[] = { + DXGI_FORMAT_UNKNOWN, // INVALID + DXGI_FORMAT_UNKNOWN, // A8_UNORM + DXGI_FORMAT_UNKNOWN, // R8_UNORM + DXGI_FORMAT_UNKNOWN, // R8G8_UNORM + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_UNORM + DXGI_FORMAT_UNKNOWN, // R16_UNORM + DXGI_FORMAT_UNKNOWN, // R16G16_UNORM + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_UNORM + DXGI_FORMAT_UNKNOWN, // R10G10B10A2_UNORM + DXGI_FORMAT_UNKNOWN, // B5G6R5_UNORM + DXGI_FORMAT_UNKNOWN, // B5G5R5A1_UNORM + DXGI_FORMAT_UNKNOWN, // B4G4R4A4_UNORM + DXGI_FORMAT_UNKNOWN, // B8G8R8A8_UNORM + DXGI_FORMAT_UNKNOWN, // BC1_UNORM + DXGI_FORMAT_UNKNOWN, // BC2_UNORM + DXGI_FORMAT_UNKNOWN, // BC3_UNORM + DXGI_FORMAT_UNKNOWN, // BC4_UNORM + DXGI_FORMAT_UNKNOWN, // BC5_UNORM + DXGI_FORMAT_UNKNOWN, // BC7_UNORM + DXGI_FORMAT_UNKNOWN, // BC6H_FLOAT + DXGI_FORMAT_UNKNOWN, // BC6H_UFLOAT + DXGI_FORMAT_UNKNOWN, // R8_SNORM + DXGI_FORMAT_UNKNOWN, // R8G8_SNORM + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_SNORM + DXGI_FORMAT_UNKNOWN, // R16_SNORM + DXGI_FORMAT_UNKNOWN, // R16G16_SNORM + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_SNORM + DXGI_FORMAT_UNKNOWN, // R16_FLOAT + DXGI_FORMAT_UNKNOWN, // R16G16_FLOAT + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_FLOAT + DXGI_FORMAT_UNKNOWN, // R32_FLOAT + DXGI_FORMAT_UNKNOWN, // R32G32_FLOAT + DXGI_FORMAT_UNKNOWN, // R32G32B32A32_FLOAT + DXGI_FORMAT_UNKNOWN, // R11G11B10_UFLOAT + DXGI_FORMAT_UNKNOWN, // R8_UINT + DXGI_FORMAT_UNKNOWN, // R8G8_UINT + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_UINT + DXGI_FORMAT_UNKNOWN, // R16_UINT + DXGI_FORMAT_UNKNOWN, // R16G16_UINT + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_UINT + DXGI_FORMAT_UNKNOWN, // R32_UINT + DXGI_FORMAT_UNKNOWN, // R32G32_UINT + DXGI_FORMAT_UNKNOWN, // R32G32B32A32_UINT + DXGI_FORMAT_UNKNOWN, // R8_INT + DXGI_FORMAT_UNKNOWN, // R8G8_INT + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_INT + DXGI_FORMAT_UNKNOWN, // R16_INT + DXGI_FORMAT_UNKNOWN, // R16G16_INT + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_INT + DXGI_FORMAT_UNKNOWN, // R32_INT + DXGI_FORMAT_UNKNOWN, // R32G32_INT + DXGI_FORMAT_UNKNOWN, // R32G32B32A32_INT + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // B8G8R8A8_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // BC1_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // BC2_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // BC3_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // BC7_UNORM_SRGB + DXGI_FORMAT_R16_TYPELESS, // D16_UNORM + DXGI_FORMAT_R24G8_TYPELESS, // D24_UNORM + DXGI_FORMAT_R32_TYPELESS, // D32_FLOAT + DXGI_FORMAT_R24G8_TYPELESS, // D24_UNORM_S8_UINT + DXGI_FORMAT_R32G8X24_TYPELESS, // D32_FLOAT_S8_UINT + DXGI_FORMAT_UNKNOWN, // ASTC_4x4_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_5x4_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_5x5_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_6x5_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_6x6_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_8x5_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_8x6_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_8x8_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_10x5_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_10x6_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_10x8_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_10x10_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_12x10_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_12x12_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_4x4_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_5x4_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_5x5_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_6x5_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_6x6_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_8x5_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_8x6_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_8x8_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_10x5_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_10x6_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_10x8_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_10x10_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_12x10_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_12x12_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_4x4_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_5x4_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_5x5_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_6x5_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_6x6_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_8x5_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_8x6_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_8x8_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_10x5_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_10x6_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_10x8_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_10x10_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_12x10_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_12x12_FLOAT +}; +SDL_COMPILE_TIME_ASSERT(SDLToD3D12_TypelessFormat, SDL_arraysize(SDLToD3D12_TypelessFormat) == SDL_GPU_TEXTUREFORMAT_MAX_ENUM_VALUE); + static D3D12_COMPARISON_FUNC SDLToD3D12_CompareOp[] = { D3D12_COMPARISON_FUNC_NEVER, // INVALID D3D12_COMPARISON_FUNC_NEVER, // NEVER @@ -906,26 +1015,38 @@ struct D3D12CommandBuffer Uint32 vertexBufferOffsets[MAX_VERTEX_BUFFERS]; Uint32 vertexBufferCount; - D3D12Texture *vertexSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Sampler *vertexSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Texture *vertexStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - D3D12Buffer *vertexStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE vertexSamplerTextureDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE vertexSamplerDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE vertexStorageTextureDescriptorHandles[MAX_STORAGE_TEXTURES_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE vertexStorageBufferDescriptorHandles[MAX_STORAGE_BUFFERS_PER_STAGE]; + D3D12UniformBuffer *vertexUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE]; - D3D12Texture *fragmentSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Sampler *fragmentSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Texture *fragmentStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - D3D12Buffer *fragmentStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE fragmentSamplerTextureDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE fragmentSamplerDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE fragmentStorageTextureDescriptorHandles[MAX_STORAGE_TEXTURES_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE fragmentStorageBufferDescriptorHandles[MAX_STORAGE_BUFFERS_PER_STAGE]; + D3D12UniformBuffer *fragmentUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE]; - D3D12Texture *computeSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Sampler *computeSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE computeSamplerTextureDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE computeSamplerDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE computeReadOnlyStorageTextureDescriptorHandles[MAX_STORAGE_TEXTURES_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE computeReadOnlyStorageBufferDescriptorHandles[MAX_STORAGE_BUFFERS_PER_STAGE]; + + // Track these separately because barriers can happen mid compute pass D3D12Texture *computeReadOnlyStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; D3D12Buffer *computeReadOnlyStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + + D3D12_CPU_DESCRIPTOR_HANDLE computeReadWriteStorageTextureDescriptorHandles[MAX_COMPUTE_WRITE_TEXTURES]; + D3D12_CPU_DESCRIPTOR_HANDLE computeReadWriteStorageBufferDescriptorHandles[MAX_COMPUTE_WRITE_BUFFERS]; + + // Track these separately because they are bound when the compute pass begins D3D12TextureSubresource *computeReadWriteStorageTextureSubresources[MAX_COMPUTE_WRITE_TEXTURES]; Uint32 computeReadWriteStorageTextureSubresourceCount; D3D12Buffer *computeReadWriteStorageBuffers[MAX_COMPUTE_WRITE_BUFFERS]; Uint32 computeReadWriteStorageBufferCount; + D3D12UniformBuffer *computeUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE]; // Resource tracking @@ -989,22 +1110,14 @@ typedef struct D3D12GraphicsRootSignature struct D3D12GraphicsPipeline { + GraphicsPipelineCommonHeader header; + ID3D12PipelineState *pipelineState; D3D12GraphicsRootSignature *rootSignature; SDL_GPUPrimitiveType primitiveType; Uint32 vertexStrides[MAX_VERTEX_BUFFERS]; - Uint32 vertexSamplerCount; - Uint32 vertexUniformBufferCount; - Uint32 vertexStorageBufferCount; - Uint32 vertexStorageTextureCount; - - Uint32 fragmentSamplerCount; - Uint32 fragmentUniformBufferCount; - Uint32 fragmentStorageBufferCount; - Uint32 fragmentStorageTextureCount; - SDL_AtomicInt referenceCount; }; @@ -1023,16 +1136,11 @@ typedef struct D3D12ComputeRootSignature struct D3D12ComputePipeline { + ComputePipelineCommonHeader header; + ID3D12PipelineState *pipelineState; D3D12ComputeRootSignature *rootSignature; - Uint32 numSamplers; - Uint32 numReadOnlyStorageTextures; - Uint32 numReadOnlyStorageBuffers; - Uint32 numReadWriteStorageTextures; - Uint32 numReadWriteStorageBuffers; - Uint32 numUniformBuffers; - SDL_AtomicInt referenceCount; }; @@ -1086,7 +1194,6 @@ struct D3D12UniformBuffer D3D12Buffer *buffer; Uint32 writeOffset; Uint32 drawOffset; - Uint32 currentBlockSize; }; // Forward function declarations @@ -1333,6 +1440,8 @@ static void D3D12_INTERNAL_ReleaseTextureContainer( container->textures[i]); } + SDL_DestroyProperties(container->header.info.props); + // Containers are just client handles, so we can destroy immediately if (container->debugName) { SDL_free(container->debugName); @@ -2028,7 +2137,7 @@ static void D3D12_InsertDebugLabel( d3d12CommandBuffer->graphicsCommandList, 0, wchar_text, - (UINT)SDL_wcslen(wchar_text)); + (UINT)SDL_wcslen(wchar_text) * sizeof(WCHAR)); SDL_free(wchar_text); } @@ -2044,7 +2153,7 @@ static void D3D12_PushDebugGroup( d3d12CommandBuffer->graphicsCommandList, 0, wchar_text, - (UINT)SDL_wcslen(wchar_text)); + (UINT)SDL_wcslen(wchar_text) * sizeof(WCHAR)); SDL_free(wchar_text); } @@ -2777,12 +2886,12 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( computePipeline->pipelineState = pipelineState; computePipeline->rootSignature = rootSignature; - computePipeline->numSamplers = createinfo->num_samplers; - computePipeline->numReadOnlyStorageTextures = createinfo->num_readonly_storage_textures; - computePipeline->numReadOnlyStorageBuffers = createinfo->num_readonly_storage_buffers; - computePipeline->numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures; - computePipeline->numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers; - computePipeline->numUniformBuffers = createinfo->num_uniform_buffers; + computePipeline->header.numSamplers = createinfo->num_samplers; + computePipeline->header.numReadonlyStorageTextures = createinfo->num_readonly_storage_textures; + computePipeline->header.numReadonlyStorageBuffers = createinfo->num_readonly_storage_buffers; + computePipeline->header.numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures; + computePipeline->header.numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers; + computePipeline->header.numUniformBuffers = createinfo->num_uniform_buffers; SDL_SetAtomicInt(&computePipeline->referenceCount, 0); if (renderer->debug_mode && SDL_HasProperty(createinfo->props, SDL_PROP_GPU_COMPUTEPIPELINE_CREATE_NAME_STRING)) { @@ -2928,7 +3037,7 @@ static bool D3D12_INTERNAL_ConvertVertexInputState(SDL_GPUVertexInputState verte desc[i].AlignedByteOffset = attribute.offset; desc[i].InputSlotClass = SDLToD3D12_InputRate[vertexInputState.vertex_buffer_descriptions[attribute.buffer_slot].input_rate]; desc[i].InstanceDataStepRate = (vertexInputState.vertex_buffer_descriptions[attribute.buffer_slot].input_rate == SDL_GPU_VERTEXINPUTRATE_INSTANCE) - ? vertexInputState.vertex_buffer_descriptions[attribute.buffer_slot].instance_step_rate + ? 1 : 0; } @@ -3010,15 +3119,13 @@ static SDL_GPUGraphicsPipeline *D3D12_CreateGraphicsPipeline( return NULL; } - Uint32 sampleMask = createinfo->multisample_state.enable_mask ? - createinfo->multisample_state.sample_mask : - 0xFFFFFFFF; - - psoDesc.SampleMask = sampleMask; + psoDesc.SampleMask = 0xFFFFFFFF; psoDesc.SampleDesc.Count = SDLToD3D12_SampleCount[createinfo->multisample_state.sample_count]; psoDesc.SampleDesc.Quality = (createinfo->multisample_state.sample_count > SDL_GPU_SAMPLECOUNT_1) ? D3D12_STANDARD_MULTISAMPLE_PATTERN : 0; - psoDesc.DSVFormat = SDLToD3D12_DepthFormat[createinfo->target_info.depth_stencil_format]; + if (createinfo->target_info.has_depth_stencil_target) { + psoDesc.DSVFormat = SDLToD3D12_DepthFormat[createinfo->target_info.depth_stencil_format]; + } psoDesc.NumRenderTargets = createinfo->target_info.num_color_targets; for (uint32_t i = 0; i < createinfo->target_info.num_color_targets; i += 1) { psoDesc.RTVFormats[i] = SDLToD3D12_TextureFormat[createinfo->target_info.color_target_descriptions[i].format]; @@ -3065,15 +3172,15 @@ static SDL_GPUGraphicsPipeline *D3D12_CreateGraphicsPipeline( pipeline->primitiveType = createinfo->primitive_type; - pipeline->vertexSamplerCount = vertShader->num_samplers; - pipeline->vertexStorageTextureCount = vertShader->numStorageTextures; - pipeline->vertexStorageBufferCount = vertShader->numStorageBuffers; - pipeline->vertexUniformBufferCount = vertShader->numUniformBuffers; + pipeline->header.num_vertex_samplers = vertShader->num_samplers; + pipeline->header.num_vertex_storage_textures = vertShader->numStorageTextures; + pipeline->header.num_vertex_storage_buffers = vertShader->numStorageBuffers; + pipeline->header.num_vertex_uniform_buffers = vertShader->numUniformBuffers; - pipeline->fragmentSamplerCount = fragShader->num_samplers; - pipeline->fragmentStorageTextureCount = fragShader->numStorageTextures; - pipeline->fragmentStorageBufferCount = fragShader->numStorageBuffers; - pipeline->fragmentUniformBufferCount = fragShader->numUniformBuffers; + pipeline->header.num_fragment_samplers = fragShader->num_samplers; + pipeline->header.num_fragment_storage_textures = fragShader->numStorageTextures; + pipeline->header.num_fragment_storage_buffers = fragShader->numStorageBuffers; + pipeline->header.num_fragment_uniform_buffers = fragShader->numUniformBuffers; SDL_SetAtomicInt(&pipeline->referenceCount, 0); @@ -3190,6 +3297,10 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( D3D12_CLEAR_VALUE clearValue; DXGI_FORMAT format; bool useClearValue = false; + bool needsSRV = + (createinfo->usage & SDL_GPU_TEXTUREUSAGE_SAMPLER) || + (createinfo->usage & SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ) || + (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ); bool needsUAV = (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE) || (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE); @@ -3209,6 +3320,7 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( if (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COLOR_TARGET) { resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; useClearValue = true; + clearValue.Format = format; clearValue.Color[0] = SDL_GetFloatProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_R_FLOAT, 0); clearValue.Color[1] = SDL_GetFloatProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_G_FLOAT, 0); clearValue.Color[2] = SDL_GetFloatProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_B_FLOAT, 0); @@ -3218,9 +3330,10 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( if (createinfo->usage & SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET) { resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; useClearValue = true; + clearValue.Format = SDLToD3D12_DepthFormat[createinfo->format]; clearValue.DepthStencil.Depth = SDL_GetFloatProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT, 0); - clearValue.DepthStencil.Stencil = (UINT8)SDL_GetNumberProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_UINT8, 0); - format = SDLToD3D12_DepthFormat[createinfo->format]; + clearValue.DepthStencil.Stencil = (UINT8)SDL_GetNumberProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_NUMBER, 0); + format = needsSRV ? SDLToD3D12_TypelessFormat[createinfo->format] : SDLToD3D12_DepthFormat[createinfo->format]; } if (needsUAV) { @@ -3237,7 +3350,7 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( if (createinfo->type != SDL_GPU_TEXTURETYPE_3D) { desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; - desc.Alignment = isSwapchainTexture ? 0 : D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; + desc.Alignment = isSwapchainTexture ? 0 : isMultisample ? D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT : D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; desc.Width = createinfo->width; desc.Height = createinfo->height; desc.DepthOrArraySize = (UINT16)createinfo->layer_count_or_depth; @@ -3262,7 +3375,6 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( } initialState = isSwapchainTexture ? D3D12_RESOURCE_STATE_PRESENT : D3D12_INTERNAL_DefaultTextureResourceState(createinfo->usage); - clearValue.Format = desc.Format; res = ID3D12Device_CreateCommittedResource( renderer->device, @@ -3282,9 +3394,7 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( texture->resource = handle; // Create the SRV if applicable - if ((createinfo->usage & SDL_GPU_TEXTUREUSAGE_SAMPLER) || - (createinfo->usage & SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ) || - (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ)) { + if (needsSRV) { D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc; D3D12_INTERNAL_AssignStagingDescriptorHandle( @@ -3485,7 +3595,9 @@ static SDL_GPUTexture *D3D12_CreateTexture( // Copy properties so we don't lose information when the client destroys them container->header.info = *createinfo; container->header.info.props = SDL_CreateProperties(); - SDL_CopyProperties(createinfo->props, container->header.info.props); + if (createinfo->props) { + SDL_CopyProperties(createinfo->props, container->header.info.props); + } container->textureCapacity = 1; container->textureCount = 1; @@ -4385,7 +4497,6 @@ static D3D12UniformBuffer *D3D12_INTERNAL_AcquireUniformBufferFromPool( SDL_UnlockMutex(renderer->acquireUniformBufferLock); - uniformBuffer->currentBlockSize = 0; uniformBuffer->drawOffset = 0; uniformBuffer->writeOffset = 0; @@ -4424,6 +4535,7 @@ static void D3D12_INTERNAL_PushUniformData( Uint32 length) { D3D12UniformBuffer *uniformBuffer; + Uint32 blockSize; if (shaderStage == SDL_GPU_SHADERSTAGE_VERTEX) { if (commandBuffer->vertexUniformBuffers[slotIndex] == NULL) { @@ -4448,13 +4560,13 @@ static void D3D12_INTERNAL_PushUniformData( return; } - uniformBuffer->currentBlockSize = + blockSize = D3D12_INTERNAL_Align( length, 256); // If there is no more room, acquire a new uniform buffer - if (uniformBuffer->writeOffset + uniformBuffer->currentBlockSize >= UNIFORM_BUFFER_SIZE) { + if (uniformBuffer->writeOffset + blockSize >= UNIFORM_BUFFER_SIZE) { ID3D12Resource_Unmap( uniformBuffer->buffer->handle, 0, @@ -4484,7 +4596,7 @@ static void D3D12_INTERNAL_PushUniformData( data, length); - uniformBuffer->writeOffset += uniformBuffer->currentBlockSize; + uniformBuffer->writeOffset += blockSize; if (shaderStage == SDL_GPU_SHADERSTAGE_VERTEX) { commandBuffer->needVertexUniformBufferBind[slotIndex] = true; @@ -4525,14 +4637,14 @@ static void D3D12_BindGraphicsPipeline( d3d12CommandBuffer->needFragmentUniformBufferBind[i] = true; } - for (i = 0; i < pipeline->vertexUniformBufferCount; i += 1) { + for (i = 0; i < pipeline->header.num_vertex_uniform_buffers; i += 1) { if (d3d12CommandBuffer->vertexUniformBuffers[i] == NULL) { d3d12CommandBuffer->vertexUniformBuffers[i] = D3D12_INTERNAL_AcquireUniformBufferFromPool( d3d12CommandBuffer); } } - for (i = 0; i < pipeline->fragmentUniformBufferCount; i += 1) { + for (i = 0; i < pipeline->header.num_fragment_uniform_buffers; i += 1) { if (d3d12CommandBuffer->fragmentUniformBuffers[i] == NULL) { d3d12CommandBuffer->fragmentUniformBuffers[i] = D3D12_INTERNAL_AcquireUniformBufferFromPool( d3d12CommandBuffer); @@ -4599,21 +4711,21 @@ static void D3D12_BindVertexSamplers( D3D12TextureContainer *container = (D3D12TextureContainer *)textureSamplerBindings[i].texture; D3D12Sampler *sampler = (D3D12Sampler *)textureSamplerBindings[i].sampler; - if (d3d12CommandBuffer->vertexSamplers[firstSlot + i] != sampler) { + if (d3d12CommandBuffer->vertexSamplerDescriptorHandles[firstSlot + i].ptr != sampler->handle.cpuHandle.ptr) { D3D12_INTERNAL_TrackSampler( d3d12CommandBuffer, sampler); - d3d12CommandBuffer->vertexSamplers[firstSlot + i] = sampler; + d3d12CommandBuffer->vertexSamplerDescriptorHandles[firstSlot + i] = sampler->handle.cpuHandle; d3d12CommandBuffer->needVertexSamplerBind = true; } - if (d3d12CommandBuffer->vertexSamplerTextures[firstSlot + i] != container->activeTexture) { + if (d3d12CommandBuffer->vertexSamplerTextureDescriptorHandles[firstSlot + i].ptr != container->activeTexture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture( d3d12CommandBuffer, container->activeTexture); - d3d12CommandBuffer->vertexSamplerTextures[firstSlot + i] = container->activeTexture; + d3d12CommandBuffer->vertexSamplerTextureDescriptorHandles[firstSlot + i] = container->activeTexture->srvHandle.cpuHandle; d3d12CommandBuffer->needVertexSamplerBind = true; } } @@ -4631,10 +4743,10 @@ static void D3D12_BindVertexStorageTextures( D3D12TextureContainer *container = (D3D12TextureContainer *)storageTextures[i]; D3D12Texture *texture = container->activeTexture; - if (d3d12CommandBuffer->vertexStorageTextures[firstSlot + i] != texture) { + if (d3d12CommandBuffer->vertexStorageTextureDescriptorHandles[firstSlot + i].ptr != texture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture(d3d12CommandBuffer, texture); - d3d12CommandBuffer->vertexStorageTextures[firstSlot + i] = texture; + d3d12CommandBuffer->vertexStorageTextureDescriptorHandles[firstSlot + i] = texture->srvHandle.cpuHandle; d3d12CommandBuffer->needVertexStorageTextureBind = true; } } @@ -4650,12 +4762,12 @@ static void D3D12_BindVertexStorageBuffers( for (Uint32 i = 0; i < numBindings; i += 1) { D3D12BufferContainer *container = (D3D12BufferContainer *)storageBuffers[i]; - if (d3d12CommandBuffer->vertexStorageBuffers[firstSlot + i] != container->activeBuffer) { + if (d3d12CommandBuffer->vertexStorageBufferDescriptorHandles[firstSlot + i].ptr != container->activeBuffer->srvDescriptor.cpuHandle.ptr) { D3D12_INTERNAL_TrackBuffer( d3d12CommandBuffer, container->activeBuffer); - d3d12CommandBuffer->vertexStorageBuffers[firstSlot + i] = container->activeBuffer; + d3d12CommandBuffer->vertexStorageBufferDescriptorHandles[firstSlot + i] = container->activeBuffer->srvDescriptor.cpuHandle; d3d12CommandBuffer->needVertexStorageBufferBind = true; } } @@ -4673,21 +4785,21 @@ static void D3D12_BindFragmentSamplers( D3D12TextureContainer *container = (D3D12TextureContainer *)textureSamplerBindings[i].texture; D3D12Sampler *sampler = (D3D12Sampler *)textureSamplerBindings[i].sampler; - if (d3d12CommandBuffer->fragmentSamplers[firstSlot + i] != sampler) { + if (d3d12CommandBuffer->fragmentSamplerDescriptorHandles[firstSlot + i].ptr != sampler->handle.cpuHandle.ptr) { D3D12_INTERNAL_TrackSampler( d3d12CommandBuffer, sampler); - d3d12CommandBuffer->fragmentSamplers[firstSlot + i] = sampler; + d3d12CommandBuffer->fragmentSamplerDescriptorHandles[firstSlot + i] = sampler->handle.cpuHandle; d3d12CommandBuffer->needFragmentSamplerBind = true; } - if (d3d12CommandBuffer->fragmentSamplerTextures[firstSlot + i] != container->activeTexture) { + if (d3d12CommandBuffer->fragmentSamplerTextureDescriptorHandles[firstSlot + i].ptr != container->activeTexture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture( d3d12CommandBuffer, container->activeTexture); - d3d12CommandBuffer->fragmentSamplerTextures[firstSlot + i] = container->activeTexture; + d3d12CommandBuffer->fragmentSamplerTextureDescriptorHandles[firstSlot + i] = container->activeTexture->srvHandle.cpuHandle; d3d12CommandBuffer->needFragmentSamplerBind = true; } } @@ -4705,10 +4817,10 @@ static void D3D12_BindFragmentStorageTextures( D3D12TextureContainer *container = (D3D12TextureContainer *)storageTextures[i]; D3D12Texture *texture = container->activeTexture; - if (d3d12CommandBuffer->fragmentStorageTextures[firstSlot + i] != texture) { + if (d3d12CommandBuffer->fragmentStorageTextureDescriptorHandles[firstSlot + i].ptr != texture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture(d3d12CommandBuffer, texture); - d3d12CommandBuffer->fragmentStorageTextures[firstSlot + i] = texture; + d3d12CommandBuffer->fragmentStorageTextureDescriptorHandles[firstSlot + i] = texture->srvHandle.cpuHandle; d3d12CommandBuffer->needFragmentStorageTextureBind = true; } } @@ -4725,12 +4837,12 @@ static void D3D12_BindFragmentStorageBuffers( for (Uint32 i = 0; i < numBindings; i += 1) { D3D12BufferContainer *container = (D3D12BufferContainer *)storageBuffers[i]; - if (d3d12CommandBuffer->fragmentStorageBuffers[firstSlot + i] != container->activeBuffer) { + if (d3d12CommandBuffer->fragmentStorageBufferDescriptorHandles[firstSlot + i].ptr != container->activeBuffer->srvDescriptor.cpuHandle.ptr) { D3D12_INTERNAL_TrackBuffer( d3d12CommandBuffer, container->activeBuffer); - d3d12CommandBuffer->fragmentStorageBuffers[firstSlot + i] = container->activeBuffer; + d3d12CommandBuffer->fragmentStorageBufferDescriptorHandles[firstSlot + i] = container->activeBuffer->srvDescriptor.cpuHandle; d3d12CommandBuffer->needFragmentStorageBufferBind = true; } } @@ -4811,15 +4923,19 @@ static void D3D12_INTERNAL_WriteGPUDescriptors( gpuBaseDescriptor->ptr = heap->descriptorHeapGPUStart.ptr + (heap->currentDescriptorIndex * heap->descriptorSize); for (Uint32 i = 0; i < resourceHandleCount; i += 1) { - ID3D12Device_CopyDescriptorsSimple( - commandBuffer->renderer->device, - 1, - gpuHeapCpuHandle, - resourceDescriptorHandles[i], - heapType); + // This will crash the driver if it gets a null handle! Cool! + if (resourceDescriptorHandles[i].ptr != 0) + { + ID3D12Device_CopyDescriptorsSimple( + commandBuffer->renderer->device, + 1, + gpuHeapCpuHandle, + resourceDescriptorHandles[i], + heapType); - heap->currentDescriptorIndex += 1; - gpuHeapCpuHandle.ptr += heap->descriptorSize; + heap->currentDescriptorIndex += 1; + gpuHeapCpuHandle.ptr += heap->descriptorSize; + } } } @@ -4849,19 +4965,21 @@ static void D3D12_INTERNAL_BindGraphicsResources( 0, commandBuffer->vertexBufferCount, vertexBufferViews); + + commandBuffer->needVertexBufferBind = false; } if (commandBuffer->needVertexSamplerBind) { - if (graphicsPipeline->vertexSamplerCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->vertexSamplerCount; i += 1) { - cpuHandles[i] = commandBuffer->vertexSamplers[i]->handle.cpuHandle; + if (graphicsPipeline->header.num_vertex_samplers > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_samplers; i += 1) { + cpuHandles[i] = commandBuffer->vertexSamplerDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, cpuHandles, - graphicsPipeline->vertexSamplerCount, + graphicsPipeline->header.num_vertex_samplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4869,15 +4987,15 @@ static void D3D12_INTERNAL_BindGraphicsResources( graphicsPipeline->rootSignature->vertexSamplerRootIndex, gpuDescriptorHandle); - for (Uint32 i = 0; i < graphicsPipeline->vertexSamplerCount; i += 1) { - cpuHandles[i] = commandBuffer->vertexSamplerTextures[i]->srvHandle.cpuHandle; + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_samplers; i += 1) { + cpuHandles[i] = commandBuffer->vertexSamplerTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->vertexSamplerCount, + graphicsPipeline->header.num_vertex_samplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4889,16 +5007,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needVertexStorageTextureBind) { - if (graphicsPipeline->vertexStorageTextureCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->vertexStorageTextureCount; i += 1) { - cpuHandles[i] = commandBuffer->vertexStorageTextures[i]->srvHandle.cpuHandle; + if (graphicsPipeline->header.num_vertex_storage_textures > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_storage_textures; i += 1) { + cpuHandles[i] = commandBuffer->vertexStorageTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->vertexStorageTextureCount, + graphicsPipeline->header.num_vertex_storage_textures, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4910,16 +5028,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needVertexStorageBufferBind) { - if (graphicsPipeline->vertexStorageBufferCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->vertexStorageBufferCount; i += 1) { - cpuHandles[i] = commandBuffer->vertexStorageBuffers[i]->srvDescriptor.cpuHandle; + if (graphicsPipeline->header.num_vertex_storage_buffers > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_storage_buffers; i += 1) { + cpuHandles[i] = commandBuffer->vertexStorageBufferDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->vertexStorageBufferCount, + graphicsPipeline->header.num_vertex_storage_buffers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4932,7 +5050,7 @@ static void D3D12_INTERNAL_BindGraphicsResources( for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) { if (commandBuffer->needVertexUniformBufferBind[i]) { - if (graphicsPipeline->vertexUniformBufferCount > i) { + if (graphicsPipeline->header.num_vertex_uniform_buffers > i) { ID3D12GraphicsCommandList_SetGraphicsRootConstantBufferView( commandBuffer->graphicsCommandList, graphicsPipeline->rootSignature->vertexUniformBufferRootIndex[i], @@ -4943,16 +5061,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needFragmentSamplerBind) { - if (graphicsPipeline->fragmentSamplerCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->fragmentSamplerCount; i += 1) { - cpuHandles[i] = commandBuffer->fragmentSamplers[i]->handle.cpuHandle; + if (graphicsPipeline->header.num_fragment_samplers > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_samplers; i += 1) { + cpuHandles[i] = commandBuffer->fragmentSamplerDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, cpuHandles, - graphicsPipeline->fragmentSamplerCount, + graphicsPipeline->header.num_fragment_samplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4960,15 +5078,15 @@ static void D3D12_INTERNAL_BindGraphicsResources( graphicsPipeline->rootSignature->fragmentSamplerRootIndex, gpuDescriptorHandle); - for (Uint32 i = 0; i < graphicsPipeline->fragmentSamplerCount; i += 1) { - cpuHandles[i] = commandBuffer->fragmentSamplerTextures[i]->srvHandle.cpuHandle; + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_samplers; i += 1) { + cpuHandles[i] = commandBuffer->fragmentSamplerTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->fragmentSamplerCount, + graphicsPipeline->header.num_fragment_samplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4980,16 +5098,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needFragmentStorageTextureBind) { - if (graphicsPipeline->fragmentStorageTextureCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->fragmentStorageTextureCount; i += 1) { - cpuHandles[i] = commandBuffer->fragmentStorageTextures[i]->srvHandle.cpuHandle; + if (graphicsPipeline->header.num_fragment_storage_textures > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_storage_textures; i += 1) { + cpuHandles[i] = commandBuffer->fragmentStorageTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->fragmentStorageTextureCount, + graphicsPipeline->header.num_fragment_storage_textures, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -5001,16 +5119,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needFragmentStorageBufferBind) { - if (graphicsPipeline->fragmentStorageBufferCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->fragmentStorageBufferCount; i += 1) { - cpuHandles[i] = commandBuffer->fragmentStorageBuffers[i]->srvDescriptor.cpuHandle; + if (graphicsPipeline->header.num_fragment_storage_buffers > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_storage_buffers; i += 1) { + cpuHandles[i] = commandBuffer->fragmentStorageBufferDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->fragmentStorageBufferCount, + graphicsPipeline->header.num_fragment_storage_buffers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -5023,7 +5141,7 @@ static void D3D12_INTERNAL_BindGraphicsResources( for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) { if (commandBuffer->needFragmentUniformBufferBind[i]) { - if (graphicsPipeline->fragmentUniformBufferCount > i) { + if (graphicsPipeline->header.num_fragment_uniform_buffers > i) { ID3D12GraphicsCommandList_SetGraphicsRootConstantBufferView( commandBuffer->graphicsCommandList, graphicsPipeline->rootSignature->fragmentUniformBufferRootIndex[i], @@ -5188,15 +5306,15 @@ static void D3D12_EndRenderPass( SDL_zeroa(d3d12CommandBuffer->vertexBufferOffsets); d3d12CommandBuffer->vertexBufferCount = 0; - SDL_zeroa(d3d12CommandBuffer->vertexSamplerTextures); - SDL_zeroa(d3d12CommandBuffer->vertexSamplers); - SDL_zeroa(d3d12CommandBuffer->vertexStorageTextures); - SDL_zeroa(d3d12CommandBuffer->vertexStorageBuffers); + SDL_zeroa(d3d12CommandBuffer->vertexSamplerTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->vertexSamplerDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->vertexStorageTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->vertexStorageBufferDescriptorHandles); - SDL_zeroa(d3d12CommandBuffer->fragmentSamplerTextures); - SDL_zeroa(d3d12CommandBuffer->fragmentSamplers); - SDL_zeroa(d3d12CommandBuffer->fragmentStorageTextures); - SDL_zeroa(d3d12CommandBuffer->fragmentStorageBuffers); + SDL_zeroa(d3d12CommandBuffer->fragmentSamplerTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->fragmentSamplerDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->fragmentStorageTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->fragmentStorageBufferDescriptorHandles); } // Compute Pass @@ -5230,6 +5348,7 @@ static void D3D12_BeginComputePass( D3D12_RESOURCE_STATE_UNORDERED_ACCESS); d3d12CommandBuffer->computeReadWriteStorageTextureSubresources[i] = subresource; + d3d12CommandBuffer->computeReadWriteStorageTextureDescriptorHandles[i] = subresource->uavHandle.cpuHandle; D3D12_INTERNAL_TrackTexture( d3d12CommandBuffer, @@ -5248,6 +5367,7 @@ static void D3D12_BeginComputePass( D3D12_RESOURCE_STATE_UNORDERED_ACCESS); d3d12CommandBuffer->computeReadWriteStorageBuffers[i] = buffer; + d3d12CommandBuffer->computeReadWriteStorageBufferDescriptorHandles[i] = buffer->uavDescriptor.cpuHandle; D3D12_INTERNAL_TrackBuffer( d3d12CommandBuffer, @@ -5289,7 +5409,7 @@ static void D3D12_BindComputePipeline( d3d12CommandBuffer->needComputeUniformBufferBind[i] = true; } - for (Uint32 i = 0; i < pipeline->numUniformBuffers; i += 1) { + for (Uint32 i = 0; i < pipeline->header.numUniformBuffers; i += 1) { if (d3d12CommandBuffer->computeUniformBuffers[i] == NULL) { d3d12CommandBuffer->computeUniformBuffers[i] = D3D12_INTERNAL_AcquireUniformBufferFromPool( d3d12CommandBuffer); @@ -5299,9 +5419,9 @@ static void D3D12_BindComputePipeline( D3D12_INTERNAL_TrackComputePipeline(d3d12CommandBuffer, pipeline); // Bind write-only resources after setting root signature - if (pipeline->numReadWriteStorageTextures > 0) { - for (Uint32 i = 0; i < pipeline->numReadWriteStorageTextures; i += 1) { - cpuHandles[i] = d3d12CommandBuffer->computeReadWriteStorageTextureSubresources[i]->uavHandle.cpuHandle; + if (pipeline->header.numReadWriteStorageTextures > 0) { + for (Uint32 i = 0; i < pipeline->header.numReadWriteStorageTextures; i += 1) { + cpuHandles[i] = d3d12CommandBuffer->computeReadWriteStorageTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( @@ -5317,9 +5437,9 @@ static void D3D12_BindComputePipeline( gpuDescriptorHandle); } - if (pipeline->numReadWriteStorageBuffers > 0) { - for (Uint32 i = 0; i < pipeline->numReadWriteStorageBuffers; i += 1) { - cpuHandles[i] = d3d12CommandBuffer->computeReadWriteStorageBuffers[i]->uavDescriptor.cpuHandle; + if (pipeline->header.numReadWriteStorageBuffers > 0) { + for (Uint32 i = 0; i < pipeline->header.numReadWriteStorageBuffers; i += 1) { + cpuHandles[i] = d3d12CommandBuffer->computeReadWriteStorageBufferDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( @@ -5348,21 +5468,21 @@ static void D3D12_BindComputeSamplers( D3D12TextureContainer *container = (D3D12TextureContainer *)textureSamplerBindings[i].texture; D3D12Sampler *sampler = (D3D12Sampler *)textureSamplerBindings[i].sampler; - if (d3d12CommandBuffer->computeSamplers[firstSlot + i] != sampler) { + if (d3d12CommandBuffer->computeSamplerDescriptorHandles[firstSlot + i].ptr != sampler->handle.cpuHandle.ptr) { D3D12_INTERNAL_TrackSampler( d3d12CommandBuffer, (D3D12Sampler *)textureSamplerBindings[i].sampler); - d3d12CommandBuffer->computeSamplers[firstSlot + i] = (D3D12Sampler *)textureSamplerBindings[i].sampler; + d3d12CommandBuffer->computeSamplerDescriptorHandles[firstSlot + i] = sampler->handle.cpuHandle; d3d12CommandBuffer->needComputeSamplerBind = true; } - if (d3d12CommandBuffer->computeSamplerTextures[firstSlot + i] != container->activeTexture) { + if (d3d12CommandBuffer->computeSamplerTextureDescriptorHandles[firstSlot + i].ptr != container->activeTexture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture( d3d12CommandBuffer, container->activeTexture); - d3d12CommandBuffer->computeSamplerTextures[firstSlot + i] = container->activeTexture; + d3d12CommandBuffer->computeSamplerTextureDescriptorHandles[firstSlot + i] = container->activeTexture->srvHandle.cpuHandle; d3d12CommandBuffer->needComputeSamplerBind = true; } } @@ -5399,6 +5519,7 @@ static void D3D12_BindComputeStorageTextures( container->activeTexture); d3d12CommandBuffer->computeReadOnlyStorageTextures[firstSlot + i] = container->activeTexture; + d3d12CommandBuffer->computeReadOnlyStorageTextureDescriptorHandles[firstSlot + i] = container->activeTexture->srvHandle.cpuHandle; d3d12CommandBuffer->needComputeReadOnlyStorageTextureBind = true; } } @@ -5436,6 +5557,7 @@ static void D3D12_BindComputeStorageBuffers( buffer); d3d12CommandBuffer->computeReadOnlyStorageBuffers[firstSlot + i] = buffer; + d3d12CommandBuffer->computeReadOnlyStorageBufferDescriptorHandles[firstSlot + i] = buffer->srvDescriptor.cpuHandle; d3d12CommandBuffer->needComputeReadOnlyStorageBufferBind = true; } } @@ -5471,16 +5593,16 @@ static void D3D12_INTERNAL_BindComputeResources( D3D12_GPU_DESCRIPTOR_HANDLE gpuDescriptorHandle; if (commandBuffer->needComputeSamplerBind) { - if (computePipeline->numSamplers > 0) { - for (Uint32 i = 0; i < computePipeline->numSamplers; i += 1) { - cpuHandles[i] = commandBuffer->computeSamplers[i]->handle.cpuHandle; + if (computePipeline->header.numSamplers > 0) { + for (Uint32 i = 0; i < computePipeline->header.numSamplers; i += 1) { + cpuHandles[i] = commandBuffer->computeSamplerDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, cpuHandles, - computePipeline->numSamplers, + computePipeline->header.numSamplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetComputeRootDescriptorTable( @@ -5488,15 +5610,15 @@ static void D3D12_INTERNAL_BindComputeResources( computePipeline->rootSignature->samplerRootIndex, gpuDescriptorHandle); - for (Uint32 i = 0; i < computePipeline->numSamplers; i += 1) { - cpuHandles[i] = commandBuffer->computeSamplerTextures[i]->srvHandle.cpuHandle; + for (Uint32 i = 0; i < computePipeline->header.numSamplers; i += 1) { + cpuHandles[i] = commandBuffer->computeSamplerTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - computePipeline->numSamplers, + computePipeline->header.numSamplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetComputeRootDescriptorTable( @@ -5508,16 +5630,16 @@ static void D3D12_INTERNAL_BindComputeResources( } if (commandBuffer->needComputeReadOnlyStorageTextureBind) { - if (computePipeline->numReadOnlyStorageTextures > 0) { - for (Uint32 i = 0; i < computePipeline->numReadOnlyStorageTextures; i += 1) { - cpuHandles[i] = commandBuffer->computeReadOnlyStorageTextures[i]->srvHandle.cpuHandle; + if (computePipeline->header.numReadonlyStorageTextures > 0) { + for (Uint32 i = 0; i < computePipeline->header.numReadonlyStorageTextures; i += 1) { + cpuHandles[i] = commandBuffer->computeReadOnlyStorageTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - computePipeline->numReadOnlyStorageTextures, + computePipeline->header.numReadonlyStorageTextures, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetComputeRootDescriptorTable( @@ -5529,16 +5651,16 @@ static void D3D12_INTERNAL_BindComputeResources( } if (commandBuffer->needComputeReadOnlyStorageBufferBind) { - if (computePipeline->numReadOnlyStorageBuffers > 0) { - for (Uint32 i = 0; i < computePipeline->numReadOnlyStorageBuffers; i += 1) { - cpuHandles[i] = commandBuffer->computeReadOnlyStorageBuffers[i]->srvDescriptor.cpuHandle; + if (computePipeline->header.numReadonlyStorageBuffers > 0) { + for (Uint32 i = 0; i < computePipeline->header.numReadonlyStorageBuffers; i += 1) { + cpuHandles[i] = commandBuffer->computeReadOnlyStorageBufferDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - computePipeline->numReadOnlyStorageBuffers, + computePipeline->header.numReadonlyStorageBuffers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetComputeRootDescriptorTable( @@ -5551,7 +5673,7 @@ static void D3D12_INTERNAL_BindComputeResources( for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) { if (commandBuffer->needComputeUniformBufferBind[i]) { - if (computePipeline->numUniformBuffers > i) { + if (computePipeline->header.numUniformBuffers > i) { ID3D12GraphicsCommandList_SetComputeRootConstantBufferView( commandBuffer->graphicsCommandList, computePipeline->rootSignature->uniformBufferRootIndex[i], @@ -5650,8 +5772,11 @@ static void D3D12_EndComputePass( } } - SDL_zeroa(d3d12CommandBuffer->computeSamplerTextures); - SDL_zeroa(d3d12CommandBuffer->computeSamplers); + SDL_zeroa(d3d12CommandBuffer->computeSamplerTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->computeSamplerDescriptorHandles); + + SDL_zeroa(d3d12CommandBuffer->computeReadWriteStorageTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->computeReadWriteStorageBufferDescriptorHandles); d3d12CommandBuffer->currentComputePipeline = NULL; } @@ -6737,7 +6862,7 @@ static bool D3D12_INTERNAL_CreateSwapchain( swapchainDesc.SampleDesc.Quality = 0; swapchainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapchainDesc.BufferCount = windowData->swapchainTextureCount; - swapchainDesc.Scaling = DXGI_SCALING_STRETCH; + swapchainDesc.Scaling = DXGI_SCALING_NONE; swapchainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; swapchainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; swapchainDesc.Flags = 0; @@ -7240,20 +7365,22 @@ static SDL_GPUCommandBuffer *D3D12_AcquireCommandBuffer( SDL_zeroa(commandBuffer->vertexBufferOffsets); commandBuffer->vertexBufferCount = 0; - SDL_zeroa(commandBuffer->vertexSamplerTextures); - SDL_zeroa(commandBuffer->vertexSamplers); - SDL_zeroa(commandBuffer->vertexStorageTextures); - SDL_zeroa(commandBuffer->vertexStorageBuffers); + SDL_zeroa(commandBuffer->vertexSamplerTextureDescriptorHandles); + SDL_zeroa(commandBuffer->vertexSamplerDescriptorHandles); + SDL_zeroa(commandBuffer->vertexStorageTextureDescriptorHandles); + SDL_zeroa(commandBuffer->vertexStorageBufferDescriptorHandles); SDL_zeroa(commandBuffer->vertexUniformBuffers); - SDL_zeroa(commandBuffer->fragmentSamplerTextures); - SDL_zeroa(commandBuffer->fragmentSamplers); - SDL_zeroa(commandBuffer->fragmentStorageTextures); - SDL_zeroa(commandBuffer->fragmentStorageBuffers); + SDL_zeroa(commandBuffer->fragmentSamplerTextureDescriptorHandles); + SDL_zeroa(commandBuffer->fragmentSamplerDescriptorHandles); + SDL_zeroa(commandBuffer->fragmentStorageTextureDescriptorHandles); + SDL_zeroa(commandBuffer->fragmentStorageBufferDescriptorHandles); SDL_zeroa(commandBuffer->fragmentUniformBuffers); - SDL_zeroa(commandBuffer->computeSamplerTextures); - SDL_zeroa(commandBuffer->computeSamplers); + SDL_zeroa(commandBuffer->computeSamplerTextureDescriptorHandles); + SDL_zeroa(commandBuffer->computeSamplerDescriptorHandles); + SDL_zeroa(commandBuffer->computeReadOnlyStorageTextureDescriptorHandles); + SDL_zeroa(commandBuffer->computeReadOnlyStorageBufferDescriptorHandles); SDL_zeroa(commandBuffer->computeReadOnlyStorageTextures); SDL_zeroa(commandBuffer->computeReadOnlyStorageBuffers); SDL_zeroa(commandBuffer->computeReadWriteStorageTextureSubresources); @@ -8102,8 +8229,7 @@ static void D3D12_INTERNAL_InitBlitResources( shaderCreateInfo.code = (Uint8 *)D3D12_FullscreenVert; shaderCreateInfo.code_size = sizeof(D3D12_FullscreenVert); shaderCreateInfo.stage = SDL_GPU_SHADERSTAGE_VERTEX; - shaderCreateInfo.format = SDL_GPU_SHADERFORMAT_DXBC; - shaderCreateInfo.entrypoint = "main"; + shaderCreateInfo.format = SDL_GPU_SHADERFORMAT_DXIL; renderer->blitVertexShader = D3D12_CreateShader( (SDL_GPURenderer *)renderer, @@ -8340,38 +8466,38 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) } #if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) && defined(HAVE_IDXGIINFOQUEUE) -static void D3D12_INTERNAL_TryInitializeDXGIDebug(D3D12Renderer *renderer) +static bool D3D12_INTERNAL_TryInitializeDXGIDebug(D3D12Renderer *renderer) { PFN_DXGI_GET_DEBUG_INTERFACE DXGIGetDebugInterfaceFunc; HRESULT res; renderer->dxgidebug_dll = SDL_LoadObject(DXGIDEBUG_DLL); if (renderer->dxgidebug_dll == NULL) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not find " DXGIDEBUG_DLL); - return; + return false; } DXGIGetDebugInterfaceFunc = (PFN_DXGI_GET_DEBUG_INTERFACE)SDL_LoadFunction( renderer->dxgidebug_dll, DXGI_GET_DEBUG_INTERFACE_FUNC); if (DXGIGetDebugInterfaceFunc == NULL) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not load function: " DXGI_GET_DEBUG_INTERFACE_FUNC); - return; + return false; } res = DXGIGetDebugInterfaceFunc(&D3D_IID_IDXGIDebug, (void **)&renderer->dxgiDebug); if (FAILED(res)) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not get IDXGIDebug interface"); + return false; } res = DXGIGetDebugInterfaceFunc(&D3D_IID_IDXGIInfoQueue, (void **)&renderer->dxgiInfoQueue); if (FAILED(res)) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not get IDXGIInfoQueue interface"); + return false; } + + return true; } #endif -static void D3D12_INTERNAL_TryInitializeD3D12Debug(D3D12Renderer *renderer) +static bool D3D12_INTERNAL_TryInitializeD3D12Debug(D3D12Renderer *renderer) { PFN_D3D12_GET_DEBUG_INTERFACE D3D12GetDebugInterfaceFunc; HRESULT res; @@ -8380,21 +8506,20 @@ static void D3D12_INTERNAL_TryInitializeD3D12Debug(D3D12Renderer *renderer) renderer->d3d12_dll, D3D12_GET_DEBUG_INTERFACE_FUNC); if (D3D12GetDebugInterfaceFunc == NULL) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not load function: " D3D12_GET_DEBUG_INTERFACE_FUNC); - return; + return false; } res = D3D12GetDebugInterfaceFunc(D3D_GUID(D3D_IID_ID3D12Debug), (void **)&renderer->d3d12Debug); if (FAILED(res)) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not get ID3D12Debug interface"); - return; + return false; } ID3D12Debug_EnableDebugLayer(renderer->d3d12Debug); + return true; } #if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) -static bool D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(D3D12Renderer *renderer) +static void D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(D3D12Renderer *renderer) { ID3D12InfoQueue *infoQueue = NULL; D3D12_MESSAGE_SEVERITY severities[] = { D3D12_MESSAGE_SEVERITY_INFO }; @@ -8406,7 +8531,7 @@ static bool D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(D3D12Renderer *rende D3D_GUID(D3D_IID_ID3D12InfoQueue), (void **)&infoQueue); if (FAILED(res)) { - CHECK_D3D12_ERROR_AND_RETURN("Failed to convert ID3D12Device to ID3D12InfoQueue", false); + return; } SDL_zero(filter); @@ -8422,8 +8547,6 @@ static bool D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(D3D12Renderer *rende true); ID3D12InfoQueue_Release(infoQueue); - - return true; } static void WINAPI D3D12_INTERNAL_OnD3D12DebugInfoMsg( @@ -8572,9 +8695,12 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD #ifdef HAVE_IDXGIINFOQUEUE // Initialize the DXGI debug layer, if applicable + bool hasDxgiDebug = false; if (debugMode) { - D3D12_INTERNAL_TryInitializeDXGIDebug(renderer); + hasDxgiDebug = D3D12_INTERNAL_TryInitializeDXGIDebug(renderer); } +#else + bool hasDxgiDebug = true; #endif // Load the CreateDXGIFactory1 function @@ -8707,7 +8833,27 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD // Initialize the D3D12 debug layer, if applicable if (debugMode) { - D3D12_INTERNAL_TryInitializeD3D12Debug(renderer); + bool hasD3d12Debug = D3D12_INTERNAL_TryInitializeD3D12Debug(renderer); +#if (defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + if (hasD3d12Debug) { + SDL_LogInfo( + SDL_LOG_CATEGORY_GPU, + "Validation layers enabled, expect debug level performance!"); +#else + if (hasDxgiDebug && hasD3d12Debug) { + SDL_LogInfo( + SDL_LOG_CATEGORY_GPU, + "Validation layers enabled, expect debug level performance!"); + } else if (hasDxgiDebug || hasD3d12Debug) { + SDL_LogWarn( + SDL_LOG_CATEGORY_GPU, + "Validation layers partially enabled, some warnings may not be available"); +#endif + } else { + SDL_LogWarn( + SDL_LOG_CATEGORY_GPU, + "Validation layers not found, continuing without validation"); + } } // Create the D3D12Device @@ -8754,9 +8900,7 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD // Initialize the D3D12 debug info queue, if applicable if (debugMode) { - if (!D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(renderer)) { - return NULL; - } + D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(renderer); D3D12_INTERNAL_TryInitializeD3D12DebugInfoLogger(renderer); } #endif @@ -9037,8 +9181,23 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD return NULL; } + SDL_GPUShaderFormat shaderFormats = SDL_GPU_SHADERFORMAT_DXBC; + + D3D12_FEATURE_DATA_SHADER_MODEL shaderModel; + shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_0; + + res = ID3D12Device_CheckFeatureSupport( + renderer->device, + D3D12_FEATURE_SHADER_MODEL, + &shaderModel, + sizeof(shaderModel)); + if (SUCCEEDED(res) && shaderModel.HighestShaderModel >= D3D_SHADER_MODEL_6_0) { + shaderFormats |= SDL_GPU_SHADERFORMAT_DXIL; + } + ASSIGN_DRIVER(D3D12) result->driverData = (SDL_GPURenderer *)renderer; + result->shader_formats = shaderFormats; result->debug_mode = debugMode; renderer->sdlGPUDevice = result; diff --git a/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m b/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m index 3020492..591bd05 100644 --- a/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m +++ b/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m @@ -476,35 +476,21 @@ typedef struct MetalShader typedef struct MetalGraphicsPipeline { - id handle; + GraphicsPipelineCommonHeader header; - Uint32 sample_mask; + id handle; SDL_GPURasterizerState rasterizerState; SDL_GPUPrimitiveType primitiveType; id depth_stencil_state; - - Uint32 vertexSamplerCount; - Uint32 vertexUniformBufferCount; - Uint32 vertexStorageBufferCount; - Uint32 vertexStorageTextureCount; - - Uint32 fragmentSamplerCount; - Uint32 fragmentUniformBufferCount; - Uint32 fragmentStorageBufferCount; - Uint32 fragmentStorageTextureCount; } MetalGraphicsPipeline; typedef struct MetalComputePipeline { + ComputePipelineCommonHeader header; + id handle; - Uint32 numSamplers; - Uint32 numReadonlyStorageTextures; - Uint32 numReadWriteStorageTextures; - Uint32 numReadonlyStorageBuffers; - Uint32 numReadWriteStorageBuffers; - Uint32 numUniformBuffers; Uint32 threadcountX; Uint32 threadcountY; Uint32 threadcountZ; @@ -842,6 +828,10 @@ static MetalLibraryFunction METAL_INTERNAL_CompileShader( dispatch_data_t data; id function; + if (!entrypoint) { + entrypoint = "main0"; + } + if (format == SDL_GPU_SHADERFORMAT_MSL) { NSString *codeString = [[NSString alloc] initWithBytes:code @@ -856,7 +846,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!"); @@ -898,6 +888,7 @@ static void METAL_INTERNAL_DestroyTextureContainer( container->textures[i]->handle = nil; SDL_free(container->textures[i]); } + SDL_DestroyProperties(container->header.info.props); if (container->debugName != NULL) { SDL_free(container->debugName); } @@ -1057,12 +1048,12 @@ static SDL_GPUComputePipeline *METAL_CreateComputePipeline( pipeline = SDL_calloc(1, sizeof(MetalComputePipeline)); pipeline->handle = handle; - pipeline->numSamplers = createinfo->num_samplers; - pipeline->numReadonlyStorageTextures = createinfo->num_readonly_storage_textures; - pipeline->numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures; - pipeline->numReadonlyStorageBuffers = createinfo->num_readonly_storage_buffers; - pipeline->numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers; - pipeline->numUniformBuffers = createinfo->num_uniform_buffers; + pipeline->header.numSamplers = createinfo->num_samplers; + pipeline->header.numReadonlyStorageTextures = createinfo->num_readonly_storage_textures; + pipeline->header.numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures; + pipeline->header.numReadonlyStorageBuffers = createinfo->num_readonly_storage_buffers; + pipeline->header.numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers; + pipeline->header.numUniformBuffers = createinfo->num_uniform_buffers; pipeline->threadcountX = createinfo->threadcount_x; pipeline->threadcountY = createinfo->threadcount_y; pipeline->threadcountZ = createinfo->threadcount_z; @@ -1181,9 +1172,7 @@ static SDL_GPUGraphicsPipeline *METAL_CreateGraphicsPipeline( for (Uint32 i = 0; i < createinfo->vertex_input_state.num_vertex_buffers; i += 1) { binding = METAL_FIRST_VERTEX_BUFFER_SLOT + createinfo->vertex_input_state.vertex_buffer_descriptions[i].slot; vertexDescriptor.layouts[binding].stepFunction = SDLToMetal_StepFunction[createinfo->vertex_input_state.vertex_buffer_descriptions[i].input_rate]; - vertexDescriptor.layouts[binding].stepRate = (createinfo->vertex_input_state.vertex_buffer_descriptions[i].input_rate == SDL_GPU_VERTEXINPUTRATE_INSTANCE) - ? createinfo->vertex_input_state.vertex_buffer_descriptions[i].instance_step_rate - : 1; + vertexDescriptor.layouts[binding].stepRate = 1; vertexDescriptor.layouts[binding].stride = createinfo->vertex_input_state.vertex_buffer_descriptions[i].pitch; } @@ -1202,24 +1191,19 @@ static SDL_GPUGraphicsPipeline *METAL_CreateGraphicsPipeline( SET_ERROR_AND_RETURN("Creating render pipeline failed: %s", [[error description] UTF8String], NULL); } - Uint32 sampleMask = createinfo->multisample_state.enable_mask ? - createinfo->multisample_state.sample_mask : - 0xFFFFFFFF; - result = SDL_calloc(1, sizeof(MetalGraphicsPipeline)); result->handle = pipelineState; - result->sample_mask = sampleMask; result->depth_stencil_state = depthStencilState; result->rasterizerState = createinfo->rasterizer_state; result->primitiveType = createinfo->primitive_type; - result->vertexSamplerCount = vertexShader->numSamplers; - result->vertexUniformBufferCount = vertexShader->numUniformBuffers; - result->vertexStorageBufferCount = vertexShader->numStorageBuffers; - result->vertexStorageTextureCount = vertexShader->numStorageTextures; - result->fragmentSamplerCount = fragmentShader->numSamplers; - result->fragmentUniformBufferCount = fragmentShader->numUniformBuffers; - result->fragmentStorageBufferCount = fragmentShader->numStorageBuffers; - result->fragmentStorageTextureCount = fragmentShader->numStorageTextures; + result->header.num_vertex_samplers = vertexShader->numSamplers; + result->header.num_vertex_uniform_buffers = vertexShader->numUniformBuffers; + result->header.num_vertex_storage_buffers = vertexShader->numStorageBuffers; + result->header.num_vertex_storage_textures = vertexShader->numStorageTextures; + result->header.num_fragment_samplers = fragmentShader->numSamplers; + result->header.num_fragment_uniform_buffers = fragmentShader->numUniformBuffers; + result->header.num_fragment_storage_buffers = fragmentShader->numStorageBuffers; + result->header.num_fragment_storage_textures = fragmentShader->numStorageTextures; return (SDL_GPUGraphicsPipeline *)result; } } @@ -1503,7 +1487,9 @@ static SDL_GPUTexture *METAL_CreateTexture( // Copy properties so we don't lose information when the client destroys them container->header.info = *createinfo; container->header.info.props = SDL_CreateProperties(); - SDL_CopyProperties(createinfo->props, container->header.info.props); + if (createinfo->props) { + SDL_CopyProperties(createinfo->props, container->header.info.props); + } container->activeTexture = texture; container->textureCapacity = 1; @@ -2415,14 +2401,14 @@ static void METAL_BindGraphicsPipeline( metalCommandBuffer->needFragmentUniformBufferBind[i] = true; } - for (i = 0; i < pipeline->vertexUniformBufferCount; i += 1) { + for (i = 0; i < pipeline->header.num_vertex_uniform_buffers; i += 1) { if (metalCommandBuffer->vertexUniformBuffers[i] == NULL) { metalCommandBuffer->vertexUniformBuffers[i] = METAL_INTERNAL_AcquireUniformBufferFromPool( metalCommandBuffer); } } - for (i = 0; i < pipeline->fragmentUniformBufferCount; i += 1) { + for (i = 0; i < pipeline->header.num_fragment_uniform_buffers; i += 1) { if (metalCommandBuffer->fragmentUniformBuffers[i] == NULL) { metalCommandBuffer->fragmentUniformBuffers[i] = METAL_INTERNAL_AcquireUniformBufferFromPool( metalCommandBuffer); @@ -2653,11 +2639,11 @@ static void METAL_INTERNAL_BindGraphicsResources( // Vertex Samplers+Textures if (commandBuffer->needVertexSamplerBind) { - if (graphicsPipeline->vertexSamplerCount > 0) { + if (graphicsPipeline->header.num_vertex_samplers > 0) { [commandBuffer->renderEncoder setVertexSamplerStates:commandBuffer->vertexSamplers - withRange:NSMakeRange(0, graphicsPipeline->vertexSamplerCount)]; + withRange:NSMakeRange(0, graphicsPipeline->header.num_vertex_samplers)]; [commandBuffer->renderEncoder setVertexTextures:commandBuffer->vertexTextures - withRange:NSMakeRange(0, graphicsPipeline->vertexSamplerCount)]; + withRange:NSMakeRange(0, graphicsPipeline->header.num_vertex_samplers)]; } commandBuffer->needVertexSamplerBind = false; } @@ -2665,10 +2651,10 @@ static void METAL_INTERNAL_BindGraphicsResources( // Vertex Storage Textures if (commandBuffer->needVertexStorageTextureBind) { - if (graphicsPipeline->vertexStorageTextureCount > 0) { + if (graphicsPipeline->header.num_vertex_storage_textures > 0) { [commandBuffer->renderEncoder setVertexTextures:commandBuffer->vertexStorageTextures - withRange:NSMakeRange(graphicsPipeline->vertexSamplerCount, - graphicsPipeline->vertexStorageTextureCount)]; + withRange:NSMakeRange(graphicsPipeline->header.num_vertex_samplers, + graphicsPipeline->header.num_vertex_storage_textures)]; } commandBuffer->needVertexStorageTextureBind = false; } @@ -2676,20 +2662,20 @@ static void METAL_INTERNAL_BindGraphicsResources( // Vertex Storage Buffers if (commandBuffer->needVertexStorageBufferBind) { - if (graphicsPipeline->vertexStorageBufferCount > 0) { + if (graphicsPipeline->header.num_vertex_storage_buffers > 0) { [commandBuffer->renderEncoder setVertexBuffers:commandBuffer->vertexStorageBuffers offsets:offsets - withRange:NSMakeRange(graphicsPipeline->vertexUniformBufferCount, - graphicsPipeline->vertexStorageBufferCount)]; + withRange:NSMakeRange(graphicsPipeline->header.num_vertex_uniform_buffers, + graphicsPipeline->header.num_vertex_storage_buffers)]; } commandBuffer->needVertexStorageBufferBind = false; } // Vertex Uniform Buffers - for (Uint32 i = 0; i < graphicsPipeline->vertexUniformBufferCount; i += 1) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_uniform_buffers; i += 1) { if (commandBuffer->needVertexUniformBufferBind[i]) { - if (graphicsPipeline->vertexUniformBufferCount > i) { + if (graphicsPipeline->header.num_vertex_uniform_buffers > i) { [commandBuffer->renderEncoder setVertexBuffer:commandBuffer->vertexUniformBuffers[i]->handle offset:commandBuffer->vertexUniformBuffers[i]->drawOffset @@ -2702,11 +2688,11 @@ static void METAL_INTERNAL_BindGraphicsResources( // Fragment Samplers+Textures if (commandBuffer->needFragmentSamplerBind) { - if (graphicsPipeline->fragmentSamplerCount > 0) { + if (graphicsPipeline->header.num_fragment_samplers > 0) { [commandBuffer->renderEncoder setFragmentSamplerStates:commandBuffer->fragmentSamplers - withRange:NSMakeRange(0, graphicsPipeline->fragmentSamplerCount)]; + withRange:NSMakeRange(0, graphicsPipeline->header.num_fragment_samplers)]; [commandBuffer->renderEncoder setFragmentTextures:commandBuffer->fragmentTextures - withRange:NSMakeRange(0, graphicsPipeline->fragmentSamplerCount)]; + withRange:NSMakeRange(0, graphicsPipeline->header.num_fragment_samplers)]; } commandBuffer->needFragmentSamplerBind = false; } @@ -2714,10 +2700,10 @@ static void METAL_INTERNAL_BindGraphicsResources( // Fragment Storage Textures if (commandBuffer->needFragmentStorageTextureBind) { - if (graphicsPipeline->fragmentStorageTextureCount > 0) { + if (graphicsPipeline->header.num_fragment_storage_textures > 0) { [commandBuffer->renderEncoder setFragmentTextures:commandBuffer->fragmentStorageTextures - withRange:NSMakeRange(graphicsPipeline->fragmentSamplerCount, - graphicsPipeline->fragmentStorageTextureCount)]; + withRange:NSMakeRange(graphicsPipeline->header.num_fragment_samplers, + graphicsPipeline->header.num_fragment_storage_textures)]; } commandBuffer->needFragmentStorageTextureBind = false; } @@ -2725,20 +2711,20 @@ static void METAL_INTERNAL_BindGraphicsResources( // Fragment Storage Buffers if (commandBuffer->needFragmentStorageBufferBind) { - if (graphicsPipeline->fragmentStorageBufferCount > 0) { + if (graphicsPipeline->header.num_fragment_storage_buffers > 0) { [commandBuffer->renderEncoder setFragmentBuffers:commandBuffer->fragmentStorageBuffers offsets:offsets - withRange:NSMakeRange(graphicsPipeline->fragmentUniformBufferCount, - graphicsPipeline->fragmentStorageBufferCount)]; + withRange:NSMakeRange(graphicsPipeline->header.num_fragment_uniform_buffers, + graphicsPipeline->header.num_fragment_storage_buffers)]; } commandBuffer->needFragmentStorageBufferBind = false; } // Fragment Uniform Buffers - for (Uint32 i = 0; i < graphicsPipeline->fragmentUniformBufferCount; i += 1) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_uniform_buffers; i += 1) { if (commandBuffer->needFragmentUniformBufferBind[i]) { - if (graphicsPipeline->fragmentUniformBufferCount > i) { + if (graphicsPipeline->header.num_fragment_uniform_buffers > i) { [commandBuffer->renderEncoder setFragmentBuffer:commandBuffer->fragmentUniformBuffers[i]->handle offset:commandBuffer->fragmentUniformBuffers[i]->drawOffset @@ -2757,38 +2743,38 @@ static void METAL_INTERNAL_BindComputeResources( NSUInteger offsets[MAX_STORAGE_BUFFERS_PER_STAGE] = { 0 }; if (commandBuffer->needComputeSamplerBind) { - if (computePipeline->numSamplers > 0) { + if (computePipeline->header.numSamplers > 0) { [commandBuffer->computeEncoder setTextures:commandBuffer->computeSamplerTextures - withRange:NSMakeRange(0, computePipeline->numSamplers)]; + withRange:NSMakeRange(0, computePipeline->header.numSamplers)]; [commandBuffer->computeEncoder setSamplerStates:commandBuffer->computeSamplers - withRange:NSMakeRange(0, computePipeline->numSamplers)]; + withRange:NSMakeRange(0, computePipeline->header.numSamplers)]; } commandBuffer->needComputeSamplerBind = false; } if (commandBuffer->needComputeReadOnlyStorageTextureBind) { - if (computePipeline->numReadonlyStorageTextures > 0) { + if (computePipeline->header.numReadonlyStorageTextures > 0) { [commandBuffer->computeEncoder setTextures:commandBuffer->computeReadOnlyTextures withRange:NSMakeRange( - computePipeline->numSamplers, - computePipeline->numReadonlyStorageTextures)]; + computePipeline->header.numSamplers, + computePipeline->header.numReadonlyStorageTextures)]; } commandBuffer->needComputeReadOnlyStorageTextureBind = false; } if (commandBuffer->needComputeReadOnlyStorageBufferBind) { - if (computePipeline->numReadonlyStorageBuffers > 0) { + if (computePipeline->header.numReadonlyStorageBuffers > 0) { [commandBuffer->computeEncoder setBuffers:commandBuffer->computeReadOnlyBuffers offsets:offsets - withRange:NSMakeRange(computePipeline->numUniformBuffers, - computePipeline->numReadonlyStorageBuffers)]; + withRange:NSMakeRange(computePipeline->header.numUniformBuffers, + computePipeline->header.numReadonlyStorageBuffers)]; } commandBuffer->needComputeReadOnlyStorageBufferBind = false; } for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) { if (commandBuffer->needComputeUniformBufferBind[i]) { - if (computePipeline->numUniformBuffers > i) { + if (computePipeline->header.numUniformBuffers > i) { [commandBuffer->computeEncoder setBuffer:commandBuffer->computeUniformBuffers[i]->handle offset:commandBuffer->computeUniformBuffers[i]->drawOffset @@ -3136,7 +3122,7 @@ static void METAL_BindComputePipeline( metalCommandBuffer->needComputeUniformBufferBind[i] = true; } - for (Uint32 i = 0; i < pipeline->numUniformBuffers; i += 1) { + for (Uint32 i = 0; i < pipeline->header.numUniformBuffers; i += 1) { if (metalCommandBuffer->computeUniformBuffers[i] == NULL) { metalCommandBuffer->computeUniformBuffers[i] = METAL_INTERNAL_AcquireUniformBufferFromPool( metalCommandBuffer); @@ -3144,22 +3130,22 @@ static void METAL_BindComputePipeline( } // Bind write-only resources - if (pipeline->numReadWriteStorageTextures > 0) { + if (pipeline->header.numReadWriteStorageTextures > 0) { [metalCommandBuffer->computeEncoder setTextures:metalCommandBuffer->computeReadWriteTextures withRange:NSMakeRange( - pipeline->numSamplers + - pipeline->numReadonlyStorageTextures, - pipeline->numReadWriteStorageTextures)]; + pipeline->header.numSamplers + + pipeline->header.numReadonlyStorageTextures, + pipeline->header.numReadWriteStorageTextures)]; } NSUInteger offsets[MAX_COMPUTE_WRITE_BUFFERS] = { 0 }; - if (pipeline->numReadWriteStorageBuffers > 0) { + if (pipeline->header.numReadWriteStorageBuffers > 0) { [metalCommandBuffer->computeEncoder setBuffers:metalCommandBuffer->computeReadWriteBuffers offsets:offsets withRange:NSMakeRange( - pipeline->numUniformBuffers + - pipeline->numReadonlyStorageBuffers, - pipeline->numReadWriteStorageBuffers)]; + pipeline->header.numUniformBuffers + + pipeline->header.numReadonlyStorageBuffers, + pipeline->header.numReadWriteStorageBuffers)]; } } } @@ -4573,6 +4559,7 @@ static SDL_GPUDevice *METAL_CreateDevice(bool debugMode, bool preferLowPower, SD SDL_GPUDevice *result = SDL_calloc(1, sizeof(SDL_GPUDevice)); ASSIGN_DRIVER(METAL) result->driverData = (SDL_GPURenderer *)renderer; + result->shader_formats = SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_METALLIB; renderer->sdlGPUDevice = result; return result; diff --git a/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c b/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c index 52c44b0..547b7ca 100644 --- a/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -56,8 +56,6 @@ typedef struct VulkanExtensions // Core since 1.2, but requires annoying paperwork to implement Uint8 KHR_driver_properties; - // EXT, probably not going to be Core - Uint8 EXT_vertex_attribute_divisor; // Only required for special implementations (i.e. MoltenVK) Uint8 KHR_portability_subset; // Only required for decoding HDR ASTC textures @@ -608,7 +606,7 @@ typedef struct VulkanSampler typedef struct VulkanShader { VkShaderModule shaderModule; - const char *entrypointName; + char *entrypointName; SDL_GPUShaderStage stage; Uint32 numSamplers; Uint32 numStorageTextures; @@ -734,7 +732,7 @@ typedef struct WindowData // Synchronization primitives VkSemaphore imageAvailableSemaphore[MAX_FRAMES_IN_FLIGHT]; - VkSemaphore renderFinishedSemaphore[MAX_FRAMES_IN_FLIGHT]; + VkSemaphore *renderFinishedSemaphore; SDL_GPUFence *inFlightFences[MAX_FRAMES_IN_FLIGHT]; Uint32 frameCounter; @@ -824,13 +822,13 @@ typedef struct DescriptorSetLayout typedef struct GraphicsPipelineResourceLayoutHashTableKey { Uint32 vertexSamplerCount; - Uint32 vertexStorageBufferCount; Uint32 vertexStorageTextureCount; + Uint32 vertexStorageBufferCount; Uint32 vertexUniformBufferCount; Uint32 fragmentSamplerCount; - Uint32 fragmentStorageBufferCount; Uint32 fragmentStorageTextureCount; + Uint32 fragmentStorageBufferCount; Uint32 fragmentUniformBufferCount; } GraphicsPipelineResourceLayoutHashTableKey; @@ -848,18 +846,20 @@ typedef struct VulkanGraphicsPipelineResourceLayout DescriptorSetLayout *descriptorSetLayouts[4]; Uint32 vertexSamplerCount; - Uint32 vertexStorageBufferCount; Uint32 vertexStorageTextureCount; + Uint32 vertexStorageBufferCount; Uint32 vertexUniformBufferCount; Uint32 fragmentSamplerCount; - Uint32 fragmentStorageBufferCount; Uint32 fragmentStorageTextureCount; + Uint32 fragmentStorageBufferCount; Uint32 fragmentUniformBufferCount; } VulkanGraphicsPipelineResourceLayout; typedef struct VulkanGraphicsPipeline { + GraphicsPipelineCommonHeader header; + VkPipeline pipeline; SDL_GPUPrimitiveType primitiveType; @@ -903,6 +903,8 @@ typedef struct VulkanComputePipelineResourceLayout typedef struct VulkanComputePipeline { + ComputePipelineCommonHeader header; + VkShaderModule shaderModule; VkPipeline pipeline; VulkanComputePipelineResourceLayout *resourceLayout; @@ -1040,25 +1042,33 @@ typedef struct VulkanCommandBuffer Uint32 vertexBufferCount; bool needVertexBufferBind; - VulkanTexture *vertexSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanSampler *vertexSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanTexture *vertexStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - VulkanBuffer *vertexStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + VkImageView vertexSamplerTextureViewBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkSampler vertexSamplerBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkImageView vertexStorageTextureViewBindings[MAX_STORAGE_TEXTURES_PER_STAGE]; + VkBuffer vertexStorageBufferBindings[MAX_STORAGE_BUFFERS_PER_STAGE]; - VulkanTexture *fragmentSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanSampler *fragmentSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanTexture *fragmentStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - VulkanBuffer *fragmentStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + VkImageView fragmentSamplerTextureViewBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkSampler fragmentSamplerBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkImageView fragmentStorageTextureViewBindings[MAX_STORAGE_TEXTURES_PER_STAGE]; + VkBuffer fragmentStorageBufferBindings[MAX_STORAGE_BUFFERS_PER_STAGE]; + VkImageView computeSamplerTextureViewBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkSampler computeSamplerBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkImageView readOnlyComputeStorageTextureViewBindings[MAX_STORAGE_TEXTURES_PER_STAGE]; + VkBuffer readOnlyComputeStorageBufferBindings[MAX_STORAGE_BUFFERS_PER_STAGE]; + + // Track these separately because barriers can happen mid compute pass + VulkanTexture *readOnlyComputeStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; + VulkanBuffer *readOnlyComputeStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + + VkImageView readWriteComputeStorageTextureViewBindings[MAX_COMPUTE_WRITE_TEXTURES]; + VkBuffer readWriteComputeStorageBufferBindings[MAX_COMPUTE_WRITE_BUFFERS]; + + // Track these separately because they are barriered when the compute pass begins VulkanTextureSubresource *readWriteComputeStorageTextureSubresources[MAX_COMPUTE_WRITE_TEXTURES]; Uint32 readWriteComputeStorageTextureSubresourceCount; VulkanBuffer *readWriteComputeStorageBuffers[MAX_COMPUTE_WRITE_BUFFERS]; - VulkanTexture *computeSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanSampler *computeSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanTexture *readOnlyComputeStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - VulkanBuffer *readOnlyComputeStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; - // Uniform buffers VulkanUniformBuffer *vertexUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE]; @@ -1098,6 +1108,7 @@ typedef struct VulkanCommandBuffer VulkanFenceHandle *inFlightFence; bool autoReleaseFence; + bool swapchainRequested; bool isDefrag; // Whether this CB was created for defragging } VulkanCommandBuffer; @@ -1137,6 +1148,7 @@ struct VulkanRenderer VulkanMemoryAllocator *memoryAllocator; VkPhysicalDeviceMemoryProperties memoryProperties; + bool checkEmptyAllocations; WindowData **claimedWindows; Uint32 claimedWindowCount; @@ -1205,7 +1217,11 @@ struct VulkanRenderer SDL_Mutex *submitLock; SDL_Mutex *acquireCommandBufferLock; SDL_Mutex *acquireUniformBufferLock; + SDL_Mutex *renderPassFetchLock; SDL_Mutex *framebufferFetchLock; + SDL_Mutex *graphicsPipelineLayoutFetchLock; + SDL_Mutex *computePipelineLayoutFetchLock; + SDL_Mutex *descriptorSetLayoutFetchLock; SDL_Mutex *windowLock; Uint8 defragInProgress; @@ -1223,7 +1239,7 @@ struct VulkanRenderer // Forward declarations -static bool VULKAN_INTERNAL_DefragmentMemory(VulkanRenderer *renderer); +static bool VULKAN_INTERNAL_DefragmentMemory(VulkanRenderer *renderer, VulkanCommandBuffer *commandBuffer); static bool VULKAN_INTERNAL_BeginCommandBuffer(VulkanRenderer *renderer, VulkanCommandBuffer *commandBuffer); static void VULKAN_ReleaseWindow(SDL_GPURenderer *driverData, SDL_Window *window); static bool VULKAN_Wait(SDL_GPURenderer *driverData); @@ -1255,19 +1271,27 @@ static inline const char *VkErrorMessages(VkResult code) ERR_TO_STR(VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT) ERR_TO_STR(VK_SUBOPTIMAL_KHR) ERR_TO_STR(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR) + ERR_TO_STR(VK_ERROR_INVALID_SHADER_NV) default: return "Unhandled VkResult!"; } #undef ERR_TO_STR } -#define SET_ERROR_AND_RETURN(fmt, msg, ret) \ +#define SET_ERROR(fmt, msg) \ do { \ if (renderer->debugMode) { \ SDL_LogError(SDL_LOG_CATEGORY_GPU, fmt, msg); \ } \ SDL_SetError((fmt), (msg)); \ - return ret; \ + } while (0) + +#define SET_STRING_ERROR(msg) SET_ERROR("%s", msg) + +#define SET_ERROR_AND_RETURN(fmt, msg, ret) \ + do { \ + SET_ERROR(fmt, msg); \ + return ret; \ } while (0) #define SET_STRING_ERROR_AND_RETURN(msg, ret) SET_ERROR_AND_RETURN("%s", msg, ret) @@ -1579,6 +1603,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); @@ -1586,7 +1614,7 @@ static void VULKAN_INTERNAL_RemoveMemoryUsedRegion( static bool VULKAN_INTERNAL_CheckMemoryTypeArrayUnique( Uint32 memoryTypeIndex, - Uint32 *memoryTypeIndexArray, + const Uint32 *memoryTypeIndexArray, Uint32 count) { Uint32 i = 0; @@ -2924,57 +2952,74 @@ static void VULKAN_INTERNAL_DestroyFramebuffer( SDL_free(framebuffer); } +typedef struct CheckOneFramebufferForRemovalData +{ + Uint32 keysToRemoveCapacity; + Uint32 keysToRemoveCount; + FramebufferHashTableKey **keysToRemove; + VkImageView view; +} CheckOneFramebufferForRemovalData; + +static bool SDLCALL CheckOneFramebufferForRemoval(void *userdata, const SDL_HashTable *table, const void *vkey, const void *vvalue) +{ + CheckOneFramebufferForRemovalData *data = (CheckOneFramebufferForRemovalData *) userdata; + FramebufferHashTableKey *key = (FramebufferHashTableKey *) vkey; + VkImageView view = data->view; + bool remove = false; + + for (Uint32 i = 0; i < key->numColorTargets; i += 1) { + if (key->colorAttachmentViews[i] == view) { + remove = true; + } + } + for (Uint32 i = 0; i < key->numResolveAttachments; i += 1) { + if (key->resolveAttachmentViews[i] == view) { + remove = true; + } + } + if (key->depthStencilAttachmentView == view) { + remove = true; + } + + if (remove) { + if (data->keysToRemoveCount == data->keysToRemoveCapacity) { + data->keysToRemoveCapacity *= 2; + void *ptr = SDL_realloc(data->keysToRemove, data->keysToRemoveCapacity * sizeof(FramebufferHashTableKey *)); + if (!ptr) { + return false; // ugh, stop iterating. We're in trouble. + } + data->keysToRemove = (FramebufferHashTableKey **) ptr; + } + data->keysToRemove[data->keysToRemoveCount] = key; + data->keysToRemoveCount++; + } + + return true; // keep iterating. +} + static void VULKAN_INTERNAL_RemoveFramebuffersContainingView( VulkanRenderer *renderer, VkImageView view) { - FramebufferHashTableKey *key; - VulkanFramebuffer *value; - void *iter = NULL; - // Can't remove while iterating! - Uint32 keysToRemoveCapacity = 8; - Uint32 keysToRemoveCount = 0; - FramebufferHashTableKey **keysToRemove = SDL_malloc(keysToRemoveCapacity * sizeof(FramebufferHashTableKey *)); + + CheckOneFramebufferForRemovalData data = { 8, 0, NULL, view }; + data.keysToRemove = (FramebufferHashTableKey **) SDL_malloc(data.keysToRemoveCapacity * sizeof(FramebufferHashTableKey *)); + if (!data.keysToRemove) { + return; // uhoh. + } SDL_LockMutex(renderer->framebufferFetchLock); - while (SDL_IterateHashTable(renderer->framebufferHashTable, (const void **)&key, (const void **)&value, &iter)) { - bool remove = false; - for (Uint32 i = 0; i < key->numColorTargets; i += 1) { - if (key->colorAttachmentViews[i] == view) { - remove = true; - } - } - for (Uint32 i = 0; i < key->numResolveAttachments; i += 1) { - if (key->resolveAttachmentViews[i] == view) { - remove = true; - } - } - if (key->depthStencilAttachmentView == view) { - remove = true; - } + SDL_IterateHashTable(renderer->framebufferHashTable, CheckOneFramebufferForRemoval, &data); - if (remove) { - if (keysToRemoveCount == keysToRemoveCapacity) { - keysToRemoveCapacity *= 2; - keysToRemove = SDL_realloc( - keysToRemove, - keysToRemoveCapacity * sizeof(FramebufferHashTableKey *)); - } - - keysToRemove[keysToRemoveCount] = key; - keysToRemoveCount += 1; - } - } - - for (Uint32 i = 0; i < keysToRemoveCount; i += 1) { - SDL_RemoveFromHashTable(renderer->framebufferHashTable, (void *)keysToRemove[i]); + for (Uint32 i = 0; i < data.keysToRemoveCount; i += 1) { + SDL_RemoveFromHashTable(renderer->framebufferHashTable, (void *)data.keysToRemove[i]); } SDL_UnlockMutex(renderer->framebufferFetchLock); - SDL_free(keysToRemove); + SDL_free(data.keysToRemove); } static void VULKAN_INTERNAL_DestroyTexture( @@ -3154,7 +3199,7 @@ static void VULKAN_INTERNAL_DestroyShader( vulkanShader->shaderModule, NULL); - SDL_free((void *)vulkanShader->entrypointName); + SDL_free(vulkanShader->entrypointName); SDL_free(vulkanShader); } @@ -3192,7 +3237,6 @@ static void VULKAN_INTERNAL_DestroySwapchain( SDL_free(windowData->textureContainers[i].activeTexture->subresources); SDL_free(windowData->textureContainers[i].activeTexture); } - windowData->imageCount = 0; SDL_free(windowData->textureContainers); windowData->textureContainers = NULL; @@ -3221,7 +3265,8 @@ static void VULKAN_INTERNAL_DestroySwapchain( NULL); windowData->imageAvailableSemaphore[i] = VK_NULL_HANDLE; } - + } + for (i = 0; i < windowData->imageCount; i += 1) { if (windowData->renderFinishedSemaphore[i]) { renderer->vkDestroySemaphore( renderer->logicalDevice, @@ -3230,6 +3275,10 @@ static void VULKAN_INTERNAL_DestroySwapchain( windowData->renderFinishedSemaphore[i] = VK_NULL_HANDLE; } } + SDL_free(windowData->renderFinishedSemaphore); + windowData->renderFinishedSemaphore = NULL; + + windowData->imageCount = 0; } static void VULKAN_INTERNAL_DestroyGraphicsPipelineResourceLayout( @@ -3280,7 +3329,7 @@ static void VULKAN_INTERNAL_DestroyDescriptorSetCache( // Hashtable functions -static Uint32 VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashFunction(const void *key, void *data) +static Uint32 SDLCALL VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashFunction(void *userdata, const void *key) { GraphicsPipelineResourceLayoutHashTableKey *hashTableKey = (GraphicsPipelineResourceLayoutHashTableKey *)key; /* The algorithm for this hashing function @@ -3299,19 +3348,19 @@ static Uint32 VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashFunction(const v result = result * hashFactor + hashTableKey->fragmentUniformBufferCount; return result; } -static bool VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashKeyMatch(const void *aKey, const void *bKey, void *data) +static bool SDLCALL VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashKeyMatch(void *userdata, const void *aKey, const void *bKey) { return SDL_memcmp(aKey, bKey, sizeof(GraphicsPipelineResourceLayoutHashTableKey)) == 0; } -static void VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashNuke(const void *key, const void *value, void *data) +static void SDLCALL VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashDestroy(void *userdata, const void *key, const void *value) { - VulkanRenderer *renderer = (VulkanRenderer *)data; + VulkanRenderer *renderer = (VulkanRenderer *)userdata; VulkanGraphicsPipelineResourceLayout *resourceLayout = (VulkanGraphicsPipelineResourceLayout *)value; VULKAN_INTERNAL_DestroyGraphicsPipelineResourceLayout(renderer, resourceLayout); SDL_free((void*)key); } -static Uint32 VULKAN_INTERNAL_ComputePipelineResourceLayoutHashFunction(const void *key, void *data) +static Uint32 SDLCALL VULKAN_INTERNAL_ComputePipelineResourceLayoutHashFunction(void *userdata, const void *key) { ComputePipelineResourceLayoutHashTableKey *hashTableKey = (ComputePipelineResourceLayoutHashTableKey *)key; /* The algorithm for this hashing function @@ -3329,20 +3378,20 @@ static Uint32 VULKAN_INTERNAL_ComputePipelineResourceLayoutHashFunction(const vo return result; } -static bool VULKAN_INTERNAL_ComputePipelineResourceLayoutHashKeyMatch(const void *aKey, const void *bKey, void *data) +static bool SDLCALL VULKAN_INTERNAL_ComputePipelineResourceLayoutHashKeyMatch(void *userdata, const void *aKey, const void *bKey) { return SDL_memcmp(aKey, bKey, sizeof(ComputePipelineResourceLayoutHashTableKey)) == 0; } -static void VULKAN_INTERNAL_ComputePipelineResourceLayoutHashNuke(const void *key, const void *value, void *data) +static void SDLCALL VULKAN_INTERNAL_ComputePipelineResourceLayoutHashDestroy(void *userdata, const void *key, const void *value) { - VulkanRenderer *renderer = (VulkanRenderer *)data; + VulkanRenderer *renderer = (VulkanRenderer *)userdata; VulkanComputePipelineResourceLayout *resourceLayout = (VulkanComputePipelineResourceLayout *)value; VULKAN_INTERNAL_DestroyComputePipelineResourceLayout(renderer, resourceLayout); SDL_free((void*)key); } -static Uint32 VULKAN_INTERNAL_DescriptorSetLayoutHashFunction(const void *key, void *data) +static Uint32 SDLCALL VULKAN_INTERNAL_DescriptorSetLayoutHashFunction(void *userdata, const void *key) { DescriptorSetLayoutHashTableKey *hashTableKey = (DescriptorSetLayoutHashTableKey *)key; @@ -3362,42 +3411,40 @@ static Uint32 VULKAN_INTERNAL_DescriptorSetLayoutHashFunction(const void *key, v return result; } -static bool VULKAN_INTERNAL_DescriptorSetLayoutHashKeyMatch(const void *aKey, const void *bKey, void *data) +static bool SDLCALL VULKAN_INTERNAL_DescriptorSetLayoutHashKeyMatch(void *userdata, const void *aKey, const void *bKey) { return SDL_memcmp(aKey, bKey, sizeof(DescriptorSetLayoutHashTableKey)) == 0; } -static void VULKAN_INTERNAL_DescriptorSetLayoutHashNuke(const void *key, const void *value, void *data) +static void SDLCALL VULKAN_INTERNAL_DescriptorSetLayoutHashDestroy(void *userdata, const void *key, const void *value) { - VulkanRenderer *renderer = (VulkanRenderer *)data; + VulkanRenderer *renderer = (VulkanRenderer *)userdata; DescriptorSetLayout *layout = (DescriptorSetLayout *)value; VULKAN_INTERNAL_DestroyDescriptorSetLayout(renderer, layout); SDL_free((void*)key); } -static Uint32 VULKAN_INTERNAL_CommandPoolHashFunction(const void *key, void *data) +static Uint32 SDLCALL VULKAN_INTERNAL_CommandPoolHashFunction(void *userdata, const void *key) { return (Uint32)((CommandPoolHashTableKey *)key)->threadID; } -static bool VULKAN_INTERNAL_CommandPoolHashKeyMatch(const void *aKey, const void *bKey, void *data) +static bool SDLCALL VULKAN_INTERNAL_CommandPoolHashKeyMatch(void *userdata, const void *aKey, const void *bKey) { CommandPoolHashTableKey *a = (CommandPoolHashTableKey *)aKey; CommandPoolHashTableKey *b = (CommandPoolHashTableKey *)bKey; return a->threadID == b->threadID; } -static void VULKAN_INTERNAL_CommandPoolHashNuke(const void *key, const void *value, void *data) +static void SDLCALL VULKAN_INTERNAL_CommandPoolHashDestroy(void *userdata, const void *key, const void *value) { - VulkanRenderer *renderer = (VulkanRenderer *)data; + VulkanRenderer *renderer = (VulkanRenderer *)userdata; VulkanCommandPool *pool = (VulkanCommandPool *)value; VULKAN_INTERNAL_DestroyCommandPool(renderer, pool); SDL_free((void *)key); } -static Uint32 VULKAN_INTERNAL_RenderPassHashFunction( - const void *key, - void *data) +static Uint32 SDLCALL VULKAN_INTERNAL_RenderPassHashFunction(void *userdata, const void *key) { RenderPassHashTableKey *hashTableKey = (RenderPassHashTableKey *)key; @@ -3429,10 +3476,7 @@ static Uint32 VULKAN_INTERNAL_RenderPassHashFunction( return result; } -static bool VULKAN_INTERNAL_RenderPassHashKeyMatch( - const void *aKey, - const void *bKey, - void *data) +static bool SDLCALL VULKAN_INTERNAL_RenderPassHashKeyMatch(void *userdata, const void *aKey, const void *bKey) { RenderPassHashTableKey *a = (RenderPassHashTableKey *)aKey; RenderPassHashTableKey *b = (RenderPassHashTableKey *)bKey; @@ -3492,9 +3536,9 @@ static bool VULKAN_INTERNAL_RenderPassHashKeyMatch( return 1; } -static void VULKAN_INTERNAL_RenderPassHashNuke(const void *key, const void *value, void *data) +static void SDLCALL VULKAN_INTERNAL_RenderPassHashDestroy(void *userdata, const void *key, const void *value) { - VulkanRenderer *renderer = (VulkanRenderer *)data; + VulkanRenderer *renderer = (VulkanRenderer *)userdata; VulkanRenderPassHashTableValue *renderPassWrapper = (VulkanRenderPassHashTableValue *)value; renderer->vkDestroyRenderPass( renderer->logicalDevice, @@ -3504,9 +3548,7 @@ static void VULKAN_INTERNAL_RenderPassHashNuke(const void *key, const void *valu SDL_free((void *)key); } -static Uint32 VULKAN_INTERNAL_FramebufferHashFunction( - const void *key, - void *data) +static Uint32 SDLCALL VULKAN_INTERNAL_FramebufferHashFunction(void *userdata, const void *key) { FramebufferHashTableKey *hashTableKey = (FramebufferHashTableKey *)key; @@ -3531,10 +3573,7 @@ static Uint32 VULKAN_INTERNAL_FramebufferHashFunction( return result; } -static bool VULKAN_INTERNAL_FramebufferHashKeyMatch( - const void *aKey, - const void *bKey, - void *data) +static bool SDLCALL VULKAN_INTERNAL_FramebufferHashKeyMatch(void *userdata, const void *aKey, const void *bKey) { FramebufferHashTableKey *a = (FramebufferHashTableKey *)aKey; FramebufferHashTableKey *b = (FramebufferHashTableKey *)bKey; @@ -3574,9 +3613,9 @@ static bool VULKAN_INTERNAL_FramebufferHashKeyMatch( return 1; } -static void VULKAN_INTERNAL_FramebufferHashNuke(const void *key, const void *value, void *data) +static void SDLCALL VULKAN_INTERNAL_FramebufferHashDestroy(void *userdata, const void *key, const void *value) { - VulkanRenderer *renderer = (VulkanRenderer *)data; + VulkanRenderer *renderer = (VulkanRenderer *)userdata; VulkanFramebuffer *framebuffer = (VulkanFramebuffer *)value; VULKAN_INTERNAL_ReleaseFramebuffer(renderer, framebuffer); SDL_free((void *)key); @@ -3739,10 +3778,13 @@ static DescriptorSetLayout *VULKAN_INTERNAL_FetchDescriptorSetLayout( key.writeStorageBufferCount = writeStorageBufferCount; key.uniformBufferCount = uniformBufferCount; + SDL_LockMutex(renderer->descriptorSetLayoutFetchLock); + if (SDL_FindInHashTable( renderer->descriptorSetLayoutHashTable, (const void *)&key, (const void **)&layout)) { + SDL_UnlockMutex(renderer->descriptorSetLayoutFetchLock); return layout; } @@ -3825,7 +3867,10 @@ static DescriptorSetLayout *VULKAN_INTERNAL_FetchDescriptorSetLayout( NULL, &descriptorSetLayout); - CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateDescriptorSetLayout, NULL); + if (vulkanResult != VK_SUCCESS) { + SDL_UnlockMutex(renderer->descriptorSetLayoutFetchLock); + CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateDescriptorSetLayout, NULL); + } layout = SDL_malloc(sizeof(DescriptorSetLayout)); layout->descriptorSetLayout = descriptorSetLayout; @@ -3845,8 +3890,9 @@ static DescriptorSetLayout *VULKAN_INTERNAL_FetchDescriptorSetLayout( SDL_InsertIntoHashTable( renderer->descriptorSetLayoutHashTable, (const void *)allocedKey, - (const void *)layout); + (const void *)layout, true); + SDL_UnlockMutex(renderer->descriptorSetLayoutFetchLock); return layout; } @@ -3867,10 +3913,14 @@ static VulkanGraphicsPipelineResourceLayout *VULKAN_INTERNAL_FetchGraphicsPipeli key.fragmentStorageTextureCount = fragmentShader->numStorageTextures; key.fragmentStorageBufferCount = fragmentShader->numStorageBuffers; key.fragmentUniformBufferCount = fragmentShader->numUniformBuffers; + + SDL_LockMutex(renderer->graphicsPipelineLayoutFetchLock); + if (SDL_FindInHashTable( renderer->graphicsPipelineResourceLayoutHashTable, (const void *)&key, (const void **)&pipelineResourceLayout)) { + SDL_UnlockMutex(renderer->graphicsPipelineLayoutFetchLock); return pipelineResourceLayout; } @@ -3953,6 +4003,7 @@ static VulkanGraphicsPipelineResourceLayout *VULKAN_INTERNAL_FetchGraphicsPipeli if (vulkanResult != VK_SUCCESS) { VULKAN_INTERNAL_DestroyGraphicsPipelineResourceLayout(renderer, pipelineResourceLayout); + SDL_UnlockMutex(renderer->graphicsPipelineLayoutFetchLock); CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreatePipelineLayout, NULL); } @@ -3962,8 +4013,9 @@ static VulkanGraphicsPipelineResourceLayout *VULKAN_INTERNAL_FetchGraphicsPipeli SDL_InsertIntoHashTable( renderer->graphicsPipelineResourceLayoutHashTable, (const void *)allocedKey, - (const void *)pipelineResourceLayout); + (const void *)pipelineResourceLayout, true); + SDL_UnlockMutex(renderer->graphicsPipelineLayoutFetchLock); return pipelineResourceLayout; } @@ -3982,10 +4034,13 @@ static VulkanComputePipelineResourceLayout *VULKAN_INTERNAL_FetchComputePipeline key.readWriteStorageBufferCount = createinfo->num_readwrite_storage_buffers; key.uniformBufferCount = createinfo->num_uniform_buffers; + SDL_LockMutex(renderer->computePipelineLayoutFetchLock); + if (SDL_FindInHashTable( renderer->computePipelineResourceLayoutHashTable, (const void *)&key, (const void **)&pipelineResourceLayout)) { + SDL_UnlockMutex(renderer->computePipelineLayoutFetchLock); return pipelineResourceLayout; } @@ -4054,6 +4109,7 @@ static VulkanComputePipelineResourceLayout *VULKAN_INTERNAL_FetchComputePipeline if (vulkanResult != VK_SUCCESS) { VULKAN_INTERNAL_DestroyComputePipelineResourceLayout(renderer, pipelineResourceLayout); + SDL_UnlockMutex(renderer->computePipelineLayoutFetchLock); CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreatePipelineLayout, NULL); } @@ -4063,8 +4119,9 @@ static VulkanComputePipelineResourceLayout *VULKAN_INTERNAL_FetchComputePipeline SDL_InsertIntoHashTable( renderer->computePipelineResourceLayoutHashTable, (const void *)allocedKey, - (const void *)pipelineResourceLayout); + (const void *)pipelineResourceLayout, true); + SDL_UnlockMutex(renderer->computePipelineLayoutFetchLock); return pipelineResourceLayout; } @@ -4441,7 +4498,7 @@ static bool VULKAN_INTERNAL_VerifySwapSurfaceFormat( static bool VULKAN_INTERNAL_VerifySwapPresentMode( VkPresentModeKHR presentMode, - VkPresentModeKHR *availablePresentModes, + const VkPresentModeKHR *availablePresentModes, Uint32 availablePresentModesLength) { Uint32 i; @@ -4629,7 +4686,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; @@ -4775,7 +4836,13 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false); } - renderer->vkCreateSemaphore( + windowData->inFlightFences[i] = NULL; + } + + windowData->renderFinishedSemaphore = SDL_malloc( + sizeof(VkSemaphore) * windowData->imageCount); + for (i = 0; i < windowData->imageCount; i += 1) { + vulkanResult = renderer->vkCreateSemaphore( renderer->logicalDevice, &semaphoreCreateInfo, NULL, @@ -4794,8 +4861,6 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( windowData->swapchain = VK_NULL_HANDLE; CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false); } - - windowData->inFlightFences[i] = NULL; } windowData->needsSwapchainRecreate = false; @@ -4929,7 +4994,11 @@ 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->graphicsPipelineLayoutFetchLock); + SDL_DestroyMutex(renderer->computePipelineLayoutFetchLock); + SDL_DestroyMutex(renderer->descriptorSetLayoutFetchLock); SDL_DestroyMutex(renderer->windowLock); renderer->vkDestroyDevice(renderer->logicalDevice, NULL); @@ -5077,8 +5146,8 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pBufferInfo = NULL; - imageInfos[imageInfoCount].sampler = commandBuffer->vertexSamplers[i]->sampler; - imageInfos[imageInfoCount].imageView = commandBuffer->vertexSamplerTextures[i]->fullView; + imageInfos[imageInfoCount].sampler = commandBuffer->vertexSamplerBindings[i]; + imageInfos[imageInfoCount].imageView = commandBuffer->vertexSamplerTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -5101,7 +5170,7 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pBufferInfo = NULL; imageInfos[imageInfoCount].sampler = VK_NULL_HANDLE; - imageInfos[imageInfoCount].imageView = commandBuffer->vertexStorageTextures[i]->fullView; + imageInfos[imageInfoCount].imageView = commandBuffer->vertexStorageTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_GENERAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -5123,7 +5192,7 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pImageInfo = NULL; - bufferInfos[bufferInfoCount].buffer = commandBuffer->vertexStorageBuffers[i]->buffer; + bufferInfos[bufferInfoCount].buffer = commandBuffer->vertexStorageBufferBindings[i]; bufferInfos[bufferInfoCount].offset = 0; bufferInfos[bufferInfoCount].range = VK_WHOLE_SIZE; @@ -5196,8 +5265,8 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pBufferInfo = NULL; - imageInfos[imageInfoCount].sampler = commandBuffer->fragmentSamplers[i]->sampler; - imageInfos[imageInfoCount].imageView = commandBuffer->fragmentSamplerTextures[i]->fullView; + imageInfos[imageInfoCount].sampler = commandBuffer->fragmentSamplerBindings[i]; + imageInfos[imageInfoCount].imageView = commandBuffer->fragmentSamplerTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -5220,7 +5289,7 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pBufferInfo = NULL; imageInfos[imageInfoCount].sampler = VK_NULL_HANDLE; - imageInfos[imageInfoCount].imageView = commandBuffer->fragmentStorageTextures[i]->fullView; + imageInfos[imageInfoCount].imageView = commandBuffer->fragmentStorageTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_GENERAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -5242,7 +5311,7 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pImageInfo = NULL; - bufferInfos[bufferInfoCount].buffer = commandBuffer->fragmentStorageBuffers[i]->buffer; + bufferInfos[bufferInfoCount].buffer = commandBuffer->fragmentStorageBufferBindings[i]; bufferInfos[bufferInfoCount].offset = 0; bufferInfos[bufferInfoCount].range = VK_WHOLE_SIZE; @@ -5330,7 +5399,7 @@ static void VULKAN_DrawIndexedPrimitives( Uint32 firstInstance) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VULKAN_INTERNAL_BindGraphicsDescriptorSets(renderer, vulkanCommandBuffer); @@ -5351,7 +5420,7 @@ static void VULKAN_DrawPrimitives( Uint32 firstInstance) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VULKAN_INTERNAL_BindGraphicsDescriptorSets(renderer, vulkanCommandBuffer); @@ -5370,7 +5439,7 @@ static void VULKAN_DrawPrimitivesIndirect( Uint32 drawCount) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VulkanBuffer *vulkanBuffer = ((VulkanBufferContainer *)buffer)->activeBuffer; Uint32 pitch = sizeof(SDL_GPUIndirectDrawCommand); Uint32 i; @@ -5407,7 +5476,7 @@ static void VULKAN_DrawIndexedPrimitivesIndirect( Uint32 drawCount) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VulkanBuffer *vulkanBuffer = ((VulkanBufferContainer *)buffer)->activeBuffer; Uint32 pitch = sizeof(SDL_GPUIndexedIndirectDrawCommand); Uint32 i; @@ -5540,12 +5609,12 @@ static void VULKAN_InsertDebugLabel( const char *text) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VkDebugUtilsLabelEXT labelInfo; if (renderer->supportsDebugUtils) { + SDL_zero(labelInfo); labelInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; - labelInfo.pNext = NULL; labelInfo.pLabelName = text; renderer->vkCmdInsertDebugUtilsLabelEXT( @@ -5559,12 +5628,12 @@ static void VULKAN_PushDebugGroup( const char *name) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VkDebugUtilsLabelEXT labelInfo; if (renderer->supportsDebugUtils) { + SDL_zero(labelInfo); labelInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; - labelInfo.pNext = NULL; labelInfo.pLabelName = name; renderer->vkCmdBeginDebugUtilsLabelEXT( @@ -5577,7 +5646,7 @@ static void VULKAN_PopDebugGroup( SDL_GPUCommandBuffer *commandBuffer) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; if (renderer->supportsDebugUtils) { renderer->vkCmdEndDebugUtilsLabelEXT(vulkanCommandBuffer->commandBuffer); @@ -5586,6 +5655,7 @@ static void VULKAN_PopDebugGroup( static VulkanTexture *VULKAN_INTERNAL_CreateTexture( VulkanRenderer *renderer, + bool transitionToDefaultLayout, const SDL_GPUTextureCreateInfo *createinfo) { VkResult vulkanResult; @@ -5813,15 +5883,17 @@ static VulkanTexture *VULKAN_INTERNAL_CreateTexture( &nameInfo); } - // Let's transition to the default barrier state, because for some reason Vulkan doesn't let us do that with initialLayout. - VulkanCommandBuffer *barrierCommandBuffer = (VulkanCommandBuffer *)VULKAN_AcquireCommandBuffer((SDL_GPURenderer *)renderer); - VULKAN_INTERNAL_TextureTransitionToDefaultUsage( - renderer, - barrierCommandBuffer, - VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, - texture); - VULKAN_INTERNAL_TrackTexture(barrierCommandBuffer, texture); - VULKAN_Submit((SDL_GPUCommandBuffer *)barrierCommandBuffer); + if (transitionToDefaultLayout) { + // Let's transition to the default barrier state, because for some reason Vulkan doesn't let us do that with initialLayout. + VulkanCommandBuffer *barrierCommandBuffer = (VulkanCommandBuffer *)VULKAN_AcquireCommandBuffer((SDL_GPURenderer *)renderer); + VULKAN_INTERNAL_TextureTransitionToDefaultUsage( + renderer, + barrierCommandBuffer, + VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, + texture); + VULKAN_INTERNAL_TrackTexture(barrierCommandBuffer, texture); + VULKAN_Submit((SDL_GPUCommandBuffer *)barrierCommandBuffer); + } return texture; } @@ -5871,6 +5943,7 @@ static void VULKAN_INTERNAL_CycleActiveBuffer( static void VULKAN_INTERNAL_CycleActiveTexture( VulkanRenderer *renderer, + VulkanCommandBuffer *commandBuffer, VulkanTextureContainer *container) { VulkanTexture *texture; @@ -5888,8 +5961,15 @@ static void VULKAN_INTERNAL_CycleActiveTexture( // No texture is available, generate a new one. texture = VULKAN_INTERNAL_CreateTexture( renderer, + false, &container->header.info); + VULKAN_INTERNAL_TextureTransitionToDefaultUsage( + renderer, + commandBuffer, + VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, + texture); + if (!texture) { return; } @@ -5953,6 +6033,7 @@ static VulkanTextureSubresource *VULKAN_INTERNAL_PrepareTextureSubresourceForWri SDL_GetAtomicInt(&textureContainer->activeTexture->referenceCount) > 0) { VULKAN_INTERNAL_CycleActiveTexture( renderer, + commandBuffer, textureContainer); textureSubresource = VULKAN_INTERNAL_FetchTextureSubresource( @@ -6195,11 +6276,8 @@ static SDL_GPUGraphicsPipeline *VULKAN_CreateGraphicsPipeline( VkPipelineShaderStageCreateInfo shaderStageCreateInfos[2]; VkPipelineVertexInputStateCreateInfo vertexInputStateCreateInfo; - VkPipelineVertexInputDivisorStateCreateInfoEXT divisorStateCreateInfo; VkVertexInputBindingDescription *vertexInputBindingDescriptions = SDL_stack_alloc(VkVertexInputBindingDescription, createinfo->vertex_input_state.num_vertex_buffers); VkVertexInputAttributeDescription *vertexInputAttributeDescriptions = SDL_stack_alloc(VkVertexInputAttributeDescription, createinfo->vertex_input_state.num_vertex_attributes); - VkVertexInputBindingDivisorDescriptionEXT *divisorDescriptions = SDL_stack_alloc(VkVertexInputBindingDivisorDescriptionEXT, createinfo->vertex_input_state.num_vertex_buffers); - Uint32 divisorDescriptionCount = 0; VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCreateInfo; @@ -6282,10 +6360,6 @@ static SDL_GPUGraphicsPipeline *VULKAN_CreateGraphicsPipeline( vertexInputBindingDescriptions[i].binding = createinfo->vertex_input_state.vertex_buffer_descriptions[i].slot; vertexInputBindingDescriptions[i].inputRate = SDLToVK_VertexInputRate[createinfo->vertex_input_state.vertex_buffer_descriptions[i].input_rate]; vertexInputBindingDescriptions[i].stride = createinfo->vertex_input_state.vertex_buffer_descriptions[i].pitch; - - if (createinfo->vertex_input_state.vertex_buffer_descriptions[i].input_rate == SDL_GPU_VERTEXINPUTRATE_INSTANCE) { - divisorDescriptionCount += 1; - } } for (i = 0; i < createinfo->vertex_input_state.num_vertex_attributes; i += 1) { @@ -6303,26 +6377,6 @@ static SDL_GPUGraphicsPipeline *VULKAN_CreateGraphicsPipeline( vertexInputStateCreateInfo.vertexAttributeDescriptionCount = createinfo->vertex_input_state.num_vertex_attributes; vertexInputStateCreateInfo.pVertexAttributeDescriptions = vertexInputAttributeDescriptions; - if (divisorDescriptionCount > 0) { - divisorDescriptionCount = 0; - - for (i = 0; i < createinfo->vertex_input_state.num_vertex_buffers; i += 1) { - if (createinfo->vertex_input_state.vertex_buffer_descriptions[i].input_rate == SDL_GPU_VERTEXINPUTRATE_INSTANCE) { - divisorDescriptions[divisorDescriptionCount].binding = createinfo->vertex_input_state.vertex_buffer_descriptions[i].slot; - divisorDescriptions[divisorDescriptionCount].divisor = createinfo->vertex_input_state.vertex_buffer_descriptions[i].instance_step_rate; - - divisorDescriptionCount += 1; - } - } - - divisorStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT; - divisorStateCreateInfo.pNext = NULL; - divisorStateCreateInfo.vertexBindingDivisorCount = divisorDescriptionCount; - divisorStateCreateInfo.pVertexBindingDivisors = divisorDescriptions; - - vertexInputStateCreateInfo.pNext = &divisorStateCreateInfo; - } - // Topology inputAssemblyStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; @@ -6369,9 +6423,7 @@ static SDL_GPUGraphicsPipeline *VULKAN_CreateGraphicsPipeline( // Multisample - Uint32 sampleMask = createinfo->multisample_state.enable_mask ? - createinfo->multisample_state.sample_mask : - 0xFFFFFFFF; + Uint32 sampleMask = 0xFFFFFFFF; multisampleStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampleStateCreateInfo.pNext = NULL; @@ -6469,7 +6521,6 @@ static SDL_GPUGraphicsPipeline *VULKAN_CreateGraphicsPipeline( SDL_stack_free(vertexInputBindingDescriptions); SDL_stack_free(vertexInputAttributeDescriptions); SDL_stack_free(colorBlendAttachmentStates); - SDL_stack_free(divisorDescriptions); SDL_free(graphicsPipeline); SET_STRING_ERROR_AND_RETURN("Failed to initialize pipeline resource layout!", NULL); } @@ -6508,7 +6559,6 @@ static SDL_GPUGraphicsPipeline *VULKAN_CreateGraphicsPipeline( SDL_stack_free(vertexInputBindingDescriptions); SDL_stack_free(vertexInputAttributeDescriptions); SDL_stack_free(colorBlendAttachmentStates); - SDL_stack_free(divisorDescriptions); renderer->vkDestroyRenderPass( renderer->logicalDevice, @@ -6535,6 +6585,16 @@ static SDL_GPUGraphicsPipeline *VULKAN_CreateGraphicsPipeline( &nameInfo); } + // Put this data in the pipeline we can do validation in gpu.c + graphicsPipeline->header.num_vertex_samplers = graphicsPipeline->resourceLayout->vertexSamplerCount; + graphicsPipeline->header.num_vertex_storage_buffers = graphicsPipeline->resourceLayout->vertexStorageBufferCount; + graphicsPipeline->header.num_vertex_storage_textures = graphicsPipeline->resourceLayout->vertexStorageTextureCount; + graphicsPipeline->header.num_vertex_uniform_buffers = graphicsPipeline->resourceLayout->vertexUniformBufferCount; + graphicsPipeline->header.num_fragment_samplers = graphicsPipeline->resourceLayout->fragmentSamplerCount; + graphicsPipeline->header.num_fragment_storage_buffers = graphicsPipeline->resourceLayout->fragmentStorageBufferCount; + graphicsPipeline->header.num_fragment_storage_textures = graphicsPipeline->resourceLayout->fragmentStorageTextureCount; + graphicsPipeline->header.num_fragment_uniform_buffers = graphicsPipeline->resourceLayout->fragmentUniformBufferCount; + return (SDL_GPUGraphicsPipeline *)graphicsPipeline; } @@ -6629,6 +6689,14 @@ static SDL_GPUComputePipeline *VULKAN_CreateComputePipeline( &nameInfo); } + // Track these here for debug layer + vulkanComputePipeline->header.numSamplers = vulkanComputePipeline->resourceLayout->numSamplers; + vulkanComputePipeline->header.numReadonlyStorageTextures = vulkanComputePipeline->resourceLayout->numReadonlyStorageTextures; + vulkanComputePipeline->header.numReadonlyStorageBuffers = vulkanComputePipeline->resourceLayout->numReadonlyStorageBuffers; + vulkanComputePipeline->header.numReadWriteStorageTextures = vulkanComputePipeline->resourceLayout->numReadWriteStorageTextures; + vulkanComputePipeline->header.numReadWriteStorageBuffers = vulkanComputePipeline->resourceLayout->numReadWriteStorageBuffers; + vulkanComputePipeline->header.numUniformBuffers = vulkanComputePipeline->resourceLayout->numUniformBuffers; + return (SDL_GPUComputePipeline *)vulkanComputePipeline; } @@ -6697,7 +6765,6 @@ static SDL_GPUShader *VULKAN_CreateShader( VkResult vulkanResult; VkShaderModuleCreateInfo vkShaderModuleCreateInfo; VulkanRenderer *renderer = (VulkanRenderer *)driverData; - size_t entryPointNameLength; vulkanShader = SDL_malloc(sizeof(VulkanShader)); vkShaderModuleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; @@ -6717,10 +6784,11 @@ static SDL_GPUShader *VULKAN_CreateShader( CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateShaderModule, NULL); } - entryPointNameLength = SDL_strlen(createinfo->entrypoint) + 1; - vulkanShader->entrypointName = SDL_malloc(entryPointNameLength); - SDL_utf8strlcpy((char *)vulkanShader->entrypointName, createinfo->entrypoint, entryPointNameLength); - + const char *entrypoint = createinfo->entrypoint; + if (!entrypoint) { + entrypoint = "main"; + } + vulkanShader->entrypointName = SDL_strdup(entrypoint); vulkanShader->stage = createinfo->stage; vulkanShader->numSamplers = createinfo->num_samplers; vulkanShader->numStorageTextures = createinfo->num_storage_textures; @@ -6766,6 +6834,7 @@ static SDL_GPUTexture *VULKAN_CreateTexture( texture = VULKAN_INTERNAL_CreateTexture( renderer, + true, createinfo); if (texture == NULL) { @@ -6777,7 +6846,9 @@ static SDL_GPUTexture *VULKAN_CreateTexture( // Copy properties so we don't lose information when the client destroys them container->header.info = *createinfo; container->header.info.props = SDL_CreateProperties(); - SDL_CopyProperties(createinfo->props, container->header.info.props); + if (createinfo->props) { + SDL_CopyProperties(createinfo->props, container->header.info.props); + } container->canBeCycled = true; container->activeTexture = texture; @@ -6840,15 +6911,12 @@ static SDL_GPUTransferBuffer *VULKAN_CreateTransferBuffer( Uint32 size, const char *debugName) { - // We use dedicated allocations for download buffers to avoid an issue - // where a defrag is triggered after submitting a download but before - // waiting on the fence. return (SDL_GPUTransferBuffer *)VULKAN_INTERNAL_CreateBufferContainer( (VulkanRenderer *)driverData, (VkDeviceSize)size, 0, VULKAN_BUFFER_TYPE_TRANSFER, - usage == SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD, + true, // Dedicated allocations preserve the data even if a defrag is triggered. debugName); } @@ -6891,6 +6959,8 @@ static void VULKAN_ReleaseTexture( VULKAN_INTERNAL_ReleaseTexture(renderer, vulkanTextureContainer->textures[i]); } + SDL_DestroyProperties(vulkanTextureContainer->header.info.props); + // Containers are just client handles, so we can destroy immediately if (vulkanTextureContainer->debugName != NULL) { SDL_free(vulkanTextureContainer->debugName); @@ -6943,7 +7013,7 @@ static void VULKAN_INTERNAL_ReleaseBuffer( renderer->buffersToDestroy[renderer->buffersToDestroyCount] = vulkanBuffer; renderer->buffersToDestroyCount += 1; - vulkanBuffer->markedForDestroy = 1; + vulkanBuffer->markedForDestroy = true; vulkanBuffer->container = NULL; SDL_UnlockMutex(renderer->disposeLock); @@ -7110,12 +7180,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; } @@ -7127,6 +7200,7 @@ static VkRenderPass VULKAN_INTERNAL_FetchRenderPass( depthStencilTargetInfo); if (renderPassHandle == VK_NULL_HANDLE) { + SDL_UnlockMutex(renderer->renderPassFetchLock); return VK_NULL_HANDLE; } @@ -7140,7 +7214,9 @@ static VkRenderPass VULKAN_INTERNAL_FetchRenderPass( SDL_InsertIntoHashTable( renderer->renderPassHashTable, (const void *)allocedKey, - (const void *)renderPassWrapper); + (const void *)renderPassWrapper, true); + + SDL_UnlockMutex(renderer->renderPassFetchLock); return renderPassHandle; } @@ -7210,9 +7286,8 @@ static VulkanFramebuffer *VULKAN_INTERNAL_FetchFramebuffer( (const void *)&key, (const void **)&vulkanFramebuffer); - SDL_UnlockMutex(renderer->framebufferFetchLock); - if (findResult) { + SDL_UnlockMutex(renderer->framebufferFetchLock); return vulkanFramebuffer; } @@ -7280,19 +7355,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); + (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; } @@ -7300,8 +7374,8 @@ static void VULKAN_INTERNAL_SetCurrentViewport( VulkanCommandBuffer *commandBuffer, const SDL_GPUViewport *viewport) { - VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanCommandBuffer *vulkanCommandBuffer = commandBuffer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; vulkanCommandBuffer->currentViewport.x = viewport->x; vulkanCommandBuffer->currentViewport.width = viewport->w; @@ -7334,7 +7408,7 @@ static void VULKAN_INTERNAL_SetCurrentScissor( VulkanCommandBuffer *vulkanCommandBuffer, const SDL_Rect *scissor) { - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; vulkanCommandBuffer->currentScissor.offset.x = scissor->x; vulkanCommandBuffer->currentScissor.offset.y = scissor->y; @@ -7363,7 +7437,7 @@ static void VULKAN_INTERNAL_SetCurrentBlendConstants( VulkanCommandBuffer *vulkanCommandBuffer, SDL_FColor blendConstants) { - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; vulkanCommandBuffer->blendConstants[0] = blendConstants.r; vulkanCommandBuffer->blendConstants[1] = blendConstants.g; @@ -7390,7 +7464,7 @@ static void VULKAN_INTERNAL_SetCurrentStencilReference( VulkanCommandBuffer *vulkanCommandBuffer, Uint8 reference) { - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; vulkanCommandBuffer->stencilRef = reference; @@ -7423,21 +7497,21 @@ static void VULKAN_BindVertexSamplers( VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)textureSamplerBindings[i].texture; VulkanSampler *sampler = (VulkanSampler *)textureSamplerBindings[i].sampler; - if (vulkanCommandBuffer->vertexSamplers[firstSlot + i] != sampler) { + if (vulkanCommandBuffer->vertexSamplerBindings[firstSlot + i] != sampler->sampler) { VULKAN_INTERNAL_TrackSampler( vulkanCommandBuffer, (VulkanSampler *)textureSamplerBindings[i].sampler); - vulkanCommandBuffer->vertexSamplers[firstSlot + i] = (VulkanSampler *)textureSamplerBindings[i].sampler; + vulkanCommandBuffer->vertexSamplerBindings[firstSlot + i] = sampler->sampler; vulkanCommandBuffer->needNewVertexResourceDescriptorSet = true; } - if (vulkanCommandBuffer->vertexSamplerTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->vertexSamplerTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->vertexSamplerTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->vertexSamplerTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewVertexResourceDescriptorSet = true; } } @@ -7454,12 +7528,12 @@ static void VULKAN_BindVertexStorageTextures( for (Uint32 i = 0; i < numBindings; i += 1) { VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)storageTextures[i]; - if (vulkanCommandBuffer->vertexStorageTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->vertexStorageTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->vertexStorageTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->vertexStorageTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewVertexResourceDescriptorSet = true; } } @@ -7476,12 +7550,12 @@ static void VULKAN_BindVertexStorageBuffers( for (Uint32 i = 0; i < numBindings; i += 1) { VulkanBufferContainer *bufferContainer = (VulkanBufferContainer *)storageBuffers[i]; - if (vulkanCommandBuffer->vertexStorageBuffers[firstSlot + i] != bufferContainer->activeBuffer) { + if (vulkanCommandBuffer->vertexStorageBufferBindings[firstSlot + i] != bufferContainer->activeBuffer->buffer) { VULKAN_INTERNAL_TrackBuffer( vulkanCommandBuffer, bufferContainer->activeBuffer); - vulkanCommandBuffer->vertexStorageBuffers[firstSlot + i] = bufferContainer->activeBuffer; + vulkanCommandBuffer->vertexStorageBufferBindings[firstSlot + i] = bufferContainer->activeBuffer->buffer; vulkanCommandBuffer->needNewVertexResourceDescriptorSet = true; } } @@ -7499,21 +7573,21 @@ static void VULKAN_BindFragmentSamplers( VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)textureSamplerBindings[i].texture; VulkanSampler *sampler = (VulkanSampler *)textureSamplerBindings[i].sampler; - if (vulkanCommandBuffer->fragmentSamplers[firstSlot + i] != sampler) { + if (vulkanCommandBuffer->fragmentSamplerBindings[firstSlot + i] != sampler->sampler) { VULKAN_INTERNAL_TrackSampler( vulkanCommandBuffer, (VulkanSampler *)textureSamplerBindings[i].sampler); - vulkanCommandBuffer->fragmentSamplers[firstSlot + i] = (VulkanSampler *)textureSamplerBindings[i].sampler; + vulkanCommandBuffer->fragmentSamplerBindings[firstSlot + i] = sampler->sampler; vulkanCommandBuffer->needNewFragmentResourceDescriptorSet = true; } - if (vulkanCommandBuffer->fragmentSamplerTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->fragmentSamplerTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->fragmentSamplerTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->fragmentSamplerTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewFragmentResourceDescriptorSet = true; } } @@ -7530,12 +7604,12 @@ static void VULKAN_BindFragmentStorageTextures( for (Uint32 i = 0; i < numBindings; i += 1) { VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)storageTextures[i]; - if (vulkanCommandBuffer->fragmentStorageTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->fragmentStorageTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->fragmentStorageTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->fragmentStorageTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewFragmentResourceDescriptorSet = true; } } @@ -7554,12 +7628,12 @@ static void VULKAN_BindFragmentStorageBuffers( for (i = 0; i < numBindings; i += 1) { bufferContainer = (VulkanBufferContainer *)storageBuffers[i]; - if (vulkanCommandBuffer->fragmentStorageBuffers[firstSlot + i] != bufferContainer->activeBuffer) { + if (vulkanCommandBuffer->fragmentStorageBufferBindings[firstSlot + i] != bufferContainer->activeBuffer->buffer) { VULKAN_INTERNAL_TrackBuffer( vulkanCommandBuffer, bufferContainer->activeBuffer); - vulkanCommandBuffer->fragmentStorageBuffers[firstSlot + i] = bufferContainer->activeBuffer; + vulkanCommandBuffer->fragmentStorageBufferBindings[firstSlot + i] = bufferContainer->activeBuffer->buffer; vulkanCommandBuffer->needNewFragmentResourceDescriptorSet = true; } } @@ -7699,7 +7773,7 @@ static void VULKAN_BeginRenderPass( const SDL_GPUDepthStencilTargetInfo *depthStencilTargetInfo) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VkRenderPass renderPass; VulkanFramebuffer *framebuffer; @@ -7911,7 +7985,7 @@ static void VULKAN_BindGraphicsPipeline( SDL_GPUGraphicsPipeline *graphicsPipeline) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VulkanGraphicsPipeline *pipeline = (VulkanGraphicsPipeline *)graphicsPipeline; renderer->vkCmdBindPipeline( @@ -7957,11 +8031,11 @@ static void VULKAN_BindVertexBuffers( for (Uint32 i = 0; i < numBindings; i += 1) { VulkanBuffer *buffer = ((VulkanBufferContainer *)bindings[i].buffer)->activeBuffer; - if (vulkanCommandBuffer->vertexBuffers[i] != buffer->buffer || vulkanCommandBuffer->vertexBufferOffsets[i] != bindings[i].offset) { + if (vulkanCommandBuffer->vertexBuffers[firstSlot + i] != buffer->buffer || vulkanCommandBuffer->vertexBufferOffsets[firstSlot + i] != bindings[i].offset) { VULKAN_INTERNAL_TrackBuffer(vulkanCommandBuffer, buffer); - vulkanCommandBuffer->vertexBuffers[i] = buffer->buffer; - vulkanCommandBuffer->vertexBufferOffsets[i] = bindings[i].offset; + vulkanCommandBuffer->vertexBuffers[firstSlot + i] = buffer->buffer; + vulkanCommandBuffer->vertexBufferOffsets[firstSlot + i] = bindings[i].offset; vulkanCommandBuffer->needVertexBufferBind = true; } } @@ -7976,7 +8050,7 @@ static void VULKAN_BindIndexBuffer( SDL_GPUIndexElementSize indexElementSize) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VulkanBuffer *vulkanBuffer = ((VulkanBufferContainer *)binding->buffer)->activeBuffer; VULKAN_INTERNAL_TrackBuffer(vulkanCommandBuffer, vulkanBuffer); @@ -8024,7 +8098,7 @@ static void VULKAN_EndRenderPass( SDL_GPUCommandBuffer *commandBuffer) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; Uint32 i; renderer->vkCmdEndRenderPass( @@ -8073,15 +8147,15 @@ static void VULKAN_EndRenderPass( SDL_zeroa(vulkanCommandBuffer->vertexBufferOffsets); vulkanCommandBuffer->vertexBufferCount = 0; - SDL_zeroa(vulkanCommandBuffer->vertexSamplers); - SDL_zeroa(vulkanCommandBuffer->vertexSamplerTextures); - SDL_zeroa(vulkanCommandBuffer->vertexStorageTextures); - SDL_zeroa(vulkanCommandBuffer->vertexStorageBuffers); + SDL_zeroa(vulkanCommandBuffer->vertexSamplerBindings); + SDL_zeroa(vulkanCommandBuffer->vertexSamplerTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->vertexStorageTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->vertexStorageBufferBindings); - SDL_zeroa(vulkanCommandBuffer->fragmentSamplers); - SDL_zeroa(vulkanCommandBuffer->fragmentSamplerTextures); - SDL_zeroa(vulkanCommandBuffer->fragmentStorageTextures); - SDL_zeroa(vulkanCommandBuffer->fragmentStorageBuffers); + SDL_zeroa(vulkanCommandBuffer->fragmentSamplerBindings); + SDL_zeroa(vulkanCommandBuffer->fragmentSamplerTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->fragmentStorageTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->fragmentStorageBufferBindings); } static void VULKAN_BeginComputePass( @@ -8111,6 +8185,7 @@ static void VULKAN_BeginComputePass( VULKAN_TEXTURE_USAGE_MODE_COMPUTE_STORAGE_READ_WRITE); vulkanCommandBuffer->readWriteComputeStorageTextureSubresources[i] = subresource; + vulkanCommandBuffer->readWriteComputeStorageTextureViewBindings[i] = subresource->computeWriteView; VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, @@ -8124,9 +8199,10 @@ static void VULKAN_BeginComputePass( vulkanCommandBuffer, bufferContainer, storageBufferBindings[i].cycle, - VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ); + VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ_WRITE); vulkanCommandBuffer->readWriteComputeStorageBuffers[i] = buffer; + vulkanCommandBuffer->readWriteComputeStorageBufferBindings[i] = buffer->buffer; VULKAN_INTERNAL_TrackBuffer( vulkanCommandBuffer, @@ -8139,7 +8215,7 @@ static void VULKAN_BindComputePipeline( SDL_GPUComputePipeline *computePipeline) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VulkanComputePipeline *vulkanComputePipeline = (VulkanComputePipeline *)computePipeline; renderer->vkCmdBindPipeline( @@ -8178,21 +8254,21 @@ static void VULKAN_BindComputeSamplers( VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)textureSamplerBindings[i].texture; VulkanSampler *sampler = (VulkanSampler *)textureSamplerBindings[i].sampler; - if (vulkanCommandBuffer->computeSamplers[firstSlot + i] != sampler) { + if (vulkanCommandBuffer->computeSamplerBindings[firstSlot + i] != sampler->sampler) { VULKAN_INTERNAL_TrackSampler( vulkanCommandBuffer, sampler); - vulkanCommandBuffer->computeSamplers[firstSlot + i] = sampler; + vulkanCommandBuffer->computeSamplerBindings[firstSlot + i] = sampler->sampler; vulkanCommandBuffer->needNewComputeReadOnlyDescriptorSet = true; } - if (vulkanCommandBuffer->computeSamplerTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->computeSamplerTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->computeSamplerTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->computeSamplerTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewComputeReadOnlyDescriptorSet = true; } } @@ -8233,6 +8309,7 @@ static void VULKAN_BindComputeStorageTextures( textureContainer->activeTexture); vulkanCommandBuffer->readOnlyComputeStorageTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->readOnlyComputeStorageTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewComputeReadOnlyDescriptorSet = true; } } @@ -8272,6 +8349,7 @@ static void VULKAN_BindComputeStorageBuffers( bufferContainer->activeBuffer); vulkanCommandBuffer->readOnlyComputeStorageBuffers[firstSlot + i] = bufferContainer->activeBuffer; + vulkanCommandBuffer->readOnlyComputeStorageBufferBindings[firstSlot + i] = bufferContainer->activeBuffer->buffer; vulkanCommandBuffer->needNewComputeReadOnlyDescriptorSet = true; } } @@ -8346,8 +8424,8 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pBufferInfo = NULL; - imageInfos[imageInfoCount].sampler = commandBuffer->computeSamplers[i]->sampler; - imageInfos[imageInfoCount].imageView = commandBuffer->computeSamplerTextures[i]->fullView; + imageInfos[imageInfoCount].sampler = commandBuffer->computeSamplerBindings[i]; + imageInfos[imageInfoCount].imageView = commandBuffer->computeSamplerTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -8370,7 +8448,7 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pBufferInfo = NULL; imageInfos[imageInfoCount].sampler = VK_NULL_HANDLE; - imageInfos[imageInfoCount].imageView = commandBuffer->readOnlyComputeStorageTextures[i]->fullView; + imageInfos[imageInfoCount].imageView = commandBuffer->readOnlyComputeStorageTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_GENERAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -8392,7 +8470,7 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pImageInfo = NULL; - bufferInfos[bufferInfoCount].buffer = commandBuffer->readOnlyComputeStorageBuffers[i]->buffer; + bufferInfos[bufferInfoCount].buffer = commandBuffer->readOnlyComputeStorageBufferBindings[i]; bufferInfos[bufferInfoCount].offset = 0; bufferInfos[bufferInfoCount].range = VK_WHOLE_SIZE; @@ -8427,7 +8505,7 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pBufferInfo = NULL; imageInfos[imageInfoCount].sampler = VK_NULL_HANDLE; - imageInfos[imageInfoCount].imageView = commandBuffer->readWriteComputeStorageTextureSubresources[i]->computeWriteView; + imageInfos[imageInfoCount].imageView = commandBuffer->readWriteComputeStorageTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_GENERAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -8449,7 +8527,7 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pImageInfo = NULL; - bufferInfos[bufferInfoCount].buffer = commandBuffer->readWriteComputeStorageBuffers[i]->buffer; + bufferInfos[bufferInfoCount].buffer = commandBuffer->readWriteComputeStorageBufferBindings[i]; bufferInfos[bufferInfoCount].offset = 0; bufferInfos[bufferInfoCount].range = VK_WHOLE_SIZE; @@ -8524,7 +8602,7 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( dynamicOffsetCount, dynamicOffsets); - commandBuffer->needNewVertexUniformOffsets = false; + commandBuffer->needNewComputeUniformOffsets = false; } static void VULKAN_DispatchCompute( @@ -8534,7 +8612,7 @@ static void VULKAN_DispatchCompute( Uint32 groupcountZ) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VULKAN_INTERNAL_BindComputeDescriptorSets(renderer, vulkanCommandBuffer); @@ -8551,7 +8629,7 @@ static void VULKAN_DispatchComputeIndirect( Uint32 offset) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VulkanBuffer *vulkanBuffer = ((VulkanBufferContainer *)buffer)->activeBuffer; VULKAN_INTERNAL_BindComputeDescriptorSets(renderer, vulkanCommandBuffer); @@ -8616,9 +8694,12 @@ static void VULKAN_EndComputePass( } } - // we don't need a barrier because sampler state is always the default if sampler bit is set - SDL_zeroa(vulkanCommandBuffer->computeSamplerTextures); - SDL_zeroa(vulkanCommandBuffer->computeSamplers); + // we don't need a barrier for sampler resources because sampler state is always the default if sampler bit is set + SDL_zeroa(vulkanCommandBuffer->computeSamplerTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->computeSamplerBindings); + + SDL_zeroa(vulkanCommandBuffer->readWriteComputeStorageTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->readWriteComputeStorageBufferBindings); vulkanCommandBuffer->currentComputePipeline = NULL; @@ -8673,7 +8754,7 @@ static void VULKAN_UploadToTexture( bool cycle) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VulkanBufferContainer *transferBufferContainer = (VulkanBufferContainer *)source->transfer_buffer; VulkanTextureContainer *vulkanTextureContainer = (VulkanTextureContainer *)destination->texture; VulkanTextureSubresource *vulkanTextureSubresource; @@ -8729,7 +8810,7 @@ static void VULKAN_UploadToBuffer( bool cycle) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VulkanBufferContainer *transferBufferContainer = (VulkanBufferContainer *)source->transfer_buffer; VulkanBufferContainer *bufferContainer = (VulkanBufferContainer *)destination->buffer; VkBufferCopy bufferCopy; @@ -8872,7 +8953,7 @@ static void VULKAN_CopyTextureToTexture( bool cycle) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VulkanTextureSubresource *srcSubresource; VulkanTextureSubresource *dstSubresource; VkImageCopy imageCopy; @@ -8948,7 +9029,7 @@ static void VULKAN_CopyBufferToBuffer( bool cycle) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VulkanBufferContainer *srcContainer = (VulkanBufferContainer *)source->buffer; VulkanBufferContainer *dstContainer = (VulkanBufferContainer *)destination->buffer; VkBufferCopy bufferCopy; @@ -8998,7 +9079,7 @@ static void VULKAN_GenerateMipmaps( SDL_GPUTexture *texture) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VulkanTextureContainer *container = (VulkanTextureContainer *)texture; VulkanTextureSubresource *srcTextureSubresource; VulkanTextureSubresource *dstTextureSubresource; @@ -9099,7 +9180,7 @@ static void VULKAN_Blit( const SDL_GPUBlitInfo *info) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; TextureCommonHeader *srcHeader = (TextureCommonHeader *)info->source.texture; TextureCommonHeader *dstHeader = (TextureCommonHeader *)info->destination.texture; VkImageBlit region; @@ -9389,7 +9470,7 @@ static VulkanCommandPool *VULKAN_INTERNAL_FetchCommandPool( SDL_InsertIntoHashTable( renderer->commandPoolHashTable, (const void *)allocedKey, - (const void *)vulkanCommandPool); + (const void *)vulkanCommandPool, true); return vulkanCommandPool; } @@ -9484,21 +9565,23 @@ static SDL_GPUCommandBuffer *VULKAN_AcquireCommandBuffer( SDL_zeroa(commandBuffer->vertexBufferOffsets); commandBuffer->vertexBufferCount = 0; - SDL_zeroa(commandBuffer->vertexSamplerTextures); - SDL_zeroa(commandBuffer->vertexSamplers); - SDL_zeroa(commandBuffer->vertexStorageTextures); - SDL_zeroa(commandBuffer->vertexStorageBuffers); + SDL_zeroa(commandBuffer->vertexSamplerTextureViewBindings); + SDL_zeroa(commandBuffer->vertexSamplerBindings); + SDL_zeroa(commandBuffer->vertexStorageTextureViewBindings); + SDL_zeroa(commandBuffer->vertexStorageBufferBindings); - SDL_zeroa(commandBuffer->fragmentSamplerTextures); - SDL_zeroa(commandBuffer->fragmentSamplers); - SDL_zeroa(commandBuffer->fragmentStorageTextures); - SDL_zeroa(commandBuffer->fragmentStorageBuffers); + SDL_zeroa(commandBuffer->fragmentSamplerTextureViewBindings); + SDL_zeroa(commandBuffer->fragmentSamplerBindings); + SDL_zeroa(commandBuffer->fragmentStorageTextureViewBindings); + SDL_zeroa(commandBuffer->fragmentStorageBufferBindings); SDL_zeroa(commandBuffer->readWriteComputeStorageTextureSubresources); commandBuffer->readWriteComputeStorageTextureSubresourceCount = 0; SDL_zeroa(commandBuffer->readWriteComputeStorageBuffers); - SDL_zeroa(commandBuffer->computeSamplerTextures); - SDL_zeroa(commandBuffer->computeSamplers); + SDL_zeroa(commandBuffer->computeSamplerTextureViewBindings); + SDL_zeroa(commandBuffer->computeSamplerBindings); + SDL_zeroa(commandBuffer->readOnlyComputeStorageTextureViewBindings); + SDL_zeroa(commandBuffer->readOnlyComputeStorageBufferBindings); SDL_zeroa(commandBuffer->readOnlyComputeStorageTextures); SDL_zeroa(commandBuffer->readOnlyComputeStorageBuffers); @@ -9832,7 +9915,7 @@ static bool VULKAN_INTERNAL_AcquireSwapchainTexture( Uint32 *swapchainTextureHeight) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; Uint32 swapchainImageIndex; WindowData *windowData; VkResult acquireResult = VK_SUCCESS; @@ -9852,6 +9935,14 @@ static bool VULKAN_INTERNAL_AcquireSwapchainTexture( SET_STRING_ERROR_AND_RETURN("Cannot acquire a swapchain texture from an unclaimed window!", false); } + // The command buffer is flagged for cleanup when the swapchain is requested as a cleanup timing mechanism + vulkanCommandBuffer->swapchainRequested = true; + + if (window->flags & SDL_WINDOW_HIDDEN) { + // Edge case, texture is filled in with NULL but not an error + return true; + } + // If window data marked as needing swapchain recreate, try to recreate if (windowData->needsSwapchainRecreate) { Uint32 recreateSwapchainResult = VULKAN_INTERNAL_RecreateSwapchain(renderer, windowData); @@ -9869,13 +9960,6 @@ static bool VULKAN_INTERNAL_AcquireSwapchainTexture( } } - if (swapchainTextureWidth) { - *swapchainTextureWidth = windowData->width; - } - if (swapchainTextureHeight) { - *swapchainTextureHeight = windowData->height; - } - if (windowData->inFlightFences[windowData->frameCounter] != NULL) { if (block) { // If we are blocking, just wait for the fence! @@ -9927,6 +10011,14 @@ static bool VULKAN_INTERNAL_AcquireSwapchainTexture( } } + if (swapchainTextureWidth) { + *swapchainTextureWidth = windowData->width; + } + + if (swapchainTextureHeight) { + *swapchainTextureHeight = windowData->height; + } + swapchainTextureContainer = &windowData->textureContainers[swapchainImageIndex]; // We need a special execution dependency with pWaitDstStageMask or image transition can start before acquire finishes @@ -9995,7 +10087,7 @@ static bool VULKAN_INTERNAL_AcquireSwapchainTexture( } vulkanCommandBuffer->signalSemaphores[vulkanCommandBuffer->signalSemaphoreCount] = - windowData->renderFinishedSemaphore[windowData->frameCounter]; + windowData->renderFinishedSemaphore[swapchainImageIndex]; vulkanCommandBuffer->signalSemaphoreCount += 1; *swapchainTexture = (SDL_GPUTexture *)swapchainTextureContainer; @@ -10304,6 +10396,7 @@ static void VULKAN_INTERNAL_CleanCommandBuffer( commandBuffer->presentDataCount = 0; commandBuffer->waitSemaphoreCount = 0; commandBuffer->signalSemaphoreCount = 0; + commandBuffer->swapchainRequested = false; // Reset defrag state @@ -10450,7 +10543,7 @@ static bool VULKAN_Submit( SDL_GPUCommandBuffer *commandBuffer) { VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer; - VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer; + VulkanRenderer *renderer = vulkanCommandBuffer->renderer; VkSubmitInfo submitInfo; VkPresentInfoKHR presentInfo; VulkanPresentData *presentData; @@ -10458,9 +10551,10 @@ static bool VULKAN_Submit( VkPipelineStageFlags waitStages[MAX_PRESENT_COUNT]; Uint32 swapchainImageIndex; VulkanTextureSubresource *swapchainTextureSubresource; - Uint8 commandBufferCleaned = 0; VulkanMemorySubAllocator *allocator; - bool presenting = false; + bool performCleanups = + (renderer->claimedWindowCount > 0 && vulkanCommandBuffer->swapchainRequested) || + renderer->claimedWindowCount == 0; SDL_LockMutex(renderer->submitLock); @@ -10483,6 +10577,15 @@ static bool VULKAN_Submit( swapchainTextureSubresource); } + if (performCleanups && + renderer->allocationsToDefragCount > 0 && + !renderer->defragInProgress) { + if (!VULKAN_INTERNAL_DefragmentMemory(renderer, vulkanCommandBuffer)) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s", "Failed to defragment memory, likely OOM!"); + } + } + if (!VULKAN_INTERNAL_EndCommandBuffer(renderer, vulkanCommandBuffer)) { SDL_UnlockMutex(renderer->submitLock); return false; @@ -10520,17 +10623,13 @@ static bool VULKAN_Submit( } // Present, if applicable - bool result = true; - for (Uint32 j = 0; j < vulkanCommandBuffer->presentDataCount; j += 1) { - presenting = true; - presentData = &vulkanCommandBuffer->presentDatas[j]; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.pNext = NULL; presentInfo.pWaitSemaphores = - &presentData->windowData->renderFinishedSemaphore[presentData->windowData->frameCounter]; + &presentData->windowData->renderFinishedSemaphore[presentData->swapchainImageIndex]; presentInfo.waitSemaphoreCount = 1; presentInfo.pSwapchains = &presentData->windowData->swapchain; presentInfo.swapchainCount = 1; @@ -10562,60 +10661,50 @@ static bool VULKAN_Submit( (presentData->windowData->frameCounter + 1) % renderer->allowedFramesInFlight; } - // Check if we can perform any cleanups + if (performCleanups) { + for (Sint32 i = renderer->submittedCommandBufferCount - 1; i >= 0; i -= 1) { + vulkanResult = renderer->vkGetFenceStatus( + renderer->logicalDevice, + renderer->submittedCommandBuffers[i]->inFlightFence->fence); - for (Sint32 i = renderer->submittedCommandBufferCount - 1; i >= 0; i -= 1) { - vulkanResult = renderer->vkGetFenceStatus( - renderer->logicalDevice, - renderer->submittedCommandBuffers[i]->inFlightFence->fence); - - if (vulkanResult == VK_SUCCESS) { - VULKAN_INTERNAL_CleanCommandBuffer( - renderer, - renderer->submittedCommandBuffers[i], - false); - - commandBufferCleaned = 1; - } - } - - if (commandBufferCleaned) { - SDL_LockMutex(renderer->allocatorLock); - - for (Uint32 i = 0; i < VK_MAX_MEMORY_TYPES; i += 1) { - allocator = &renderer->memoryAllocator->subAllocators[i]; - - for (Sint32 j = allocator->allocationCount - 1; j >= 0; j -= 1) { - if (allocator->allocations[j]->usedRegionCount == 0) { - VULKAN_INTERNAL_DeallocateMemory( - renderer, - allocator, - j); - } + if (vulkanResult == VK_SUCCESS) { + VULKAN_INTERNAL_CleanCommandBuffer( + renderer, + renderer->submittedCommandBuffers[i], + false); } } - SDL_UnlockMutex(renderer->allocatorLock); - } + if (renderer->checkEmptyAllocations) { + SDL_LockMutex(renderer->allocatorLock); - // Check pending destroys - VULKAN_INTERNAL_PerformPendingDestroys(renderer); + for (Uint32 i = 0; i < VK_MAX_MEMORY_TYPES; i += 1) { + allocator = &renderer->memoryAllocator->subAllocators[i]; - // Defrag! - if ( - presenting && - renderer->allocationsToDefragCount > 0 && - !renderer->defragInProgress) { - result = VULKAN_INTERNAL_DefragmentMemory(renderer); + for (Sint32 j = allocator->allocationCount - 1; j >= 0; j -= 1) { + if (allocator->allocations[j]->usedRegionCount == 0) { + VULKAN_INTERNAL_DeallocateMemory( + renderer, + allocator, + j); + } + } + } + + renderer->checkEmptyAllocations = false; + + SDL_UnlockMutex(renderer->allocatorLock); + } + + VULKAN_INTERNAL_PerformPendingDestroys(renderer); } // Mark command buffer as submitted - // This must happen after defrag, because it will try to acquire new command buffers. VULKAN_INTERNAL_ReleaseCommandBuffer(vulkanCommandBuffer); SDL_UnlockMutex(renderer->submitLock); - return result; + return true; } static bool VULKAN_Cancel( @@ -10642,43 +10731,28 @@ static bool VULKAN_Cancel( } static bool VULKAN_INTERNAL_DefragmentMemory( - VulkanRenderer *renderer) + VulkanRenderer *renderer, + VulkanCommandBuffer *commandBuffer) { - VulkanMemoryAllocation *allocation; - VulkanMemoryUsedRegion *currentRegion; - VulkanBuffer *newBuffer; - VulkanTexture *newTexture; - VkBufferCopy bufferCopy; - VkImageCopy imageCopy; - VulkanCommandBuffer *commandBuffer; - VulkanTextureSubresource *srcSubresource; - VulkanTextureSubresource *dstSubresource; - Uint32 i, subresourceIndex; - renderer->defragInProgress = 1; - - commandBuffer = (VulkanCommandBuffer *)VULKAN_AcquireCommandBuffer((SDL_GPURenderer *)renderer); - if (commandBuffer == NULL) { - return false; - } commandBuffer->isDefrag = 1; SDL_LockMutex(renderer->allocatorLock); - allocation = renderer->allocationsToDefrag[renderer->allocationsToDefragCount - 1]; + VulkanMemoryAllocation *allocation = renderer->allocationsToDefrag[renderer->allocationsToDefragCount - 1]; renderer->allocationsToDefragCount -= 1; /* For each used region in the allocation * create a new resource, copy the data * and re-point the resource containers */ - for (i = 0; i < allocation->usedRegionCount; i += 1) { - currentRegion = allocation->usedRegions[i]; + for (Uint32 i = 0; i < allocation->usedRegionCount; i += 1) { + VulkanMemoryUsedRegion *currentRegion = allocation->usedRegions[i]; if (currentRegion->isBuffer && !currentRegion->vulkanBuffer->markedForDestroy) { currentRegion->vulkanBuffer->usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; - newBuffer = VULKAN_INTERNAL_CreateBuffer( + VulkanBuffer *newBuffer = VULKAN_INTERNAL_CreateBuffer( renderer, currentRegion->vulkanBuffer->size, currentRegion->vulkanBuffer->usage, @@ -10688,6 +10762,7 @@ static bool VULKAN_INTERNAL_DefragmentMemory( if (newBuffer == NULL) { SDL_UnlockMutex(renderer->allocatorLock); + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s", "Failed to allocate defrag buffer!"); return false; } @@ -10706,6 +10781,7 @@ static bool VULKAN_INTERNAL_DefragmentMemory( VULKAN_BUFFER_USAGE_MODE_COPY_DESTINATION, newBuffer); + VkBufferCopy bufferCopy; bufferCopy.srcOffset = 0; bufferCopy.dstOffset = 0; bufferCopy.size = currentRegion->resourceSize; @@ -10745,20 +10821,22 @@ static bool VULKAN_INTERNAL_DefragmentMemory( VULKAN_INTERNAL_ReleaseBuffer(renderer, currentRegion->vulkanBuffer); } else if (!currentRegion->isBuffer && !currentRegion->vulkanTexture->markedForDestroy) { - newTexture = VULKAN_INTERNAL_CreateTexture( + VulkanTexture *newTexture = VULKAN_INTERNAL_CreateTexture( renderer, + false, ¤tRegion->vulkanTexture->container->header.info); if (newTexture == NULL) { SDL_UnlockMutex(renderer->allocatorLock); + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s", "Failed to allocate defrag buffer!"); return false; } SDL_GPUTextureCreateInfo info = currentRegion->vulkanTexture->container->header.info; - for (subresourceIndex = 0; subresourceIndex < currentRegion->vulkanTexture->subresourceCount; subresourceIndex += 1) { + for (Uint32 subresourceIndex = 0; subresourceIndex < currentRegion->vulkanTexture->subresourceCount; subresourceIndex += 1) { // copy subresource if necessary - srcSubresource = ¤tRegion->vulkanTexture->subresources[subresourceIndex]; - dstSubresource = &newTexture->subresources[subresourceIndex]; + VulkanTextureSubresource *srcSubresource = ¤tRegion->vulkanTexture->subresources[subresourceIndex]; + VulkanTextureSubresource *dstSubresource = &newTexture->subresources[subresourceIndex]; VULKAN_INTERNAL_TextureSubresourceTransitionFromDefaultUsage( renderer, @@ -10766,12 +10844,14 @@ static bool VULKAN_INTERNAL_DefragmentMemory( VULKAN_TEXTURE_USAGE_MODE_COPY_SOURCE, srcSubresource); - VULKAN_INTERNAL_TextureSubresourceTransitionFromDefaultUsage( + VULKAN_INTERNAL_TextureSubresourceMemoryBarrier( renderer, commandBuffer, + VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, VULKAN_TEXTURE_USAGE_MODE_COPY_DESTINATION, dstSubresource); + VkImageCopy imageCopy; imageCopy.srcOffset.x = 0; imageCopy.srcOffset.y = 0; imageCopy.srcOffset.z = 0; @@ -10823,8 +10903,7 @@ static bool VULKAN_INTERNAL_DefragmentMemory( SDL_UnlockMutex(renderer->allocatorLock); - return VULKAN_Submit( - (SDL_GPUCommandBuffer *)commandBuffer); + return true; } // Format Info @@ -10891,7 +10970,7 @@ static inline Uint8 CheckDeviceExtensions( supports->ext = 1; \ } CHECK(KHR_swapchain) - else CHECK(KHR_maintenance1) else CHECK(KHR_driver_properties) else CHECK(EXT_vertex_attribute_divisor) else CHECK(KHR_portability_subset) else CHECK(EXT_texture_compression_astc_hdr) + else CHECK(KHR_maintenance1) else CHECK(KHR_driver_properties) else CHECK(KHR_portability_subset) else CHECK(EXT_texture_compression_astc_hdr) #undef CHECK } @@ -10905,7 +10984,6 @@ static inline Uint32 GetDeviceExtensionCount(VulkanExtensions *supports) supports->KHR_swapchain + supports->KHR_maintenance1 + supports->KHR_driver_properties + - supports->EXT_vertex_attribute_divisor + supports->KHR_portability_subset + supports->EXT_texture_compression_astc_hdr); } @@ -10922,7 +11000,6 @@ static inline void CreateDeviceExtensionArray( CHECK(KHR_swapchain) CHECK(KHR_maintenance1) CHECK(KHR_driver_properties) - CHECK(EXT_vertex_attribute_divisor) CHECK(KHR_portability_subset) CHECK(EXT_texture_compression_astc_hdr) #undef CHECK @@ -11223,7 +11300,7 @@ static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( &queueFamilyCount, NULL); - queueProps = (VkQueueFamilyProperties *)SDL_stack_alloc( + queueProps = SDL_stack_alloc( VkQueueFamilyProperties, queueFamilyCount); renderer->vkGetPhysicalDeviceQueueFamilyProperties( @@ -11599,7 +11676,7 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this) { // Set up dummy VulkanRenderer VulkanRenderer *renderer; - Uint8 result; + bool result = false; if (_this->Vulkan_CreateSurface == NULL) { return false; @@ -11609,16 +11686,16 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this) return false; } - renderer = (VulkanRenderer *)SDL_malloc(sizeof(VulkanRenderer)); - SDL_memset(renderer, '\0', sizeof(VulkanRenderer)); - - result = VULKAN_INTERNAL_PrepareVulkan(renderer); - - if (result) { - renderer->vkDestroyInstance(renderer->instance, NULL); + renderer = (VulkanRenderer *)SDL_calloc(1, sizeof(*renderer)); + if (renderer) { + result = VULKAN_INTERNAL_PrepareVulkan(renderer); + if (result) { + renderer->vkDestroyInstance(renderer->instance, NULL); + } + SDL_free(renderer); } - SDL_free(renderer); SDL_Vulkan_UnloadLibrary(); + return result; } @@ -11634,16 +11711,21 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S return NULL; } - renderer = (VulkanRenderer *)SDL_malloc(sizeof(VulkanRenderer)); - SDL_memset(renderer, '\0', sizeof(VulkanRenderer)); + renderer = (VulkanRenderer *)SDL_calloc(1, sizeof(*renderer)); + if (!renderer) { + SDL_Vulkan_UnloadLibrary(); + return NULL; + } + renderer->debugMode = debugMode; renderer->preferLowPower = preferLowPower; renderer->allowedFramesInFlight = 2; if (!VULKAN_INTERNAL_PrepareVulkan(renderer)) { + SET_STRING_ERROR("Failed to initialize Vulkan!"); SDL_free(renderer); SDL_Vulkan_UnloadLibrary(); - SET_STRING_ERROR_AND_RETURN("Failed to initialize Vulkan!", NULL); + return NULL; } SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: Vulkan"); @@ -11669,9 +11751,10 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S if (!VULKAN_INTERNAL_CreateLogicalDevice( renderer)) { + SET_STRING_ERROR("Failed to create logical device!"); SDL_free(renderer); SDL_Vulkan_UnloadLibrary(); - SET_STRING_ERROR_AND_RETURN("Failed to create logical device!", NULL); + return NULL; } // FIXME: just move this into this function @@ -11679,6 +11762,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S ASSIGN_DRIVER(VULKAN) result->driverData = (SDL_GPURenderer *)renderer; + result->shader_formats = SDL_GPU_SHADERFORMAT_SPIRV; /* * Create initial swapchain array @@ -11696,7 +11780,11 @@ 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->graphicsPipelineLayoutFetchLock = SDL_CreateMutex(); + renderer->computePipelineLayoutFetchLock = SDL_CreateMutex(); + renderer->descriptorSetLayoutFetchLock = SDL_CreateMutex(); renderer->windowLock = SDL_CreateMutex(); /* @@ -11747,59 +11835,53 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S // Initialize caches - // manually synchronized due to submission timing renderer->commandPoolHashTable = SDL_CreateHashTable( - (void *)renderer, - 64, + 0, // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful. + false, // manually synchronized due to submission timing VULKAN_INTERNAL_CommandPoolHashFunction, VULKAN_INTERNAL_CommandPoolHashKeyMatch, - VULKAN_INTERNAL_CommandPoolHashNuke, - false, false); + VULKAN_INTERNAL_CommandPoolHashDestroy, + (void *)renderer); - // thread-safe renderer->renderPassHashTable = SDL_CreateHashTable( - (void *)renderer, - 64, + 0, // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful. + false, // manually synchronized due to lookup timing VULKAN_INTERNAL_RenderPassHashFunction, VULKAN_INTERNAL_RenderPassHashKeyMatch, - VULKAN_INTERNAL_RenderPassHashNuke, - true, false); + VULKAN_INTERNAL_RenderPassHashDestroy, + (void *)renderer); - // manually synchronized due to iteration renderer->framebufferHashTable = SDL_CreateHashTable( - (void *)renderer, - 64, + 0, // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful. + false, // manually synchronized due to iteration VULKAN_INTERNAL_FramebufferHashFunction, VULKAN_INTERNAL_FramebufferHashKeyMatch, - VULKAN_INTERNAL_FramebufferHashNuke, - false, false); + VULKAN_INTERNAL_FramebufferHashDestroy, + (void *)renderer); - // thread-safe renderer->graphicsPipelineResourceLayoutHashTable = SDL_CreateHashTable( - (void *)renderer, - 64, + 0, // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful. + false, // manually synchronized due to lookup timing VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashFunction, VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashKeyMatch, - VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashNuke, - true, false); + VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashDestroy, + (void *)renderer); - // thread-safe renderer->computePipelineResourceLayoutHashTable = SDL_CreateHashTable( - (void *)renderer, - 64, + 0, // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful. + false, // manually synchronized due to lookup timing VULKAN_INTERNAL_ComputePipelineResourceLayoutHashFunction, VULKAN_INTERNAL_ComputePipelineResourceLayoutHashKeyMatch, - VULKAN_INTERNAL_ComputePipelineResourceLayoutHashNuke, - true, false); + VULKAN_INTERNAL_ComputePipelineResourceLayoutHashDestroy, + (void *)renderer); - // thread-safe renderer->descriptorSetLayoutHashTable = SDL_CreateHashTable( - (void *)renderer, - 64, + 0, // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful. + false, // manually synchronized due to lookup timing VULKAN_INTERNAL_DescriptorSetLayoutHashFunction, VULKAN_INTERNAL_DescriptorSetLayoutHashKeyMatch, - VULKAN_INTERNAL_DescriptorSetLayoutHashNuke, - true, false); + VULKAN_INTERNAL_DescriptorSetLayoutHashDestroy, + (void *)renderer); // Initialize fence pool diff --git a/libs/SDL3/src/haptic/SDL_haptic.c b/libs/SDL3/src/haptic/SDL_haptic.c index 671a702..1c11db6 100644 --- a/libs/SDL3/src/haptic/SDL_haptic.c +++ b/libs/SDL3/src/haptic/SDL_haptic.c @@ -23,6 +23,84 @@ #include "SDL_syshaptic.h" #include "SDL_haptic_c.h" #include "../joystick/SDL_joystick_c.h" // For SDL_IsJoystickValid +#include "../SDL_hints_c.h" + +typedef struct SDL_Haptic_VIDPID_Naxes { + Uint16 vid; + Uint16 pid; + Uint16 naxes; +} SDL_Haptic_VIDPID_Naxes; + +static void SDL_Haptic_Load_Axes_List(SDL_Haptic_VIDPID_Naxes **entries, int *num_entries) +{ + SDL_Haptic_VIDPID_Naxes entry; + const char *spot; + int length = 0; + + spot = SDL_GetHint(SDL_HINT_JOYSTICK_HAPTIC_AXES); + if (!spot) + return; + + while (SDL_sscanf(spot, "0x%hx/0x%hx/%hu%n", &entry.vid, &entry.pid, &entry.naxes, &length) == 3) { + SDL_assert(length > 0); + spot += length; + length = 0; + + if ((*num_entries % 8) == 0) { + int new_max = *num_entries + 8; + SDL_Haptic_VIDPID_Naxes *new_entries = + (SDL_Haptic_VIDPID_Naxes *)SDL_realloc(*entries, new_max * sizeof(**entries)); + + // Out of memory, go with what we have already + if (!new_entries) + break; + + *entries = new_entries; + } + (*entries)[(*num_entries)++] = entry; + + if (spot[0] == ',') + spot++; + } +} + +// /* Return -1 if not found */ +static int SDL_Haptic_Naxes_List_Index(struct SDL_Haptic_VIDPID_Naxes *entries, int num_entries, Uint16 vid, Uint16 pid) +{ + if (!entries) + return -1; + + int i; + for (i = 0; i < num_entries; ++i) { + if (entries[i].vid == vid && entries[i].pid == pid) + return i; + } + + return -1; +} + +// Check if device needs a custom number of naxes +static int SDL_Haptic_Get_Naxes(Uint16 vid, Uint16 pid) +{ + int num_entries = 0, index = 0, naxes = -1; + SDL_Haptic_VIDPID_Naxes *naxes_list = NULL; + + SDL_Haptic_Load_Axes_List(&naxes_list, &num_entries); + if (!num_entries || !naxes_list) + return -1; + + // Perform "wildcard" pass + index = SDL_Haptic_Naxes_List_Index(naxes_list, num_entries, 0xffff, 0xffff); + if (index >= 0) + naxes = naxes_list[index].naxes; + + index = SDL_Haptic_Naxes_List_Index(naxes_list, num_entries, vid, pid); + if (index >= 0) + naxes = naxes_list[index].naxes; + + SDL_free(naxes_list); + return naxes; +} static SDL_Haptic *SDL_haptics = NULL; @@ -282,6 +360,19 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) } SDL_UnlockJoysticks(); + // Check if custom number of haptic axes was defined + Uint16 vid = SDL_GetJoystickVendor(joystick); + Uint16 pid = SDL_GetJoystickProduct(joystick); + int general_axes = SDL_GetNumJoystickAxes(joystick); + + int naxes = SDL_Haptic_Get_Naxes(vid, pid); + if (naxes > 0) + haptic->naxes = naxes; + + // Limit to the actual number of axes found on the device + if (general_axes >= 0 && naxes > general_axes) + haptic->naxes = general_axes; + // Add haptic to list ++haptic->ref_count; // Link the haptic in the list diff --git a/libs/SDL3/src/haptic/windows/SDL_dinputhaptic.c b/libs/SDL3/src/haptic/windows/SDL_dinputhaptic.c index 255aac0..79c4b35 100644 --- a/libs/SDL3/src/haptic/windows/SDL_dinputhaptic.c +++ b/libs/SDL3/src/haptic/windows/SDL_dinputhaptic.c @@ -31,11 +31,7 @@ /* * External stuff. */ -#ifdef SDL_VIDEO_DRIVER_WINDOWS extern HWND SDL_HelperWindow; -#else -static const HWND SDL_HelperWindow = NULL; -#endif /* * Internal stuff. diff --git a/libs/SDL3/src/hidapi/SDL_hidapi.c b/libs/SDL3/src/hidapi/SDL_hidapi.c index b14b75e..2378db6 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi.c +++ b/libs/SDL3/src/hidapi/SDL_hidapi.c @@ -807,6 +807,8 @@ typedef struct LIBUSB_hid_device_ LIBUSB_hid_device; #define hid_send_feature_report LIBUSB_hid_send_feature_report #define hid_set_nonblocking LIBUSB_hid_set_nonblocking #define hid_write LIBUSB_hid_write +#define hid_version LIBUSB_hid_version +#define hid_version_str LIBUSB_hid_version_str #define input_report LIBUSB_input_report #define make_path LIBUSB_make_path #define new_hid_device LIBUSB_new_hid_device diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_windows.h b/libs/SDL3/src/hidapi/SDL_hidapi_windows.h index c29122b..91f71c2 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_windows.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_windows.h @@ -62,6 +62,10 @@ #define wcsstr SDL_wcsstr #define wcstol SDL_wcstol +// These functions conflict when linking both SDL and hidapi statically +#define hid_winapi_descriptor_reconstruct_pp_data SDL_hid_winapi_descriptor_reconstruct_pp_data +#define hid_winapi_get_container_id SDL_hid_winapi_get_container_id + #undef HIDAPI_H__ #include "windows/hid.c" #define HAVE_PLATFORM_BACKEND 1 diff --git a/libs/SDL3/src/hidapi/libusb/hid.c b/libs/SDL3/src/hidapi/libusb/hid.c index f4b1ccb..21fa1c0 100644 --- a/libs/SDL3/src/hidapi/libusb/hid.c +++ b/libs/SDL3/src/hidapi/libusb/hid.c @@ -71,6 +71,11 @@ extern "C" { #define DETACH_KERNEL_DRIVER #endif +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:5287) /* operands are different enum types */ +#endif + /* Uncomment to enable the retrieval of Usage and Usage Page in hid_enumerate(). Warning, on platforms different from FreeBSD this is very invasive as it requires the detach @@ -918,6 +923,22 @@ static int should_enumerate_interface(unsigned short vendor_id, const struct lib return 0; } +static int libusb_blacklist(unsigned short vendor_id, unsigned short product_id) +{ + size_t i; + static const struct { unsigned short vid; unsigned short pid; } known_bad[] = { + { 0x1532, 0x0227 } /* Razer Huntsman Gaming keyboard - long delay asking for device details */ + }; + + for (i = 0; i < (sizeof(known_bad)/sizeof(known_bad[0])); i++) { + if ((vendor_id == known_bad[i].vid) && (product_id == known_bad[i].pid || known_bad[i].pid == 0x0000)) { + return 1; + } + } + + return 0; +} + struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) { libusb_device **devs; @@ -948,7 +969,8 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short dev_pid = desc.idProduct; if ((vendor_id != 0x0 && vendor_id != dev_vid) || - (product_id != 0x0 && product_id != dev_pid)) { + (product_id != 0x0 && product_id != dev_pid) || + libusb_blacklist(dev_vid, dev_pid)) { continue; } @@ -1238,6 +1260,7 @@ static void init_xbox360(libusb_device_handle *device_handle, unsigned short idV (void)conf_desc; if ((idVendor == 0x05ac && idProduct == 0x055b) /* Gamesir-G3w */ || + (idVendor == 0x20d6 && idProduct == 0x4010) /* PowerA Battle Dragon Advanced Wireless Controller */ || idVendor == 0x0f0d /* Hori Xbox controllers */) { unsigned char data[20]; @@ -2139,6 +2162,10 @@ uint16_t get_usb_code_for_current_locale(void) return 0x0; } +#if defined(_MSC_VER) +#pragma warning (pop) +#endif + #ifdef __cplusplus } #endif diff --git a/libs/SDL3/src/hidapi/linux/hid.c b/libs/SDL3/src/hidapi/linux/hid.c index dbbb327..fa303d7 100644 --- a/libs/SDL3/src/hidapi/linux/hid.c +++ b/libs/SDL3/src/hidapi/linux/hid.c @@ -134,7 +134,6 @@ static wchar_t *utf8_to_wchar_t(const char *utf8) * Use register_error_str(NULL) to free the error message completely. */ static void register_error_str(wchar_t **error_str, const char *msg) { - free(*error_str); #ifdef HIDAPI_USING_SDL_RUNTIME /* Thread-safe error handling */ if (msg) { @@ -143,6 +142,7 @@ static void register_error_str(wchar_t **error_str, const char *msg) SDL_ClearError(); } #else + free(*error_str); *error_str = utf8_to_wchar_t(msg); #endif } diff --git a/libs/SDL3/src/hidapi/windows/hid.c b/libs/SDL3/src/hidapi/windows/hid.c index 3376ba9..87aa639 100644 --- a/libs/SDL3/src/hidapi/windows/hid.c +++ b/libs/SDL3/src/hidapi/windows/hid.c @@ -949,6 +949,7 @@ static int hid_blacklist(unsigned short vendor_id, unsigned short product_id) { 0x0D8C, 0x0014 }, /* Sharkoon Skiller SGH2 headset - causes deadlock asking for device details */ { 0x1532, 0x0109 }, /* Razer Lycosa Gaming keyboard - causes deadlock asking for device details */ { 0x1532, 0x010B }, /* Razer Arctosa Gaming keyboard - causes deadlock asking for device details */ + { 0x1532, 0x0227 }, /* Razer Huntsman Gaming keyboard - long delay asking for device details */ { 0x1B1C, 0x1B3D }, /* Corsair Gaming keyboard - causes deadlock asking for device details */ { 0x1CCF, 0x0000 } /* All Konami Amusement Devices - causes deadlock asking for device details */ }; diff --git a/libs/SDL3/src/io/SDL_asyncio.c b/libs/SDL3/src/io/SDL_asyncio.c index 3d1658d..51adf6b 100644 --- a/libs/SDL3/src/io/SDL_asyncio.c +++ b/libs/SDL3/src/io/SDL_asyncio.c @@ -57,12 +57,14 @@ SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode) } SDL_AsyncIO *asyncio = (SDL_AsyncIO *)SDL_calloc(1, sizeof(*asyncio)); - if (asyncio) { - asyncio->lock = SDL_CreateMutex(); - if (!asyncio->lock) { - SDL_free(asyncio); - return NULL; - } + if (!asyncio) { + return NULL; + } + + asyncio->lock = SDL_CreateMutex(); + if (!asyncio->lock) { + SDL_free(asyncio); + return NULL; } if (!SDL_SYS_AsyncIOFromFile(file, binary_mode, asyncio)) { @@ -307,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 2efa9c3..5bb2808 100644 --- a/libs/SDL3/src/joystick/SDL_gamepad.c +++ b/libs/SDL3/src/joystick/SDL_gamepad.c @@ -311,7 +311,7 @@ void SDL_PrivateGamepadAdded(SDL_JoystickID instance_id) { SDL_Event event; - if (!SDL_gamepads_initialized) { + if (!SDL_gamepads_initialized || SDL_IsJoystickBeingAdded()) { return; } @@ -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; @@ -428,7 +430,7 @@ static bool SDLCALL SDL_GamepadEventWatcher(void *userdata, SDL_Event *event) orientation, so when it's changed orientation to be used as a gamepad, change the sensor orientation to match. */ -static void AdjustSensorOrientation(SDL_Joystick *joystick, float *src, float *dst) +static void AdjustSensorOrientation(const SDL_Joystick *joystick, const float *src, float *dst) { unsigned int i, j; @@ -562,12 +564,10 @@ static void PopMappingChangeTracking(void) GamepadMapping_t *old_mapping = gamepad ? gamepad->mapping : tracker->joystick_mappings[i]; if (new_mapping && !old_mapping) { - SDL_RemoveFromHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick); - SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)true); + SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)true, true); SDL_PrivateGamepadAdded(joystick); } else if (old_mapping && !new_mapping) { - SDL_RemoveFromHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick); - SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)false); + SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)false, true); SDL_PrivateGamepadRemoved(joystick); } else if (old_mapping != new_mapping || HasMappingChangeTracking(tracker, new_mapping)) { if (gamepad) { @@ -731,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)); @@ -775,13 +775,7 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) // All other gamepads have the standard set of 19 buttons and 6 axes 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,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", sizeof(mapping_string)); - if (SDL_IsJoystickXboxSeriesX(vendor, product)) { - // XBox Series X Controllers have a share button under the guide button - SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); - } else if (SDL_IsJoystickXboxOneElite(vendor, product)) { - // XBox One Elite Controllers have 4 back paddle buttons - SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,", sizeof(mapping_string)); - } else if (SDL_IsJoystickSteamController(vendor, product)) { + if (SDL_IsJoystickSteamController(vendor, product)) { // Steam controllers have 2 back paddle buttons SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,", sizeof(mapping_string)); } else if (SDL_IsJoystickNintendoSwitchPro(vendor, product) || @@ -822,6 +816,15 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) SDL_strlcat(mapping_string, "paddle1:b16,paddle2:b15,paddle3:b14,paddle4:b13,", sizeof(mapping_string)); } break; + case SDL_GAMEPAD_TYPE_XBOXONE: + if (SDL_IsJoystickXboxOneElite(vendor, product)) { + // XBox One Elite Controllers have 4 back paddle buttons + SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,", sizeof(mapping_string)); + } else if (SDL_IsJoystickXboxSeriesX(vendor, product)) { + // XBox Series X Controllers have a share button under the guide button + SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); + } + break; default: if (vendor == 0 && product == 0) { // This is a Bluetooth Nintendo Switch Pro controller @@ -910,7 +913,7 @@ static GamepadMapping_t *SDL_PrivateMatchGamepadMappingForGUID(SDL_GUID guid, bo // An exact match, including CRC return mapping; } else if (crc && exact_match_crc) { - return NULL; + continue; } if (!best_match) { @@ -1396,17 +1399,22 @@ static void SDL_UpdateGamepadFaceStyle(SDL_Gamepad *gamepad) static void SDL_FixupHIDAPIMapping(SDL_Gamepad *gamepad) { // Check to see if we need fixup + bool need_fixup = false; for (int i = 0; i < gamepad->num_bindings; ++i) { SDL_GamepadBinding *binding = &gamepad->bindings[i]; if (binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON && - binding->output.button == SDL_GAMEPAD_BUTTON_DPAD_UP) { - if (binding->input_type != SDL_GAMEPAD_BINDTYPE_BUTTON || - binding->input.button != SDL_GAMEPAD_BUTTON_DPAD_UP) { - // New style binding - return; + binding->output.button >= SDL_GAMEPAD_BUTTON_DPAD_UP) { + if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON && + binding->input.button == binding->output.button) { + // Old style binding + need_fixup = true; } + break; } } + if (!need_fixup) { + return; + } for (int i = 0; i < gamepad->num_bindings; ++i) { SDL_GamepadBinding *binding = &gamepad->bindings[i]; @@ -1778,6 +1786,11 @@ static GamepadMapping_t *SDL_PrivateGenerateAutomaticGamepadMapping(const char * char name_string[128]; char mapping[1024]; + // Remove the CRC from the GUID + // We already know that this GUID doesn't have a mapping without the CRC, and we want newly + // added mappings without a CRC to override this mapping. + SDL_SetJoystickGUIDCRC(&guid, 0); + // Remove any commas in the name SDL_strlcpy(name_string, name, sizeof(name_string)); { @@ -2637,9 +2650,9 @@ bool SDL_IsGamepad(SDL_JoystickID instance_id) } if (!s_gamepadInstanceIDs) { - s_gamepadInstanceIDs = SDL_CreateHashTable(NULL, 4, SDL_HashID, SDL_KeyMatchID, NULL, false, false); + s_gamepadInstanceIDs = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, NULL, NULL); } - SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, (void *)(uintptr_t)result); + SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, (void *)(uintptr_t)result, true); } } SDL_UnlockJoysticks(); @@ -2746,6 +2759,7 @@ SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) gamepad->joystick = SDL_OpenJoystick(instance_id); if (!gamepad->joystick) { + SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false); SDL_free(gamepad); SDL_UnlockJoysticks(); return NULL; @@ -2754,6 +2768,7 @@ SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) if (gamepad->joystick->naxes) { gamepad->last_match_axis = (SDL_GamepadBinding **)SDL_calloc(gamepad->joystick->naxes, sizeof(*gamepad->last_match_axis)); if (!gamepad->last_match_axis) { + SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false); SDL_CloseJoystick(gamepad->joystick); SDL_free(gamepad); SDL_UnlockJoysticks(); @@ -2763,6 +2778,7 @@ SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) if (gamepad->joystick->nhats) { gamepad->last_hat_mask = (Uint8 *)SDL_calloc(gamepad->joystick->nhats, sizeof(*gamepad->last_hat_mask)); if (!gamepad->last_hat_mask) { + SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false); SDL_CloseJoystick(gamepad->joystick); SDL_free(gamepad->last_match_axis); SDL_free(gamepad); @@ -2824,7 +2840,7 @@ bool SDL_GamepadHasAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis) */ Sint16 SDL_GetGamepadAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis) { - Sint16 result = true; + Sint16 result = 0; SDL_LockJoysticks(); { @@ -3054,7 +3070,7 @@ SDL_GamepadButtonLabel SDL_GetGamepadButtonLabel(SDL_Gamepad *gamepad, SDL_Gamep */ int SDL_GetNumGamepadTouchpads(SDL_Gamepad *gamepad) { - int result = true; + int result = 0; SDL_LockJoysticks(); { @@ -3073,7 +3089,7 @@ int SDL_GetNumGamepadTouchpads(SDL_Gamepad *gamepad) */ int SDL_GetNumGamepadTouchpadFingers(SDL_Gamepad *gamepad, int touchpad) { - int result = true; + int result = 0; SDL_LockJoysticks(); { @@ -3081,8 +3097,6 @@ int SDL_GetNumGamepadTouchpadFingers(SDL_Gamepad *gamepad, int touchpad) if (joystick) { if (touchpad >= 0 && touchpad < joystick->ntouchpads) { result = joystick->touchpads[touchpad].nfingers; - } else { - result = SDL_InvalidParamError("touchpad"); } } } @@ -3326,7 +3340,7 @@ SDL_JoystickID SDL_GetGamepadID(SDL_Gamepad *gamepad) SDL_PropertiesID SDL_GetGamepadProperties(SDL_Gamepad *gamepad) { - SDL_PropertiesID result = true; + SDL_PropertiesID result = 0; SDL_LockJoysticks(); { diff --git a/libs/SDL3/src/joystick/SDL_gamepad_db.h b/libs/SDL3/src/joystick/SDL_gamepad_db.h index cd84e0a..1222cea 100644 --- a/libs/SDL3/src/joystick/SDL_gamepad_db.h +++ b/libs/SDL3/src/joystick/SDL_gamepad_db.h @@ -50,6 +50,7 @@ static const char *s_GamepadMappings[] = { "03000000c82d00001038000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000650000000000000,8BitDo M30 Gamepad,a:b0,b:b1,back:b10,guide:b2,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,", "03000000c82d00005106000000000000,8BitDo M30 Gamepad,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b10,guide:b2,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,", + "03000000c82d00002090000000000000,8BitDo Micro gamepad,a:b1,b:b0,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,guide:b12,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,righttrigger:b9,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001590000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00006528000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000003512000012ab000000000000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", @@ -214,6 +215,7 @@ static const char *s_GamepadMappings[] = { "03000000362800000100000000000000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,x:b1,y:b2,", "03000000782300000a10000000000000,Onlive Wireless Controller,a:b15,b:b14,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b11,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b13,y:b12,", "030000006b14000001a1000000000000,Orange Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,", + "0300000009120000072f000000000000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:-a2,leftx:a0,lefty:a1,righttrigger:-a5,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", "03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,", "030000006f0e00000901000000000000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "03000000632500002306000000000000,PS Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", @@ -299,7 +301,6 @@ static const char *s_GamepadMappings[] = { "03000000a30600002106000000000000,Saitek PS1000,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:a4,start:b9,x:b0,y:b3,", "03000000a306000020f6000000000000,Saitek PS2700,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:a4,start:b9,x:b0,y:b3,", "03000000300f00001101000000000000,Saitek Rumble Pad,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,", - "03000000790000000600000000000000,Sanwa Supply JY-P76USV,crc:20f0,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b2,y:b3,", "0300000000050000289b000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,", "030000009b2800000500000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,", "030000008f0e00000800000000000000,SpeedLink Strike FX,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", @@ -350,6 +351,7 @@ static const char *s_GamepadMappings[] = { "03000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000650000001000000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a5,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00005106000000010000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000ac05000004000000a8986d04,8BitDo Micro gamepad,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,lefttrigger:b12,rightshoulder:b13,righttrigger:b14,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001590000001000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000003512000012ab000001000000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", @@ -414,11 +416,13 @@ static const char *s_GamepadMappings[] = { "03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,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:b0,y:b3,", "0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,", "03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,", + "03000000853200008906000000010000,NACON Revolution X Unlimited,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,", "03000000550900001472000025050000,NVIDIA Controller v01.04,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,", "030000004b120000014d000000010000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", "030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,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:a3,start:b9,x:b2,y:b3,", "050000007e05000009200000ff070000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "0300000009120000072f000000010000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a2,leftx:a0,lefty:a1,righttrigger:a5,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", "030000006f0e00000901000002010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", "030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", @@ -486,6 +490,7 @@ static const char *s_GamepadMappings[] = { "03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00005106000000010000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d00002090000000010000,8BitDo Micro gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b12,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,righttrigger:b9,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001590000011010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000003512000012ab000010010000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", @@ -549,6 +554,7 @@ static const char *s_GamepadMappings[] = { "03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,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:b0,y:b3,", "03000000bc2000000055000011010000,GameSir G3w,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "0500000049190000020400001b010000,GameSir T4 Pro,crc:8283,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b23,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000373500009710000001020000,GameSir-K1 FLUX,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "03000000ac0500001a06000011010000,GameSir-T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", "03000000c01100000140000011010000,GameStop PS4 Fun Controller,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:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", @@ -637,6 +643,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,", @@ -785,6 +797,7 @@ static const char *s_GamepadMappings[] = { "03000000c0160000e105000010010000,Xin-Mo Dual Arcade,crc:82d5,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,", /* Ultimate Atari Fight Stick */ "03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,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:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000120c0000101e000011010000,ZEROPLUS P4 Wired Gamepad,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:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000120c0000182e000011010000,ZEROPLUS P4 Wired Gamepad,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:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,", "03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", @@ -800,6 +813,7 @@ static const char *s_GamepadMappings[] = { #ifdef SDL_PLATFORM_ANDROID "05000000c82d000006500000ffff3f00,8BitDo M30 Gamepad,a:b1,b:b0,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a4,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000051060000ffff3f00,8BitDo M30 Gamepad,a:b1,b:b0,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d000020900000ffff3f00,8BitDo Micro gamepad,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b5,leftshoulder:b9,lefttrigger:a6,rightshoulder:b10,righttrigger:a7,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000015900000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000065280000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000000220000000900000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", @@ -855,6 +869,10 @@ static const char *s_GamepadMappings[] = { "05000000ac05000001000000df076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", "05000000ac05000001000000ff076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "05000000ac050000020000004f066d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,", + "05000000ac05000004000000a8986d04,8BitDo Micro gamepad,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,lefttrigger:b12,rightshoulder:b13,righttrigger:b14,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000ac05000004000000fd216d04,8BitDo Pro 2,crc:ac95,a:b3,b:b2,back:b6,dpdown:b9,dpleft:b10,dpright:b11,dpup:b12,guide:b4,leftshoulder:b13,leftstick:b14,lefttrigger:+a2,leftx:a0,lefty:a1~,paddle1:b1,paddle2:b0,rightshoulder:b16,rightstick:b17,righttrigger:+a5,rightx:a3,righty:a4~,start:b5,x:b8,y:b7,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000ac050000040000003b8a6d04,8BitDo SN30 Pro+,crc:3e00,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,leftstick:b12,lefttrigger:b13,leftx:a0,lefty:a1~,rightshoulder:b14,rightstick:b15,righttrigger:b16,rightx:a2,righty:a3~,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000ac05000004000000209f6d04,8Bitdo SN30 Pro,crc:40d6,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,leftstick:b12,lefttrigger:b13,leftx:a0,lefty:a1~,rightshoulder:b14,rightstick:b15,righttrigger:b16,rightx:a2,righty:a3~,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000008a35000003010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "050000008a35000004010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "050000007e050000062000000f060000,Nintendo Switch Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", @@ -891,7 +909,7 @@ static const char *s_GamepadMappings[] = { "0000000050535669746120436f6e7400,PSVita Controller,crc:d598,a:b2,b:b1,back:b10,dpdown:b6,dpleft:b7,dpright:b9,dpup:b8,leftshoulder:b4,leftstick:b14,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,", #endif #ifdef SDL_JOYSTICK_N3DS - "000000004e696e74656e646f20334400,Nintendo 3DS,crc:3210,a:b0,b:b1,back:b2,dpdown:b7,dpleft:b5,dpright:b4,dpup:b6,leftshoulder:b9,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b8,righttrigger:b15,rightx:a2,righty:a3,start:b3,x:b10,y:b11,", + "000000004e696e74656e646f20334400,Nintendo 3DS,crc:3210,a:b1,b:b0,back:b2,dpdown:b7,dpleft:b5,dpright:b4,dpup:b6,leftshoulder:b9,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b8,righttrigger:b15,rightx:a2,righty:a3,start:b3,x:b11,y:b10,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", #endif NULL }; diff --git a/libs/SDL3/src/joystick/SDL_joystick.c b/libs/SDL3/src/joystick/SDL_joystick.c index a8bbe0c..3ad49bb 100644 --- a/libs/SDL3/src/joystick/SDL_joystick.c +++ b/libs/SDL3/src/joystick/SDL_joystick.c @@ -277,6 +277,7 @@ static Uint32 initial_blacklist_devices[] = { MAKE_VIDPID(0x1532, 0x0282), // Razer Huntsman Mini Analog, non-functional DInput device MAKE_VIDPID(0x26ce, 0x01a2), // ASRock LED Controller MAKE_VIDPID(0x20d6, 0x0002), // PowerA Enhanced Wireless Controller for Nintendo Switch (charging port only) + MAKE_VIDPID(0x3434, 0x0211), // Keychron K1 Pro System Control }; static SDL_vidpid_list blacklist_devices = { SDL_HINT_JOYSTICK_BLACKLIST_DEVICES, 0, 0, NULL, @@ -289,7 +290,17 @@ static Uint32 initial_flightstick_devices[] = { MAKE_VIDPID(0x044f, 0x0402), // HOTAS Warthog Joystick MAKE_VIDPID(0x044f, 0xb10a), // ThrustMaster, Inc. T.16000M Joystick MAKE_VIDPID(0x046d, 0xc215), // Logitech Extreme 3D + MAKE_VIDPID(0x0583, 0x6258), // Padix USB joystick with viewfinder + MAKE_VIDPID(0x0583, 0x688f), // Padix QF-688uv Windstorm Pro + MAKE_VIDPID(0x0583, 0x7070), // Padix QF-707u Bazooka + MAKE_VIDPID(0x0583, 0xa019), // Padix USB vibration joystick with viewfinder + MAKE_VIDPID(0x0583, 0xa131), // Padix USB Wireless 2.4GHz + MAKE_VIDPID(0x0583, 0xa209), // Padix MetalStrike ForceFeedback + MAKE_VIDPID(0x0583, 0xb010), // Padix MetalStrike Pro + MAKE_VIDPID(0x0583, 0xb012), // Padix Wireless MetalStrike + MAKE_VIDPID(0x0583, 0xb013), // Padix USB Wireless 2.4GHZ MAKE_VIDPID(0x0738, 0x2221), // Saitek Pro Flight X-56 Rhino Stick + MAKE_VIDPID(0x10f5, 0x7084), // Turtle Beach VelocityOne MAKE_VIDPID(0x231d, 0x0126), // Gunfighter Mk.III 'Space Combat Edition' (right) MAKE_VIDPID(0x231d, 0x0127), // Gunfighter Mk.III 'Space Combat Edition' (left) MAKE_VIDPID(0x362c, 0x0001), // Yawman Arrow @@ -331,6 +342,7 @@ static SDL_vidpid_list rog_gamepad_mice = { static Uint32 initial_throttle_devices[] = { MAKE_VIDPID(0x044f, 0x0404), // HOTAS Warthog Throttle MAKE_VIDPID(0x0738, 0xa221), // Saitek Pro Flight X-56 Rhino Throttle + MAKE_VIDPID(0x10f5, 0x7085), // Turtle Beach VelocityOne Throttle }; static SDL_vidpid_list throttle_devices = { SDL_HINT_JOYSTICK_THROTTLE_DEVICES, 0, 0, NULL, @@ -374,6 +386,14 @@ static Uint32 initial_wheel_devices[] = { MAKE_VIDPID(0x046d, 0xca03), // Logitech Momo Racing MAKE_VIDPID(0x0483, 0x0522), // Simagic Wheelbase (including M10, Alpha Mini, Alpha, Alpha U) MAKE_VIDPID(0x0483, 0xa355), // VRS DirectForce Pro Wheel Base + MAKE_VIDPID(0x0583, 0xa132), // Padix USB Wireless 2.4GHz Wheelpad + MAKE_VIDPID(0x0583, 0xa133), // Padix USB Wireless 2.4GHz Wheel + MAKE_VIDPID(0x0583, 0xa202), // Padix Force Feedback Wheel + MAKE_VIDPID(0x0583, 0xb002), // Padix Vibration USB Wheel + MAKE_VIDPID(0x0583, 0xb005), // Padix USB Wheel + MAKE_VIDPID(0x0583, 0xb008), // Padix USB Wireless 2.4GHz Wheel + MAKE_VIDPID(0x0583, 0xb009), // Padix USB Wheel + MAKE_VIDPID(0x0583, 0xb018), // Padix TW6 Wheel MAKE_VIDPID(0x0eb7, 0x0001), // Fanatec ClubSport Wheel Base V2 MAKE_VIDPID(0x0eb7, 0x0004), // Fanatec ClubSport Wheel Base V2.5 MAKE_VIDPID(0x0eb7, 0x0005), // Fanatec CSL Elite Wheel Base+ (PS4) @@ -427,6 +447,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 +1070,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 +1142,11 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id) joystick->attached = true; joystick->led_expiration = SDL_GetTicks(); joystick->battery_percent = -1; +#ifdef SDL_JOYSTICK_VIRTUAL + joystick->is_virtual = (driver == &SDL_VIRTUAL_JoystickDriver); +#else + joystick->is_virtual = false; +#endif if (!driver->Open(joystick, device_index)) { SDL_SetObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK, false); @@ -1137,9 +1190,35 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id) // If this joystick is known to have all zero centered axes, skip the auto-centering code if (SDL_JoystickAxesCenteredAtZero(joystick)) { - int i; + for (int i = 0; i < joystick->naxes; ++i) { + joystick->axes[i].has_initial_value = true; + } + } - for (i = 0; i < joystick->naxes; ++i) { + // We know the initial values for HIDAPI and XInput joysticks + if ((SDL_IsJoystickHIDAPI(joystick->guid) || + SDL_IsJoystickXInput(joystick->guid) || + SDL_IsJoystickRAWINPUT(joystick->guid) || + SDL_IsJoystickWGI(joystick->guid)) && + joystick->naxes >= SDL_GAMEPAD_AXIS_COUNT) { + int left_trigger, right_trigger; + if (SDL_IsJoystickXInput(joystick->guid)) { + left_trigger = 2; + right_trigger = 5; + } else { + left_trigger = SDL_GAMEPAD_AXIS_LEFT_TRIGGER; + right_trigger = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; + } + for (int i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) { + int initial_value; + if (i == left_trigger || i == right_trigger) { + initial_value = SDL_MIN_SINT16; + } else { + initial_value = 0; + } + joystick->axes[i].value = initial_value; + joystick->axes[i].zero = initial_value; + joystick->axes[i].initial_value = initial_value; joystick->axes[i].has_initial_value = true; } } @@ -1148,6 +1227,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 +1305,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 +1325,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 +1345,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 +1365,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 +1385,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 +1405,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); @@ -1778,6 +1864,14 @@ bool SDL_RumbleJoystickTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint result = true; } else { result = joystick->driver->RumbleTriggers(joystick, left_rumble, right_rumble); + if (result) { + joystick->trigger_rumble_resend = SDL_GetTicks() + SDL_RUMBLE_RESEND_MS; + if (joystick->trigger_rumble_resend == 0) { + joystick->trigger_rumble_resend = 1; + } + } else { + joystick->trigger_rumble_resend = 0; + } } if (result) { @@ -1788,6 +1882,7 @@ bool SDL_RumbleJoystickTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint joystick->trigger_rumble_expiration = SDL_GetTicks() + SDL_min(duration_ms, SDL_MAX_RUMBLE_DURATION_MS); } else { joystick->trigger_rumble_expiration = 0; + joystick->trigger_rumble_resend = 0; } } } @@ -2048,6 +2143,7 @@ void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id) SDL_JoystickDriver *driver; int device_index; int player_index = -1; + bool is_gamepad; SDL_AssertJoysticksLocked(); @@ -2082,9 +2178,12 @@ void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id) } } + // This might create an automatic gamepad mapping, so wait to send the event + is_gamepad = SDL_IsGamepad(instance_id); + SDL_joystick_being_added = false; - if (SDL_IsGamepad(instance_id)) { + if (is_gamepad) { SDL_PrivateGamepadAdded(instance_id); } } @@ -2305,6 +2404,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 +2471,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; } } @@ -2421,6 +2541,15 @@ void SDL_UpdateJoysticks(void) if (joystick->trigger_rumble_expiration && now >= joystick->trigger_rumble_expiration) { SDL_RumbleJoystickTriggers(joystick, 0, 0, 0); + joystick->trigger_rumble_resend = 0; + } + + if (joystick->trigger_rumble_resend && now >= joystick->trigger_rumble_resend) { + joystick->driver->RumbleTriggers(joystick, joystick->left_trigger_rumble, joystick->right_trigger_rumble); + joystick->trigger_rumble_resend = now + SDL_RUMBLE_RESEND_MS; + if (joystick->trigger_rumble_resend == 0) { + joystick->trigger_rumble_resend = 1; + } } } @@ -2546,158 +2675,14 @@ void SDL_GetJoystickGUIDInfo(SDL_GUID guid, Uint16 *vendor, Uint16 *product, Uin } } -static int PrefixMatch(const char *a, const char *b) -{ - int matchlen = 0; - while (*a && *b) { - if (SDL_tolower((unsigned char)*a++) == SDL_tolower((unsigned char)*b++)) { - ++matchlen; - } else { - break; - } - } - return matchlen; -} - char *SDL_CreateJoystickName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name) { - static struct - { - const char *prefix; - const char *replacement; - } replacements[] = { - { "ASTRO Gaming", "ASTRO" }, - { "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" }, - { "Guangzhou Chicken Run Network Technology Co., Ltd.", "GameSir" }, - { "HORI CO.,LTD", "HORI" }, - { "HORI CO.,LTD.", "HORI" }, - { "Mad Catz Inc.", "Mad Catz" }, - { "Nintendo Co., Ltd.", "Nintendo" }, - { "NVIDIA Corporation ", "" }, - { "Performance Designed Products", "PDP" }, - { "QANBA USA, LLC", "Qanba" }, - { "QANBA USA,LLC", "Qanba" }, - { "Unknown ", "" }, - }; - const char *custom_name; - char *name; - size_t i, len; - - custom_name = GuessControllerName(vendor, product); + const char *custom_name = GuessControllerName(vendor, product); if (custom_name) { return SDL_strdup(custom_name); } - if (!vendor_name) { - vendor_name = ""; - } - if (!product_name) { - product_name = ""; - } - - while (*vendor_name == ' ') { - ++vendor_name; - } - while (*product_name == ' ') { - ++product_name; - } - - if (*vendor_name && *product_name) { - len = (SDL_strlen(vendor_name) + 1 + SDL_strlen(product_name) + 1); - name = (char *)SDL_malloc(len); - if (name) { - (void)SDL_snprintf(name, len, "%s %s", vendor_name, product_name); - } - } else if (*product_name) { - name = SDL_strdup(product_name); - } else if (vendor || product) { - // Couldn't find a controller name, try to give it one based on device type - switch (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, true)) { - case SDL_GAMEPAD_TYPE_XBOX360: - name = SDL_strdup("Xbox 360 Controller"); - break; - case SDL_GAMEPAD_TYPE_XBOXONE: - name = SDL_strdup("Xbox One Controller"); - break; - case SDL_GAMEPAD_TYPE_PS3: - name = SDL_strdup("PS3 Controller"); - break; - case SDL_GAMEPAD_TYPE_PS4: - name = SDL_strdup("PS4 Controller"); - break; - case SDL_GAMEPAD_TYPE_PS5: - name = SDL_strdup("DualSense Wireless Controller"); - break; - case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: - name = SDL_strdup("Nintendo Switch Pro Controller"); - break; - default: - len = (6 + 1 + 6 + 1); - name = (char *)SDL_malloc(len); - if (name) { - (void)SDL_snprintf(name, len, "0x%.4x/0x%.4x", vendor, product); - } - break; - } - } else { - name = SDL_strdup("Controller"); - } - - if (!name) { - return NULL; - } - - // Trim trailing whitespace - for (len = SDL_strlen(name); (len > 0 && name[len - 1] == ' '); --len) { - // continue - } - name[len] = '\0'; - - // Compress duplicate spaces - for (i = 0; i < (len - 1);) { - if (name[i] == ' ' && name[i + 1] == ' ') { - SDL_memmove(&name[i], &name[i + 1], (len - i)); - --len; - } else { - ++i; - } - } - - // Perform any manufacturer replacements - for (i = 0; i < SDL_arraysize(replacements); ++i) { - size_t prefixlen = SDL_strlen(replacements[i].prefix); - if (SDL_strncasecmp(name, replacements[i].prefix, prefixlen) == 0) { - size_t replacementlen = SDL_strlen(replacements[i].replacement); - if (replacementlen <= prefixlen) { - SDL_memcpy(name, replacements[i].replacement, replacementlen); - SDL_memmove(name + replacementlen, name + prefixlen, (len - prefixlen) + 1); - len -= (prefixlen - replacementlen); - } else { - // FIXME: Need to handle the expand case by reallocating the string - } - break; - } - } - - /* Remove duplicate manufacturer or product in the name - * e.g. Razer Razer Raiju Tournament Edition Wired - */ - for (i = 1; i < (len - 1); ++i) { - int matchlen = PrefixMatch(name, &name[i]); - while (matchlen > 0) { - if (name[matchlen] == ' ' || name[matchlen] == '-') { - SDL_memmove(name, name + matchlen + 1, len - matchlen); - break; - } - --matchlen; - } - if (matchlen > 0) { - // We matched the manufacturer's name and removed it - break; - } - } - - return name; + return SDL_CreateDeviceName(vendor, product, vendor_name, product_name, "Controller"); } SDL_GUID SDL_CreateJoystickGUID(Uint16 bus, Uint16 vendor, Uint16 product, Uint16 version, const char *vendor_name, const char *product_name, Uint8 driver_signature, Uint8 driver_data) @@ -2945,7 +2930,8 @@ bool SDL_IsJoystickXboxSeriesX(Uint16 vendor_id, Uint16 product_id) } if (vendor_id == USB_VENDOR_HORI) { if (product_id == USB_PRODUCT_HORI_FIGHTING_COMMANDER_OCTA_SERIES_X || - product_id == USB_PRODUCT_HORI_HORIPAD_PRO_SERIES_X) { + product_id == USB_PRODUCT_HORI_HORIPAD_PRO_SERIES_X || + product_id == USB_PRODUCT_HORI_TAIKO_DRUM_CONTROLLER) { return true; } } diff --git a/libs/SDL3/src/joystick/SDL_sysjoystick.h b/libs/SDL3/src/joystick/SDL_sysjoystick.h index 343d206..041ebc3 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; @@ -111,6 +113,7 @@ struct SDL_Joystick Uint16 left_trigger_rumble _guarded; Uint16 right_trigger_rumble _guarded; Uint64 trigger_rumble_expiration _guarded; + Uint64 trigger_rumble_resend _guarded; Uint8 led_red _guarded; Uint8 led_green _guarded; diff --git a/libs/SDL3/src/joystick/controller_list.h b/libs/SDL3/src/joystick/controller_list.h index 5a62ece..c6f4f42 100644 --- a/libs/SDL3/src/joystick/controller_list.h +++ b/libs/SDL3/src/joystick/controller_list.h @@ -21,7 +21,6 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x0079, 0x181a ), k_eControllerType_PS3Controller, NULL }, // Venom Arcade Stick - { MAKE_CONTROLLER_ID( 0x0079, 0x1844 ), k_eControllerType_PS3Controller, NULL }, // From SDL { MAKE_CONTROLLER_ID( 0x044f, 0xb315 ), k_eControllerType_PS3Controller, NULL }, // Firestorm Dual Analog 3 { MAKE_CONTROLLER_ID( 0x044f, 0xd007 ), k_eControllerType_PS3Controller, NULL }, // Thrustmaster wireless 3-1 { MAKE_CONTROLLER_ID( 0x046d, 0xcad1 ), k_eControllerType_PS3Controller, NULL }, // Logitech Chillstream @@ -96,6 +95,7 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x0c12, 0x0ef6 ), k_eControllerType_PS4Controller, NULL }, // Hitbox Arcade Stick { MAKE_CONTROLLER_ID( 0x0c12, 0x1cf6 ), k_eControllerType_PS4Controller, NULL }, // EMIO PS4 Elite Controller { MAKE_CONTROLLER_ID( 0x0c12, 0x1e10 ), k_eControllerType_PS4Controller, NULL }, // P4 Wired Gamepad generic knock off - lightbar but not trackpad or gyro + { MAKE_CONTROLLER_ID( 0x0c12, 0x2e18 ), k_eControllerType_PS4Controller, NULL }, // ZEROPLUS P4 Wired Gamepad { MAKE_CONTROLLER_ID( 0x0e6f, 0x0203 ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS (PS4 peripheral but no trackpad/lightbar) { MAKE_CONTROLLER_ID( 0x0e6f, 0x0207 ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS V2 w/ Touchpad for PS4 { MAKE_CONTROLLER_ID( 0x0e6f, 0x020a ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS PS4/PS5 (PS4 mode) diff --git a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c index 9327276..e87ab82 100644 --- a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c +++ b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c @@ -27,6 +27,7 @@ #include "SDL_iokitjoystick_c.h" #include "../hidapi/SDL_hidapijoystick_c.h" #include "../../haptic/darwin/SDL_syshaptic_c.h" // For haptic hot plugging +#include "../usb_ids.h" #define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick") @@ -213,6 +214,29 @@ static bool GetHIDScaledCalibratedState(recDevice *pDevice, recElement *pElement return result; } +static bool GetHIDScaledCalibratedState_NACON_Revolution_X_Unlimited(recDevice *pDevice, recElement *pElement, SInt32 min, SInt32 max, SInt32 *pValue) +{ + if (pElement->minReport == 0 && pElement->maxReport == 255) { + return GetHIDScaledCalibratedState(pDevice, pElement, min, max, pValue); + } + + // This device thumbstick axes have an unusual axis range that + // doesn't work with GetHIDScaledCalibratedState() above. + // + // See https://github.com/libsdl-org/SDL/issues/13143 for details + if (GetHIDElementState(pDevice, pElement, pValue)) { + if (*pValue >= 0) { + // Negative axis values range from 32767 (at rest) to 0 (minimum) + *pValue = -32767 + *pValue; + } else if (*pValue < 0) { + // Positive axis values range from -32768 (at rest) to 0 (maximum) + *pValue = 32768 + *pValue; + } + return true; + } + return false; +} + static void JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender) { recDevice *device = (recDevice *)ctx; @@ -506,6 +530,11 @@ static bool GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice) pDevice->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, (Uint16)vendor, (Uint16)product, (Uint16)version, manufacturer_string, product_string, 0, 0); pDevice->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot((Uint16)vendor, (Uint16)product, product_string); + if (vendor == USB_VENDOR_NACON_ALT && + product == USB_PRODUCT_NACON_REVOLUTION_X_UNLIMITED_BT) { + pDevice->nacon_revolution_x_unlimited = true; + } + array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone); if (array) { AddHIDElements(array, pDevice); @@ -957,7 +986,11 @@ static void DARWIN_JoystickUpdate(SDL_Joystick *joystick) i = 0; while (element) { - goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value); + if (device->nacon_revolution_x_unlimited) { + goodRead = GetHIDScaledCalibratedState_NACON_Revolution_X_Unlimited(device, element, -32768, 32767, &value); + } else { + goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value); + } if (goodRead) { SDL_SendJoystickAxis(timestamp, joystick, i, value); } diff --git a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h index 91deb24..3a70d2b 100644 --- a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h +++ b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h @@ -72,6 +72,7 @@ struct joystick_hwdata int instance_id; SDL_GUID guid; int steam_virtual_gamepad_slot; + bool nacon_revolution_x_unlimited; struct joystick_hwdata *pNext; // next device }; diff --git a/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.c b/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.c index 27e36e0..6cf0a90 100644 --- a/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.c +++ b/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.c @@ -24,9 +24,7 @@ #include "../SDL_sysjoystick.h" #include "../usb_ids.h" - -#define COBJMACROS -#include +#include "../../core/windows/SDL_gameinput.h" // Default value for SDL_HINT_JOYSTICK_GAMEINPUT #if defined(SDL_PLATFORM_GDK) @@ -67,12 +65,10 @@ typedef struct joystick_hwdata } GAMEINPUT_InternalJoystickHwdata; static GAMEINPUT_InternalList g_GameInputList = { NULL }; -static SDL_SharedObject *g_hGameInputDLL = NULL; static IGameInput *g_pGameInput = NULL; static GameInputCallbackToken g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE; static Uint64 g_GameInputTimestampOffset; - static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info) { if (info->supportedInput & GameInputKindGamepad) { @@ -245,24 +241,8 @@ static bool GAMEINPUT_JoystickInit(void) return true; } - if (!g_hGameInputDLL) { - g_hGameInputDLL = SDL_LoadObject("gameinput.dll"); - if (!g_hGameInputDLL) { - return false; - } - } - - if (!g_pGameInput) { - typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput * *gameInput); - GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate"); - if (!GameInputCreateFunc) { - return false; - } - - hR = GameInputCreateFunc(&g_pGameInput); - if (FAILED(hR)) { - return SDL_SetError("GameInputCreate failure with HRESULT of %08lX", hR); - } + if (!SDL_InitGameInput(&g_pGameInput)) { + return false; } hR = IGameInput_RegisterDeviceCallback(g_pGameInput, @@ -615,6 +595,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) } else { bool *button_state = SDL_stack_alloc(bool, info->controllerButtonCount); float *axis_state = SDL_stack_alloc(float, info->controllerAxisCount); + GameInputSwitchPosition *switch_state = SDL_stack_alloc(GameInputSwitchPosition, info->controllerSwitchCount); if (button_state) { uint32_t i; @@ -635,6 +616,46 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) SDL_stack_free(axis_state); } #undef CONVERT_AXIS + + if (switch_state) { + uint32_t i; + uint32_t switch_count = IGameInputReading_GetControllerSwitchState(reading, info->controllerSwitchCount, switch_state); + for (i = 0; i < switch_count; ++i) { + Uint8 hat; + switch (switch_state[i]) { + case GameInputSwitchUp: + hat = SDL_HAT_UP; + break; + case GameInputSwitchUpRight: + hat = SDL_HAT_UP | SDL_HAT_RIGHT; + break; + case GameInputSwitchRight: + hat = SDL_HAT_RIGHT; + break; + case GameInputSwitchDownRight: + hat = SDL_HAT_DOWN | SDL_HAT_RIGHT; + break; + case GameInputSwitchDown: + hat = SDL_HAT_DOWN; + break; + case GameInputSwitchDownLeft: + hat = SDL_HAT_DOWN | SDL_HAT_LEFT; + break; + case GameInputSwitchLeft: + hat = SDL_HAT_LEFT; + break; + case GameInputSwitchUpLeft: + hat = SDL_HAT_UP | SDL_HAT_LEFT; + break; + case GameInputSwitchCenter: + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat); + } + SDL_stack_free(switch_state); + } } if (info->supportedInput & GameInputKindTouch) { @@ -689,14 +710,9 @@ static void GAMEINPUT_JoystickQuit(void) GAMEINPUT_InternalRemoveByIndex(0); } - IGameInput_Release(g_pGameInput); + SDL_QuitGameInput(); g_pGameInput = NULL; } - - if (g_hGameInputDLL) { - SDL_UnloadObject(g_hGameInputDLL); - g_hGameInputDLL = NULL; - } } static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_combined.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_combined.c index abfce17..5426edb 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_combined.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_combined.c @@ -70,6 +70,8 @@ static bool HIDAPI_DriverCombined_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Jo for (i = 0; i < device->num_children; ++i) { SDL_HIDAPI_Device *child = device->children[i]; if (!child->driver->OpenJoystick(child, joystick)) { + child->broken = true; + while (i-- > 0) { child = device->children[i]; child->driver->CloseJoystick(child, joystick); diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c index 4d45c7a..c03d927 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c @@ -31,7 +31,9 @@ #ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE // Define this if you want to log all packets from the controller -// #define DEBUG_GAMECUBE_PROTOCOL +#if 0 +#define DEBUG_GAMECUBE_PROTOCOL +#endif #define MAX_CONTROLLERS 4 @@ -119,22 +121,15 @@ static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) } device->context = ctx; - ctx->joysticks[0] = 0; - ctx->joysticks[1] = 0; - ctx->joysticks[2] = 0; - ctx->joysticks[3] = 0; ctx->rumble[0] = rumbleMagic; - ctx->useRumbleBrake = false; if (device->vendor_id != USB_VENDOR_NINTENDO) { ctx->pc_mode = true; } if (ctx->pc_mode) { - for (i = 0; i < MAX_CONTROLLERS; ++i) { - ResetAxisRange(ctx, i); - HIDAPI_JoystickConnected(device, &ctx->joysticks[i]); - } + ResetAxisRange(ctx, 0); + HIDAPI_JoystickConnected(device, &ctx->joysticks[0]); } else { // This is all that's needed to initialize the device. Really! if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) { @@ -204,69 +199,61 @@ static void HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device { } -static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, const Uint8 *packet, int size) +static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, const Uint8 *packet, bool invert_c_stick) { SDL_Joystick *joystick; - Uint8 i, v; + const Uint8 i = 0; // We have a separate context for each connected controller in PC mode, just use the first index + Uint8 v; Sint16 axis_value; Uint64 timestamp = SDL_GetTicksNS(); - if (size != 10) { - return; // How do we handle this packet? - } - - i = packet[0] - 1; - if (i >= MAX_CONTROLLERS) { - return; // How do we handle this packet? - } - joystick = SDL_GetJoystickFromID(ctx->joysticks[i]); if (!joystick) { // Hasn't been opened yet, skip return; } -#define READ_BUTTON(off, flag, button) \ - SDL_SendJoystickButton( \ - timestamp, \ - joystick, \ - button, \ +#define READ_BUTTON(off, flag, button) \ + SDL_SendJoystickButton( \ + timestamp, \ + joystick, \ + button, \ ((packet[off] & flag) != 0)); - READ_BUTTON(1, 0x02, 0) // A - READ_BUTTON(1, 0x04, 1) // B - READ_BUTTON(1, 0x08, 3) // Y - READ_BUTTON(1, 0x01, 2) // X - READ_BUTTON(2, 0x80, 4) // DPAD_LEFT - READ_BUTTON(2, 0x20, 5) // DPAD_RIGHT - READ_BUTTON(2, 0x40, 6) // DPAD_DOWN - READ_BUTTON(2, 0x10, 7) // DPAD_UP - READ_BUTTON(2, 0x02, 8) // START - READ_BUTTON(1, 0x80, 9) // RIGHTSHOULDER + READ_BUTTON(0, 0x02, 0) // A + READ_BUTTON(0, 0x04, 1) // B + READ_BUTTON(0, 0x08, 3) // Y + READ_BUTTON(0, 0x01, 2) // X + READ_BUTTON(1, 0x80, 4) // DPAD_LEFT + READ_BUTTON(1, 0x20, 5) // DPAD_RIGHT + READ_BUTTON(1, 0x40, 6) // DPAD_DOWN + READ_BUTTON(1, 0x10, 7) // DPAD_UP + READ_BUTTON(1, 0x02, 8) // START + READ_BUTTON(0, 0x80, 9) // RIGHTSHOULDER /* These two buttons are for the bottoms of the analog triggers. * More than likely, you're going to want to read the axes instead! * -flibit */ - READ_BUTTON(1, 0x20, 10) // TRIGGERRIGHT - READ_BUTTON(1, 0x10, 11) // TRIGGERLEFT + READ_BUTTON(0, 0x20, 10) // TRIGGERRIGHT + READ_BUTTON(0, 0x10, 11) // TRIGGERLEFT #undef READ_BUTTON -#define READ_AXIS(off, axis, invert) \ - v = invert ? (0xff - packet[off]) : packet[off]; \ - if (v < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ - ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ - if (v > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ - ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ +#define READ_AXIS(off, axis, invert) \ + v = (invert) ? (0xff - packet[off]) : packet[off]; \ + if (v < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ + ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ + if (v > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ + ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ axis_value = (Sint16)HIDAPI_RemapVal(v, ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \ - SDL_SendJoystickAxis( \ - timestamp, \ - joystick, \ + SDL_SendJoystickAxis( \ + timestamp, \ + joystick, \ axis, axis_value); - READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX, 0) - READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY, 1) - READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTX, 0) - READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTY, 1) - READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0) - READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0) + READ_AXIS(2, SDL_GAMEPAD_AXIS_LEFTX, 0) + READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTY, 1) + READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTX, invert_c_stick ? 1 : 0) + READ_AXIS(4, SDL_GAMEPAD_AXIS_RIGHTY, invert_c_stick ? 0 : 1) + READ_AXIS(6, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0) + READ_AXIS(7, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0) #undef READ_AXIS } @@ -365,7 +352,18 @@ static bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device) HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size); #endif if (ctx->pc_mode) { - HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, size); + if (size == 10) { + // This is the older firmware + // The first byte is the index of the connected controller + // The C stick has an inverted value range compared to the left stick + HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, &packet[1], true); + } else if (size == 9) { + // This is the newer firmware (version 0x7) + // The C stick has the same value range compared to the left stick + HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, false); + } else { + // How do we handle this packet? + } } else { HIDAPI_DriverGameCube_HandleNintendoPacket(device, ctx, packet, size); } diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps4.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps4.c index 7404bf2..d4332df 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps4.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -1003,8 +1003,8 @@ static bool HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device static void HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS4_Context *ctx, PS4StatePacket_t *packet, int size) { - static const float TOUCHPAD_SCALEX = 1.0f / 1920; - static const float TOUCHPAD_SCALEY = 1.0f / 920; // This is noted as being 944 resolution, but 920 feels better + static const float TOUCHPAD_SCALEX = 5.20833333e-4f; // 1.0f / 1920 + static const float TOUCHPAD_SCALEY = 1.08695652e-3f; // 1.0f / 920 // This is noted as being 944 resolution, but 920 feels better Sint16 axis; bool touchpad_down; int touchpad_x, touchpad_y; diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps5.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps5.c index 94b720d..4249578 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps5.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps5.c @@ -812,14 +812,19 @@ static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx } if (ctx->sensors_supported) { + // Standard DualSense sensor update rate is 250 Hz over USB + float update_rate = 250.0f; + if (ctx->device->is_bluetooth) { // Bluetooth sensor update rate appears to be 1000 Hz - SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 1000.0f); - SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 1000.0f); - } else { - SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 250.0f); - SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 250.0f); + update_rate = 1000.0f; + } else if (SDL_IsJoystickDualSenseEdge(ctx->device->vendor_id, ctx->device->product_id)) { + // DualSense Edge sensor update rate is 1000 Hz over USB + update_rate = 1000.0f; } + + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, update_rate); + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, update_rate); } ctx->report_battery = true; @@ -952,7 +957,7 @@ static bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystic SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, SDL_PS5EnhancedReportsChanged, ctx); - SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, SDL_PS5PlayerLEDHintChanged, ctx); return true; @@ -1355,8 +1360,8 @@ static void HIDAPI_DriverPS5_HandleStatePacketCommon(SDL_Joystick *joystick, SDL static void HIDAPI_DriverPS5_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacket_t *packet, Uint64 timestamp) { - static const float TOUCHPAD_SCALEX = 1.0f / 1920; - static const float TOUCHPAD_SCALEY = 1.0f / 1070; + static const float TOUCHPAD_SCALEX = 5.20833333e-4f; // 1.0f / 1920 + static const float TOUCHPAD_SCALEY = 9.34579439e-4f; // 1.0f / 1070 bool touchpad_down; int touchpad_x, touchpad_y; @@ -1406,8 +1411,8 @@ static void HIDAPI_DriverPS5_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_d static void HIDAPI_DriverPS5_HandleStatePacketAlt(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacketAlt_t *packet, Uint64 timestamp) { - static const float TOUCHPAD_SCALEX = 1.0f / 1920; - static const float TOUCHPAD_SCALEY = 1.0f / 1070; + static const float TOUCHPAD_SCALEX = 5.20833333e-4f; // 1.0f / 1920 + static const float TOUCHPAD_SCALEY = 9.34579439e-4f; // 1.0f / 1070 bool touchpad_down; int touchpad_x, touchpad_y; diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c index 85f551a..8f9b1bf 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c @@ -58,9 +58,7 @@ #define SWITCH_GYRO_SCALE 14.2842f #define SWITCH_ACCEL_SCALE 4096.f -#define SWITCH_GYRO_SCALE_OFFSET 13371.0f #define SWITCH_GYRO_SCALE_MULT 936.0f -#define SWITCH_ACCEL_SCALE_OFFSET 16384.0f #define SWITCH_ACCEL_SCALE_MULT 4.0f enum @@ -813,6 +811,12 @@ static Uint8 GetDefaultInputMode(SDL_DriverSwitch_Context *ctx) } break; } + + // Wired controllers break if they are put into simple controller state + if (input_mode == k_eSwitchInputReportIDs_SimpleControllerState && + !ctx->device->is_bluetooth) { + input_mode = k_eSwitchInputReportIDs_FullControllerState; + } return input_mode; } @@ -869,33 +873,22 @@ static void SetEnhancedModeAvailable(SDL_DriverSwitch_Context *ctx) } } -static void SetEnhancedMode(SDL_DriverSwitch_Context *ctx, bool bEnabled) -{ - if (bEnabled) { - SetEnhancedModeAvailable(ctx); - } - - if (bEnabled != ctx->m_bEnhancedMode) { - ctx->m_bEnhancedMode = bEnabled; - - UpdateInputMode(ctx); - } -} - static void SetEnhancedReportHint(SDL_DriverSwitch_Context *ctx, HIDAPI_Switch_EnhancedReportHint eEnhancedReportHint) { + ctx->m_eEnhancedReportHint = eEnhancedReportHint; + switch (eEnhancedReportHint) { case SWITCH_ENHANCED_REPORT_HINT_OFF: - SetEnhancedMode(ctx, false); + ctx->m_bEnhancedMode = false; break; case SWITCH_ENHANCED_REPORT_HINT_ON: - SetEnhancedMode(ctx, true); + SetEnhancedModeAvailable(ctx); + ctx->m_bEnhancedMode = true; break; case SWITCH_ENHANCED_REPORT_HINT_AUTO: SetEnhancedModeAvailable(ctx); break; } - ctx->m_eEnhancedReportHint = eEnhancedReportHint; UpdateInputMode(ctx); } @@ -935,13 +928,14 @@ static bool SetIMUEnabled(SDL_DriverSwitch_Context *ctx, bool enabled) static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx) { - Uint8 *pLeftStickCal; - Uint8 *pRightStickCal; + Uint8 *pLeftStickCal = NULL; + Uint8 *pRightStickCal = NULL; size_t stick, axis; SwitchSubcommandInputPacket_t *user_reply = NULL; SwitchSubcommandInputPacket_t *factory_reply = NULL; SwitchSPIOpData_t readUserParams; SwitchSPIOpData_t readFactoryParams; + Uint8 userParamsReadSuccessCount = 0; // Read User Calibration Info readUserParams.unAddress = k_unSPIStickUserCalibrationStartOffset; @@ -954,21 +948,46 @@ static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx) readFactoryParams.unAddress = k_unSPIStickFactoryCalibrationStartOffset; readFactoryParams.ucLength = k_unSPIStickFactoryCalibrationLength; - if (!WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readFactoryParams, sizeof(readFactoryParams), &factory_reply)) { - return false; - } - // Automatically select the user calibration if magic bytes are set if (user_reply && user_reply->stickUserCalibration.rgucLeftMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucLeftMagic[1] == 0xA1) { + userParamsReadSuccessCount += 1; pLeftStickCal = user_reply->stickUserCalibration.rgucLeftCalibration; - } else { - pLeftStickCal = factory_reply->stickFactoryCalibration.rgucLeftCalibration; } if (user_reply && user_reply->stickUserCalibration.rgucRightMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucRightMagic[1] == 0xA1) { + userParamsReadSuccessCount += 1; pRightStickCal = user_reply->stickUserCalibration.rgucRightCalibration; - } else { - pRightStickCal = factory_reply->stickFactoryCalibration.rgucRightCalibration; + } + + // Only read the factory calibration info if we failed to receive the correct magic bytes + if (userParamsReadSuccessCount < 2) { + // Read Factory Calibration Info + readFactoryParams.unAddress = k_unSPIStickFactoryCalibrationStartOffset; + readFactoryParams.ucLength = k_unSPIStickFactoryCalibrationLength; + + const int MAX_ATTEMPTS = 3; + for (int attempt = 0;; ++attempt) { + if (!WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readFactoryParams, sizeof(readFactoryParams), &factory_reply)) { + return false; + } + + if (factory_reply->stickFactoryCalibration.opData.unAddress == k_unSPIStickFactoryCalibrationStartOffset) { + // We successfully read the calibration data + pLeftStickCal = factory_reply->stickFactoryCalibration.rgucLeftCalibration; + pRightStickCal = factory_reply->stickFactoryCalibration.rgucRightCalibration; + break; + } + + if (attempt == MAX_ATTEMPTS) { + return false; + } + } + } + + // If we still don't have calibration data, return false + if (pLeftStickCal == NULL || pRightStickCal == NULL) + { + return false; } /* Stick calibration values are 12-bits each and are packed by bit @@ -1037,6 +1056,8 @@ static bool LoadIMUCalibration(SDL_DriverSwitch_Context *ctx) if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readParams, sizeof(readParams), &reply)) { Uint8 *pIMUScale; Sint16 sAccelRawX, sAccelRawY, sAccelRawZ, sGyroRawX, sGyroRawY, sGyroRawZ; + Sint16 sAccelSensCoeffX, sAccelSensCoeffY, sAccelSensCoeffZ; + Sint16 sGyroSensCoeffX, sGyroSensCoeffY, sGyroSensCoeffZ; // IMU scale gives us multipliers for converting raw values to real world values pIMUScale = reply->spiReadData.rgucReadData; @@ -1045,10 +1066,18 @@ static bool LoadIMUCalibration(SDL_DriverSwitch_Context *ctx) sAccelRawY = (pIMUScale[3] << 8) | pIMUScale[2]; sAccelRawZ = (pIMUScale[5] << 8) | pIMUScale[4]; + sAccelSensCoeffX = (pIMUScale[7] << 8) | pIMUScale[6]; + sAccelSensCoeffY = (pIMUScale[9] << 8) | pIMUScale[8]; + sAccelSensCoeffZ = (pIMUScale[11] << 8) | pIMUScale[10]; + sGyroRawX = (pIMUScale[13] << 8) | pIMUScale[12]; sGyroRawY = (pIMUScale[15] << 8) | pIMUScale[14]; sGyroRawZ = (pIMUScale[17] << 8) | pIMUScale[16]; + sGyroSensCoeffX = (pIMUScale[19] << 8) | pIMUScale[18]; + sGyroSensCoeffY = (pIMUScale[21] << 8) | pIMUScale[20]; + sGyroSensCoeffZ = (pIMUScale[23] << 8) | pIMUScale[22]; + // Check for user calibration data. If it's present and set, it'll override the factory settings readParams.unAddress = k_unSPIIMUUserScaleStartOffset; readParams.ucLength = k_unSPIIMUUserScaleLength; @@ -1065,14 +1094,14 @@ static bool LoadIMUCalibration(SDL_DriverSwitch_Context *ctx) } // Accelerometer scale - ctx->m_IMUScaleData.fAccelScaleX = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawX) * SDL_STANDARD_GRAVITY; - ctx->m_IMUScaleData.fAccelScaleY = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawY) * SDL_STANDARD_GRAVITY; - ctx->m_IMUScaleData.fAccelScaleZ = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawZ) * SDL_STANDARD_GRAVITY; + ctx->m_IMUScaleData.fAccelScaleX = SWITCH_ACCEL_SCALE_MULT / ((float)sAccelSensCoeffX - (float)sAccelRawX) * SDL_STANDARD_GRAVITY; + ctx->m_IMUScaleData.fAccelScaleY = SWITCH_ACCEL_SCALE_MULT / ((float)sAccelSensCoeffY - (float)sAccelRawY) * SDL_STANDARD_GRAVITY; + ctx->m_IMUScaleData.fAccelScaleZ = SWITCH_ACCEL_SCALE_MULT / ((float)sAccelSensCoeffZ - (float)sAccelRawZ) * SDL_STANDARD_GRAVITY; // Gyro scale - ctx->m_IMUScaleData.fGyroScaleX = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawX) * SDL_PI_F / 180.0f; - ctx->m_IMUScaleData.fGyroScaleY = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawY) * SDL_PI_F / 180.0f; - ctx->m_IMUScaleData.fGyroScaleZ = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawZ) * SDL_PI_F / 180.0f; + ctx->m_IMUScaleData.fGyroScaleX = SWITCH_GYRO_SCALE_MULT / ((float)sGyroSensCoeffX - (float)sGyroRawX) * SDL_PI_F / 180.0f; + ctx->m_IMUScaleData.fGyroScaleY = SWITCH_GYRO_SCALE_MULT / ((float)sGyroSensCoeffY - (float)sGyroRawY) * SDL_PI_F / 180.0f; + ctx->m_IMUScaleData.fGyroScaleZ = SWITCH_GYRO_SCALE_MULT / ((float)sGyroSensCoeffZ - (float)sGyroRawZ) * SDL_PI_F / 180.0f; } else { // Use default values @@ -1094,14 +1123,17 @@ static Sint16 ApplyStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, i { sRawValue -= ctx->m_StickCalData[nStick].axis[nAxis].sCenter; - if (sRawValue > ctx->m_StickExtents[nStick].axis[nAxis].sMax) { - ctx->m_StickExtents[nStick].axis[nAxis].sMax = sRawValue; + if (sRawValue >= 0) { + if (sRawValue > ctx->m_StickExtents[nStick].axis[nAxis].sMax) { + ctx->m_StickExtents[nStick].axis[nAxis].sMax = sRawValue; + } + return (Sint16)HIDAPI_RemapVal(sRawValue, 0, ctx->m_StickExtents[nStick].axis[nAxis].sMax, 0, SDL_MAX_SINT16); + } else { + if (sRawValue < ctx->m_StickExtents[nStick].axis[nAxis].sMin) { + ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue; + } + return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, 0, SDL_MIN_SINT16, 0); } - if (sRawValue < ctx->m_StickExtents[nStick].axis[nAxis].sMin) { - ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue; - } - - return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, ctx->m_StickExtents[nStick].axis[nAxis].sMax, SDL_MIN_SINT16, SDL_MAX_SINT16); } static Sint16 ApplySimpleStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue) @@ -1111,14 +1143,17 @@ static Sint16 ApplySimpleStickCalibration(SDL_DriverSwitch_Context *ctx, int nSt sRawValue -= usJoystickCenter; - if (sRawValue > ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax) { - ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax = sRawValue; + if (sRawValue >= 0) { + if (sRawValue > ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax) { + ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax = sRawValue; + } + return (Sint16)HIDAPI_RemapVal(sRawValue, 0, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax, 0, SDL_MAX_SINT16); + } else { + if (sRawValue < ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin) { + ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin = sRawValue; + } + return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin, 0, SDL_MIN_SINT16, 0); } - if (sRawValue < ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin) { - ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin = sRawValue; - } - - return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax, SDL_MIN_SINT16, SDL_MAX_SINT16); } static Uint8 RemapButton(SDL_DriverSwitch_Context *ctx, Uint8 button) @@ -1519,6 +1554,10 @@ static bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys ctx->m_bSyncWrite = true; if (!ctx->m_bInputOnly) { +#ifdef SDL_PLATFORM_MACOS + // Wait for the OS to finish its handshake with the controller + SDL_Delay(250); +#endif GetInitialInputMode(ctx); ctx->m_nCurrentInputMode = ctx->m_nInitialInputMode; @@ -2727,6 +2766,7 @@ static bool HIDAPI_DriverSwitch_UpdateDevice(SDL_HIDAPI_Device *device) // Reconnect the Bluetooth device once the USB device is gone if (device->num_joysticks == 0 && device->is_bluetooth && packet_count > 0 && + !device->parent && !HIDAPI_HasConnectedUSBDevice(device->serial)) { HIDAPI_JoystickConnected(device, NULL); } diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c index 6885f8b..c7607ae 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c @@ -363,7 +363,7 @@ static SDL_HIDAPI_Device *HIDAPI_GetDeviceByIndex(int device_index, SDL_Joystick SDL_AssertJoysticksLocked(); for (device = SDL_HIDAPI_devices; device; device = device->next) { - if (device->parent) { + if (device->parent || device->broken) { continue; } if (device->driver) { @@ -533,24 +533,6 @@ static bool HIDAPI_JoystickInit(void) return true; } -#ifdef SDL_USE_LIBUDEV - if (linux_enumeration_method == ENUMERATION_UNSET) { - if (!SDL_GetHintBoolean(SDL_HINT_HIDAPI_UDEV, true)) { - SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, - "udev disabled by SDL_HINT_HIDAPI_UDEV"); - linux_enumeration_method = ENUMERATION_FALLBACK; - } else if (SDL_GetSandbox() != SDL_SANDBOX_NONE) { - SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, - "Container detected, disabling HIDAPI udev integration"); - linux_enumeration_method = ENUMERATION_FALLBACK; - } else { - SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, - "Using udev for HIDAPI joystick device discovery"); - linux_enumeration_method = ENUMERATION_LIBUDEV; - } - } -#endif - if (SDL_hid_init() < 0) { return SDL_SetError("Couldn't initialize hidapi"); } @@ -703,7 +685,7 @@ bool HIDAPI_HasConnectedUSBDevice(const char *serial) } for (device = SDL_HIDAPI_devices; device; device = device->next) { - if (!device->driver) { + if (!device->driver || device->broken) { continue; } @@ -729,7 +711,7 @@ void HIDAPI_DisconnectBluetoothDevice(const char *serial) } for (device = SDL_HIDAPI_devices; device; device = device->next) { - if (!device->driver) { + if (!device->driver || device->broken) { continue; } @@ -769,11 +751,12 @@ bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoysti ++SDL_HIDAPI_numjoysticks; - SDL_PrivateJoystickAdded(joystickID); - if (pJoystickID) { *pJoystickID = joystickID; } + + SDL_PrivateJoystickAdded(joystickID); + return true; } @@ -886,10 +869,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; @@ -1034,6 +1015,10 @@ static bool HIDAPI_CreateCombinedJoyCons(void) // This device is already part of a combined device continue; } + if (device->broken) { + // This device can't be used + continue; + } SDL_GetJoystickGUIDInfo(device->guid, &vendor, &product, NULL, NULL); @@ -1067,6 +1052,11 @@ static bool HIDAPI_CreateCombinedJoyCons(void) info.usage = USB_USAGE_GENERIC_GAMEPAD; info.manufacturer_string = L"Nintendo"; info.product_string = L"Switch Joy-Con (L/R)"; + if (children[0]->is_bluetooth || children[1]->is_bluetooth) { + info.bus_type = SDL_HID_API_BUS_BLUETOOTH; + } else { + info.bus_type = SDL_HID_API_BUS_USB; + } combined = HIDAPI_AddDevice(&info, 2, children); if (combined && combined->driver) { @@ -1149,11 +1139,18 @@ 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 && device->broken && device->parent) { + HIDAPI_DelDevice(device->parent); + + // We deleted a different device here, restart the loop + goto check_removed; + } device = next; } @@ -1496,7 +1493,7 @@ static bool HIDAPI_JoystickOpen(SDL_Joystick *joystick, int device_index) SDL_AssertJoysticksLocked(); - if (!device || !device->driver) { + if (!device || !device->driver || device->broken) { // This should never happen - validated before being called return SDL_SetError("Couldn't find HIDAPI device at index %d", device_index); } diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h index f8d7709..9cd9f40 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -101,6 +101,10 @@ typedef struct SDL_HIDAPI_Device // Used to flag that the device is being updated bool updating; + // Used to flag devices that failed open + // This can happen on Windows with Bluetooth devices that have turned off + bool broken; + struct SDL_HIDAPI_Device *parent; int num_children; struct SDL_HIDAPI_Device **children; diff --git a/libs/SDL3/src/joystick/linux/SDL_sysjoystick.c b/libs/SDL3/src/joystick/linux/SDL_sysjoystick.c index 283f805..ea73821 100644 --- a/libs/SDL3/src/joystick/linux/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/linux/SDL_sysjoystick.c @@ -1518,6 +1518,7 @@ static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item) return NULL; } if (ioctl(fd_item, EVIOCGUNIQ(sizeof(uniq_item) - 1), &uniq_item) < 0) { + close(fd_item); return NULL; } close(fd_item); diff --git a/libs/SDL3/src/joystick/usb_ids.h b/libs/SDL3/src/joystick/usb_ids.h index 794beb8..4c85b6d 100644 --- a/libs/SDL3/src/joystick/usb_ids.h +++ b/libs/SDL3/src/joystick/usb_ids.h @@ -76,6 +76,7 @@ #define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS5 0x0184 #define USB_PRODUCT_HORI_STEAM_CONTROLLER 0x01AB #define USB_PRODUCT_HORI_STEAM_CONTROLLER_BT 0x0196 +#define USB_PRODUCT_HORI_TAIKO_DRUM_CONTROLLER 0x01b2 #define USB_PRODUCT_LOGITECH_F310 0xc216 #define USB_PRODUCT_LOGITECH_CHILLSTREAM 0xcad1 #define USB_PRODUCT_MADCATZ_SAITEK_SIDE_PANEL_CONTROL_DECK 0x2218 @@ -83,6 +84,7 @@ #define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRED 0x0d17 #define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS 0x0d18 #define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRED 0x0d19 +#define USB_PRODUCT_NACON_REVOLUTION_X_UNLIMITED_BT 0x0689 #define USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER 0x0337 #define USB_PRODUCT_NINTENDO_N64_CONTROLLER 0x2019 #define USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER 0x201e diff --git a/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c b/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c index b00218d..5b4b6fc 100644 --- a/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c +++ b/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c @@ -40,11 +40,7 @@ #define CONVERT_MAGNITUDE(x) (((x)*10000) / 0x7FFF) // external variables referenced. -#ifdef SDL_VIDEO_DRIVER_WINDOWS extern HWND SDL_HelperWindow; -#else -static const HWND SDL_HelperWindow = NULL; -#endif // local variables static bool coinitialized = false; diff --git a/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c b/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c index d5166de..8590d9a 100644 --- a/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c +++ b/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c @@ -158,8 +158,11 @@ struct joystick_hwdata Uint8 wgi_correlation_count; Uint8 wgi_uncorrelate_count; WindowsGamingInputGamepadState *wgi_slot; + struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; #endif + bool triggers_rumbling; + SDL_RAWINPUT_Device *device; }; typedef struct joystick_hwdata RAWINPUT_DeviceContext; @@ -447,7 +450,6 @@ typedef struct WindowsGamingInputGamepadState bool used; // Is currently mapped to an SDL device bool connected; // Just used during update to track disconnected Uint8 correlation_id; - struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; } WindowsGamingInputGamepadState; static struct @@ -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); } @@ -1026,7 +1030,7 @@ static bool RAWINPUT_JoystickInit(void) { SDL_assert(!SDL_RAWINPUT_inited); - if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT, true)) { + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT, false)) { return true; } @@ -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,12 +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 + ctx->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; + ctx->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); + HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, ctx->vibration); if (SUCCEEDED(hr)) { rumbled = true; } @@ -1504,15 +1508,15 @@ static bool RAWINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_ #ifdef SDL_JOYSTICK_RAWINPUT_WGI RAWINPUT_DeviceContext *ctx = joystick->hwdata; + ctx->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16; + ctx->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16; if (ctx->wgi_correlated) { WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; - HRESULT hr; - gamepad_state->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16; - gamepad_state->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16; - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration); + HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, ctx->vibration); 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/process/windows/SDL_windowsprocess.c b/libs/SDL3/src/process/windows/SDL_windowsprocess.c index c1aee5c..221d36d 100644 --- a/libs/SDL3/src/process/windows/SDL_windowsprocess.c +++ b/libs/SDL3/src/process/windows/SDL_windowsprocess.c @@ -304,6 +304,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID if (stderr_option == SDL_PROCESS_STDIO_INHERITED) { stderr_option = SDL_PROCESS_STDIO_NULL; } + creation_flags |= CREATE_NO_WINDOW; } switch (stdin_option) { @@ -329,7 +330,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID startup_info.hStdInput = stdin_pipe[READ_END]; break; case SDL_PROCESS_STDIO_NULL: - startup_info.hStdInput = CreateFile(TEXT("\\\\.\\NUL"), GENERIC_ALL, 0, &security_attributes, OPEN_EXISTING, 0, NULL); + startup_info.hStdInput = CreateFile(TEXT("\\\\.\\NUL"), (GENERIC_READ | GENERIC_WRITE), 0, &security_attributes, OPEN_EXISTING, 0, NULL); break; case SDL_PROCESS_STDIO_INHERITED: default: @@ -366,7 +367,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID startup_info.hStdOutput = stdout_pipe[WRITE_END]; break; case SDL_PROCESS_STDIO_NULL: - startup_info.hStdOutput = CreateFile(TEXT("\\\\.\\NUL"), GENERIC_ALL, 0, &security_attributes, OPEN_EXISTING, 0, NULL); + startup_info.hStdOutput = CreateFile(TEXT("\\\\.\\NUL"), (GENERIC_READ | GENERIC_WRITE), 0, &security_attributes, OPEN_EXISTING, 0, NULL); break; case SDL_PROCESS_STDIO_INHERITED: default: @@ -412,7 +413,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID startup_info.hStdError = stderr_pipe[WRITE_END]; break; case SDL_PROCESS_STDIO_NULL: - startup_info.hStdError = CreateFile(TEXT("\\\\.\\NUL"), GENERIC_ALL, 0, &security_attributes, OPEN_EXISTING, 0, NULL); + startup_info.hStdError = CreateFile(TEXT("\\\\.\\NUL"), (GENERIC_READ | GENERIC_WRITE), 0, &security_attributes, OPEN_EXISTING, 0, NULL); break; case SDL_PROCESS_STDIO_INHERITED: default: diff --git a/libs/SDL3/src/render/SDL_render.c b/libs/SDL3/src/render/SDL_render.c index b8e6653..cd3a929 100644 --- a/libs/SDL3/src/render/SDL_render.c +++ b/libs/SDL3/src/render/SDL_render.c @@ -25,6 +25,7 @@ #include "SDL_sysrender.h" #include "SDL_render_debug_font.h" #include "software/SDL_render_sw_c.h" +#include "../events/SDL_windowevents_c.h" #include "../video/SDL_pixels_c.h" #include "../video/SDL_video_c.h" @@ -212,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; } @@ -461,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); } @@ -471,17 +470,18 @@ static bool QueueCmdSetClipRect(SDL_Renderer *renderer) { bool result = true; - SDL_Rect clip_rect = renderer->view->pixel_clip_rect; + const SDL_RenderViewState *view = renderer->view; + SDL_Rect clip_rect = view->pixel_clip_rect; if (!renderer->cliprect_queued || - renderer->view->clipping_enabled != renderer->last_queued_cliprect_enabled || + view->clipping_enabled != renderer->last_queued_cliprect_enabled || SDL_memcmp(&clip_rect, &renderer->last_queued_cliprect, sizeof(clip_rect)) != 0) { SDL_RenderCommand *cmd = AllocateRenderCommand(renderer); if (cmd) { cmd->command = SDL_RENDERCMD_SETCLIPRECT; - cmd->data.cliprect.enabled = renderer->view->clipping_enabled; + cmd->data.cliprect.enabled = view->clipping_enabled; SDL_copyp(&cmd->data.cliprect.rect, &clip_rect); SDL_copyp(&renderer->last_queued_cliprect, &clip_rect); - renderer->last_queued_cliprect_enabled = renderer->view->clipping_enabled; + renderer->last_queued_cliprect_enabled = view->clipping_enabled; renderer->cliprect_queued = true; } else { result = false; @@ -571,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; } } @@ -736,9 +739,9 @@ static void UpdateMainViewDimensions(SDL_Renderer *renderer) if (renderer->window) { SDL_GetWindowSize(renderer->window, &window_w, &window_h); } - SDL_GetRenderOutputSize(renderer, &renderer->output_pixel_w, &renderer->output_pixel_h); - renderer->main_view.pixel_w = renderer->output_pixel_w; - renderer->main_view.pixel_h = renderer->output_pixel_h; + + SDL_GetRenderOutputSize(renderer, &renderer->main_view.pixel_w, &renderer->main_view.pixel_h); + if (window_w > 0 && window_h > 0) { renderer->dpi_scale.x = (float)renderer->main_view.pixel_w / window_w; renderer->dpi_scale.y = (float)renderer->main_view.pixel_h / window_h; @@ -810,8 +813,7 @@ const char *SDL_GetRenderDriver(int index) { #ifndef SDL_RENDER_DISABLED if (index < 0 || index >= SDL_GetNumRenderDrivers()) { - SDL_SetError("index must be in the range of 0 - %d", - SDL_GetNumRenderDrivers() - 1); + SDL_InvalidParamError("index"); return NULL; } return render_drivers[index]->name; @@ -821,10 +823,15 @@ const char *SDL_GetRenderDriver(int index) #endif } -void SDL_RendererEventWatch(SDL_Renderer *renderer, SDL_Event *event) +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); } @@ -832,7 +839,10 @@ void SDL_RendererEventWatch(SDL_Renderer *renderer, SDL_Event *event) if (event->type == SDL_EVENT_WINDOW_RESIZED || event->type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED || event->type == SDL_EVENT_WINDOW_METAL_VIEW_RESIZED) { - UpdateLogicalPresentation(renderer); + SDL_RenderViewState *view = renderer->view; + renderer->view = &renderer->main_view; // only update the main_view (the window framebuffer) for window changes. + UpdateLogicalPresentation(renderer); + renderer->view = view; // put us back on whatever the current render target's actual view is. } else if (event->type == SDL_EVENT_WINDOW_HIDDEN) { renderer->hidden = true; } else if (event->type == SDL_EVENT_WINDOW_SHOWN) { @@ -850,6 +860,7 @@ void SDL_RendererEventWatch(SDL_Renderer *renderer, SDL_Event *event) event->type == SDL_EVENT_WINDOW_HDR_STATE_CHANGED) { UpdateHDRProperties(renderer); } + return true; } bool SDL_CreateWindowAndRenderer(const char *title, int width, int height, SDL_WindowFlags window_flags, SDL_Window **window, SDL_Renderer **renderer) @@ -1108,13 +1119,12 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) SDL_SetRenderViewport(renderer, NULL); - 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); - } + if (window) { + SDL_AddWindowEventWatch(SDL_WINDOW_EVENT_WATCH_NORMAL, SDL_RendererEventWatch, renderer); } + + int vsync = (int)SDL_GetNumberProperty(props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 0); + SDL_SetRenderVSync(renderer, vsync); SDL_CalculateSimulatedVSyncInterval(renderer, window); SDL_LogInfo(SDL_LOG_CATEGORY_RENDER, @@ -1239,11 +1249,12 @@ bool SDL_GetCurrentRenderOutputSize(SDL_Renderer *renderer, int *w, int *h) CHECK_RENDERER_MAGIC(renderer, false); + const SDL_RenderViewState *view = renderer->view; if (w) { - *w = renderer->view->pixel_w; + *w = view->pixel_w; } if (h) { - *h = renderer->view->pixel_h; + *h = view->pixel_h; } return true; } @@ -1282,7 +1293,19 @@ static SDL_PixelFormat GetClosestSupportedFormat(SDL_Renderer *renderer, SDL_Pix { int i; - if (SDL_ISPIXELFORMAT_FOURCC(format)) { + if (format == SDL_PIXELFORMAT_MJPG) { + // We'll decode to SDL_PIXELFORMAT_NV12 or SDL_PIXELFORMAT_RGBA32 + for (i = 0; i < renderer->num_texture_formats; ++i) { + if (renderer->texture_formats[i] == SDL_PIXELFORMAT_NV12) { + return renderer->texture_formats[i]; + } + } + for (i = 0; i < renderer->num_texture_formats; ++i) { + if (renderer->texture_formats[i] == SDL_PIXELFORMAT_RGBA32) { + return renderer->texture_formats[i]; + } + } + } else if (SDL_ISPIXELFORMAT_FOURCC(format)) { // Look for an exact match for (i = 0; i < renderer->num_texture_formats; ++i) { if (renderer->texture_formats[i] == format) { @@ -1411,11 +1434,15 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert closest_format = renderer->texture_formats[0]; } - default_colorspace = SDL_GetDefaultColorspaceForFormat(closest_format); - if (SDL_COLORSPACETYPE(texture->colorspace) == SDL_COLORSPACETYPE(default_colorspace)) { - SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, texture->colorspace); + if (format == SDL_PIXELFORMAT_MJPG && closest_format == SDL_PIXELFORMAT_NV12) { + SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, SDL_COLORSPACE_JPEG); } else { - SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, default_colorspace); + default_colorspace = SDL_GetDefaultColorspaceForFormat(closest_format); + if (SDL_COLORSPACETYPE(texture->colorspace) == SDL_COLORSPACETYPE(default_colorspace)) { + SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, texture->colorspace); + } else { + SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, default_colorspace); + } } SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, closest_format); SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, texture->access); @@ -1444,7 +1471,9 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert texture->next = texture->native; renderer->textures = texture; - if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) { + if (texture->format == SDL_PIXELFORMAT_MJPG) { + // We have a custom decode + upload path for this + } else if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) { #ifdef SDL_HAVE_YUV texture->yuv = SDL_SW_CreateYUVTexture(texture->format, texture->colorspace, w, h); #else @@ -1629,8 +1658,6 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s } } - surface_colorspace = SDL_GetSurfaceColorspace(surface); - // Try to have the best pixel format for the texture // No alpha, but a colorkey => promote to alpha if (!SDL_ISPIXELFORMAT_ALPHA(surface->format) && SDL_SurfaceHasColorKey(surface)) { @@ -1692,6 +1719,9 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s } } + surface_colorspace = SDL_GetSurfaceColorspace(surface); + texture_colorspace = surface_colorspace; + if (surface_colorspace == SDL_COLORSPACE_SRGB_LINEAR || SDL_COLORSPACETRANSFER(surface_colorspace) == SDL_TRANSFER_CHARACTERISTICS_PQ) { if (SDL_ISPIXELFORMAT_FLOAT(format)) { @@ -1933,8 +1963,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 && @@ -1942,12 +1970,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; } @@ -2030,9 +2056,9 @@ static bool SDL_UpdateTextureNative(SDL_Texture *texture, const SDL_Rect *rect, if (!SDL_LockTexture(native, rect, &native_pixels, &native_pitch)) { return false; } - SDL_ConvertPixels(rect->w, rect->h, - texture->format, pixels, pitch, - native->format, native_pixels, native_pitch); + SDL_ConvertPixelsAndColorspace(rect->w, rect->h, + texture->format, texture->colorspace, 0, pixels, pitch, + native->format, native->colorspace, 0, native_pixels, native_pitch); SDL_UnlockTexture(native); } else { // Use a temporary buffer for updating @@ -2043,9 +2069,9 @@ static bool SDL_UpdateTextureNative(SDL_Texture *texture, const SDL_Rect *rect, if (!temp_pixels) { return false; } - SDL_ConvertPixels(rect->w, rect->h, - texture->format, pixels, pitch, - native->format, temp_pixels, temp_pitch); + SDL_ConvertPixelsAndColorspace(rect->w, rect->h, + texture->format, texture->colorspace, 0, pixels, pitch, + native->format, native->colorspace, 0, temp_pixels, temp_pitch); SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch); SDL_free(temp_pixels); } @@ -2541,121 +2567,136 @@ SDL_Texture *SDL_GetRenderTarget(SDL_Renderer *renderer) static void UpdateLogicalPresentation(SDL_Renderer *renderer) { - if (renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_DISABLED) { - renderer->main_view.logical_offset.x = renderer->main_view.logical_offset.y = 0.0f; - renderer->main_view.logical_scale.x = renderer->main_view.logical_scale.y = 1.0f; - renderer->main_view.current_scale.x = renderer->main_view.scale.x; // skip the multiplications against 1.0f. - renderer->main_view.current_scale.y = renderer->main_view.scale.y; - UpdateMainViewDimensions(renderer); - UpdatePixelClipRect(renderer, &renderer->main_view); - return; // All done! - } - + SDL_RenderViewState *view = renderer->view; + const bool is_main_view = (view == &renderer->main_view); + const float logical_w = view->logical_w; + const float logical_h = view->logical_h; int iwidth, iheight; - SDL_GetRenderOutputSize(renderer, &iwidth, &iheight); - const float output_w = (float)iwidth; - const float output_h = (float)iheight; - const float logical_w = renderer->logical_w; - const float logical_h = renderer->logical_h; - const float want_aspect = logical_w / logical_h; - const float real_aspect = output_w / output_h; - - renderer->logical_src_rect.x = 0.0f; - renderer->logical_src_rect.y = 0.0f; - renderer->logical_src_rect.w = logical_w; - renderer->logical_src_rect.h = logical_h; - - if ((logical_w <= 0.0f) || (logical_h <= 0.0f)) { - renderer->logical_dst_rect.x = 0.0f; - renderer->logical_dst_rect.y = 0.0f; - renderer->logical_dst_rect.w = output_w; - renderer->logical_dst_rect.h = output_h; - } else if (renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_INTEGER_SCALE) { - float scale; - if (want_aspect > real_aspect) { - scale = (float)((int)output_w / (int)logical_w); // This an integer division! - } else { - scale = (float)((int)output_h / (int)logical_h); // This an integer division! - } - - if (scale < 1.0f) { - scale = 1.0f; - } - - renderer->logical_dst_rect.w = SDL_floorf(logical_w * scale); - renderer->logical_dst_rect.x = (output_w - renderer->logical_dst_rect.w) / 2.0f; - renderer->logical_dst_rect.h = SDL_floorf(logical_h * scale); - renderer->logical_dst_rect.y = (output_h - renderer->logical_dst_rect.h) / 2.0f; - - } else if (renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_STRETCH || - SDL_fabsf(want_aspect - real_aspect) < 0.0001f) { - renderer->logical_dst_rect.x = 0.0f; - renderer->logical_dst_rect.y = 0.0f; - renderer->logical_dst_rect.w = output_w; - renderer->logical_dst_rect.h = output_h; - - } else if (want_aspect > real_aspect) { - if (renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) { - // We want a wider aspect ratio than is available - letterbox it - const float scale = output_w / logical_w; - renderer->logical_dst_rect.x = 0.0f; - renderer->logical_dst_rect.w = output_w; - renderer->logical_dst_rect.h = SDL_floorf(logical_h * scale); - renderer->logical_dst_rect.y = (output_h - renderer->logical_dst_rect.h) / 2.0f; - } else { // renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_OVERSCAN - /* We want a wider aspect ratio than is available - - zoom so logical height matches the real height - and the width will grow off the screen - */ - const float scale = output_h / logical_h; - renderer->logical_dst_rect.y = 0.0f; - renderer->logical_dst_rect.h = output_h; - renderer->logical_dst_rect.w = SDL_floorf(logical_w * scale); - renderer->logical_dst_rect.x = (output_w - renderer->logical_dst_rect.w) / 2.0f; - } + if (!is_main_view && renderer->target) { + iwidth = (int)renderer->target->w; + iheight = (int)renderer->target->h; } else { - if (renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) { - // We want a narrower aspect ratio than is available - use side-bars - const float scale = output_h / logical_h; - renderer->logical_dst_rect.y = 0.0f; - renderer->logical_dst_rect.h = output_h; - renderer->logical_dst_rect.w = SDL_floorf(logical_w * scale); - renderer->logical_dst_rect.x = (output_w - renderer->logical_dst_rect.w) / 2.0f; - } else { // renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_OVERSCAN - /* We want a narrower aspect ratio than is available - - zoom so logical width matches the real width - and the height will grow off the screen - */ - const float scale = output_w / logical_w; - renderer->logical_dst_rect.x = 0.0f; - renderer->logical_dst_rect.w = output_w; - renderer->logical_dst_rect.h = SDL_floorf(logical_h * scale); - renderer->logical_dst_rect.y = (output_h - renderer->logical_dst_rect.h) / 2.0f; - } + SDL_GetRenderOutputSize(renderer, &iwidth, &iheight); } - renderer->main_view.logical_scale.x = (logical_w > 0.0f) ? renderer->logical_dst_rect.w / logical_w : 0.0f; - renderer->main_view.logical_scale.y = (logical_h > 0.0f) ? renderer->logical_dst_rect.h / logical_h : 0.0f; - renderer->main_view.current_scale.x = renderer->main_view.scale.x * renderer->main_view.logical_scale.x; - renderer->main_view.current_scale.y = renderer->main_view.scale.y * renderer->main_view.logical_scale.y; - renderer->main_view.logical_offset.x = renderer->logical_dst_rect.x; - renderer->main_view.logical_offset.y = renderer->logical_dst_rect.y; + view->logical_src_rect.x = 0.0f; + view->logical_src_rect.y = 0.0f; + view->logical_src_rect.w = logical_w; + view->logical_src_rect.h = logical_h; - UpdateMainViewDimensions(renderer); // this will replace pixel_w and pixel_h while making sure the dpi_scale is right. - renderer->main_view.pixel_w = (int) renderer->logical_dst_rect.w; - renderer->main_view.pixel_h = (int) renderer->logical_dst_rect.h; - UpdatePixelViewport(renderer, &renderer->main_view); - UpdatePixelClipRect(renderer, &renderer->main_view); + if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_DISABLED) { + view->logical_dst_rect.x = 0.0f; + view->logical_dst_rect.y = 0.0f; + view->logical_dst_rect.w = iwidth; + view->logical_dst_rect.h = iheight; + view->logical_offset.x = view->logical_offset.y = 0.0f; + view->logical_scale.x = view->logical_scale.y = 1.0f; + view->current_scale.x = view->scale.x; // skip the multiplications against 1.0f. + view->current_scale.y = view->scale.y; + } else { + const float output_w = (float)iwidth; + const float output_h = (float)iheight; + const float want_aspect = logical_w / logical_h; + const float real_aspect = output_w / output_h; + + if ((logical_w <= 0.0f) || (logical_h <= 0.0f)) { + view->logical_dst_rect.x = 0.0f; + view->logical_dst_rect.y = 0.0f; + view->logical_dst_rect.w = output_w; + view->logical_dst_rect.h = output_h; + } else if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_INTEGER_SCALE) { + float scale; + if (want_aspect > real_aspect) { + scale = (float)((int)output_w / (int)logical_w); // This an integer division! + } else { + scale = (float)((int)output_h / (int)logical_h); // This an integer division! + } + + if (scale < 1.0f) { + scale = 1.0f; + } + + view->logical_dst_rect.w = SDL_floorf(logical_w * scale); + view->logical_dst_rect.x = (output_w - view->logical_dst_rect.w) / 2.0f; + view->logical_dst_rect.h = SDL_floorf(logical_h * scale); + view->logical_dst_rect.y = (output_h - view->logical_dst_rect.h) / 2.0f; + + } else if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_STRETCH || SDL_fabsf(want_aspect - real_aspect) < 0.0001f) { + view->logical_dst_rect.x = 0.0f; + view->logical_dst_rect.y = 0.0f; + view->logical_dst_rect.w = output_w; + view->logical_dst_rect.h = output_h; + + } else if (want_aspect > real_aspect) { + if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) { + // We want a wider aspect ratio than is available - letterbox it + const float scale = output_w / logical_w; + view->logical_dst_rect.x = 0.0f; + view->logical_dst_rect.w = output_w; + view->logical_dst_rect.h = SDL_floorf(logical_h * scale); + view->logical_dst_rect.y = (output_h - view->logical_dst_rect.h) / 2.0f; + } else { // view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_OVERSCAN + /* We want a wider aspect ratio than is available - + zoom so logical height matches the real height + and the width will grow off the screen + */ + const float scale = output_h / logical_h; + view->logical_dst_rect.y = 0.0f; + view->logical_dst_rect.h = output_h; + view->logical_dst_rect.w = SDL_floorf(logical_w * scale); + view->logical_dst_rect.x = (output_w - view->logical_dst_rect.w) / 2.0f; + } + } else { + if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) { + // We want a narrower aspect ratio than is available - use side-bars + const float scale = output_h / logical_h; + view->logical_dst_rect.y = 0.0f; + view->logical_dst_rect.h = output_h; + view->logical_dst_rect.w = SDL_floorf(logical_w * scale); + view->logical_dst_rect.x = (output_w - view->logical_dst_rect.w) / 2.0f; + } else { // view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_OVERSCAN + /* We want a narrower aspect ratio than is available - + zoom so logical width matches the real width + and the height will grow off the screen + */ + const float scale = output_w / logical_w; + view->logical_dst_rect.x = 0.0f; + view->logical_dst_rect.w = output_w; + view->logical_dst_rect.h = SDL_floorf(logical_h * scale); + view->logical_dst_rect.y = (output_h - view->logical_dst_rect.h) / 2.0f; + } + } + + view->logical_scale.x = (logical_w > 0.0f) ? view->logical_dst_rect.w / logical_w : 0.0f; + view->logical_scale.y = (logical_h > 0.0f) ? view->logical_dst_rect.h / logical_h : 0.0f; + view->current_scale.x = view->scale.x * view->logical_scale.x; + view->current_scale.y = view->scale.y * view->logical_scale.y; + view->logical_offset.x = view->logical_dst_rect.x; + view->logical_offset.y = view->logical_dst_rect.y; + } + + if (is_main_view) { + // This makes sure the dpi_scale is right. It also sets pixel_w and pixel_h, but we're going to change them directly below here. + UpdateMainViewDimensions(renderer); + } + + view->pixel_w = (int) view->logical_dst_rect.w; + view->pixel_h = (int) view->logical_dst_rect.h; + UpdatePixelViewport(renderer, view); + UpdatePixelClipRect(renderer, view); + QueueCmdSetViewport(renderer); + QueueCmdSetClipRect(renderer); } bool SDL_SetRenderLogicalPresentation(SDL_Renderer *renderer, int w, int h, SDL_RendererLogicalPresentation mode) { CHECK_RENDERER_MAGIC(renderer, false); - renderer->logical_presentation_mode = mode; - renderer->logical_w = w; - renderer->logical_h = h; + SDL_RenderViewState *view = renderer->view; + view->logical_presentation_mode = mode; + view->logical_w = w; + view->logical_h = h; UpdateLogicalPresentation(renderer); @@ -2672,9 +2713,10 @@ bool SDL_GetRenderLogicalPresentation(SDL_Renderer *renderer, int *w, int *h, SD CHECK_RENDERER_MAGIC(renderer, false); - SETVAL(w, renderer->logical_w); - SETVAL(h, renderer->logical_h); - SETVAL(mode, renderer->logical_presentation_mode); + const SDL_RenderViewState *view = renderer->view; + SETVAL(w, view->logical_w); + SETVAL(h, view->logical_h); + SETVAL(mode, view->logical_presentation_mode); #undef SETVAL @@ -2690,21 +2732,14 @@ bool SDL_GetRenderLogicalPresentationRect(SDL_Renderer *renderer, SDL_FRect *rec CHECK_RENDERER_MAGIC(renderer, false); if (rect) { - if (renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_DISABLED) { - rect->x = 0.0f; - rect->y = 0.0f; - rect->w = (float)renderer->output_pixel_w; - rect->h = (float)renderer->output_pixel_h; - } else { - SDL_copyp(rect, &renderer->logical_dst_rect); - } + SDL_copyp(rect, &renderer->view->logical_dst_rect); } return true; } -static void SDL_RenderLogicalBorders(SDL_Renderer *renderer) +static void SDL_RenderLogicalBorders(SDL_Renderer *renderer, const SDL_FRect *dst) { - const SDL_FRect *dst = &renderer->logical_dst_rect; + const SDL_RenderViewState *view = renderer->view; if (dst->x > 0.0f || dst->y > 0.0f) { SDL_BlendMode saved_blend_mode = renderer->blendMode; @@ -2719,11 +2754,11 @@ static void SDL_RenderLogicalBorders(SDL_Renderer *renderer) rect.x = 0.0f; rect.y = 0.0f; rect.w = dst->x; - rect.h = (float)renderer->view->pixel_h; + rect.h = (float)view->pixel_h; SDL_RenderFillRect(renderer, &rect); rect.x = dst->x + dst->w; - rect.w = (float)renderer->view->pixel_w - rect.x; + rect.w = (float)view->pixel_w - rect.x; SDL_RenderFillRect(renderer, &rect); } @@ -2732,12 +2767,12 @@ static void SDL_RenderLogicalBorders(SDL_Renderer *renderer) rect.x = 0.0f; rect.y = 0.0f; - rect.w = (float)renderer->view->pixel_w; + rect.w = (float)view->pixel_w; rect.h = dst->y; SDL_RenderFillRect(renderer, &rect); rect.y = dst->y + dst->h; - rect.h = (float)renderer->view->pixel_h - rect.y; + rect.h = (float)view->pixel_h - rect.y; SDL_RenderFillRect(renderer, &rect); } @@ -2748,17 +2783,19 @@ static void SDL_RenderLogicalBorders(SDL_Renderer *renderer) static void SDL_RenderLogicalPresentation(SDL_Renderer *renderer) { - const SDL_RendererLogicalPresentation mode = renderer->logical_presentation_mode; + SDL_assert(renderer->view == &renderer->main_view); + + SDL_RenderViewState *view = &renderer->main_view; + const SDL_RendererLogicalPresentation mode = view->logical_presentation_mode; if (mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) { // save off some state we're going to trample. - SDL_assert(renderer->view == &renderer->main_view); - SDL_RenderViewState *view = &renderer->main_view; - const int logical_w = renderer->logical_w; - const int logical_h = renderer->logical_h; + const int logical_w = view->logical_w; + const int logical_h = view->logical_h; const float scale_x = view->scale.x; const float scale_y = view->scale.y; const bool clipping_enabled = view->clipping_enabled; SDL_Rect orig_viewport, orig_cliprect; + const SDL_FRect logical_dst_rect = view->logical_dst_rect; SDL_copyp(&orig_viewport, &view->viewport); if (clipping_enabled) { @@ -2774,10 +2811,10 @@ static void SDL_RenderLogicalPresentation(SDL_Renderer *renderer) SDL_SetRenderScale(renderer, 1.0f, 1.0f); // draw the borders. - SDL_RenderLogicalBorders(renderer); + SDL_RenderLogicalBorders(renderer, &logical_dst_rect); // now set everything back. - renderer->logical_presentation_mode = mode; + view->logical_presentation_mode = mode; SDL_SetRenderViewport(renderer, &orig_viewport); if (clipping_enabled) { SDL_SetRenderClipRect(renderer, &orig_cliprect); @@ -2788,21 +2825,21 @@ static void SDL_RenderLogicalPresentation(SDL_Renderer *renderer) } } -static bool SDL_RenderVectorFromWindow(SDL_Renderer *renderer, float window_dx, float window_dy, float *restrict dx, float *restrict dy) +static bool SDL_RenderVectorFromWindow(SDL_Renderer *renderer, float window_dx, float window_dy, float *dx, float *dy) { // Convert from window coordinates to pixels within the window window_dx *= renderer->dpi_scale.x; window_dy *= renderer->dpi_scale.y; // Convert from pixels within the window to pixels within the view - if (renderer->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED) { - const SDL_FRect *src = &renderer->logical_src_rect; - const SDL_FRect *dst = &renderer->logical_dst_rect; + const SDL_RenderViewState *view = &renderer->main_view; + if (view->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED) { + const SDL_FRect *src = &view->logical_src_rect; + const SDL_FRect *dst = &view->logical_dst_rect; window_dx = (window_dx * src->w) / dst->w; window_dy = (window_dy * src->h) / dst->h; } - const SDL_RenderViewState *view = &renderer->main_view; window_dx /= view->scale.x; window_dy /= view->scale.y; @@ -2822,14 +2859,14 @@ bool SDL_RenderCoordinatesFromWindow(SDL_Renderer *renderer, float window_x, flo render_y = window_y * renderer->dpi_scale.y; // Convert from pixels within the window to pixels within the view - if (renderer->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED) { - const SDL_FRect *src = &renderer->logical_src_rect; - const SDL_FRect *dst = &renderer->logical_dst_rect; + const SDL_RenderViewState *view = &renderer->main_view; + if (view->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED) { + const SDL_FRect *src = &view->logical_src_rect; + const SDL_FRect *dst = &view->logical_dst_rect; render_x = ((render_x - dst->x) * src->w) / dst->w; render_y = ((render_y - dst->y) * src->h) / dst->h; } - const SDL_RenderViewState *view = &renderer->main_view; render_x = (render_x / view->scale.x) - view->viewport.x; render_y = (render_y / view->scale.y) - view->viewport.y; @@ -2851,9 +2888,9 @@ bool SDL_RenderCoordinatesToWindow(SDL_Renderer *renderer, float x, float y, flo y = (view->viewport.y + y) * view->scale.y; // Convert from render coordinates to pixels within the window - if (renderer->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED) { - const SDL_FRect *src = &renderer->logical_src_rect; - const SDL_FRect *dst = &renderer->logical_dst_rect; + if (view->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED) { + const SDL_FRect *src = &view->logical_src_rect; + const SDL_FRect *dst = &view->logical_dst_rect; x = dst->x + ((x * dst->w) / src->w); y = dst->y + ((y * dst->h) / src->h); } @@ -2944,18 +2981,17 @@ bool SDL_SetRenderViewport(SDL_Renderer *renderer, const SDL_Rect *rect) { CHECK_RENDERER_MAGIC(renderer, false); + SDL_RenderViewState *view = renderer->view; if (rect) { if ((rect->w < 0) || (rect->h < 0)) { return SDL_SetError("rect has a negative size"); } - SDL_copyp(&renderer->view->viewport, rect); + SDL_copyp(&view->viewport, rect); } else { - renderer->view->viewport.x = 0; - renderer->view->viewport.y = 0; - renderer->view->viewport.w = -1; - renderer->view->viewport.h = -1; + view->viewport.x = view->viewport.y = 0; + view->viewport.w = view->viewport.h = -1; } - UpdatePixelViewport(renderer, renderer->view); + UpdatePixelViewport(renderer, view); return QueueCmdSetViewport(renderer); } @@ -2977,7 +3013,7 @@ bool SDL_GetRenderViewport(SDL_Renderer *renderer, SDL_Rect *rect) } else { rect->w = (int)SDL_ceilf(view->pixel_w / view->current_scale.x); } - if (renderer->view->viewport.h >= 0) { + if (view->viewport.h >= 0) { rect->h = view->viewport.h; } else { rect->h = (int)SDL_ceilf(view->pixel_h / view->current_scale.y); @@ -2990,11 +3026,8 @@ bool SDL_RenderViewportSet(SDL_Renderer *renderer) { CHECK_RENDERER_MAGIC(renderer, false); - if (renderer->view->viewport.w >= 0 && - renderer->view->viewport.h >= 0) { - return true; - } - return false; + const SDL_RenderViewState *view = renderer->view; + return (view->viewport.w >= 0 && view->viewport.h >= 0); } static void GetRenderViewportSize(SDL_Renderer *renderer, SDL_FRect *rect) @@ -3070,14 +3103,15 @@ bool SDL_SetRenderClipRect(SDL_Renderer *renderer, const SDL_Rect *rect) { CHECK_RENDERER_MAGIC(renderer, false) + SDL_RenderViewState *view = renderer->view; if (rect && rect->w >= 0 && rect->h >= 0) { - renderer->view->clipping_enabled = true; - SDL_copyp(&renderer->view->clip_rect, rect); + view->clipping_enabled = true; + SDL_copyp(&view->clip_rect, rect); } else { - renderer->view->clipping_enabled = false; - SDL_zero(renderer->view->clip_rect); + view->clipping_enabled = false; + SDL_zero(view->clip_rect); } - UpdatePixelClipRect(renderer, renderer->view); + UpdatePixelClipRect(renderer, view); return QueueCmdSetClipRect(renderer); } @@ -3108,17 +3142,18 @@ bool SDL_SetRenderScale(SDL_Renderer *renderer, float scaleX, float scaleY) CHECK_RENDERER_MAGIC(renderer, false); - if (renderer->view->scale.x == scaleX && - renderer->view->scale.y == scaleY) { + SDL_RenderViewState *view = renderer->view; + + if ((view->scale.x == scaleX) && (view->scale.y == scaleY)) { return true; } - renderer->view->scale.x = scaleX; - renderer->view->scale.y = scaleY; - renderer->view->current_scale.x = scaleX * renderer->view->logical_scale.x; - renderer->view->current_scale.y = scaleY * renderer->view->logical_scale.y; - UpdatePixelViewport(renderer, renderer->view); - UpdatePixelClipRect(renderer, renderer->view); + view->scale.x = scaleX; + view->scale.y = scaleY; + view->current_scale.x = scaleX * view->logical_scale.x; + view->current_scale.y = scaleY * view->logical_scale.y; + UpdatePixelViewport(renderer, view); + UpdatePixelClipRect(renderer, view); // The scale affects the existing viewport and clip rectangle result &= QueueCmdSetViewport(renderer); @@ -3137,11 +3172,13 @@ bool SDL_GetRenderScale(SDL_Renderer *renderer, float *scaleX, float *scaleY) CHECK_RENDERER_MAGIC(renderer, false); + const SDL_RenderViewState *view = renderer->view; + if (scaleX) { - *scaleX = renderer->view->scale.x; + *scaleX = view->scale.x; } if (scaleY) { - *scaleY = renderer->view->scale.y; + *scaleY = view->scale.y; } return true; } @@ -3326,8 +3363,9 @@ static bool RenderPointsWithRects(SDL_Renderer *renderer, const SDL_FPoint *fpoi return false; } - const float scale_x = renderer->view->current_scale.x; - const float scale_y = renderer->view->current_scale.y; + const SDL_RenderViewState *view = renderer->view; + const float scale_x = view->current_scale.x; + const float scale_y = view->current_scale.y; for (i = 0; i < count; ++i) { frects[i].x = fpoints[i].x * scale_x; frects[i].y = fpoints[i].y * scale_y; @@ -3362,7 +3400,8 @@ bool SDL_RenderPoints(SDL_Renderer *renderer, const SDL_FPoint *points, int coun } #endif - if ((renderer->view->current_scale.x != 1.0f) || (renderer->view->current_scale.y != 1.0f)) { + const SDL_RenderViewState *view = renderer->view; + if ((view->current_scale.x != 1.0f) || (view->current_scale.y != 1.0f)) { result = RenderPointsWithRects(renderer, points, count); } else { result = QueueCmdDrawPoints(renderer, points, count); @@ -3382,7 +3421,8 @@ bool SDL_RenderLine(SDL_Renderer *renderer, float x1, float y1, float x2, float static bool RenderLineBresenham(SDL_Renderer *renderer, int x1, int y1, int x2, int y2, bool draw_last) { - const int MAX_PIXELS = SDL_max(renderer->view->pixel_w, renderer->view->pixel_h) * 4; + const SDL_RenderViewState *view = renderer->view; + const int MAX_PIXELS = SDL_max(view->pixel_w, view->pixel_h) * 4; int i, deltax, deltay, numpixels; int d, dinc1, dinc2; int x, xinc1, xinc2; @@ -3395,7 +3435,7 @@ static bool RenderLineBresenham(SDL_Renderer *renderer, int x1, int y1, int x2, /* the backend might clip this further to the clipping rect, but we just want a basic safety against generating millions of points for massive lines. */ - viewport = renderer->view->pixel_viewport; + viewport = view->pixel_viewport; viewport.x = 0; viewport.y = 0; if (!SDL_GetRectAndLineIntersection(&viewport, &x1, &y1, &x2, &y2)) { @@ -3464,7 +3504,7 @@ static bool RenderLineBresenham(SDL_Renderer *renderer, int x1, int y1, int x2, } } - if ((renderer->view->current_scale.x != 1.0f) || (renderer->view->current_scale.y != 1.0f)) { + if ((view->current_scale.x != 1.0f) || (view->current_scale.y != 1.0f)) { result = RenderPointsWithRects(renderer, points, numpixels); } else { result = QueueCmdDrawPoints(renderer, points, numpixels); @@ -3477,8 +3517,9 @@ static bool RenderLineBresenham(SDL_Renderer *renderer, int x1, int y1, int x2, static bool RenderLinesWithRectsF(SDL_Renderer *renderer, const SDL_FPoint *points, const int count) { - const float scale_x = renderer->view->current_scale.x; - const float scale_y = renderer->view->current_scale.y; + const SDL_RenderViewState *view = renderer->view; + const float scale_x = view->current_scale.x; + const float scale_y = view->current_scale.y; SDL_FRect *frect; SDL_FRect *frects; int i, nrects = 0; @@ -3565,11 +3606,12 @@ bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count } #endif - const bool islogical = ((renderer->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED) && (renderer->view == &renderer->main_view)); + SDL_RenderViewState *view = renderer->view; + const bool islogical = (view->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED); if (islogical || (renderer->line_method == SDL_RENDERLINEMETHOD_GEOMETRY)) { - const float scale_x = renderer->view->current_scale.x; - const float scale_y = renderer->view->current_scale.y; + const float scale_x = view->current_scale.x; + const float scale_y = view->current_scale.y; bool isstack1; bool isstack2; float *xy = SDL_small_alloc(float, 4 * 2 * count, &isstack1); @@ -3584,7 +3626,7 @@ bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count int num_indices = 0; const int size_indices = 4; int cur_index = -4; - const int is_looping = (points[0].x == points[count - 1].x && points[0].y == points[count - 1].y); + const bool is_looping = (points[0].x == points[count - 1].x && points[0].y == points[count - 1].y); SDL_FPoint p; // previous point p.x = p.y = 0.0f; /* p q @@ -3616,7 +3658,7 @@ bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count num_indices += 3; // closed polyline, don´t draw twice the point - if (i || is_looping == 0) { + if (i || !is_looping) { ADD_TRIANGLE(4, 5, 6) ADD_TRIANGLE(4, 6, 7) } @@ -3688,7 +3730,7 @@ bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count } else if (renderer->line_method == SDL_RENDERLINEMETHOD_POINTS) { result = RenderLinesWithRectsF(renderer, points, count); - } else if (renderer->view->scale.x != 1.0f || renderer->view->scale.y != 1.0f) { /* we checked for logical scale elsewhere. */ + } else if (view->scale.x != 1.0f || view->scale.y != 1.0f) { /* we checked for logical scale elsewhere. */ result = RenderLinesWithRectsF(renderer, points, count); } else { result = QueueCmdDrawLines(renderer, points, count); @@ -3793,8 +3835,9 @@ bool SDL_RenderFillRects(SDL_Renderer *renderer, const SDL_FRect *rects, int cou return false; } - const float scale_x = renderer->view->current_scale.x; - const float scale_y = renderer->view->current_scale.y; + const SDL_RenderViewState *view = renderer->view; + const float scale_x = view->current_scale.x; + const float scale_y = view->current_scale.y; for (i = 0; i < count; ++i) { frects[i].x = rects[i].x * scale_x; frects[i].y = rects[i].y * scale_y; @@ -3811,8 +3854,9 @@ bool SDL_RenderFillRects(SDL_Renderer *renderer, const SDL_FRect *rects, int cou static bool SDL_RenderTextureInternal(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FRect *dstrect) { - const float scale_x = renderer->view->current_scale.x; - const float scale_y = renderer->view->current_scale.y; + const SDL_RenderViewState *view = renderer->view; + const float scale_x = view->current_scale.x; + const float scale_y = view->current_scale.y; const bool use_rendergeometry = (!renderer->QueueCopy); bool result; @@ -3951,8 +3995,9 @@ bool SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture, texture->last_command_generation = renderer->render_command_generation; - const float scale_x = renderer->view->current_scale.x; - const float scale_y = renderer->view->current_scale.y; + const SDL_RenderViewState *view = renderer->view; + const float scale_x = view->current_scale.x; + const float scale_y = view->current_scale.y; { float xy[8]; @@ -4085,8 +4130,9 @@ bool SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture, texture->last_command_generation = renderer->render_command_generation; - const float scale_x = renderer->view->current_scale.x; - const float scale_y = renderer->view->current_scale.y; + const SDL_RenderViewState *view = renderer->view; + const float scale_x = view->current_scale.x; + const float scale_y = view->current_scale.y; const bool use_rendergeometry = (!renderer->QueueCopyEx); if (use_rendergeometry) { @@ -4217,10 +4263,11 @@ static bool SDL_RenderTextureTiled_Wrap(SDL_Renderer *renderer, SDL_Texture *tex xy[6] = minx; xy[7] = maxy; + const SDL_RenderViewState *view = renderer->view; return QueueCmdGeometry(renderer, texture, xy, xy_stride, &texture->color, 0 /* color_stride */, uv, uv_stride, num_vertices, indices, num_indices, size_indices, - renderer->view->current_scale.x, renderer->view->current_scale.y, SDL_TEXTURE_ADDRESS_WRAP); + view->current_scale.x, view->current_scale.y, SDL_TEXTURE_ADDRESS_WRAP); } static bool SDL_RenderTextureTiled_Iterate(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float scale, const SDL_FRect *dstrect) @@ -4571,8 +4618,9 @@ static bool SDLCALL SDL_SW_RenderGeometryRaw(SDL_Renderer *renderer, float texw = 0.0f, texh = 0.0f; SDL_BlendMode blendMode = SDL_BLENDMODE_NONE; float r = 0, g = 0, b = 0, a = 0; - const float scale_x = renderer->view->current_scale.x; - const float scale_y = renderer->view->current_scale.y; + const SDL_RenderViewState *view = renderer->view; + const float scale_x = view->current_scale.x; + const float scale_y = view->current_scale.y; // Save SDL_GetRenderDrawBlendMode(renderer, &blendMode); @@ -4972,10 +5020,11 @@ bool SDL_RenderGeometryRaw(SDL_Renderer *renderer, } #endif + const SDL_RenderViewState *view = renderer->view; return QueueCmdGeometry(renderer, texture, xy, xy_stride, color, color_stride, uv, uv_stride, num_vertices, indices, num_indices, size_indices, - renderer->view->current_scale.x, renderer->view->current_scale.y, + view->current_scale.x, view->current_scale.y, texture_address_mode); } @@ -4994,6 +5043,7 @@ SDL_Surface *SDL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect) if (rect) { if (!SDL_GetRectIntersection(rect, &real_rect, &real_rect)) { + SDL_SetError("Can't read outside the current viewport"); return NULL; } } @@ -5206,6 +5256,8 @@ void SDL_DestroyRendererWithoutFreeing(SDL_Renderer *renderer) renderer->destroyed = true; + SDL_RemoveWindowEventWatch(SDL_WINDOW_EVENT_WATCH_NORMAL, SDL_RendererEventWatch, renderer); + if (renderer->window) { SDL_PropertiesID props = SDL_GetWindowProperties(renderer->window); if (SDL_GetPointerProperty(props, SDL_PROP_WINDOW_RENDERER_POINTER, NULL) == renderer) { @@ -5214,6 +5266,10 @@ void SDL_DestroyRendererWithoutFreeing(SDL_Renderer *renderer) SDL_RemoveWindowRenderer(renderer->window, renderer); } + if (renderer->software) { + // Make sure all drawing to a surface is complete + FlushRenderCommands(renderer); + } SDL_DiscardAllCommands(renderer); if (renderer->debug_char_texture_atlas) { @@ -5435,7 +5491,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; @@ -5446,12 +5503,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 375419a..bbafefd 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, @@ -65,8 +66,15 @@ typedef struct SDL_RenderViewState SDL_Rect pixel_clip_rect; bool clipping_enabled; SDL_FPoint scale; + + // Support for logical output coordinates + SDL_RendererLogicalPresentation logical_presentation_mode; + int logical_w, logical_h; + SDL_FRect logical_src_rect; + SDL_FRect logical_dst_rect; SDL_FPoint logical_scale; SDL_FPoint logical_offset; + SDL_FPoint current_scale; // this is just `scale * logical_scale`, precalculated, since we use it a lot. } SDL_RenderViewState; @@ -148,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 @@ -217,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); @@ -248,18 +256,9 @@ struct SDL_Renderer Uint64 simulate_vsync_interval_ns; Uint64 last_present; - // Support for logical output coordinates - SDL_RendererLogicalPresentation logical_presentation_mode; - int logical_w, logical_h; - SDL_FRect logical_src_rect; - SDL_FRect logical_dst_rect; - SDL_RenderViewState *view; SDL_RenderViewState main_view; - // Cache the output size in pixels - int output_pixel_w, output_pixel_h; - // The window pixel to point coordinate scale SDL_FPoint dpi_scale; @@ -339,9 +338,6 @@ extern SDL_RenderDriver GPU_RenderDriver; // Clean up any renderers at shutdown extern void SDL_QuitRender(void); -// Handle window events for a renderer -extern void SDL_RendererEventWatch(SDL_Renderer *renderer, SDL_Event *event); - // Add a supported texture format to a renderer extern bool SDL_AddSupportedTextureFormat(SDL_Renderer *renderer, SDL_PixelFormat format); diff --git a/libs/SDL3/src/render/direct3d/SDL_render_d3d.c b/libs/SDL3/src/render/direct3d/SDL_render_d3d.c index ccb5b3c..1d395f1 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 45943e2..60f646f 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 @@ -231,11 +230,14 @@ SDL_PixelFormat D3D11_DXGIFormatToSDLPixelFormat(DXGI_FORMAT dxgiFormat) case DXGI_FORMAT_B8G8R8A8_UNORM: case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: return SDL_PIXELFORMAT_ARGB8888; + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + return SDL_PIXELFORMAT_ABGR8888; case DXGI_FORMAT_B8G8R8X8_UNORM: case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: return SDL_PIXELFORMAT_XRGB8888; case DXGI_FORMAT_R10G10B10A2_UNORM: - return SDL_PIXELFORMAT_XBGR2101010; + return SDL_PIXELFORMAT_ABGR2101010; case DXGI_FORMAT_R16G16B16A16_FLOAT: return SDL_PIXELFORMAT_RGBA64_FLOAT; default: @@ -248,13 +250,18 @@ static DXGI_FORMAT SDLPixelFormatToDXGITextureFormat(Uint32 format, Uint32 outpu switch (format) { case SDL_PIXELFORMAT_RGBA64_FLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT; - case SDL_PIXELFORMAT_XBGR2101010: + case SDL_PIXELFORMAT_ABGR2101010: return DXGI_FORMAT_R10G10B10A2_UNORM; case SDL_PIXELFORMAT_ARGB8888: if (output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) { return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; } return DXGI_FORMAT_B8G8R8A8_UNORM; + case SDL_PIXELFORMAT_ABGR8888: + if (output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) { + return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + } + return DXGI_FORMAT_R8G8B8A8_UNORM; case SDL_PIXELFORMAT_XRGB8888: if (output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) { return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB; @@ -278,13 +285,18 @@ static DXGI_FORMAT SDLPixelFormatToDXGIMainResourceViewFormat(Uint32 format, Uin switch (format) { case SDL_PIXELFORMAT_RGBA64_FLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT; - case SDL_PIXELFORMAT_XBGR2101010: + case SDL_PIXELFORMAT_ABGR2101010: return DXGI_FORMAT_R10G10B10A2_UNORM; case SDL_PIXELFORMAT_ARGB8888: if (colorspace == SDL_COLORSPACE_SRGB_LINEAR) { return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; } return DXGI_FORMAT_B8G8R8A8_UNORM; + case SDL_PIXELFORMAT_ABGR8888: + if (colorspace == SDL_COLORSPACE_SRGB_LINEAR) { + return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + } + return DXGI_FORMAT_R8G8B8A8_UNORM; case SDL_PIXELFORMAT_XRGB8888: if (colorspace == SDL_COLORSPACE_SRGB_LINEAR) { return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB; @@ -1159,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; @@ -1783,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; @@ -2305,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]; @@ -2318,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]; @@ -2331,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) { @@ -2699,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; @@ -2718,8 +2717,9 @@ static bool D3D11_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL renderer->name = D3D11_RenderDriver.name; SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB8888); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR8888); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XRGB8888); - SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XBGR2101010); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR2101010); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA64_FLOAT); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_YV12); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_IYUV); diff --git a/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h b/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h index e795a0c..b8f40fe 100644 --- a/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h +++ b/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h @@ -685,12 +685,12 @@ attributes #2 = { nounwind readonly } #endif const unsigned char g_main[] = { - 0x44, 0x58, 0x42, 0x43, 0x2b, 0xe5, 0x43, 0x0a, 0x03, 0x52, 0x0f, 0x2c, - 0xe8, 0x70, 0xa0, 0x5e, 0x29, 0xbe, 0x51, 0x24, 0x01, 0x00, 0x00, 0x00, - 0xc1, 0x20, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x44, 0x58, 0x42, 0x43, 0x78, 0xad, 0xb4, 0x13, 0x3d, 0x52, 0x26, 0xa9, + 0x43, 0x6f, 0x29, 0xd4, 0x3d, 0xe5, 0x45, 0x27, 0x01, 0x00, 0x00, 0x00, + 0xd9, 0x20, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0xdb, 0x00, 0x00, 0x00, 0x15, 0x01, 0x00, 0x00, - 0x61, 0x02, 0x00, 0x00, 0x49, 0x03, 0x00, 0x00, 0xc1, 0x0c, 0x00, 0x00, - 0xdd, 0x0c, 0x00, 0x00, 0x53, 0x46, 0x49, 0x30, 0x08, 0x00, 0x00, 0x00, + 0x61, 0x02, 0x00, 0x00, 0x61, 0x03, 0x00, 0x00, 0xd9, 0x0c, 0x00, 0x00, + 0xf5, 0x0c, 0x00, 0x00, 0x53, 0x46, 0x49, 0x30, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, 0x31, 0x83, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -736,23 +736,25 @@ const unsigned char g_main[] = { 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x52, 0x54, 0x53, - 0x30, 0xe0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, - 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, + 0x30, 0xf8, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, - 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, - 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, - 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, - 0x00, 0xc0, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, + 0x00, 0x60, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, - 0xff, 0x01, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x01, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, - 0x00, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, + 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x53, 0x54, 0x41, 0x54, 0x70, 0x09, 0x00, 0x00, 0x60, 0x00, 0x00, diff --git a/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Colors.h b/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Colors.h index 88859b9..7b1a917 100644 --- a/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Colors.h +++ b/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Colors.h @@ -152,12 +152,12 @@ attributes #2 = { nounwind readonly } #endif const unsigned char g_main[] = { - 0x44, 0x58, 0x42, 0x43, 0x02, 0x08, 0x1c, 0x9e, 0xe7, 0x83, 0x00, 0x57, - 0x68, 0x79, 0x21, 0xa9, 0x0a, 0xf5, 0x0a, 0xa7, 0x01, 0x00, 0x00, 0x00, - 0x99, 0x0f, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x44, 0x58, 0x42, 0x43, 0xe0, 0x6a, 0x51, 0x4c, 0x14, 0x1a, 0x6a, 0x67, + 0x04, 0x23, 0x10, 0x54, 0xf8, 0xe3, 0xa6, 0x1a, 0x01, 0x00, 0x00, 0x00, + 0xb1, 0x0f, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0xdb, 0x00, 0x00, 0x00, 0x15, 0x01, 0x00, 0x00, - 0x01, 0x02, 0x00, 0x00, 0x39, 0x02, 0x00, 0x00, 0x6d, 0x09, 0x00, 0x00, - 0x89, 0x09, 0x00, 0x00, 0x53, 0x46, 0x49, 0x30, 0x08, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x00, 0x00, 0x51, 0x02, 0x00, 0x00, 0x85, 0x09, 0x00, 0x00, + 0xa1, 0x09, 0x00, 0x00, 0x53, 0x46, 0x49, 0x30, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, 0x31, 0x83, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -195,10 +195,12 @@ const unsigned char g_main[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x52, 0x54, 0x53, - 0x30, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, + 0x30, 0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x53, 0x54, 0x41, 0x54, 0x2c, 0x07, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x00, 0x00, 0x44, 0x58, 0x49, 0x4c, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x14, 0x07, 0x00, diff --git a/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Textures.h b/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Textures.h index 829f5d3..4aa8ef0 100644 --- a/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Textures.h +++ b/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Textures.h @@ -180,12 +180,12 @@ attributes #2 = { nounwind readonly } #endif const unsigned char g_main[] = { - 0x44, 0x58, 0x42, 0x43, 0x7e, 0x01, 0x52, 0x7e, 0xc9, 0xe3, 0xe4, 0xf6, - 0x3d, 0xab, 0x9a, 0xb9, 0xeb, 0xf9, 0xcb, 0xde, 0x01, 0x00, 0x00, 0x00, - 0xc9, 0x12, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x44, 0x58, 0x42, 0x43, 0xb2, 0xdd, 0x4e, 0x8d, 0x7f, 0x3c, 0x1f, 0x5e, + 0x6f, 0x0e, 0x03, 0xe7, 0x8c, 0xce, 0x62, 0x95, 0x01, 0x00, 0x00, 0x00, + 0xe1, 0x12, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0xdb, 0x00, 0x00, 0x00, 0x15, 0x01, 0x00, 0x00, - 0x31, 0x02, 0x00, 0x00, 0xc1, 0x02, 0x00, 0x00, 0x19, 0x0b, 0x00, 0x00, - 0x35, 0x0b, 0x00, 0x00, 0x53, 0x46, 0x49, 0x30, 0x08, 0x00, 0x00, 0x00, + 0x31, 0x02, 0x00, 0x00, 0xd9, 0x02, 0x00, 0x00, 0x31, 0x0b, 0x00, 0x00, + 0x4d, 0x0b, 0x00, 0x00, 0x53, 0x46, 0x49, 0x30, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, 0x31, 0x83, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -227,16 +227,18 @@ const unsigned char g_main[] = { 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x52, 0x54, 0x53, - 0x30, 0x88, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, + 0x30, 0xa0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, - 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, - 0x00, 0x68, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, + 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, - 0xff, 0x01, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0xff, 0x01, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x53, 0x54, 0x41, 0x54, 0x50, 0x08, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x14, 0x02, 0x00, diff --git a/libs/SDL3/src/render/direct3d12/D3D12_RootSig_Advanced.h b/libs/SDL3/src/render/direct3d12/D3D12_RootSig_Advanced.h index b050059..ac88cbb 100644 --- a/libs/SDL3/src/render/direct3d12/D3D12_RootSig_Advanced.h +++ b/libs/SDL3/src/render/direct3d12/D3D12_RootSig_Advanced.h @@ -3,8 +3,8 @@ Disassembly failed #endif const unsigned char g_AdvancedRS[] = { - 0x44, 0x58, 0x42, 0x43, 0xc2, 0xcd, 0x2f, 0xaf, 0x3b, 0x72, 0x07, 0x2a, - 0xa9, 0x73, 0x1b, 0xab, 0x8e, 0x46, 0xf7, 0x46, 0x01, 0x00, 0x00, 0x00, + 0x44, 0x58, 0x42, 0x43, 0x61, 0xd8, 0x65, 0x6e, 0x1d, 0x30, 0x64, 0x8a, + 0x54, 0x75, 0xb7, 0x59, 0xea, 0x11, 0x73, 0xec, 0x01, 0x00, 0x00, 0x00, 0x24, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x52, 0x54, 0x53, 0x30, 0xf8, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -16,7 +16,7 @@ const unsigned char g_AdvancedRS[] = { 0x05, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, diff --git a/libs/SDL3/src/render/direct3d12/D3D12_RootSig_Color.h b/libs/SDL3/src/render/direct3d12/D3D12_RootSig_Color.h index 4d5306b..e47eb5c 100644 --- a/libs/SDL3/src/render/direct3d12/D3D12_RootSig_Color.h +++ b/libs/SDL3/src/render/direct3d12/D3D12_RootSig_Color.h @@ -3,8 +3,8 @@ Disassembly failed #endif const unsigned char g_ColorRS[] = { - 0x44, 0x58, 0x42, 0x43, 0x24, 0x3f, 0x6b, 0x5a, 0xb1, 0xd3, 0x78, 0x2f, - 0x7f, 0xd4, 0x83, 0xd9, 0x7d, 0x6b, 0xc4, 0x31, 0x01, 0x00, 0x00, 0x00, + 0x44, 0x58, 0x42, 0x43, 0x1a, 0x67, 0x3b, 0x20, 0xac, 0xdc, 0xbb, 0xa0, + 0x3c, 0x72, 0xa7, 0xdf, 0x14, 0xa5, 0x3a, 0x2e, 0x01, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x52, 0x54, 0x53, 0x30, 0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -12,5 +12,5 @@ const unsigned char g_ColorRS[] = { 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00 + 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00 }; diff --git a/libs/SDL3/src/render/direct3d12/D3D12_RootSig_Texture.h b/libs/SDL3/src/render/direct3d12/D3D12_RootSig_Texture.h index 00e3c0a..2675b98 100644 --- a/libs/SDL3/src/render/direct3d12/D3D12_RootSig_Texture.h +++ b/libs/SDL3/src/render/direct3d12/D3D12_RootSig_Texture.h @@ -3,8 +3,8 @@ Disassembly failed #endif const unsigned char g_TextureRS[] = { - 0x44, 0x58, 0x42, 0x43, 0x25, 0x9c, 0x4f, 0xa4, 0x10, 0x16, 0x82, 0x9d, - 0x3d, 0x46, 0xb7, 0x5d, 0xf0, 0xc2, 0x90, 0xa7, 0x01, 0x00, 0x00, 0x00, + 0x44, 0x58, 0x42, 0x43, 0xbc, 0x46, 0x63, 0x56, 0xe3, 0xea, 0x41, 0x4c, + 0xf7, 0x90, 0x24, 0xc2, 0x14, 0x4d, 0x79, 0xdd, 0x01, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x52, 0x54, 0x53, 0x30, 0xa0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -14,7 +14,7 @@ const unsigned char g_TextureRS[] = { 0x05, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, diff --git a/libs/SDL3/src/render/direct3d12/D3D12_VertexShader_Advanced.h b/libs/SDL3/src/render/direct3d12/D3D12_VertexShader_Advanced.h index 3f2896a..5c358d5 100644 --- a/libs/SDL3/src/render/direct3d12/D3D12_VertexShader_Advanced.h +++ b/libs/SDL3/src/render/direct3d12/D3D12_VertexShader_Advanced.h @@ -236,8 +236,8 @@ attributes #2 = { nounwind readonly } #endif const unsigned char g_mainAdvanced[] = { - 0x44, 0x58, 0x42, 0x43, 0x2a, 0x90, 0x76, 0x57, 0x08, 0xab, 0xff, 0xa1, - 0x21, 0xd0, 0xb4, 0x0c, 0x31, 0xf8, 0x05, 0x5d, 0x01, 0x00, 0x00, 0x00, + 0x44, 0x58, 0x42, 0x43, 0xff, 0x5d, 0x02, 0x19, 0xed, 0x89, 0xe9, 0x33, + 0xf6, 0x5b, 0x76, 0x8c, 0x9f, 0x8b, 0x4d, 0xf1, 0x01, 0x00, 0x00, 0x00, 0x63, 0x13, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x63, 0x01, 0x00, 0x00, 0x87, 0x02, 0x00, 0x00, 0x87, 0x03, 0x00, 0x00, 0x3f, 0x0a, 0x00, 0x00, @@ -300,7 +300,7 @@ const unsigned char g_mainAdvanced[] = { 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, diff --git a/libs/SDL3/src/render/direct3d12/D3D12_VertexShader_Color.h b/libs/SDL3/src/render/direct3d12/D3D12_VertexShader_Color.h index a7eff5b..36909c1 100644 --- a/libs/SDL3/src/render/direct3d12/D3D12_VertexShader_Color.h +++ b/libs/SDL3/src/render/direct3d12/D3D12_VertexShader_Color.h @@ -236,8 +236,8 @@ attributes #2 = { nounwind readonly } #endif const unsigned char g_mainColor[] = { - 0x44, 0x58, 0x42, 0x43, 0xd2, 0x9c, 0xbb, 0x08, 0x88, 0xc9, 0x51, 0x6d, - 0x10, 0xea, 0x39, 0xeb, 0x7b, 0xab, 0xdf, 0x50, 0x01, 0x00, 0x00, 0x00, + 0x44, 0x58, 0x42, 0x43, 0x28, 0x20, 0xca, 0xe8, 0xab, 0xe5, 0x24, 0x7a, + 0x5d, 0x2b, 0x6f, 0xa4, 0x2c, 0x10, 0xa8, 0xb6, 0x01, 0x00, 0x00, 0x00, 0xa3, 0x12, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x63, 0x01, 0x00, 0x00, 0x87, 0x02, 0x00, 0x00, 0xd7, 0x02, 0x00, 0x00, 0x87, 0x09, 0x00, 0x00, @@ -296,7 +296,7 @@ const unsigned char g_mainColor[] = { 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x53, 0x54, 0x41, 0x54, 0xa8, + 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x53, 0x54, 0x41, 0x54, 0xa8, 0x06, 0x00, 0x00, 0x60, 0x00, 0x01, 0x00, 0xaa, 0x01, 0x00, 0x00, 0x44, 0x58, 0x49, 0x4c, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x90, 0x06, 0x00, 0x00, 0x42, 0x43, 0xc0, 0xde, 0x21, 0x0c, 0x00, 0x00, 0xa1, diff --git a/libs/SDL3/src/render/direct3d12/D3D12_VertexShader_Texture.h b/libs/SDL3/src/render/direct3d12/D3D12_VertexShader_Texture.h index 0573c1e..ac68e32 100644 --- a/libs/SDL3/src/render/direct3d12/D3D12_VertexShader_Texture.h +++ b/libs/SDL3/src/render/direct3d12/D3D12_VertexShader_Texture.h @@ -236,8 +236,8 @@ attributes #2 = { nounwind readonly } #endif const unsigned char g_mainTexture[] = { - 0x44, 0x58, 0x42, 0x43, 0x74, 0x5e, 0x15, 0x62, 0x5c, 0xf4, 0x2a, 0x49, - 0x52, 0xac, 0x1f, 0x81, 0x9a, 0xff, 0xaa, 0xbf, 0x01, 0x00, 0x00, 0x00, + 0x44, 0x58, 0x42, 0x43, 0xda, 0xc9, 0x75, 0xea, 0x0d, 0x18, 0xc7, 0xf5, + 0xb4, 0x80, 0x10, 0xd9, 0xd7, 0x88, 0x5a, 0x23, 0x01, 0x00, 0x00, 0x00, 0xff, 0x12, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x63, 0x01, 0x00, 0x00, 0x87, 0x02, 0x00, 0x00, 0x2f, 0x03, 0x00, 0x00, 0xdf, 0x09, 0x00, 0x00, @@ -298,7 +298,7 @@ const unsigned char g_mainTexture[] = { 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x68, + 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x03, diff --git a/libs/SDL3/src/render/direct3d12/SDL_render_d3d12.c b/libs/SDL3/src/render/direct3d12/SDL_render_d3d12.c index 64f0474..632e0f6 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 @@ -298,11 +297,14 @@ static SDL_PixelFormat D3D12_DXGIFormatToSDLPixelFormat(DXGI_FORMAT dxgiFormat) case DXGI_FORMAT_B8G8R8A8_UNORM: case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: return SDL_PIXELFORMAT_ARGB8888; + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + return SDL_PIXELFORMAT_ABGR8888; case DXGI_FORMAT_B8G8R8X8_UNORM: case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: return SDL_PIXELFORMAT_XRGB8888; case DXGI_FORMAT_R10G10B10A2_UNORM: - return SDL_PIXELFORMAT_XBGR2101010; + return SDL_PIXELFORMAT_ABGR2101010; case DXGI_FORMAT_R16G16B16A16_FLOAT: return SDL_PIXELFORMAT_RGBA64_FLOAT; default: @@ -315,13 +317,18 @@ static DXGI_FORMAT SDLPixelFormatToDXGITextureFormat(Uint32 format, Uint32 outpu switch (format) { case SDL_PIXELFORMAT_RGBA64_FLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT; - case SDL_PIXELFORMAT_XBGR2101010: + case SDL_PIXELFORMAT_ABGR2101010: return DXGI_FORMAT_R10G10B10A2_UNORM; case SDL_PIXELFORMAT_ARGB8888: if (output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) { return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; } return DXGI_FORMAT_B8G8R8A8_UNORM; + case SDL_PIXELFORMAT_ABGR8888: + if (output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) { + return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + } + return DXGI_FORMAT_R8G8B8A8_UNORM; case SDL_PIXELFORMAT_XRGB8888: if (output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) { return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB; @@ -345,13 +352,18 @@ static DXGI_FORMAT SDLPixelFormatToDXGIMainResourceViewFormat(Uint32 format, Uin switch (format) { case SDL_PIXELFORMAT_RGBA64_FLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT; - case SDL_PIXELFORMAT_XBGR2101010: + case SDL_PIXELFORMAT_ABGR2101010: return DXGI_FORMAT_R10G10B10A2_UNORM; case SDL_PIXELFORMAT_ARGB8888: if (colorspace == SDL_COLORSPACE_SRGB_LINEAR) { return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; } return DXGI_FORMAT_B8G8R8A8_UNORM; + case SDL_PIXELFORMAT_ABGR8888: + if (colorspace == SDL_COLORSPACE_SRGB_LINEAR) { + return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + } + return DXGI_FORMAT_R8G8B8A8_UNORM; case SDL_PIXELFORMAT_XRGB8888: if (colorspace == SDL_COLORSPACE_SRGB_LINEAR) { return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB; @@ -1561,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; @@ -1979,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; } @@ -2005,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; } @@ -2023,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 @@ -2231,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; @@ -2732,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]; @@ -2745,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]; @@ -2758,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) { @@ -3235,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; @@ -3254,8 +3264,9 @@ bool D3D12_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_Proper renderer->name = D3D12_RenderDriver.name; SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB8888); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR8888); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XRGB8888); - SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XBGR2101010); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR2101010); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA64_FLOAT); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_YV12); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_IYUV); diff --git a/libs/SDL3/src/render/gpu/SDL_pipeline_gpu.c b/libs/SDL3/src/render/gpu/SDL_pipeline_gpu.c index 0f7fabf..0783374 100644 --- a/libs/SDL3/src/render/gpu/SDL_pipeline_gpu.c +++ b/libs/SDL3/src/render/gpu/SDL_pipeline_gpu.c @@ -36,58 +36,50 @@ struct GPU_PipelineCacheKeyStruct Uint64 primitive_type : 3; }; -typedef union GPU_PipelineCacheKey +typedef union GPU_PipelineCacheKeyConverter { struct GPU_PipelineCacheKeyStruct as_struct; Uint64 as_uint64; -} GPU_PipelineCacheKey; +} GPU_PipelineCacheKeyConverter; -SDL_COMPILE_TIME_ASSERT(GPU_PipelineCacheKey_Size, sizeof(GPU_PipelineCacheKey) <= sizeof(Uint64)); +SDL_COMPILE_TIME_ASSERT(GPU_PipelineCacheKeyConverter_Size, sizeof(GPU_PipelineCacheKeyConverter) <= sizeof(Uint64)); -typedef struct GPU_PipelineCacheEntry +static Uint32 SDLCALL HashPipelineCacheKey(void *userdata, const void *key) { - GPU_PipelineCacheKey key; - SDL_GPUGraphicsPipeline *pipeline; -} GPU_PipelineCacheEntry; + const GPU_PipelineParameters *params = (const GPU_PipelineParameters *) key; + GPU_PipelineCacheKeyConverter cvt; + cvt.as_uint64 = 0; + cvt.as_struct.blend_mode = params->blend_mode; + cvt.as_struct.frag_shader = params->frag_shader; + cvt.as_struct.vert_shader = params->vert_shader; + cvt.as_struct.attachment_format = params->attachment_format; + cvt.as_struct.primitive_type = params->primitive_type; -static Uint32 HashPipelineCacheKey(const GPU_PipelineCacheKey *key) -{ - Uint64 x = key->as_uint64; // 64-bit uint hash function stolen from taisei (which stole it from somewhere else) + Uint64 x = cvt.as_uint64; x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb); x = x ^ (x >> 31); return (Uint32)(x & 0xffffffff); } -static Uint32 HashPassthrough(const void *key, void *data) +static bool SDLCALL MatchPipelineCacheKey(void *userdata, const void *a, const void *b) { - // double-cast to silence a clang warning - return (Uint32)(uintptr_t)key; + return (SDL_memcmp(a, b, sizeof (GPU_PipelineParameters)) == 0); } -static bool MatchPipelineCacheKey(const void *a, const void *b, void *data) +static void SDLCALL DestroyPipelineCacheHashItem(void *userdata, const void *key, const void *value) { - return a == b; -} - -static void NukePipelineCacheEntry(const void *key, const void *value, void *data) -{ - GPU_PipelineCacheEntry *entry = (GPU_PipelineCacheEntry *)value; - SDL_GPUDevice *device = data; - - SDL_ReleaseGPUGraphicsPipeline(device, entry->pipeline); - SDL_free(entry); + SDL_GPUGraphicsPipeline *pipeline = (SDL_GPUGraphicsPipeline *) value; + SDL_GPUDevice *device = (SDL_GPUDevice *) userdata; + SDL_ReleaseGPUGraphicsPipeline(device, pipeline); + SDL_free((GPU_PipelineParameters *) key); } bool GPU_InitPipelineCache(GPU_PipelineCache *cache, SDL_GPUDevice *device) { - // FIXME how many buckets do we need? - cache->table = SDL_CreateHashTable(device, 32, HashPassthrough, MatchPipelineCacheKey, NukePipelineCacheEntry, false, true); - if (!cache->table) { - return false; - } - return true; + cache->table = SDL_CreateHashTable(0, false, HashPipelineCacheKey, MatchPipelineCacheKey, DestroyPipelineCacheHashItem, device); + return (cache->table != NULL); } void GPU_DestroyPipelineCache(GPU_PipelineCache *cache) @@ -180,45 +172,30 @@ static SDL_GPUGraphicsPipeline *MakePipeline(SDL_GPUDevice *device, GPU_Shaders return SDL_CreateGPUGraphicsPipeline(device, &pci); } -static GPU_PipelineCacheKey MakePipelineCacheKey(const GPU_PipelineParameters *params) -{ - GPU_PipelineCacheKey key; - SDL_zero(key); - key.as_struct.blend_mode = params->blend_mode; - key.as_struct.frag_shader = params->frag_shader; - key.as_struct.vert_shader = params->vert_shader; - key.as_struct.attachment_format = params->attachment_format; - key.as_struct.primitive_type = params->primitive_type; - return key; -} - SDL_GPUGraphicsPipeline *GPU_GetPipeline(GPU_PipelineCache *cache, GPU_Shaders *shaders, SDL_GPUDevice *device, const GPU_PipelineParameters *params) { - GPU_PipelineCacheKey key = MakePipelineCacheKey(params); - void *keyval = (void *)(uintptr_t)HashPipelineCacheKey(&key); SDL_GPUGraphicsPipeline *pipeline = NULL; + if (!SDL_FindInHashTable(cache->table, params, (const void **) &pipeline)) { + bool inserted = false; + // !!! FIXME: why don't we have an SDL_alloc_copy function/macro? + GPU_PipelineParameters *paramscpy = (GPU_PipelineParameters *) SDL_malloc(sizeof (*paramscpy)); + if (paramscpy) { + SDL_copyp(paramscpy, params); + pipeline = MakePipeline(device, shaders, params); + if (pipeline) { + inserted = SDL_InsertIntoHashTable(cache->table, paramscpy, pipeline, false); + } + } - void *iter = NULL; - GPU_PipelineCacheEntry *entry = NULL; - - while (SDL_IterateHashTableKey(cache->table, keyval, (const void **)&entry, &iter)) { - if (entry->key.as_uint64 == key.as_uint64) { - return entry->pipeline; + if (!inserted) { + SDL_free(paramscpy); + if (pipeline) { + SDL_ReleaseGPUGraphicsPipeline(device, pipeline); + pipeline = NULL; + } } } - pipeline = MakePipeline(device, shaders, params); - - if (pipeline == NULL) { - return NULL; - } - - entry = SDL_malloc(sizeof(*entry)); - entry->key = key; - entry->pipeline = pipeline; - - SDL_InsertIntoHashTable(cache->table, keyval, entry); - return pipeline; } diff --git a/libs/SDL3/src/render/gpu/SDL_render_gpu.c b/libs/SDL3/src/render/gpu/SDL_render_gpu.c index 57ec05e..d5f2775 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; @@ -450,7 +445,6 @@ static void GPU_InvalidateCachedState(SDL_Renderer *renderer) { GPU_RenderData *data = (GPU_RenderData *)renderer->internal; - data->state.render_target = NULL; data->state.scissor_enabled = false; } @@ -494,8 +488,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 +568,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 +778,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 +790,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 +1174,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; @@ -1242,10 +1239,10 @@ static bool GPU_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P SDL_SetGPUAllowedFramesInFlight(data->device, 1); - SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA32); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_BGRA32); - SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBX32); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA32); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_BGRX32); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBX32); SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 16384); diff --git a/libs/SDL3/src/render/metal/SDL_render_metal.m b/libs/SDL3/src/render/metal/SDL_render_metal.m index 63d9770..e11fe1c 100644 --- a/libs/SDL3/src/render/metal/SDL_render_metal.m +++ b/libs/SDL3/src/render/metal/SDL_render_metal.m @@ -79,15 +79,10 @@ static const size_t CONSTANTS_OFFSET_DECODE_BT2020_LIMITED = ALIGN_CONSTANTS(16, static const size_t CONSTANTS_OFFSET_DECODE_BT2020_FULL = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_DECODE_BT2020_LIMITED + sizeof(float) * 4 * 4); static const size_t CONSTANTS_LENGTH = CONSTANTS_OFFSET_DECODE_BT2020_FULL + sizeof(float) * 4 * 4; -// Sampler types -typedef enum -{ - SDL_METAL_SAMPLER_NEAREST_CLAMP, - SDL_METAL_SAMPLER_NEAREST_WRAP, - SDL_METAL_SAMPLER_LINEAR_CLAMP, - SDL_METAL_SAMPLER_LINEAR_WRAP, - SDL_NUM_METAL_SAMPLERS -} SDL_METAL_sampler_type; +#define RENDER_SAMPLER_HASHKEY(scale_mode, address_u, address_v) \ + (((scale_mode == SDL_SCALEMODE_NEAREST) << 0) | \ + ((address_u == SDL_TEXTURE_ADDRESS_WRAP) << 1) | \ + ((address_v == SDL_TEXTURE_ADDRESS_WRAP) << 2)) typedef enum SDL_MetalVertexFunction { @@ -139,7 +134,7 @@ typedef struct METAL_ShaderPipelines @property(nonatomic, retain) id mtlcmdencoder; @property(nonatomic, retain) id mtllibrary; @property(nonatomic, retain) id mtlbackbuffer; -@property(nonatomic, retain) NSMutableArray> *mtlsamplers; +@property(nonatomic, retain) NSMutableDictionary> *mtlsamplers; @property(nonatomic, retain) id mtlbufconstants; @property(nonatomic, retain) id mtlbufquadindices; @property(nonatomic, assign) SDL_MetalView mtlview; @@ -1070,10 +1065,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 { @@ -1299,6 +1290,9 @@ typedef struct __unsafe_unretained id vertex_buffer; size_t constants_offset; SDL_Texture *texture; + SDL_ScaleMode texture_scale_mode; + SDL_TextureAddressMode texture_address_mode_u; + SDL_TextureAddressMode texture_address_mode_v; bool cliprect_dirty; bool cliprect_enabled; SDL_Rect cliprect; @@ -1456,6 +1450,58 @@ static bool SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, c return true; } +static id GetSampler(SDL3METAL_RenderData *data, SDL_ScaleMode scale_mode, SDL_TextureAddressMode address_u, SDL_TextureAddressMode address_v) +{ + NSNumber *key = [NSNumber numberWithInteger:RENDER_SAMPLER_HASHKEY(scale_mode, address_u, address_v)]; + id mtlsampler = data.mtlsamplers[key]; + if (mtlsampler == nil) { + MTLSamplerDescriptor *samplerdesc; + samplerdesc = [[MTLSamplerDescriptor alloc] init]; + switch (scale_mode) { + case SDL_SCALEMODE_NEAREST: + samplerdesc.minFilter = MTLSamplerMinMagFilterNearest; + samplerdesc.magFilter = MTLSamplerMinMagFilterNearest; + break; + case SDL_SCALEMODE_LINEAR: + samplerdesc.minFilter = MTLSamplerMinMagFilterLinear; + samplerdesc.magFilter = MTLSamplerMinMagFilterLinear; + break; + default: + SDL_SetError("Unknown scale mode: %d", scale_mode); + return nil; + } + switch (address_u) { + case SDL_TEXTURE_ADDRESS_CLAMP: + samplerdesc.sAddressMode = MTLSamplerAddressModeClampToEdge; + break; + case SDL_TEXTURE_ADDRESS_WRAP: + samplerdesc.sAddressMode = MTLSamplerAddressModeRepeat; + break; + default: + SDL_SetError("Unknown texture address mode: %d", address_u); + return nil; + } + switch (address_v) { + case SDL_TEXTURE_ADDRESS_CLAMP: + samplerdesc.tAddressMode = MTLSamplerAddressModeClampToEdge; + break; + case SDL_TEXTURE_ADDRESS_WRAP: + samplerdesc.tAddressMode = MTLSamplerAddressModeRepeat; + break; + default: + SDL_SetError("Unknown texture address mode: %d", address_v); + return nil; + } + mtlsampler = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc]; + if (mtlsampler == nil) { + SDL_SetError("Couldn't create sampler"); + return nil; + } + data.mtlsamplers[key] = mtlsampler; + } + return mtlsampler; +} + static bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const size_t constants_offset, id mtlbufvertex, METAL_DrawStateCache *statecache) { @@ -1471,33 +1517,6 @@ static bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, c } if (texture != statecache->texture) { - id mtlsampler; - - if (texture->scaleMode == SDL_SCALEMODE_NEAREST) { - switch (cmd->data.draw.texture_address_mode) { - case SDL_TEXTURE_ADDRESS_CLAMP: - mtlsampler = data.mtlsamplers[SDL_METAL_SAMPLER_NEAREST_CLAMP]; - break; - case SDL_TEXTURE_ADDRESS_WRAP: - mtlsampler = data.mtlsamplers[SDL_METAL_SAMPLER_NEAREST_WRAP]; - break; - default: - return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode); - } - } else { - switch (cmd->data.draw.texture_address_mode) { - case SDL_TEXTURE_ADDRESS_CLAMP: - mtlsampler = data.mtlsamplers[SDL_METAL_SAMPLER_LINEAR_CLAMP]; - break; - case SDL_TEXTURE_ADDRESS_WRAP: - mtlsampler = data.mtlsamplers[SDL_METAL_SAMPLER_LINEAR_WRAP]; - break; - default: - return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode); - } - } - [data.mtlcmdencoder setFragmentSamplerState:mtlsampler atIndex:0]; - [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0]; #ifdef SDL_HAVE_YUV if (texturedata.yuv || texturedata.nv12) { @@ -1507,6 +1526,20 @@ static bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, c #endif statecache->texture = texture; } + + if (cmd->data.draw.texture_scale_mode != statecache->texture_scale_mode || + cmd->data.draw.texture_address_mode != statecache->texture_address_mode_u || + cmd->data.draw.texture_address_mode != statecache->texture_address_mode_v) { + id mtlsampler = GetSampler(data, cmd->data.draw.texture_scale_mode, cmd->data.draw.texture_address_mode, cmd->data.draw.texture_address_mode); + if (mtlsampler == nil) { + return false; + } + [data.mtlcmdencoder setFragmentSamplerState:mtlsampler atIndex:0]; + + statecache->texture_scale_mode = cmd->data.draw.texture_scale_mode; + statecache->texture_address_mode_u = cmd->data.draw.texture_address_mode; + statecache->texture_address_mode_v = cmd->data.draw.texture_address_mode; + } return true; } @@ -1527,6 +1560,9 @@ static bool METAL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd statecache.vertex_buffer = nil; statecache.constants_offset = CONSTANTS_OFFSET_INVALID; statecache.texture = NULL; + statecache.texture_scale_mode = SDL_SCALEMODE_INVALID; + statecache.texture_address_mode_u = SDL_TEXTURE_ADDRESS_INVALID; + statecache.texture_address_mode_v = SDL_TEXTURE_ADDRESS_INVALID; statecache.shader_constants_dirty = true; statecache.cliprect_dirty = true; statecache.viewport_dirty = true; @@ -1887,7 +1923,6 @@ static bool METAL_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL int maxtexsize, quadcount = UINT16_MAX / 4; UInt16 *indexdata; size_t indicessize = sizeof(UInt16) * quadcount * 6; - MTLSamplerDescriptor *samplerdesc; id mtlcmdqueue; id mtllibrary; id mtlbufconstantstaging, mtlbufquadindicesstaging, mtlbufconstants, mtlbufquadindices; @@ -2047,27 +2082,7 @@ static bool METAL_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL data.allpipelines = NULL; ChooseShaderPipelines(data, MTLPixelFormatBGRA8Unorm); - static struct - { - MTLSamplerMinMagFilter filter; - MTLSamplerAddressMode address; - } samplerParams[] = { - { MTLSamplerMinMagFilterNearest, MTLSamplerAddressModeClampToEdge }, - { MTLSamplerMinMagFilterNearest, MTLSamplerAddressModeRepeat }, - { MTLSamplerMinMagFilterLinear, MTLSamplerAddressModeClampToEdge }, - { MTLSamplerMinMagFilterLinear, MTLSamplerAddressModeRepeat }, - }; - SDL_COMPILE_TIME_ASSERT(samplerParams_SIZE, SDL_arraysize(samplerParams) == SDL_NUM_METAL_SAMPLERS); - - data.mtlsamplers = [[NSMutableArray> alloc] init]; - samplerdesc = [[MTLSamplerDescriptor alloc] init]; - for (int i = 0; i < SDL_arraysize(samplerParams); ++i) { - samplerdesc.minFilter = samplerParams[i].filter; - samplerdesc.magFilter = samplerParams[i].filter; - samplerdesc.sAddressMode = samplerParams[i].address; - samplerdesc.tAddressMode = samplerParams[i].address; - [data.mtlsamplers addObject:[data.mtldevice newSamplerStateWithDescriptor:samplerdesc]]; - } + data.mtlsamplers = [[NSMutableDictionary> alloc] init]; mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared]; @@ -2129,7 +2144,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 d534438..fecd365 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 4465629..410326b 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) { @@ -1578,13 +1651,13 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD } else { renderdata->glGenTextures(1, &data->texture_v); if (!GL_CheckError("glGenTexures()", renderer)) { + SDL_free(data->pixel_data); + SDL_free(data); return false; } } 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); @@ -1594,20 +1667,24 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD } else { renderdata->glGenTextures(1, &data->texture_u); if (!GL_CheckError("glGenTexures()", renderer)) { + SDL_free(data->pixel_data); + SDL_free(data); return false; } } 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)) { + SDL_free(data->pixel_data); + SDL_free(data); return false; } SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_U_NUMBER, data->texture_u); if (!SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8)) { + SDL_free(data->pixel_data); + SDL_free(data); return SDL_SetError("Unsupported YUV colorspace"); } } else if (data->nv12) { @@ -1617,20 +1694,24 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD } else { renderdata->glGenTextures(1, &data->texture_u); if (!GL_CheckError("glGenTexures()", renderer)) { + SDL_free(data->pixel_data); + SDL_free(data); return false; } } 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)) { + SDL_free(data->pixel_data); + SDL_free(data); return false; } SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_UV_NUMBER, data->texture_u); if (!SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8)) { + SDL_free(data->pixel_data); + SDL_free(data); return SDL_SetError("Unsupported YUV colorspace"); } } @@ -1642,14 +1723,14 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD } else { renderdata->glGenTextures(1, &data->texture); if (!GL_CheckError("glGenTexures()", renderer)) { + SDL_free(data->pixel_data); + SDL_free(data); return false; } } 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 +1981,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; @@ -2099,8 +2149,8 @@ static bool GLES2_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL renderer->window = window; renderer->name = GLES2_RenderDriver.name; - SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA32); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_BGRA32); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA32); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_BGRX32); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBX32); @@ -2151,7 +2201,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 +2221,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 ddcab9e..f414fbd 100644 --- a/libs/SDL3/src/render/ps2/SDL_render_ps2.c +++ b/libs/SDL3/src/render/ps2/SDL_render_ps2.c @@ -60,7 +60,7 @@ typedef struct static int vsync_sema_id = 0; // PRIVATE METHODS -static int vsync_handler(void) +static int vsync_handler(int reason) { iSignalSema(vsync_sema_id); @@ -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 3ce5036..14f43ba 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 2231724..37359be 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 acf31c3..c992135 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 9140a62..48af7b8 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 023f859..36c6f40 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 19962c7..e23137f 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 7398315..de248b0 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; @@ -398,6 +397,8 @@ static SDL_PixelFormat VULKAN_VkFormatToSDLPixelFormat(VkFormat vkFormat) switch (vkFormat) { case VK_FORMAT_B8G8R8A8_UNORM: return SDL_PIXELFORMAT_ARGB8888; + case VK_FORMAT_R8G8B8A8_UNORM: + return SDL_PIXELFORMAT_ABGR8888; case VK_FORMAT_A2R10G10B10_UNORM_PACK32: return SDL_PIXELFORMAT_ABGR2101010; case VK_FORMAT_R16G16B16A16_SFLOAT: @@ -452,6 +453,11 @@ static VkFormat SDLPixelFormatToVkTextureFormat(Uint32 format, Uint32 output_col return VK_FORMAT_B8G8R8A8_SRGB; } return VK_FORMAT_B8G8R8A8_UNORM; + case SDL_PIXELFORMAT_ABGR8888: + if (output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) { + return VK_FORMAT_R8G8B8A8_SRGB; + } + return VK_FORMAT_R8G8B8A8_UNORM; case SDL_PIXELFORMAT_YUY2: return VK_FORMAT_G8B8G8R8_422_UNORM; case SDL_PIXELFORMAT_UYVY: @@ -1228,7 +1234,7 @@ static VULKAN_PipelineState *VULKAN_CreatePipelineState(SDL_Renderer *renderer, // Input assembly inputAssemblyStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - inputAssemblyStateCreateInfo.topology = ( VkPrimitiveTopology ) topology; + inputAssemblyStateCreateInfo.topology = topology; inputAssemblyStateCreateInfo.primitiveRestartEnable = VK_FALSE; viewportStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; @@ -2593,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 @@ -3086,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; @@ -3253,7 +3247,7 @@ static bool VULKAN_UpdateVertexBuffer(SDL_Renderer *renderer, stateCache->vertexBuffer = vertexBuffer->buffer; - rendererData->currentVertexBuffer++; + rendererData->currentVertexBuffer = vbidx + 1; if (rendererData->currentVertexBuffer >= SDL_VULKAN_NUM_VERTEX_BUFFERS) { rendererData->currentVertexBuffer = 0; rendererData->issueBatch = true; @@ -3768,8 +3762,8 @@ static bool VULKAN_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand VULKAN_SetupShaderConstants(renderer, cmd, texture, &constants); - switch (textureData->scaleMode) { - case VK_FILTER_NEAREST: + 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[VULKAN_SAMPLER_NEAREST_CLAMP]; @@ -3781,7 +3775,7 @@ static bool VULKAN_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode); } break; - case VK_FILTER_LINEAR: + case SDL_SCALEMODE_LINEAR: switch (cmd->data.draw.texture_address_mode) { case SDL_TEXTURE_ADDRESS_CLAMP: textureSampler = rendererData->samplers[VULKAN_SAMPLER_LINEAR_CLAMP]; @@ -3794,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) { @@ -4283,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; @@ -4303,6 +4296,7 @@ static bool VULKAN_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SD renderer->name = VULKAN_RenderDriver.name; SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB8888); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR8888); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR2101010); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA64_FLOAT); SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 16384); diff --git a/libs/SDL3/src/stdlib/SDL_getenv.c b/libs/SDL3/src/stdlib/SDL_getenv.c index 2659bd4..54fb6a7 100644 --- a/libs/SDL3/src/stdlib/SDL_getenv.c +++ b/libs/SDL3/src/stdlib/SDL_getenv.c @@ -41,7 +41,12 @@ #define environ (*_NSGetEnviron()) #elif defined(SDL_PLATFORM_FREEBSD) #include -#define environ ((char **)dlsym(RTLD_DEFAULT, "environ")) +static char **get_environ_rtld(void) +{ + char ***environ_rtld = (char ***)dlsym(RTLD_DEFAULT, "environ"); + return environ_rtld ? *environ_rtld : NULL; +} +#define environ (get_environ_rtld()) #else extern char **environ; #endif @@ -53,7 +58,7 @@ static char **environ; struct SDL_Environment { - SDL_Mutex *lock; + SDL_Mutex *lock; // !!! FIXME: reuse SDL_HashTable's lock. SDL_HashTable *strings; }; static SDL_Environment *SDL_environment; @@ -88,13 +93,13 @@ SDL_Environment *SDL_CreateEnvironment(bool populated) return NULL; } - env->strings = SDL_CreateHashTable(NULL, 16, SDL_HashString, SDL_KeyMatchString, SDL_NukeFreeKey, false, false); + env->strings = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_DestroyHashKey, NULL); if (!env->strings) { SDL_free(env); return NULL; } - // Don't fail if we can't create a mutex (e.g. on a single-thread environment) + // Don't fail if we can't create a mutex (e.g. on a single-thread environment) // !!! FIXME: single-threaded environments should still return a non-NULL, do-nothing object here. Check for failure! env->lock = SDL_CreateMutex(); if (populated) { @@ -114,7 +119,7 @@ SDL_Environment *SDL_CreateEnvironment(bool populated) } *value++ = '\0'; - SDL_InsertIntoHashTable(env->strings, variable, value); + SDL_InsertIntoHashTable(env->strings, variable, value, true); } FreeEnvironmentStringsW(strings); } @@ -138,7 +143,7 @@ SDL_Environment *SDL_CreateEnvironment(bool populated) } *value++ = '\0'; - SDL_InsertIntoHashTable(env->strings, variable, value); + SDL_InsertIntoHashTable(env->strings, variable, value, true); } } #endif // SDL_PLATFORM_WINDOWS @@ -170,6 +175,49 @@ const char *SDL_GetEnvironmentVariable(SDL_Environment *env, const char *name) return result; } +typedef struct CountEnvStringsData +{ + size_t count; + size_t length; +} CountEnvStringsData; + +static bool SDLCALL CountEnvStrings(void *userdata, const SDL_HashTable *table, const void *key, const void *value) +{ + CountEnvStringsData *data = (CountEnvStringsData *) userdata; + data->length += SDL_strlen((const char *) key) + 1 + SDL_strlen((const char *) value) + 1; + data->count++; + return true; // keep iterating. +} + +typedef struct CopyEnvStringsData +{ + char **result; + char *string; + size_t count; +} CopyEnvStringsData; + +static bool SDLCALL CopyEnvStrings(void *userdata, const SDL_HashTable *table, const void *vkey, const void *vvalue) +{ + CopyEnvStringsData *data = (CopyEnvStringsData *) userdata; + const char *key = (const char *) vkey; + const char *value = (const char *) vvalue; + size_t len; + + len = SDL_strlen(key); + data->result[data->count] = data->string; + SDL_memcpy(data->string, key, len); + data->string += len; + *(data->string++) = '='; + + len = SDL_strlen(value); + SDL_memcpy(data->string, value, len); + data->string += len; + *(data->string++) = '\0'; + data->count++; + + return true; // keep iterating. +} + char **SDL_GetEnvironmentVariables(SDL_Environment *env) { char **result = NULL; @@ -181,40 +229,20 @@ char **SDL_GetEnvironmentVariables(SDL_Environment *env) SDL_LockMutex(env->lock); { - size_t count, length = 0; - void *iter; - const char *key, *value; - // First pass, get the size we need for all the strings - count = 0; - iter = NULL; - while (SDL_IterateHashTable(env->strings, (const void **)&key, (const void **)&value, &iter)) { - length += SDL_strlen(key) + 1 + SDL_strlen(value) + 1; - ++count; - } + CountEnvStringsData countdata = { 0, 0 }; + SDL_IterateHashTable(env->strings, CountEnvStrings, &countdata); // Allocate memory for the strings - result = (char **)SDL_malloc((count + 1) * sizeof(*result) + length); - char *string = (char *)(result + count + 1); - - // Second pass, copy the strings - count = 0; - iter = NULL; - while (SDL_IterateHashTable(env->strings, (const void **)&key, (const void **)&value, &iter)) { - size_t len; - - result[count] = string; - len = SDL_strlen(key); - SDL_memcpy(string, key, len); - string += len; - *string++ = '='; - len = SDL_strlen(value); - SDL_memcpy(string, value, len); - string += len; - *string++ = '\0'; - ++count; + result = (char **)SDL_malloc((countdata.count + 1) * sizeof(*result) + countdata.length); + if (result) { + // Second pass, copy the strings + char *string = (char *)(result + countdata.count + 1); + CopyEnvStringsData cpydata = { result, string, 0 }; + SDL_IterateHashTable(env->strings, CopyEnvStrings, &cpydata); + SDL_assert(countdata.count == cpydata.count); + result[cpydata.count] = NULL; } - result[count] = NULL; } SDL_UnlockMutex(env->lock); @@ -235,26 +263,23 @@ bool SDL_SetEnvironmentVariable(SDL_Environment *env, const char *name, const ch SDL_LockMutex(env->lock); { - const void *existing_value; - bool insert = true; - - if (SDL_FindInHashTable(env->strings, name, &existing_value)) { - if (!overwrite) { - result = true; - insert = false; - } else { - SDL_RemoveFromHashTable(env->strings, name); - } - } - - if (insert) { - char *string = NULL; - if (SDL_asprintf(&string, "%s=%s", name, value) > 0) { - size_t len = SDL_strlen(name); - string[len] = '\0'; - name = string; - value = string + len + 1; - result = SDL_InsertIntoHashTable(env->strings, name, value); + char *string = NULL; + if (SDL_asprintf(&string, "%s=%s", name, value) > 0) { + const size_t len = SDL_strlen(name); + string[len] = '\0'; + const char *origname = name; + name = string; + value = string + len + 1; + result = SDL_InsertIntoHashTable(env->strings, name, value, overwrite); + if (!result) { + SDL_free(string); + if (!overwrite) { + const void *existing_value = NULL; + // !!! FIXME: InsertIntoHashTable does this lookup too, maybe we should have a means to report that, to avoid duplicate work? + if (SDL_FindInHashTable(env->strings, origname, &existing_value)) { + result = true; // it already existed, and we refused to overwrite it. Call it success. + } + } } } } diff --git a/libs/SDL3/src/stdlib/SDL_malloc.c b/libs/SDL3/src/stdlib/SDL_malloc.c index 008675f..89c2830 100644 --- a/libs/SDL3/src/stdlib/SDL_malloc.c +++ b/libs/SDL3/src/stdlib/SDL_malloc.c @@ -1478,6 +1478,13 @@ DLMALLOC_EXPORT int mspace_mallopt(int, int); #endif /* NO_MALLOC_STATS */ #ifndef LACKS_ERRNO_H #include /* for MALLOC_FAILURE_ACTION */ +#else /* LACKS_ERRNO_H */ +#ifndef EINVAL +#define EINVAL 22 +#endif +#ifndef ENOMEM +#define ENOMEM 12 +#endif #endif /* LACKS_ERRNO_H */ #ifdef DEBUG #if ABORT_ON_ASSERT_FAILURE diff --git a/libs/SDL3/src/stdlib/SDL_stdlib.c b/libs/SDL3/src/stdlib/SDL_stdlib.c index 98faab9..093b2be 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 39c49e4..79679a1 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; } @@ -1448,6 +1444,7 @@ static bool CharacterMatchesSet(char c, const char *set, size_t set_len) // NOLINTNEXTLINE(readability-non-const-parameter) int SDL_vsscanf(const char *text, SDL_SCANF_FORMAT_STRING const char *fmt, va_list ap) { + const char *start = text; int result = 0; if (!text || !*text) { @@ -1718,6 +1715,36 @@ int SDL_vsscanf(const char *text, SDL_SCANF_FORMAT_STRING const char *fmt, va_li } done = true; break; + case 'n': + switch (inttype) { + case DO_SHORT: + { + short *valuep = va_arg(ap, short *); + *valuep = (short)(text - start); + } break; + case DO_INT: + { + int *valuep = va_arg(ap, int *); + *valuep = (int)(text - start); + } break; + case DO_LONG: + { + long *valuep = va_arg(ap, long *); + *valuep = (long)(text - start); + } break; + case DO_LONGLONG: + { + long long *valuep = va_arg(ap, long long *); + *valuep = (long long)(text - start); + } break; + case DO_SIZE_T: + { + size_t *valuep = va_arg(ap, size_t *); + *valuep = (size_t)(text - start); + } break; + } + done = true; + break; case '[': { const char *set = fmt + 1; diff --git a/libs/SDL3/src/storage/SDL_storage.c b/libs/SDL3/src/storage/SDL_storage.c index 75952ff..7c395b3 100644 --- a/libs/SDL3/src/storage/SDL_storage.c +++ b/libs/SDL3/src/storage/SDL_storage.c @@ -34,6 +34,9 @@ static TitleStorageBootStrap *titlebootstrap[] = { static UserStorageBootStrap *userbootstrap[] = { #ifdef SDL_STORAGE_STEAM &STEAM_userbootstrap, +#endif +#ifdef SDL_STORAGE_PRIVATE + &PRIVATE_userbootstrap, #endif &GENERIC_userbootstrap, NULL diff --git a/libs/SDL3/src/storage/SDL_sysstorage.h b/libs/SDL3/src/storage/SDL_sysstorage.h index 57d60d6..f047e55 100644 --- a/libs/SDL3/src/storage/SDL_sysstorage.h +++ b/libs/SDL3/src/storage/SDL_sysstorage.h @@ -44,6 +44,7 @@ extern TitleStorageBootStrap GENERIC_titlebootstrap; // Steam does not have title storage APIs extern UserStorageBootStrap GENERIC_userbootstrap; +extern UserStorageBootStrap PRIVATE_userbootstrap; extern UserStorageBootStrap STEAM_userbootstrap; extern SDL_Storage *GENERIC_OpenFileStorage(const char *path); diff --git a/libs/SDL3/src/test/SDL_test_common.c b/libs/SDL3/src/test/SDL_test_common.c index 3203dde..315fec0 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_fuzzer.c b/libs/SDL3/src/test/SDL_test_fuzzer.c index caa330c..67638b5 100644 --- a/libs/SDL3/src/test/SDL_test_fuzzer.c +++ b/libs/SDL3/src/test/SDL_test_fuzzer.c @@ -144,7 +144,7 @@ Sint32 SDLTest_RandomIntegerInRange(Sint32 min, Sint32 max) Uint64 range = (Sint64)max - (Sint64)min; if (range < SDL_MAX_SINT32) { - return min + (Sint32) SDL_rand_r(&rndContext, (Sint32) range + 1); + return min + SDL_rand_r(&rndContext, (Sint32) range + 1); } else { Uint64 add = SDL_rand_bits_r(&rndContext) | ((Uint64) SDL_rand_bits_r(&rndContext) << 32); return (Sint32) (min + (Sint64) (add % (range + 1))); diff --git a/libs/SDL3/src/test/SDL_test_harness.c b/libs/SDL3/src/test/SDL_test_harness.c index 7156b11..d6a3b8d 100644 --- a/libs/SDL3/src/test/SDL_test_harness.c +++ b/libs/SDL3/src/test/SDL_test_harness.c @@ -515,6 +515,7 @@ int SDLTest_ExecuteTestSuiteRunner(SDLTest_TestSuiteRunner *runner) arraySuites = SDL_malloc(nbSuites * sizeof(int)); if (!arraySuites) { + SDL_free((void *)failedTests); return SDL_OutOfMemory(); } for (i = 0; i < nbSuites; i++) { @@ -586,6 +587,8 @@ int SDLTest_ExecuteTestSuiteRunner(SDLTest_TestSuiteRunner *runner) arrayTestCases = SDL_malloc(nbTestCases * sizeof(int)); if (!arrayTestCases) { + SDL_free(arraySuites); + SDL_free((void *)failedTests); return SDL_OutOfMemory(); } for (j = 0; j < nbTestCases; j++) { diff --git a/libs/SDL3/src/test/SDL_test_memory.c b/libs/SDL3/src/test/SDL_test_memory.c index b90c4a5..e1bec17 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/SDL_thread.c b/libs/SDL3/src/thread/SDL_thread.c index ee9f9bb..99cb9dd 100644 --- a/libs/SDL3/src/thread/SDL_thread.c +++ b/libs/SDL3/src/thread/SDL_thread.c @@ -334,7 +334,6 @@ void SDL_RunThread(SDL_Thread *thread) if (!SDL_CompareAndSwapAtomicInt(&thread->state, SDL_THREAD_ALIVE, SDL_THREAD_COMPLETE)) { // Clean up if something already detached us. if (SDL_GetThreadState(thread) == SDL_THREAD_DETACHED) { - SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, false); SDL_free(thread->name); // Can't free later, we've already cleaned up TLS SDL_free(thread); } @@ -457,7 +456,7 @@ bool SDL_SetCurrentThreadPriority(SDL_ThreadPriority priority) void SDL_WaitThread(SDL_Thread *thread, int *status) { - if (!ThreadValid(thread) || SDL_GetThreadState(thread) == SDL_THREAD_DETACHED) { + if (!ThreadValid(thread)) { if (status) { *status = -1; } @@ -488,6 +487,9 @@ void SDL_DetachThread(SDL_Thread *thread) return; } + // The thread may vanish at any time, it's no longer valid + SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, false); + // Grab dibs if the state is alive+joinable. if (SDL_CompareAndSwapAtomicInt(&thread->state, SDL_THREAD_ALIVE, SDL_THREAD_DETACHED)) { SDL_SYS_DetachThread(thread); diff --git a/libs/SDL3/src/thread/n3ds/SDL_syscond.c b/libs/SDL3/src/thread/n3ds/SDL_syscond.c deleted file mode 100644 index d5c4067..0000000 --- a/libs/SDL3/src/thread/n3ds/SDL_syscond.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. -*/ -#include "SDL_internal.h" - -#ifdef SDL_THREAD_N3DS - -// An implementation of condition variables using libctru's CondVar - -#include "SDL_sysmutex_c.h" - -struct SDL_Condition -{ - CondVar cond_variable; -}; - -// Create a condition variable -SDL_Condition *SDL_CreateCondition(void) -{ - SDL_Condition *cond = (SDL_Condition *)SDL_malloc(sizeof(SDL_Condition)); - if (cond) { - CondVar_Init(&cond->cond_variable); - } - return cond; -} - -// Destroy a condition variable -void SDL_DestroyCondition(SDL_Condition *cond) -{ - if (cond) { - SDL_free(cond); - } -} - -// Restart one of the threads that are waiting on the condition variable -void SDL_SignalCondition(SDL_Condition *cond) -{ - if (!cond) { - return; - } - - CondVar_Signal(&cond->cond_variable); -} - -// Restart all threads that are waiting on the condition variable -void SDL_BroadcastCondition(SDL_Condition *cond) -{ - if (!cond) { - return; - } - - CondVar_Broadcast(&cond->cond_variable); -} - -/* Wait on the condition variable for at most 'timeoutNS' nanoseconds. - The mutex must be locked before entering this function! - The mutex is unlocked during the wait, and locked again after the wait. - -Typical use: - -Thread A: - SDL_LockMutex(lock); - while ( ! condition ) { - SDL_WaitCondition(cond, lock); - } - SDL_UnlockMutex(lock); - -Thread B: - SDL_LockMutex(lock); - ... - condition = true; - ... - SDL_SignalCondition(cond); - SDL_UnlockMutex(lock); - */ -bool SDL_WaitConditionTimeoutNS(SDL_Condition *cond, SDL_Mutex *mutex, Sint64 timeoutNS) -{ - Result res; - - if (!cond || !mutex) { - return true; - } - - res = 0; - if (timeoutNS < 0) { - CondVar_Wait(&cond->cond_variable, &mutex->lock.lock); - } else { - res = CondVar_WaitTimeout(&cond->cond_variable, &mutex->lock.lock, timeoutNS); - } - - return R_SUCCEEDED(res); -} - -#endif // SDL_THREAD_N3DS diff --git a/libs/SDL3/src/thread/n3ds/SDL_syssem.c b/libs/SDL3/src/thread/n3ds/SDL_syssem.c index 3207a2d..c03030b 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/thread/psp/SDL_systhread.c b/libs/SDL3/src/thread/psp/SDL_systhread.c index 3d60718..2c500cb 100644 --- a/libs/SDL3/src/thread/psp/SDL_systhread.c +++ b/libs/SDL3/src/thread/psp/SDL_systhread.c @@ -32,6 +32,8 @@ #include #include +#define PSP_THREAD_NAME_MAX 32 + static int ThreadEntry(SceSize args, void *argp) { SDL_RunThread(*(SDL_Thread **)argp); @@ -44,6 +46,7 @@ bool SDL_SYS_CreateThread(SDL_Thread *thread, { SceKernelThreadInfo status; int priority = 32; + char thread_name[PSP_THREAD_NAME_MAX]; // Set priority of new thread to the same as the current thread status.size = sizeof(SceKernelThreadInfo); @@ -51,7 +54,12 @@ bool SDL_SYS_CreateThread(SDL_Thread *thread, priority = status.currentPriority; } - thread->handle = sceKernelCreateThread(thread->name, ThreadEntry, + SDL_strlcpy(thread_name, "SDL thread", PSP_THREAD_NAME_MAX); + if (thread->name) { + SDL_strlcpy(thread_name, thread->name, PSP_THREAD_NAME_MAX); + } + + thread->handle = sceKernelCreateThread(thread_name, ThreadEntry, priority, thread->stacksize ? ((int)thread->stacksize) : 0x8000, PSP_THREAD_ATTR_VFPU, NULL); if (thread->handle < 0) { diff --git a/libs/SDL3/src/thread/windows/SDL_systhread.c b/libs/SDL3/src/thread/windows/SDL_systhread.c index c30694a..c5bee81 100644 --- a/libs/SDL3/src/thread/windows/SDL_systhread.c +++ b/libs/SDL3/src/thread/windows/SDL_systhread.c @@ -153,7 +153,7 @@ void SDL_SYS_SetupThread(const char *name) inf.dwFlags = 0; // The debugger catches this, renames the thread, continues on. - RaiseException(SDL_DEBUGGER_NAME_EXCEPTION_CODE, 0, sizeof(inf) / sizeof(ULONG), (const ULONG_PTR *)&inf); + RaiseException(SDL_DEBUGGER_NAME_EXCEPTION_CODE, 0, sizeof(inf) / sizeof(ULONG_PTR), (const ULONG_PTR *)&inf); RemoveVectoredExceptionHandler(exceptionHandlerHandle); } } diff --git a/libs/SDL3/src/time/unix/SDL_systime.c b/libs/SDL3/src/time/unix/SDL_systime.c index 296175a..bb3aa4b 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/tray/unix/SDL_tray.c b/libs/SDL3/src/tray/unix/SDL_tray.c index f10f92a..e8a7b0d 100644 --- a/libs/SDL3/src/tray/unix/SDL_tray.c +++ b/libs/SDL3/src/tray/unix/SDL_tray.c @@ -58,6 +58,13 @@ typedef enum static gulong (*g_signal_connect_data)(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags); static void (*g_object_unref)(gpointer object); static gchar *(*g_mkdtemp)(gchar *template); +static gpointer (*g_object_ref_sink)(gpointer object); +static gpointer (*g_object_ref)(gpointer object); + +// glib_typeof requires compiler-specific code and includes that are too complex +// to be worth copy-pasting here +//#define g_object_ref(Obj) ((glib_typeof (Obj)) (g_object_ref) (Obj)) +//#define g_object_ref_sink(Obj) ((glib_typeof (Obj)) (g_object_ref_sink) (Obj)) #define g_signal_connect(instance, detailed_signal, c_handler, data) \ g_signal_connect_data ((instance), (detailed_signal), (c_handler), (data), NULL, (GConnectFlags) 0) @@ -248,6 +255,8 @@ static bool init_gtk(void) g_mkdtemp = dlsym(libgdk, "g_mkdtemp"); g_signal_connect_data = dlsym(libgdk, "g_signal_connect_data"); g_object_unref = dlsym(libgdk, "g_object_unref"); + g_object_ref_sink = dlsym(libgdk, "g_object_ref_sink"); + g_object_ref = dlsym(libgdk, "g_object_ref"); app_indicator_new = dlsym(libappindicator, "app_indicator_new"); app_indicator_set_status = dlsym(libappindicator, "app_indicator_set_status"); @@ -268,6 +277,8 @@ static bool init_gtk(void) !gtk_menu_shell_insert || !gtk_widget_destroy || !g_mkdtemp || + !g_object_ref_sink || + !g_object_ref || !g_signal_connect_data || !g_object_unref || !app_indicator_new || @@ -326,6 +337,8 @@ struct SDL_Tray { SDL_TrayMenu *menu; char icon_dir[sizeof(ICON_DIR_TEMPLATE)]; char icon_path[256]; + + GtkMenuShell *menu_cached; }; static void call_callback(GtkMenuItem *item, gpointer ptr) @@ -384,6 +397,11 @@ static void DestroySDLMenu(SDL_TrayMenu *menu) } SDL_free(menu->entries[i]); } + + if (menu->menu) { + g_object_unref(menu->menu); + } + SDL_free(menu->entries); SDL_free(menu); } @@ -421,18 +439,24 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) return NULL; } - if (!new_tmp_filename(tray)) { - SDL_free(tray); - return NULL; - } + if (icon) { + if (!new_tmp_filename(tray)) { + SDL_free(tray); + return NULL; + } - SDL_SaveBMP(icon, tray->icon_path); + SDL_SaveBMP(icon, tray->icon_path); + } tray->indicator = app_indicator_new(get_appindicator_id(), tray->icon_path, APP_INDICATOR_CATEGORY_APPLICATION_STATUS); app_indicator_set_status(tray->indicator, APP_INDICATOR_STATUS_ACTIVE); + // The tray icon isn't shown before a menu is created; create one early. + tray->menu_cached = (GtkMenuShell *) g_object_ref_sink(gtk_menu_new()); + app_indicator_set_menu(tray->indicator, GTK_MENU(tray->menu_cached)); + SDL_RegisterTray(tray); return tray; @@ -476,14 +500,12 @@ SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray) return NULL; } - tray->menu->menu = (GtkMenuShell *)gtk_menu_new(); + tray->menu->menu = g_object_ref(tray->menu_cached); tray->menu->parent_tray = tray; tray->menu->parent_entry = NULL; tray->menu->nEntries = 0; tray->menu->entries = NULL; - app_indicator_set_menu(tray->indicator, GTK_MENU(tray->menu->menu)); - return tray->menu; } @@ -519,7 +541,7 @@ SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry) return NULL; } - entry->submenu->menu = (GtkMenuShell *)gtk_menu_new(); + entry->submenu->menu = g_object_ref_sink(gtk_menu_new()); entry->submenu->parent_tray = NULL; entry->submenu->parent_entry = entry; entry->submenu->nEntries = 0; @@ -783,6 +805,10 @@ void SDL_DestroyTray(SDL_Tray *tray) SDL_RemovePath(tray->icon_dir); } + if (tray->menu_cached) { + g_object_unref(tray->menu_cached); + } + if (tray->indicator) { g_object_unref(tray->indicator); } diff --git a/libs/SDL3/src/tray/windows/SDL_tray.c b/libs/SDL3/src/tray/windows/SDL_tray.c index 18008ee..15021ac 100644 --- a/libs/SDL3/src/tray/windows/SDL_tray.c +++ b/libs/SDL3/src/tray/windows/SDL_tray.c @@ -544,7 +544,7 @@ void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label) mii.dwTypeData = label_w; mii.cch = (UINT) SDL_wcslen(label_w); - if (!SetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, TRUE, &mii)) { + if (!SetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, FALSE, &mii)) { SDL_SetError("Couldn't update tray entry label"); } diff --git a/libs/SDL3/src/video/SDL_blit_A.c b/libs/SDL3/src/video/SDL_blit_A.c index 5d6d92d..1cefd45 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 c710e46..dd15790 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_blit_auto.h b/libs/SDL3/src/video/SDL_blit_auto.h index cd3f0d8..329ffe5 100644 --- a/libs/SDL3/src/video/SDL_blit_auto.h +++ b/libs/SDL3/src/video/SDL_blit_auto.h @@ -20,12 +20,13 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "SDL_internal.h" -#include "SDL_blit.h" #ifdef SDL_HAVE_BLIT_AUTO /* *INDENT-OFF* */ // clang-format off +#include "SDL_blit.h" + extern SDL_BlitFuncEntry SDL_GeneratedBlitFuncTable[]; /* *INDENT-ON* */ // clang-format on diff --git a/libs/SDL3/src/video/SDL_clipboard.c b/libs/SDL3/src/video/SDL_clipboard.c index 105c288..9d8c27d 100644 --- a/libs/SDL3/src/video/SDL_clipboard.c +++ b/libs/SDL3/src/video/SDL_clipboard.c @@ -42,6 +42,10 @@ void SDL_CancelClipboardData(Uint32 sequence) { SDL_VideoDevice *_this = SDL_GetVideoDevice(); + if (!_this) { + return; + } + if (sequence && sequence != _this->clipboard_sequence) { // This clipboard data was already canceled return; @@ -62,6 +66,10 @@ bool SDL_SaveClipboardMimeTypes(const char **mime_types, size_t num_mime_types) { SDL_VideoDevice *_this = SDL_GetVideoDevice(); + if (!_this) { + return SDL_UninitializedVideo(); + } + SDL_FreeClipboardMimeTypes(_this); if (mime_types && num_mime_types > 0) { @@ -234,13 +242,11 @@ bool SDL_HasClipboardData(const char *mime_type) SDL_VideoDevice *_this = SDL_GetVideoDevice(); if (!_this) { - SDL_UninitializedVideo(); - return false; + return SDL_UninitializedVideo(); } if (!mime_type) { - SDL_InvalidParamError("mime_type"); - return false; + return SDL_InvalidParamError("mime_type"); } if (_this->HasClipboardData) { diff --git a/libs/SDL3/src/video/SDL_pixels.c b/libs/SDL3/src/video/SDL_pixels.c index 1a1b59c..5e341f4 100644 --- a/libs/SDL3/src/video/SDL_pixels.c +++ b/libs/SDL3/src/video/SDL_pixels.c @@ -155,6 +155,7 @@ const char *SDL_GetPixelFormatName(SDL_PixelFormat format) CASE(SDL_PIXELFORMAT_NV21) CASE(SDL_PIXELFORMAT_P010) CASE(SDL_PIXELFORMAT_EXTERNAL_OES) + CASE(SDL_PIXELFORMAT_MJPG) default: return "SDL_PIXELFORMAT_UNKNOWN"; @@ -647,7 +648,7 @@ const SDL_PixelFormatDetails *SDL_GetPixelFormatDetails(SDL_PixelFormat format) SDL_PixelFormatDetails *details; if (SDL_ShouldInit(&SDL_format_details_init)) { - SDL_format_details = SDL_CreateHashTable(NULL, 8, SDL_HashID, SDL_KeyMatchID, SDL_NukeFreeValue, true, false); + SDL_format_details = SDL_CreateHashTable(0, true, SDL_HashID, SDL_KeyMatchID, SDL_DestroyHashValue, NULL); if (!SDL_format_details) { SDL_SetInitialized(&SDL_format_details_init, false); return NULL; @@ -670,9 +671,13 @@ const SDL_PixelFormatDetails *SDL_GetPixelFormatDetails(SDL_PixelFormat format) return NULL; } - if (!SDL_InsertIntoHashTable(SDL_format_details, (const void *)(uintptr_t)format, (void *)details)) { + if (!SDL_InsertIntoHashTable(SDL_format_details, (const void *)(uintptr_t)format, (void *)details, false)) { SDL_free(details); - return NULL; + // uh...did another thread beat us to inserting this? + if (SDL_FindInHashTable(SDL_format_details, (const void *)(uintptr_t)format, (const void **)&details)) { + return details; + } + return NULL; // oh well. } return details; @@ -687,10 +692,40 @@ 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)) { - if (format == SDL_PIXELFORMAT_P010) { + if (format == SDL_PIXELFORMAT_MJPG) { + return SDL_COLORSPACE_SRGB; + } else if (format == SDL_PIXELFORMAT_P010) { return SDL_COLORSPACE_HDR10; } else { return SDL_COLORSPACE_YUV_DEFAULT; @@ -1130,7 +1165,7 @@ Uint8 SDL_LookupRGBAColor(SDL_HashTable *palette_map, Uint32 pixel, const SDL_Pa Uint8 b = (Uint8)((pixel >> 8) & 0xFF); Uint8 a = (Uint8)((pixel >> 0) & 0xFF); color_index = SDL_FindColor(pal, r, g, b, a); - SDL_InsertIntoHashTable(palette_map, (const void *)(uintptr_t)pixel, (const void *)(uintptr_t)color_index); + SDL_InsertIntoHashTable(palette_map, (const void *)(uintptr_t)pixel, (const void *)(uintptr_t)color_index, true); } return color_index; } @@ -1496,7 +1531,7 @@ bool SDL_MapSurface(SDL_Surface *src, SDL_Surface *dst) } else { if (SDL_ISPIXELFORMAT_INDEXED(dstfmt->format)) { // BitField --> Palette - map->info.palette_map = SDL_CreateHashTable(NULL, 32, SDL_HashID, SDL_KeyMatchID, NULL, false, false); + map->info.palette_map = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, NULL, NULL); } else { // BitField --> BitField if (srcfmt == dstfmt) { diff --git a/libs/SDL3/src/video/SDL_pixels_c.h b/libs/SDL3/src/video/SDL_pixels_c.h index 92c3401..11be7f3 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_stb.c b/libs/SDL3/src/video/SDL_stb.c new file mode 100644 index 0000000..934c1ad --- /dev/null +++ b/libs/SDL3/src/video/SDL_stb.c @@ -0,0 +1,121 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include "SDL_stb_c.h" + + +// We currently only support JPEG, but we could add other image formats if we wanted +#ifdef SDL_HAVE_STB +#define malloc SDL_malloc +#define realloc SDL_realloc +#define free SDL_free +#undef memcpy +#define memcpy SDL_memcpy +#undef memset +#define memset SDL_memset +#undef strcmp +#define strcmp SDL_strcmp +#undef strncmp +#define strncmp SDL_strncmp +#define strtol SDL_strtol + +#define abs SDL_abs +#define pow SDL_pow +#define ldexp SDL_scalbn + +#define STB_IMAGE_STATIC +#define STBI_NO_THREAD_LOCALS +#define STBI_FAILURE_USERMSG +#if defined(SDL_NEON_INTRINSICS) +#define STBI_NEON +#endif +#define STBI_ONLY_JPEG +#define STBI_NO_GIF +#define STBI_NO_PNG +#define STBI_NO_HDR +#define STBI_NO_LINEAR +#define STBI_NO_ZLIB +#define STBI_NO_STDIO +#define STBI_ASSERT SDL_assert +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#undef memset +#endif + +#ifdef SDL_HAVE_STB +static bool SDL_ConvertPixels_MJPG_to_NV12(int width, int height, const void *src, int src_pitch, void *dst, int dst_pitch) +{ + int w = 0, h = 0, format = 0; + stbi__context s; + stbi__start_mem(&s, src, src_pitch); + + stbi__result_info ri; + SDL_zero(ri); + ri.bits_per_channel = 8; + ri.channel_order = STBI_ORDER_RGB; + ri.num_channels = 0; + + stbi__nv12 nv12; + nv12.w = width; + nv12.h = height; + nv12.pitch = dst_pitch; + nv12.y = (stbi_uc *)dst; + nv12.uv = nv12.y + (nv12.h * nv12.pitch); + + void *pixels = stbi__jpeg_load(&s, &w, &h, &format, 4, &nv12, &ri); + if (!pixels) { + return false; + } + return true; +} +#endif // SDL_HAVE_STB + +bool SDL_ConvertPixels_STB(int width, int height, + SDL_PixelFormat src_format, SDL_Colorspace src_colorspace, SDL_PropertiesID src_properties, const void *src, int src_pitch, + SDL_PixelFormat dst_format, SDL_Colorspace dst_colorspace, SDL_PropertiesID dst_properties, void *dst, int dst_pitch) +{ +#ifdef SDL_HAVE_STB + if (src_format == SDL_PIXELFORMAT_MJPG && dst_format == SDL_PIXELFORMAT_NV12) { + return SDL_ConvertPixels_MJPG_to_NV12(width, height, src, src_pitch, dst, dst_pitch); + } + + bool result; + int w = 0, h = 0, format = 0; + int len = (src_format == SDL_PIXELFORMAT_MJPG) ? src_pitch : (height * src_pitch); + void *pixels = stbi_load_from_memory(src, len, &w, &h, &format, 4); + if (!pixels) { + return false; + } + + if (w == width && h == height) { + result = SDL_ConvertPixelsAndColorspace(w, h, SDL_PIXELFORMAT_RGBA32, SDL_COLORSPACE_SRGB, 0, pixels, width * 4, dst_format, dst_colorspace, dst_properties, dst, dst_pitch); + } else { + result = SDL_SetError("Expected image size %dx%d, actual size %dx%d", width, height, w, h); + } + stbi_image_free(pixels); + + return result; +#else + return SDL_SetError("SDL not built with STB image support"); +#endif +} diff --git a/libs/SDL3/src/video/SDL_stb_c.h b/libs/SDL3/src/video/SDL_stb_c.h new file mode 100644 index 0000000..bf8cc4d --- /dev/null +++ b/libs/SDL3/src/video/SDL_stb_c.h @@ -0,0 +1,31 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_stb_c_h_ +#define SDL_stb_c_h_ + +#include "SDL_internal.h" + +// Image conversion functions + +extern bool SDL_ConvertPixels_STB(int width, int height, SDL_PixelFormat src_format, SDL_Colorspace src_colorspace, SDL_PropertiesID src_properties, const void *src, int src_pitch, SDL_PixelFormat dst_format, SDL_Colorspace dst_colorspace, SDL_PropertiesID dst_properties, void *dst, int dst_pitch); + +#endif // SDL_stb_c_h_ diff --git a/libs/SDL3/src/video/SDL_surface.c b/libs/SDL3/src/video/SDL_surface.c index d8f831c..92715d2 100644 --- a/libs/SDL3/src/video/SDL_surface.c +++ b/libs/SDL3/src/video/SDL_surface.c @@ -24,6 +24,7 @@ #include "SDL_video_c.h" #include "SDL_RLEaccel_c.h" #include "SDL_pixels_c.h" +#include "SDL_stb_c.h" #include "SDL_yuv_c.h" #include "../render/SDL_sysrender.h" @@ -103,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; @@ -198,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; @@ -213,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) { @@ -244,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 { @@ -1085,9 +1101,9 @@ bool SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surfac int dst_w, dst_h; // Make sure the surfaces aren't locked - if (!SDL_SurfaceValid(src)) { + if (!SDL_SurfaceValid(src) || !src->pixels) { return SDL_InvalidParamError("src"); - } else if (!SDL_SurfaceValid(dst)) { + } else if (!SDL_SurfaceValid(dst) || !dst->pixels) { return SDL_InvalidParamError("dst"); } else if ((src->flags & SDL_SURFACE_LOCKED) || (dst->flags & SDL_SURFACE_LOCKED)) { return SDL_SetError("Surfaces must not be locked during blit"); @@ -1117,6 +1133,13 @@ bool SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surfac return SDL_BlitSurface(src, srcrect, dst, dstrect); } + if (src_w == 0) { + src_w = 1; + } + if (src_h == 0) { + src_h = 1; + } + scaling_w = (double)dst_w / src_w; scaling_h = (double)dst_h / src_h; @@ -1224,7 +1247,7 @@ bool SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surfac // Clip again SDL_GetRectIntersection(clip_rect, &final_dst, &final_dst); - if (final_dst.w == 0 || final_dst.h == 0 || + if (final_dst.w <= 0 || final_dst.h <= 0 || final_src.w < 0 || final_src.h < 0) { // No-op. return true; @@ -1916,7 +1939,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; } @@ -2277,6 +2311,10 @@ bool SDL_ConvertPixelsAndColorspace(int width, int height, dst_colorspace = SDL_GetDefaultColorspaceForFormat(dst_format); } + if (src_format == SDL_PIXELFORMAT_MJPG) { + return SDL_ConvertPixels_STB(width, height, src_format, src_colorspace, src_properties, src, src_pitch, dst_format, dst_colorspace, dst_properties, dst, dst_pitch); + } + #ifdef SDL_HAVE_YUV if (SDL_ISPIXELFORMAT_FOURCC(src_format) && SDL_ISPIXELFORMAT_FOURCC(dst_format)) { return SDL_ConvertPixels_YUV_to_YUV(width, height, src_format, src_colorspace, src_properties, src, src_pitch, dst_format, dst_colorspace, dst_properties, dst, dst_pitch); @@ -2293,13 +2331,17 @@ bool SDL_ConvertPixelsAndColorspace(int width, int height, // Fast path for same format copy if (src_format == dst_format && src_colorspace == dst_colorspace) { - int i; - const int bpp = SDL_BYTESPERPIXEL(src_format); - width *= bpp; - for (i = height; i--;) { - SDL_memcpy(dst, src, width); - src = (const Uint8 *)src + src_pitch; - dst = (Uint8 *)dst + dst_pitch; + if (src_pitch == dst_pitch) { + SDL_memcpy(dst, src, height * src_pitch); + } else { + int i; + const int bpp = SDL_BYTESPERPIXEL(src_format); + width *= bpp; + for (i = height; i--;) { + SDL_memcpy(dst, src, width); + src = (const Uint8 *)src + src_pitch; + dst = (Uint8 *)dst + dst_pitch; + } } return true; } diff --git a/libs/SDL3/src/video/SDL_surface_c.h b/libs/SDL3/src/video/SDL_surface_c.h index 27dc88a..9e73a4b 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 e043305..ec84891 100644 --- a/libs/SDL3/src/video/SDL_sysvideo.h +++ b/libs/SDL3/src/video/SDL_sysvideo.h @@ -103,6 +103,7 @@ struct SDL_Window bool restore_on_show; // Child was hidden recursively by the parent, restore when shown. bool last_position_pending; // This should NOT be cleared by the backend, as it is used for fullscreen positioning. bool last_size_pending; // This should be cleared by the backend if the new size cannot be applied. + bool constrain_popup; bool is_destroying; bool is_dropping; // drag/drop in progress, expecting SDL_SendDropComplete(). @@ -129,6 +130,9 @@ struct SDL_Window SDL_WindowData *internal; + // If a toplevel window, holds the current keyboard focus for grabbing popups. + SDL_Window *keyboard_focus; + SDL_Window *prev; SDL_Window *next; @@ -163,6 +167,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; @@ -394,8 +400,7 @@ struct SDL_VideoDevice bool checked_texture_framebuffer; bool is_dummy; bool suspend_screensaver; - SDL_Window *wakeup_window; - SDL_Mutex *wakeup_lock; // Initialized only if WaitEventTimeout/SendWakeupEvent are supported + void *wakeup_window; int num_displays; SDL_VideoDisplay **displays; SDL_Rect desktop_bounds; @@ -504,6 +509,7 @@ typedef struct VideoBootStrap const char *desc; SDL_VideoDevice *(*create)(void); bool (*ShowMessageBox)(const SDL_MessageBoxData *messageboxdata, int *buttonID); // can be done without initializing backend! + bool is_preferred; } VideoBootStrap; // Not all of these are available in a given build. Use #ifdefs, etc. @@ -563,6 +569,8 @@ extern bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags); extern bool SDL_HasWindows(void); extern void SDL_RelativeToGlobalForWindow(SDL_Window *window, int rel_x, int rel_y, int *abs_x, int *abs_y); extern void SDL_GlobalToRelativeForWindow(SDL_Window *window, int abs_x, int abs_y, int *rel_x, int *rel_y); +extern bool SDL_ShouldFocusPopup(SDL_Window *window); +extern bool SDL_ShouldRelinquishPopupFocus(SDL_Window *window, SDL_Window **new_focus); extern void SDL_OnDisplayAdded(SDL_VideoDisplay *display); extern void SDL_OnDisplayMoved(SDL_VideoDisplay *display); diff --git a/libs/SDL3/src/video/SDL_video.c b/libs/SDL3/src/video/SDL_video.c index e773969..fdfe239 100644 --- a/libs/SDL3/src/video/SDL_video.c +++ b/libs/SDL3/src/video/SDL_video.c @@ -171,9 +171,10 @@ static VideoBootStrap *bootstrap[] = { } #if defined(SDL_PLATFORM_MACOS) && defined(SDL_VIDEO_DRIVER_COCOA) -// Support for macOS fullscreen spaces +// Support for macOS fullscreen spaces, etc. extern bool Cocoa_IsWindowInFullscreenSpace(SDL_Window *window); extern bool Cocoa_SetWindowFullscreenSpace(SDL_Window *window, bool state, bool blocking); +extern bool Cocoa_IsShowingModalDialog(SDL_Window *window); #endif #ifdef SDL_VIDEO_DRIVER_UIKIT @@ -596,6 +597,7 @@ const char *SDL_GetVideoDriver(int index) if (index >= 0 && index < SDL_GetNumVideoDrivers()) { return deduped_bootstrap[index]->name; } + SDL_InvalidParamError("index"); return NULL; } @@ -654,7 +656,8 @@ bool SDL_VideoInit(const char *driver_name) : SDL_strlen(driver_attempt); for (i = 0; bootstrap[i]; ++i) { - if ((driver_attempt_len == SDL_strlen(bootstrap[i]->name)) && + if (!bootstrap[i]->is_preferred && + (driver_attempt_len == SDL_strlen(bootstrap[i]->name)) && (SDL_strncasecmp(bootstrap[i]->name, driver_attempt, driver_attempt_len) == 0)) { video = bootstrap[i]->create(); if (video) { @@ -1433,6 +1436,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) { @@ -1724,12 +1732,25 @@ SDL_VideoDisplay *SDL_GetVideoDisplayForFullscreenWindow(SDL_Window *window) return SDL_GetVideoDisplay(displayID); } +#define SDL_PROP_SDL2_COMPAT_WINDOW_PREFERRED_FULLSCREEN_DISPLAY "sdl2-compat.window.preferred_fullscreen_display" + SDL_DisplayID SDL_GetDisplayForWindow(SDL_Window *window) { SDL_DisplayID displayID = 0; CHECK_WINDOW_MAGIC(window, 0); + /* sdl2-compat calls this function to get a display on which to make the window fullscreen, + * so pass it the preferred fullscreen display ID in a property. + */ + SDL_PropertiesID window_props = SDL_GetWindowProperties(window); + SDL_VideoDisplay *fs_display = SDL_GetVideoDisplayForFullscreenWindow(window); + if (fs_display) { + SDL_SetNumberProperty(window_props, SDL_PROP_SDL2_COMPAT_WINDOW_PREFERRED_FULLSCREEN_DISPLAY, fs_display->id); + } else { + SDL_ClearProperty(window_props, SDL_PROP_SDL2_COMPAT_WINDOW_PREFERRED_FULLSCREEN_DISPLAY); + } + // An explicit fullscreen display overrides all if (window->flags & SDL_WINDOW_FULLSCREEN) { displayID = window->current_fullscreen_mode.displayID; @@ -1942,6 +1963,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; } @@ -1959,6 +1982,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; } } @@ -1974,18 +1998,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 { @@ -2003,6 +2033,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) { @@ -2032,6 +2064,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 { @@ -2471,6 +2504,7 @@ SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props) window->is_destroying = false; window->last_displayID = SDL_GetDisplayForWindow(window); window->external_graphics_context = external_graphics_context; + window->constrain_popup = SDL_GetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_CONSTRAIN_POPUP_BOOLEAN, true); if (_this->windows) { _this->windows->prev = window; @@ -2481,7 +2515,9 @@ SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props) SDL_UpdateWindowHierarchy(window, parent); if (_this->CreateSDLWindow && !_this->CreateSDLWindow(_this, window, props)) { + PUSH_SDL_ERROR() SDL_DestroyWindow(window); + POP_SDL_ERROR() return NULL; } @@ -2729,7 +2765,7 @@ SDL_Window *SDL_GetWindowFromID(SDL_WindowID id) } } } - SDL_SetError("Invalid window ID"); \ + SDL_SetError("Invalid window ID"); return NULL; } @@ -2765,9 +2801,16 @@ bool SDL_SetWindowTitle(SDL_Window *window, const char *title) if (title == window->title) { return true; } + if (!title) { + title = ""; + } + if (window->title && SDL_strcmp(title, window->title) == 0) { + return true; + } + SDL_free(window->title); - window->title = SDL_strdup(title ? title : ""); + window->title = SDL_strdup(title); if (_this->SetWindowTitle) { _this->SetWindowTitle(_this, window); @@ -2898,11 +2941,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; @@ -3619,6 +3663,10 @@ bool SDL_SetWindowParent(SDL_Window *window, SDL_Window *parent) CHECK_WINDOW_NOT_POPUP(parent, false); } + if (window == parent) { + return SDL_SetError("Cannot set the parent of a window to itself."); + } + if (!_this->SetWindowParent) { return SDL_Unsupported(); } @@ -3664,6 +3712,48 @@ bool SDL_SetWindowModal(SDL_Window *window, bool modal) return _this->SetWindowModal(_this, window, modal); } +bool SDL_ShouldRelinquishPopupFocus(SDL_Window *window, SDL_Window **new_focus) +{ + SDL_Window *focus = window->parent; + bool set_focus = !!(window->flags & SDL_WINDOW_INPUT_FOCUS); + + // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed, and can grab the keyboard focus. + while (SDL_WINDOW_IS_POPUP(focus) && ((focus->flags & SDL_WINDOW_NOT_FOCUSABLE) || focus->is_hiding || focus->is_destroying)) { + focus = focus->parent; + + // If some window in the chain currently had focus, set it to the new lowest-level window. + if (!set_focus) { + set_focus = !!(focus->flags & SDL_WINDOW_INPUT_FOCUS); + } + } + + *new_focus = focus; + return set_focus; +} + +bool SDL_ShouldFocusPopup(SDL_Window *window) +{ + SDL_Window *toplevel_parent; + for (toplevel_parent = window->parent; SDL_WINDOW_IS_POPUP(toplevel_parent); toplevel_parent = toplevel_parent->parent) { + } + + SDL_Window *current_focus = toplevel_parent->keyboard_focus; + bool found_higher_focus = false; + + /* Traverse the window tree from the currently focused window to the toplevel parent and see if we encounter + * the new focus request. If the new window is found, a higher-level window already has focus. + */ + SDL_Window *w; + for (w = current_focus; w != toplevel_parent; w = w->parent) { + if (w == window) { + found_higher_focus = true; + break; + } + } + + return !found_higher_focus || w == toplevel_parent; +} + bool SDL_SetWindowFocusable(SDL_Window *window, bool focusable) { CHECK_WINDOW_MAGIC(window, false); @@ -4082,7 +4172,9 @@ static bool SDL_ShouldMinimizeOnFocusLoss(SDL_Window *window) #if defined(SDL_PLATFORM_MACOS) && defined(SDL_VIDEO_DRIVER_COCOA) if (SDL_strcmp(_this->name, "cocoa") == 0) { // don't do this for X11, etc - if (Cocoa_IsWindowInFullscreenSpace(window)) { + if (Cocoa_IsShowingModalDialog(window)) { + return false; // modal system dialogs can live over fullscreen windows, don't minimize. + } else if (Cocoa_IsWindowInFullscreenSpace(window)) { return false; } } @@ -4239,9 +4331,7 @@ void SDL_DestroyWindow(SDL_Window *window) _this->current_glwin = NULL; } - if (_this->wakeup_window == window) { - _this->wakeup_window = NULL; - } + SDL_CompareAndSwapAtomicPointer(&_this->wakeup_window, window, NULL); // Now invalidate magic SDL_SetObjectValid(window, SDL_OBJECT_TYPE_WINDOW, false); @@ -4994,8 +5084,7 @@ bool SDL_GL_GetAttribute(SDL_GLAttr attr, int *value) } if (fbo_type != GL_NONE) { glGetFramebufferAttachmentParameterivFunc(GL_FRAMEBUFFER, attachment, attachmentattrib, (GLint *)value); - } - else { + } else { *value = 0; } if (glBindFramebufferFunc && (current_fbo != 0)) { @@ -5226,7 +5315,7 @@ bool SDL_GL_SwapWindow(SDL_Window *window) bool SDL_GL_DestroyContext(SDL_GLContext context) { if (!_this) { - return SDL_UninitializedVideo(); \ + return SDL_UninitializedVideo(); } if (!context) { return SDL_InvalidParamError("context"); @@ -5378,6 +5467,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 ccacfcf..cffc950 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/android/SDL_androidvideo.c b/libs/SDL3/src/video/android/SDL_androidvideo.c index ed8106a..b154ff7 100644 --- a/libs/SDL3/src/video/android/SDL_androidvideo.c +++ b/libs/SDL3/src/video/android/SDL_androidvideo.c @@ -157,7 +157,8 @@ static SDL_VideoDevice *Android_CreateDevice(void) VideoBootStrap Android_bootstrap = { ANDROID_VID_DRIVER_NAME, "SDL Android video driver", Android_CreateDevice, - Android_ShowMessageBox + Android_ShowMessageBox, + false }; bool Android_VideoInit(SDL_VideoDevice *_this) diff --git a/libs/SDL3/src/video/cocoa/SDL_cocoaclipboard.m b/libs/SDL3/src/video/cocoa/SDL_cocoaclipboard.m index 42c2ad6..7039ff6 100644 --- a/libs/SDL3/src/video/cocoa/SDL_cocoaclipboard.m +++ b/libs/SDL3/src/video/cocoa/SDL_cocoaclipboard.m @@ -158,6 +158,17 @@ bool Cocoa_SetClipboardData(SDL_VideoDevice *_this) @autoreleasepool { SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)_this->internal; NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + + // SetClipboardText specialization so text is available after the app quits + if (_this->clipboard_callback && _this->num_clipboard_mime_types == 1) { + if (SDL_strncmp(_this->clipboard_mime_types[0], "text/plain;charset=utf-8", 24) == 0) { + [pasteboard declareTypes:@[ NSPasteboardTypeString ] owner:nil]; + [pasteboard setString:@((char *)_this->clipboard_userdata) forType:NSPasteboardTypeString]; + data.clipboard_count = [pasteboard changeCount]; + return true; + } + } + NSPasteboardItem *newItem = [NSPasteboardItem new]; NSMutableArray *utiTypes = [NSMutableArray new]; Cocoa_PasteboardDataProvider *provider = [[Cocoa_PasteboardDataProvider alloc] initWith: _this->clipboard_callback userData: _this->clipboard_userdata]; diff --git a/libs/SDL3/src/video/cocoa/SDL_cocoaevents.m b/libs/SDL3/src/video/cocoa/SDL_cocoaevents.m index 58cae99..fd178b8 100644 --- a/libs/SDL3/src/video/cocoa/SDL_cocoaevents.m +++ b/libs/SDL3/src/video/cocoa/SDL_cocoaevents.m @@ -311,15 +311,26 @@ static void Cocoa_DispatchEvent(NSEvent *theEvent) - (void)applicationDidFinishLaunching:(NSNotification *)notification { - if (!SDL_GetHintBoolean("SDL_MAC_REGISTER_ACTIVATION_HANDLERS", true)) + if (!SDL_GetHintBoolean("SDL_MAC_REGISTER_ACTIVATION_HANDLERS", true)) { return; + } /* The menu bar of SDL apps which don't have the typical .app bundle * structure fails to work the first time a window is created (until it's * de-focused and re-focused), if this call is in Cocoa_RegisterApp instead - * of here. https://bugzilla.libsdl.org/show_bug.cgi?id=3051 + * of here. https://github.com/libsdl-org/SDL/issues/1913 */ - if (!SDL_GetHintBoolean(SDL_HINT_MAC_BACKGROUND_APP, false)) { + + /* this apparently became unnecessary on macOS 14.0, and will addition pop up a + hidden dock if you're moving the mouse during launch, so change the default + behaviour there. https://github.com/libsdl-org/SDL/issues/10340 + (13.6 still needs it, presumably 13.7 does, too.) */ + bool background_app_default = false; + if (@available(macOS 14.0, *)) { + background_app_default = true; /* by default, don't explicitly activate the dock and then us again to force to foreground */ + } + + if (!SDL_GetHintBoolean(SDL_HINT_MAC_BACKGROUND_APP, background_app_default)) { // Get more aggressive for Catalina: activate the Dock first so we definitely reset all activation state. for (NSRunningApplication *i in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) { [i activateWithOptions:NSApplicationActivateIgnoringOtherApps]; diff --git a/libs/SDL3/src/video/cocoa/SDL_cocoakeyboard.m b/libs/SDL3/src/video/cocoa/SDL_cocoakeyboard.m index df614e8..e458be9 100644 --- a/libs/SDL3/src/video/cocoa/SDL_cocoakeyboard.m +++ b/libs/SDL3/src/video/cocoa/SDL_cocoakeyboard.m @@ -30,8 +30,11 @@ #include -// #define DEBUG_IME NSLog +#if 0 +#define DEBUG_IME NSLog +#else #define DEBUG_IME(...) +#endif @interface SDL3TranslatorResponder : NSView { @@ -133,8 +136,12 @@ // This key event was consumed by the IME [self clearPendingKey]; + NSUInteger utf32SelectedRangeLocation = [[aString substringToIndex:selectedRange.location] lengthOfBytesUsingEncoding:NSUTF32StringEncoding] / 4; + NSUInteger utf32SelectionRangeEnd = [[aString substringToIndex:(selectedRange.location + selectedRange.length)] lengthOfBytesUsingEncoding:NSUTF32StringEncoding] / 4; + NSUInteger utf32SelectionRangeLength = utf32SelectionRangeEnd - utf32SelectedRangeLocation; + SDL_SendEditingText([aString UTF8String], - (int)selectedRange.location, (int)selectedRange.length); + (int)utf32SelectedRangeLocation, (int)utf32SelectionRangeLength); DEBUG_IME(@"setMarkedText: %@, (%d, %d) replacement range (%d, %d)", _markedText, (int)selectedRange.location, (int)selectedRange.length, diff --git a/libs/SDL3/src/video/cocoa/SDL_cocoametalview.m b/libs/SDL3/src/video/cocoa/SDL_cocoametalview.m index d856645..af84e93 100644 --- a/libs/SDL3/src/video/cocoa/SDL_cocoametalview.m +++ b/libs/SDL3/src/video/cocoa/SDL_cocoametalview.m @@ -26,6 +26,8 @@ */ #include "SDL_internal.h" +#include "../../events/SDL_windowevents_c.h" + #import "SDL_cocoametalview.h" #if defined(SDL_VIDEO_DRIVER_COCOA) && (defined(SDL_VIDEO_VULKAN) || defined(SDL_VIDEO_METAL)) @@ -88,7 +90,7 @@ static bool SDLCALL SDL_MetalViewEventWatch(void *userdata, SDL_Event *event) self.layer.opaque = opaque; - SDL_AddEventWatch(SDL_MetalViewEventWatch, (__bridge void *)(self)); + SDL_AddWindowEventWatch(SDL_WINDOW_EVENT_WATCH_EARLY, SDL_MetalViewEventWatch, (__bridge void *)(self)); [self updateDrawableSize]; } @@ -98,7 +100,7 @@ static bool SDLCALL SDL_MetalViewEventWatch(void *userdata, SDL_Event *event) - (void)dealloc { - SDL_RemoveEventWatch(SDL_MetalViewEventWatch, (__bridge void *)(self)); + SDL_RemoveWindowEventWatch(SDL_WINDOW_EVENT_WATCH_EARLY, SDL_MetalViewEventWatch, (__bridge void *)(self)); } - (NSInteger)tag diff --git a/libs/SDL3/src/video/cocoa/SDL_cocoamodes.m b/libs/SDL3/src/video/cocoa/SDL_cocoamodes.m index b4e151b..4c168de 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_cocoamouse.m b/libs/SDL3/src/video/cocoa/SDL_cocoamouse.m index 77ebf10..f8f5829 100644 --- a/libs/SDL3/src/video/cocoa/SDL_cocoamouse.m +++ b/libs/SDL3/src/video/cocoa/SDL_cocoamouse.m @@ -447,13 +447,29 @@ void Cocoa_HandleMouseEvent(SDL_VideoDevice *_this, NSEvent *event) float deltaX, deltaY; bool seenWarp; - switch ([event type]) { - case NSEventTypeMouseEntered: - Cocoa_MouseFocus = [event window]; - return; - case NSEventTypeMouseExited: + // All events except NSEventTypeMouseExited can only happen if the window + // has mouse focus, so we'll always set the focus even if we happen to miss + // NSEventTypeMouseEntered, which apparently happens if the window is + // created under the mouse on macOS 12.7. But, only set the focus if + // the event acutally has a non-NULL window, otherwise what would happen + // is that after an NSEventTypeMouseEntered there would sometimes be + // NSEventTypeMouseMoved without a window causing us to suppress subsequent + // mouse move events. + NSEventType event_type = [event type]; + if (event_type == NSEventTypeMouseExited) { Cocoa_MouseFocus = NULL; + } else { + if ([event window] != NULL) { + Cocoa_MouseFocus = [event window]; + } + } + + switch (event_type) { + case NSEventTypeMouseEntered: + case NSEventTypeMouseExited: + // Focus is handled above return; + case NSEventTypeMouseMoved: case NSEventTypeLeftMouseDragged: case NSEventTypeRightMouseDragged: diff --git a/libs/SDL3/src/video/cocoa/SDL_cocoavideo.m b/libs/SDL3/src/video/cocoa/SDL_cocoavideo.m index aae54eb..aed193d 100644 --- a/libs/SDL3/src/video/cocoa/SDL_cocoavideo.m +++ b/libs/SDL3/src/video/cocoa/SDL_cocoavideo.m @@ -49,9 +49,6 @@ static void Cocoa_VideoQuit(SDL_VideoDevice *_this); static void Cocoa_DeleteDevice(SDL_VideoDevice *device) { @autoreleasepool { - if (device->wakeup_lock) { - SDL_DestroyMutex(device->wakeup_lock); - } CFBridgingRelease(device->internal); SDL_free(device); } @@ -81,7 +78,6 @@ static SDL_VideoDevice *Cocoa_CreateDevice(void) return NULL; } device->internal = (SDL_VideoData *)CFBridgingRetain(data); - device->wakeup_lock = SDL_CreateMutex(); device->system_theme = Cocoa_GetSystemTheme(); // Set the function pointers @@ -195,7 +191,8 @@ static SDL_VideoDevice *Cocoa_CreateDevice(void) VideoBootStrap COCOA_bootstrap = { "cocoa", "SDL Cocoa video driver", Cocoa_CreateDevice, - Cocoa_ShowMessageBox + Cocoa_ShowMessageBox, + false }; static bool Cocoa_VideoInit(SDL_VideoDevice *_this) diff --git a/libs/SDL3/src/video/cocoa/SDL_cocoawindow.h b/libs/SDL3/src/video/cocoa/SDL_cocoawindow.h index 6df69f4..e4ab6ef 100644 --- a/libs/SDL3/src/video/cocoa/SDL_cocoawindow.h +++ b/libs/SDL3/src/video/cocoa/SDL_cocoawindow.h @@ -146,13 +146,13 @@ typedef enum @property(nonatomic) BOOL was_zoomed; @property(nonatomic) NSInteger window_number; @property(nonatomic) NSInteger flash_request; -@property(nonatomic) SDL_Window *keyboard_focus; @property(nonatomic) SDL3Cocoa_WindowListener *listener; @property(nonatomic) NSModalSession modal_session; @property(nonatomic) SDL_CocoaVideoData *videodata; @property(nonatomic) bool pending_size; @property(nonatomic) bool pending_position; @property(nonatomic) bool border_toggled; +@property(nonatomic) bool has_modal_dialog; #ifdef SDL_VIDEO_OPENGL_EGL @property(nonatomic) EGLSurface egl_surface; diff --git a/libs/SDL3/src/video/cocoa/SDL_cocoawindow.m b/libs/SDL3/src/video/cocoa/SDL_cocoawindow.m index 06ba56a..27f9bd3 100644 --- a/libs/SDL3/src/video/cocoa/SDL_cocoawindow.m +++ b/libs/SDL3/src/video/cocoa/SDL_cocoawindow.m @@ -411,6 +411,39 @@ bool Cocoa_IsWindowInFullscreenSpace(SDL_Window *window) } } +bool Cocoa_IsWindowZoomed(SDL_Window *window) +{ + SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; + NSWindow *nswindow = data.nswindow; + bool zoomed = false; + + // isZoomed always returns true if the window is not resizable or the window is fullscreen + if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed] && + !(window->flags & SDL_WINDOW_FULLSCREEN) && !Cocoa_IsWindowInFullscreenSpace(window)) { + // If we are at our desired floating area, then we're not zoomed + bool floating = (window->x == window->floating.x && + window->y == window->floating.y && + window->w == window->floating.w && + window->h == window->floating.h); + if (!floating) { + zoomed = true; + } + } + return zoomed; +} + +bool Cocoa_IsShowingModalDialog(SDL_Window *window) +{ + SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; + return data.has_modal_dialog; +} + +void Cocoa_SetWindowHasModalDialog(SDL_Window *window, bool has_modal) +{ + SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; + data.has_modal_dialog = has_modal; +} + typedef enum CocoaMenuVisibility { COCOA_MENU_VISIBILITY_AUTO = 0, @@ -686,13 +719,10 @@ static SDL_Window *GetParentToplevelWindow(SDL_Window *window) static void Cocoa_SetKeyboardFocus(SDL_Window *window, bool set_active_focus) { SDL_Window *toplevel = GetParentToplevelWindow(window); - SDL_CocoaWindowData *toplevel_data; - - toplevel_data = (__bridge SDL_CocoaWindowData *)toplevel->internal; - toplevel_data.keyboard_focus = window; + toplevel->keyboard_focus = window; if (set_active_focus && !window->is_hiding && !window->is_destroying) { - SDL_SetKeyboardFocus(window); + SDL_SetKeyboardFocus(window); } } @@ -847,14 +877,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; @@ -882,6 +914,11 @@ static NSCursor *Cocoa_GetDesiredCursor(void) pendingWindowOperation &= ~operation; } +- (void)clearAllPendingWindowOperations +{ + pendingWindowOperation = PENDING_OPERATION_NONE; +} + - (void)addPendingWindowOperation:(PendingWindowOperation)operation { pendingWindowOperation |= operation; @@ -894,7 +931,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; } @@ -1161,21 +1199,27 @@ static NSCursor *Cocoa_GetDesiredCursor(void) ScheduleContextUpdates(_data); - /* isZoomed always returns true if the window is not resizable - * and fullscreen windows are considered zoomed. + /* The OS can resize the window automatically if the display density + * changes while the window is miniaturized or hidden. */ - if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed] && - !(window->flags & SDL_WINDOW_FULLSCREEN) && ![self isInFullscreenSpace]) { - zoomed = YES; - } else { - zoomed = NO; - } - if (!zoomed) { - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0); - } else if (zoomed) { - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0); - if ([self windowOperationIsPending:PENDING_OPERATION_MINIMIZE]) { - [nswindow miniaturize:nil]; + if ([nswindow isVisible]) + { + /* isZoomed always returns true if the window is not resizable + * and fullscreen windows are considered zoomed. + */ + if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed] && + !(window->flags & SDL_WINDOW_FULLSCREEN) && ![self isInFullscreenSpace]) { + zoomed = YES; + } else { + zoomed = NO; + } + if (!zoomed) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + } else { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0); + if ([self windowOperationIsPending:PENDING_OPERATION_MINIMIZE]) { + [nswindow miniaturize:nil]; + } } } @@ -1208,8 +1252,7 @@ static NSCursor *Cocoa_GetDesiredCursor(void) // Always send restored before maximized. SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_RESTORED, 0, 0); - // isZoomed always returns true if the window is not resizable. - if ((_data.window->flags & SDL_WINDOW_RESIZABLE) && [_data.nswindow isZoomed]) { + if (Cocoa_IsWindowZoomed(_data.window)) { SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0); } @@ -1224,7 +1267,7 @@ static NSCursor *Cocoa_GetDesiredCursor(void) // We're going to get keyboard events, since we're key. // This needs to be done before restoring the relative mouse mode. - Cocoa_SetKeyboardFocus(_data.keyboard_focus ? _data.keyboard_focus : window, true); + Cocoa_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window, true); // If we just gained focus we need the updated mouse position if (!(window->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE)) { @@ -1329,21 +1372,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 @@ -1351,6 +1387,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]) { @@ -1401,26 +1438,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 @@ -1429,16 +1454,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(). @@ -1483,9 +1516,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; @@ -2003,6 +2037,7 @@ static void Cocoa_SendMouseButtonClicks(SDL_Mouse *mouse, NSEvent *theEvent, SDL @interface SDL3View : NSView { SDL_Window *_sdlWindow; + NSTrackingArea *_trackingArea; // only used on macOS <= 11.0 } - (void)setSDLWindow:(SDL_Window *)window; @@ -2014,6 +2049,7 @@ static void Cocoa_SendMouseButtonClicks(SDL_Mouse *mouse, NSEvent *theEvent, SDL - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent; - (BOOL)wantsUpdateLayer; - (void)updateLayer; +- (void)updateTrackingAreas; @end @implementation SDL3View @@ -2085,15 +2121,62 @@ static void Cocoa_SendMouseButtonClicks(SDL_Mouse *mouse, NSEvent *theEvent, SDL - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent { - if (SDL_GetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH)) { + if (_sdlWindow->flags & SDL_WINDOW_POPUP_MENU) { + return YES; + } else if (SDL_GetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH)) { return SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, false); } else { return SDL_GetHintBoolean("SDL_MAC_MOUSE_FOCUS_CLICKTHROUGH", false); } } +- (void)updateTrackingAreas +{ + [super updateTrackingAreas]; + + if (@available(macOS 12.0, *)) { + // we (currently) use the tracking areas as a workaround for older macOSes, but we might be safe everywhere... + } else { + SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)_sdlWindow->internal; + if (_trackingArea) { + [self removeTrackingArea:_trackingArea]; + } + _trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:NSTrackingMouseEnteredAndExited|NSTrackingActiveAlways owner:windata.listener userInfo:nil]; + [self addTrackingArea:_trackingArea]; + } +} @end +static void Cocoa_UpdateMouseFocus() +{ + const NSPoint mouseLocation = [NSEvent mouseLocation]; + + // Find the topmost window under the pointer and send a motion event if it is an SDL window. + [NSApp enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack + usingBlock:^(NSWindow *nswin, BOOL *stop) { + NSRect r = [nswin contentRectForFrameRect:[nswin frame]]; + if (NSPointInRect(mouseLocation, r)) { + SDL_VideoDevice *vid = SDL_GetVideoDevice(); + SDL_Window *sdlwindow; + for (sdlwindow = vid->windows; sdlwindow; sdlwindow = sdlwindow->next) { + if (nswin == ((__bridge SDL_CocoaWindowData *)sdlwindow->internal).nswindow) { + break; + } + } + *stop = YES; + if (sdlwindow) { + int wx, wy; + SDL_RelativeToGlobalForWindow(sdlwindow, sdlwindow->x, sdlwindow->y, &wx, &wy); + + // Calculate the cursor coordinates relative to the window. + const float dx = mouseLocation.x - wx; + const float dy = (CGDisplayPixelsHigh(kCGDirectMainDisplay) - mouseLocation.y) - wy; + SDL_SendMouseMotion(0, sdlwindow, SDL_GLOBAL_MOUSE_ID, false, dx, dy); + } + } + }]; +} + static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow *nswindow, NSView *nsview) { @autoreleasepool { @@ -2105,6 +2188,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; @@ -2190,8 +2274,12 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow } else { if (window->flags & SDL_WINDOW_TOOLTIP) { [nswindow setIgnoresMouseEvents:YES]; - } else if (window->flags & SDL_WINDOW_POPUP_MENU) { - Cocoa_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus()); + [nswindow setAcceptsMouseMovedEvents:NO]; + } else if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_HIDDEN)) { + if (!(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + Cocoa_SetKeyboardFocus(window, true); + } + Cocoa_UpdateMouseFocus(); } } @@ -2225,7 +2313,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; } } @@ -2281,7 +2368,7 @@ bool Cocoa_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properti rect.origin.y -= screenRect.origin.y; // Constrain the popup - if (SDL_WINDOW_IS_POPUP(window)) { + if (SDL_WINDOW_IS_POPUP(window) && window->constrain_popup) { if (rect.origin.x + rect.size.width > screenRect.origin.x + screenRect.size.width) { rect.origin.x -= (rect.origin.x + rect.size.width) - (screenRect.origin.x + screenRect.size.width); } @@ -2437,7 +2524,7 @@ bool Cocoa_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) ConvertNSRect(&rect); // Position and constrain the popup - if (SDL_WINDOW_IS_POPUP(window)) { + if (SDL_WINDOW_IS_POPUP(window) && window->constrain_popup) { NSRect screenRect = [ScreenForRect(&rect) frame]; if (rect.origin.x + rect.size.width > screenRect.origin.x + screenRect.size.width) { @@ -2469,30 +2556,24 @@ void Cocoa_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) return; } - // isZoomed always returns true if the window is not resizable - if (!(window->flags & SDL_WINDOW_RESIZABLE) || ![nswindow isZoomed]) { - if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { - int x, y; - NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]]; + if (!Cocoa_IsWindowZoomed(window)) { + int x, y; + NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]]; - /* Cocoa will resize the window from the bottom-left rather than the - * top-left when -[nswindow setContentSize:] is used, so we must set the - * entire frame based on the new size, in order to preserve the position. - */ - SDL_RelativeToGlobalForWindow(window, window->floating.x, window->floating.y, &x, &y); - rect.origin.x = x; - rect.origin.y = y; - rect.size.width = window->pending.w; - rect.size.height = window->pending.h; - ConvertNSRect(&rect); + /* Cocoa will resize the window from the bottom-left rather than the + * top-left when -[nswindow setContentSize:] is used, so we must set the + * entire frame based on the new size, in order to preserve the position. + */ + SDL_RelativeToGlobalForWindow(window, window->floating.x, window->floating.y, &x, &y); + rect.origin.x = x; + rect.origin.y = y; + rect.size.width = window->pending.w; + rect.size.height = window->pending.h; + ConvertNSRect(&rect); - [nswindow setFrame:[nswindow frameRectForContentRect:rect] display:YES]; - ScheduleContextUpdates(windata); - } else { - // Can't set the window size. - window->last_size_pending = false; - } - } else { + [nswindow setFrame:[nswindow frameRectForContentRect:rect] display:YES]; + ScheduleContextUpdates(windata); + } else { // Can't set the window size. window->last_size_pending = false; } @@ -2518,8 +2599,8 @@ void Cocoa_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window) SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal; NSSize maxSize; - maxSize.width = window->max_w; - maxSize.height = window->max_h; + maxSize.width = window->max_w ? window->max_w : CGFLOAT_MAX; + maxSize.height = window->max_h ? window->max_h : CGFLOAT_MAX; [windata.nswindow setContentMaxSize:maxSize]; } @@ -2583,6 +2664,11 @@ void Cocoa_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) [nswindow orderWindow:NSWindowBelow relativeTo:[[NSApp keyWindow] windowNumber]]; } } + } else if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (!(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + Cocoa_SetKeyboardFocus(window, true); + } + Cocoa_UpdateMouseFocus(); } } [nswindow setIsVisible:YES]; @@ -2615,21 +2701,11 @@ void Cocoa_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) Cocoa_SetWindowModal(_this, window, false); // Transfer keyboard focus back to the parent when closing a popup menu - if (window->flags & SDL_WINDOW_POPUP_MENU) { - SDL_Window *new_focus = window->parent; - bool set_focus = window == SDL_GetKeyboardFocus(); - - // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed. - while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) { - new_focus = new_focus->parent; - - // If some window in the chain currently had focus, set it to the new lowest-level window. - if (!set_focus) { - set_focus = new_focus == SDL_GetKeyboardFocus(); - } - } - + if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); Cocoa_SetKeyboardFocus(new_focus, set_focus); + Cocoa_UpdateMouseFocus(); } else if (window->parent && waskey) { /* Key status is not automatically set on the parent when a child is hidden. Check if the * child window was key, and set the first visible parent to be key if so. @@ -2741,7 +2817,7 @@ void Cocoa_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) ![data.listener isInFullscreenSpace]) { if ([nswindow isMiniaturized]) { [nswindow deminiaturize:nil]; - } else if ((window->flags & SDL_WINDOW_RESIZABLE) && [data.nswindow isZoomed]) { + } else if (Cocoa_IsWindowZoomed(window)) { [nswindow zoom:nil]; } } else if (data.was_zoomed) { @@ -2767,6 +2843,7 @@ void Cocoa_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bo } else { data.border_toggled = true; } + Cocoa_UpdateClipCursor(window); } } @@ -3053,20 +3130,19 @@ void Cocoa_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) #endif // SDL_VIDEO_OPENGL SDL_Window *topmost = GetParentToplevelWindow(window); - SDL_CocoaWindowData *topmost_data = (__bridge SDL_CocoaWindowData *)topmost->internal; /* Reset the input focus of the root window if this window is still set as keyboard focus. * SDL_DestroyWindow will have already taken care of reassigning focus if this is the SDL * keyboard focus, this ensures that an inactive window with this window set as input focus * does not try to reference it the next time it gains focus. */ - if (topmost_data.keyboard_focus == window) { + if (topmost->keyboard_focus == window) { SDL_Window *new_focus = window; while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) { new_focus = new_focus->parent; } - topmost_data.keyboard_focus = new_focus; + topmost->keyboard_focus = new_focus; } if ([data.listener isInFullscreenSpace]) { @@ -3231,6 +3307,20 @@ bool Cocoa_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOper bool Cocoa_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable) { + if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (!(window->flags & SDL_WINDOW_HIDDEN)) { + if (!focusable && (window->flags & SDL_WINDOW_INPUT_FOCUS)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); + Cocoa_SetKeyboardFocus(new_focus, set_focus); + } else if (focusable) { + if (SDL_ShouldFocusPopup(window)) { + Cocoa_SetKeyboardFocus(window, true); + } + } + } + } + return true; // just succeed, the real work is done elsewhere. } @@ -3245,25 +3335,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/dummy/SDL_nullvideo.c b/libs/SDL3/src/video/dummy/SDL_nullvideo.c index e5525a6..7be4ae1 100644 --- a/libs/SDL3/src/video/dummy/SDL_nullvideo.c +++ b/libs/SDL3/src/video/dummy/SDL_nullvideo.c @@ -121,7 +121,8 @@ static SDL_VideoDevice *DUMMY_CreateDevice(void) VideoBootStrap DUMMY_bootstrap = { DUMMYVID_DRIVER_NAME, "SDL dummy video driver", DUMMY_CreateDevice, - NULL // no ShowMessageBox implementation + NULL, // no ShowMessageBox implementation + false }; #ifdef SDL_INPUT_LINUXEV @@ -144,7 +145,8 @@ static SDL_VideoDevice *DUMMY_EVDEV_CreateDevice(void) VideoBootStrap DUMMY_evdev_bootstrap = { DUMMYVID_DRIVER_EVDEV_NAME, "SDL dummy video driver with evdev", DUMMY_EVDEV_CreateDevice, - NULL // no ShowMessageBox implementation + NULL, // no ShowMessageBox implementation + false }; #endif // SDL_INPUT_LINUXEV diff --git a/libs/SDL3/src/video/emscripten/SDL_emscriptenevents.c b/libs/SDL3/src/video/emscripten/SDL_emscriptenevents.c index de9ec57..d9fa3c8 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 = EMSCRIPTEN_EVENT_TARGET_WINDOW; - } - - if (*target) { + 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/emscripten/SDL_emscriptenframebuffer.c b/libs/SDL3/src/video/emscripten/SDL_emscriptenframebuffer.c index 503fac6..89fae73 100644 --- a/libs/SDL3/src/video/emscripten/SDL_emscriptenframebuffer.c +++ b/libs/SDL3/src/video/emscripten/SDL_emscriptenframebuffer.c @@ -78,7 +78,7 @@ bool Emscripten_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wind if (!Module['SDL3']) Module['SDL3'] = {}; var SDL3 = Module['SDL3']; if (SDL3.ctxCanvas !== canvas) { - SDL3.ctx = Module['createContext'](canvas, false, true); + SDL3.ctx = Browser.createContext(canvas, false, true); SDL3.ctxCanvas = canvas; } if (SDL3.w !== w || SDL3.h !== h || SDL3.imageCtx !== SDL3.ctx) { diff --git a/libs/SDL3/src/video/emscripten/SDL_emscriptenopengles.c b/libs/SDL3/src/video/emscripten/SDL_emscriptenopengles.c index 227cdc5..bb490bb 100644 --- a/libs/SDL3/src/video/emscripten/SDL_emscriptenopengles.c +++ b/libs/SDL3/src/video/emscripten/SDL_emscriptenopengles.c @@ -101,7 +101,7 @@ SDL_GLContext Emscripten_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window * context = emscripten_webgl_create_context(window_data->canvas_id, &attribs); - if (context < 0) { + if (!context) { SDL_SetError("Could not create webgl context"); return NULL; } diff --git a/libs/SDL3/src/video/emscripten/SDL_emscriptenvideo.c b/libs/SDL3/src/video/emscripten/SDL_emscriptenvideo.c index 413d96f..e735ee8 100644 --- a/libs/SDL3/src/video/emscripten/SDL_emscriptenvideo.c +++ b/libs/SDL3/src/video/emscripten/SDL_emscriptenvideo.c @@ -64,7 +64,7 @@ static SDL_SystemTheme Emscripten_GetSystemTheme(void) /* Technically, light theme can mean explicit light theme or no preference. https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme#syntax */ - int theme_code = EM_ASM_INT({ + int theme_code = MAIN_THREAD_EM_ASM_INT({ if (!window.matchMedia) { return -1; } @@ -195,7 +195,8 @@ static SDL_VideoDevice *Emscripten_CreateDevice(void) VideoBootStrap Emscripten_bootstrap = { EMSCRIPTENVID_DRIVER_NAME, "SDL emscripten video driver", Emscripten_CreateDevice, - NULL // no ShowMessageBox implementation + NULL, // no ShowMessageBox implementation + false }; bool Emscripten_VideoInit(SDL_VideoDevice *_this) diff --git a/libs/SDL3/src/video/haiku/SDL_bvideo.cc b/libs/SDL3/src/video/haiku/SDL_bvideo.cc index 9669dd4..d7f9e7c 100644 --- a/libs/SDL3/src/video/haiku/SDL_bvideo.cc +++ b/libs/SDL3/src/video/haiku/SDL_bvideo.cc @@ -113,7 +113,8 @@ static SDL_VideoDevice * HAIKU_CreateDevice(void) VideoBootStrap HAIKU_bootstrap = { "haiku", "Haiku graphics", HAIKU_CreateDevice, - HAIKU_ShowMessageBox + HAIKU_ShowMessageBox, + false }; void HAIKU_DeleteDevice(SDL_VideoDevice * device) @@ -240,6 +241,9 @@ static bool HAIKU_SetRelativeMouseMode(bool enabled) SDL_BWin *bewin = _ToBeWin(window); BGLView *_SDL_GLView = bewin->GetGLView(); + if (!_SDL_GLView) { + return false; + } bewin->Lock(); if (enabled) diff --git a/libs/SDL3/src/video/kmsdrm/SDL_kmsdrmvideo.c b/libs/SDL3/src/video/kmsdrm/SDL_kmsdrmvideo.c index a6f3730..adca91c 100644 --- a/libs/SDL3/src/video/kmsdrm/SDL_kmsdrmvideo.c +++ b/libs/SDL3/src/video/kmsdrm/SDL_kmsdrmvideo.c @@ -332,7 +332,8 @@ VideoBootStrap KMSDRM_bootstrap = { "kmsdrm", "KMS/DRM Video Driver", KMSDRM_CreateDevice, - NULL // no ShowMessageBox implementation + NULL, // no ShowMessageBox implementation + false }; static void KMSDRM_FBDestroyCallback(struct gbm_bo *bo, void *data) @@ -1367,9 +1368,14 @@ bool KMSDRM_CreateSurfaces(SDL_VideoDevice *_this, SDL_Window *window) windata->gs = KMSDRM_gbm_surface_create(viddata->gbm_dev, dispdata->mode.hdisplay, dispdata->mode.vdisplay, surface_fmt, surface_flags); - + if (!windata->gs && errno == ENOSYS) { + // Try again without the scanout flags, needed on NVIDIA drivers + windata->gs = KMSDRM_gbm_surface_create(viddata->gbm_dev, + dispdata->mode.hdisplay, dispdata->mode.vdisplay, + surface_fmt, 0); + } if (!windata->gs) { - return SDL_SetError("Could not create GBM surface"); + return SDL_SetError("Could not create GBM surface: %s", strerror(errno)); } /* We can't get the EGL context yet because SDL_CreateRenderer has not been called, @@ -1728,9 +1734,9 @@ bool KMSDRM_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert } /* Create the window surfaces with the size we have just chosen. - Needs the window diverdata in place. */ + Needs the window driverdata in place. */ if (!KMSDRM_CreateSurfaces(_this, window)) { - return SDL_SetError("Can't window GBM/EGL surfaces on window creation."); + return false; } } // NON-Vulkan block ends. diff --git a/libs/SDL3/src/video/n3ds/SDL_n3dsvideo.c b/libs/SDL3/src/video/n3ds/SDL_n3dsvideo.c index 2198279..8e925bf 100644 --- a/libs/SDL3/src/video/n3ds/SDL_n3dsvideo.c +++ b/libs/SDL3/src/video/n3ds/SDL_n3dsvideo.c @@ -118,7 +118,7 @@ static SDL_VideoDevice *N3DS_CreateDevice(void) return device; } -VideoBootStrap N3DS_bootstrap = { N3DSVID_DRIVER_NAME, "N3DS Video Driver", N3DS_CreateDevice, NULL /* no ShowMessageBox implementation */ }; +VideoBootStrap N3DS_bootstrap = { N3DSVID_DRIVER_NAME, "N3DS Video Driver", N3DS_CreateDevice, NULL, /* no ShowMessageBox implementation */ false }; static bool N3DS_VideoInit(SDL_VideoDevice *_this) { diff --git a/libs/SDL3/src/video/offscreen/SDL_offscreenvideo.c b/libs/SDL3/src/video/offscreen/SDL_offscreenvideo.c index bcd4ffe..1ef0f9e 100644 --- a/libs/SDL3/src/video/offscreen/SDL_offscreenvideo.c +++ b/libs/SDL3/src/video/offscreen/SDL_offscreenvideo.c @@ -118,7 +118,8 @@ static SDL_VideoDevice *OFFSCREEN_CreateDevice(void) VideoBootStrap OFFSCREEN_bootstrap = { OFFSCREENVID_DRIVER_NAME, "SDL offscreen video driver", OFFSCREEN_CreateDevice, - NULL // no ShowMessageBox implementation + NULL, // no ShowMessageBox implementation + false }; static bool OFFSCREEN_VideoInit(SDL_VideoDevice *_this) diff --git a/libs/SDL3/src/video/openvr/SDL_openvrvideo.c b/libs/SDL3/src/video/openvr/SDL_openvrvideo.c index 77ad96a..6387c37 100644 --- a/libs/SDL3/src/video/openvr/SDL_openvrvideo.c +++ b/libs/SDL3/src/video/openvr/SDL_openvrvideo.c @@ -911,7 +911,7 @@ static SDL_GLContext OPENVR_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window OPENVR_SetupFrame(_this, window); - SDL_GLContext result = malloc(sizeof(struct SDL_GLContextState)); + SDL_GLContext result = SDL_malloc(sizeof(struct SDL_GLContextState)); if (!result) { return NULL; } @@ -1657,7 +1657,7 @@ error: } VideoBootStrap OPENVR_bootstrap = { - "openvr", "SDL OpenVR video driver", OPENVR_CreateDevice + "openvr", "SDL OpenVR video driver", OPENVR_CreateDevice, NULL, false }; #endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/libs/SDL3/src/video/ps2/SDL_ps2video.c b/libs/SDL3/src/video/ps2/SDL_ps2video.c index 72b8a72..e8d2635 100644 --- a/libs/SDL3/src/video/ps2/SDL_ps2video.c +++ b/libs/SDL3/src/video/ps2/SDL_ps2video.c @@ -114,7 +114,8 @@ VideoBootStrap PS2_bootstrap = { "ps2", "PS2 Video Driver", PS2_CreateDevice, - NULL // no ShowMessageBox implementation + NULL, // no ShowMessageBox implementation + false }; #endif // SDL_VIDEO_DRIVER_PS2 diff --git a/libs/SDL3/src/video/psp/SDL_pspvideo.c b/libs/SDL3/src/video/psp/SDL_pspvideo.c index 08e1e8d..afdb6bb 100644 --- a/libs/SDL3/src/video/psp/SDL_pspvideo.c +++ b/libs/SDL3/src/video/psp/SDL_pspvideo.c @@ -118,6 +118,8 @@ static SDL_VideoDevice *PSP_Create(void) device->PumpEvents = PSP_PumpEvents; + device->device_caps = VIDEO_DEVICE_CAPS_FULLSCREEN_ONLY; + return device; } @@ -265,7 +267,8 @@ VideoBootStrap PSP_bootstrap = { "psp", "PSP Video Driver", PSP_Create, - PSP_ShowMessageBox + PSP_ShowMessageBox, + false }; /*****************************************************************************/ diff --git a/libs/SDL3/src/video/qnx/SDL_qnxvideo.c b/libs/SDL3/src/video/qnx/SDL_qnxvideo.c index db23143..4192cdb 100644 --- a/libs/SDL3/src/video/qnx/SDL_qnxvideo.c +++ b/libs/SDL3/src/video/qnx/SDL_qnxvideo.c @@ -346,5 +346,6 @@ static SDL_VideoDevice *createDevice(void) VideoBootStrap QNX_bootstrap = { "qnx", "QNX Screen", createDevice, - NULL // no ShowMessageBox implementation + NULL, // no ShowMessageBox implementation + false }; diff --git a/libs/SDL3/src/video/raspberry/SDL_rpivideo.c b/libs/SDL3/src/video/raspberry/SDL_rpivideo.c index 2d1e507..d313f9e 100644 --- a/libs/SDL3/src/video/raspberry/SDL_rpivideo.c +++ b/libs/SDL3/src/video/raspberry/SDL_rpivideo.c @@ -135,7 +135,8 @@ VideoBootStrap RPI_bootstrap = { "rpi", "RPI Video Driver", RPI_Create, - NULL // no ShowMessageBox implementation + NULL, // no ShowMessageBox implementation + false }; /*****************************************************************************/ diff --git a/libs/SDL3/src/video/riscos/SDL_riscosvideo.c b/libs/SDL3/src/video/riscos/SDL_riscosvideo.c index 1002a45..1f556d2 100644 --- a/libs/SDL3/src/video/riscos/SDL_riscosvideo.c +++ b/libs/SDL3/src/video/riscos/SDL_riscosvideo.c @@ -96,7 +96,8 @@ static SDL_VideoDevice *RISCOS_CreateDevice(void) VideoBootStrap RISCOS_bootstrap = { RISCOSVID_DRIVER_NAME, "SDL RISC OS video driver", RISCOS_CreateDevice, - RISCOS_ShowMessageBox + RISCOS_ShowMessageBox, + false }; static bool RISCOS_VideoInit(SDL_VideoDevice *_this) diff --git a/libs/SDL3/src/video/sdlgenblit.pl b/libs/SDL3/src/video/sdlgenblit.pl index 47c90d5..1911122 100644 --- a/libs/SDL3/src/video/sdlgenblit.pl +++ b/libs/SDL3/src/video/sdlgenblit.pl @@ -112,7 +112,6 @@ sub open_file { 3. This notice may not be removed or altered from any source distribution. */ #include "SDL_internal.h" -#include "SDL_surface_c.h" #ifdef SDL_HAVE_BLIT_AUTO @@ -641,6 +640,14 @@ sub output_copyinc __EOF__ } +sub output_copyinc_h +{ + print FILE <<__EOF__; +#include "SDL_blit.h" + +__EOF__ +} + sub output_copyfunctable { print FILE <<__EOF__; @@ -693,7 +700,7 @@ __EOF__ } } print FILE <<__EOF__; - { 0, 0, 0, 0, NULL } + { SDL_PIXELFORMAT_UNKNOWN, SDL_PIXELFORMAT_UNKNOWN, 0, 0, NULL } }; __EOF__ @@ -716,6 +723,7 @@ sub output_copyfunc_c } open_file("SDL_blit_auto.h"); +output_copyinc_h(); output_copydefs(); for (my $i = 0; $i <= $#src_formats; ++$i) { for (my $j = 0; $j <= $#dst_formats; ++$j) { diff --git a/libs/SDL3/src/video/stb_image.h b/libs/SDL3/src/video/stb_image.h new file mode 100644 index 0000000..f7c7101 --- /dev/null +++ b/libs/SDL3/src/video/stb_image.h @@ -0,0 +1,8253 @@ +/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.30 (2024-05-31) avoid erroneous gcc warning + 2.29 (2023-05-xx) optimizations + 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen Katelyn Gadd (indexed color loading) + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Henner Zeller Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data); +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#if 0 /* SDL change */ +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; +#else +typedef Uint8 stbi_uc; +typedef Uint16 stbi_us; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +#if 0 /* not used in SDL */ +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 8-bits-per-channel indexed color +// Will fail if image is not an 8-bit PNG or TGA with a palette. +// Palette buffer needs to be at least 256 entries for PNG. +// + +#if 0 /* not used in SDL */ +STBIDEF stbi_uc *stbi_load_from_memory_with_palette (stbi_uc const *buffer, int len , int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len); +STBIDEF stbi_uc *stbi_load_from_callbacks_with_palette(stbi_io_callbacks const *clbk, void *user, int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +#if 0 /* not used in SDL */ +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +#if 0 /* not used in SDL */ +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#endif +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +#if 0 /* not used in SDL */ +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); +#endif + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +#if 0 /* not used in SDL */ +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); +#endif + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + +#ifndef STBI_NO_PNG +#if 0 /* not used in SDL */ +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); +#endif /**/ + +#ifndef STBI_NO_THREAD_LOCALS /**/ +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); +#endif +#endif + +// ZLIB client - used by PNG, available for other purposes + +#ifndef STBI_NO_ZLIB +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); +#endif + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#if 0 /* SDL change */ +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif +#else /* SDL change */ +#ifndef UINT_MAX +#define UINT_MAX SDL_MAX_UINT32 +#endif +#ifndef INT_MAX +#define INT_MAX SDL_MAX_SINT32 +#endif +#ifndef INT_MIN +#define INT_MIN SDL_MIN_SINT32 +#endif +#ifndef SHRT_MAX +#define SHRT_MAX SDL_MAX_SINT16 +#endif +#ifndef SHRT_MIN +#define SHRT_MIN SDL_MIN_SINT16 +#endif +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#if 0 /* SDL change */ +#if defined(_MSC_VER) || defined(__SYMBIAN32__) +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif +#else +typedef Uint16 stbi__uint16; +typedef Sint16 stbi__int16; +typedef Uint32 stbi__uint32; +typedef Sint32 stbi__int32; +#endif + +#ifndef STBI_BUFFER_SIZE +#define STBI_BUFFER_SIZE 128 +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#if 0 /* SDL change: */ +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +#if 0 /* not used in SDL */ +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} +#endif + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int w; + int h; + int pitch; + stbi_uc *y; + stbi_uc *uv; +} stbi__nv12; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__nv12 *nv12, stbi__result_info *ri); +#if 0 /* not used in SDL */ +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, unsigned int *palette_buffer, int palette_buffer_len, stbi__result_info *ri); +#if 0 /* not used in SDL */ +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, unsigned int *palette_buffer, int palette_buffer_len, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_FAILURE_STRINGS +#if 1 /* SDL change: */ +static int stbi__err(const char *str) +{ + SDL_SetError("%s", str); + return 0; +} +#else /* SDL change. */ +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif /**/ +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. +static int stbi__addints_valid(int a, int b) +{ + if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow + if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. + return a <= INT_MAX - b; +} + +// returns 1 if the product of two ints fits in a signed short, 0 on overflow. +static int stbi__mul2shorts_valid(int a, int b) +{ + if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow + if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid + if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN + return a >= SHRT_MIN / b; +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +#ifndef STBI_NO_PNG +#if 0 /* not used in SDL */ +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} +#endif /**/ +#endif + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +#ifndef STBI_NO_PNG +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} +#endif + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc, unsigned int *palette_buffer, int palette_buffer_len) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, palette_buffer, palette_buffer_len, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp,NULL, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, palette_buffer, palette_buffer_len, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +#if 0 /* not used in SDL */ +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} +#endif + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +#if 0 /* not used in SDL */ +static unsigned char *stbi__load_indexed(stbi__context *s, int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len) +{ + stbi__result_info ri; + int comp; + void *result; + + if (!palette_buffer) + return NULL; + + result = stbi__load_main(s, x, y, &comp, 1, &ri, 8, palette_buffer, palette_buffer_len); + if (result == NULL) + return NULL; + + if (comp != 1) { + stbi_image_free(result); + return NULL; + } + + if (ri.bits_per_channel != 8) { + stbi_image_free(result); + return NULL; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = 1; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} +#endif /**/ + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8, NULL, 0); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +#if 0 /* not used in SDL */ +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16, NULL, 0); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} +#endif /**/ + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +#if 0 /* not used in SDL */ +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} +#endif /**/ + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#if 0 /* not used in SDL */ +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_memory_with_palette(stbi_uc const *buffer, int len, int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len) +{ + stbi__context s; + stbi__start_mem(&s, buffer, len); + return stbi__load_indexed(&s, x, y, palette_buffer, palette_buffer_len); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks_with_palette(stbi_io_callbacks const *clbk, void *user, int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_indexed(&s, x, y, palette_buffer, palette_buffer_len); +} +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +#if 0 /* not used in SDL */ +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} +#endif + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +#if 0 /* not used in SDL */ +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} +#endif + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +#ifndef STBI_NO_HDR +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (data == NULL) return data; + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) { + for (j=0; j < count[i]; ++j) { + h->size[k++] = (stbi_uc) (i+1); + if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); + } + } + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + if(c < 0 || c >= 256) // symbol id out of bounds! + return -1; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + do { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } while ( 0 ) + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + do { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } while ( 0 ) + +#define dct_pass(shiftop, shift) \ + do { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } while ( 0 ) + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) do { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } while ( 0 ) +#define dct_trn32(x, y) do { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } while ( 0 ) +#define dct_trn64(x, y) do { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } while ( 0 ) + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) do { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } while ( 0 ) +#define dct_trn8_16(x, y) do { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } while ( 0 ) +#define dct_trn8_32(x, y) do { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } while ( 0 ) + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +{ + // some JPEGs have junk at end, skip over it but if we find what looks + // like a valid marker, resume there + while (!stbi__at_eof(j->s)) { + stbi_uc x = stbi__get8(j->s); + while (x == 0xff) { // might be a marker + if (stbi__at_eof(j->s)) return STBI__MARKER_none; + x = stbi__get8(j->s); + if (x != 0x00 && x != 0xff) { + // not a stuffed zero or lead-in to another marker, looks + // like an actual marker, return it + return x; + } + // stuffed zero has x=0 now which ends the loop, meaning we go + // back to regular scan loop. + // repeated 0xff keeps trying to read the next byte of the marker. + } + } + return STBI__MARKER_none; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + j->marker = stbi__skip_jpeg_junk_at_end(j); + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + m = stbi__get_marker(j); + if (STBI__RESTART(m)) + m = stbi__get_marker(j); + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + m = stbi__get_marker(j); + } else { + if (!stbi__process_marker(j, m)) return 1; + m = stbi__get_marker(j); + } + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + if (SDL_HasNEON()) { /* SDL change */ + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } /**/ +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *output_jpeg_nv12(stbi__jpeg *z, stbi__nv12 *nv12) +{ + unsigned int i,j; + + // Copy the Y plane + if (nv12->pitch == (int)z->s->img_x) { + memcpy(nv12->y, z->img_comp[0].data, z->s->img_y * z->s->img_x); + } else { + for (i=0; i < z->s->img_y; ++i) { + memcpy(nv12->y + i * nv12->pitch, z->img_comp[0].data + i * z->s->img_x, z->s->img_x); + } + } + + if (z->s->img_n == 3) { + // NV12: U and V are interleaved, each subsampled by 2 + const int nv12_hs = 2; + const int nv12_vs = 2; + const int u_hs = (z->img_h_max / z->img_comp[1].h); + const int u_vs = (z->img_v_max / z->img_comp[1].v); + const int v_hs = (z->img_h_max / z->img_comp[2].h); + const int v_vs = (z->img_v_max / z->img_comp[2].v); + for (i=0; i < (z->s->img_y + 1) / 2; ++i) { + stbi_uc *src_u = z->img_comp[1].data + i * (1 + (nv12_vs - u_vs)) * z->img_comp[1].x; + stbi_uc *src_v = z->img_comp[2].data + i * (1 + (nv12_vs - v_vs)) * z->img_comp[2].x; + stbi_uc *dst = nv12->uv + i * nv12->pitch; + for (j=0; j < (z->s->img_x + 1) / 2; ++j) { + *dst++ = *src_u; + src_u += 1 + (nv12_hs - u_hs); + *dst++ = *src_v; + src_v += 1 + (nv12_hs - v_hs); + } + } + } else { + // Grayscale + for (i=0; i < (z->s->img_y + 1) / 2; ++i) { + memset(nv12->uv + i * nv12->pitch, 0x80808080, ((z->s->img_x + 1) / 2) * 2); + } + } + + return nv12->y; +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp, stbi__nv12 *nv12) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + if (nv12) { + if (nv12->w != (int)z->s->img_x || nv12->h != (int)z->s->img_y) { + stbi__cleanup_jpeg(z); + return stbi__errpuc("badsize", "Unexpected size"); + } + + if (is_rgb) { + stbi__cleanup_jpeg(z); + return stbi__errpuc("rgbtonv12", "Can't convert RGB to NV12"); + } + + output = output_jpeg_nv12(z, nv12); + } else { + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__nv12 *nv12, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp,nv12); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +#if 0 /* not used in SDL */ +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif /**/ +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + int hit_zeof_once; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + if (!a->hit_zeof_once) { + // This is the first time we hit eof, insert 16 extra padding btis + // to allow us to keep going; if we actually consume any of them + // though, that is invalid data. This is caught later. + a->hit_zeof_once = 1; + a->num_bits += 16; // add 16 implicit zero bits + } else { + // We already inserted our extra 16 padding bits and are again + // out, this stream is actually prematurely terminated. + return -1; + } + } else { + stbi__fill_bits(a); + } + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + if (a->hit_zeof_once && a->num_bits < 16) { + // The first time we hit zeof, we inserted 16 extra zero bits into our bit + // buffer so the decoder can just do its speculative decoding. But if we + // actually consumed any of those bits (which is the case when num_bits < 16), + // the stream actually read past the end so it is malformed. + return stbi__err("unexpected end","Corrupt PNG"); + } + return 1; + } + if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (len > a->zout_end - zout) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + a->hit_zeof_once = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filter used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub +}; + +static int stbi__paeth(int a, int b, int c) +{ + // This formulation looks very different from the reference in the PNG spec, but is + // actually equivalent and has favorable data dependencies and admits straightforward + // generation of branch-free code, which helps performance significantly. + int thresh = c*3 - (a + b); + int lo = a < b ? a : b; + int hi = a < b ? b : a; + int t0 = (hi <= thresh) ? lo : c; + int t1 = (thresh <= lo) ? hi : t0; + return t1; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// adds an extra all-255 alpha channel +// dest == src is legal +// img_n must be 1 or 3 +static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n) +{ + int i; + // must process data backwards since we allow dest==src + if (img_n == 1) { + for (i=x-1; i >= 0; --i) { + dest[i*2+1] = 255; + dest[i*2+0] = src[i]; + } + } else { + STBI_ASSERT(img_n == 3); + for (i=x-1; i >= 0; --i) { + dest[i*4+3] = 255; + dest[i*4+2] = src[i*3+2]; + dest[i*4+1] = src[i*3+1]; + dest[i*4+0] = src[i*3+0]; + } + } +} + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16 ? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + stbi_uc *filter_buf; + int all_ok = 1; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + // note: error exits here don't need to clean up a->out individually, + // stbi__do_png always does on error. + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG"); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + // Allocate two scan lines worth of filter workspace buffer. + filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0); + if (!filter_buf) return stbi__err("outofmem", "Out of memory"); + + // Filtering for low-bit-depth images + if (depth < 8) { + filter_bytes = 1; + width = img_width_bytes; + } + + for (j=0; j < y; ++j) { + // cur/prior filter buffers alternate + stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes; + stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes; + stbi_uc *dest = a->out + stride*j; + int nk = width * filter_bytes; + int filter = *raw++; + + // check filter type + if (filter > 4) { + all_ok = stbi__err("invalid filter","Corrupt PNG"); + break; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // perform actual filtering + switch (filter) { + case STBI__F_none: + memcpy(cur, raw, nk); + break; + case STBI__F_sub: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); + break; + case STBI__F_up: + for (k = 0; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); + break; + case STBI__F_avg: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); + break; + case STBI__F_paeth: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes])); + break; + case STBI__F_avg_first: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); + break; + } + + raw += nk; + + // expand decoded bits in cur to dest, also adding an extra alpha channel if desired + if (depth < 8) { + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + stbi_uc *in = cur; + stbi_uc *out = dest; + stbi_uc inb = 0; + stbi__uint32 nsmp = x*img_n; + + // expand bits to bytes first + if (depth == 4) { + for (i=0; i < nsmp; ++i) { + if ((i & 1) == 0) inb = *in++; + *out++ = scale * (inb >> 4); + inb <<= 4; + } + } else if (depth == 2) { + for (i=0; i < nsmp; ++i) { + if ((i & 3) == 0) inb = *in++; + *out++ = scale * (inb >> 6); + inb <<= 2; + } + } else { + STBI_ASSERT(depth == 1); + for (i=0; i < nsmp; ++i) { + if ((i & 7) == 0) inb = *in++; + *out++ = scale * (inb >> 7); + inb <<= 1; + } + } + + // insert alpha=255 values if desired + if (img_n != out_n) + stbi__create_png_alpha_expand8(dest, dest, x, img_n); + } else if (depth == 8) { + if (img_n == out_n) + memcpy(dest, cur, x*img_n); + else + stbi__create_png_alpha_expand8(dest, cur, x, img_n); + } else if (depth == 16) { + // convert the image data from big-endian to platform-native + stbi__uint16 *dest16 = (stbi__uint16*)dest; + stbi__uint32 nsmp = x*img_n; + + if (img_n == out_n) { + for (i = 0; i < nsmp; ++i, ++dest16, cur += 2) + *dest16 = (cur[0] << 8) | cur[1]; + } else { + STBI_ASSERT(img_n+1 == out_n); + if (img_n == 1) { + for (i = 0; i < x; ++i, dest16 += 2, cur += 2) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = 0xffff; + } + } else { + STBI_ASSERT(img_n == 3); + for (i = 0; i < x; ++i, dest16 += 4, cur += 6) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = (cur[2] << 8) | cur[3]; + dest16[2] = (cur[4] << 8) | cur[5]; + dest16[3] = 0xffff; + } + } + } + } + } + + STBI_FREE(filter_buf); + if (!all_ok) return 0; + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +#if 0 /* not used in SDL */ +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} +#endif + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp, unsigned int *palette_buffer, int palette_buffer_len) +{ + stbi_uc _palette[1024], pal_img_n=0; + stbi_uc *palette = _palette; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + if (palette_buffer) { + if (palette_buffer_len < 256) + return stbi__err("palette buffer too small", "palette buffer len must be 256"); + else if (req_comp != 1) + return stbi__err("invalid req_comp", "req_comp must be 1 when loading paletted"); + else + palette = (stbi_uc *)(void *)palette_buffer; + } + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + } + // even with SCAN_header, have to scan to see if we have a tRNS + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. + if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } + if (z->depth == 16) { + for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning + tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n && k < 3; ++k) + tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { + // header scan definitely stops at first IDAT + if (pal_img_n) + s->img_n = pal_img_n; + return 1; + } + if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!palette_buffer) + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + if (s->io.skip && s->img_buffer_end > s->img_buffer) { + // rewind the additional bytes that have been read to the buffer + (s->io.skip)(s->io_user_data, (int)(s->img_buffer - s->img_buffer_end)); + } + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + (void)invalid_chunk; + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, unsigned int *palette_buffer, int palette_buffer_len, stbi__result_info *ri) +{ + void *result=NULL; + if (palette_buffer && req_comp != 1) { + stbi__err("bad req_comp", "req_comp must be 1 if loading paletted image without expansion"); + return NULL; + } + if (req_comp < 0 || req_comp > 4) { + stbi__err("bad req_comp", "Internal error"); + return NULL; + } + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp, palette_buffer, palette_buffer_len)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (palette_buffer) + ; + else if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) { + if (palette_buffer) + *n = 1; + else + *n = p->s->img_n; + } + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, unsigned int *palette_buffer, int palette_buffer_len, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, palette_buffer, palette_buffer_len, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +#if 0 /* not used in SDL */ +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, NULL, 0, NULL)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif /**/ +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + // accept some number of extra bytes after the header, but if the offset points either to before + // the header ends or implies a large amount of extra data, reject the file as malformed + int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); + int header_limit = 1024; // max we actually read is below 256 bytes currently. + int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. + if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { + return stbi__errpuc("bad header", "Corrupt BMP"); + } + // we established that bytes_read_so_far is positive and sensible. + // the first half of this test rejects offsets that are either too small positives, or + // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn + // ensures the number computed in the second half of the test can't overflow. + if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } else { + stbi__skip(s, info.offset - bytes_read_so_far); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, unsigned int *palette_buffer, int palette_buffer_len, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + if (palette_buffer) { + if (palette_buffer_len < tga_palette_len * tga_comp) { + STBI_FREE(tga_data); + return stbi__errpuc("buffer too small", "Palette buffer too small"); + } + tga_palette = (unsigned char*)(void*)palette_buffer; + } else { + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + if (!palette_buffer) + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed && !palette_buffer ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL && !palette_buffer ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + return 0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { + STBI_FREE(out); + return stbi__errpuc("bad PNM", "PNM file truncated"); + } + + if (req_comp && req_comp != s->img_n) { + if (ri->bits_per_channel == 16) { + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); + } else { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + } + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + if((value > 214748364) || (value == 214748364 && *c > '7')) + return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + if(*x == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + if (*y == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +#if 0 /* not used in SDL */ +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} +#endif /**/ + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +#if 0 /* not used in SDL */ +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} +#endif /**/ + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/libs/SDL3/src/video/uikit/SDL_uikitmessagebox.m b/libs/SDL3/src/video/uikit/SDL_uikitmessagebox.m index a57b3f7..b96f5fd 100644 --- a/libs/SDL3/src/video/uikit/SDL_uikitmessagebox.m +++ b/libs/SDL3/src/video/uikit/SDL_uikitmessagebox.m @@ -124,31 +124,30 @@ static BOOL UIKit_ShowMessageBoxAlertController(const SDL_MessageBoxData *messag return YES; } -static void UIKit_ShowMessageBoxImpl(const SDL_MessageBoxData *messageboxdata, int *buttonID, int *result) +typedef struct UIKit_ShowMessageBoxData +{ + const SDL_MessageBoxData *messageboxdata; + int *buttonID; + bool result; +} UIKit_ShowMessageBoxData; + +static void SDLCALL UIKit_ShowMessageBoxMainThreadCallback(void *userdata) { @autoreleasepool { - if (UIKit_ShowMessageBoxAlertController(messageboxdata, buttonID)) { - *result = true; - } else { - *result = SDL_SetError("Could not show message box."); - } + UIKit_ShowMessageBoxData *data = (UIKit_ShowMessageBoxData *) userdata; + data->result = UIKit_ShowMessageBoxAlertController(data->messageboxdata, data->buttonID); } } bool UIKit_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) { - @autoreleasepool { - __block int result = true; - - if ([NSThread isMainThread]) { - UIKit_ShowMessageBoxImpl(messageboxdata, buttonID, &result); - } else { - dispatch_sync(dispatch_get_main_queue(), ^{ - UIKit_ShowMessageBoxImpl(messageboxdata, buttonID, &result); - }); - } - return result; + UIKit_ShowMessageBoxData data = { messageboxdata, buttonID, false }; + if (!SDL_RunOnMainThread(UIKit_ShowMessageBoxMainThreadCallback, &data, true)) { + return false; + } else if (!data.result) { + return SDL_SetError("Could not show message box."); } + return true; } #endif // SDL_VIDEO_DRIVER_UIKIT diff --git a/libs/SDL3/src/video/uikit/SDL_uikitvideo.m b/libs/SDL3/src/video/uikit/SDL_uikitvideo.m index 43f0b30..f5326d8 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" @@ -144,7 +145,8 @@ static SDL_VideoDevice *UIKit_CreateDevice(void) VideoBootStrap UIKIT_bootstrap = { UIKITVID_DRIVER_NAME, "SDL UIKit video driver", UIKit_CreateDevice, - UIKit_ShowMessageBox + UIKit_ShowMessageBox, + false }; static bool UIKit_VideoInit(SDL_VideoDevice *_this) @@ -169,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/uikit/SDL_uikitview.m b/libs/SDL3/src/video/uikit/SDL_uikitview.m index ba3b09b..ed649b1 100644 --- a/libs/SDL3/src/video/uikit/SDL_uikitview.m +++ b/libs/SDL3/src/video/uikit/SDL_uikitview.m @@ -240,7 +240,7 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick; int i; SDL_MouseButtonFlags buttons = SDL_GetMouseState(NULL, NULL); - for (i = 0; i < MAX_MOUSE_BUTTONS; ++i) { + for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) { if (buttons & SDL_BUTTON_MASK(i)) { SDL_SendMouseButton(UIKit_GetEventTimestamp([touch timestamp]), sdlwindow, SDL_GLOBAL_MOUSE_ID, (Uint8)i, false); } diff --git a/libs/SDL3/src/video/vita/SDL_vitavideo.c b/libs/SDL3/src/video/vita/SDL_vitavideo.c index 603b093..3455dba 100644 --- a/libs/SDL3/src/video/vita/SDL_vitavideo.c +++ b/libs/SDL3/src/video/vita/SDL_vitavideo.c @@ -153,6 +153,8 @@ static SDL_VideoDevice *VITA_Create(void) device->PumpEvents = VITA_PumpEvents; + device->device_caps = VIDEO_DEVICE_CAPS_FULLSCREEN_ONLY; + return device; } @@ -160,7 +162,8 @@ VideoBootStrap VITA_bootstrap = { "vita", "VITA Video Driver", VITA_Create, - VITA_ShowMessageBox + VITA_ShowMessageBox, + false }; /*****************************************************************************/ diff --git a/libs/SDL3/src/video/vita/SDL_vitavideo.h b/libs/SDL3/src/video/vita/SDL_vitavideo.h index 268bed8..f5b397b 100644 --- a/libs/SDL3/src/video/vita/SDL_vitavideo.h +++ b/libs/SDL3/src/video/vita/SDL_vitavideo.h @@ -60,8 +60,6 @@ extern SDL_Window *Vita_Window; // Display and window functions extern bool VITA_VideoInit(SDL_VideoDevice *_this); extern void VITA_VideoQuit(SDL_VideoDevice *_this); -extern bool VITA_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display); -extern bool VITA_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode); extern bool VITA_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); extern void VITA_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); extern bool VITA_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); diff --git a/libs/SDL3/src/video/vivante/SDL_vivantevideo.c b/libs/SDL3/src/video/vivante/SDL_vivantevideo.c index 269367b..245c496 100644 --- a/libs/SDL3/src/video/vivante/SDL_vivantevideo.c +++ b/libs/SDL3/src/video/vivante/SDL_vivantevideo.c @@ -107,7 +107,8 @@ VideoBootStrap VIVANTE_bootstrap = { "vivante", "Vivante EGL Video Driver", VIVANTE_Create, - NULL // no ShowMessageBox implementation + NULL, // no ShowMessageBox implementation + false }; /*****************************************************************************/ diff --git a/libs/SDL3/src/video/wayland/SDL_waylandcolor.c b/libs/SDL3/src/video/wayland/SDL_waylandcolor.c new file mode 100644 index 0000000..dfc69bb --- /dev/null +++ b/libs/SDL3/src/video/wayland/SDL_waylandcolor.c @@ -0,0 +1,303 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_DRIVER_WAYLAND + +#include "SDL_waylandcolor.h" +#include "SDL_waylandvideo.h" +#include "SDL_waylandwindow.h" +#include "color-management-v1-client-protocol.h" +#include "../../events/SDL_windowevents_c.h" + +typedef struct Wayland_ColorInfoState +{ + struct wp_image_description_v1 *wp_image_description; + struct wp_image_description_info_v1 *wp_image_description_info; + + union + { + SDL_WindowData *window_data; + SDL_DisplayData *display_data; + }; + + enum + { + WAYLAND_COLOR_OBJECT_TYPE_WINDOW, + WAYLAND_COLOR_OBJECT_TYPE_DISPLAY + } object_type; + + SDL_HDROutputProperties HDR; + + // The ICC fd is only valid if the size is non-zero. + int icc_fd; + Uint32 icc_size; + + bool deferred_event_processing; +} Wayland_ColorInfoState; + +static void Wayland_CancelColorInfoRequest(Wayland_ColorInfoState *state) +{ + if (state) { + if (state->wp_image_description_info) { + wp_image_description_info_v1_destroy(state->wp_image_description_info); + state->wp_image_description_info = NULL; + } + if (state->wp_image_description) { + wp_image_description_v1_destroy(state->wp_image_description); + state->wp_image_description = NULL; + } + } +} + +void Wayland_FreeColorInfoState(Wayland_ColorInfoState *state) +{ + if (state) { + Wayland_CancelColorInfoRequest(state); + + switch (state->object_type) { + case WAYLAND_COLOR_OBJECT_TYPE_WINDOW: + state->window_data->color_info_state = NULL; + break; + case WAYLAND_COLOR_OBJECT_TYPE_DISPLAY: + state->display_data->color_info_state = NULL; + break; + } + + SDL_free(state); + } +} + +static void image_description_info_handle_done(void *data, + struct wp_image_description_info_v1 *wp_image_description_info_v1) +{ + Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data; + Wayland_CancelColorInfoRequest(state); + + switch (state->object_type) { + case WAYLAND_COLOR_OBJECT_TYPE_WINDOW: + { + SDL_SetWindowHDRProperties(state->window_data->sdlwindow, &state->HDR, true); + if (state->icc_size) { + state->window_data->icc_fd = state->icc_fd; + state->window_data->icc_size = state->icc_size; + SDL_SendWindowEvent(state->window_data->sdlwindow, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0); + } + } break; + case WAYLAND_COLOR_OBJECT_TYPE_DISPLAY: + { + SDL_copyp(&state->display_data->HDR, &state->HDR); + } break; + } +} + +static void image_description_info_handle_icc_file(void *data, + struct wp_image_description_info_v1 *wp_image_description_info_v1, + int32_t icc, uint32_t icc_size) +{ + Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data; + + state->icc_fd = icc; + state->icc_size = icc_size; +} + +static void image_description_info_handle_primaries(void *data, + struct wp_image_description_info_v1 *wp_image_description_info_v1, + int32_t r_x, int32_t r_y, + int32_t g_x, int32_t g_y, + int32_t b_x, int32_t b_y, + int32_t w_x, int32_t w_y) +{ + // NOP +} + +static void image_description_info_handle_primaries_named(void *data, + struct wp_image_description_info_v1 *wp_image_description_info_v1, + uint32_t primaries) +{ + // NOP +} + +static void image_description_info_handle_tf_power(void *data, + struct wp_image_description_info_v1 *wp_image_description_info_v1, + uint32_t eexp) +{ + // NOP +} + +static void image_description_info_handle_tf_named(void *data, + struct wp_image_description_info_v1 *wp_image_description_info_v1, + uint32_t tf) +{ + // NOP +} + +static void image_description_info_handle_luminances(void *data, + struct wp_image_description_info_v1 *wp_image_description_info_v1, + uint32_t min_lum, + uint32_t max_lum, + uint32_t reference_lum) +{ + Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data; + state->HDR.HDR_headroom = (float)max_lum / (float)reference_lum; +} + +static void image_description_info_handle_target_primaries(void *data, + struct wp_image_description_info_v1 *wp_image_description_info_v1, + int32_t r_x, int32_t r_y, + int32_t g_x, int32_t g_y, + int32_t b_x, int32_t b_y, + int32_t w_x, int32_t w_y) +{ + // NOP +} + +static void image_description_info_handle_target_luminance(void *data, + struct wp_image_description_info_v1 *wp_image_description_info_v1, + uint32_t min_lum, + uint32_t max_lum) +{ + // NOP +} + +static void image_description_info_handle_target_max_cll(void *data, + struct wp_image_description_info_v1 *wp_image_description_info_v1, + uint32_t max_cll) +{ + // NOP +} + +static void image_description_info_handle_target_max_fall(void *data, + struct wp_image_description_info_v1 *wp_image_description_info_v1, + uint32_t max_fall) +{ + // NOP +} + +static const struct wp_image_description_info_v1_listener image_description_info_listener = { + image_description_info_handle_done, + image_description_info_handle_icc_file, + image_description_info_handle_primaries, + image_description_info_handle_primaries_named, + image_description_info_handle_tf_power, + image_description_info_handle_tf_named, + image_description_info_handle_luminances, + image_description_info_handle_target_primaries, + image_description_info_handle_target_luminance, + image_description_info_handle_target_max_cll, + image_description_info_handle_target_max_fall +}; + +static void PumpColorspaceEvents(Wayland_ColorInfoState *state) +{ + SDL_VideoData *vid = SDL_GetVideoDevice()->internal; + + // Run the image description sequence to completion in its own queue. + struct wl_event_queue *queue = WAYLAND_wl_display_create_queue(vid->display); + if (state->deferred_event_processing) { + WAYLAND_wl_proxy_set_queue((struct wl_proxy *)state->wp_image_description_info, queue); + } else { + WAYLAND_wl_proxy_set_queue((struct wl_proxy *)state->wp_image_description, queue); + } + + while (state->wp_image_description) { + WAYLAND_wl_display_dispatch_queue(vid->display, queue); + } + + WAYLAND_wl_event_queue_destroy(queue); + Wayland_FreeColorInfoState(state); +} + +static void image_description_handle_failed(void *data, + struct wp_image_description_v1 *wp_image_description_v1, + uint32_t cause, + const char *msg) +{ + Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data; + Wayland_CancelColorInfoRequest(state); + + if (state->deferred_event_processing) { + Wayland_FreeColorInfoState(state); + } +} + +static void image_description_handle_ready(void *data, + struct wp_image_description_v1 *wp_image_description_v1, + uint32_t identity) +{ + Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data; + + // This will inherit the queue of the factory image description object. + state->wp_image_description_info = wp_image_description_v1_get_information(state->wp_image_description); + wp_image_description_info_v1_add_listener(state->wp_image_description_info, &image_description_info_listener, data); + + if (state->deferred_event_processing) { + PumpColorspaceEvents(state); + } +} + +static const struct wp_image_description_v1_listener image_description_listener = { + image_description_handle_failed, + image_description_handle_ready +}; + +void Wayland_GetColorInfoForWindow(SDL_WindowData *window_data, bool defer_event_processing) +{ + // Cancel any pending request, as it is out-of-date. + Wayland_FreeColorInfoState(window_data->color_info_state); + Wayland_ColorInfoState *state = SDL_calloc(1, sizeof(Wayland_ColorInfoState)); + + if (state) { + window_data->color_info_state = state; + state->window_data = window_data; + state->object_type = WAYLAND_COLOR_OBJECT_TYPE_WINDOW; + state->deferred_event_processing = defer_event_processing; + state->wp_image_description = wp_color_management_surface_feedback_v1_get_preferred(window_data->wp_color_management_surface_feedback); + wp_image_description_v1_add_listener(state->wp_image_description, &image_description_listener, state); + + if (!defer_event_processing) { + PumpColorspaceEvents(state); + } + } +} + +void Wayland_GetColorInfoForOutput(SDL_DisplayData *display_data, bool defer_event_processing) +{ + // Cancel any pending request, as it is out-of-date. + Wayland_FreeColorInfoState(display_data->color_info_state); + Wayland_ColorInfoState *state = SDL_calloc(1, sizeof(Wayland_ColorInfoState)); + + if (state) { + display_data->color_info_state = state; + state->display_data = display_data; + state->object_type = WAYLAND_COLOR_OBJECT_TYPE_DISPLAY; + state->deferred_event_processing = defer_event_processing; + state->wp_image_description = wp_color_management_output_v1_get_image_description(display_data->wp_color_management_output); + wp_image_description_v1_add_listener(state->wp_image_description, &image_description_listener, state); + + if (!defer_event_processing) { + PumpColorspaceEvents(state); + } + } +} + +#endif // SDL_VIDEO_DRIVER_WAYLAND diff --git a/libs/SDL3/src/video/wayland/SDL_waylandcolor.h b/libs/SDL3/src/video/wayland/SDL_waylandcolor.h new file mode 100644 index 0000000..bef5d8c --- /dev/null +++ b/libs/SDL3/src/video/wayland/SDL_waylandcolor.h @@ -0,0 +1,35 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifndef SDL_waylandcolor_h_ +#define SDL_waylandcolor_h_ + +#include "../SDL_sysvideo.h" + +struct Wayland_ColorInfoState; + +extern void Wayland_FreeColorInfoState(struct Wayland_ColorInfoState *state); +extern void Wayland_GetColorInfoForWindow(SDL_WindowData *window_data, bool defer_event_processing); +extern void Wayland_GetColorInfoForOutput(SDL_DisplayData *display_data, bool defer_event_processing); + +#endif // SDL_waylandcolor_h_ diff --git a/libs/SDL3/src/video/wayland/SDL_waylanddatamanager.c b/libs/SDL3/src/video/wayland/SDL_waylanddatamanager.c index 8a7bf65..6f5b32b 100644 --- a/libs/SDL3/src/video/wayland/SDL_waylanddatamanager.c +++ b/libs/SDL3/src/video/wayland/SDL_waylanddatamanager.c @@ -272,7 +272,7 @@ void Wayland_primary_selection_source_set_callback(SDL_WaylandPrimarySelectionSo } } -static void *Wayland_clone_data_buffer(const void *buffer, size_t *len) +static void *Wayland_clone_data_buffer(const void *buffer, const size_t *len) { void *clone = NULL; if (*len > 0 && buffer) { diff --git a/libs/SDL3/src/video/wayland/SDL_waylandevents.c b/libs/SDL3/src/video/wayland/SDL_waylandevents.c index c5aaf4b..6b24bce 100644 --- a/libs/SDL3/src/video/wayland/SDL_waylandevents.c +++ b/libs/SDL3/src/video/wayland/SDL_waylandevents.c @@ -163,16 +163,9 @@ static bool Wayland_SurfaceHasActiveTouches(struct wl_surface *surface) static Uint64 Wayland_GetEventTimestamp(Uint64 nsTimestamp) { - static Uint64 last; - static Uint64 timestamp_offset; + static Uint64 timestamp_offset = 0; const Uint64 now = SDL_GetTicksNS(); - if (nsTimestamp < last) { - // 32-bit timer rollover, bump the offset - timestamp_offset += SDL_MS_TO_NS(0x100000000LLU); - } - last = nsTimestamp; - if (!timestamp_offset) { timestamp_offset = (now - nsTimestamp); } @@ -196,19 +189,24 @@ static const struct zwp_input_timestamps_v1_listener timestamp_listener = { Wayland_input_timestamp_listener }; +static Uint64 Wayland_EventTimestampMSToNS(Uint32 wl_timestamp_ms) +{ + static Uint64 timestamp_offset = 0; + static Uint32 last = 0; + + // Handle 32-bit timer rollover. + if (wl_timestamp_ms < last) { + timestamp_offset += SDL_MS_TO_NS(SDL_UINT64_C(0x100000000)); + } + last = wl_timestamp_ms; + + return SDL_MS_TO_NS(wl_timestamp_ms) + timestamp_offset; +} + static Uint64 Wayland_GetKeyboardTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms) { if (wl_timestamp_ms) { - return Wayland_GetEventTimestamp(input->keyboard_timestamp_ns ? input->keyboard_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms)); - } - - return 0; -} - -static Uint64 Wayland_GetKeyboardTimestampRaw(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms) -{ - if (wl_timestamp_ms) { - return input->keyboard_timestamp_ns ? input->keyboard_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms); + return Wayland_GetEventTimestamp(input->keyboard_timestamp_ns ? input->keyboard_timestamp_ns : Wayland_EventTimestampMSToNS(wl_timestamp_ms)); } return 0; @@ -217,7 +215,7 @@ static Uint64 Wayland_GetKeyboardTimestampRaw(struct SDL_WaylandInput *input, Ui static Uint64 Wayland_GetPointerTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms) { if (wl_timestamp_ms) { - return Wayland_GetEventTimestamp(input->pointer_timestamp_ns ? input->pointer_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms)); + return Wayland_GetEventTimestamp(input->pointer_timestamp_ns ? input->pointer_timestamp_ns : Wayland_EventTimestampMSToNS(wl_timestamp_ms)); } return 0; @@ -226,7 +224,7 @@ static Uint64 Wayland_GetPointerTimestamp(struct SDL_WaylandInput *input, Uint32 Uint64 Wayland_GetTouchTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms) { if (wl_timestamp_ms) { - return Wayland_GetEventTimestamp(input->touch_timestamp_ns ? input->touch_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms)); + return Wayland_GetEventTimestamp(input->touch_timestamp_ns ? input->touch_timestamp_ns : Wayland_EventTimestampMSToNS(wl_timestamp_ms)); } return 0; @@ -269,10 +267,11 @@ void Wayland_CreateCursorShapeDevice(struct SDL_WaylandInput *input) static bool keyboard_repeat_handle(SDL_WaylandKeyboardRepeat *repeat_info, Uint64 elapsed) { bool ret = false; + while (elapsed >= repeat_info->next_repeat_ns) { if (repeat_info->scancode != SDL_SCANCODE_UNKNOWN) { - const Uint64 timestamp = repeat_info->wl_press_time_ns + repeat_info->next_repeat_ns; - SDL_SendKeyboardKeyIgnoreModifiers(Wayland_GetEventTimestamp(timestamp), repeat_info->keyboard_id, repeat_info->key, repeat_info->scancode, true); + const Uint64 timestamp = repeat_info->base_time_ns + repeat_info->next_repeat_ns; + SDL_SendKeyboardKeyIgnoreModifiers(timestamp, repeat_info->keyboard_id, repeat_info->key, repeat_info->scancode, true); } if (repeat_info->text[0]) { SDL_SendKeyboardText(repeat_info->text); @@ -291,8 +290,8 @@ static void keyboard_repeat_clear(SDL_WaylandKeyboardRepeat *repeat_info) repeat_info->is_key_down = false; } -static void keyboard_repeat_set(SDL_WaylandKeyboardRepeat *repeat_info, Uint32 keyboard_id, uint32_t key, Uint64 wl_press_time_ns, - uint32_t scancode, bool has_text, char text[8]) +static void keyboard_repeat_set(SDL_WaylandKeyboardRepeat *repeat_info, Uint32 keyboard_id, uint32_t key, Uint32 wl_press_time_ms, + Uint64 base_time_ns, uint32_t scancode, bool has_text, char text[8]) { if (!repeat_info->is_initialized || !repeat_info->repeat_rate) { return; @@ -300,12 +299,13 @@ static void keyboard_repeat_set(SDL_WaylandKeyboardRepeat *repeat_info, Uint32 k repeat_info->is_key_down = true; repeat_info->keyboard_id = keyboard_id; repeat_info->key = key; - repeat_info->wl_press_time_ns = wl_press_time_ns; + repeat_info->wl_press_time_ms = wl_press_time_ms; + repeat_info->base_time_ns = base_time_ns; repeat_info->sdl_press_time_ns = SDL_GetTicksNS(); repeat_info->next_repeat_ns = SDL_MS_TO_NS(repeat_info->repeat_delay_ms); repeat_info->scancode = scancode; if (has_text) { - SDL_copyp(repeat_info->text, text); + SDL_memcpy(repeat_info->text, text, sizeof(repeat_info->text)); } else { repeat_info->text[0] = '\0'; } @@ -598,11 +598,11 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer, wind->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE; input->buttons_pressed = 0; - SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_LEFT, false); - SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_RIGHT, false); - SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_MIDDLE, false); - SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_X1, false); - SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_X2, false); + SDL_SendMouseButton(0, wind->sdlwindow, input->pointer_id, SDL_BUTTON_LEFT, false); + SDL_SendMouseButton(0, wind->sdlwindow, input->pointer_id, SDL_BUTTON_RIGHT, false); + SDL_SendMouseButton(0, wind->sdlwindow, input->pointer_id, SDL_BUTTON_MIDDLE, false); + SDL_SendMouseButton(0, wind->sdlwindow, input->pointer_id, SDL_BUTTON_X1, false); + SDL_SendMouseButton(0, wind->sdlwindow, input->pointer_id, SDL_BUTTON_X2, false); } /* A pointer leave event may be emitted if the compositor hides the pointer in response to receiving a touch event. @@ -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); @@ -1199,7 +1199,7 @@ static void touch_handler_motion(void *data, struct wl_touch *touch, uint32_t ti const float x = (float)wl_fixed_to_double(fx) / window_data->current.logical_width; const float y = (float)wl_fixed_to_double(fy) / window_data->current.logical_height; - SDL_SendTouchMotion(Wayland_GetPointerTimestamp(input, timestamp), (SDL_TouchID)(uintptr_t)touch, + SDL_SendTouchMotion(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(uintptr_t)touch, (SDL_FingerID)(id + 1), window_data->sdlwindow, x, y, 1.0f); } } @@ -1717,7 +1717,7 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, window->keyboard_device = input; // Restore the keyboard focus to the child popup that was holding it - SDL_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window->sdlwindow); + SDL_SetKeyboardFocus(window->sdlwindow->keyboard_focus ? window->sdlwindow->keyboard_focus : window->sdlwindow); #ifdef SDL_USE_IME if (!input->text_input) { @@ -1855,7 +1855,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, char text[8]; bool has_text = false; bool handled_by_ime = false; - const Uint64 timestamp_raw_ns = Wayland_GetKeyboardTimestampRaw(input, time); + const Uint64 timestamp_ns = Wayland_GetKeyboardTimestamp(input, time); Wayland_UpdateImplicitGrabSerial(input, serial); @@ -1871,7 +1871,8 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, * Using SDL_GetTicks would be wrong, as it would report when the release event is processed, * which may be off if the application hasn't pumped events for a while. */ - keyboard_repeat_handle(&input->keyboard_repeat, timestamp_raw_ns - input->keyboard_repeat.wl_press_time_ns); + const Uint64 elapsed = SDL_MS_TO_NS(time - input->keyboard_repeat.wl_press_time_ms); + keyboard_repeat_handle(&input->keyboard_repeat, elapsed); keyboard_repeat_clear(&input->keyboard_repeat); } keyboard_input_get_text(text, input, key, false, &handled_by_ime); @@ -1879,18 +1880,17 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, const SDL_Scancode scancode = Wayland_GetScancodeForKey(input, key); Wayland_HandleModifierKeys(input, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED); - Uint64 timestamp = Wayland_GetKeyboardTimestamp(input, time); - SDL_SendKeyboardKeyIgnoreModifiers(timestamp, input->keyboard_id, key, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED); + SDL_SendKeyboardKeyIgnoreModifiers(timestamp_ns, 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); } } if (input->xkb.keymap && WAYLAND_xkb_keymap_key_repeats(input->xkb.keymap, key + 8)) { - keyboard_repeat_set(&input->keyboard_repeat, input->keyboard_id, key, timestamp_raw_ns, scancode, has_text, text); + keyboard_repeat_set(&input->keyboard_repeat, input->keyboard_id, key, time, timestamp_ns, scancode, has_text, text); } } } @@ -2563,8 +2563,6 @@ static void primary_selection_device_handle_selection(void *data, struct zwp_pri SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, ". In zwp_primary_selection_device_v1_listener . primary_selection_device_handle_selection on primary_selection_offer 0x%08x", (id ? WAYLAND_wl_proxy_get_id((struct wl_proxy *)id) : -1)); - - notifyFromMimes(offer ? &offer->mimes : NULL); } static const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = { @@ -2953,7 +2951,7 @@ static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool return; // Not a pen we report on. } - const Uint64 timestamp = Wayland_GetEventTimestamp(SDL_MS_TO_NS(time)); + const Uint64 timestamp = Wayland_GetEventTimestamp(Wayland_EventTimestampMSToNS(time)); const SDL_PenID instance_id = sdltool->instance_id; SDL_Window *window = sdltool->tool_focus; @@ -3306,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. */ @@ -3355,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_waylandevents_c.h b/libs/SDL3/src/video/wayland/SDL_waylandevents_c.h index 6158882..d961b37 100644 --- a/libs/SDL3/src/video/wayland/SDL_waylandevents_c.h +++ b/libs/SDL3/src/video/wayland/SDL_waylandevents_c.h @@ -49,17 +49,18 @@ typedef struct SDL_WaylandTabletInput typedef struct { - int32_t repeat_rate; // Repeat rate in range of [1, 1000] character(s) per second - int32_t repeat_delay_ms; // Time to first repeat event in milliseconds - Uint32 keyboard_id; // ID of the source keyboard. + Sint32 repeat_rate; // Repeat rate in range of [1, 1000] character(s) per second + Sint32 repeat_delay_ms; // Time to first repeat event in milliseconds + Uint32 keyboard_id; // ID of the source keyboard. bool is_initialized; bool is_key_down; - uint32_t key; - Uint64 wl_press_time_ns; // Key press time as reported by the Wayland API + Uint32 key; + Uint32 wl_press_time_ms; // Key press time as reported by the Wayland API in milliseconds + Uint64 base_time_ns; // Key press time as reported by the Wayland API in nanoseconds Uint64 sdl_press_time_ns; // Key press time expressed in SDL ticks Uint64 next_repeat_ns; // Next repeat event in nanoseconds - uint32_t scancode; + Uint32 scancode; char text[8]; } SDL_WaylandKeyboardRepeat; diff --git a/libs/SDL3/src/video/wayland/SDL_waylandkeyboard.c b/libs/SDL3/src/video/wayland/SDL_waylandkeyboard.c index df1628c..6712f9d 100644 --- a/libs/SDL3/src/video/wayland/SDL_waylandkeyboard.c +++ b/libs/SDL3/src/video/wayland/SDL_waylandkeyboard.c @@ -58,7 +58,7 @@ bool Wayland_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_Prop if (internal->text_input_manager) { if (input && input->text_input) { - const SDL_Rect *rect = &input->text_input->cursor_rect; + const SDL_Rect *rect = &input->text_input->text_input_rect; enum zwp_text_input_v3_content_hint hint = ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE; enum zwp_text_input_v3_content_purpose purpose; @@ -125,12 +125,21 @@ bool Wayland_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_Prop // Now that it's enabled, set the input properties zwp_text_input_v3_set_content_type(input->text_input->text_input, hint, purpose); if (!SDL_RectEmpty(rect)) { - // This gets reset on enable so we have to cache it + SDL_WindowData *wind = window->internal; + const SDL_Rect scaled_rect = { + (int)SDL_floor(window->text_input_rect.x / wind->pointer_scale.x), + (int)SDL_floor(window->text_input_rect.y / wind->pointer_scale.y), + (int)SDL_ceil(window->text_input_rect.w / wind->pointer_scale.x), + (int)SDL_ceil(window->text_input_rect.h / wind->pointer_scale.y) + }; + const int scaled_cursor = (int)SDL_floor(window->text_input_cursor / wind->pointer_scale.x); + + // Clamp the x value so it doesn't run too far past the end of the text input area. zwp_text_input_v3_set_cursor_rectangle(input->text_input->text_input, - rect->x, - rect->y, - rect->w, - rect->h); + SDL_min(scaled_rect.x + scaled_cursor, scaled_rect.x + scaled_rect.w), + scaled_rect.y, + 1, + scaled_rect.h); } zwp_text_input_v3_commit(input->text_input->text_input); } @@ -174,13 +183,24 @@ bool Wayland_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window) if (internal->text_input_manager) { struct SDL_WaylandInput *input = internal->input; if (input && input->text_input) { - if (!SDL_RectsEqual(&window->text_input_rect, &input->text_input->cursor_rect)) { - SDL_copyp(&input->text_input->cursor_rect, &window->text_input_rect); + SDL_WindowData *wind = window->internal; + const SDL_Rect scaled_rect = { + (int)SDL_floor(window->text_input_rect.x / wind->pointer_scale.x), + (int)SDL_floor(window->text_input_rect.y / wind->pointer_scale.y), + (int)SDL_ceil(window->text_input_rect.w / wind->pointer_scale.x), + (int)SDL_ceil(window->text_input_rect.h / wind->pointer_scale.y) + }; + const int scaled_cursor = (int)SDL_floor(window->text_input_cursor / wind->pointer_scale.x); + if (!SDL_RectsEqual(&scaled_rect, &input->text_input->text_input_rect) || scaled_cursor != input->text_input->text_input_cursor) { + SDL_copyp(&input->text_input->text_input_rect, &scaled_rect); + input->text_input->text_input_cursor = scaled_cursor; + + // Clamp the x value so it doesn't run too far past the end of the text input area. zwp_text_input_v3_set_cursor_rectangle(input->text_input->text_input, - window->text_input_rect.x, - window->text_input_rect.y, - window->text_input_rect.w, - window->text_input_rect.h); + SDL_min(scaled_rect.x + scaled_cursor, scaled_rect.x + scaled_rect.w), + scaled_rect.y, + 1, + scaled_rect.h); zwp_text_input_v3_commit(input->text_input->text_input); } } diff --git a/libs/SDL3/src/video/wayland/SDL_waylandkeyboard.h b/libs/SDL3/src/video/wayland/SDL_waylandkeyboard.h index f570edb..a1ea076 100644 --- a/libs/SDL3/src/video/wayland/SDL_waylandkeyboard.h +++ b/libs/SDL3/src/video/wayland/SDL_waylandkeyboard.h @@ -26,7 +26,8 @@ typedef struct SDL_WaylandTextInput { struct zwp_text_input_v3 *text_input; - SDL_Rect cursor_rect; + SDL_Rect text_input_rect; + int text_input_cursor; bool has_preedit; } SDL_WaylandTextInput; diff --git a/libs/SDL3/src/video/wayland/SDL_waylandmouse.c b/libs/SDL3/src/video/wayland/SDL_waylandmouse.c index 968cb6f..d464cf3 100644 --- a/libs/SDL3/src/video/wayland/SDL_waylandmouse.c +++ b/libs/SDL3/src/video/wayland/SDL_waylandmouse.c @@ -598,6 +598,13 @@ static SDL_Cursor *Wayland_CreateDefaultCursor(void) static void Wayland_FreeCursorData(SDL_CursorData *d) { + SDL_VideoDevice *vd = SDL_GetVideoDevice(); + struct SDL_WaylandInput *input = vd->internal->input; + + if (input->current_cursor == d) { + input->current_cursor = NULL; + } + // Buffers for system cursors must not be destroyed. if (d->is_system_cursor) { if (d->cursor_data.system.frame_callback) { @@ -896,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_waylandvideo.c b/libs/SDL3/src/video/wayland/SDL_waylandvideo.c index fc8edc1..d6cd949 100644 --- a/libs/SDL3/src/video/wayland/SDL_waylandvideo.c +++ b/libs/SDL3/src/video/wayland/SDL_waylandvideo.c @@ -27,6 +27,7 @@ #include "../../events/SDL_events_c.h" #include "SDL_waylandclipboard.h" +#include "SDL_waylandcolor.h" #include "SDL_waylandevents_c.h" #include "SDL_waylandkeyboard.h" #include "SDL_waylandmessagebox.h" @@ -63,6 +64,7 @@ #include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-shell-client-protocol.h" #include "xdg-toplevel-icon-v1-client-protocol.h" +#include "color-management-v1-client-protocol.h" #ifdef HAVE_LIBDECOR_H #include @@ -249,7 +251,7 @@ static int SDLCALL Wayland_DisplayPositionCompare(const void *a, const void *b) * The primary is determined by the following criteria, in order: * - Landscape is preferred over portrait * - The highest native resolution - * - TODO: A higher HDR range is preferred + * - A higher HDR range is preferred * - Higher refresh is preferred (ignoring small differences) * - Lower scale values are preferred (larger display) */ @@ -271,6 +273,7 @@ static int Wayland_GetPrimaryDisplay(SDL_VideoData *vid) int best_width = 0; int best_height = 0; double best_scale = 0.0; + float best_headroom = 0.0f; int best_refresh = 0; bool best_is_landscape = false; int best_index = 0; @@ -286,11 +289,15 @@ static int Wayland_GetPrimaryDisplay(SDL_VideoData *vid) if (d->pixel_width > best_width || d->pixel_height > best_height) { have_new_best = true; } else if (d->pixel_width == best_width && d->pixel_height == best_height) { - if (d->refresh - best_refresh > REFRESH_DELTA) { // Favor a higher refresh rate, but ignore small differences (e.g. 59.97 vs 60.1) - have_new_best = true; - } else if (d->scale_factor < best_scale && SDL_abs(d->refresh - best_refresh) <= REFRESH_DELTA) { - // Prefer a lower scale display if the difference in refresh rate is small. + if (d->HDR.HDR_headroom > best_headroom) { // Favor a higher HDR luminance range have_new_best = true; + } else if (d->HDR.HDR_headroom == best_headroom) { + if (d->refresh - best_refresh > REFRESH_DELTA) { // Favor a higher refresh rate, but ignore small differences (e.g. 59.97 vs 60.1) + have_new_best = true; + } else if (d->scale_factor < best_scale && SDL_abs(d->refresh - best_refresh) <= REFRESH_DELTA) { + // Prefer a lower scale display if the difference in refresh rate is small. + have_new_best = true; + } } } } @@ -299,6 +306,7 @@ static int Wayland_GetPrimaryDisplay(SDL_VideoData *vid) best_width = d->pixel_width; best_height = d->pixel_height; best_scale = d->scale_factor; + best_headroom = d->HDR.HDR_headroom; best_refresh = d->refresh; best_is_landscape = is_landscape; best_index = i; @@ -438,9 +446,6 @@ static void Wayland_DeleteDevice(SDL_VideoDevice *device) WAYLAND_wl_display_disconnect(data->display); SDL_ClearProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER); } - if (device->wakeup_lock) { - SDL_DestroyMutex(device->wakeup_lock); - } SDL_free(data); SDL_free(device); SDL_WAYLAND_UnloadSymbols(); @@ -581,7 +586,6 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols) } device->internal = data; - device->wakeup_lock = SDL_CreateMutex(); // Set the function pointers device->VideoInit = Wayland_VideoInit; @@ -630,6 +634,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols) device->SetWindowIcon = Wayland_SetWindowIcon; device->GetWindowSizeInPixels = Wayland_GetWindowSizeInPixels; device->GetWindowContentScale = Wayland_GetWindowContentScale; + device->GetWindowICCProfile = Wayland_GetWindowICCProfile; device->GetDisplayForWindow = Wayland_GetDisplayForWindow; device->DestroyWindow = Wayland_DestroyWindow; device->SetWindowHitTest = Wayland_SetWindowHitTest; @@ -637,6 +642,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols) device->HasScreenKeyboardSupport = Wayland_HasScreenKeyboardSupport; device->ShowWindowSystemMenu = Wayland_ShowWindowSystemMenu; device->SyncWindow = Wayland_SyncWindow; + device->SetWindowFocusable = Wayland_SetWindowFocusable; #ifdef SDL_USE_LIBDBUS if (SDL_SystemTheme_Init()) @@ -685,13 +691,15 @@ static SDL_VideoDevice *Wayland_Fallback_CreateDevice(void) VideoBootStrap Wayland_preferred_bootstrap = { WAYLANDVID_DRIVER_NAME, "SDL Wayland video driver", Wayland_Preferred_CreateDevice, - Wayland_ShowMessageBox + Wayland_ShowMessageBox, + true }; VideoBootStrap Wayland_bootstrap = { WAYLANDVID_DRIVER_NAME, "SDL Wayland video driver", Wayland_Fallback_CreateDevice, - Wayland_ShowMessageBox + Wayland_ShowMessageBox, + false }; static void xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output, @@ -716,7 +724,7 @@ static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xd static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) { - SDL_DisplayData *internal = (void *)data; + SDL_DisplayData *internal = data; /* * xdg-output.done events are deprecated and only apply below version 3 of the protocol. @@ -1048,6 +1056,8 @@ static void display_handle_done(void *data, AddEmulatedModes(internal, native_mode.w, native_mode.h); } + SDL_SetDisplayHDRProperties(dpy, &internal->HDR); + if (internal->display == 0) { // First time getting display info, initialize the VideoDisplay if (internal->physical_width_mm >= internal->physical_height_mm) { @@ -1105,6 +1115,18 @@ static const struct wl_output_listener output_listener = { display_handle_description // Version 4 }; +static void handle_output_image_description_changed(void *data, + struct wp_color_management_output_v1 *wp_color_management_output_v1) +{ + SDL_DisplayData *display = (SDL_DisplayData *)data; + // wl_display.done is called after this event, so the display HDR status will be updated there. + Wayland_GetColorInfoForOutput(display, false); +} + +static const struct wp_color_management_output_v1_listener wp_color_management_output_listener = { + handle_output_image_description_changed +}; + static bool Wayland_add_display(SDL_VideoData *d, uint32_t id, uint32_t version) { struct wl_output *output; @@ -1134,6 +1156,11 @@ static bool Wayland_add_display(SDL_VideoData *d, uint32_t id, uint32_t version) data->xdg_output = zxdg_output_manager_v1_get_xdg_output(data->videodata->xdg_output_manager, output); zxdg_output_v1_add_listener(data->xdg_output, &xdg_output_listener, data); } + if (data->videodata->wp_color_manager_v1) { + data->wp_color_management_output = wp_color_manager_v1_get_output(data->videodata->wp_color_manager_v1, output); + wp_color_management_output_v1_add_listener(data->wp_color_management_output, &wp_color_management_output_listener, data); + Wayland_GetColorInfoForOutput(data, true); + } return true; } @@ -1151,6 +1178,11 @@ static void Wayland_free_display(SDL_VideoDisplay *display, bool send_event) SDL_free(display_data->wl_output_name); + if (display_data->wp_color_management_output) { + Wayland_FreeColorInfoState(display_data->color_info_state); + wp_color_management_output_v1_destroy(display_data->wp_color_management_output); + } + if (display_data->xdg_output) { zxdg_output_v1_destroy(display_data->xdg_output); } @@ -1178,13 +1210,23 @@ static void Wayland_FinalizeDisplays(SDL_VideoData *vid) static void Wayland_init_xdg_output(SDL_VideoData *d) { - for(int i = 0; i < d->output_count; ++i) { + for (int i = 0; i < d->output_count; ++i) { SDL_DisplayData *disp = d->output_list[i]; disp->xdg_output = zxdg_output_manager_v1_get_xdg_output(disp->videodata->xdg_output_manager, disp->output); zxdg_output_v1_add_listener(disp->xdg_output, &xdg_output_listener, disp); } } +static void Wayland_InitColorManager(SDL_VideoData *d) +{ + for (int i = 0; i < d->output_count; ++i) { + SDL_DisplayData *disp = d->output_list[i]; + disp->wp_color_management_output = wp_color_manager_v1_get_output(disp->videodata->wp_color_manager_v1, disp->output); + wp_color_management_output_v1_add_listener(disp->wp_color_management_output, &wp_color_management_output_listener, disp); + Wayland_GetColorInfoForOutput(disp, true); + } +} + static void handle_ping_xdg_wm_base(void *data, struct xdg_wm_base *xdg, uint32_t serial) { xdg_wm_base_pong(xdg, serial); @@ -1278,6 +1320,9 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint d->xdg_toplevel_icon_manager_v1 = wl_registry_bind(d->registry, id, &xdg_toplevel_icon_manager_v1_interface, 1); } else if (SDL_strcmp(interface, "frog_color_management_factory_v1") == 0) { d->frog_color_management_factory_v1 = wl_registry_bind(d->registry, id, &frog_color_management_factory_v1_interface, 1); + } else if (SDL_strcmp(interface, "wp_color_manager_v1") == 0) { + d->wp_color_manager_v1 = wl_registry_bind(d->registry, id, &wp_color_manager_v1_interface, 1); + Wayland_InitColorManager(d); } } @@ -1366,10 +1411,16 @@ bool Wayland_VideoInit(SDL_VideoDevice *_this) // First roundtrip to receive all registry objects. WAYLAND_wl_display_roundtrip(data->display); - // Require viewports for display scaling. - if (data->scale_to_display_enabled && !data->viewporter) { - SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Display scaling requires the missing 'wp_viewporter' protocol: disabling"); - data->scale_to_display_enabled = false; + // Require viewports and xdg-output for display scaling. + if (data->scale_to_display_enabled) { + if (!data->viewporter) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Display scaling requires the missing 'wp_viewporter' protocol: disabling"); + data->scale_to_display_enabled = false; + } + if (!data->xdg_output_manager) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Display scaling requires the missing 'zxdg_output_manager_v1' protocol: disabling"); + data->scale_to_display_enabled = false; + } } // Now that we have all the protocols, load libdecor if applicable @@ -1559,6 +1610,11 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this) data->frog_color_management_factory_v1 = NULL; } + if (data->wp_color_manager_v1) { + wp_color_manager_v1_destroy(data->wp_color_manager_v1); + data->wp_color_manager_v1 = NULL; + } + if (data->compositor) { wl_compositor_destroy(data->compositor); data->compositor = NULL; diff --git a/libs/SDL3/src/video/wayland/SDL_waylandvideo.h b/libs/SDL3/src/video/wayland/SDL_waylandvideo.h index 44f5f35..8cde64c 100644 --- a/libs/SDL3/src/video/wayland/SDL_waylandvideo.h +++ b/libs/SDL3/src/video/wayland/SDL_waylandvideo.h @@ -83,6 +83,7 @@ struct SDL_VideoData struct wp_alpha_modifier_v1 *wp_alpha_modifier_v1; struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1; struct frog_color_management_factory_v1 *frog_color_management_factory_v1; + struct wp_color_manager_v1 *wp_color_manager_v1; struct zwp_tablet_manager_v2 *tablet_manager; struct xkb_context *xkb_context; @@ -102,6 +103,7 @@ struct SDL_DisplayData SDL_VideoData *videodata; struct wl_output *output; struct zxdg_output_v1 *xdg_output; + struct wp_color_management_output_v1 *wp_color_management_output; char *wl_output_name; double scale_factor; uint32_t registry_id; @@ -111,9 +113,12 @@ struct SDL_DisplayData SDL_DisplayOrientation orientation; int physical_width_mm, physical_height_mm; bool has_logical_position, has_logical_size; + bool running_colorspace_event_queue; + SDL_HDROutputProperties HDR; SDL_DisplayID display; SDL_VideoDisplay placeholder; int wl_output_done_count; + struct Wayland_ColorInfoState *color_info_state; }; // Needed here to get wl_surface declaration, fixes GitHub#4594 diff --git a/libs/SDL3/src/video/wayland/SDL_waylandwindow.c b/libs/SDL3/src/video/wayland/SDL_waylandwindow.c index ebf79a2..75d18bd 100644 --- a/libs/SDL3/src/video/wayland/SDL_waylandwindow.c +++ b/libs/SDL3/src/video/wayland/SDL_waylandwindow.c @@ -23,6 +23,8 @@ #ifdef SDL_VIDEO_DRIVER_WAYLAND +#include + #include "../SDL_sysvideo.h" #include "../../events/SDL_events_c.h" #include "../../core/unix/SDL_appid.h" @@ -31,6 +33,7 @@ #include "SDL_waylandwindow.h" #include "SDL_waylandvideo.h" #include "../../SDL_hints_c.h" +#include "SDL_waylandcolor.h" #include "alpha-modifier-v1-client-protocol.h" #include "xdg-shell-client-protocol.h" @@ -43,6 +46,7 @@ #include "xdg-dialog-v1-client-protocol.h" #include "frog-color-management-v1-client-protocol.h" #include "xdg-toplevel-icon-v1-client-protocol.h" +#include "color-management-v1-client-protocol.h" #ifdef HAVE_LIBDECOR_H #include @@ -177,7 +181,7 @@ static void SetMinMaxDimensions(SDL_Window *window) #ifdef HAVE_LIBDECOR_H if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - if (!wind->shell_surface.libdecor.initial_configure_seen || !wind->shell_surface.libdecor.frame) { + if (!wind->shell_surface.libdecor.frame) { return; // Can't do anything yet, wait for ShowWindow } /* No need to change these values if the window is non-resizable, @@ -194,7 +198,7 @@ static void SetMinMaxDimensions(SDL_Window *window) } else #endif if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { - if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) { + if (!wind->shell_surface.xdg.toplevel.xdg_toplevel) { return; // Can't do anything yet, wait for ShowWindow } xdg_toplevel_set_min_size(wind->shell_surface.xdg.toplevel.xdg_toplevel, @@ -211,32 +215,32 @@ static void EnsurePopupPositionIsValid(SDL_Window *window, int *x, int *y) int adj_count = 0; /* Per the xdg-positioner spec, child popup windows must intersect or at - * least be partially adjacent to the parent window. + * least be partially adjoining the parent window. * * Failure to ensure this on a compositor that enforces this restriction * can result in behavior ranging from the window being spuriously closed * to a protocol violation. */ - if (*x + window->w < 0) { + if (*x + window->w <= 0) { *x = -window->w; ++adj_count; } - if (*y + window->h < 0) { + if (*y + window->h <= 0) { *y = -window->h; ++adj_count; } - if (*x > window->parent->w) { + if (*x >= window->parent->w) { *x = window->parent->w; ++adj_count; } - if (*y > window->parent->h) { + if (*y >= window->parent->h) { *y = window->parent->h; ++adj_count; } /* If adjustment was required on the x and y axes, the popup is aligned with - * the parent corner-to-corner and is neither overlapping nor adjacent, so it - * must be nudged by 1 to be considered adjacent. + * the parent corner-to-corner and is neither overlapping nor adjoining, so it + * must be nudged by 1 to be considered adjoining. */ if (adj_count > 1) { *x += *x < 0 ? 1 : -1; @@ -703,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. */ @@ -746,7 +753,9 @@ static void handle_configure_xdg_shell_surface(void *data, struct xdg_surface *x xdg_surface_ack_configure(xdg, serial); } - wind->shell_surface.xdg.initial_configure_seen = true; + if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) { + wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME; + } } static const struct xdg_surface_listener shell_surface_listener_xdg = { @@ -963,10 +972,6 @@ static void handle_configure_xdg_toplevel(void *data, wind->active = active; window->tiled = tiled; wind->resizing = resizing; - - if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) { - wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME; - } } static void handle_close_xdg_toplevel(void *data, struct xdg_toplevel *xdg_toplevel) @@ -1115,9 +1120,7 @@ static void handle_configure_zxdg_decoration(void *data, WAYLAND_wl_display_roundtrip(internal->waylandData->display); Wayland_HideWindow(device, window); - SDL_zero(internal->shell_surface); internal->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR; - Wayland_ShowWindow(device, window); } } @@ -1399,11 +1402,8 @@ static void decoration_frame_configure(struct libdecor_frame *frame, libdecor_state_free(state); } - if (!wind->shell_surface.libdecor.initial_configure_seen) { - LibdecorGetMinContentSize(frame, &wind->system_limits.min_width, &wind->system_limits.min_height); - wind->shell_surface.libdecor.initial_configure_seen = true; - } if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) { + LibdecorGetMinContentSize(frame, &wind->system_limits.min_width, &wind->system_limits.min_height); wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME; } @@ -1647,7 +1647,19 @@ static const struct frog_color_managed_surface_listener frog_surface_listener = frog_preferred_metadata_handler }; -static void SetKeyboardFocus(SDL_Window *window, bool set_focus) +static void feedback_surface_preferred_changed(void *data, + struct wp_color_management_surface_feedback_v1 *wp_color_management_surface_feedback_v1, + uint32_t identity) +{ + SDL_WindowData *wind = (SDL_WindowData *)data; + Wayland_GetColorInfoForWindow(wind, false); +} + +static const struct wp_color_management_surface_feedback_v1_listener color_management_surface_feedback_listener = { + feedback_surface_preferred_changed +}; + +static void Wayland_SetKeyboardFocus(SDL_Window *window, bool set_focus) { SDL_Window *toplevel = window; @@ -1656,7 +1668,7 @@ static void SetKeyboardFocus(SDL_Window *window, bool set_focus) toplevel = toplevel->parent; } - toplevel->internal->keyboard_focus = window; + toplevel->keyboard_focus = window; if (set_focus && !window->is_hiding && !window->is_destroying) { SDL_SetKeyboardFocus(window); @@ -1800,12 +1812,10 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) } } - /* The window was hidden, but the sync point hasn't yet been reached. - * Pump events to avoid a possible protocol violation. - */ - if (data->show_hide_sync_required) { + // Always roundtrip to ensure there are no pending buffer attachments. + do { WAYLAND_wl_display_roundtrip(c->display); - } + } while (data->show_hide_sync_required); data->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE; @@ -1838,7 +1848,10 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) } else { libdecor_frame_set_app_id(data->shell_surface.libdecor.frame, data->app_id); libdecor_frame_map(data->shell_surface.libdecor.frame); - libdecor_frame_set_visibility(data->shell_surface.libdecor.frame, !(window->flags & SDL_WINDOW_BORDERLESS)); + if (window->flags & SDL_WINDOW_BORDERLESS) { + // Note: Calling this with 'true' immediately after mapping will cause the libdecor Cairo plugin to crash. + libdecor_frame_set_visibility(data->shell_surface.libdecor.frame, false); + } if (c->zxdg_exporter_v2) { data->exported = zxdg_exporter_v2_export_toplevel(c->zxdg_exporter_v2, data->surface); @@ -1883,8 +1896,9 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) data->shell_surface.xdg.popup.xdg_positioner = xdg_wm_base_create_positioner(c->shell.xdg); xdg_positioner_set_anchor(data->shell_surface.xdg.popup.xdg_positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); xdg_positioner_set_anchor_rect(data->shell_surface.xdg.popup.xdg_positioner, 0, 0, parent->internal->current.logical_width, parent->internal->current.logical_width); - xdg_positioner_set_constraint_adjustment(data->shell_surface.xdg.popup.xdg_positioner, - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y); + + const Uint32 constraint = window->constrain_popup ? (XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y) : XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE; + xdg_positioner_set_constraint_adjustment(data->shell_surface.xdg.popup.xdg_positioner, constraint); xdg_positioner_set_gravity(data->shell_surface.xdg.popup.xdg_positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); xdg_positioner_set_size(data->shell_surface.xdg.popup.xdg_positioner, data->current.logical_width, data->current.logical_height); @@ -1913,8 +1927,8 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) wl_region_add(region, 0, 0, 0, 0); wl_surface_set_input_region(data->surface, region); wl_region_destroy(region); - } else if (window->flags & SDL_WINDOW_POPUP_MENU) { - SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus()); + } else if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + Wayland_SetKeyboardFocus(window, true); } SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_POPUP_POINTER, data->shell_surface.xdg.popup.xdg_popup); @@ -1962,7 +1976,7 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) #ifdef HAVE_LIBDECOR_H if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { if (data->shell_surface.libdecor.frame) { - while (!data->shell_surface.libdecor.initial_configure_seen) { + while (data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) { WAYLAND_wl_display_flush(c->display); WAYLAND_wl_display_dispatch(c->display); } @@ -1976,7 +1990,7 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) */ wl_surface_commit(data->surface); if (data->shell_surface.xdg.surface) { - while (!data->shell_surface.xdg.initial_configure_seen) { + while (data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) { WAYLAND_wl_display_flush(c->display); WAYLAND_wl_display_dispatch(c->display); } @@ -2033,6 +2047,10 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) struct wl_callback *cb = wl_display_sync(_this->internal->display); wl_callback_add_listener(cb, &show_hide_sync_listener, (void*)((uintptr_t)window->id)); + data->showing_window = true; + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_SHOWN, 0, 0); + data->showing_window = false; + // Send an exposure event to signal that the client should draw. if (data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME) { SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); @@ -2057,21 +2075,10 @@ static void Wayland_ReleasePopup(SDL_VideoDevice *_this, SDL_Window *popup) return; } - if (popup->flags & SDL_WINDOW_POPUP_MENU) { - SDL_Window *new_focus = popup->parent; - bool set_focus = popup == SDL_GetKeyboardFocus(); - - // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed. - while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) { - new_focus = new_focus->parent; - - // If some window in the chain currently had focus, set it to the new lowest-level window. - if (!set_focus) { - set_focus = new_focus == SDL_GetKeyboardFocus(); - } - } - - SetKeyboardFocus(new_focus, set_focus); + if ((popup->flags & SDL_WINDOW_POPUP_MENU) && !(popup->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(popup, &new_focus); + Wayland_SetKeyboardFocus(new_focus, set_focus); } xdg_popup_destroy(popupdata->shell_surface.xdg.popup.xdg_popup); @@ -2109,12 +2116,6 @@ void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) wind->server_decoration = NULL; } - // Be sure to detach after this is done, otherwise ShowWindow crashes! - if (wind->shell_surface_type != WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { - wl_surface_attach(wind->surface, NULL, 0, 0); - wl_surface_commit(wind->surface); - } - // Clean up the export handle. if (wind->exported) { zxdg_exported_v2_destroy(wind->exported); @@ -2132,26 +2133,31 @@ void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { if (wind->shell_surface.libdecor.frame) { libdecor_frame_unref(wind->shell_surface.libdecor.frame); - wind->shell_surface.libdecor.frame = NULL; SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, NULL); SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, NULL); } } else #endif + { if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { - Wayland_ReleasePopup(_this, window); - } else if (wind->shell_surface.xdg.toplevel.xdg_toplevel) { - xdg_toplevel_destroy(wind->shell_surface.xdg.toplevel.xdg_toplevel); - wind->shell_surface.xdg.toplevel.xdg_toplevel = NULL; - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, NULL); - } - if (wind->shell_surface.xdg.surface) { - xdg_surface_destroy(wind->shell_surface.xdg.surface); - wind->shell_surface.xdg.surface = NULL; - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, NULL); + Wayland_ReleasePopup(_this, window); + } else if (wind->shell_surface.xdg.toplevel.xdg_toplevel) { + xdg_toplevel_destroy(wind->shell_surface.xdg.toplevel.xdg_toplevel); + SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, NULL); + } + + if (wind->shell_surface.xdg.surface) { + xdg_surface_destroy(wind->shell_surface.xdg.surface); + SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, NULL); + } } + // Attach a null buffer to unmap the surface. + wl_surface_attach(wind->surface, NULL, 0, 0); + wl_surface_commit(wind->surface); + + SDL_zero(wind->shell_surface); wind->show_hide_sync_required = true; struct wl_callback *cb = wl_display_sync(_this->internal->display); wl_callback_add_listener(cb, &show_hide_sync_listener, (void*)((uintptr_t)window->id)); @@ -2255,6 +2261,11 @@ SDL_FullscreenResult Wayland_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Win return SDL_FULLSCREEN_FAILED; } + // Drop fullscreen leave requests when showing the window. + if (wind->showing_window && fullscreen == SDL_FULLSCREEN_OP_LEAVE) { + return SDL_FULLSCREEN_SUCCEEDED; + } + if (wind->show_hide_sync_required) { WAYLAND_wl_display_roundtrip(_this->internal->display); } @@ -2280,8 +2291,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) { /* @@ -2309,6 +2320,11 @@ void Wayland_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *wind = window->internal; + // Drop restore requests when showing the window. + if (wind->showing_window) { + return; + } + // Not currently fullscreen or maximized, and no state pending; nothing to do. if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED)) && !wind->fullscreen_deadline_count && !wind->maximized_restored_deadline_count) { @@ -2594,7 +2610,11 @@ bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Proper } if (!custom_surface_role) { - if (c->frog_color_management_factory_v1) { + if (c->wp_color_manager_v1) { + data->wp_color_management_surface_feedback = wp_color_manager_v1_get_surface_feedback(c->wp_color_manager_v1, data->surface); + wp_color_management_surface_feedback_v1_add_listener(data->wp_color_management_surface_feedback, &color_management_surface_feedback_listener, data); + Wayland_GetColorInfoForWindow(data, true); + } else if (c->frog_color_management_factory_v1) { data->frog_color_managed_surface = frog_color_management_factory_v1_get_color_managed_surface(c->frog_color_management_factory_v1, data->surface); frog_color_managed_surface_add_listener(data->frog_color_managed_surface, &frog_surface_listener, data); } @@ -2837,28 +2857,56 @@ bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surfa struct xdg_toplevel *toplevel = NULL; if (!_this->internal->xdg_toplevel_icon_manager_v1) { - return SDL_SetError("wayland: cannot set icon; xdg_toplevel_icon_v1 protocol not supported"); + return SDL_SetError("wayland: cannot set icon; required xdg_toplevel_icon_v1 protocol not supported"); } if (icon->w != icon->h) { return SDL_SetError("wayland: icon width and height must be equal, got %ix%i", icon->w, icon->h); } + int image_count = 0; + SDL_Surface **images = SDL_GetSurfaceImages(icon, &image_count); + if (!images || !image_count) { + return false; + } + + // Release the old icon resources. if (wind->xdg_toplevel_icon_v1) { xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1); wind->xdg_toplevel_icon_v1 = NULL; } - // TODO: Add high-DPI icon support - Wayland_ReleaseSHMBuffer(&wind->icon); - if (!Wayland_AllocSHMBuffer(icon->w, icon->h, &wind->icon)) { - return SDL_SetError("wayland: failed to allocate SHM buffer for the icon"); + for (int i = 0; i < wind->icon_buffer_count; ++i) { + Wayland_ReleaseSHMBuffer(&wind->icon_buffers[i]); } - - SDL_PremultiplyAlpha(icon->w, icon->h, icon->format, icon->pixels, icon->pitch, SDL_PIXELFORMAT_ARGB8888, wind->icon.shm_data, icon->w * 4, true); + SDL_free(wind->icon_buffers); + wind->icon_buffer_count = 0; wind->xdg_toplevel_icon_v1 = xdg_toplevel_icon_manager_v1_create_icon(_this->internal->xdg_toplevel_icon_manager_v1); - xdg_toplevel_icon_v1_add_buffer(wind->xdg_toplevel_icon_v1, wind->icon.wl_buffer, 1); + wind->icon_buffers = SDL_calloc(image_count, sizeof(struct Wayland_SHMBuffer)); + if (!wind->icon_buffers) { + goto failure_cleanup; + } + + for (int i = 0; i < image_count; ++i) { + if (images[i]->w == images[i]->h) { + struct Wayland_SHMBuffer *buffer = &wind->icon_buffers[wind->icon_buffer_count]; + + if (!Wayland_AllocSHMBuffer(images[i]->w, images[i]->h, buffer)) { + SDL_SetError("wayland: failed to allocate SHM buffer for the icon"); + goto failure_cleanup; + } + + SDL_PremultiplyAlpha(images[i]->w, images[i]->h, images[i]->format, images[i]->pixels, images[i]->pitch, SDL_PIXELFORMAT_ARGB8888, buffer->shm_data, images[i]->w * 4, true); + const int scale = (int)SDL_ceil((double)images[i]->w / (double)icon->w); + xdg_toplevel_icon_v1_add_buffer(wind->xdg_toplevel_icon_v1, buffer->wl_buffer, scale); + wind->icon_buffer_count++; + } else { + SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "wayland: icon width and height must be equal, got %ix%i for image level %i; skipping", images[i]->w, images[i]->h, i); + } + } + + SDL_free(images); #ifdef HAVE_LIBDECOR_H if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR && wind->shell_surface.libdecor.frame) { @@ -2874,6 +2922,42 @@ bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surfa } return true; + +failure_cleanup: + + if (wind->xdg_toplevel_icon_v1) { + xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1); + wind->xdg_toplevel_icon_v1 = NULL; + } + + for (int i = 0; i < wind->icon_buffer_count; ++i) { + Wayland_ReleaseSHMBuffer(&wind->icon_buffers[i]); + } + SDL_free(wind->icon_buffers); + wind->icon_buffers = NULL; + wind->icon_buffer_count = 0; + + return false; +} + +void *Wayland_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size) +{ + SDL_WindowData *wind = window->internal; + void *ret = NULL; + + if (wind->icc_size > 0) { + void *icc_map = mmap(NULL, wind->icc_size, PROT_READ, MAP_PRIVATE, wind->icc_fd, 0); + if (icc_map != MAP_FAILED) { + ret = SDL_malloc(wind->icc_size); + if (ret) { + *size = wind->icc_size; + SDL_memcpy(ret, icc_map, *size); + } + munmap(icc_map, wind->icc_size); + } + } + + return ret; } bool Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window) @@ -2887,6 +2971,27 @@ bool Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window) return true; } +bool Wayland_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable) +{ + if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (!(window->flags & SDL_WINDOW_HIDDEN)) { + if (!focusable && (window->flags & SDL_WINDOW_INPUT_FOCUS)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); + Wayland_SetKeyboardFocus(new_focus, set_focus); + } else if (focusable) { + if (SDL_ShouldFocusPopup(window)) { + Wayland_SetKeyboardFocus(window, true); + } + } + } + + return true; + } + + return SDL_SetError("wayland: focus can only be toggled on popup menu windows"); +} + void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y) { SDL_WindowData *wind = window->internal; @@ -2995,6 +3100,11 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) frog_color_managed_surface_destroy(wind->frog_color_managed_surface); } + if (wind->wp_color_management_surface_feedback) { + Wayland_FreeColorInfoState(wind->color_info_state); + wp_color_management_surface_feedback_v1_destroy(wind->wp_color_management_surface_feedback); + } + SDL_free(wind->outputs); SDL_free(wind->app_id); @@ -3018,7 +3128,11 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1); } - Wayland_ReleaseSHMBuffer(&wind->icon); + for (int i = 0; i < wind->icon_buffer_count; ++i) { + Wayland_ReleaseSHMBuffer(&wind->icon_buffers[i]); + } + SDL_free(wind->icon_buffers); + wind->icon_buffer_count = 0; SDL_free(wind); WAYLAND_wl_display_flush(data->display); diff --git a/libs/SDL3/src/video/wayland/SDL_waylandwindow.h b/libs/SDL3/src/video/wayland/SDL_waylandwindow.h index 906c5e4..569f870 100644 --- a/libs/SDL3/src/video/wayland/SDL_waylandwindow.h +++ b/libs/SDL3/src/video/wayland/SDL_waylandwindow.h @@ -48,7 +48,6 @@ struct SDL_WindowData struct { struct libdecor_frame *frame; - bool initial_configure_seen; } libdecor; #endif struct @@ -66,7 +65,6 @@ struct SDL_WindowData struct xdg_positioner *xdg_positioner; } popup; }; - bool initial_configure_seen; } xdg; } shell_surface; enum @@ -116,18 +114,20 @@ struct SDL_WindowData struct wp_alpha_modifier_surface_v1 *wp_alpha_modifier_surface_v1; struct xdg_toplevel_icon_v1 *xdg_toplevel_icon_v1; struct frog_color_managed_surface *frog_color_managed_surface; + struct wp_color_management_surface_feedback_v1 *wp_color_management_surface_feedback; + + struct Wayland_ColorInfoState *color_info_state; SDL_AtomicInt swap_interval_ready; SDL_DisplayData **outputs; int num_outputs; - SDL_Window *keyboard_focus; - char *app_id; double scale_factor; - struct Wayland_SHMBuffer icon; + struct Wayland_SHMBuffer *icon_buffers; + int icon_buffer_count; struct { @@ -184,6 +184,8 @@ struct SDL_WindowData int fullscreen_deadline_count; int maximized_restored_deadline_count; Uint64 last_focus_event_time_ns; + int icc_fd; + Uint32 icc_size; bool floating; bool suspended; bool resizing; @@ -192,6 +194,7 @@ struct SDL_WindowData bool is_fullscreen; bool fullscreen_exclusive; bool drop_fullscreen_requests; + bool showing_window; bool fullscreen_was_positioned; bool show_hide_sync_required; bool scale_to_display; @@ -230,7 +233,9 @@ extern void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y); extern void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); extern bool Wayland_SuspendScreenSaver(SDL_VideoDevice *_this); extern bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon); +extern bool Wayland_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable); extern float Wayland_GetWindowContentScale(SDL_VideoDevice *_this, SDL_Window *window); +extern void *Wayland_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size); extern bool Wayland_SetWindowHitTest(SDL_Window *window, bool enabled); extern bool Wayland_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); diff --git a/libs/SDL3/src/video/windows/SDL_windowsclipboard.c b/libs/SDL3/src/video/windows/SDL_windowsclipboard.c index 46e579a..39f86ef 100644 --- a/libs/SDL3/src/video/windows/SDL_windowsclipboard.c +++ b/libs/SDL3/src/video/windows/SDL_windowsclipboard.c @@ -145,7 +145,7 @@ static void *WIN_ConvertDIBtoBMP(HANDLE hMem, size_t *size) pbfh->bfSize = (DWORD)bmp_size; pbfh->bfReserved1 = 0; pbfh->bfReserved2 = 0; - pbfh->bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + color_table_size); + pbfh->bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + pbih->biSize + color_table_size); SDL_memcpy((Uint8 *)bmp + sizeof(BITMAPFILEHEADER), dib, dib_size); *size = bmp_size; } diff --git a/libs/SDL3/src/video/windows/SDL_windowsevents.c b/libs/SDL3/src/video/windows/SDL_windowsevents.c index 947a05e..b3bb274 100644 --- a/libs/SDL3/src/video/windows/SDL_windowsevents.c +++ b/libs/SDL3/src/video/windows/SDL_windowsevents.c @@ -317,7 +317,42 @@ static void WIN_CheckAsyncMouseRelease(Uint64 timestamp, SDL_WindowData *data) data->mouse_button_flags = (WPARAM)-1; } -static void WIN_UpdateFocus(SDL_Window *window, bool expect_focus) +static void WIN_UpdateMouseCapture(void) +{ + SDL_Window *focusWindow = SDL_GetKeyboardFocus(); + + if (focusWindow && (focusWindow->flags & SDL_WINDOW_MOUSE_CAPTURE)) { + SDL_WindowData *data = focusWindow->internal; + + if (!data->mouse_tracked) { + POINT cursorPos; + + if (GetCursorPos(&cursorPos) && ScreenToClient(data->hwnd, &cursorPos)) { + bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0; + SDL_MouseID mouseID = SDL_GLOBAL_MOUSE_ID; + + SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, mouseID, false, (float)cursorPos.x, (float)cursorPos.y); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + !swapButtons ? SDL_BUTTON_LEFT : SDL_BUTTON_RIGHT, + (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + !swapButtons ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT, + (GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + SDL_BUTTON_MIDDLE, + (GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + SDL_BUTTON_X1, + (GetAsyncKeyState(VK_XBUTTON1) & 0x8000) != 0); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + SDL_BUTTON_X2, + (GetAsyncKeyState(VK_XBUTTON2) & 0x8000) != 0); + } + } + } +} + +static void WIN_UpdateFocus(SDL_Window *window, bool expect_focus, DWORD pos) { SDL_WindowData *data = window->internal; HWND hwnd = data->hwnd; @@ -350,11 +385,12 @@ static void WIN_UpdateFocus(SDL_Window *window, bool expect_focus) } } - SDL_SetKeyboardFocus(data->keyboard_focus ? data->keyboard_focus : window); + SDL_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window); // In relative mode we are guaranteed to have mouse focus if we have keyboard focus if (!SDL_GetMouse()->relative_mode) { - GetCursorPos(&cursorPos); + cursorPos.x = (LONG)GET_X_LPARAM(pos); + cursorPos.y = (LONG)GET_Y_LPARAM(pos); ScreenToClient(hwnd, &cursorPos); SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, SDL_GLOBAL_MOUSE_ID, false, (float)cursorPos.x, (float)cursorPos.y); } @@ -437,11 +473,6 @@ static SDL_MOUSE_EVENT_SOURCE GetMouseMessageSource(ULONG extrainfo) return SDL_MOUSE_EVENT_SOURCE_PEN; } } - /* Sometimes WM_INPUT events won't have the correct touch signature, - so we have to rely purely on the touch bit being set. */ - if (SDL_TouchDevicesAvailable() && extrainfo & 0x80) { - return SDL_MOUSE_EVENT_SOURCE_TOUCH; - } return SDL_MOUSE_EVENT_SOURCE_MOUSE; } #endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) @@ -579,13 +610,14 @@ static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_VideoData *data, HANDL return; } - if (GetMouseMessageSource(rawmouse->ulExtraInformation) != SDL_MOUSE_EVENT_SOURCE_MOUSE) { + if (GetMouseMessageSource(rawmouse->ulExtraInformation) != SDL_MOUSE_EVENT_SOURCE_MOUSE || + (SDL_TouchDevicesAvailable() && (rawmouse->ulExtraInformation & 0x80) == 0x80)) { return; } 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 { @@ -700,6 +732,10 @@ static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_VideoData *data, HANDL float fAmount = (float)amount / WHEEL_DELTA; SDL_SendMouseWheel(WIN_GetEventTimestamp(), window, mouseID, fAmount, 0.0f, SDL_MOUSEWHEEL_NORMAL); } + + /* Invalidate the mouse button flags. If we don't do this then disabling raw input + will cause held down mouse buttons to persist when released. */ + windowdata->mouse_button_flags = (WPARAM)-1; } } @@ -861,31 +897,96 @@ static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count) } #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) -static void GetDeviceName(HDEVINFO devinfo, const char *instance, char *name, size_t len) +static char *GetDeviceName(HANDLE hDevice, HDEVINFO devinfo, const char *instance, const char *default_name, bool hid_loaded) { - name[0] = '\0'; + char *vendor_name = NULL; + char *product_name = NULL; + char *name = NULL; - SP_DEVINFO_DATA data; - SDL_zero(data); - data.cbSize = sizeof(data); - for (DWORD i = 0;; ++i) { - if (!SetupDiEnumDeviceInfo(devinfo, i, &data)) { - if (GetLastError() == ERROR_NO_MORE_ITEMS) { - break; - } else { - continue; + // These are 126 for USB, but can be longer for Bluetooth devices + WCHAR vend[256], prod[256]; + vend[0] = 0; + prod[0] = 0; + + + HIDD_ATTRIBUTES attr; + attr.VendorID = 0; + attr.ProductID = 0; + attr.Size = sizeof(attr); + + if (hid_loaded) { + char devName[MAX_PATH + 1]; + UINT cap = sizeof(devName) - 1; + UINT len = GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, devName, &cap); + if (len != (UINT)-1) { + devName[len] = '\0'; + + // important: for devices with exclusive access mode as per + // https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections-opened-by-windows-for-system-use + // they can only be opened with a desired access of none instead of generic read. + HANDLE hFile = CreateFileA(devName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + SDL_HidD_GetAttributes(hFile, &attr); + SDL_HidD_GetManufacturerString(hFile, vend, sizeof(vend)); + SDL_HidD_GetProductString(hFile, prod, sizeof(prod)); + CloseHandle(hFile); } } + } - char DeviceInstanceId[64]; - if (!SetupDiGetDeviceInstanceIdA(devinfo, &data, DeviceInstanceId, sizeof(DeviceInstanceId), NULL)) - continue; + if (vend[0]) { + vendor_name = WIN_StringToUTF8W(vend); + } - if (SDL_strcasecmp(instance, DeviceInstanceId) == 0) { - SetupDiGetDeviceRegistryPropertyA(devinfo, &data, SPDRP_DEVICEDESC, NULL, (PBYTE)name, (DWORD)len, NULL); - return; + if (prod[0]) { + product_name = WIN_StringToUTF8W(prod); + } else { + SP_DEVINFO_DATA data; + SDL_zero(data); + data.cbSize = sizeof(data); + for (DWORD i = 0;; ++i) { + if (!SetupDiEnumDeviceInfo(devinfo, i, &data)) { + if (GetLastError() == ERROR_NO_MORE_ITEMS) { + break; + } else { + continue; + } + } + + char DeviceInstanceId[64]; + if (!SetupDiGetDeviceInstanceIdA(devinfo, &data, DeviceInstanceId, sizeof(DeviceInstanceId), NULL)) + continue; + + if (SDL_strcasecmp(instance, DeviceInstanceId) == 0) { + DWORD size = 0; + if (SetupDiGetDeviceRegistryPropertyW(devinfo, &data, SPDRP_DEVICEDESC, NULL, (PBYTE)prod, sizeof(prod), &size)) { + // Make sure the device description is null terminated + size /= sizeof(*prod); + if (size >= SDL_arraysize(prod)) { + // Truncated description... + size = (SDL_arraysize(prod) - 1); + } + prod[size] = 0; + + if (attr.VendorID || attr.ProductID) { + SDL_asprintf(&product_name, "%S (0x%.4x/0x%.4x)", prod, attr.VendorID, attr.ProductID); + } else { + product_name = WIN_StringToUTF8W(prod); + } + } + break; + } } } + + if (!product_name && (attr.VendorID || attr.ProductID)) { + SDL_asprintf(&product_name, "%s (0x%.4x/0x%.4x)", default_name, attr.VendorID, attr.ProductID); + } + name = SDL_CreateDeviceName(attr.VendorID, attr.ProductID, vendor_name, product_name, default_name); + SDL_free(vendor_name); + SDL_free(product_name); + + return name; } void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check) @@ -931,6 +1032,7 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check old_keyboards = SDL_GetKeyboards(&old_keyboard_count); old_mice = SDL_GetMice(&old_mouse_count); + bool hid_loaded = WIN_LoadHIDDLL(); for (UINT i = 0; i < raw_device_count; i++) { RID_DEVICE_INFO rdi; char devName[MAX_PATH] = { 0 }; @@ -938,7 +1040,7 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check UINT nameSize = SDL_arraysize(devName); int vendor = 0, product = 0; DWORD dwType = raw_devices[i].dwType; - char *instance, *ptr, name[64]; + char *instance, *ptr, *name; if (dwType != RIM_TYPEKEYBOARD && dwType != RIM_TYPEMOUSE) { continue; @@ -976,8 +1078,9 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check SDL_KeyboardID keyboardID = (Uint32)(uintptr_t)raw_devices[i].hDevice; AddDeviceID(keyboardID, &new_keyboards, &new_keyboard_count); if (!HasDeviceID(keyboardID, old_keyboards, old_keyboard_count)) { - GetDeviceName(devinfo, instance, name, sizeof(name)); + name = GetDeviceName(raw_devices[i].hDevice, devinfo, instance, "Keyboard", hid_loaded); SDL_AddKeyboard(keyboardID, name, send_event); + SDL_free(name); } } break; @@ -986,8 +1089,9 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check SDL_MouseID mouseID = (Uint32)(uintptr_t)raw_devices[i].hDevice; AddDeviceID(mouseID, &new_mice, &new_mouse_count); if (!HasDeviceID(mouseID, old_mice, old_mouse_count)) { - GetDeviceName(devinfo, instance, name, sizeof(name)); + name = GetDeviceName(raw_devices[i].hDevice, devinfo, instance, "Mouse", hid_loaded); SDL_AddMouse(mouseID, name, send_event); + SDL_free(name); } } break; @@ -995,6 +1099,9 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check break; } } + if (hid_loaded) { + WIN_UnloadHIDDLL(); + } for (int i = old_keyboard_count; i--;) { if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) { @@ -1129,22 +1236,19 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara case WM_NCACTIVATE: { // Don't immediately clip the cursor in case we're clicking minimize/maximize buttons - // This is the only place that this flag is set. This causes all subsequent calls to - // WIN_UpdateClipCursor for this window to be no-ops in this frame's message-pumping. - // This flag is unset at the end of message pumping each frame for every window, and - // should never be carried over between frames. - data->skip_update_clipcursor = true; + data->postpone_clipcursor = true; + data->clipcursor_queued = true; /* Update the focus here, since it's possible to get WM_ACTIVATE and WM_SETFOCUS without actually being the foreground window, but this appears to get called in all cases where the global foreground window changes to and from this window. */ - WIN_UpdateFocus(data->window, !!wParam); + WIN_UpdateFocus(data->window, !!wParam, GetMessagePos()); } break; case WM_ACTIVATE: { // Update the focus in case we changed focus to a child window and then away from the application - WIN_UpdateFocus(data->window, !!LOWORD(wParam)); + WIN_UpdateFocus(data->window, !!LOWORD(wParam), GetMessagePos()); } break; case WM_MOUSEACTIVATE: @@ -1167,14 +1271,14 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara case WM_SETFOCUS: { // Update the focus in case it's changing between top-level windows in the same application - WIN_UpdateFocus(data->window, true); + WIN_UpdateFocus(data->window, true, GetMessagePos()); } break; case WM_KILLFOCUS: case WM_ENTERIDLE: { // Update the focus in case it's changing between top-level windows in the same application - WIN_UpdateFocus(data->window, false); + WIN_UpdateFocus(data->window, false, GetMessagePos()); } break; case WM_POINTERENTER: @@ -1255,8 +1359,10 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara const Uint64 timestamp = WIN_GetEventTimestamp(); SDL_Window *window = data->window; + const bool istouching = IS_POINTER_INCONTACT_WPARAM(wParam) && IS_POINTER_FIRSTBUTTON_WPARAM(wParam); + // if lifting off, do it first, so any motion changes don't cause app issues. - if (msg == WM_POINTERUP) { + if (!istouching) { SDL_SendPenTouch(timestamp, pen, window, (pen_info.penFlags & PEN_FLAG_INVERTED) != 0, false); } @@ -1286,7 +1392,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara } // if setting down, do it last, so the pen is positioned correctly from the first contact. - if (msg == WM_POINTERDOWN) { + if (istouching) { SDL_SendPenTouch(timestamp, pen, window, (pen_info.penFlags & PEN_FLAG_INVERTED) != 0, true); } @@ -1302,9 +1408,8 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara window->flags & (SDL_WINDOW_MOUSE_RELATIVE_MODE | SDL_WINDOW_MOUSE_GRABBED) || (window->mouse_rect.w > 0 && window->mouse_rect.h > 0) ); - if (wish_clip_cursor) { - data->skip_update_clipcursor = false; - WIN_UpdateClipCursor(window); + if (wish_clip_cursor) { // queue clipcursor refresh on pump finish + data->clipcursor_queued = true; } } @@ -1318,6 +1423,8 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara if (TrackMouseEvent(&trackMouseEvent)) { data->mouse_tracked = true; } + + WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data); } if (!data->videodata->raw_mouse_enabled) { @@ -1327,6 +1434,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, SDL_GLOBAL_MOUSE_ID, false, (float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam)); } } + } break; case WM_LBUTTONUP: @@ -1390,8 +1498,10 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara if (!(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) { if (SDL_GetMouseFocus() == data->window && !SDL_GetMouse()->relative_mode && !IsIconic(hwnd)) { SDL_Mouse *mouse; + DWORD pos = GetMessagePos(); POINT cursorPos; - GetCursorPos(&cursorPos); + cursorPos.x = GET_X_LPARAM(pos); + cursorPos.y = GET_Y_LPARAM(pos); ScreenToClient(hwnd, &cursorPos); mouse = SDL_GetMouse(); if (!mouse->was_touch_mouse_events) { // we're not a touch handler causing a mouse leave? @@ -1525,6 +1635,15 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara case WM_NCLBUTTONDOWN: { data->in_title_click = true; + + // Fix for 500ms hang after user clicks on the title bar, but before moving mouse + // Reference: https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/ + if (SendMessage(hwnd, WM_NCHITTEST, wParam, lParam) == HTCAPTION) { + POINT cursorPos; + GetCursorPos(&cursorPos); // want the most current pos so as to not cause position change + ScreenToClient(hwnd, &cursorPos); + PostMessage(hwnd, WM_MOUSEMOVE, 0, cursorPos.x | (((Uint32)((Sint16)cursorPos.y)) << 16)); + } } break; case WM_CAPTURECHANGED: @@ -1675,7 +1794,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); @@ -1687,7 +1806,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; @@ -1735,6 +1854,9 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara data->initial_size_rect.bottom = data->window->y + data->window->h; SetTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks, USER_TIMER_MINIMUM, NULL); + + // Reset the keyboard, as we won't get any key up events during the modal loop + SDL_ResetKeyboard(); } } break; @@ -1742,6 +1864,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; @@ -1967,7 +2098,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); } @@ -2317,43 +2448,6 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara } } -#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) -static void WIN_UpdateMouseCapture(void) -{ - SDL_Window *focusWindow = SDL_GetKeyboardFocus(); - - if (focusWindow && (focusWindow->flags & SDL_WINDOW_MOUSE_CAPTURE)) { - SDL_WindowData *data = focusWindow->internal; - - if (!data->mouse_tracked) { - POINT cursorPos; - - if (GetCursorPos(&cursorPos) && ScreenToClient(data->hwnd, &cursorPos)) { - bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0; - SDL_MouseID mouseID = SDL_GLOBAL_MOUSE_ID; - - SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, mouseID, false, (float)cursorPos.x, (float)cursorPos.y); - SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, - !swapButtons ? SDL_BUTTON_LEFT : SDL_BUTTON_RIGHT, - (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0); - SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, - !swapButtons ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT, - (GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0); - SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, - SDL_BUTTON_MIDDLE, - (GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0); - SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, - SDL_BUTTON_X1, - (GetAsyncKeyState(VK_XBUTTON1) & 0x8000) != 0); - SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, - SDL_BUTTON_X2, - (GetAsyncKeyState(VK_XBUTTON2) & 0x8000) != 0); - } - } - } -} -#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) - int WIN_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS) { if (g_WindowsEnableMessageLoop) { @@ -2501,13 +2595,27 @@ void WIN_PumpEvents(SDL_VideoDevice *_this) } } - // Update the clipping rect in case someone else has stolen it + // fire queued clipcursor refreshes if (_this) { SDL_Window *window = _this->windows; while (window) { + bool refresh_clipcursor = false; SDL_WindowData *data = window->internal; - if (data && data->skip_update_clipcursor) { - data->skip_update_clipcursor = false; + if (data) { + refresh_clipcursor = data->clipcursor_queued; + data->clipcursor_queued = false; // Must be cleared unconditionally. + data->postpone_clipcursor = false; // Must be cleared unconditionally. + // Must happen before UpdateClipCursor. + // Although its occurrence currently + // always coincides with the queuing of + // clipcursor, it is logically distinct + // and this coincidence might no longer + // be true in the future. + // Ergo this placement concordantly + // conveys its unconditionality + // vis-a-vis the queuing of clipcursor. + } + if (refresh_clipcursor) { WIN_UpdateClipCursor(window); } window = window->next; diff --git a/libs/SDL3/src/video/windows/SDL_windowsgameinput.c b/libs/SDL3/src/video/windows/SDL_windowsgameinput.c index ddc116a..3f4ad80 100644 --- a/libs/SDL3/src/video/windows/SDL_windowsgameinput.c +++ b/libs/SDL3/src/video/windows/SDL_windowsgameinput.c @@ -27,9 +27,7 @@ #ifdef HAVE_GAMEINPUT_H -#define COBJMACROS -#include - +#include "../../core/windows/SDL_gameinput.h" #include "../../events/SDL_mouse_c.h" #include "../../events/SDL_keyboard_c.h" #include "../../events/scancodes_windows.h" @@ -61,7 +59,6 @@ typedef struct GAMEINPUT_Device struct WIN_GameInputData { - void *hGameInputDLL; IGameInput *pGameInput; GameInputCallbackToken gameinput_callback_token; int num_devices; @@ -237,20 +234,7 @@ bool WIN_InitGameInput(SDL_VideoDevice *_this) goto done; } - data->hGameInputDLL = SDL_LoadObject("gameinput.dll"); - if (!data->hGameInputDLL) { - goto done; - } - - typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput * *gameInput); - GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(data->hGameInputDLL, "GameInputCreate"); - if (!GameInputCreateFunc) { - goto done; - } - - hr = GameInputCreateFunc(&data->pGameInput); - if (FAILED(hr)) { - SDL_SetError("GameInputCreate failure with HRESULT of %08X", hr); + if (!SDL_InitGameInput(&data->pGameInput)) { goto done; } @@ -293,6 +277,9 @@ static void GAMEINPUT_InitialMouseReading(WIN_GameInputData *data, SDL_Window *w bool down = ((state.buttons & mask) != 0); SDL_SendMouseButton(timestamp, window, mouseID, GAMEINPUT_button_map[i], down); } + + // Invalidate mouse button flags + window->internal->mouse_button_flags = (WPARAM)-1; } } @@ -323,6 +310,9 @@ static void GAMEINPUT_HandleMouseDelta(WIN_GameInputData *data, SDL_Window *wind SDL_SendMouseButton(timestamp, window, mouseID, GAMEINPUT_button_map[i], down); } } + + // Invalidate mouse button flags + window->internal->mouse_button_flags = (WPARAM)-1; } if (delta.wheelX || delta.wheelY) { float fAmountX = (float)delta.wheelX / WHEEL_DELTA; @@ -485,12 +475,13 @@ void WIN_UpdateGameInput(SDL_VideoDevice *_this) device->last_mouse_reading = reading; } if (hr != GAMEINPUT_E_READING_NOT_FOUND) { - // The last reading is too old, resynchronize - IGameInputReading_Release(device->last_mouse_reading); - device->last_mouse_reading = NULL; + if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindMouse, device->pDevice, &reading))) { + GAMEINPUT_HandleMouseDelta(data, window, device, device->last_mouse_reading, reading); + IGameInputReading_Release(device->last_mouse_reading); + device->last_mouse_reading = reading; + } } - } - if (!device->last_mouse_reading) { + } else { if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindMouse, device->pDevice, &reading))) { GAMEINPUT_InitialMouseReading(data, window, device, reading); device->last_mouse_reading = reading; @@ -514,12 +505,13 @@ void WIN_UpdateGameInput(SDL_VideoDevice *_this) device->last_keyboard_reading = reading; } if (hr != GAMEINPUT_E_READING_NOT_FOUND) { - // The last reading is too old, resynchronize - IGameInputReading_Release(device->last_keyboard_reading); - device->last_keyboard_reading = NULL; + if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindKeyboard, device->pDevice, &reading))) { + GAMEINPUT_HandleKeyboardDelta(data, window, device, device->last_keyboard_reading, reading); + IGameInputReading_Release(device->last_keyboard_reading); + device->last_keyboard_reading = reading; + } } - } - if (!device->last_keyboard_reading) { + } else { if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindKeyboard, device->pDevice, &reading))) { GAMEINPUT_InitialKeyboardReading(data, window, device, reading); device->last_keyboard_reading = reading; @@ -587,9 +579,9 @@ void WIN_QuitGameInput(SDL_VideoDevice *_this) data->pGameInput = NULL; } - if (data->hGameInputDLL) { - SDL_UnloadObject(data->hGameInputDLL); - data->hGameInputDLL = NULL; + if (data->pGameInput) { + SDL_QuitGameInput(); + data->pGameInput = NULL; } if (data->lock) { diff --git a/libs/SDL3/src/video/windows/SDL_windowsmouse.c b/libs/SDL3/src/video/windows/SDL_windowsmouse.c index a0e9c25..3d6bcc4 100644 --- a/libs/SDL3/src/video/windows/SDL_windowsmouse.c +++ b/libs/SDL3/src/video/windows/SDL_windowsmouse.c @@ -72,18 +72,22 @@ static SDL_Cursor *WIN_CreateCursorAndData(HCURSOR hcursor) } SDL_Cursor *cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor)); - if (cursor) { - SDL_CursorData *data = (SDL_CursorData *)SDL_calloc(1, sizeof(*data)); - if (!data) { - SDL_free(cursor); - return NULL; - } - data->cursor = hcursor; - cursor->internal = data; + if (!cursor) { + return NULL; } + + SDL_CursorData *data = (SDL_CursorData *)SDL_calloc(1, sizeof(*data)); + if (!data) { + SDL_free(cursor); + return NULL; + } + + data->cursor = hcursor; + cursor->internal = data; return cursor; } + static bool IsMonochromeSurface(SDL_Surface *surface) { int x, y; @@ -129,6 +133,9 @@ static HBITMAP CreateColorBitmap(SDL_Surface *surface) bitmap = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &pixels, NULL, 0); if (!bitmap || !pixels) { WIN_SetError("CreateDIBSection()"); + if (bitmap) { + DeleteObject(bitmap); + } return NULL; } @@ -158,6 +165,7 @@ static HBITMAP CreateMaskBitmap(SDL_Surface *surface, bool is_monochrome) pixels = SDL_small_alloc(Uint8, size * (is_monochrome ? 2 : 1), &isstack); if (!pixels) { + SDL_OutOfMemory(); return NULL; } @@ -210,20 +218,30 @@ static HCURSOR WIN_CreateHCursor(SDL_Surface *surface, int hot_x, int hot_y) if (!ii.hbmMask || (!is_monochrome && !ii.hbmColor)) { SDL_SetError("Couldn't create cursor bitmaps"); + if (ii.hbmMask) { + DeleteObject(ii.hbmMask); + } + if (ii.hbmColor) { + DeleteObject(ii.hbmColor); + } return NULL; } hcursor = CreateIconIndirect(&ii); + if (!hcursor) { + WIN_SetError("CreateIconIndirect()"); + DeleteObject(ii.hbmMask); + if (ii.hbmColor) { + DeleteObject(ii.hbmColor); + } + return NULL; + } DeleteObject(ii.hbmMask); if (ii.hbmColor) { DeleteObject(ii.hbmColor); } - if (!hcursor) { - WIN_SetError("CreateIconIndirect()"); - return NULL; - } return hcursor; } @@ -286,7 +304,7 @@ static SDL_Cursor *WIN_CreateSystemCursor(SDL_SystemCursor id) name = IDC_CROSS; break; case SDL_SYSTEM_CURSOR_PROGRESS: - name = IDC_WAIT; + name = IDC_APPSTARTING; break; case SDL_SYSTEM_CURSOR_NWSE_RESIZE: name = IDC_SIZENWSE; @@ -353,7 +371,9 @@ static void WIN_FreeCursor(SDL_Cursor *cursor) while (data->cache) { CachedCursor *entry = data->cache; data->cache = entry->next; - DestroyCursor(entry->cursor); + if (entry->cursor) { + DestroyCursor(entry->cursor); + } SDL_free(entry); } if (data->cursor) { diff --git a/libs/SDL3/src/video/windows/SDL_windowsrawinput.c b/libs/SDL3/src/video/windows/SDL_windowsrawinput.c index fa24991..64c612c 100644 --- a/libs/SDL3/src/video/windows/SDL_windowsrawinput.c +++ b/libs/SDL3/src/video/windows/SDL_windowsrawinput.c @@ -112,7 +112,9 @@ static DWORD WINAPI WIN_RawInputThread(LPVOID param) } devices[0].dwFlags |= RIDEV_REMOVE; + devices[0].hwndTarget = NULL; devices[1].dwFlags |= RIDEV_REMOVE; + devices[1].hwndTarget = NULL; RegisterRawInputDevices(devices, count, sizeof(devices[0])); DestroyWindow(window); diff --git a/libs/SDL3/src/video/windows/SDL_windowsshape.c b/libs/SDL3/src/video/windows/SDL_windowsshape.c index 2c3f1cc..db4e4a0 100644 --- a/libs/SDL3/src/video/windows/SDL_windowsshape.c +++ b/libs/SDL3/src/video/windows/SDL_windowsshape.c @@ -116,6 +116,7 @@ bool WIN_UpdateWindowShape(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surfa } } if (!SetWindowRgn(data->hwnd, mask, TRUE)) { + DeleteObject(mask); return WIN_SetError("SetWindowRgn failed"); } return true; diff --git a/libs/SDL3/src/video/windows/SDL_windowsvideo.c b/libs/SDL3/src/video/windows/SDL_windowsvideo.c index ff7c0c2..841a9ec 100644 --- a/libs/SDL3/src/video/windows/SDL_windowsvideo.c +++ b/libs/SDL3/src/video/windows/SDL_windowsvideo.c @@ -76,10 +76,15 @@ static void SDLCALL UpdateWindowFrameUsableWhileCursorHidden(void *userdata, con #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) static bool WIN_SuspendScreenSaver(SDL_VideoDevice *_this) { + DWORD result; if (_this->suspend_screensaver) { - SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED); + result = SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED); } else { - SetThreadExecutionState(ES_CONTINUOUS); + result = SetThreadExecutionState(ES_CONTINUOUS); + } + if (result == 0) { + SDL_SetError("SetThreadExecutionState() failed"); + return false; } return true; } @@ -103,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) { @@ -112,9 +120,6 @@ static void WIN_DeleteDevice(SDL_VideoDevice *device) SDL_UnloadObject(data->dxgiDLL); } #endif - if (device->wakeup_lock) { - SDL_DestroyMutex(device->wakeup_lock); - } SDL_free(device->internal->rawinput); SDL_free(device->internal); SDL_free(device); @@ -140,7 +145,6 @@ static SDL_VideoDevice *WIN_CreateDevice(void) return NULL; } device->internal = data; - device->wakeup_lock = SDL_CreateMutex(); device->system_theme = WIN_GetSystemTheme(); #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) @@ -179,6 +183,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 @@ -330,10 +345,11 @@ static SDL_VideoDevice *WIN_CreateDevice(void) VideoBootStrap WINDOWS_bootstrap = { "windows", "SDL Windows video driver", WIN_CreateDevice, #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) - WIN_ShowMessageBox + WIN_ShowMessageBox, #else - NULL + NULL, #endif + false }; static BOOL WIN_DeclareDPIAwareUnaware(SDL_VideoDevice *_this) diff --git a/libs/SDL3/src/video/windows/SDL_windowsvideo.h b/libs/SDL3/src/video/windows/SDL_windowsvideo.h index 53cfbad..c80a721 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 2ae107e..5dc16dc 100644 --- a/libs/SDL3/src/video/windows/SDL_windowswindow.c +++ b/libs/SDL3/src/video/windows/SDL_windowswindow.c @@ -33,15 +33,12 @@ #include "../SDL_sysvideo.h" #include "SDL_windowsvideo.h" +#include "SDL_windowskeyboard.h" #include "SDL_windowswindow.h" // 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 +77,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 @@ -135,12 +92,6 @@ typedef HRESULT(WINAPI *DwmEnableBlurBehindWindow_t)(HWND hwnd, const DWM_BLURBE // #define HIGHDPI_DEBUG -// Fake window to help with DirectInput events. -HWND SDL_HelperWindow = NULL; -static const TCHAR *SDL_HelperWindowClassName = TEXT("SDLHelperWindowInputCatcher"); -static const TCHAR *SDL_HelperWindowName = TEXT("SDLHelperWindowInputMsgWindow"); -static ATOM SDL_HelperWindowClass = 0; - /* For borderless Windows, still want the following flag: - WS_MINIMIZEBOX: window will respond to Windows minimize commands sent to all windows, such as windows key + m, shaking title bar, etc. Additionally, non-fullscreen windows can add: @@ -392,6 +343,10 @@ bool WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags, SDL_WindowRec // Update any child windows for (child_window = window->first_child; child_window; child_window = child_window->next_sibling) { + if (!child_window->internal) { + // This child window is not yet fully initialized. + continue; + } if (!WIN_SetWindowPositionInternal(child_window, flags, SDL_WINDOWRECT_CURRENT)) { result = false; } @@ -447,7 +402,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 +489,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; @@ -679,7 +633,7 @@ static void CleanupWindowData(SDL_VideoDevice *_this, SDL_Window *window) static void WIN_ConstrainPopup(SDL_Window *window, bool output_to_pending) { - // Clamp popup windows to the output borders + // Possibly clamp popup windows to the output borders if (SDL_WINDOW_IS_POPUP(window)) { SDL_Window *w; SDL_DisplayID displayID; @@ -690,28 +644,30 @@ static void WIN_ConstrainPopup(SDL_Window *window, bool output_to_pending) const int height = window->last_size_pending ? window->pending.h : window->floating.h; int offset_x = 0, offset_y = 0; - // Calculate the total offset from the parents - for (w = window->parent; SDL_WINDOW_IS_POPUP(w); w = w->parent) { + if (window->constrain_popup) { + // Calculate the total offset from the parents + for (w = window->parent; SDL_WINDOW_IS_POPUP(w); w = w->parent) { + offset_x += w->x; + offset_y += w->y; + } + offset_x += w->x; offset_y += w->y; - } + abs_x += offset_x; + abs_y += offset_y; - offset_x += w->x; - offset_y += w->y; - abs_x += offset_x; - abs_y += offset_y; - - // Constrain the popup window to the display of the toplevel parent - displayID = SDL_GetDisplayForWindow(w); - SDL_GetDisplayBounds(displayID, &rect); - if (abs_x + width > rect.x + rect.w) { - abs_x -= (abs_x + width) - (rect.x + rect.w); + // Constrain the popup window to the display of the toplevel parent + displayID = SDL_GetDisplayForWindow(w); + SDL_GetDisplayBounds(displayID, &rect); + if (abs_x + width > rect.x + rect.w) { + abs_x -= (abs_x + width) - (rect.x + rect.w); + } + if (abs_y + height > rect.y + rect.h) { + abs_y -= (abs_y + height) - (rect.y + rect.h); + } + abs_x = SDL_max(abs_x, rect.x); + abs_y = SDL_max(abs_y, rect.y); } - if (abs_y + height > rect.y + rect.h) { - abs_y -= (abs_y + height) - (rect.y + rect.h); - } - abs_x = SDL_max(abs_x, rect.x); - abs_y = SDL_max(abs_y, rect.y); if (output_to_pending) { window->pending.x = abs_x - offset_x; @@ -736,7 +692,7 @@ static void WIN_SetKeyboardFocus(SDL_Window *window, bool set_active_focus) toplevel = toplevel->parent; } - toplevel->internal->keyboard_focus = window; + toplevel->keyboard_focus = window; if (set_active_focus && !window->is_hiding && !window->is_destroying) { SDL_SetKeyboardFocus(window); @@ -745,6 +701,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) { @@ -790,6 +747,9 @@ bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properties return false; } + // Ensure that the IME isn't active on the new window until explicitly requested. + WIN_StopTextInput(_this, window); + // Inform Windows of the frame change so we can respond to WM_NCCALCSIZE SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE); @@ -806,24 +766,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 +1017,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) { @@ -1077,8 +1032,9 @@ void WIN_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int * void WIN_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) { + SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; DWORD style; - HWND hwnd; bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, true); @@ -1087,20 +1043,33 @@ void WIN_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) WIN_SetWindowPosition(_this, window); } - hwnd = window->internal->hwnd; + // If the window isn't borderless and will be fullscreen, use the borderless style to hide the initial borders. + if (window->pending_flags & SDL_WINDOW_FULLSCREEN) { + if (!(window->flags & SDL_WINDOW_BORDERLESS)) { + window->flags |= SDL_WINDOW_BORDERLESS; + style = GetWindowLong(hwnd, GWL_STYLE); + style &= ~STYLE_MASK; + style |= GetWindowStyle(window); + SetWindowLong(hwnd, GWL_STYLE, style); + window->flags &= ~SDL_WINDOW_BORDERLESS; + } + } style = GetWindowLong(hwnd, GWL_EXSTYLE); if (style & WS_EX_NOACTIVATE) { bActivate = false; } + + data->showing_window = true; if (bActivate) { ShowWindow(hwnd, SW_SHOW); } else { // Use SetWindowPos instead of ShowWindow to avoid activating the parent window if this is a child window SetWindowPos(hwnd, NULL, 0, 0, 0, 0, window->internal->copybits_flag | SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); } + data->showing_window = false; - if (window->flags & SDL_WINDOW_POPUP_MENU && bActivate) { - WIN_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus()); + if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE) && bActivate) { + WIN_SetKeyboardFocus(window, true); } if (window->flags & SDL_WINDOW_MODAL) { WIN_SetWindowModal(_this, window, true); @@ -1117,21 +1086,10 @@ void WIN_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) ShowWindow(hwnd, SW_HIDE); - // Transfer keyboard focus back to the parent - if (window->flags & SDL_WINDOW_POPUP_MENU) { - SDL_Window *new_focus = window->parent; - bool set_focus = window == SDL_GetKeyboardFocus(); - - // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed. - while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) { - new_focus = new_focus->parent; - - // If some window in the chain currently had keyboard focus, set it to the new lowest-level window. - if (!set_focus) { - set_focus = new_focus == SDL_GetKeyboardFocus(); - } - } - + // Transfer keyboard focus back to the parent from a grabbing popup. + if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); WIN_SetKeyboardFocus(new_focus, set_focus); } } @@ -1169,7 +1127,7 @@ void WIN_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window) } if (bActivate) { SetForegroundWindow(hwnd); - if (window->flags & SDL_WINDOW_POPUP_MENU) { + if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { WIN_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus()); } } else { @@ -1256,51 +1214,35 @@ void WIN_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *data = window->internal; if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { - HWND hwnd = data->hwnd; - data->expected_resize = true; - ShowWindow(hwnd, SW_RESTORE); - data->expected_resize = false; + if (!data->showing_window || window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED)) { + HWND hwnd = data->hwnd; + data->expected_resize = true; + ShowWindow(hwnd, SW_RESTORE); + data->expected_resize = false; + } } else { data->windowed_mode_was_maximized = false; } } -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); +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + SDL_VideoData *videodata = _this->internal; + if (videodata->DwmSetWindowAttribute) { + videodata->DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPref, sizeof(cornerPref)); } - - return oldPref; +#endif } -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); +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + SDL_VideoData *videodata = _this->internal; + if (videodata->DwmSetWindowAttribute) { + videodata->DwmSetWindowAttribute(hwnd, DWMWA_BORDER_COLOR, &colorRef, sizeof(colorRef)); } - - return oldPref; +#endif } /** @@ -1311,7 +1253,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 +1308,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 @@ -1391,6 +1333,35 @@ SDL_FullscreenResult WIN_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window &w, &h, SDL_WINDOWRECT_FLOATING); data->windowed_mode_was_maximized = false; + + /* A window may have been maximized by dragging it to the top of another display, in which case the floating + * position may be out-of-date. If the window is being restored to maximized, and the maximized and floating + * position are on different displays, try to center the window on the maximized display for restoration, which + * mimics native Windows behavior. + */ + if (enterMaximized) { + const SDL_Point windowed_point = { window->windowed.x, window->windowed.y }; + const SDL_Point floating_point = { window->floating.x, window->floating.y }; + const SDL_DisplayID floating_display = SDL_GetDisplayForPoint(&floating_point); + const SDL_DisplayID windowed_display = SDL_GetDisplayForPoint(&windowed_point); + + if (floating_display != windowed_display) { + SDL_Rect bounds; + + SDL_zero(bounds); + SDL_GetDisplayUsableBounds(windowed_display, &bounds); + if (w < bounds.w) { + x = bounds.x + (bounds.w - w) / 2; + } else { + x = bounds.x; + } + if (h < bounds.h) { + y = bounds.y + (bounds.h - h) / 2; + } else { + y = bounds.y; + } + } + } } /* Always reset the window to the base floating size before possibly re-applying the maximized state, @@ -1533,72 +1504,6 @@ void WIN_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) CleanupWindowData(_this, window); } -/* - * Creates a HelperWindow used for DirectInput. - */ -bool SDL_HelperWindowCreate(void) -{ - HINSTANCE hInstance = GetModuleHandle(NULL); - WNDCLASS wce; - - // Make sure window isn't created twice. - if (SDL_HelperWindow != NULL) { - return true; - } - - // Create the class. - SDL_zero(wce); - wce.lpfnWndProc = DefWindowProc; - wce.lpszClassName = SDL_HelperWindowClassName; - wce.hInstance = hInstance; - - // Register the class. - SDL_HelperWindowClass = RegisterClass(&wce); - if (SDL_HelperWindowClass == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) { - return WIN_SetError("Unable to create Helper Window Class"); - } - - // Create the window. - SDL_HelperWindow = CreateWindowEx(0, SDL_HelperWindowClassName, - SDL_HelperWindowName, - WS_OVERLAPPED, CW_USEDEFAULT, - CW_USEDEFAULT, CW_USEDEFAULT, - CW_USEDEFAULT, HWND_MESSAGE, NULL, - hInstance, NULL); - if (!SDL_HelperWindow) { - UnregisterClass(SDL_HelperWindowClassName, hInstance); - return WIN_SetError("Unable to create Helper Window"); - } - - return true; -} - -/* - * Destroys the HelperWindow previously created with SDL_HelperWindowCreate. - */ -void SDL_HelperWindowDestroy(void) -{ - HINSTANCE hInstance = GetModuleHandle(NULL); - - // Destroy the window. - if (SDL_HelperWindow != NULL) { - if (DestroyWindow(SDL_HelperWindow) == 0) { - WIN_SetError("Unable to destroy Helper Window"); - return; - } - SDL_HelperWindow = NULL; - } - - // Unregister the class. - if (SDL_HelperWindowClass != 0) { - if ((UnregisterClass(SDL_HelperWindowClassName, hInstance)) == 0) { - WIN_SetError("Unable to destroy Helper Window Class"); - return; - } - SDL_HelperWindowClass = 0; - } -} - #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) void WIN_OnWindowEnter(SDL_VideoDevice *_this, SDL_Window *window) { @@ -1633,7 +1538,7 @@ void WIN_UnclipCursorForWindow(SDL_Window *window) { void WIN_UpdateClipCursor(SDL_Window *window) { SDL_WindowData *data = window->internal; - if (data->in_title_click || data->focus_click_pending || data->skip_update_clipcursor) { + if (data->in_title_click || data->focus_click_pending || data->postpone_clipcursor) { return; } @@ -2272,24 +2177,40 @@ void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y) bool WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable) { - SDL_WindowData *data = window->internal; - HWND hwnd = data->hwnd; - const LONG style = GetWindowLong(hwnd, GWL_EXSTYLE); + if (!SDL_WINDOW_IS_POPUP(window)) { + SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; + const LONG style = GetWindowLong(hwnd, GWL_EXSTYLE); - SDL_assert(style != 0); + SDL_assert(style != 0); - if (focusable) { - if (style & WS_EX_NOACTIVATE) { - if (SetWindowLong(hwnd, GWL_EXSTYLE, style & ~WS_EX_NOACTIVATE) == 0) { - return WIN_SetError("SetWindowLong()"); + if (focusable) { + if (style & WS_EX_NOACTIVATE) { + if (SetWindowLong(hwnd, GWL_EXSTYLE, style & ~WS_EX_NOACTIVATE) == 0) { + return WIN_SetError("SetWindowLong()"); + } + } + } else { + if (!(style & WS_EX_NOACTIVATE)) { + if (SetWindowLong(hwnd, GWL_EXSTYLE, style | WS_EX_NOACTIVATE) == 0) { + return WIN_SetError("SetWindowLong()"); + } } } - } else { - if (!(style & WS_EX_NOACTIVATE)) { - if (SetWindowLong(hwnd, GWL_EXSTYLE, style | WS_EX_NOACTIVATE) == 0) { - return WIN_SetError("SetWindowLong()"); + } else if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (!(window->flags & SDL_WINDOW_HIDDEN)) { + if (!focusable && (window->flags & SDL_WINDOW_INPUT_FOCUS)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); + WIN_SetKeyboardFocus(new_focus, set_focus); + } else if (focusable) { + if (SDL_ShouldFocusPopup(window)) { + WIN_SetKeyboardFocus(window, true); + } } } + + return true; } return true; @@ -2299,38 +2220,38 @@ bool WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool foc void WIN_UpdateDarkModeForHWND(HWND hwnd) { #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) - SDL_SharedObject *ntdll = SDL_LoadObject("ntdll.dll"); + HMODULE ntdll = LoadLibrary(TEXT("ntdll.dll")); if (!ntdll) { return; } // There is no function to get Windows build number, so let's get it here via RtlGetVersion - RtlGetVersion_t RtlGetVersionFunc = (RtlGetVersion_t)SDL_LoadFunction(ntdll, "RtlGetVersion"); + RtlGetVersion_t RtlGetVersionFunc = (RtlGetVersion_t)GetProcAddress(ntdll, "RtlGetVersion"); NT_OSVERSIONINFOW os_info; os_info.dwOSVersionInfoSize = sizeof(NT_OSVERSIONINFOW); os_info.dwBuildNumber = 0; if (RtlGetVersionFunc) { RtlGetVersionFunc(&os_info); } - SDL_UnloadObject(ntdll); + FreeLibrary(ntdll); os_info.dwBuildNumber &= ~0xF0000000; if (os_info.dwBuildNumber < 17763) { // Too old to support dark mode return; } - SDL_SharedObject *uxtheme = SDL_LoadObject("uxtheme.dll"); + HMODULE uxtheme = LoadLibrary(TEXT("uxtheme.dll")); if (!uxtheme) { return; } - RefreshImmersiveColorPolicyState_t RefreshImmersiveColorPolicyStateFunc = (RefreshImmersiveColorPolicyState_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(104)); - ShouldAppsUseDarkMode_t ShouldAppsUseDarkModeFunc = (ShouldAppsUseDarkMode_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(132)); - AllowDarkModeForWindow_t AllowDarkModeForWindowFunc = (AllowDarkModeForWindow_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(133)); + RefreshImmersiveColorPolicyState_t RefreshImmersiveColorPolicyStateFunc = (RefreshImmersiveColorPolicyState_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(104)); + ShouldAppsUseDarkMode_t ShouldAppsUseDarkModeFunc = (ShouldAppsUseDarkMode_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(132)); + AllowDarkModeForWindow_t AllowDarkModeForWindowFunc = (AllowDarkModeForWindow_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(133)); if (os_info.dwBuildNumber < 18362) { - AllowDarkModeForApp_t AllowDarkModeForAppFunc = (AllowDarkModeForApp_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(135)); + AllowDarkModeForApp_t AllowDarkModeForAppFunc = (AllowDarkModeForApp_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(135)); if (AllowDarkModeForAppFunc) { AllowDarkModeForAppFunc(true); } } else { - SetPreferredAppMode_t SetPreferredAppModeFunc = (SetPreferredAppMode_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(135)); + SetPreferredAppMode_t SetPreferredAppModeFunc = (SetPreferredAppMode_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(135)); if (SetPreferredAppModeFunc) { SetPreferredAppModeFunc(UXTHEME_APPMODE_ALLOW_DARK); } @@ -2348,7 +2269,7 @@ void WIN_UpdateDarkModeForHWND(HWND hwnd) } else { value = (SDL_GetSystemTheme() == SDL_SYSTEM_THEME_DARK) ? TRUE : FALSE; } - SDL_UnloadObject(uxtheme); + FreeLibrary(uxtheme); if (os_info.dwBuildNumber < 18362) { SetProp(hwnd, TEXT("UseImmersiveDarkModeColors"), SDL_reinterpret_cast(HANDLE, SDL_static_cast(INT_PTR, value))); } else { diff --git a/libs/SDL3/src/video/windows/SDL_windowswindow.h b/libs/SDL3/src/video/windows/SDL_windowswindow.h index a2c9a21..7e4a3be 100644 --- a/libs/SDL3/src/video/windows/SDL_windowswindow.h +++ b/libs/SDL3/src/video/windows/SDL_windowswindow.h @@ -78,22 +78,21 @@ struct SDL_WindowData bool in_border_change; bool in_title_click; Uint8 focus_click_pending; - bool skip_update_clipcursor; + bool postpone_clipcursor; + bool clipcursor_queued; bool windowed_mode_was_maximized; bool in_window_deactivation; bool force_ws_maximizebox; bool disable_move_size_events; + bool showing_window; int in_modal_loop; 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; WCHAR *ICMFileName; - SDL_Window *keyboard_focus; SDL_WindowEraseBackgroundMode hint_erase_background_mode; struct SDL_VideoData *videodata; #ifdef SDL_VIDEO_OPENGL_EGL diff --git a/libs/SDL3/src/video/x11/SDL_x11dyn.h b/libs/SDL3/src/video/x11/SDL_x11dyn.h index e9831fc..7cd1e0e 100644 --- a/libs/SDL3/src/video/x11/SDL_x11dyn.h +++ b/libs/SDL3/src/video/x11/SDL_x11dyn.h @@ -71,6 +71,9 @@ #ifdef SDL_VIDEO_DRIVER_X11_XSHAPE #include #endif +#ifdef SDL_VIDEO_DRIVER_X11_XTEST +#include +#endif #ifdef __cplusplus extern "C" { diff --git a/libs/SDL3/src/video/x11/SDL_x11events.c b/libs/SDL3/src/video/x11/SDL_x11events.c index 7bca58b..d639346 100644 --- a/libs/SDL3/src/video/x11/SDL_x11events.c +++ b/libs/SDL3/src/video/x11/SDL_x11events.c @@ -194,7 +194,7 @@ static bool X11_KeyRepeat(Display *display, XEvent *event) return d.found; } -static bool X11_IsWheelEvent(Display *display, int button, int *xticks, int *yticks) +bool X11_IsWheelEvent(int button, int *xticks, int *yticks) { /* according to the xlib docs, no specific mouse wheel events exist. However, the defacto standard is that the vertical wheel is X buttons @@ -233,7 +233,7 @@ void SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata) #ifdef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS static void X11_HandleGenericEvent(SDL_VideoDevice *_this, XEvent *xev) { - SDL_VideoData *videodata = (SDL_VideoData *)_this->internal; + SDL_VideoData *videodata = _this->internal; // event is a union, so cookie == &event, but this is type safe. XGenericEventCookie *cookie = &xev->xcookie; @@ -310,6 +310,12 @@ static void X11_ReconcileModifiers(SDL_VideoData *viddata) viddata->xkb.sdl_modifiers &= ~SDL_KMOD_MODE; } + if (xk_modifiers & LockMask) { + viddata->xkb.sdl_modifiers |= SDL_KMOD_CAPS; + } else { + viddata->xkb.sdl_modifiers &= ~SDL_KMOD_CAPS; + } + if (xk_modifiers & viddata->xkb.numlock_mask) { viddata->xkb.sdl_modifiers |= SDL_KMOD_NUM; } else { @@ -325,10 +331,11 @@ static void X11_ReconcileModifiers(SDL_VideoData *viddata) SDL_SetModState(viddata->xkb.sdl_modifiers); } -static void X11_HandleModifierKeys(SDL_VideoData *viddata, SDL_Scancode scancode, bool pressed, bool reconcile) +static void X11_HandleModifierKeys(SDL_VideoData *viddata, SDL_Scancode scancode, bool pressed, bool allow_reconciliation) { const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false); SDL_Keymod mod = SDL_KMOD_NONE; + bool reconcile = false; /* SDL clients expect modifier state to be activated at the same time as the * source keypress, so we set pressed modifier state with the usual modifier @@ -367,7 +374,36 @@ static void X11_HandleModifierKeys(SDL_VideoData *viddata, SDL_Scancode scancode case SDLK_LEVEL5_SHIFT: mod = SDL_KMOD_LEVEL5; break; + case SDLK_CAPSLOCK: + case SDLK_NUMLOCKCLEAR: + case SDLK_SCROLLLOCK: + { + /* For locking modifier keys, query the lock state directly, or we may have to wait until the next + * key press event to know if a lock was actually activated from the key event. + */ + unsigned int cur_mask = viddata->xkb.xkb_modifiers; + X11_UpdateSystemKeyModifiers(viddata); + + if (viddata->xkb.xkb_modifiers & LockMask) { + cur_mask |= LockMask; + } else { + cur_mask &= ~LockMask; + } + if (viddata->xkb.xkb_modifiers & viddata->xkb.numlock_mask) { + cur_mask |= viddata->xkb.numlock_mask; + } else { + cur_mask &= ~viddata->xkb.numlock_mask; + } + if (viddata->xkb.xkb_modifiers & viddata->xkb.scrolllock_mask) { + cur_mask |= viddata->xkb.scrolllock_mask; + } else { + cur_mask &= ~viddata->xkb.scrolllock_mask; + } + + viddata->xkb.xkb_modifiers = cur_mask; + } SDL_FALLTHROUGH; default: + reconcile = true; break; } @@ -377,8 +413,12 @@ static void X11_HandleModifierKeys(SDL_VideoData *viddata, SDL_Scancode scancode viddata->xkb.sdl_modifiers &= ~mod; } - if (reconcile) { - X11_ReconcileModifiers(viddata); + if (allow_reconciliation) { + if (reconcile) { + X11_ReconcileModifiers(viddata); + } else { + SDL_SetModState(viddata->xkb.sdl_modifiers); + } } } @@ -466,17 +506,32 @@ static void X11_DispatchFocusOut(SDL_VideoDevice *_this, SDL_WindowData *data) static void X11_DispatchMapNotify(SDL_WindowData *data) { SDL_Window *window = data->window; - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_SHOWN, 0, 0); - if (!(window->flags & SDL_WINDOW_HIDDEN) && (window->flags & SDL_WINDOW_INPUT_FOCUS)) { + data->was_shown = true; + + // This may be sent when restoring a minimized window. + if (window->flags & SDL_WINDOW_MINIMIZED) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); + } + + if (window->flags & SDL_WINDOW_INPUT_FOCUS) { SDL_UpdateWindowGrab(window); } } static void X11_DispatchUnmapNotify(SDL_WindowData *data) { - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIDDEN, 0, 0); - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); + SDL_Window *window = data->window; + + // This may be sent when minimizing a window. + if (!window->is_hiding) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_OCCLUDED, 0, 0); + } else { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_HIDDEN, 0, 0); + } } static void DispatchWindowMove(SDL_VideoDevice *_this, const SDL_WindowData *data, const SDL_Point *point) @@ -857,7 +912,7 @@ static int XLookupStringAsUTF8(XKeyEvent *event_struct, char *buffer_return, int SDL_WindowData *X11_FindWindow(SDL_VideoDevice *_this, Window window) { - const SDL_VideoData *videodata = (SDL_VideoData *)_this->internal; + const SDL_VideoData *videodata = _this->internal; int i; if (videodata && videodata->windowlist) { @@ -879,7 +934,7 @@ Uint64 X11_GetEventTimestamp(unsigned long time) void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_KeyboardID keyboardID, XEvent *xevent) { - SDL_VideoData *videodata = (SDL_VideoData *)_this->internal; + SDL_VideoData *videodata = _this->internal; Display *display = videodata->display; KeyCode keycode = xevent->xkey.keycode; KeySym keysym = NoSymbol; @@ -906,7 +961,7 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_ #endif // DEBUG SCANCODES text[0] = '\0'; - X11_UpdateSystemKeyModifiers(videodata); + videodata->xkb.xkb_modifiers = xevent->xkey.state; if (SDL_TextInputActive(windowdata->window)) { // filter events catches XIM events and sends them to the correct handler @@ -937,7 +992,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); @@ -961,8 +1016,6 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_ void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_MouseID mouseID, int button, float x, float y, unsigned long time) { SDL_Window *window = windowdata->window; - const SDL_VideoData *videodata = (SDL_VideoData *)_this->internal; - Display *display = videodata->display; int xticks = 0, yticks = 0; Uint64 timestamp = X11_GetEventTimestamp(time); @@ -976,7 +1029,7 @@ void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *windowdata, S SDL_SendMouseMotion(timestamp, window, mouseID, false, x, y); } - if (X11_IsWheelEvent(display, button, &xticks, &yticks)) { + if (X11_IsWheelEvent(button, &xticks, &yticks)) { SDL_SendMouseWheel(timestamp, window, mouseID, (float)-xticks, (float)yticks, SDL_MOUSEWHEEL_NORMAL); } else { bool ignore_click = false; @@ -1008,8 +1061,6 @@ void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *windowdata, S void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_MouseID mouseID, int button, unsigned long time) { SDL_Window *window = windowdata->window; - const SDL_VideoData *videodata = (SDL_VideoData *)_this->internal; - Display *display = videodata->display; // The X server sends a Release event for each Press for wheels. Ignore them. int xticks = 0, yticks = 0; Uint64 timestamp = X11_GetEventTimestamp(time); @@ -1017,7 +1068,7 @@ void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *windowdata, #ifdef DEBUG_XEVENTS SDL_Log("window 0x%lx: ButtonRelease (X11 button = %d)", windowdata->xwindow, button); #endif - if (!X11_IsWheelEvent(display, button, &xticks, &yticks)) { + if (!X11_IsWheelEvent(button, &xticks, &yticks)) { if (button > 7) { // see explanation at case ButtonPress button -= (8 - SDL_BUTTON_X1); @@ -1056,6 +1107,41 @@ void X11_GetBorderValues(SDL_WindowData *data) } } +void X11_EmitConfigureNotifyEvents(SDL_WindowData *data, XConfigureEvent *xevent) +{ + if (xevent->x != data->last_xconfigure.x || + xevent->y != data->last_xconfigure.y) { + if (!data->size_move_event_flags) { + SDL_Window *w; + int x = xevent->x; + int y = xevent->y; + + data->pending_operation &= ~X11_PENDING_OP_MOVE; + SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y); + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y); + + for (w = data->window->first_child; w; w = w->next_sibling) { + // Don't update hidden child popup windows, their relative position doesn't change + if (SDL_WINDOW_IS_POPUP(w) && !(w->flags & SDL_WINDOW_HIDDEN)) { + X11_UpdateWindowPosition(w, true); + } + } + } + } + + if (xevent->width != data->last_xconfigure.width || + xevent->height != data->last_xconfigure.height) { + if (!data->size_move_event_flags) { + data->pending_operation &= ~X11_PENDING_OP_RESIZE; + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED, + xevent->width, + xevent->height); + } + } + + SDL_copyp(&data->last_xconfigure, xevent); +} + static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) { SDL_VideoData *videodata = _this->internal; @@ -1251,8 +1337,10 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) SDL_SendMouseMotion(0, data->window, SDL_GLOBAL_MOUSE_ID, false, (float)xevent->xcrossing.x, (float)xevent->xcrossing.y); } - // We ungrab in LeaveNotify, so we may need to grab again here - SDL_UpdateWindowGrab(data->window); + // We ungrab in LeaveNotify, so we may need to grab again here, but not if captured, as the capture can be lost. + if (!(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) { + SDL_UpdateWindowGrab(data->window); + } X11_ProcessHitTest(_this, data, mouse->last_x, mouse->last_y, true); } break; @@ -1280,7 +1368,7 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) xevent->xcrossing.detail != NotifyInferior) { /* In order for interaction with the window decorations and menu to work properly - on Mutter, we need to ungrab the keyboard when the the mouse leaves. */ + on Mutter, we need to ungrab the keyboard when the mouse leaves. */ if (!(data->window->flags & SDL_WINDOW_FULLSCREEN)) { X11_SetWindowKeyboardGrab(_this, data->window, false); } @@ -1407,9 +1495,8 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) xevent->xconfigure.x, xevent->xconfigure.y, xevent->xconfigure.width, xevent->xconfigure.height); #endif - // Real configure notify events are relative to the parent, synthetic events are absolute. - if (!xevent->xconfigure.send_event) - { + // Real configure notify events are relative to the parent, synthetic events are absolute. + if (!xevent->xconfigure.send_event) { unsigned int NumChildren; Window ChildReturn, Root, Parent; Window *Children; @@ -1422,41 +1509,24 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) &ChildReturn); } - if (xevent->xconfigure.x != data->last_xconfigure.x || - xevent->xconfigure.y != data->last_xconfigure.y) { - if (!data->size_move_event_flags) { - SDL_Window *w; - int x = xevent->xconfigure.x; - int y = xevent->xconfigure.y; + /* Some window managers send ConfigureNotify before PropertyNotify when changing state (Xfce and + * fvwm are known to do this), which is backwards from other window managers, as well as what is + * expected by SDL and its clients. Defer emitting the size/move events until the corresponding + * PropertyNotify arrives for consistency. + */ + const Uint32 changed = X11_GetNetWMState(_this, data->window, xevent->xproperty.window) ^ data->window->flags; + if (changed & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED)) { + SDL_copyp(&data->pending_xconfigure, &xevent->xconfigure); + data->emit_size_move_after_property_notify = true; + } - data->pending_operation &= ~X11_PENDING_OP_MOVE; - SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y); - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y); - - for (w = data->window->first_child; w; w = w->next_sibling) { - // Don't update hidden child popup windows, their relative position doesn't change - if (SDL_WINDOW_IS_POPUP(w) && !(w->flags & SDL_WINDOW_HIDDEN)) { - X11_UpdateWindowPosition(w, true); - } - } - } + if (!data->emit_size_move_after_property_notify) { + X11_EmitConfigureNotifyEvents(data, &xevent->xconfigure); } #ifdef SDL_VIDEO_DRIVER_X11_XSYNC X11_HandleConfigure(data->window, &xevent->xconfigure); #endif /* SDL_VIDEO_DRIVER_X11_XSYNC */ - - if (xevent->xconfigure.width != data->last_xconfigure.width || - xevent->xconfigure.height != data->last_xconfigure.height) { - if (!data->size_move_event_flags) { - data->pending_operation &= ~X11_PENDING_OP_RESIZE; - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED, - xevent->xconfigure.width, - xevent->xconfigure.height); - } - } - - data->last_xconfigure = xevent->xconfigure; } break; // Have we been requested to quit (or another client message?) @@ -1742,19 +1812,15 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) if (xevent->xproperty.atom == data->videodata->atoms._NET_WM_STATE) { /* Get the new state from the window manager. - Compositing window managers can alter visibility of windows - without ever mapping / unmapping them, so we handle that here, - because they use the NETWM protocol to notify us of changes. + * Compositing window managers can alter visibility of windows + * without ever mapping / unmapping them, so we handle that here, + * because they use the NETWM protocol to notify us of changes. */ const SDL_WindowFlags flags = X11_GetNetWMState(_this, data->window, xevent->xproperty.window); const SDL_WindowFlags changed = flags ^ data->window->flags; - if ((changed & (SDL_WINDOW_HIDDEN | SDL_WINDOW_FULLSCREEN)) != 0) { - if (flags & SDL_WINDOW_HIDDEN) { - X11_DispatchUnmapNotify(data); - } else { - X11_DispatchMapNotify(data); - } + if ((changed & SDL_WINDOW_HIDDEN) && !(flags & SDL_WINDOW_HIDDEN)) { + X11_DispatchMapNotify(data); } if (!SDL_WINDOW_IS_POPUP(data->window)) { @@ -1765,6 +1831,8 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) if (!(flags & SDL_WINDOW_MINIMIZED)) { const bool commit = SDL_memcmp(&data->window->current_fullscreen_mode, &data->requested_fullscreen_mode, sizeof(SDL_DisplayMode)) != 0; + // Ensure the maximized flag is cleared before entering fullscreen. + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0); SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0); if (commit) { /* This was initiated by the compositor, or the mode was changed between the request and the window @@ -1856,6 +1924,10 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) } } } + if (data->emit_size_move_after_property_notify) { + X11_EmitConfigureNotifyEvents(data, &data->pending_xconfigure); + data->emit_size_move_after_property_notify = false; + } if ((flags & SDL_WINDOW_INPUT_FOCUS)) { if (data->pending_move) { DispatchWindowMove(_this, data, &data->pending_move_point); @@ -1875,13 +1947,12 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) right approach, but it seems to work. */ X11_UpdateKeymap(_this, true); } else if (xevent->xproperty.atom == videodata->atoms._NET_FRAME_EXTENTS) { - /* Events are disabled when leaving fullscreen until the borders appear to avoid - * incorrect size/position events. - */ + X11_GetBorderValues(data); if (data->size_move_event_flags) { + /* Events are disabled when leaving fullscreen until the borders appear to avoid + * incorrect size/position events on compositing window managers. + */ data->size_move_event_flags &= ~X11_SIZE_MOVE_EVENTS_WAIT_FOR_BORDERS; - X11_GetBorderValues(data); - } if (!(data->window->flags & SDL_WINDOW_FULLSCREEN) && data->toggle_borders) { data->toggle_borders = false; @@ -2073,6 +2144,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_x11events.h b/libs/SDL3/src/video/x11/SDL_x11events.h index bb76f83..bc0c226 100644 --- a/libs/SDL3/src/video/x11/SDL_x11events.h +++ b/libs/SDL3/src/video/x11/SDL_x11events.h @@ -36,5 +36,6 @@ extern void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *wind extern SDL_WindowData *X11_FindWindow(SDL_VideoDevice *_this, Window window); extern bool X11_ProcessHitTest(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y, bool force_new_result); extern bool X11_TriggerHitTestAction(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y); +extern bool X11_IsWheelEvent(int button, int *xticks, int *yticks); #endif // SDL_x11events_h_ diff --git a/libs/SDL3/src/video/x11/SDL_x11keyboard.c b/libs/SDL3/src/video/x11/SDL_x11keyboard.c index f9ca027..2534092 100644 --- a/libs/SDL3/src/video/x11/SDL_x11keyboard.c +++ b/libs/SDL3/src/video/x11/SDL_x11keyboard.c @@ -660,7 +660,8 @@ void X11_CreateInputContext(SDL_WindowData *data) NULL); X11_XFree(attr); } - } else { + } + if (!data->ic) { data->ic = X11_XCreateIC(videodata->im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, data->xwindow, @@ -761,7 +762,9 @@ void X11_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_Prop break; } (void)SDL_snprintf(deeplink, sizeof(deeplink), - "steam://open/keyboard?XPosition=0&YPosition=0&Width=0&Height=0&Mode=%d", + "steam://open/keyboard?XPosition=%i&YPosition=%i&Width=%i&Height=%i&Mode=%d", + window->text_input_rect.x, window->text_input_rect.y, + window->text_input_rect.w, window->text_input_rect.h, mode); SDL_OpenURL(deeplink); videodata->steam_keyboard_open = true; diff --git a/libs/SDL3/src/video/x11/SDL_x11messagebox.c b/libs/SDL3/src/video/x11/SDL_x11messagebox.c index a2fdba4..cbc3c98 100644 --- a/libs/SDL3/src/video/x11/SDL_x11messagebox.c +++ b/libs/SDL3/src/video/x11/SDL_x11messagebox.c @@ -30,7 +30,10 @@ #include #include +#ifndef SDL_FORK_MESSAGEBOX #define SDL_FORK_MESSAGEBOX 1 +#endif + #define SDL_SET_LOCALE 1 #if SDL_FORK_MESSAGEBOX @@ -48,13 +51,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 +207,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 @@ -415,6 +428,13 @@ static bool X11_MessageBoxCreateWindow(SDL_MessageBoxDataX11 *data) Display *display = data->display; SDL_WindowData *windowdata = NULL; const SDL_MessageBoxData *messageboxdata = data->messageboxdata; +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR +#ifdef XRANDR_DISABLED_BY_DEFAULT + const bool use_xrandr_by_default = false; +#else + const bool use_xrandr_by_default = true; +#endif +#endif if (messageboxdata->window) { SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(messageboxdata->window); @@ -487,7 +507,17 @@ static bool X11_MessageBoxCreateWindow(SDL_MessageBoxDataX11 *data) const SDL_DisplayData *dpydata = dpy->internal; x = dpydata->x + ((dpy->current_mode->w - data->dialog_width) / 2); y = dpydata->y + ((dpy->current_mode->h - data->dialog_height) / 3); - } else { // oh well. This will misposition on a multi-head setup. Init first next time. + } +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR + else if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XRANDR, use_xrandr_by_default)) { + XRRScreenResources *screen = X11_XRRGetScreenResourcesCurrent(display, DefaultRootWindow(display)); + XRRCrtcInfo *crtc_info = X11_XRRGetCrtcInfo(display, screen, screen->crtcs[0]); + x = (crtc_info->width - data->dialog_width) / 2; + y = (crtc_info->height - data->dialog_height) / 3; + } +#endif + else { + // oh well. This will misposition on a multi-head setup. Init first next time. x = (DisplayWidth(display, data->screen) - data->dialog_width) / 2; y = (DisplayHeight(display, data->screen) - data->dialog_height) / 3; } diff --git a/libs/SDL3/src/video/x11/SDL_x11modes.c b/libs/SDL3/src/video/x11/SDL_x11modes.c index e17a2d1..37198a7 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_x11pen.c b/libs/SDL3/src/video/x11/SDL_x11pen.c index f636545..c29c629 100644 --- a/libs/SDL3/src/video/x11/SDL_x11pen.c +++ b/libs/SDL3/src/video/x11/SDL_x11pen.c @@ -31,7 +31,7 @@ // Does this device have a valuator for pressure sensitivity? static bool X11_XInput2DeviceIsPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev) { - const SDL_VideoData *data = (SDL_VideoData *)_this->internal; + const SDL_VideoData *data = _this->internal; for (int i = 0; i < dev->num_classes; i++) { const XIAnyClassInfo *classinfo = dev->classes[i]; if (classinfo->type == XIValuatorClass) { @@ -49,7 +49,7 @@ static bool X11_XInput2DeviceIsPen(SDL_VideoDevice *_this, const XIDeviceInfo *d static bool X11_XInput2PenIsEraser(SDL_VideoDevice *_this, int deviceid, char *devicename) { #define PEN_ERASER_NAME_TAG "eraser" // String constant to identify erasers - SDL_VideoData *data = (SDL_VideoData *)_this->internal; + SDL_VideoData *data = _this->internal; if (data->atoms.pen_atom_wacom_tool_type != None) { Atom type_return; @@ -105,7 +105,7 @@ static bool X11_XInput2PenIsEraser(SDL_VideoDevice *_this, int deviceid, char *d // Returns number of Sint32s written (<= max_words), or 0 on error. static size_t X11_XInput2PenGetIntProperty(SDL_VideoDevice *_this, int deviceid, Atom property, Sint32 *dest, size_t max_words) { - const SDL_VideoData *data = (SDL_VideoData *)_this->internal; + const SDL_VideoData *data = _this->internal; Atom type_return; int format_return; unsigned long num_items_return; @@ -153,7 +153,7 @@ static size_t X11_XInput2PenGetIntProperty(SDL_VideoDevice *_this, int deviceid, // Identify Wacom devices (if true is returned) and extract their device type and serial IDs static bool X11_XInput2PenWacomDeviceID(SDL_VideoDevice *_this, int deviceid, Uint32 *wacom_devicetype_id, Uint32 *wacom_serial) { - SDL_VideoData *data = (SDL_VideoData *)_this->internal; + SDL_VideoData *data = _this->internal; Sint32 serial_id_buf[3]; int result; @@ -196,7 +196,7 @@ X11_PenHandle *X11_FindPenByDeviceID(int deviceid) static X11_PenHandle *X11_MaybeAddPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev) { - SDL_VideoData *data = (SDL_VideoData *)_this->internal; + SDL_VideoData *data = _this->internal; SDL_PenCapabilityFlags capabilities = 0; X11_PenHandle *handle = NULL; @@ -283,14 +283,16 @@ static X11_PenHandle *X11_MaybeAddPen(SDL_VideoDevice *_this, const XIDeviceInfo X11_PenHandle *X11_MaybeAddPenByDeviceID(SDL_VideoDevice *_this, int deviceid) { - SDL_VideoData *data = (SDL_VideoData *)_this->internal; - int num_device_info = 0; - XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, deviceid, &num_device_info); - if (device_info) { - SDL_assert(num_device_info == 1); - X11_PenHandle *handle = X11_MaybeAddPen(_this, device_info); - X11_XIFreeDeviceInfo(device_info); - return handle; + if (X11_Xinput2IsInitialized()) { + SDL_VideoData *data = _this->internal; + int num_device_info = 0; + XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, deviceid, &num_device_info); + if (device_info) { + SDL_assert(num_device_info == 1); + X11_PenHandle *handle = X11_MaybeAddPen(_this, device_info); + X11_XIFreeDeviceInfo(device_info); + return handle; + } } return NULL; } @@ -306,7 +308,11 @@ void X11_RemovePenByDeviceID(int deviceid) void X11_InitPen(SDL_VideoDevice *_this) { - SDL_VideoData *data = (SDL_VideoData *)_this->internal; + if (!X11_Xinput2IsInitialized()) { + return; // we need XIQueryDevice() for this. + } + + SDL_VideoData *data = _this->internal; #define LOOKUP_PEN_ATOM(X) X11_XInternAtom(data->display, X, False) data->atoms.pen_atom_device_product_id = LOOKUP_PEN_ATOM("Device Product ID"); diff --git a/libs/SDL3/src/video/x11/SDL_x11shape.c b/libs/SDL3/src/video/x11/SDL_x11shape.c index 92c44f9..e433598 100644 --- a/libs/SDL3/src/video/x11/SDL_x11shape.c +++ b/libs/SDL3/src/video/x11/SDL_x11shape.c @@ -31,7 +31,7 @@ static Uint8 *GenerateShapeMask(SDL_Surface *shape) { int x, y; const size_t ppb = 8; - const size_t bytes_per_scanline = (size_t)(shape->w + (ppb - 1)) / ppb; + const size_t bytes_per_scanline = (shape->w + (ppb - 1)) / ppb; const Uint8 *a; Uint8 *mask; Uint8 *mask_scanline; diff --git a/libs/SDL3/src/video/x11/SDL_x11video.c b/libs/SDL3/src/video/x11/SDL_x11video.c index d87dcc0..86f5b86 100644 --- a/libs/SDL3/src/video/x11/SDL_x11video.c +++ b/libs/SDL3/src/video/x11/SDL_x11video.c @@ -63,9 +63,6 @@ static void X11_DeleteDevice(SDL_VideoDevice *device) X11_XCloseDisplay(data->request_display); } SDL_free(data->windowlist); - if (device->wakeup_lock) { - SDL_DestroyMutex(device->wakeup_lock); - } SDL_free(device->internal); SDL_free(device); @@ -129,8 +126,6 @@ static SDL_VideoDevice *X11_CreateDevice(void) return NULL; } - device->wakeup_lock = SDL_CreateMutex(); - #ifdef X11_DEBUG X11_XSynchronize(data->display, True); #endif @@ -256,13 +251,13 @@ 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; data->is_xwayland = X11_IsXWayland(x11_display); if (data->is_xwayland) { device->device_caps |= VIDEO_DEVICE_CAPS_MODE_SWITCHING_EMULATED | - VIDEO_DEVICE_CAPS_DISABLE_MOUSE_WARP_ON_FULLSCREEN_TRANSITIONS; + VIDEO_DEVICE_CAPS_DISABLE_MOUSE_WARP_ON_FULLSCREEN_TRANSITIONS | + VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS; } return device; @@ -271,7 +266,8 @@ static SDL_VideoDevice *X11_CreateDevice(void) VideoBootStrap X11_bootstrap = { "x11", "SDL X11 video driver", X11_CreateDevice, - X11_ShowMessageBox + X11_ShowMessageBox, + false }; static int (*handler)(Display *, XErrorEvent *) = NULL; diff --git a/libs/SDL3/src/video/x11/SDL_x11window.c b/libs/SDL3/src/video/x11/SDL_x11window.c index c26ee7f..5422f9a 100644 --- a/libs/SDL3/src/video/x11/SDL_x11window.c +++ b/libs/SDL3/src/video/x11/SDL_x11window.c @@ -86,6 +86,23 @@ static Bool X11_XIfEventTimeout(Display *display, XEvent *event_return, Bool (*p } */ +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 bool X11_IsWindowMapped(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *data = window->internal; @@ -100,6 +117,14 @@ static bool X11_IsWindowMapped(SDL_VideoDevice *_this, SDL_Window *window) } } +static bool X11_IsDisplayOk(Display *display) +{ + if (display->flags & XlibDisplayIOError) { + return false; + } + return true; +} + #if 0 static bool X11_IsActionAllowed(SDL_Window *window, Atom action) { @@ -207,28 +232,30 @@ static void X11_ConstrainPopup(SDL_Window *window, bool output_to_pending) int abs_y = window->last_position_pending ? window->pending.y : window->floating.y; int offset_x = 0, offset_y = 0; - // Calculate the total offset from the parents - for (w = window->parent; SDL_WINDOW_IS_POPUP(w); w = w->parent) { + if (window->constrain_popup) { + // Calculate the total offset from the parents + for (w = window->parent; SDL_WINDOW_IS_POPUP(w); w = w->parent) { + offset_x += w->x; + offset_y += w->y; + } + offset_x += w->x; offset_y += w->y; - } + abs_x += offset_x; + abs_y += offset_y; - offset_x += w->x; - offset_y += w->y; - abs_x += offset_x; - abs_y += offset_y; + displayID = SDL_GetDisplayForWindow(w); - displayID = SDL_GetDisplayForWindow(w); - - SDL_GetDisplayBounds(displayID, &rect); - if (abs_x + window->w > rect.x + rect.w) { - abs_x -= (abs_x + window->w) - (rect.x + rect.w); + SDL_GetDisplayBounds(displayID, &rect); + if (abs_x + window->w > rect.x + rect.w) { + abs_x -= (abs_x + window->w) - (rect.x + rect.w); + } + if (abs_y + window->h > rect.y + rect.h) { + abs_y -= (abs_y + window->h) - (rect.y + rect.h); + } + abs_x = SDL_max(abs_x, rect.x); + abs_y = SDL_max(abs_y, rect.y); } - if (abs_y + window->h > rect.y + rect.h) { - abs_y -= (abs_y + window->h) - (rect.y + rect.h); - } - abs_x = SDL_max(abs_x, rect.x); - abs_y = SDL_max(abs_y, rect.y); if (output_to_pending) { window->pending.x = abs_x - offset_x; @@ -249,7 +276,7 @@ static void X11_SetKeyboardFocus(SDL_Window *window, bool set_active_focus) toplevel = toplevel->parent; } - toplevel->internal->keyboard_focus = window; + toplevel->keyboard_focus = window; if (set_active_focus && !window->is_hiding && !window->is_destroying) { SDL_SetKeyboardFocus(window); @@ -387,11 +414,11 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, Window w X11_XGetWindowAttributes(data->videodata->display, w, &attrib); if (!SDL_WINDOW_IS_POPUP(window)) { - window->x = data->expected.x = attrib.x; - window->y = data->expected.y = attrib.y - data->border_top; + window->x = window->windowed.x = window->floating.x = attrib.x; + window->y = window->windowed.y = window->floating.y = attrib.y - data->border_top; } - window->w = data->expected.w = attrib.width; - window->h = data->expected.h = attrib.height; + window->w = window->windowed.w = window->floating.w = attrib.width; + window->h = window->windowed.h = window->floating.h = attrib.height; if (attrib.map_state != IsUnmapped) { window->flags &= ~SDL_WINDOW_HIDDEN; } else { @@ -513,7 +540,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; } @@ -524,7 +553,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_OPENGL); // Doesn't work well with Vulkan SDL_WindowData *windowdata; Display *display = data->display; int screen = displaydata->screen; @@ -924,6 +953,75 @@ static int X11_CatchAnyError(Display *d, XErrorEvent *e) return 0; } +static void X11_ExternalResizeMoveSync(SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + int (*prev_handler)(Display *, XErrorEvent *); + unsigned int childCount; + Window childReturn, root, parent; + Window *children; + XWindowAttributes attrs; + Uint64 timeout = 0; + int x, y; + const bool send_move = !!(data->pending_operation & X11_PENDING_OP_MOVE); + const bool send_resize = !!(data->pending_operation & X11_PENDING_OP_RESIZE); + + X11_XSync(display, False); + X11_XQueryTree(display, data->xwindow, &root, &parent, &children, &childCount); + prev_handler = X11_XSetErrorHandler(X11_CatchAnyError); + + /* Wait a brief time to see if the window manager decided to let the move or resize happen. + * If the window changes at all, even to an unexpected value, we break out. + */ + timeout = SDL_GetTicksNS() + SDL_MS_TO_NS(100); + while (true) { + caught_x11_error = false; + X11_XSync(display, False); + X11_XGetWindowAttributes(display, data->xwindow, &attrs); + X11_XTranslateCoordinates(display, parent, DefaultRootWindow(display), + attrs.x, attrs.y, &x, &y, &childReturn); + SDL_GlobalToRelativeForWindow(window, x, y, &x, &y); + + if (!caught_x11_error) { + if ((data->pending_operation & X11_PENDING_OP_MOVE) && (x == data->expected.x + data->border_left && y == data->expected.y + data->border_top)) { + data->pending_operation &= ~X11_PENDING_OP_MOVE; + } + if ((data->pending_operation & X11_PENDING_OP_RESIZE) && (attrs.width == data->expected.w && attrs.height == data->expected.h)) { + data->pending_operation &= ~X11_PENDING_OP_RESIZE; + } + + if (data->pending_operation == X11_PENDING_OP_NONE) { + break; + } + } + + if (SDL_GetTicksNS() >= timeout) { + // Timed out without the expected values. Update the requested data so future sync calls won't block. + data->pending_operation &= ~(X11_PENDING_OP_MOVE | X11_PENDING_OP_RESIZE); + data->expected.x = x; + data->expected.y = y; + data->expected.w = attrs.width; + data->expected.h = attrs.height; + break; + } + + SDL_Delay(10); + } + + if (!caught_x11_error) { + if (send_move) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y); + } + if (send_resize) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, attrs.width, attrs.height); + } + } + + X11_XSetErrorHandler(prev_handler); + caught_x11_error = false; +} + /* Wait a brief time, or not, to see if the window manager decided to move/resize the window. * Send MOVED and RESIZED window events */ static bool X11_SyncWindowTimeout(SDL_VideoDevice *_this, SDL_Window *window, Uint64 param_timeout) @@ -1099,41 +1197,6 @@ bool X11_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) return true; } -static void X11_SetWMNormalHints(SDL_VideoDevice *_this, SDL_Window *window, XSizeHints *sizehints) -{ - SDL_WindowData *data = window->internal; - Display *display = data->videodata->display; - int dest_x, dest_y; - - X11_XSetWMNormalHints(display, data->xwindow, sizehints); - - /* From Pierre-Loup: - WMs each have their little quirks with that. When you change the - size hints, they get a ConfigureNotify event with the - WM_NORMAL_SIZE_HINTS Atom. They all save the hints then, but they - don't all resize the window right away to enforce the new hints. - - Some of them resize only after: - - A user-initiated move or resize - - A code-initiated move or resize - - Hiding & showing window (Unmap & map) - - The following move & resize seems to help a lot of WMs that didn't - properly update after the hints were changed. We don't do a - hide/show, because there are supposedly subtle problems with doing so - and transitioning from windowed to fullscreen in Unity. - */ - X11_XResizeWindow(display, data->xwindow, window->pending.w, window->pending.h); - const int x = window->last_position_pending ? window->pending.x : window->floating.x; - const int y = window->last_position_pending ? window->pending.y : window->floating.y; - SDL_RelativeToGlobalForWindow(window, - x - data->border_left, - y - data->border_top, - &dest_x, &dest_y); - X11_XMoveWindow(display, data->xwindow, dest_x, dest_y); - X11_XRaiseWindow(display, data->xwindow); -} - void X11_SetWindowMinMax(SDL_Window *window, bool use_current) { SDL_WindowData *data = window->internal; @@ -1235,14 +1298,42 @@ void X11_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) */ XSizeHints *sizehints = X11_XAllocSizeHints(); long userhints; + int dest_x, dest_y; X11_XGetWMNormalHints(display, data->xwindow, sizehints, &userhints); - sizehints->min_width = sizehints->max_width = window->pending.w; - sizehints->min_height = sizehints->max_height = window->pending.h; + data->expected.w = sizehints->min_width = sizehints->max_width = window->pending.w; + data->expected.h = sizehints->min_height = sizehints->max_height = window->pending.h; sizehints->flags |= PMinSize | PMaxSize; + data->pending_operation |= X11_PENDING_OP_RESIZE; - X11_SetWMNormalHints(_this, window, sizehints); + X11_XSetWMNormalHints(display, data->xwindow, sizehints); + + /* From Pierre-Loup: + WMs each have their little quirks with that. When you change the + size hints, they get a ConfigureNotify event with the + WM_NORMAL_SIZE_HINTS Atom. They all save the hints then, but they + don't all resize the window right away to enforce the new hints. + + Some of them resize only after: + - A user-initiated move or resize + - A code-initiated move or resize + - Hiding & showing window (Unmap & map) + + The following move & resize seems to help a lot of WMs that didn't + properly update after the hints were changed. We don't do a + hide/show, because there are supposedly subtle problems with doing so + and transitioning from windowed to fullscreen in Unity. + */ + X11_XResizeWindow(display, data->xwindow, window->pending.w, window->pending.h); + const int x = window->last_position_pending ? window->pending.x : window->x; + const int y = window->last_position_pending ? window->pending.y : window->y; + SDL_RelativeToGlobalForWindow(window, + x - data->border_left, + y - data->border_top, + &dest_x, &dest_y); + X11_XMoveWindow(display, data->xwindow, dest_x, dest_y); + X11_XRaiseWindow(display, data->xwindow); X11_XFree(sizehints); } @@ -1436,12 +1527,14 @@ 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 set_position = false; XEvent event; if (SDL_WINDOW_IS_POPUP(window)) { // Update the position in case the parent moved while we were hidden X11_ConstrainPopup(window, true); - X11_UpdateWindowPosition(window, false); + data->pending_position = true; + set_position = true; } /* Whether XMapRaised focuses the window is based on the window type and it is @@ -1453,10 +1546,12 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) /* Blocking wait for "MapNotify" event. * We use X11_XIfEvent because pXWindowEvent takes a mask rather than a type, * and XCheckTypedWindowEvent doesn't block */ - if (!(window->flags & SDL_WINDOW_EXTERNAL)) { + if (!(window->flags & SDL_WINDOW_EXTERNAL) && X11_IsDisplayOk(display)) { X11_XIfEvent(display, &event, &isMapNotify, (XPointer)&data->xwindow); } X11_XFlush(display); + set_position = data->pending_position || + (!(window->flags & SDL_WINDOW_BORDERLESS) && !window->undefined_x && !window->undefined_y); } if (!data->videodata->net_wm) { @@ -1466,9 +1561,9 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) X11_XFlush(display); } - // Popup menus grab the keyboard - if (window->flags & SDL_WINDOW_POPUP_MENU) { - X11_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus()); + // Grabbing popup menus get keyboard focus. + if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + X11_SetKeyboardFocus(window, true); } // Get some valid border values, if we haven't received them yet @@ -1476,8 +1571,28 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) X11_GetBorderValues(data); } - // Apply the pending position, if any, after the window is mapped. - data->pending_position = window->last_position_pending; + if (set_position) { + // Apply the window position, accounting for offsets due to the borders appearing. + 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; + + SDL_RelativeToGlobalForWindow(window, + tx - data->border_left, ty - data->border_top, + &x, &y); + + data->pending_position = false; + X11_XMoveWindow(display, data->xwindow, x, y); + } + + /* XMonad ignores size hints and shrinks the client area to overlay borders on fixed-size windows, + * even if no borders were requested, resulting in the window client area being smaller than + * requested. Calling XResizeWindow after mapping seems to fix it, even though resizing fixed-size + * windows in this manner doesn't work on any other window manager. + */ + if (!(window->flags & SDL_WINDOW_RESIZABLE) && X11_CheckCurrentDesktop("xmonad")) { + X11_XResizeWindow(display, data->xwindow, window->w, window->h); + } /* Some window managers can send garbage coordinates while mapping the window, so don't emit size and position * events during the initial configure events. @@ -1487,6 +1602,12 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) X11_PumpEvents(_this); data->size_move_event_flags = 0; + /* A MapNotify or PropertyNotify may not have arrived, so ensure that the shown event is dispatched + * to apply pending state before clearing the flag. + */ + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_SHOWN, 0, 0); + data->was_shown = true; + // If a configure event was received (type is non-zero), send the final window size and coordinates. if (data->last_xconfigure.type) { int x, y; @@ -1507,27 +1628,16 @@ void X11_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) if (X11_IsWindowMapped(_this, window)) { X11_XWithdrawWindow(display, data->xwindow, screen); // Blocking wait for "UnmapNotify" event - if (!(window->flags & SDL_WINDOW_EXTERNAL)) { + if (!(window->flags & SDL_WINDOW_EXTERNAL) && X11_IsDisplayOk(display)) { X11_XIfEvent(display, &event, &isUnmapNotify, (XPointer)&data->xwindow); } X11_XFlush(display); } // Transfer keyboard focus back to the parent - if (window->flags & SDL_WINDOW_POPUP_MENU) { - SDL_Window *new_focus = window->parent; - bool set_focus = window == SDL_GetKeyboardFocus(); - - // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed. - while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) { - new_focus = new_focus->parent; - - // If some window in the chain currently had focus, set it to the new lowest-level window. - if (!set_focus) { - set_focus = new_focus == SDL_GetKeyboardFocus(); - } - } - + if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); X11_SetKeyboardFocus(new_focus, set_focus); } @@ -1678,6 +1788,11 @@ void X11_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window) void X11_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) { + // Don't restore the window the first time it is being shown. + if (!window->internal->was_shown) { + return; + } + if (window->internal->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MAXIMIZE | X11_PENDING_OP_MINIMIZE)) { SDL_SyncWindow(window); } @@ -1712,6 +1827,10 @@ static SDL_FullscreenResult X11_SetWindowFullscreenViaWM(SDL_VideoDevice *_this, Atom _NET_WM_STATE = data->videodata->atoms._NET_WM_STATE; Atom _NET_WM_STATE_FULLSCREEN = data->videodata->atoms._NET_WM_STATE_FULLSCREEN; + if (!data->was_shown && fullscreen == SDL_FULLSCREEN_OP_LEAVE) { + return SDL_FULLSCREEN_SUCCEEDED; + } + if (X11_IsWindowMapped(_this, window)) { XEvent e; @@ -1995,6 +2114,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 { @@ -2179,6 +2325,15 @@ void X11_ShowWindowSystemMenu(SDL_Window *window, int x, int y) bool X11_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window) { + SDL_WindowData *data = window->internal; + + // If the window is external and has only a pending resize or move event, use the special external sync path to avoid processing events. + if ((window->flags & SDL_WINDOW_EXTERNAL) && + (data->pending_operation & ~(X11_PENDING_OP_RESIZE | X11_PENDING_OP_MOVE)) == X11_PENDING_OP_NONE) { + X11_ExternalResizeMoveSync(window); + return true; + } + const Uint64 current_time = SDL_GetTicksNS(); Uint64 timeout = 0; @@ -2200,21 +2355,37 @@ bool X11_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window) bool X11_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable) { - SDL_WindowData *data = window->internal; - Display *display = data->videodata->display; - XWMHints *wmhints; + if (!SDL_WINDOW_IS_POPUP(window)) { + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + XWMHints *wmhints; - wmhints = X11_XGetWMHints(display, data->xwindow); - if (!wmhints) { - return SDL_SetError("Couldn't get WM hints"); + wmhints = X11_XGetWMHints(display, data->xwindow); + if (!wmhints) { + return SDL_SetError("Couldn't get WM hints"); + } + + wmhints->input = focusable ? True : False; + wmhints->flags |= InputHint; + + X11_XSetWMHints(display, data->xwindow, wmhints); + X11_XFree(wmhints); + } else if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (!(window->flags & SDL_WINDOW_HIDDEN)) { + if (!focusable && (window->flags & SDL_WINDOW_INPUT_FOCUS)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); + X11_SetKeyboardFocus(new_focus, set_focus); + } else if (focusable) { + if (SDL_ShouldFocusPopup(window)) { + X11_SetKeyboardFocus(window, true); + } + } + } + + return true; } - wmhints->input = focusable ? True : False; - wmhints->flags |= InputHint; - - X11_XSetWMHints(display, data->xwindow, wmhints); - X11_XFree(wmhints); - return true; } diff --git a/libs/SDL3/src/video/x11/SDL_x11window.h b/libs/SDL3/src/video/x11/SDL_x11window.h index f1a73ab..8933340 100644 --- a/libs/SDL3/src/video/x11/SDL_x11window.h +++ b/libs/SDL3/src/video/x11/SDL_x11window.h @@ -68,13 +68,13 @@ struct SDL_WindowData bool pending_move; SDL_Point pending_move_point; XConfigureEvent last_xconfigure; + XConfigureEvent pending_xconfigure; struct SDL_VideoData *videodata; unsigned long user_time; Atom xdnd_req; Window xdnd_source; bool flashing_window; Uint64 flash_cancel_time; - SDL_Window *keyboard_focus; #ifdef SDL_VIDEO_OPENGL_EGL EGLSurface egl_surface; #endif @@ -115,6 +115,8 @@ struct SDL_WindowData bool previous_borders_nonzero; bool toggle_borders; bool fullscreen_borders_forced_on; + bool was_shown; + bool emit_size_move_after_property_notify; SDL_HitTestResult hit_test_result; XPoint xim_spot; diff --git a/libs/SDL3/src/video/x11/SDL_x11xinput2.c b/libs/SDL3/src/video/x11/SDL_x11xinput2.c index caee307..eb21237 100644 --- a/libs/SDL3/src/video/x11/SDL_x11xinput2.c +++ b/libs/SDL3/src/video/x11/SDL_x11xinput2.c @@ -129,6 +129,11 @@ bool X11_InitXinput2(SDL_VideoDevice *_this) unsigned char mask[4] = { 0, 0, 0, 0 }; int event, err; + /* XInput2 is required for relative mouse mode, so you probably want to leave this enabled */ + if (!SDL_GetHintBoolean("SDL_VIDEO_X11_XINPUT2", true)) { + return false; + } + /* * Initialize XInput 2 * According to http://who-t.blogspot.com/2009/05/xi2-recipes-part-1.html its better @@ -279,7 +284,7 @@ static SDL_XInput2DeviceInfo *xinput2_get_device_info(SDL_VideoData *videodata, void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) { #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 - SDL_VideoData *videodata = (SDL_VideoData *)_this->internal; + SDL_VideoData *videodata = _this->internal; if (cookie->extension != xinput2_opcode) { return; @@ -401,11 +406,15 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) case XI_ButtonRelease: { const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; - X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid); + X11_PenHandle *pen = X11_FindPenByDeviceID(xev->sourceid); const int button = xev->detail; const bool down = (cookie->evtype == XI_ButtonPress); if (pen) { + if (xev->deviceid != xev->sourceid) { + // Discard events from "Master" devices to avoid duplicates. + break; + } // Only report button event; if there was also pen movement / pressure changes, we expect an XI_Motion event first anyway. SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); if (button == 1) { // button 1 is the pen tip @@ -416,9 +425,12 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) } else { // Otherwise assume a regular mouse SDL_WindowData *windowdata = xinput2_get_sdlwindowdata(videodata, xev->event); + int x_ticks = 0, y_ticks = 0; - if (xev->deviceid != xev->sourceid) { - // Discard events from "Master" devices to avoid duplicates. + /* Discard wheel events from "Master" devices to avoid duplicates, + * as coarse wheel events are stateless and can't be deduplicated. + */ + if (xev->deviceid != xev->sourceid && X11_IsWheelEvent(button, &x_ticks, &y_ticks)) { break; } @@ -444,7 +456,8 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) videodata->global_mouse_changed = true; - X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid); + X11_PenHandle *pen = X11_FindPenByDeviceID(xev->sourceid); + if (pen) { if (xev->deviceid != xev->sourceid) { // Discard events from "Master" devices to avoid duplicates. @@ -550,10 +563,10 @@ bool X11_Xinput2IsInitialized(void) bool X11_Xinput2SelectMouseAndKeyboard(SDL_VideoDevice *_this, SDL_Window *window) { - SDL_WindowData *windowdata = (SDL_WindowData *)window->internal; + SDL_WindowData *windowdata = window->internal; #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 - const SDL_VideoData *data = (SDL_VideoData *)_this->internal; + const SDL_VideoData *data = _this->internal; if (X11_Xinput2IsInitialized()) { XIEventMask eventmask; diff --git a/libs/SDL3/src/video/x11/SDL_x11xsync.c b/libs/SDL3/src/video/x11/SDL_x11xsync.c index 5981f6f..5310d67 100644 --- a/libs/SDL3/src/video/x11/SDL_x11xsync.c +++ b/libs/SDL3/src/video/x11/SDL_x11xsync.c @@ -42,7 +42,7 @@ static bool xsync_version_atleast(const int version, const int wantmajor, const void X11_InitXsync(SDL_VideoDevice *_this) { - SDL_VideoData *data = (SDL_VideoData *) _this->internal; + SDL_VideoData *data = _this->internal; int version = 0; int event, error; @@ -70,7 +70,7 @@ int X11_XsyncIsInitialized(void) int X11_InitResizeSync(SDL_Window *window) { SDL_assert(window != NULL); - SDL_WindowData *data = (SDL_WindowData *) window->internal; + SDL_WindowData *data = window->internal; Display *display = data->videodata->display; Atom counter_prop = data->videodata->atoms._NET_WM_SYNC_REQUEST_COUNTER; XSyncCounter counter; @@ -99,7 +99,7 @@ int X11_InitResizeSync(SDL_Window *window) void X11_TermResizeSync(SDL_Window *window) { - SDL_WindowData *data = (SDL_WindowData *) window->internal; + SDL_WindowData *data = window->internal; Display *display = data->videodata->display; Atom counter_prop = data->videodata->atoms._NET_WM_SYNC_REQUEST_COUNTER; XSyncCounter counter = data->resize_counter; @@ -112,7 +112,7 @@ void X11_TermResizeSync(SDL_Window *window) void X11_HandleSyncRequest(SDL_Window *window, XClientMessageEvent *event) { - SDL_WindowData *data = (SDL_WindowData *) window->internal; + SDL_WindowData *data = window->internal; data->resize_id.lo = event->data.l[2]; data->resize_id.hi = event->data.l[3]; @@ -121,7 +121,7 @@ void X11_HandleSyncRequest(SDL_Window *window, XClientMessageEvent *event) void X11_HandleConfigure(SDL_Window *window, XConfigureEvent *event) { - SDL_WindowData *data = (SDL_WindowData *) window->internal; + SDL_WindowData *data = window->internal; if (data->resize_id.lo || data->resize_id.hi) { data->resize_in_progress = true; @@ -130,7 +130,7 @@ void X11_HandleConfigure(SDL_Window *window, XConfigureEvent *event) void X11_HandlePresent(SDL_Window *window) { - SDL_WindowData *data = (SDL_WindowData *) window->internal; + SDL_WindowData *data = window->internal; Display *display = data->videodata->display; XSyncCounter counter = data->resize_counter; diff --git a/libs/SDL3/test/CMakeLists.txt b/libs/SDL3/test/CMakeLists.txt index 3be04e2..8bf5c25 100644 --- a/libs/SDL3/test/CMakeLists.txt +++ b/libs/SDL3/test/CMakeLists.txt @@ -411,7 +411,7 @@ add_sdl_test_executable(testdialog SOURCES testdialog.c) add_sdl_test_executable(testtime SOURCES testtime.c) add_sdl_test_executable(testmanymouse SOURCES testmanymouse.c) add_sdl_test_executable(testmodal SOURCES testmodal.c) -add_sdl_test_executable(testtray SOURCES testtray.c) +add_sdl_test_executable(testtray NEEDS_RESOURCES TESTUTILS SOURCES testtray.c) add_sdl_test_executable(testprocess diff --git a/libs/SDL3/test/testaudio.c b/libs/SDL3/test/testaudio.c index c39861f..8abb3b2 100644 --- a/libs/SDL3/test/testaudio.c +++ b/libs/SDL3/test/testaudio.c @@ -300,6 +300,7 @@ static void DestroyThing(Thing *thing) } if (thing->prev) { + SDL_assert(thing != things); thing->prev->next = thing->next; } else { SDL_assert(thing == things); diff --git a/libs/SDL3/test/testaudiohotplug.c b/libs/SDL3/test/testaudiohotplug.c index 9f22158..00b851e 100644 --- a/libs/SDL3/test/testaudiohotplug.c +++ b/libs/SDL3/test/testaudiohotplug.c @@ -69,7 +69,7 @@ static void iteration(void) done = 1; } } else if (e.type == SDL_EVENT_AUDIO_DEVICE_ADDED) { - const SDL_AudioDeviceID which = (SDL_AudioDeviceID) e.adevice.which; + const SDL_AudioDeviceID which = e.adevice.which; const bool recording = e.adevice.recording ? true : false; const char *name = SDL_GetAudioDeviceName(which); if (name) { @@ -93,7 +93,7 @@ static void iteration(void) } } } else if (e.type == SDL_EVENT_AUDIO_DEVICE_REMOVED) { - dev = (SDL_AudioDeviceID)e.adevice.which; + dev = e.adevice.which; SDL_Log("%s device %u removed.", devtypestr(e.adevice.recording), (unsigned int)dev); /* !!! FIXME: we need to keep track of our streams and destroy them here. */ } diff --git a/libs/SDL3/test/testautomation_audio.c b/libs/SDL3/test/testautomation_audio.c index 9d03338..7c141b3 100644 --- a/libs/SDL3/test/testautomation_audio.c +++ b/libs/SDL3/test/testautomation_audio.c @@ -809,6 +809,7 @@ static int SDLCALL audio_convertAudio(void *arg) src_buf = (Uint8 *)SDL_malloc(src_len); SDLTest_AssertCheck(src_buf != NULL, "Check src data buffer to convert is not NULL"); if (src_buf == NULL) { + SDL_DestroyAudioStream(stream); return TEST_ABORTED; } @@ -819,6 +820,8 @@ static int SDLCALL audio_convertAudio(void *arg) dst_buf = (Uint8 *)SDL_malloc(dst_len); SDLTest_AssertCheck(dst_buf != NULL, "Check dst data buffer to convert is not NULL"); if (dst_buf == NULL) { + SDL_DestroyAudioStream(stream); + SDL_free(src_buf); return TEST_ABORTED; } @@ -828,6 +831,9 @@ static int SDLCALL audio_convertAudio(void *arg) /* Run the audio converter */ if (!SDL_PutAudioStreamData(stream, src_buf, src_len) || !SDL_FlushAudioStream(stream)) { + SDL_DestroyAudioStream(stream); + SDL_free(src_buf); + SDL_free(dst_buf); return TEST_ABORTED; } @@ -837,6 +843,9 @@ static int SDLCALL audio_convertAudio(void *arg) real_dst_len = SDL_GetAudioStreamData(stream, dst_buf, dst_len); SDLTest_AssertCheck(dst_len == real_dst_len, "Verify result value; expected: %i; got: %i", dst_len, real_dst_len); if (dst_len != real_dst_len) { + SDL_DestroyAudioStream(stream); + SDL_free(src_buf); + SDL_free(dst_buf); return TEST_ABORTED; } @@ -848,6 +857,9 @@ static int SDLCALL audio_convertAudio(void *arg) for (m = 0; m < dst_len; ++m) { if (dst_buf[m] != dst_silence) { SDLTest_LogError("Output buffer is not silent"); + SDL_DestroyAudioStream(stream); + SDL_free(src_buf); + SDL_free(dst_buf); return TEST_ABORTED; } } @@ -1104,6 +1116,7 @@ static int SDLCALL audio_resampleLoss(void *arg) SDLTest_AssertCheck(buf_out != NULL, "Expected output buffer to be created."); if (buf_out == NULL) { SDL_DestroyAudioStream(stream); + SDL_free(buf_in); return TEST_ABORTED; } @@ -1114,6 +1127,7 @@ static int SDLCALL audio_resampleLoss(void *arg) SDL_free(buf_in); if (len_out != len_target) { SDL_DestroyAudioStream(stream); + SDL_free(buf_out); return TEST_ABORTED; } @@ -1130,6 +1144,7 @@ static int SDLCALL audio_resampleLoss(void *arg) sum_squared_value += target * target; } } + SDL_DestroyAudioStream(stream); SDL_free(buf_out); signal_to_noise = 10 * SDL_log10(sum_squared_value / sum_squared_error); /* decibel */ SDLTest_AssertCheck(ISFINITE(sum_squared_value), "Sum of squared target should be finite."); diff --git a/libs/SDL3/test/testautomation_events.c b/libs/SDL3/test/testautomation_events.c index aca6faf..ed4e684 100644 --- a/libs/SDL3/test/testautomation_events.c +++ b/libs/SDL3/test/testautomation_events.c @@ -211,48 +211,93 @@ static int SDLCALL events_addDelEventWatchWithUserdata(void *arg) * */ +typedef struct IncrementCounterData_t +{ + Uint32 delay; + int counter; +} IncrementCounterData_t; + static void SDLCALL IncrementCounter(void *userdata) { - int *value = (int *)userdata; - *value = *value + 1; + IncrementCounterData_t *data = (IncrementCounterData_t *)userdata; + ++data->counter; } #ifndef SDL_PLATFORM_EMSCRIPTEN /* Emscripten doesn't have threads */ static int SDLCALL IncrementCounterThread(void *userdata) { + IncrementCounterData_t *data = (IncrementCounterData_t *)userdata; + SDL_Event event; + SDL_assert(!SDL_IsMainThread()); - SDL_RunOnMainThread(IncrementCounter, userdata, false); - SDL_RunOnMainThread(IncrementCounter, userdata, true); + + if (data->delay > 0) { + SDL_Delay(data->delay); + } + + if (!SDL_RunOnMainThread(IncrementCounter, userdata, false)) { + SDLTest_LogError("Couldn't run IncrementCounter asynchronously on main thread: %s", SDL_GetError()); + } + if (!SDL_RunOnMainThread(IncrementCounter, userdata, true)) { + SDLTest_LogError("Couldn't run IncrementCounter synchronously on main thread: %s", SDL_GetError()); + } + + /* Send an event to unblock the main thread, which is waiting in SDL_WaitEvent() */ + event.type = SDL_EVENT_USER; + SDL_PushEvent(&event); + return 0; } #endif /* !SDL_PLATFORM_EMSCRIPTEN */ static int SDLCALL events_mainThreadCallbacks(void *arg) { - int counter = 0; + IncrementCounterData_t data = { 0, 0 }; /* Make sure we're on the main thread */ SDLTest_AssertCheck(SDL_IsMainThread(), "Verify we're on the main thread"); - SDL_RunOnMainThread(IncrementCounter, &counter, true); - SDLTest_AssertCheck(counter == 1, "Incremented counter on main thread, expected 1, got %d", counter); + SDL_RunOnMainThread(IncrementCounter, &data, true); + SDLTest_AssertCheck(data.counter == 1, "Incremented counter on main thread, expected 1, got %d", data.counter); #ifndef SDL_PLATFORM_EMSCRIPTEN /* Emscripten doesn't have threads */ { + SDL_Window *window; SDL_Thread *thread; + SDL_Event event; - thread = SDL_CreateThread(IncrementCounterThread, NULL, &counter); + window = SDL_CreateWindow("test", 0, 0, SDL_WINDOW_HIDDEN); + SDLTest_AssertCheck(window != NULL, "Create window, expected non-NULL, got %p", window); + + /* Flush any pending events */ + SDL_PumpEvents(); + SDL_FlushEvents(SDL_EVENT_FIRST, SDL_EVENT_LAST); + + /* Increment the counter on a thread, waiting for both calls to be queued */ + thread = SDL_CreateThread(IncrementCounterThread, NULL, &data); SDLTest_AssertCheck(thread != NULL, "Create counter thread"); /* Wait for both increment calls to be queued up */ SDL_Delay(100); /* Run the main callbacks */ - while (counter < 3) { - SDL_PumpEvents(); - } + SDL_WaitEvent(&event); + SDLTest_AssertCheck(event.type == SDL_EVENT_USER, "Expected user event (0x%.4x), got 0x%.4x", SDL_EVENT_USER, (int)event.type); SDL_WaitThread(thread, NULL); - SDLTest_AssertCheck(counter == 3, "Incremented counter on main thread, expected 3, got %d", counter); + SDLTest_AssertCheck(data.counter == 3, "Incremented counter on main thread, expected 3, got %d", data.counter); + + /* Try again, but this time delay the calls until we've started waiting for events */ + data.delay = 100; + thread = SDL_CreateThread(IncrementCounterThread, NULL, &data); + SDLTest_AssertCheck(thread != NULL, "Create counter thread"); + + /* Run the main callbacks */ + SDL_WaitEvent(&event); + SDLTest_AssertCheck(event.type == SDL_EVENT_USER, "Expected user event (0x%.4x), got 0x%.4x", SDL_EVENT_USER, (int)event.type); + SDL_WaitThread(thread, NULL); + SDLTest_AssertCheck(data.counter == 5, "Incremented counter on main thread, expected 5, got %d", data.counter); + + SDL_DestroyWindow(window); } #endif /* !SDL_PLATFORM_EMSCRIPTEN */ diff --git a/libs/SDL3/test/testautomation_intrinsics.c b/libs/SDL3/test/testautomation_intrinsics.c index 4b9cb65..8338d33 100644 --- a/libs/SDL3/test/testautomation_intrinsics.c +++ b/libs/SDL3/test/testautomation_intrinsics.c @@ -323,6 +323,7 @@ static int SDLCALL intrinsics_selftest(void *arg) size_t size; Uint32 *dest, *a, *b; if (allocate_random_uint_arrays(&dest, &a, &b, &size) < 0) { + free_arrays(dest, a, b); return TEST_ABORTED; } kernel_uints_mul_cpu(dest, a, b, size); @@ -333,6 +334,7 @@ static int SDLCALL intrinsics_selftest(void *arg) size_t size; Uint32 *dest, *a, *b; if (allocate_random_uint_arrays(&dest, &a, &b, &size) < 0) { + free_arrays(dest, a, b); return TEST_ABORTED; } kernel_uints_add_cpu(dest, a, b, size); @@ -343,6 +345,7 @@ static int SDLCALL intrinsics_selftest(void *arg) size_t size; float *dest, *a, *b; if (allocate_random_float_arrays(&dest, &a, &b, &size) < 0) { + free_arrays(dest, a, b); return TEST_ABORTED; } kernel_floats_add_cpu(dest, a, b, size); @@ -353,6 +356,7 @@ static int SDLCALL intrinsics_selftest(void *arg) size_t size; double *dest, *a, *b; if (allocate_random_double_arrays(&dest, &a, &b, &size) < 0) { + free_arrays(dest, a, b); return TEST_ABORTED; } kernel_doubles_add_cpu(dest, a, b, size); @@ -373,6 +377,7 @@ static int SDLCALL intrinsics_testMMX(void *arg) SDLTest_AssertCheck(true, "Test executable uses MMX intrinsics."); if (allocate_random_uint_arrays(&dest, &a, &b, &size) < 0) { + free_arrays(dest, a, b); return TEST_ABORTED; } kernel_uints_add_mmx(dest, a, b, size); @@ -401,6 +406,7 @@ static int SDLCALL intrinsics_testSSE(void *arg) SDLTest_AssertCheck(true, "Test executable uses SSE intrinsics."); if (allocate_random_float_arrays(&dest, &a, &b, &size) < 0) { + free_arrays(dest, a, b); return TEST_ABORTED; } kernel_floats_add_sse(dest, a, b, size); @@ -429,6 +435,7 @@ static int SDLCALL intrinsics_testSSE2(void *arg) SDLTest_AssertCheck(true, "Test executable uses SSE2 intrinsics."); if (allocate_random_double_arrays(&dest, &a, &b, &size) < 0) { + free_arrays(dest, a, b); return TEST_ABORTED; } kernel_doubles_add_sse2(dest, a, b, size); @@ -457,6 +464,7 @@ static int SDLCALL intrinsics_testSSE3(void *arg) SDLTest_AssertCheck(true, "Test executable uses SSE3 intrinsics."); if (allocate_random_uint_arrays(&dest, &a, &b, &size) < 0) { + free_arrays(dest, a, b); return TEST_ABORTED; } kernel_uints_add_sse3(dest, a, b, size); @@ -485,6 +493,7 @@ static int SDLCALL intrinsics_testSSE4_1(void *arg) SDLTest_AssertCheck(true, "Test executable uses SSE4.1 intrinsics."); if (allocate_random_uint_arrays(&dest, &a, &b, &size) < 0) { + free_arrays(dest, a, b); return TEST_ABORTED; } kernel_uints_mul_sse4_1(dest, a, b, size); @@ -548,6 +557,7 @@ static int SDLCALL intrinsics_testAVX(void *arg) SDLTest_AssertCheck(true, "Test executable uses AVX intrinsics."); if (allocate_random_float_arrays(&dest, &a, &b, &size) < 0) { + free_arrays(dest, a, b); return TEST_ABORTED; } kernel_floats_add_avx(dest, a, b, size); @@ -576,6 +586,7 @@ static int SDLCALL intrinsics_testAVX2(void *arg) SDLTest_AssertCheck(true, "Test executable uses AVX2 intrinsics."); if (allocate_random_uint_arrays(&dest, &a, &b, &size) < 0) { + free_arrays(dest, a, b); return TEST_ABORTED; } kernel_uints_add_avx2(dest, a, b, size); @@ -604,6 +615,7 @@ static int SDLCALL intrinsics_testAVX512F(void *arg) SDLTest_AssertCheck(true, "Test executable uses AVX512F intrinsics."); if (allocate_random_float_arrays(&dest, &a, &b, &size) < 0) { + free_arrays(dest, a, b); return TEST_ABORTED; } kernel_floats_add_avx512f(dest, a, b, size); diff --git a/libs/SDL3/test/testautomation_render.c b/libs/SDL3/test/testautomation_render.c index 25572ee..9a0503d 100644 --- a/libs/SDL3/test/testautomation_render.c +++ b/libs/SDL3/test/testautomation_render.c @@ -1439,8 +1439,8 @@ static int SDLCALL render_testUVWrapping(void *arg) rect.w = (float)tface->w * 2; rect.h = (float)tface->h * 2; - rect.x = (float)(TESTRENDER_SCREEN_W - rect.w) / 2; - rect.y = (float)(TESTRENDER_SCREEN_H - rect.h) / 2; + rect.x = (TESTRENDER_SCREEN_W - rect.w) / 2; + rect.y = (TESTRENDER_SCREEN_H - rect.h) / 2; /* * 0--1 @@ -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 3073510..e3f2d47 100644 --- a/libs/SDL3/test/testautomation_stdlib.c +++ b/libs/SDL3/test/testautomation_stdlib.c @@ -731,6 +731,7 @@ static int SDLCALL stdlib_getsetenv(void *arg) #endif #define FMT_PRILLd "%" SDL_PRILLd +#define FMT_PRILLdn "%" SDL_PRILLd "%" SDL_PRILL_PREFIX "n" #define FMT_PRILLu "%" SDL_PRILLu /** @@ -740,14 +741,16 @@ static int SDLCALL stdlib_sscanf(void *arg) { int output; int result; + int length; int expected_output; int expected_result; - short short_output, expected_short_output; - long long_output, expected_long_output; - long long long_long_output, expected_long_long_output; + short short_output, expected_short_output, short_length; + long long_output, expected_long_output, long_length; + long long long_long_output, expected_long_long_output, long_long_length; 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; @@ -764,43 +767,62 @@ static int SDLCALL stdlib_sscanf(void *arg) SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); output = 123; + length = 0; expected_output = 2; expected_result = 1; - result = SDL_sscanf("2", "%i", &output); - SDLTest_AssertPass("Call to SDL_sscanf(\"2\", \"%%i\", &output)"); + result = SDL_sscanf("2", "%i%n", &output, &length); + SDLTest_AssertPass("Call to SDL_sscanf(\"2\", \"%%i%%n\", &output, &length)"); SDLTest_AssertCheck(expected_output == output, "Check output, expected: %i, got: %i", expected_output, output); 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); output = 123; + length = 0; expected_output = 0xa; expected_result = 1; - result = SDL_sscanf("aa", "%1x", &output); - SDLTest_AssertPass("Call to SDL_sscanf(\"aa\", \"%%1x\", &output)"); + result = SDL_sscanf("aa", "%1x%n", &output, &length); + SDLTest_AssertPass("Call to SDL_sscanf(\"aa\", \"%%1x%%n\", &output, &length)"); SDLTest_AssertCheck(expected_output == output, "Check output, expected: %i, got: %i", expected_output, output); 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); -#define SIZED_TEST_CASE(type, var, format_specifier) \ - var##_output = 123; \ - expected_##var##_output = (type)(((unsigned type)(~0)) >> 1); \ - expected_result = 1; \ - result = SDL_snprintf(text, sizeof(text), format_specifier, expected_##var##_output); \ - result = SDL_sscanf(text, format_specifier, &var##_output); \ - SDLTest_AssertPass("Call to SDL_sscanf(\"%s\", \"%s\", &output)", text, #format_specifier); \ - SDLTest_AssertCheck(expected_##var##_output == var##_output, "Check output, expected: " format_specifier ", got: " format_specifier, expected_##var##_output, var##_output); \ - SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); \ - \ - var##_output = 123; \ - expected_##var##_output = ~(type)(((unsigned type)(~0)) >> 1); \ - expected_result = 1; \ - result = SDL_snprintf(text, sizeof(text), format_specifier, expected_##var##_output); \ - result = SDL_sscanf(text, format_specifier, &var##_output); \ - SDLTest_AssertPass("Call to SDL_sscanf(\"%s\", \"%s\", &output)", text, #format_specifier); \ - SDLTest_AssertCheck(expected_##var##_output == var##_output, "Check output, expected: " format_specifier ", got: " format_specifier, expected_##var##_output, var##_output); \ + 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); - SIZED_TEST_CASE(short, short, "%hd") - SIZED_TEST_CASE(long, long, "%ld") - SIZED_TEST_CASE(long long, long_long, FMT_PRILLd) +#define SIZED_TEST_CASE(type, var, printf_specifier, scanf_specifier) \ + var##_output = 123; \ + var##_length = 0; \ + expected_##var##_output = (type)(((unsigned type)(~0)) >> 1); \ + expected_result = 1; \ + result = SDL_snprintf(text, sizeof(text), printf_specifier, expected_##var##_output); \ + result = SDL_sscanf(text, scanf_specifier, &var##_output, &var##_length); \ + SDLTest_AssertPass("Call to SDL_sscanf(\"%s\", %s, &output, &length)", text, #scanf_specifier); \ + SDLTest_AssertCheck(expected_##var##_output == var##_output, "Check output, expected: " printf_specifier ", got: " printf_specifier, expected_##var##_output, var##_output); \ + SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); \ + SDLTest_AssertCheck(var##_length == (type)SDL_strlen(text), "Check length, expected: %i, got: %i", (int)SDL_strlen(text), (int)var##_length); \ + \ + var##_output = 123; \ + var##_length = 0; \ + expected_##var##_output = ~(type)(((unsigned type)(~0)) >> 1); \ + expected_result = 1; \ + result = SDL_snprintf(text, sizeof(text), printf_specifier, expected_##var##_output); \ + result = SDL_sscanf(text, scanf_specifier, &var##_output, &var##_length); \ + SDLTest_AssertPass("Call to SDL_sscanf(\"%s\", %s, &output, &length)", text, #scanf_specifier); \ + SDLTest_AssertCheck(expected_##var##_output == var##_output, "Check output, expected: " printf_specifier ", got: " printf_specifier, expected_##var##_output, var##_output); \ + SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); \ + SDLTest_AssertCheck(var##_length == (type)SDL_strlen(text), "Check length, expected: %i, got: %i", (int)SDL_strlen(text), (int)var##_length); \ + + SIZED_TEST_CASE(short, short, "%hd", "%hd%hn") + SIZED_TEST_CASE(long, long, "%ld", "%ld%ln") + SIZED_TEST_CASE(long long, long_long, FMT_PRILLd, FMT_PRILLdn) size_output = 123; expected_size_output = ~((size_t)0); diff --git a/libs/SDL3/test/testautomation_surface.c b/libs/SDL3/test/testautomation_surface.c index c1d641e..2a52fbb 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 */ @@ -941,6 +959,75 @@ static int SDLCALL surface_testBlitBlendMul(void *arg) return TEST_COMPLETED; } +/** + * Tests blitting invalid surfaces. + */ +static int SDLCALL surface_testBlitInvalid(void *arg) +{ + SDL_Surface *valid, *invalid; + bool result; + + valid = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA8888); + SDLTest_AssertCheck(valid != NULL, "Check surface creation"); + invalid = SDL_CreateSurface(0, 0, SDL_PIXELFORMAT_RGBA8888); + SDLTest_AssertCheck(invalid != NULL, "Check surface creation"); + SDLTest_AssertCheck(invalid->pixels == NULL, "Check surface pixels are NULL"); + + result = SDL_BlitSurface(invalid, NULL, valid, NULL); + SDLTest_AssertCheck(result == true, "SDL_BlitSurface(invalid, NULL, valid, NULL), result = %s\n", result ? "true" : "false"); + result = SDL_BlitSurface(valid, NULL, invalid, NULL); + SDLTest_AssertCheck(result == true, "SDL_BlitSurface(valid, NULL, invalid, NULL), result = %s\n", result ? "true" : "false"); + + result = SDL_BlitSurfaceScaled(invalid, NULL, valid, NULL, SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(result == false, "SDL_BlitSurfaceScaled(invalid, NULL, valid, NULL, SDL_SCALEMODE_NEAREST), result = %s\n", result ? "true" : "false"); + result = SDL_BlitSurfaceScaled(valid, NULL, invalid, NULL, SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(result == false, "SDL_BlitSurfaceScaled(valid, NULL, invalid, NULL, SDL_SCALEMODE_NEAREST), result = %s\n", result ? "true" : "false"); + + SDL_DestroySurface(valid); + SDL_DestroySurface(invalid); + + return TEST_COMPLETED; +} + +static int SDLCALL surface_testBlitsWithBadCoordinates(void *arg) +{ + const SDL_Rect rect[8] = { + { SDL_MAX_SINT32, 0, 2, 2 }, + { 0, SDL_MAX_SINT32, 2, 2 }, + { 0, 0, SDL_MAX_SINT32, 2 }, + { 0, 0, 2, SDL_MAX_SINT32 }, + { SDL_MIN_SINT32, 0, 2, 2 }, + { 0, SDL_MIN_SINT32, 2, 2 }, + { 0, 0, SDL_MIN_SINT32, 2 }, + { 0, 0, 2, SDL_MIN_SINT32 } + }; + + SDL_Surface *s; + bool result; + int i; + + s = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA8888); + SDLTest_AssertCheck(s != NULL, "Check surface creation"); + + for (i = 0; i < 8; i++) { + result = SDL_BlitSurface(s, NULL, s, &rect[i]); + SDLTest_AssertCheck(result == true, "SDL_BlitSurface(valid, NULL, valid, &rect), result = %s", result ? "true" : "false"); + + result = SDL_BlitSurface(s, &rect[i], s, NULL); + SDLTest_AssertCheck(result == true, "SDL_BlitSurface(valid, &rect, valid, NULL), result = %s", result ? "true" : "false"); + + result = SDL_BlitSurfaceScaled(s, NULL, s, &rect[i], SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(result == true, "SDL_BlitSurfaceScaled(valid, NULL, valid, &rect, SDL_SCALEMODE_NEAREST), result = %s", result ? "true" : "false"); + + result = SDL_BlitSurfaceScaled(s, &rect[i], s, NULL, SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(result == true, "SDL_BlitSurfaceScaled(valid, &rect, valid, NULL, SDL_SCALEMODE_NEAREST), result = %s", result ? "true" : "false"); + } + + SDL_DestroySurface(s); + + return TEST_COMPLETED; +} + static int SDLCALL surface_testOverflow(void *arg) { char buf[1024]; @@ -1540,6 +1627,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 }; @@ -1608,6 +1699,14 @@ static const SDLTest_TestCaseReference surfaceTestBlitBlendMul = { surface_testBlitBlendMul, "surface_testBlitBlendMul", "Tests blitting routines with mul blending mode.", TEST_ENABLED }; +static const SDLTest_TestCaseReference surfaceTestBlitInvalid = { + surface_testBlitInvalid, "surface_testBlitInvalid", "Tests blitting routines with invalid surfaces.", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference surfaceTestBlitsWithBadCoordinates = { + surface_testBlitsWithBadCoordinates, "surface_testBlitsWithBadCoordinates", "Test blitting routines with bad coordinates.", TEST_ENABLED +}; + static const SDLTest_TestCaseReference surfaceTestOverflow = { surface_testOverflow, "surface_testOverflow", "Test overflow detection.", TEST_ENABLED }; @@ -1638,6 +1737,7 @@ static const SDLTest_TestCaseReference surfaceTestScale = { /* Sequence of Surface test cases */ static const SDLTest_TestCaseReference *surfaceTests[] = { + &surfaceTestInvalidFormat, &surfaceTestSaveLoadBitmap, &surfaceTestBlitZeroSource, &surfaceTestBlit, @@ -1655,6 +1755,8 @@ static const SDLTest_TestCaseReference *surfaceTests[] = { &surfaceTestBlitBlendAddPremultiplied, &surfaceTestBlitBlendMod, &surfaceTestBlitBlendMul, + &surfaceTestBlitInvalid, + &surfaceTestBlitsWithBadCoordinates, &surfaceTestOverflow, &surfaceTestFlip, &surfaceTestPalette, diff --git a/libs/SDL3/test/testcamera.c b/libs/SDL3/test/testcamera.c index 7d3e64c..b270921 100644 --- a/libs/SDL3/test/testcamera.c +++ b/libs/SDL3/test/testcamera.c @@ -97,8 +97,6 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) NULL, }; SDLTest_CommonLogUsage(state, argv[0], options); - SDL_Quit(); - SDLTest_CommonDestroyState(state); return SDL_APP_FAILURE; } i += consumed; diff --git a/libs/SDL3/test/testclipboard.c b/libs/SDL3/test/testclipboard.c index cb57bbf..9dad02b 100644 --- a/libs/SDL3/test/testclipboard.c +++ b/libs/SDL3/test/testclipboard.c @@ -63,6 +63,8 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) if (event->key.key == SDLK_C && event->key.mod & SDL_KMOD_CTRL) { SDL_SetClipboardData(ClipboardDataCallback, NULL, NULL, mime_types, SDL_arraysize(mime_types)); break; + } else if (event->key.key == SDLK_P && event->key.mod & SDL_KMOD_CTRL) { + SDL_SetPrimarySelectionText("SDL Primary Selection Text!"); } break; @@ -99,6 +101,15 @@ static float PrintClipboardText(float x, float y, const char *mime_type) return 0.0f; } +static float PrintPrimarySelectionText(float x, float y) +{ + if (SDL_HasPrimarySelectionText()) { + SDL_RenderDebugText(renderer, x, y, SDL_GetPrimarySelectionText()); + return SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE + 2.0f; + } + return 0.0f; +} + static float PrintClipboardImage(float x, float y, const char *mime_type) { /* We don't actually need to read this data each frame, but this is a simple example */ @@ -134,7 +145,7 @@ static float PrintClipboardImage(float x, float y, const char *mime_type) return 0.0f; } -static void PrintClipboardContents(float x, float y) +static float PrintClipboardContents(float x, float y) { char **clipboard_mime_types = SDL_GetClipboardMimeTypes(NULL); if (clipboard_mime_types) { @@ -152,6 +163,8 @@ static void PrintClipboardContents(float x, float y) } SDL_free(clipboard_mime_types); } + + return y; } SDL_AppResult SDL_AppIterate(void *appstate) @@ -164,10 +177,18 @@ SDL_AppResult SDL_AppIterate(void *appstate) float y = 4.0f; SDL_RenderDebugText(renderer, x, y, "Press Ctrl+C to copy content to the clipboard"); y += SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * 2; + SDL_RenderDebugText(renderer, x, y, "Press Ctrl+P to set the primary selection text"); + y += SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * 2; SDL_RenderDebugText(renderer, x, y, "Clipboard contents:"); x += SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * 2; y += SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE + 2; - PrintClipboardContents(x, y); + y = PrintClipboardContents(x, y); + if (SDL_HasPrimarySelectionText()) { + x = 4.0f; + SDL_RenderDebugText(renderer, x, y, "Primary selection text contents:"); + y += SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE + 2; + PrintPrimarySelectionText(x + SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * 2, y); + } SDL_RenderPresent(renderer); diff --git a/libs/SDL3/test/testcontroller.c b/libs/SDL3/test/testcontroller.c index 8f74c3b..b717b30 100644 --- a/libs/SDL3/test/testcontroller.c +++ b/libs/SDL3/test/testcontroller.c @@ -2101,7 +2101,7 @@ SDL_AppResult SDLCALL SDL_AppInit(void **appstate, int argc, char *argv[]) } screen_width = (int)SDL_ceilf(SCREEN_WIDTH * content_scale); screen_height = (int)SDL_ceilf(SCREEN_HEIGHT * content_scale); - window = SDL_CreateWindow("SDL Controller Test", screen_width, screen_height, 0); + window = SDL_CreateWindow("SDL Controller Test", screen_width, screen_height, SDL_WINDOW_HIGH_PIXEL_DENSITY); if (!window) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s", SDL_GetError()); return SDL_APP_FAILURE; diff --git a/libs/SDL3/test/testdialog.c b/libs/SDL3/test/testdialog.c index 3d19fb9..dcff5bd 100644 --- a/libs/SDL3/test/testdialog.c +++ b/libs/SDL3/test/testdialog.c @@ -15,8 +15,9 @@ #include #include -const SDL_DialogFileFilter filters[3] = { +const SDL_DialogFileFilter filters[] = { { "All files", "*" }, + { "SVI Session Indexes", "index;svi-index;index.pb" }, { "JPG images", "jpg;jpeg" }, { "PNG images", "png" } }; @@ -54,7 +55,6 @@ int main(int argc, char *argv[]) const SDL_FRect open_folder_rect = { 370, 50, 220, 140 }; int i; const char *initial_path = NULL; - const int nfilters = sizeof(filters) / sizeof(*filters); /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); @@ -112,11 +112,11 @@ int main(int argc, char *argv[]) * - Nonzero if the user is allowed to choose multiple entries (not for SDL_ShowSaveFileDialog) */ if (SDL_PointInRectFloat(&p, &open_file_rect)) { - SDL_ShowOpenFileDialog(callback, NULL, w, filters, nfilters, initial_path, 1); + SDL_ShowOpenFileDialog(callback, NULL, w, filters, SDL_arraysize(filters), initial_path, 1); } else if (SDL_PointInRectFloat(&p, &open_folder_rect)) { SDL_ShowOpenFolderDialog(callback, NULL, w, initial_path, 1); } else if (SDL_PointInRectFloat(&p, &save_file_rect)) { - SDL_ShowSaveFileDialog(callback, NULL, w, filters, nfilters, initial_path); + SDL_ShowSaveFileDialog(callback, NULL, w, filters, SDL_arraysize(filters), initial_path); } } } diff --git a/libs/SDL3/test/testdraw.c b/libs/SDL3/test/testdraw.c index 741706f..2ed6211 100644 --- a/libs/SDL3/test/testdraw.c +++ b/libs/SDL3/test/testdraw.c @@ -167,8 +167,8 @@ static void DrawRects(SDL_Renderer *renderer) rect.w = (float)SDL_rand(viewport.h / 2); rect.h = (float)SDL_rand(viewport.h / 2); - rect.x = (float)((SDL_rand(viewport.w * 2) - viewport.w) - (rect.w / 2)); - rect.y = (float)((SDL_rand(viewport.h * 2) - viewport.h) - (rect.h / 2)); + rect.x = (SDL_rand(viewport.w * 2) - viewport.w) - (rect.w / 2); + rect.y = (SDL_rand(viewport.h * 2) - viewport.h) - (rect.h / 2); SDL_RenderFillRect(renderer, &rect); } } diff --git a/libs/SDL3/test/testdrawchessboard.c b/libs/SDL3/test/testdrawchessboard.c index 0b6b119..c68771c 100644 --- a/libs/SDL3/test/testdrawchessboard.c +++ b/libs/SDL3/test/testdrawchessboard.c @@ -50,8 +50,8 @@ static void DrawChessBoard(void) rect.w = (float)(darea.w / 8); rect.h = (float)(darea.h / 8); - rect.x = (float)(x * rect.w); - rect.y = (float)(row * rect.h); + rect.x = x * rect.w; + rect.y = row * rect.h; x = x + 2; SDL_RenderFillRect(renderer, &rect); diff --git a/libs/SDL3/test/testffmpeg_vulkan.c b/libs/SDL3/test/testffmpeg_vulkan.c index 4f2ba81..a6da6cb 100644 --- a/libs/SDL3/test/testffmpeg_vulkan.c +++ b/libs/SDL3/test/testffmpeg_vulkan.c @@ -679,6 +679,16 @@ void SetupVulkanRenderProperties(VulkanVideoContext *context, SDL_PropertiesID p SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_GRAPHICS_QUEUE_FAMILY_INDEX_NUMBER, context->graphicsQueueFamilyIndex); } +#if LIBAVUTIL_VERSION_MAJOR >= 59 +static void AddQueueFamily(AVVulkanDeviceContext *ctx, int idx, int num, VkQueueFlagBits flags) +{ + AVVulkanDeviceQueueFamily *entry = &ctx->qf[ctx->nb_qf++]; + entry->idx = idx; + entry->num = num; + entry->flags = flags; +} +#endif /* LIBAVUTIL_VERSION_MAJOR */ + void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceContext *ctx) { ctx->get_proc_addr = context->vkGetInstanceProcAddr; @@ -690,6 +700,12 @@ void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceCon ctx->nb_enabled_inst_extensions = context->instanceExtensionsCount; ctx->enabled_dev_extensions = context->deviceExtensions; ctx->nb_enabled_dev_extensions = context->deviceExtensionsCount; +#if LIBAVUTIL_VERSION_MAJOR >= 59 + AddQueueFamily(ctx, context->graphicsQueueFamilyIndex, context->graphicsQueueCount, VK_QUEUE_GRAPHICS_BIT); + AddQueueFamily(ctx, context->transferQueueFamilyIndex, context->transferQueueCount, VK_QUEUE_TRANSFER_BIT); + AddQueueFamily(ctx, context->computeQueueFamilyIndex, context->computeQueueCount, VK_QUEUE_COMPUTE_BIT); + AddQueueFamily(ctx, context->decodeQueueFamilyIndex, context->decodeQueueCount, VK_QUEUE_VIDEO_DECODE_BIT_KHR); +#else ctx->queue_family_index = context->graphicsQueueFamilyIndex; ctx->nb_graphics_queues = context->graphicsQueueCount; ctx->queue_family_tx_index = context->transferQueueFamilyIndex; @@ -700,6 +716,7 @@ void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceCon ctx->nb_encode_queues = 0; ctx->queue_family_decode_index = context->decodeQueueFamilyIndex; ctx->nb_decode_queues = context->decodeQueueCount; +#endif /* LIBAVUTIL_VERSION_MAJOR */ } static int CreateCommandBuffers(VulkanVideoContext *context, SDL_Renderer *renderer) diff --git a/libs/SDL3/test/testgpu_spinning_cube.c b/libs/SDL3/test/testgpu_spinning_cube.c index b2f7aef..0eeb034 100644 --- a/libs/SDL3/test/testgpu_spinning_cube.c +++ b/libs/SDL3/test/testgpu_spinning_cube.c @@ -152,7 +152,7 @@ perspective_matrix(float fovy, float aspect, float znear, float zfar, float *r) * major. In-place multiplication is supported. */ static void -multiply_matrix(float *lhs, float *rhs, float *r) +multiply_matrix(const float *lhs, const float *rhs, float *r) { int i, j, k; float tmp[16]; diff --git a/libs/SDL3/test/testmodal.c b/libs/SDL3/test/testmodal.c index 9e5ea63..d898b89 100644 --- a/libs/SDL3/test/testmodal.c +++ b/libs/SDL3/test/testmodal.c @@ -97,7 +97,7 @@ int main(int argc, char *argv[]) } if (e.key.key == SDLK_M) { - if (SDL_SetWindowParent(w2, w2)) { + if (SDL_SetWindowParent(w2, w1)) { if (SDL_SetWindowModal(w2, true)) { SDL_SetWindowTitle(w2, "Modal Window"); } diff --git a/libs/SDL3/test/testpopup.c b/libs/SDL3/test/testpopup.c index 786f03c..987eab8 100644 --- a/libs/SDL3/test/testpopup.c +++ b/libs/SDL3/test/testpopup.c @@ -49,6 +49,9 @@ struct PopupWindow static struct PopupWindow *menus; static struct PopupWindow tooltip; +static bool no_constraints; +static bool no_grab; + /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ static void quit(int rc) { @@ -95,14 +98,27 @@ static bool create_popup(struct PopupWindow *new_popup, bool is_menu) const int w = is_menu ? MENU_WIDTH : TOOLTIP_WIDTH; const int h = is_menu ? MENU_HEIGHT : TOOLTIP_HEIGHT; const int v_off = is_menu ? 0 : 32; - const SDL_WindowFlags flags = is_menu ? SDL_WINDOW_POPUP_MENU : SDL_WINDOW_TOOLTIP; float x, y; focus = SDL_GetMouseFocus(); SDL_GetMouseState(&x, &y); - new_win = SDL_CreatePopupWindow(focus, - (int)x, (int)y + v_off, w, h, flags); + + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, focus); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_CONSTRAIN_POPUP_BOOLEAN, !no_constraints); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN, !no_grab); + if (is_menu) { + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN, true); + } else { + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_TOOLTIP_BOOLEAN, true); + } + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, w); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, h); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, (int)x); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, (int)y + v_off); + new_win = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); if (new_win) { new_renderer = SDL_CreateRenderer(new_win, state->renderdriver); @@ -249,8 +265,30 @@ int main(int argc, char *argv[]) } /* Parse commandline */ - if (!SDLTest_CommonDefaultArgs(state, argc, argv)) { - return 1; + for (i = 1; i < argc;) { + int consumed; + + consumed = SDLTest_CommonArg(state, i); + if (consumed == 0) { + consumed = -1; + if (SDL_strcasecmp(argv[i], "--no-constraints") == 0) { + no_constraints = true; + consumed = 1; + } else if (SDL_strcasecmp(argv[i], "--no-grab") == 0) { + no_grab = true; + consumed = 1; + } + } + if (consumed < 0) { + static const char *options[] = { + "[--no-constraints]", + "[--no-grab]", + NULL + }; + SDLTest_CommonLogUsage(state, argv[0], options); + return 1; + } + i += consumed; } if (!SDLTest_CommonInit(state)) { diff --git a/libs/SDL3/test/testrendercopyex.c b/libs/SDL3/test/testrendercopyex.c index b3b998b..e46d522 100644 --- a/libs/SDL3/test/testrendercopyex.c +++ b/libs/SDL3/test/testrendercopyex.c @@ -75,8 +75,8 @@ static void Draw(DrawState *s) s->scale_direction = 1; } } - s->sprite_rect.x = (float)((viewport.w - s->sprite_rect.w) / 2); - s->sprite_rect.y = (float)((viewport.h - s->sprite_rect.h) / 2); + s->sprite_rect.x = (viewport.w - s->sprite_rect.w) / 2; + s->sprite_rect.y = (viewport.h - s->sprite_rect.h) / 2; SDL_RenderTextureRotated(s->renderer, s->sprite, NULL, &s->sprite_rect, (double)s->sprite_rect.w, center, SDL_FLIP_NONE); diff --git a/libs/SDL3/test/testrendertarget.c b/libs/SDL3/test/testrendertarget.c index e5ef962..bad57fd 100644 --- a/libs/SDL3/test/testrendertarget.c +++ b/libs/SDL3/test/testrendertarget.c @@ -113,8 +113,8 @@ DrawComposite(DrawState *s) s->scale_direction = 1; } } - s->sprite_rect.x = (float)((viewport.w - s->sprite_rect.w) / 2); - s->sprite_rect.y = (float)((viewport.h - s->sprite_rect.h) / 2); + s->sprite_rect.x = (viewport.w - s->sprite_rect.w) / 2; + s->sprite_rect.y = (viewport.h - s->sprite_rect.h) / 2; SDL_RenderTexture(s->renderer, s->sprite, NULL, &s->sprite_rect); @@ -168,8 +168,8 @@ Draw(DrawState *s) s->scale_direction = 1; } } - s->sprite_rect.x = (float)((viewport.w - s->sprite_rect.w) / 2); - s->sprite_rect.y = (float)((viewport.h - s->sprite_rect.h) / 2); + s->sprite_rect.x = (viewport.w - s->sprite_rect.w) / 2; + s->sprite_rect.y = (viewport.h - s->sprite_rect.h) / 2; SDL_RenderTexture(s->renderer, s->sprite, NULL, &s->sprite_rect); diff --git a/libs/SDL3/test/testscale.c b/libs/SDL3/test/testscale.c index 571817f..a282b07 100644 --- a/libs/SDL3/test/testscale.c +++ b/libs/SDL3/test/testscale.c @@ -68,8 +68,8 @@ static void Draw(DrawState *s) s->scale_direction = 1; } } - s->sprite_rect.x = (float)((viewport.w - s->sprite_rect.w) / 2); - s->sprite_rect.y = (float)((viewport.h - s->sprite_rect.h) / 2); + s->sprite_rect.x = (viewport.w - s->sprite_rect.w) / 2; + s->sprite_rect.y = (viewport.h - s->sprite_rect.h) / 2; SDL_RenderTexture(s->renderer, s->sprite, NULL, &s->sprite_rect); diff --git a/libs/SDL3/test/testtray.c b/libs/SDL3/test/testtray.c index fdb12da..8046d60 100644 --- a/libs/SDL3/test/testtray.c +++ b/libs/SDL3/test/testtray.c @@ -1,3 +1,4 @@ +#include "testutils.h" #include #include #include @@ -520,14 +521,17 @@ int main(int argc, char **argv) goto quit; } - /* TODO: Resource paths? */ - SDL_Surface *icon = SDL_LoadBMP("../test/sdl-test_round.bmp"); + char *icon1filename = GetResourceFilename(NULL, "sdl-test_round.bmp"); + SDL_Surface *icon = SDL_LoadBMP(icon1filename); + SDL_free(icon1filename); if (!icon) { SDL_Log("Couldn't load icon 1, proceeding without: %s", SDL_GetError()); } - SDL_Surface *icon2 = SDL_LoadBMP("../test/speaker.bmp"); + char *icon2filename = GetResourceFilename(NULL, "speaker.bmp"); + SDL_Surface *icon2 = SDL_LoadBMP(icon2filename); + SDL_free(icon2filename); if (!icon2) { SDL_Log("Couldn't load icon 2, proceeding without: %s", SDL_GetError()); diff --git a/libs/SDL3/test/testviewport.c b/libs/SDL3/test/testviewport.c index 7554100..01e27e7 100644 --- a/libs/SDL3/test/testviewport.c +++ b/libs/SDL3/test/testviewport.c @@ -85,7 +85,7 @@ static void DrawOnViewport(SDL_Renderer *renderer) /* Add a box at the top */ rect.w = 8.0f; rect.h = 8.0f; - rect.x = (float)((viewport.w - rect.w) / 2); + rect.x = (viewport.w - rect.w) / 2; rect.y = 0.0f; SDL_RenderFillRect(renderer, &rect); diff --git a/libs/SDL3/wayland-protocols/color-management-v1.xml b/libs/SDL3/wayland-protocols/color-management-v1.xml new file mode 100644 index 0000000..7f8da78 --- /dev/null +++ b/libs/SDL3/wayland-protocols/color-management-v1.xml @@ -0,0 +1,1631 @@ + + + + Copyright 2019 Sebastian Wick + Copyright 2019 Erwin Burema + Copyright 2020 AMD + Copyright 2020-2024 Collabora, Ltd. + Copyright 2024 Xaver Hugl + Copyright 2022-2025 Red Hat, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + The aim of the color management extension is to allow clients to know + the color properties of outputs, and to tell the compositor about the color + properties of their content on surfaces. Doing this enables a compositor + to perform automatic color management of content for different outputs + according to how content is intended to look like. + + The color properties are represented as an image description object which + is immutable after it has been created. A wl_output always has an + associated image description that clients can observe. A wl_surface + always has an associated preferred image description as a hint chosen by + the compositor that clients can also observe. Clients can set an image + description on a wl_surface to denote the color characteristics of the + surface contents. + + An image description includes SDR and HDR colorimetry and encoding, HDR + metadata, and viewing environment parameters. An image description does + not include the properties set through color-representation extension. + It is expected that the color-representation extension is used in + conjunction with the color management extension when necessary, + particularly with the YUV family of pixel formats. + + Recommendation ITU-T H.273 + "Coding-independent code points for video signal type identification" + shall be referred to as simply H.273 here. + + The color-and-hdr repository + (https://gitlab.freedesktop.org/pq/color-and-hdr) contains + background information on the protocol design and legacy color management. + It also contains a glossary, learning resources for digital color, tools, + samples and more. + + The terminology used in this protocol is based on common color science and + color encoding terminology where possible. The glossary in the color-and-hdr + repository shall be the authority on the definition of terms in this + protocol. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + A singleton global interface used for getting color management extensions + for wl_surface and wl_output objects, and for creating client defined + image description objects. The extension interfaces allow + getting the image description of outputs and setting the image + description of surfaces. + + Compositors should never remove this global. + + + + + Destroy the wp_color_manager_v1 object. This does not affect any other + objects in any way. + + + + + + + + + + + See the ICC.1:2022 specification from the International Color Consortium + for more details about rendering intents. + + The principles of ICC defined rendering intents apply with all types of + image descriptions, not only those with ICC file profiles. + + Compositors must support the perceptual rendering intent. Other + rendering intents are optional. + + + + + + + + + + + + + + + + + + + + The compositor supports set_mastering_display_primaries request with a + target color volume fully contained inside the primary color volume. + + + + + The compositor additionally supports target color volumes that + extend outside of the primary color volume. + + This can only be advertised if feature set_mastering_display_primaries + is supported as well. + + + + + + + + Named color primaries used to encode well-known sets of primaries. H.273 + is the authority, when it comes to the exact values of primaries and + authoritative specifications, where an equivalent code point exists. + + A value of 0 is invalid and will never be present in the list of enums. + + Descriptions do list the specifications for convenience. + + + + + Color primaries as defined by + - Rec. ITU-R BT.709-6 + - Rec. ITU-R BT.1361-0 conventional colour gamut system and extended + colour gamut system (historical) + - IEC 61966-2-1 sRGB or sYCC + - IEC 61966-2-4 + - Society of Motion Picture and Television Engineers (SMPTE) RP 177 + (1993) Annex B + Equivalent to H.273 ColourPrimaries code point 1. + + + + + Color primaries as defined by + - Rec. ITU-R BT.470-6 System M (historical) + - United States National Television System Committee 1953 + Recommendation for transmission standards for color television + - United States Federal Communications Commission (2003) Title 47 Code + of Federal Regulations 73.682 (a)(20) + Equivalent to H.273 ColourPrimaries code point 4. + + + + + Color primaries as defined by + - Rec. ITU-R BT.470-6 System B, G (historical) + - Rec. ITU-R BT.601-7 625 + - Rec. ITU-R BT.1358-0 625 (historical) + - Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM + Equivalent to H.273 ColourPrimaries code point 5. + + + + + Color primaries as defined by + - Rec. ITU-R BT.601-7 525 + - Rec. ITU-R BT.1358-1 525 or 625 (historical) + - Rec. ITU-R BT.1700-0 NTSC + - SMPTE 170M (2004) + - SMPTE 240M (1999) (historical) + Equivalent to H.273 ColourPrimaries code point 6 and 7. + + + + + Color primaries as defined by H.273 for generic film. + Equivalent to H.273 ColourPrimaries code point 8. + + + + + Color primaries as defined by + - Rec. ITU-R BT.2020-2 + - Rec. ITU-R BT.2100-0 + Equivalent to H.273 ColourPrimaries code point 9. + + + + + Color primaries as defined as the maximum of the CIE 1931 XYZ color + space by + - SMPTE ST 428-1 + - (CIE 1931 XYZ as in ISO 11664-1) + Equivalent to H.273 ColourPrimaries code point 10. + + + + + Color primaries as defined by Digital Cinema System and published in + SMPTE RP 431-2 (2011). Equivalent to H.273 ColourPrimaries code point + 11. + + + + + Color primaries as defined by Digital Cinema System and published in + SMPTE EG 432-1 (2010). + Equivalent to H.273 ColourPrimaries code point 12. + + + + + Color primaries as defined by Adobe as "Adobe RGB" and later published + by ISO 12640-4 (2011). + + + + + + + Named transfer functions used to represent well-known transfer + characteristics. H.273 is the authority, when it comes to the exact + formulas and authoritative specifications, where an equivalent code + point exists. + + A value of 0 is invalid and will never be present in the list of enums. + + Descriptions do list the specifications for convenience. + + + + + Rec. ITU-R BT.1886 is the display transfer characteristic assumed by + - Rec. ITU-R BT.601-7 525 and 625 + - Rec. ITU-R BT.709-6 + - Rec. ITU-R BT.2020-2 + These recommendations are referred to by H.273 TransferCharacteristics + code points 1, 6, 14, and 15, which are all equivalent. + + This TF implies these default luminances from Rec. ITU-R BT.2035: + - primary color volume minimum: 0.01 cd/m² + - primary color volume maximum: 100 cd/m² + - reference white: 100 cd/m² + + + + + Transfer characteristics as defined by + - Rec. ITU-R BT.470-6 System M (historical) + - United States National Television System Committee 1953 + Recommendation for transmission standards for color television + - United States Federal Communications Commission (2003) Title 47 Code + of Federal Regulations 73.682 (a) (20) + - Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM + Equivalent to H.273 TransferCharacteristics code point 4. + + + + + Transfer characteristics as defined by + - Rec. ITU-R BT.470-6 System B, G (historical) + Equivalent to H.273 TransferCharacteristics code point 5. + + + + + Transfer characteristics as defined by + - SMPTE ST 240 (1999) + Equivalent to H.273 TransferCharacteristics code point 7. + + + + + Linear transfer function defined over all real numbers. + Normalised electrical values are equal the normalised optical values. + + The differences to H.273 TransferCharacteristics code point 8 are + the definition over all real numbers. + + + + + Logarithmic transfer characteristic (100:1 range). + Equivalent to H.273 TransferCharacteristics code point 9. + + + + + Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range). + Equivalent to H.273 TransferCharacteristics code point 10. + + + + + Transfer characteristics as defined by + - IEC 61966-2-4 + Equivalent to H.273 TransferCharacteristics code point 11. + + + + + Transfer characteristics as defined by + - IEC 61966-2-1 sRGB + Equivalent to H.273 TransferCharacteristics code point 13 with + MatrixCoefficients set to 0. + + + + + Transfer characteristics as defined by + - IEC 61966-2-1 sYCC + Equivalent to H.273 TransferCharacteristics code point 13 with + MatrixCoefficients set to anything but 0. + + + + + Transfer characteristics as defined by + - SMPTE ST 2084 (2014) for 10-, 12-, 14- and 16-bit systems + - Rec. ITU-R BT.2100-2 perceptual quantization (PQ) system + Equivalent to H.273 TransferCharacteristics code point 16. + + This TF implies these default luminances + - primary color volume minimum: 0.005 cd/m² + - primary color volume maximum: 10000 cd/m² + - reference white: 203 cd/m² + + The difference between the primary color volume minimum and maximum + must be approximately 10000 cd/m² as that is the swing of the EOTF + defined by ST 2084 and BT.2100. The default value for the + reference white is a protocol addition: it is suggested by + Report ITU-R BT.2408-7 and is not part of ST 2084 or BT.2100. + + + + + Transfer characteristics as defined by + - SMPTE ST 428-1 (2019) + Equivalent to H.273 TransferCharacteristics code point 17. + + + + + Transfer characteristics as defined by + - ARIB STD-B67 (2015) + - Rec. ITU-R BT.2100-2 hybrid log-gamma (HLG) system + Equivalent to H.273 TransferCharacteristics code point 18. + + This TF implies these default luminances + - primary color volume minimum: 0.005 cd/m² + - primary color volume maximum: 1000 cd/m² + - reference white: 203 cd/m² + + HLG is a relative display-referred signal with a specified + non-linear mapping to the display peak luminance (the HLG OOTF). + All absolute luminance values used here for HLG assume a 1000 cd/m² + peak display. + + The default value for the reference white is a protocol addition: + it is suggested by Report ITU-R BT.2408-7 and is not part of + ARIB STD-B67 or BT.2100. + + + + + + + This creates a new wp_color_management_output_v1 object for the + given wl_output. + + See the wp_color_management_output_v1 interface for more details. + + + + + + + + + If a wp_color_management_surface_v1 object already exists for the given + wl_surface, the protocol error surface_exists is raised. + + This creates a new color wp_color_management_surface_v1 object for the + given wl_surface. + + See the wp_color_management_surface_v1 interface for more details. + + + + + + + + + This creates a new color wp_color_management_surface_feedback_v1 object + for the given wl_surface. + + See the wp_color_management_surface_feedback_v1 interface for more + details. + + + + + + + + + Makes a new ICC-based image description creator object with all + properties initially unset. The client can then use the object's + interface to define all the required properties for an image description + and finally create a wp_image_description_v1 object. + + This request can be used when the compositor advertises + wp_color_manager_v1.feature.icc_v2_v4. + Otherwise this request raises the protocol error unsupported_feature. + + + + + + + + Makes a new parametric image description creator object with all + properties initially unset. The client can then use the object's + interface to define all the required properties for an image description + and finally create a wp_image_description_v1 object. + + This request can be used when the compositor advertises + wp_color_manager_v1.feature.parametric. + Otherwise this request raises the protocol error unsupported_feature. + + + + + + + + This creates a pre-defined image description for the so-called + Windows-scRGB stimulus encoding. This comes from the Windows 10 handling + of its own definition of an scRGB color space for an HDR screen + driven in BT.2100/PQ signalling mode. + + Windows-scRGB uses sRGB (BT.709) color primaries and white point. + The transfer characteristic is extended linear. + + The nominal color channel value range is extended, meaning it includes + negative and greater than 1.0 values. Negative values are used to + escape the sRGB color gamut boundaries. To make use of the extended + range, the client needs to use a pixel format that can represent those + values, e.g. floating-point 16 bits per channel. + + Nominal color value R=G=B=0.0 corresponds to BT.2100/PQ system + 0 cd/m², and R=G=B=1.0 corresponds to BT.2100/PQ system 80 cd/m². + The maximum is R=G=B=125.0 corresponding to 10k cd/m². + + Windows-scRGB is displayed by Windows 10 by converting it to + BT.2100/PQ, maintaining the CIE 1931 chromaticity and mapping the + luminance as above. No adjustment is made to the signal to account + for the viewing conditions. + + The reference white level of Windows-scRGB is unknown. If a + reference white level must be assumed for compositor processing, it + should be R=G=B=2.5375 corresponding to 203 cd/m² of Report ITU-R + BT.2408-7. + + The target color volume of Windows-scRGB is unknown. The color gamut + may be anything between sRGB and BT.2100. + + Note: EGL_EXT_gl_colorspace_scrgb_linear definition differs from + Windows-scRGB by using R=G=B=1.0 as the reference white level, while + Windows-scRGB reference white level is unknown or varies. However, + it seems probable that Windows implements both + EGL_EXT_gl_colorspace_scrgb_linear and Vulkan + VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT as Windows-scRGB. + + This request can be used when the compositor advertises + wp_color_manager_v1.feature.windows_scrgb. + Otherwise this request raises the protocol error unsupported_feature. + + The resulting image description object does not allow get_information + request. The wp_image_description_v1.ready event shall be sent. + + + + + + + + When this object is created, it shall immediately send this event once + for each rendering intent the compositor supports. + + + + + + + + When this object is created, it shall immediately send this event once + for each compositor supported feature listed in the enumeration. + + + + + + + + When this object is created, it shall immediately send this event once + for each named transfer function the compositor supports with the + parametric image description creator. + + + + + + + + When this object is created, it shall immediately send this event once + for each named set of primaries the compositor supports with the + parametric image description creator. + + + + + + + + This event is sent when all supported rendering intents, features, + transfer functions and named primaries have been sent. + + + + + + + A wp_color_management_output_v1 describes the color properties of an + output. + + The wp_color_management_output_v1 is associated with the wl_output global + underlying the wl_output object. Therefore the client destroying the + wl_output object has no impact, but the compositor removing the output + global makes the wp_color_management_output_v1 object inert. + + + + + Destroy the color wp_color_management_output_v1 object. This does not + affect any remaining protocol objects. + + + + + + This event is sent whenever the image description of the output changed, + followed by one wl_output.done event common to output events across all + extensions. + + If the client wants to use the updated image description, it needs to do + get_image_description again, because image description objects are + immutable. + + + + + + This creates a new wp_image_description_v1 object for the current image + description of the output. There always is exactly one image description + active for an output so the client should destroy the image description + created by earlier invocations of this request. This request is usually + sent as a reaction to the image_description_changed event or when + creating a wp_color_management_output_v1 object. + + The image description of an output represents the color encoding the + output expects. There might be performance and power advantages, as well + as improved color reproduction, if a content update matches the image + description of the output it is being shown on. If a content update is + shown on any other output than the one it matches the image description + of, then the color reproduction on those outputs might be considerably + worse. + + The created wp_image_description_v1 object preserves the image + description of the output from the time the object was created. + + The resulting image description object allows get_information request. + + If this protocol object is inert, the resulting image description object + shall immediately deliver the wp_image_description_v1.failed event with + the no_output cause. + + If the interface version is inadequate for the output's image + description, meaning that the client does not support all the events + needed to deliver the crucial information, the resulting image + description object shall immediately deliver the + wp_image_description_v1.failed event with the low_version cause. + + Otherwise the object shall immediately deliver the ready event. + + + + + + + + + A wp_color_management_surface_v1 allows the client to set the color + space and HDR properties of a surface. + + If the wl_surface associated with the wp_color_management_surface_v1 is + destroyed, the wp_color_management_surface_v1 object becomes inert. + + + + + Destroy the wp_color_management_surface_v1 object and do the same as + unset_image_description. + + + + + + + + + + + + + If this protocol object is inert, the protocol error inert is raised. + + Set the image description of the underlying surface. The image + description and rendering intent are double-buffered state, see + wl_surface.commit. + + It is the client's responsibility to understand the image description + it sets on a surface, and to provide content that matches that image + description. Compositors might convert images to match their own or any + other image descriptions. + + Image descriptions which are not ready (see wp_image_description_v1) + are forbidden in this request, and in such case the protocol error + image_description is raised. + + All image descriptions which are ready (see wp_image_description_v1) + are allowed and must always be accepted by the compositor. + + A rendering intent provides the client's preference on how content + colors should be mapped to each output. The render_intent value must + be one advertised by the compositor with + wp_color_manager_v1.render_intent event, otherwise the protocol error + render_intent is raised. + + When an image description is set on a surface, the Transfer + Characteristics of the image description defines the valid range of + the nominal (real-valued) color channel values. The processing of + out-of-range color channel values is undefined, but compositors are + recommended to clamp the values to the valid range when possible. + + By default, a surface does not have an associated image description + nor a rendering intent. The handling of color on such surfaces is + compositor implementation defined. Compositors should handle such + surfaces as sRGB, but may handle them differently if they have specific + requirements. + + Setting the image description has copy semantics; after this request, + the image description can be immediately destroyed without affecting + the pending state of the surface. + + + + + + + + + If this protocol object is inert, the protocol error inert is raised. + + This request removes any image description from the surface. See + set_image_description for how a compositor handles a surface without + an image description. This is double-buffered state, see + wl_surface.commit. + + + + + + + A wp_color_management_surface_feedback_v1 allows the client to get the + preferred image description of a surface. + + If the wl_surface associated with this object is destroyed, the + wp_color_management_surface_feedback_v1 object becomes inert. + + + + + Destroy the wp_color_management_surface_feedback_v1 object. + + + + + + + + + + + + The preferred image description is the one which likely has the most + performance and/or quality benefits for the compositor if used by the + client for its wl_surface contents. This event is sent whenever the + compositor changes the wl_surface's preferred image description. + + This event sends the identity of the new preferred state as the argument, + so clients who are aware of the image description already can reuse it. + Otherwise, if the client client wants to know what the preferred image + description is, it shall use the get_preferred request. + + The preferred image description is not automatically used for anything. + It is only a hint, and clients may set any valid image description with + set_image_description, but there might be performance and color accuracy + improvements by providing the wl_surface contents in the preferred + image description. Therefore clients that can, should render according + to the preferred image description + + + + + + + + If this protocol object is inert, the protocol error inert is raised. + + The preferred image description represents the compositor's preferred + color encoding for this wl_surface at the current time. There might be + performance and power advantages, as well as improved color + reproduction, if the image description of a content update matches the + preferred image description. + + This creates a new wp_image_description_v1 object for the currently + preferred image description for the wl_surface. The client should + stop using and destroy the image descriptions created by earlier + invocations of this request for the associated wl_surface. + This request is usually sent as a reaction to the preferred_changed + event or when creating a wp_color_management_surface_feedback_v1 object + if the client is capable of adapting to image descriptions. + + The created wp_image_description_v1 object preserves the preferred image + description of the wl_surface from the time the object was created. + + The resulting image description object allows get_information request. + + If the image description is parametric, the client should set it on its + wl_surface only if the image description is an exact match with the + client content. Particularly if everything else matches, but the target + color volume is greater than what the client needs, the client should + create its own parameric image description with its exact parameters. + + If the interface version is inadequate for the preferred image + description, meaning that the client does not support all the + events needed to deliver the crucial information, the resulting image + description object shall immediately deliver the + wp_image_description_v1.failed event with the low_version cause, + otherwise the object shall immediately deliver the ready event. + + + + + + + + The same description as for get_preferred applies, except the returned + image description is guaranteed to be parametric. This is meant for + clients that can only deal with parametric image descriptions. + + If the compositor doesn't support parametric image descriptions, the + unsupported_feature error is emitted. + + + + + + + + + This type of object is used for collecting all the information required + to create a wp_image_description_v1 object from an ICC file. A complete + set of required parameters consists of these properties: + - ICC file + + Each required property must be set exactly once if the client is to create + an image description. The set requests verify that a property was not + already set. The create request verifies that all required properties are + set. There may be several alternative requests for setting each property, + and in that case the client must choose one of them. + + Once all properties have been set, the create request must be used to + create the image description object, destroying the creator in the + process. + + + + + + + + + + + + + + + Create an image description object based on the ICC information + previously set on this object. A compositor must parse the ICC data in + some undefined but finite amount of time. + + The completeness of the parameter set is verified. If the set is not + complete, the protocol error incomplete_set is raised. For the + definition of a complete set, see the description of this interface. + + If the particular combination of the information is not supported + by the compositor, the resulting image description object shall + immediately deliver the wp_image_description_v1.failed event with the + 'unsupported' cause. If a valid image description was created from the + information, the wp_image_description_v1.ready event will eventually + be sent instead. + + This request destroys the wp_image_description_creator_icc_v1 object. + + The resulting image description object does not allow get_information + request. + + + + + + + + Sets the ICC profile file to be used as the basis of the image + description. + + The data shall be found through the given fd at the given offset, having + the given length. The fd must be seekable and readable. Violating these + requirements raises the bad_fd protocol error. + + If reading the data fails due to an error independent of the client, the + compositor shall send the wp_image_description_v1.failed event on the + created wp_image_description_v1 with the 'operating_system' cause. + + The maximum size of the ICC profile is 32 MB. If length is greater than + that or zero, the protocol error bad_size is raised. If offset + length + exceeds the file size, the protocol error out_of_file is raised. + + A compositor may read the file at any time starting from this request + and only until whichever happens first: + - If create request was issued, the wp_image_description_v1 object + delivers either failed or ready event; or + - if create request was not issued, this + wp_image_description_creator_icc_v1 object is destroyed. + + A compositor shall not modify the contents of the file, and the fd may + be sealed for writes and size changes. The client must ensure to its + best ability that the data does not change while the compositor is + reading it. + + The data must represent a valid ICC profile. The ICC profile version + must be 2 or 4, it must be a 3 channel profile and the class must be + Display or ColorSpace. Violating these requirements will not result in a + protocol error, but will eventually send the + wp_image_description_v1.failed event on the created + wp_image_description_v1 with the 'unsupported' cause. + + See the International Color Consortium specification ICC.1:2022 for more + details about ICC profiles. + + If ICC file has already been set on this object, the protocol error + already_set is raised. + + + + + + + + + + + This type of object is used for collecting all the parameters required + to create a wp_image_description_v1 object. A complete set of required + parameters consists of these properties: + - transfer characteristic function (tf) + - chromaticities of primaries and white point (primary color volume) + + The following properties are optional and have a well-defined default + if not explicitly set: + - primary color volume luminance range + - reference white luminance level + - mastering display primaries and white point (target color volume) + - mastering luminance range + + The following properties are optional and will be ignored + if not explicitly set: + - maximum content light level + - maximum frame-average light level + + Each required property must be set exactly once if the client is to create + an image description. The set requests verify that a property was not + already set. The create request verifies that all required properties are + set. There may be several alternative requests for setting each property, + and in that case the client must choose one of them. + + Once all properties have been set, the create request must be used to + create the image description object, destroying the creator in the + process. + + + + + + + + + + + + + + + + Create an image description object based on the parameters previously + set on this object. + + The completeness of the parameter set is verified. If the set is not + complete, the protocol error incomplete_set is raised. For the + definition of a complete set, see the description of this interface. + + The protocol error invalid_luminance is raised if any of the following + requirements is not met: + - When max_cll is set, it must be greater than min L and less or equal + to max L of the mastering luminance range. + - When max_fall is set, it must be greater than min L and less or equal + to max L of the mastering luminance range. + - When both max_cll and max_fall are set, max_fall must be less or equal + to max_cll. + + If the particular combination of the parameter set is not supported + by the compositor, the resulting image description object shall + immediately deliver the wp_image_description_v1.failed event with the + 'unsupported' cause. If a valid image description was created from the + parameter set, the wp_image_description_v1.ready event will eventually + be sent instead. + + This request destroys the wp_image_description_creator_params_v1 + object. + + The resulting image description object does not allow get_information + request. + + + + + + + + Sets the transfer characteristic using explicitly enumerated named + functions. + + When the resulting image description is attached to an image, the + content should be encoded and decoded according to the industry standard + practices for the transfer characteristic. + + Only names advertised with wp_color_manager_v1 event supported_tf_named + are allowed. Other values shall raise the protocol error invalid_tf. + + If transfer characteristic has already been set on this object, the + protocol error already_set is raised. + + + + + + + + Sets the color component transfer characteristic to a power curve with + the given exponent. Negative values are handled by mirroring the + positive half of the curve through the origin. The valid domain and + range of the curve are all finite real numbers. This curve represents + the conversion from electrical to optical color channel values. + + When the resulting image description is attached to an image, the + content should be encoded with the inverse of the power curve. + + The curve exponent shall be multiplied by 10000 to get the argument eexp + value to carry the precision of 4 decimals. + + The curve exponent must be at least 1.0 and at most 10.0. Otherwise the + protocol error invalid_tf is raised. + + If transfer characteristic has already been set on this object, the + protocol error already_set is raised. + + This request can be used when the compositor advertises + wp_color_manager_v1.feature.set_tf_power. Otherwise this request raises + the protocol error unsupported_feature. + + + + + + + + Sets the color primaries and white point using explicitly named sets. + This describes the primary color volume which is the basis for color + value encoding. + + Only names advertised with wp_color_manager_v1 event + supported_primaries_named are allowed. Other values shall raise the + protocol error invalid_primaries_named. + + If primaries have already been set on this object, the protocol error + already_set is raised. + + + + + + + + Sets the color primaries and white point using CIE 1931 xy chromaticity + coordinates. This describes the primary color volume which is the basis + for color value encoding. + + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. + + If primaries have already been set on this object, the protocol error + already_set is raised. + + This request can be used if the compositor advertises + wp_color_manager_v1.feature.set_primaries. Otherwise this request raises + the protocol error unsupported_feature. + + + + + + + + + + + + + + + Sets the primary color volume luminance range and the reference white + luminance level. These values include the minimum display emission + and ambient flare luminances, assumed to be optically additive and have + the chromaticity of the primary color volume white point. + + The default luminances from + https://www.color.org/chardata/rgb/srgb.xalter are + - primary color volume minimum: 0.2 cd/m² + - primary color volume maximum: 80 cd/m² + - reference white: 80 cd/m² + + Setting a named transfer characteristic can imply other default + luminances. + + The default luminances get overwritten when this request is used. + With transfer_function.st2084_pq the given 'max_lum' value is ignored, + and 'max_lum' is taken as 'min_lum' + 10000 cd/m². + + 'min_lum' and 'max_lum' specify the minimum and maximum luminances of + the primary color volume as reproduced by the targeted display. + + 'reference_lum' specifies the luminance of the reference white as + reproduced by the targeted display, and reflects the targeted viewing + environment. + + Compositors should make sure that all content is anchored, meaning that + an input signal level of 'reference_lum' on one image description and + another input signal level of 'reference_lum' on another image + description should produce the same output level, even though the + 'reference_lum' on both image representations can be different. + + 'reference_lum' may be higher than 'max_lum'. In that case reaching + the reference white output level in image content requires the + 'extended_target_volume' feature support. + + If 'max_lum' or 'reference_lum' are less than or equal to 'min_lum', + the protocol error invalid_luminance is raised. + + The minimum luminance is multiplied by 10000 to get the argument + 'min_lum' value and carries precision of 4 decimals. The maximum + luminance and reference white luminance values are unscaled. + + If the primary color volume luminance range and the reference white + luminance level have already been set on this object, the protocol error + already_set is raised. + + This request can be used if the compositor advertises + wp_color_manager_v1.feature.set_luminances. Otherwise this request + raises the protocol error unsupported_feature. + + + + + + + + + + Provides the color primaries and white point of the mastering display + using CIE 1931 xy chromaticity coordinates. This is compatible with the + SMPTE ST 2086 definition of HDR static metadata. + + The mastering display primaries and mastering display luminances define + the target color volume. + + If mastering display primaries are not explicitly set, the target color + volume is assumed to have the same primaries as the primary color volume. + + The target color volume is defined by all tristimulus values between 0.0 + and 1.0 (inclusive) of the color space defined by the given mastering + display primaries and white point. The colorimetry is identical between + the container color space and the mastering display color space, + including that no chromatic adaptation is applied even if the white + points differ. + + The target color volume can exceed the primary color volume to allow for + a greater color volume with an existing color space definition (for + example scRGB). It can be smaller than the primary color volume to + minimize gamut and tone mapping distances for big color spaces (HDR + metadata). + + To make use of the entire target color volume a suitable pixel format + has to be chosen (e.g. floating point to exceed the primary color + volume, or abusing limited quantization range as with xvYCC). + + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. + + If mastering display primaries have already been set on this object, the + protocol error already_set is raised. + + This request can be used if the compositor advertises + wp_color_manager_v1.feature.set_mastering_display_primaries. Otherwise + this request raises the protocol error unsupported_feature. The + advertisement implies support only for target color volumes fully + contained within the primary color volume. + + If a compositor additionally supports target color volume exceeding the + primary color volume, it must advertise + wp_color_manager_v1.feature.extended_target_volume. If a client uses + target color volume exceeding the primary color volume and the + compositor does not support it, the result is implementation defined. + Compositors are recommended to detect this case and fail the image + description gracefully, but it may as well result in color artifacts. + + + + + + + + + + + + + + + Sets the luminance range that was used during the content mastering + process as the minimum and maximum absolute luminance L. These values + include the minimum display emission and ambient flare luminances, + assumed to be optically additive and have the chromaticity of the + primary color volume white point. This should be + compatible with the SMPTE ST 2086 definition of HDR static metadata. + + The mastering display primaries and mastering display luminances define + the target color volume. + + If mastering luminances are not explicitly set, the target color volume + is assumed to have the same min and max luminances as the primary color + volume. + + If max L is less than or equal to min L, the protocol error + invalid_luminance is raised. + + Min L value is multiplied by 10000 to get the argument min_lum value + and carry precision of 4 decimals. Max L value is unscaled for max_lum. + + This request can be used if the compositor advertises + wp_color_manager_v1.feature.set_mastering_display_primaries. Otherwise + this request raises the protocol error unsupported_feature. The + advertisement implies support only for target color volumes fully + contained within the primary color volume. + + If a compositor additionally supports target color volume exceeding the + primary color volume, it must advertise + wp_color_manager_v1.feature.extended_target_volume. If a client uses + target color volume exceeding the primary color volume and the + compositor does not support it, the result is implementation defined. + Compositors are recommended to detect this case and fail the image + description gracefully, but it may as well result in color artifacts. + + + + + + + + + Sets the maximum content light level (max_cll) as defined by CTA-861-H. + + max_cll is undefined by default. + + + + + + + + Sets the maximum frame-average light level (max_fall) as defined by + CTA-861-H. + + max_fall is undefined by default. + + + + + + + + + An image description carries information about the color encoding used on + a surface when attached to a wl_surface via + wp_color_management_surface_v1.set_image_description. A compositor can use + this information to decode pixel values into colorimetrically meaningful + quantities. + + Note, that the wp_image_description_v1 object is not ready to be used + immediately after creation. The object eventually delivers either the + 'ready' or the 'failed' event, specified in all requests creating it. The + object is deemed "ready" after receiving the 'ready' event. + + An object which is not ready is illegal to use, it can only be destroyed. + Any other request in this interface shall result in the 'not_ready' + protocol error. Attempts to use an object which is not ready through other + interfaces shall raise protocol errors defined there. + + Once created and regardless of how it was created, a + wp_image_description_v1 object always refers to one fixed image + description. It cannot change after creation. + + + + + Destroy this object. It is safe to destroy an object which is not ready. + + Destroying a wp_image_description_v1 object has no side-effects, not + even if a wp_color_management_surface_v1.set_image_description has not + yet been followed by a wl_surface.commit. + + + + + + + + + + + + + + + + + + + + + + If creating a wp_image_description_v1 object fails for a reason that is + not defined as a protocol error, this event is sent. + + The requests that create image description objects define whether and + when this can occur. Only such creation requests can trigger this event. + This event cannot be triggered after the image description was + successfully formed. + + Once this event has been sent, the wp_image_description_v1 object will + never become ready and it can only be destroyed. + + + + + + + + + Once this event has been sent, the wp_image_description_v1 object is + deemed "ready". Ready objects can be used to send requests and can be + used through other interfaces. + + Every ready wp_image_description_v1 protocol object refers to an + underlying image description record in the compositor. Multiple protocol + objects may end up referring to the same record. Clients may identify + these "copies" by comparing their id numbers: if the numbers from two + protocol objects are identical, the protocol objects refer to the same + image description record. Two different image description records + cannot have the same id number simultaneously. The id number does not + change during the lifetime of the image description record. + + The id number is valid only as long as the protocol object is alive. If + all protocol objects referring to the same image description record are + destroyed, the id number may be recycled for a different image + description record. + + Image description id number is not a protocol object id. Zero is + reserved as an invalid id number. It shall not be possible for a client + to refer to an image description by its id number in protocol. The id + numbers might not be portable between Wayland connections. A compositor + shall not send an invalid id number. + + This identity allows clients to de-duplicate image description records + and avoid get_information request if they already have the image + description information. + + + + + + + + Creates a wp_image_description_info_v1 object which delivers the + information that makes up the image description. + + Not all image description protocol objects allow get_information + request. Whether it is allowed or not is defined by the request that + created the object. If get_information is not allowed, the protocol + error no_information is raised. + + + + + + + + + Sends all matching events describing an image description object exactly + once and finally sends the 'done' event. + + This means + - if the image description is parametric, it must send + - primaries + - named_primaries, if applicable + - at least one of tf_power and tf_named, as applicable + - luminances + - target_primaries + - target_luminance + - if the image description is parametric, it may send, if applicable, + - target_max_cll + - target_max_fall + - if the image description contains an ICC profile, it must send the + icc_file event + + Once a wp_image_description_info_v1 object has delivered a 'done' event it + is automatically destroyed. + + Every wp_image_description_info_v1 created from the same + wp_image_description_v1 shall always return the exact same data. + + + + + Signals the end of information events and destroys the object. + + + + + + The icc argument provides a file descriptor to the client which may be + memory-mapped to provide the ICC profile matching the image description. + The fd is read-only, and if mapped then it must be mapped with + MAP_PRIVATE by the client. + + The ICC profile version and other details are determined by the + compositor. There is no provision for a client to ask for a specific + kind of a profile. + + + + + + + + + + Delivers the primary color volume primaries and white point using CIE + 1931 xy chromaticity coordinates. + + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. + + + + + + + + + + + + + + + Delivers the primary color volume primaries and white point using an + explicitly enumerated named set. + + + + + + + + The color component transfer characteristic of this image description is + a pure power curve. This event provides the exponent of the power + function. This curve represents the conversion from electrical to + optical pixel or color values. + + The curve exponent has been multiplied by 10000 to get the argument eexp + value to carry the precision of 4 decimals. + + + + + + + + Delivers the transfer characteristic using an explicitly enumerated + named function. + + + + + + + + Delivers the primary color volume luminance range and the reference + white luminance level. These values include the minimum display emission + and ambient flare luminances, assumed to be optically additive and have + the chromaticity of the primary color volume white point. + + The minimum luminance is multiplied by 10000 to get the argument + 'min_lum' value and carries precision of 4 decimals. The maximum + luminance and reference white luminance values are unscaled. + + + + + + + + + + Provides the color primaries and white point of the target color volume + using CIE 1931 xy chromaticity coordinates. This is compatible with the + SMPTE ST 2086 definition of HDR static metadata for mastering displays. + + While primary color volume is about how color is encoded, the target + color volume is the actually displayable color volume. If target color + volume is equal to the primary color volume, then this event is not + sent. + + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. + + + + + + + + + + + + + + + Provides the luminance range that the image description is targeting as + the minimum and maximum absolute luminance L. These values include the + minimum display emission and ambient flare luminances, assumed to be + optically additive and have the chromaticity of the primary color + volume white point. This should be compatible with the SMPTE ST 2086 + definition of HDR static metadata. + + This luminance range is only theoretical and may not correspond to the + luminance of light emitted on an actual display. + + Min L value is multiplied by 10000 to get the argument min_lum value and + carry precision of 4 decimals. Max L value is unscaled for max_lum. + + + + + + + + + Provides the targeted max_cll of the image description. max_cll is + defined by CTA-861-H. + + This luminance is only theoretical and may not correspond to the + luminance of light emitted on an actual display. + + + + + + + + Provides the targeted max_fall of the image description. max_fall is + defined by CTA-861-H. + + This luminance is only theoretical and may not correspond to the + luminance of light emitted on an actual display. + + + + + + diff --git a/src/main.cpp b/src/main.cpp index b29ec63..38d059e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -809,7 +809,6 @@ static bool recreate_graphics_pipelines() { .slot = 1, .pitch = sizeof(Instance), .input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE, - .instance_step_rate = 1, }, }; @@ -925,7 +924,6 @@ static bool recreate_graphics_pipelines() { .slot = 1, .pitch = sizeof(Uint32), .input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE, - .instance_step_rate = 1, }, }; @@ -1028,7 +1026,6 @@ static bool recreate_graphics_pipelines() { .slot = 1, .pitch = sizeof(Uint32), .input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE, - .instance_step_rate = 1, }, };