diff --git a/libs/SDL3/.git-hash b/libs/SDL3/.git-hash index 5c68109..346bf49 100644 --- a/libs/SDL3/.git-hash +++ b/libs/SDL3/.git-hash @@ -1 +1 @@ -96292a5b464258a2b926e0a3d72f8b98c2a81aa6 +683181b47cfabd293e3ea409f838915b8297a4fd diff --git a/libs/SDL3/.wikiheaders-options b/libs/SDL3/.wikiheaders-options index 9c2b5bb..696a08c 100644 --- a/libs/SDL3/.wikiheaders-options +++ b/libs/SDL3/.wikiheaders-options @@ -9,6 +9,7 @@ versionfname = include/SDL3/SDL_version.h versionmajorregex = \A\#define\s+SDL_MAJOR_VERSION\s+(\d+)\Z versionminorregex = \A\#define\s+SDL_MINOR_VERSION\s+(\d+)\Z versionmicroregex = \A\#define\s+SDL_MICRO_VERSION\s+(\d+)\Z +apipropertyregex = \A\s*\#\s*define\s+SDL_PROP_ selectheaderregex = \ASDL.*?\.h\Z projecturl = https://libsdl.org/ wikiurl = https://wiki.libsdl.org @@ -25,8 +26,16 @@ manpagesymbolfilterregex = \A[US]int\d+\Z headercategoryeval = s/\ASDL_test_?.*?\.h\Z//; s/\ASDL_?(.*?)\.h\Z/$1/; ucfirst(); quickrefenabled = 1 -quickrefcategoryorder = Init,Hints,Error,Version,Properties,Log,Video,Events,Keyboard,Mouse,Touch,Gamepad,Joystick,Haptic,Audio,Time,Timer,Render,SharedObject,Thread,Mutex,Atomic,Filesystem,IOStream,AsyncIO,Storage,Pixels,Surface,Blendmode,Rect,Camera,Clipboard,Dialog,GPU,Messagebox,Vulkan,Metal,Platform,Power,Sensor,Process,Bits,Endian,Assert,CPUInfo,Intrinsics,Locale,System,Misc,GUID,Main,Stdinc +quickrefcategoryorder = Init,Hints,Error,Version,Properties,Log,Video,Events,Keyboard,Mouse,Touch,Gamepad,Joystick,Haptic,Audio,Time,Timer,Render,SharedObject,Thread,Mutex,Atomic,Filesystem,IOStream,AsyncIO,Storage,Pixels,Surface,Blendmode,Rect,Camera,Clipboard,Dialog,Tray,Messagebox,GPU,Vulkan,Metal,Platform,Power,Sensor,Process,Bits,Endian,Assert,CPUInfo,Intrinsics,Locale,System,Misc,GUID,Main,Stdinc quickreftitle = SDL3 API Quick Reference quickrefurl = https://libsdl.org/ quickrefdesc = The latest version of this document can be found at https://wiki.libsdl.org/SDL3/QuickReference quickrefmacroregex = \A(SDL_PLATFORM_.*|SDL_.*_INTRINSICS|SDL_Atomic...Ref|SDL_assert.*?|SDL_COMPILE_TIME_ASSERT|SDL_arraysize|SDL_Swap[BL]E\d\d|SDL_[a-z]+_cast)\Z + +envvarenabled = 1 +envvartitle = SDL3 Environment Variables +envvardesc = SDL3 can be controlled by the user, externally, with environment variables. They are all operate exactly like the [hints you can get and set programmatically](CategoryHints), but named without the `_HINT` part (so `"SDL_HINT_A"` would be environment variable `"SDL_A"`).\n\nThis list matches the latest in SDL3's revision control. +envvarsymregex = \ASDL_HINT_(.*)\Z +envvarsymreplace = SDL_$1 + + diff --git a/libs/SDL3/Android.mk b/libs/SDL3/Android.mk index 413967c..f4600bf 100644 --- a/libs/SDL3/Android.mk +++ b/libs/SDL3/Android.mk @@ -42,6 +42,7 @@ LOCAL_SRC_FILES := \ $(wildcard $(LOCAL_PATH)/src/haptic/*.c) \ $(wildcard $(LOCAL_PATH)/src/haptic/android/*.c) \ $(wildcard $(LOCAL_PATH)/src/haptic/dummy/*.c) \ + $(wildcard $(LOCAL_PATH)/src/haptic/hidapi/*.c) \ $(wildcard $(LOCAL_PATH)/src/hidapi/*.c) \ $(wildcard $(LOCAL_PATH)/src/hidapi/android/*.cpp) \ $(wildcard $(LOCAL_PATH)/src/joystick/*.c) \ @@ -107,16 +108,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 -LOCAL_STATIC_LIBRARIES := cpufeatures - include $(BUILD_SHARED_LIBRARY) @@ -126,6 +121,12 @@ include $(BUILD_SHARED_LIBRARY) # ########################### +include $(CLEAR_VARS) + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/src + +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include + LOCAL_MODULE := SDL3_test LOCAL_MODULE_FILENAME := libSDL3_test @@ -142,24 +143,3 @@ LOCAL_EXPORT_LDLIBS := include $(BUILD_STATIC_LIBRARY) - -########################### -# -# SDL static library -# -########################### - -LOCAL_MODULE := SDL3_static - -LOCAL_MODULE_FILENAME := libSDL3 - -LOCAL_LDLIBS := - -LOCAL_LDFLAGS := - -LOCAL_EXPORT_LDLIBS := -ldl -lGLESv1_CM -lGLESv2 -llog -landroid - -include $(BUILD_STATIC_LIBRARY) - -$(call import-module,android/cpufeatures) - diff --git a/libs/SDL3/CMakeLists.txt b/libs/SDL3/CMakeLists.txt index 5873957..7f4bef4 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.20") +project(SDL3 LANGUAGES C VERSION "3.4.2") if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) set(SDL3_MAINPROJECT ON) @@ -46,6 +46,7 @@ endif() include(CheckLibraryExists) include(CheckIncludeFile) +include(CheckIncludeFiles) include(CheckLanguage) include(CheckSymbolExists) include(CheckCSourceCompiles) @@ -67,18 +68,25 @@ find_package(PkgConfig) list(APPEND CMAKE_MODULE_PATH "${SDL3_SOURCE_DIR}/cmake") include("${SDL3_SOURCE_DIR}/cmake/macros.cmake") include("${SDL3_SOURCE_DIR}/cmake/sdlchecks.cmake") +include("${SDL3_SOURCE_DIR}/cmake/sdlcommands.cmake") include("${SDL3_SOURCE_DIR}/cmake/sdlcompilers.cmake") include("${SDL3_SOURCE_DIR}/cmake/sdlcpu.cmake") include("${SDL3_SOURCE_DIR}/cmake/sdlmanpages.cmake") include("${SDL3_SOURCE_DIR}/cmake/sdlplatform.cmake") -include("${SDL3_SOURCE_DIR}/cmake/sdltargets.cmake") include("${SDL3_SOURCE_DIR}/cmake/GetGitRevisionDescription.cmake") include("${SDL3_SOURCE_DIR}/cmake/3rdparty.cmake") include("${SDL3_SOURCE_DIR}/cmake/PreseedMSVCCache.cmake") include("${SDL3_SOURCE_DIR}/cmake/PreseedEmscriptenCache.cmake") +include("${SDL3_SOURCE_DIR}/cmake/PreseedNokiaNGageCache.cmake") SDL_DetectCompiler() SDL_DetectTargetCPUArchitectures(SDL_CPUS) +if(APPLE AND CMAKE_OSX_ARCHITECTURES) + list(LENGTH CMAKE_OSX_ARCHITECTURES _num_arches) + if(_num_arches GREATER 1) + set(APPLE_MULTIARCH TRUE) + endif() +endif() # Increment this if there is an incompatible change - but if that happens, # we should rename the library from SDL3 to SDL4, at which point this would @@ -155,19 +163,19 @@ endif() # The hidraw support doesn't catch Xbox, PS4 and Nintendo controllers, # so we'll just use libusb when it's available. libusb does not support iOS, # so we default to yes on iOS. -if(IOS OR TVOS OR VISIONOS OR WATCHOS OR ANDROID) +if(IOS OR TVOS OR VISIONOS OR WATCHOS OR ANDROID OR NGAGE) set(SDL_HIDAPI_LIBUSB_AVAILABLE FALSE) else() set(SDL_HIDAPI_LIBUSB_AVAILABLE TRUE) endif() set(SDL_ASSEMBLY_DEFAULT OFF) -if(USE_CLANG OR USE_GCC OR USE_INTELCC OR MSVC_VERSION GREATER 1400) +if(USE_CLANG OR USE_GCC OR USE_INTELCC OR USE_TCC OR MSVC_VERSION GREATER 1400) set(SDL_ASSEMBLY_DEFAULT ON) endif() set(SDL_GCC_ATOMICS_DEFAULT OFF) -if(USE_GCC OR USE_CLANG OR USE_INTELCC OR USE_QCC) +if(USE_GCC OR USE_CLANG OR USE_INTELCC OR USE_QCC OR USE_TCC) set(SDL_GCC_ATOMICS_DEFAULT ON) endif() @@ -183,29 +191,6 @@ if(MSVC) set(SDL_RELOCATABLE_DEFAULT ON) endif() -if(MSVC) - if(NOT SDL_LIBC) - # 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_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) - # clang-cl treats /W4 as '-Wall -Wextra' -- we don't need -Wextra - foreach(flag_var - CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE - CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) - string(REGEX REPLACE "/W4" "/W3" ${flag_var} "${${flag_var}}") - endforeach(flag_var) - endif() -endif() - set(SDL_SHARED_DEFAULT ON) set(SDL_STATIC_DEFAULT ON) @@ -222,7 +207,7 @@ if(EMSCRIPTEN) set(SDL_SHARED_AVAILABLE OFF) endif() -if(VITA OR PSP OR PS2 OR N3DS OR RISCOS) +if(VITA OR PSP OR PS2 OR N3DS OR RISCOS OR NGAGE) set(SDL_SHARED_AVAILABLE OFF) endif() @@ -275,6 +260,7 @@ define_sdl_subsystem(Hidapi) define_sdl_subsystem(Power) define_sdl_subsystem(Sensor) define_sdl_subsystem(Dialog) +define_sdl_subsystem(Tray) cmake_dependent_option(SDL_FRAMEWORK "Build SDL libraries as Apple Framework" OFF "APPLE" OFF) if(SDL_FRAMEWORK) @@ -323,6 +309,7 @@ dep_option(SDL_ARMNEON "Use NEON assembly routines" ON "SDL_ASSEMBLY dep_option(SDL_LSX "Use LSX assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_LOONGARCH64" OFF) dep_option(SDL_LASX "Use LASX assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_LOONGARCH64" OFF) +dep_option(SDL_DLOPEN_NOTES "Record dlopen dependencies in .note.dlopen section" TRUE UNIX_SYS OFF) set_option(SDL_LIBC "Use the system C library" ${SDL_LIBC_DEFAULT}) set_option(SDL_SYSTEM_ICONV "Use iconv() from system-installed libraries" ${SDL_SYSTEM_ICONV_DEFAULT}) set_option(SDL_LIBICONV "Prefer iconv() from libiconv, if available, over libc version" OFF) @@ -360,6 +347,11 @@ dep_option(SDL_X11_XRANDR "Enable Xrandr support" "${SDL_X11_XRANDR_DEF 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_X11_XTEST "Enable XTest support" ON SDL_X11 OFF) +dep_option(SDL_FRIBIDI "Enable Fribidi support" ON SDL_X11 OFF) +dep_option(SDL_FRIBIDI_SHARED "Dynamically load Fribidi support" ON "SDL_FRIBIDI;SDL_DEPS_SHARED" OFF) +dep_option(SDL_LIBTHAI "Enable Thai support" ON SDL_X11 OFF) +dep_option(SDL_LIBTHAI_SHARED "Dynamically load Thai support" ON "SDL_LIBTHAI;SDL_DEPS_SHARED" 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;SDL_DEPS_SHARED" OFF) dep_option(SDL_WAYLAND_LIBDECOR "Use client-side window decorations on Wayland" ON "SDL_WAYLAND" OFF) @@ -373,10 +365,10 @@ dep_option(SDL_WASAPI "Use the Windows WASAPI audio driver" ON "WIN dep_option(SDL_RENDER_D3D "Enable the Direct3D 9 render driver" ON "SDL_RENDER;SDL_DIRECTX" OFF) dep_option(SDL_RENDER_D3D11 "Enable the Direct3D 11 render driver" ON "SDL_RENDER;SDL_DIRECTX" OFF) dep_option(SDL_RENDER_D3D12 "Enable the Direct3D 12 render driver" ON "SDL_RENDER;SDL_DIRECTX" OFF) -dep_option(SDL_RENDER_METAL "Enable the Metal render driver" ON "SDL_RENDER;${APPLE}" OFF) +dep_option(SDL_RENDER_METAL "Enable the Metal render driver" ON "SDL_RENDER;APPLE" OFF) dep_option(SDL_RENDER_GPU "Enable the SDL_GPU render driver" ON "SDL_RENDER;SDL_GPU" OFF) dep_option(SDL_VIVANTE "Use Vivante EGL video driver" ON "${UNIX_SYS};SDL_CPU_ARM32" OFF) -dep_option(SDL_VULKAN "Enable Vulkan support" ON "SDL_VIDEO;ANDROID OR APPLE OR LINUX OR FREEBSD OR WINDOWS" OFF) +dep_option(SDL_VULKAN "Enable Vulkan support" ON "SDL_VIDEO;ANDROID OR APPLE OR LINUX OR FREEBSD OR OPENBSD OR WINDOWS" OFF) dep_option(SDL_RENDER_VULKAN "Enable the Vulkan render driver" ON "SDL_RENDER;SDL_VULKAN" OFF) dep_option(SDL_METAL "Enable Metal support" ON "APPLE" OFF) set_option(SDL_OPENVR "Use OpenVR video driver" OFF) @@ -387,7 +379,7 @@ dep_option(SDL_DUMMYCAMERA "Support the dummy camera driver" ON SDL_CAME 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 "Use libusb for low level joystick drivers" ON "SDL_HIDAPI;SDL_HIDAPI_LIBUSB_AVAILABLE" 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) @@ -395,7 +387,6 @@ set_option(SDL_LIBUDEV "Enable libudev support" ON) set_option(SDL_ASAN "Use AddressSanitizer to detect memory errors" OFF) set_option(SDL_CCACHE "Use Ccache to speed up build" OFF) set_option(SDL_CLANG_TIDY "Run clang-tidy static analysis" OFF) -set_option(SDL_GPU_DXVK "Build SDL_GPU with DXVK support" OFF) set(SDL_VENDOR_INFO "" CACHE STRING "Vendor name and/or version to add to SDL_REVISION") @@ -403,7 +394,7 @@ cmake_dependent_option(SDL_SHARED "Build a shared version of the library" ${SDL_ cmake_dependent_option(SDL_STATIC "Build a static version of the library" ${SDL_STATIC_DEFAULT} ${SDL_STATIC_AVAILABLE} OFF) option(SDL_TEST_LIBRARY "Build the SDL3_test library" ON) -dep_option(SDL_TESTS "Build the test directory" OFF SDL_TEST_LIBRARY OFF) +dep_option(SDL_TESTS "Build the test directory" ${SDL3_MAINPROJECT} SDL_TEST_LIBRARY OFF) dep_option(SDL_INSTALL_TESTS "Install test-cases" OFF "SDL_INSTALL;NOT SDL_FRAMEWORK" OFF) dep_option(SDL_TESTS_LINK_SHARED "link tests to shared SDL library" "${SDL_SHARED}" "SDL_SHARED;SDL_STATIC" "${SDL_SHARED}") set(SDL_TESTS_TIMEOUT_MULTIPLIER "1" CACHE STRING "Timeout multiplier to account for really slow machines") @@ -416,6 +407,25 @@ if(VITA) set_option(VIDEO_VITA_PVR "Build with PSVita PVR gles/gles2 support" OFF) endif() +if (NGAGE) + set(SDL_GPU OFF) + set(SDL_CAMERA OFF) + set(SDL_JOYSTICK OFF) + set(SDL_HAPTIC OFF) + set(SDL_HIDAPI OFF) + set(SDL_POWER OFF) + set(SDL_SENSOR OFF) + set(SDL_DIALOG OFF) + set(SDL_DISKAUDIO OFF) + set(SDL_DUMMYAUDIO OFF) + set(SDL_DUMMYCAMERA OFF) + set(SDL_DUMMYVIDEO OFF) + set(SDL_OFFSCREEN OFF) + set(SDL_RENDER_GPU OFF) + set(SDL_TRAY OFF) + set(SDL_VIRTUAL_JOYSTICK OFF) +endif() + if(NOT (SDL_SHARED OR SDL_STATIC)) message(FATAL_ERROR "SDL_SHARED and SDL_STATIC cannot both be disabled") endif() @@ -424,14 +434,42 @@ if(SDL_PRESEED) SDL_Preseed_CMakeCache() endif() +if(MSVC) + if(NOT SDL_LIBC) + # 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_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) + # clang-cl treats /W4 as '-Wall -Wextra' -- we don't need -Wextra + foreach(flag_var + CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) + string(REGEX REPLACE "/W4" "/W3" ${flag_var} "${${flag_var}}") + endforeach(flag_var) + endif() +endif() + if(SDL_SHARED) add_library(SDL3-shared SHARED) add_library(SDL3::SDL3-shared ALIAS SDL3-shared) SDL_AddCommonCompilerFlags(SDL3-shared) + target_compile_definitions(SDL3-shared PRIVATE "$<$:DEBUG>") + set_property(TARGET SDL3-shared PROPERTY UNITY_BUILD OFF) if ("c_std_99" IN_LIST CMAKE_C_COMPILE_FEATURES) target_compile_features(SDL3-shared PRIVATE c_std_99) else() - message(WARNING "target_compile_features does not know c_std_99 for C compiler") + # tcc does support the subset of C99 used by SDL + if (NOT USE_TCC) + message(WARNING "target_compile_features does not know c_std_99 for C compiler") + endif() endif() endif() @@ -439,10 +477,14 @@ if(SDL_STATIC) add_library(SDL3-static STATIC) add_library(SDL3::SDL3-static ALIAS SDL3-static) SDL_AddCommonCompilerFlags(SDL3-static) + target_compile_definitions(SDL3-static PRIVATE "$<$:DEBUG>") + set_property(TARGET SDL3-static PROPERTY UNITY_BUILD OFF) if ("c_std_99" IN_LIST CMAKE_C_COMPILE_FEATURES) target_compile_features(SDL3-static PRIVATE c_std_99) else() - message(WARNING "target_compile_features does not know c_std_99 for C compiler") + if (NOT USE_TCC) + message(WARNING "target_compile_features does not know c_std_99 for C compiler") + endif() endif() endif() @@ -450,6 +492,7 @@ if(SDL_TEST_LIBRARY) add_library(SDL3_test STATIC) add_library(SDL3::SDL3_test ALIAS SDL3_test) SDL_AddCommonCompilerFlags(SDL3_test) + target_compile_definitions(SDL3_test PRIVATE "$<$:DEBUG>") endif() # Make sure SDL3::SDL3 always exists @@ -475,7 +518,10 @@ check_linker_supports_version_file(HAVE_WL_VERSION_SCRIPT) if(HAVE_WL_VERSION_SCRIPT) sdl_shared_link_options("-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/dynapi/SDL_dynapi.sym") else() - if((LINUX AND LIBC_IS_GLIBC) OR ANDROID) + # When building with tcc on Linux+glibc or Android, avoid emitting an error + # for lack of support of the version-script linker flag: the option will be + # silently ignored by the compiler and the build will still succeed. + if(((LINUX AND LIBC_IS_GLIBC) OR ANDROID) AND (NOT USE_TCC)) message(FATAL_ERROR "Linker does not support '-Wl,--version-script=xxx.sym'. This is required on the current host platform (${SDL_CMAKE_PLATFORM}).") endif() endif() @@ -607,14 +653,13 @@ if(USE_GCC OR USE_CLANG OR USE_INTELCC OR USE_QCC) if(COMPILER_SUPPORTS_WNO_ERROR_DEPRECATED_DECLARATIONS) sdl_compile_options(PRIVATE "-Wno-error=deprecated-declarations") endif() + check_c_compiler_flag(-Wno-deprecated-declarations COMPILER_SUPPORTS_WNO_DEPRECATED_DECLARATIONS) + if(COMPILER_SUPPORTS_WNO_DEPRECATED_DECLARATIONS) + sdl_compile_options(PRIVATE "-Wno-deprecated-declarations") + endif() endif() - if(APPLE) - check_linker_flag(C "-Wl,-undefined,error" LINKER_SUPPORTS_WL_UNDEFINED_ERROR) - if(LINKER_SUPPORTS_WL_UNDEFINED_ERROR) - sdl_shared_link_options("-Wl,-undefined,error") - endif() - elseif(NOT OPENBSD) + if(NOT OPENBSD) cmake_push_check_state() check_linker_flag(C "-Wl,--no-undefined" LINKER_SUPPORTS_WL_NO_UNDEFINED) #FIXME: originally this if had an additional "AND NOT (USE_CLANG AND WINDOWS)" @@ -658,7 +703,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -mmmx") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void ints_add(int *dest, int *a, int *b, unsigned size) { for (; size >= 2; size -= 2, dest += 2, a += 2, b += 2) { @@ -668,7 +713,7 @@ if(SDL_ASSEMBLY) int main(int argc, char *argv[]) { ints_add((int*)0, (int*)0, (int*)0, 0); return 0; - }" COMPILER_SUPPORTS_MMX) + }]==] COMPILER_SUPPORTS_MMX) cmake_pop_check_state() if(COMPILER_SUPPORTS_MMX) set(HAVE_MMX TRUE) @@ -679,7 +724,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -msse") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void floats_add(float *dest, float *a, float *b, unsigned size) { for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { @@ -689,7 +734,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { floats_add((float*)0, (float*)0, (float*)0, 0); return 0; - }" COMPILER_SUPPORTS_SSE) + }]==] COMPILER_SUPPORTS_SSE) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE) set(HAVE_SSE TRUE) @@ -700,7 +745,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -msse2") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void doubles_add(double *dest, double *a, double *b, unsigned size) { for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { @@ -710,7 +755,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { doubles_add((double*)0, (double*)0, (double*)0, 0); return 0; - }" COMPILER_SUPPORTS_SSE2) + }]==] COMPILER_SUPPORTS_SSE2) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE2) set(HAVE_SSE2 TRUE) @@ -721,7 +766,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -msse3") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void ints_add(int *dest, int *a, int *b, unsigned size) { for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { @@ -731,7 +776,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { ints_add((int*)0, (int*)0, (int*)0, 0); return 0; - }" COMPILER_SUPPORTS_SSE3) + }]==] COMPILER_SUPPORTS_SSE3) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE3) set(HAVE_SSE3 TRUE) @@ -742,7 +787,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -msse4.1") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void ints_mul(int *dest, int *a, int *b, unsigned size) { for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { @@ -752,7 +797,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { ints_mul((int*)0, (int*)0, (int*)0, 0); return 0; - }" COMPILER_SUPPORTS_SSE4_1) + }]==] COMPILER_SUPPORTS_SSE4_1) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE4_1) set(HAVE_SSE4_1 TRUE) @@ -761,21 +806,16 @@ 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 -mcrc32") + string(APPEND CMAKE_REQUIRED_FLAGS " -msse4.2") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include - unsigned calc_crc32c(const char *text, unsigned len) { - unsigned crc32c = ~0; - for (; len >= 4; len -= 4, text += 4) { - crc32c = (unsigned)_mm_crc32_u32(crc32c, *(unsigned*)text); - } - return crc32c; - } + __m128i bitmask; + char data[16]; int main(int argc, char **argv) { - calc_crc32c(\"SDL_SSE4\",8); + bitmask = _mm_cmpgt_epi64(_mm_set1_epi64x(0), _mm_loadu_si128((void*)data)); return 0; - }" COMPILER_SUPPORTS_SSE4_2) + }]==] COMPILER_SUPPORTS_SSE4_2) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE4_2) set(HAVE_SSE4_2 TRUE) @@ -786,7 +826,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -mavx") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void floats_add(float *dest, float *a, float *b, unsigned size) { for (; size >= 8; size -= 8, dest += 8, a += 8, b += 8) { @@ -796,7 +836,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { floats_add((float*)0, (float*)0, (float*)0, 0); return 0; - }" COMPILER_SUPPORTS_AVX) + }]==] COMPILER_SUPPORTS_AVX) cmake_pop_check_state() if(COMPILER_SUPPORTS_AVX) set(HAVE_AVX TRUE) @@ -807,7 +847,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -mavx2") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void ints_add(int *dest, int *a, int *b, unsigned size) { for (; size >= 8; size -= 8, dest += 8, a += 8, b += 8) { @@ -817,7 +857,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { ints_add((int*)0, (int*)0, (int*)0, 0); return 0; - }" COMPILER_SUPPORTS_AVX2) + }]==] COMPILER_SUPPORTS_AVX2) cmake_pop_check_state() if(COMPILER_SUPPORTS_AVX2) set(HAVE_AVX2 TRUE) @@ -828,7 +868,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -mavx512f") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void floats_add(float *dest, float *a, float *b, unsigned size) { for (; size >= 16; size -= 16, dest += 16, a += 16, b += 16) { @@ -838,7 +878,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { floats_add((float*)0, (float*)0, (float*)0, 0); return 0; - }" COMPILER_SUPPORTS_AVX512F) + }]==] COMPILER_SUPPORTS_AVX512F) cmake_pop_check_state() if(COMPILER_SUPPORTS_AVX512F) set(HAVE_AVX512F TRUE) @@ -846,18 +886,17 @@ if(SDL_ASSEMBLY) endif() if(SDL_ARMNEON) - check_c_source_compiles(" - #include - void floats_add(float *dest, float *a, float *b, unsigned size) { - for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { - vst1q_f32(dest, vaddq_f32(vld1q_f32(a), vld1q_f32(b))); - } + check_arm_source_compiles([==[ + #include + void floats_add(float *dest, float *a, float *b, unsigned size) { + for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { + vst1q_f32(dest, vaddq_f32(vld1q_f32(a), vld1q_f32(b))); } - int main(int argc, char *argv[]) { - floats_add((float*)0, (float*)0, (float*)0, 0); - return 0; - }" COMPILER_SUPPORTS_ARMNEON) - + } + int main(int argc, char *argv[]) { + floats_add((float*)0, (float*)0, (float*)0, 0); + return 0; + }]==] COMPILER_SUPPORTS_ARMNEON) if(COMPILER_SUPPORTS_ARMNEON) set(HAVE_ARMNEON TRUE) endif() @@ -881,6 +920,7 @@ if(SDL_ASSEMBLY) set(HAVE_ALTIVEC TRUE) set(SDL_ALTIVEC_BLITTERS 1) sdl_compile_options(PRIVATE "-maltivec") + sdl_compile_options(PRIVATE "-fno-tree-vectorize") set_property(SOURCE "${SDL3_SOURCE_DIR}/src/video/SDL_blit_N.c" APPEND PROPERTY COMPILE_DEFINITIONS "SDL_ENABLE_ALTIVEC") set_property(SOURCE "${SDL3_SOURCE_DIR}/src/video/SDL_blit_N.c" PROPERTY SKIP_PRECOMPILE_HEADERS 1) endif() @@ -898,7 +938,17 @@ if(SDL_ASSEMBLY) cmake_pop_check_state() if(COMPILER_SUPPORTS_LSX AND HAVE_LSXINTRIN_H) - set_property(SOURCE "${SDL3_SOURCE_DIR}/src/video/yuv2rgb/yuv_rgb_lsx.c" APPEND PROPERTY COMPILE_OPTIONS "-mlsx") + set_property(SOURCE + "${SDL3_SOURCE_DIR}/src/video/yuv2rgb/yuv_rgb_lsx.c" + "${SDL3_SOURCE_DIR}/src/video/SDL_blit_A.c" + "${SDL3_SOURCE_DIR}/src/video/SDL_fillrect.c" + APPEND PROPERTY COMPILE_OPTIONS "-mlsx") + + set_property(SOURCE + "${SDL3_SOURCE_DIR}/src/video/yuv2rgb/yuv_rgb_lsx.c" + "${SDL3_SOURCE_DIR}/src/video/SDL_blit_A.c" + "${SDL3_SOURCE_DIR}/src/video/SDL_fillrect.c" + PROPERTY SKIP_PRECOMPILE_HEADERS 1) set(HAVE_LSX TRUE) endif() endif() @@ -972,7 +1022,14 @@ set(SDL_DISABLE_ALLOCA 0) check_include_file("alloca.h" "HAVE_ALLOCA_H") if(MSVC) check_include_file("malloc.h" "HAVE_MALLOC_H") - check_symbol_exists("_alloca" "malloc.h" _ALLOCA_IN_MALLOC_H) + # Cannot use CheckSymbolExists for _alloca: purely intrinsic functions have no address (C7552) + if(NOT DEFINED _ALLOCA_IN_MALLOC_H) + message(STATUS "Looking for _alloca in malloc.h") + set(testsrc "${CMAKE_CURRENT_SOURCE_DIR}/test_malloc_alloca.c") + file(WRITE "${testsrc}" "#include \n\nint main(int argc, char *argv[]) { void *ptr = _alloca(argc * (int)argv[0][0]); return ptr != (void *)0; }") + try_compile(_ALLOCA_IN_MALLOC_H "${CMAKE_CURRENT_BINARY_DIR}/alloca_in_malloc_h" SOURCES "${testsrc}") + message(STATUS "Looking for _alloca in malloc.h - ${_ALLOCA_IN_MALLOC_H}") + endif() if(NOT HAVE_ALLOCA_H AND NOT _ALLOCA_IN_MALLOC_H) set(SDL_DISABLE_ALLOCA 1) endif() @@ -1060,8 +1117,10 @@ if(SDL_LIBC) cmake_push_check_state() if(MSVC) string(APPEND CMAKE_REQUIRED_FLAGS " -we4244 -WX") # 'conversion' conversion from 'type1' to 'type2', possible loss of data - else() + elseif(HAVE_GCC_WFLOAT_CONVERSION) string(APPEND CMAKE_REQUIRED_FLAGS " -Wfloat-conversion -Werror") + else() + string(APPEND CMAKE_REQUIRED_FLAGS " -Wconversion -Werror") endif() foreach(math_fn isinf isnan) string(TOUPPER "${math_fn}" MATH_FN) @@ -1098,7 +1157,10 @@ if(SDL_LIBC) check_symbol_exists(fdatasync "unistd.h" HAVE_FDATASYNC) check_symbol_exists(gethostname "unistd.h" HAVE_GETHOSTNAME) check_symbol_exists(getpagesize "unistd.h" HAVE_GETPAGESIZE) + check_symbol_exists(getresgid "unistd.h" HAVE_GETRESGID) + check_symbol_exists(getresuid "unistd.h" HAVE_GETRESUID) check_symbol_exists(sigaction "signal.h" HAVE_SIGACTION) + check_symbol_exists(sigtimedwait "signal.h" HAVE_SIGTIMEDWAIT) check_symbol_exists(setjmp "setjmp.h" HAVE_SETJMP) check_symbol_exists(nanosleep "time.h" HAVE_NANOSLEEP) check_symbol_exists(gmtime_r "time.h" HAVE_GMTIME_R) @@ -1108,9 +1170,11 @@ if(SDL_LIBC) check_symbol_exists(sysctlbyname "sys/types.h;sys/sysctl.h" HAVE_SYSCTLBYNAME) check_symbol_exists(getauxval "sys/auxv.h" HAVE_GETAUXVAL) check_symbol_exists(elf_aux_info "sys/auxv.h" HAVE_ELF_AUX_INFO) - check_symbol_exists(poll "poll.h" HAVE_POLL) + check_symbol_exists(ppoll "poll.h" HAVE_PPOLL) check_symbol_exists(memfd_create "sys/mman.h" HAVE_MEMFD_CREATE) check_symbol_exists(posix_fallocate "fcntl.h" HAVE_POSIX_FALLOCATE) + check_symbol_exists(posix_spawn_file_actions_addchdir "spawn.h" HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR) + check_symbol_exists(posix_spawn_file_actions_addchdir_np "spawn.h" HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP) if(SDL_SYSTEM_ICONV) check_c_source_compiles(" @@ -1167,50 +1231,84 @@ endif() # General source files sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/*.c" + "${SDL3_SOURCE_DIR}/src/*.h" "${SDL3_SOURCE_DIR}/src/atomic/*.c" + "${SDL3_SOURCE_DIR}/src/atomic/*.h" "${SDL3_SOURCE_DIR}/src/audio/*.c" + "${SDL3_SOURCE_DIR}/src/audio/*.h" "${SDL3_SOURCE_DIR}/src/camera/*.c" + "${SDL3_SOURCE_DIR}/src/camera/*.h" "${SDL3_SOURCE_DIR}/src/core/*.c" + "${SDL3_SOURCE_DIR}/src/core/*.h" "${SDL3_SOURCE_DIR}/src/cpuinfo/*.c" + "${SDL3_SOURCE_DIR}/src/cpuinfo/*.h" "${SDL3_SOURCE_DIR}/src/dynapi/*.c" + "${SDL3_SOURCE_DIR}/src/dynapi/*.h" "${SDL3_SOURCE_DIR}/src/events/*.c" + "${SDL3_SOURCE_DIR}/src/events/*.h" "${SDL3_SOURCE_DIR}/src/io/*.c" + "${SDL3_SOURCE_DIR}/src/io/*.h" "${SDL3_SOURCE_DIR}/src/io/generic/*.c" + "${SDL3_SOURCE_DIR}/src/io/generic/*.h" "${SDL3_SOURCE_DIR}/src/filesystem/*.c" + "${SDL3_SOURCE_DIR}/src/filesystem/*.h" "${SDL3_SOURCE_DIR}/src/gpu/*.c" + "${SDL3_SOURCE_DIR}/src/gpu/*.h" "${SDL3_SOURCE_DIR}/src/joystick/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/*.h" "${SDL3_SOURCE_DIR}/src/haptic/*.c" + "${SDL3_SOURCE_DIR}/src/haptic/*.h" "${SDL3_SOURCE_DIR}/src/hidapi/*.c" + "${SDL3_SOURCE_DIR}/src/hidapi/*.h" "${SDL3_SOURCE_DIR}/src/locale/*.c" + "${SDL3_SOURCE_DIR}/src/locale/*.h" "${SDL3_SOURCE_DIR}/src/main/*.c" + "${SDL3_SOURCE_DIR}/src/main/*.h" "${SDL3_SOURCE_DIR}/src/misc/*.c" + "${SDL3_SOURCE_DIR}/src/misc/*.h" "${SDL3_SOURCE_DIR}/src/power/*.c" + "${SDL3_SOURCE_DIR}/src/power/*.h" "${SDL3_SOURCE_DIR}/src/render/*.c" + "${SDL3_SOURCE_DIR}/src/render/*.h" "${SDL3_SOURCE_DIR}/src/render/*/*.c" + "${SDL3_SOURCE_DIR}/src/render/*/*.h" "${SDL3_SOURCE_DIR}/src/sensor/*.c" + "${SDL3_SOURCE_DIR}/src/sensor/*.h" "${SDL3_SOURCE_DIR}/src/stdlib/*.c" + "${SDL3_SOURCE_DIR}/src/stdlib/*.h" "${SDL3_SOURCE_DIR}/src/storage/*.c" + "${SDL3_SOURCE_DIR}/src/storage/*.h" "${SDL3_SOURCE_DIR}/src/thread/*.c" + "${SDL3_SOURCE_DIR}/src/thread/*.h" "${SDL3_SOURCE_DIR}/src/time/*.c" + "${SDL3_SOURCE_DIR}/src/time/*.h" "${SDL3_SOURCE_DIR}/src/timer/*.c" + "${SDL3_SOURCE_DIR}/src/timer/*.h" "${SDL3_SOURCE_DIR}/src/video/*.c" + "${SDL3_SOURCE_DIR}/src/video/*.h" "${SDL3_SOURCE_DIR}/src/video/yuv2rgb/*.c" + "${SDL3_SOURCE_DIR}/src/video/yuv2rgb/*.h" ) -# Build uclibc as a static library such that non-used symbols don't end up in the SDL3 shared library. file(GLOB SDL_UCLIBC_SOURCES "${SDL3_SOURCE_DIR}/src/libm/*.c") -add_library(SDL_uclibc STATIC "${SDL_UCLIBC_SOURCES}") -target_compile_definitions(SDL_uclibc PRIVATE USING_GENERATED_CONFIG_H) -target_include_directories(SDL_uclibc PRIVATE "${SDL3_BINARY_DIR}/include-config-$>/build_config") -target_include_directories(SDL_uclibc PRIVATE "${SDL3_SOURCE_DIR}/src") -target_include_directories(SDL_uclibc PRIVATE "${SDL3_SOURCE_DIR}/include") -SDL_AddCommonCompilerFlags(SDL_uclibc) -sdl_sources(STATIC "$") if(TARGET SDL3-shared) + # Build uclibc as a static library such that non-used symbols don't end up in the SDL3 shared library. + add_library(SDL_uclibc STATIC ${SDL_UCLIBC_SOURCES}) + set_property(TARGET SDL_uclibc PROPERTY POSITION_INDEPENDENT_CODE TRUE) + target_compile_definitions(SDL_uclibc PRIVATE USING_GENERATED_CONFIG_H) + target_include_directories(SDL_uclibc PRIVATE "${SDL3_BINARY_DIR}/include-config-$>/build_config") + target_include_directories(SDL_uclibc PRIVATE "${SDL3_SOURCE_DIR}/src") + target_include_directories(SDL_uclibc PRIVATE "${SDL3_SOURCE_DIR}/include") + SDL_AddCommonCompilerFlags(SDL_uclibc) + target_compile_definitions(SDL_uclibc PRIVATE "$<$:DEBUG>") + set_property(TARGET SDL_uclibc PROPERTY UNITY_BUILD OFF) target_link_libraries(SDL3-shared PRIVATE SDL_uclibc) + if(HAVE_GCC_FVISIBILITY) + set_property(TARGET SDL_uclibc PROPERTY C_VISIBILITY_PRESET "hidden") + endif() endif() -if(HAVE_GCC_FVISIBILITY) - set_property(TARGET SDL_uclibc PROPERTY C_VISIBILITY_PRESET "hidden") +if(TARGET SDL3-static) + target_sources(SDL3-static PRIVATE ${SDL_UCLIBC_SOURCES}) endif() # Enable/disable various subsystems of the SDL library @@ -1233,13 +1331,19 @@ if(SDL_AUDIO) # CheckDummyAudio/CheckDiskAudio - valid for all platforms if(SDL_DUMMYAUDIO) set(SDL_AUDIO_DRIVER_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/audio/dummy/*.h" + ) set(HAVE_DUMMYAUDIO TRUE) set(HAVE_SDL_AUDIO TRUE) endif() if(SDL_DISKAUDIO) set(SDL_AUDIO_DRIVER_DISK 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/disk/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/disk/*.c" + "${SDL3_SOURCE_DIR}/src/audio/disk/*.h" + ) set(HAVE_DISKAUDIO TRUE) set(HAVE_SDL_AUDIO TRUE) endif() @@ -1249,7 +1353,10 @@ if(SDL_CAMERA) # CheckDummyCamera/CheckDiskCamera - valid for all platforms if(SDL_DUMMYCAMERA) set(SDL_CAMERA_DRIVER_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/camera/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/camera/dummy/*.h" + ) set(HAVE_DUMMYCAMERA TRUE) set(HAVE_SDL_CAMERA TRUE) endif() @@ -1268,7 +1375,10 @@ if(UNIX OR APPLE) CheckDLOPEN() if(HAVE_DLOPEN) set(SDL_LOADSO_DLOPEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/loadso/dlopen/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/loadso/dlopen/*.c" + "${SDL3_SOURCE_DIR}/src/loadso/dlopen/*.h" + ) set(HAVE_SDL_LOADSO TRUE) endif() endif() @@ -1281,14 +1391,20 @@ if(SDL_JOYSTICK) if(SDL_VIRTUAL_JOYSTICK) set(HAVE_VIRTUAL_JOYSTICK TRUE) set(SDL_JOYSTICK_VIRTUAL 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/virtual/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/virtual/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/virtual/*.h" + ) endif() endif() if(SDL_VIDEO) if(SDL_DUMMYVIDEO) set(SDL_VIDEO_DRIVER_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/video/dummy/*.h" + ) set(HAVE_DUMMYVIDEO TRUE) set(HAVE_SDL_VIDEO TRUE) endif() @@ -1298,11 +1414,15 @@ endif() 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("${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/core/android/*.c" + "${SDL3_SOURCE_DIR}/src/core/android/*.h" + ) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/misc/android/*.c" + "${SDL3_SOURCE_DIR}/src/misc/android/*.h" + ) set(HAVE_SDL_MISC TRUE) # SDL_spinlock.c Needs to be compiled in ARM mode. @@ -1320,18 +1440,27 @@ if(ANDROID) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_OPENSLES 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/openslES/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/openslES/*.c" + "${SDL3_SOURCE_DIR}/src/audio/openslES/*.h" + ) sdl_link_dependency(opensles LIBS ${ANDROID_DL_LIBRARY} OpenSLES) set(SDL_AUDIO_DRIVER_AAUDIO 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/aaudio/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/aaudio/*.c" + "${SDL3_SOURCE_DIR}/src/audio/aaudio/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() set(SDL_FILESYSTEM_ANDROID 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/filesystem/android/*.c" + "${SDL3_SOURCE_DIR}/src/filesystem/android/*.h" + ) set(HAVE_SDL_FILESYSTEM TRUE) set(SDL_FSOPS_POSIX 1) # !!! FIXME: this might need something else for .apk data? @@ -1340,7 +1469,10 @@ if(ANDROID) if(SDL_HAPTIC) set(SDL_HAPTIC_ANDROID 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/haptic/android/*.c" + "${SDL3_SOURCE_DIR}/src/haptic/android/*.h" + ) set(HAVE_SDL_HAPTIC TRUE) endif() @@ -1350,46 +1482,71 @@ if(ANDROID) set(SDL_JOYSTICK_ANDROID 1) sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/joystick/android/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/android/*.h" ) set(HAVE_SDL_JOYSTICK TRUE) endif() set(SDL_LOADSO_DLOPEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/loadso/dlopen/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/loadso/dlopen/*.c" + "${SDL3_SOURCE_DIR}/src/loadso/dlopen/*.h" + ) set(HAVE_SDL_LOADSO TRUE) if(SDL_POWER) set(SDL_POWER_ANDROID 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/power/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/power/android/*.c" + "${SDL3_SOURCE_DIR}/src/power/android/*.h" + ) set(HAVE_SDL_POWER TRUE) endif() - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/locale/android/*.c" + "${SDL3_SOURCE_DIR}/src/locale/android/*.h" + ) set(HAVE_SDL_LOCALE TRUE) set(SDL_TIME_UNIX 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/time/unix/*.c" + "${SDL3_SOURCE_DIR}/src/time/unix/*.h" + ) set(HAVE_SDL_TIME TRUE) set(SDL_TIMER_UNIX 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/unix/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/timer/unix/*.c" + "${SDL3_SOURCE_DIR}/src/timer/unix/*.h" + ) set(HAVE_SDL_TIMERS TRUE) if(SDL_SENSOR) set(SDL_SENSOR_ANDROID 1) set(HAVE_SDL_SENSORS TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/sensor/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/sensor/android/*.c" + "${SDL3_SOURCE_DIR}/src/sensor/android/*.h" + ) endif() if(SDL_CAMERA) set(SDL_CAMERA_DRIVER_ANDROID 1) set(HAVE_CAMERA TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/camera/android/*.c" + "${SDL3_SOURCE_DIR}/src/camera/android/*.h" + ) endif() if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_ANDROID 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/android/*.c" + "${SDL3_SOURCE_DIR}/src/video/android/*.h" + ) set(HAVE_SDL_VIDEO TRUE) # Core stuff @@ -1486,31 +1643,41 @@ if(ANDROID) endif() 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() + if(TARGET SDL3-static) + target_link_options(SDL3-static INTERFACE "-Wl,-u,JNI_OnLoad") + 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. sdl_compile_options(PRIVATE "-Wno-warn-absolute-paths") - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/main/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/main/emscripten/*.h" + ) set(HAVE_SDL_MAIN_CALLBACKS TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/misc/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/misc/emscripten/*.h" + ) set(HAVE_SDL_MISC TRUE) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_EMSCRIPTEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/audio/emscripten/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() set(SDL_FILESYSTEM_EMSCRIPTEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/filesystem/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/filesystem/emscripten/*.h" + ) set(HAVE_SDL_FILESYSTEM TRUE) set(SDL_FSOPS_POSIX 1) @@ -1520,44 +1687,73 @@ elseif(EMSCRIPTEN) if(SDL_CAMERA) set(SDL_CAMERA_DRIVER_EMSCRIPTEN 1) set(HAVE_CAMERA TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/camera/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/camera/emscripten/*.h" + ) endif() if(SDL_JOYSTICK) set(SDL_JOYSTICK_EMSCRIPTEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/emscripten/*.h" + ) set(HAVE_SDL_JOYSTICK TRUE) endif() if(SDL_POWER) set(SDL_POWER_EMSCRIPTEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/power/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/power/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/power/emscripten/*.h" + ) set(HAVE_SDL_POWER TRUE) endif() - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/locale/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/locale/emscripten/*.h" + ) set(HAVE_SDL_LOCALE TRUE) set(SDL_TIME_UNIX 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/time/unix/*.c" + "${SDL3_SOURCE_DIR}/src/time/unix/*.h" + ) set(HAVE_SDL_TIME TRUE) set(SDL_TIMER_UNIX 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/unix/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/timer/unix/*.c" + "${SDL3_SOURCE_DIR}/src/timer/unix/*.h" + ) set(HAVE_SDL_TIMERS TRUE) if(SDL_CLOCK_GETTIME) set(HAVE_CLOCK_GETTIME 1) endif() + if(SDL_SENSOR) + set(SDL_SENSOR_EMSCRIPTEN 1) + set(HAVE_SDL_SENSORS TRUE) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/sensor/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/sensor/emscripten/*.h" + ) + endif() + if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_EMSCRIPTEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/video/emscripten/*.h" + ) set(HAVE_SDL_VIDEO TRUE) #enable gles if(SDL_OPENGLES) - set(SDL_VIDEO_OPENGL_EGL 1) set(HAVE_OPENGLES TRUE) set(SDL_VIDEO_OPENGL_ES2 1) set(SDL_VIDEO_RENDER_OGL_ES2 1) @@ -1568,14 +1764,41 @@ elseif(EMSCRIPTEN) CheckLibUnwind() elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) + + set(SDL_DISABLE_DLOPEN_NOTES TRUE) + if(SDL_DLOPEN_NOTES) + set(CHECK_ELF_DLNOTES_SRC [==[ + #ifndef __ELF__ + ELF DL notes is only supported on ELF platforms + #endif + __attribute__ ((used,aligned(4),section(".note.dlopen"))) static const struct { + struct { int a; int b; int c; } hdr; char name[4]; __attribute__((aligned(4))) char json[24]; + } dlnote = { { 4, 0x407c0c0aU, 16 }, "FDO", "[\\"a\\":{\\"a\\":\\"1\\",\\"b\\":\\"2\\"}]" }; + int main(int argc, char *argv[]) { + return argc + dlnote.hdr.a; + } + ]==]) + check_c_source_compiles("${CHECK_ELF_DLNOTES_SRC}" COMPILER_SUPPORTS_ELFNOTES) + if(COMPILER_SUPPORTS_ELFNOTES) + set(SDL_DISABLE_DLOPEN_NOTES FALSE) + set(HAVE_DLOPEN_NOTES TRUE) + endif() + endif() + if(SDL_AUDIO) if(NETBSD) set(SDL_AUDIO_DRIVER_NETBSD 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/netbsd/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/netbsd/*.c" + "${SDL3_SOURCE_DIR}/src/audio/netbsd/*.h" + ) set(HAVE_SDL_AUDIO TRUE) elseif(QNX) set(SDL_AUDIO_DRIVER_QNX 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/qnx/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/qnx/*.c" + "${SDL3_SOURCE_DIR}/src/audio/qnx/*.h" + ) sdl_link_dependency(asound LIBS asound) set(HAVE_SDL_AUDIO TRUE) endif() @@ -1593,6 +1816,8 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) # Need to check for ROCKCHIP platform and get rid of "Can't window GBM/EGL surfaces on window creation." CheckROCKCHIP() CheckX11() + CheckFribidi() + CheckLibThai() # Need to check for EGL first because KMSDRM and Wayland depend on it. CheckEGL() CheckKMSDRM() @@ -1604,13 +1829,21 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) CheckVivante() CheckVulkan() CheckQNXScreen() + endif() - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/unix/*.c") + if(SDL_TRAY) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/tray/unix/*.c" + "${SDL3_SOURCE_DIR}/src/tray/unix/*.h" + ) set(HAVE_SDL_TRAY TRUE) endif() if(UNIX) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/unix/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/core/unix/*.c" + "${SDL3_SOURCE_DIR}/src/core/unix/*.h" + ) check_c_source_compiles(" #include @@ -1659,7 +1892,10 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) if(SDL_CAMERA AND HAVE_LINUX_VIDEODEV2_H) set(SDL_CAMERA_DRIVER_V4L2 1) set(HAVE_CAMERA TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/v4l2/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/camera/v4l2/*.c" + "${SDL3_SOURCE_DIR}/src/camera/v4l2/*.h" + ) endif() if(HAVE_LINUX_INPUT_H) @@ -1668,7 +1904,10 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) if(SDL_HAPTIC AND HAVE_LINUX_INPUT_H) set(SDL_HAPTIC_LINUX 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/linux/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/haptic/linux/*.c" + "${SDL3_SOURCE_DIR}/src/haptic/linux/*.h" + ) set(HAVE_SDL_HAPTIC TRUE) endif() @@ -1728,7 +1967,6 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) set(HAVE_LIBURING TRUE) endif() endif() - if((FREEBSD OR NETBSD) AND NOT HAVE_INOTIFY) set(LibInotify_PKG_CONFIG_SPEC libinotify) pkg_check_modules(PC_LIBINOTIFY IMPORTED_TARGET ${LibInotify_PKG_CONFIG_SPEC}) @@ -1744,24 +1982,40 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) if(HAVE_DBUS_DBUS_H) sdl_sources( "${SDL3_SOURCE_DIR}/src/core/linux/SDL_dbus.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_dbus.h" "${SDL3_SOURCE_DIR}/src/core/linux/SDL_system_theme.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_system_theme.h" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_progressbar.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_progressbar.h" ) endif() if(SDL_USE_IME) - sdl_sources("${SDL3_SOURCE_DIR}/src/core/linux/SDL_ime.c") + sdl_sources( + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_ime.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_ime.h" + ) endif() if(HAVE_IBUS_IBUS_H) - sdl_sources("${SDL3_SOURCE_DIR}/src/core/linux/SDL_ibus.c") + sdl_sources( + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_ibus.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_ibus.h" + ) endif() if(HAVE_FCITX) - sdl_sources("${SDL3_SOURCE_DIR}/src/core/linux/SDL_fcitx.c") + sdl_sources( + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_fcitx.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_fcitx.h" + ) endif() if(HAVE_LIBUDEV_H) - sdl_sources("${SDL3_SOURCE_DIR}/src/core/linux/SDL_udev.c") + sdl_sources( + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_udev.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_udev.h" + ) endif() if(HAVE_LINUX_INPUT_H) @@ -1772,26 +2026,20 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) endif() if(HAVE_INPUT_KBIO) - sdl_sources("${SDL3_SOURCE_DIR}/src/core/freebsd/SDL_evdev_kbd_freebsd.c") + sdl_sources( + "${SDL3_SOURCE_DIR}/src/core/freebsd/SDL_evdev_kbd_freebsd.c" + "${SDL3_SOURCE_DIR}/src/core/freebsd/SDL_evdev_kbd_default_keyaccmap.h" + ) endif() if(HAVE_INPUT_WSCONS) sdl_sources( "${SDL3_SOURCE_DIR}/src/core/openbsd/SDL_wscons_kbd.c" "${SDL3_SOURCE_DIR}/src/core/openbsd/SDL_wscons_mouse.c" + "${SDL3_SOURCE_DIR}/src/core/openbsd/SDL_wscons.h" ) endif() - if(SDL_GPU AND SDL_GPU_DXVK) - if(PKG_CONFIG_FOUND) - pkg_search_module(DXVK_NATIVE dxvk-dxgi) - if(DXVK_NATIVE_FOUND) - set(HAVE_D3D11_H TRUE) - sdl_include_directories(PRIVATE SYSTEM ${DXVK_NATIVE_INCLUDE_DIRS}) - endif() - endif() - endif() - if(HAVE_LIBURING_H) sdl_sources("${SDL3_SOURCE_DIR}/src/io/io_uring/SDL_asyncio_liburing.c") endif() @@ -1799,6 +2047,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) # Always compiled for Linux, unconditionally: sdl_sources( "${SDL3_SOURCE_DIR}/src/core/linux/SDL_evdev_capabilities.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_evdev_capabilities.h" "${SDL3_SOURCE_DIR}/src/core/linux/SDL_threadprio.c" ) @@ -1817,6 +2066,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) set(SDL_JOYSTICK_LINUX 1) sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/joystick/linux/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/linux/*.h" ) set(HAVE_SDL_JOYSTICK TRUE) endif() @@ -1859,7 +2109,10 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/generic/*.c") if(LINUX) set(SDL_STORAGE_STEAM 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/steam/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/storage/steam/*.c" + "${SDL3_SOURCE_DIR}/src/storage/steam/*.h" + ) endif() set(HAVE_SDL_STORAGE 1) @@ -1902,11 +2155,16 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) set (USE_POSIX_SPAWN 1) endif() elseif(WINDOWS) + enable_language(CXX) check_c_source_compiles(" #include int main(int argc, char **argv) { return 0; }" HAVE_WIN32_CC) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/windows/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/core/windows/*.c" + "${SDL3_SOURCE_DIR}/src/core/windows/*.cpp" + "${SDL3_SOURCE_DIR}/src/core/windows/*.h" + ) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/windows/*.c") sdl_glob_sources("${SDL3_SOURCE_DIR}/src/io/windows/*.c") @@ -1916,7 +2174,7 @@ elseif(WINDOWS) target_compile_options(SDL_uclibc PRIVATE $<$:/GS-> $<$:/Gs1048576>) if(SDL_CPU_X86) target_compile_options(SDL3-shared PRIVATE "/arch:SSE") - target_compile_options(SDL3-SDL_uclibc PRIVATE "/arch:SSE") + target_compile_options(SDL_uclibc PRIVATE "/arch:SSE") endif() endif() @@ -1955,7 +2213,7 @@ elseif(WINDOWS) else() set(PROCESSOR_ARCH "x86") endif() - sdl_link_directories("$") + sdl_link_directories("$") sdl_include_directories(PRIVATE SYSTEM "$") endif() endif() @@ -1966,7 +2224,8 @@ elseif(WINDOWS) check_c_source_compiles(" #include #include - int main(int argc, char **argv) { return 0; }" HAVE_XINPUT_H) + int main(int argc, char **argv) { return 0; }" HAVE_XINPUT_H + ) endif() # headers needed elsewhere @@ -1982,6 +2241,7 @@ elseif(WINDOWS) #include int main(int argc, char **argv) { return 0; }" HAVE_GAMEINPUT_H ) + check_include_file(dxgi1_5.h HAVE_DXGI1_5_H) check_include_file(dxgi1_6.h HAVE_DXGI1_6_H) check_include_file(tpcshrd.h HAVE_TPCSHRD_H) check_include_file(roapi.h HAVE_ROAPI_H) @@ -1994,6 +2254,7 @@ elseif(WINDOWS) #include #include #include + static MFVideoPrimaries primaries = MFVideoPrimaries_DCI_P3; int main(int argc, char **argv) { return 0; } " HAVE_MFAPI_H ) @@ -2001,21 +2262,31 @@ elseif(WINDOWS) if(SDL_AUDIO) if(HAVE_DSOUND_H) set(SDL_AUDIO_DRIVER_DSOUND 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/directsound/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/directsound/*.c" + "${SDL3_SOURCE_DIR}/src/audio/directsound/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() if(SDL_WASAPI AND HAVE_AUDIOCLIENT_H AND HAVE_MMDEVICEAPI_H) set(SDL_AUDIO_DRIVER_WASAPI 1) set(HAVE_WASAPI TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/wasapi/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/wasapi/*.c" + "${SDL3_SOURCE_DIR}/src/audio/wasapi/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() endif() if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_WINDOWS 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/windows/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/windows/*.c" + "${SDL3_SOURCE_DIR}/src/video/windows/*.cpp" + "${SDL3_SOURCE_DIR}/src/video/windows/*.h" + ) CheckOpenVR() @@ -2027,7 +2298,7 @@ elseif(WINDOWS) set(SDL_VIDEO_RENDER_D3D11 1) set(HAVE_RENDER_D3D11 TRUE) endif() - if(SDL_RENDER_D3D12) + if(SDL_RENDER_D3D12 AND HAVE_DXGI1_6_H) set(SDL_VIDEO_RENDER_D3D12 1) set(HAVE_RENDER_D3D12 TRUE) endif() @@ -2039,7 +2310,9 @@ elseif(WINDOWS) set(SDL_THREAD_WINDOWS 1) sdl_sources( "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock_c.h" "${SDL3_SOURCE_DIR}/src/thread/windows/SDL_syscond_cv.c" "${SDL3_SOURCE_DIR}/src/thread/windows/SDL_sysmutex.c" "${SDL3_SOURCE_DIR}/src/thread/windows/SDL_sysrwlock_srw.c" @@ -2053,7 +2326,10 @@ elseif(WINDOWS) if(SDL_SENSOR AND HAVE_SENSORSAPI_H) set(SDL_SENSOR_WINDOWS 1) set(HAVE_SDL_SENSORS TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/sensor/windows/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/sensor/windows/*.c" + "${SDL3_SOURCE_DIR}/src/sensor/windows/*.h" + ) endif() if(SDL_POWER) @@ -2075,7 +2351,10 @@ elseif(WINDOWS) set(SDL_STORAGE_GENERIC 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/generic/*.c") set(SDL_STORAGE_STEAM 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/steam/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/storage/steam/*.c" + "${SDL3_SOURCE_DIR}/src/storage/steam/*.h" + ) set(HAVE_SDL_STORAGE 1) # Libraries for Win32 native and MinGW @@ -2093,7 +2372,10 @@ elseif(WINDOWS) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/loadso/windows/*.c") set(HAVE_SDL_LOADSO TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/windows/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/core/windows/*.c" + "${SDL3_SOURCE_DIR}/src/core/windows/*.h" + ) if(SDL_VIDEO) if(SDL_OPENGL) @@ -2118,7 +2400,9 @@ elseif(WINDOWS) set(HAVE_RENDER_VULKAN TRUE) endif() endif() + endif() + if(SDL_TRAY) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/windows/*.c") set(HAVE_SDL_TRAY TRUE) endif() @@ -2128,7 +2412,10 @@ elseif(WINDOWS) endif() if(SDL_JOYSTICK) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/windows/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/windows/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/windows/*.h" + ) set(SDL_JOYSTICK_RAWINPUT 1) if(HAVE_DINPUT_H) @@ -2143,14 +2430,17 @@ elseif(WINDOWS) set(SDL_JOYSTICK_WGI 1) endif() if(HAVE_GAMEINPUT_H) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/gdk/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/gdk/*.cpp") set(SDL_JOYSTICK_GAMEINPUT 1) endif() set(HAVE_SDL_JOYSTICK TRUE) if(SDL_HAPTIC) if(HAVE_DINPUT_H) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/windows/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/haptic/windows/*.c" + "${SDL3_SOURCE_DIR}/src/haptic/windows/*.h" + ) set(SDL_HAPTIC_DINPUT 1) set(HAVE_SDL_HAPTIC TRUE) endif() @@ -2212,7 +2502,10 @@ elseif(APPLE) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_COREAUDIO 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/coreaudio/*.m") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/coreaudio/*.m" + "${SDL3_SOURCE_DIR}/src/audio/coreaudio/*.h" + ) set(HAVE_SDL_AUDIO TRUE) set(SDL_FRAMEWORK_COREAUDIO 1) set(SDL_FRAMEWORK_AUDIOTOOLBOX 1) @@ -2224,7 +2517,10 @@ elseif(APPLE) endif() if(SDL_JOYSTICK) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/apple/*.m") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/apple/*.m" + "${SDL3_SOURCE_DIR}/src/joystick/apple/*.h" + ) if(IOS OR TVOS OR VISIONOS OR WATCHOS) set(SDL_JOYSTICK_MFI 1) if(IOS OR VISIONOS OR WATCHOS) @@ -2233,7 +2529,10 @@ elseif(APPLE) set(SDL_FRAMEWORK_GAMECONTROLLER 1) set(SDL_FRAMEWORK_COREHAPTICS 1) else() - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/darwin/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/darwin/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/darwin/*.h" + ) set_property(SOURCE ${MFI_JOYSTICK_SOURCES} APPEND_STRING PROPERTY COMPILE_FLAGS " -fobjc-weak") check_objc_source_compiles(" #include @@ -2273,7 +2572,10 @@ elseif(APPLE) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/dummy/*.c") set(SDL_HAPTIC_DUMMY 1) else() - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/darwin/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/haptic/darwin/*.c" + "${SDL3_SOURCE_DIR}/src/haptic/darwin/*.h" + ) set(SDL_HAPTIC_IOKIT 1) set(SDL_FRAMEWORK_IOKIT 1) set(SDL_FRAMEWORK_FF 1) @@ -2283,7 +2585,10 @@ elseif(APPLE) if(SDL_POWER) if (IOS OR TVOS OR VISIONOS OR WATCHOS) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/power/uikit/*.m") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/power/uikit/*.m" + "${SDL3_SOURCE_DIR}/src/power/uikit/*.h" + ) set(SDL_POWER_UIKIT 1) else() sdl_glob_sources("${SDL3_SOURCE_DIR}/src/power/macos/*.c") @@ -2313,7 +2618,10 @@ elseif(APPLE) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/generic/*.c") if(MACOS) set(SDL_STORAGE_STEAM 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/steam/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/storage/steam/*.c" + "${SDL3_SOURCE_DIR}/src/storage/steam/*.h" + ) endif() set(HAVE_SDL_STORAGE 1) @@ -2325,7 +2633,10 @@ elseif(APPLE) if(IOS OR VISIONOS OR WATCHOS) set(SDL_SENSOR_COREMOTION 1) set(HAVE_SDL_SENSORS TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/sensor/coremotion/*.m") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/sensor/coremotion/*.m" + "${SDL3_SOURCE_DIR}/src/sensor/coremotion/*.h" + ) endif() endif() @@ -2338,7 +2649,11 @@ elseif(APPLE) set(SDL_FRAMEWORK_UIKIT 1) set(SDL_IPHONE_KEYBOARD 1) set(SDL_IPHONE_LAUNCHSCREEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/uikit/*.m") + set(SDL_FRAMEWORK_GAMECONTROLLER 1) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/uikit/*.m" + "${SDL3_SOURCE_DIR}/src/video/uikit/*.h" + ) set(HAVE_SDL_VIDEO TRUE) else() CheckCOCOA() @@ -2388,21 +2703,27 @@ elseif(APPLE) set(HAVE_METAL TRUE) endif() if(SDL_RENDER_METAL) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/render/metal/*.m") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/render/metal/*.m" + "${SDL3_SOURCE_DIR}/src/render/metal/*.h" + ) set(SDL_VIDEO_RENDER_METAL 1) set(HAVE_RENDER_METAL TRUE) endif() if (SDL_GPU) set(SDL_GPU_METAL 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/gpu/metal/*.m") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/gpu/metal/*.m" + "${SDL3_SOURCE_DIR}/src/gpu/metal/*.h" + ) endif() endif() endif() + endif() - if(MACOS) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/cocoa/*.m") - set(HAVE_SDL_TRAY TRUE) - endif() + if(SDL_TRAY AND MACOS) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/cocoa/*.m") + set(HAVE_SDL_TRAY TRUE) endif() # Minimum version for $ @@ -2488,7 +2809,10 @@ elseif(HAIKU) enable_language(CXX) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_HAIKU 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/haiku/*.cc") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/haiku/*.cc" + "${SDL3_SOURCE_DIR}/src/audio/haiku/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() @@ -2503,7 +2827,10 @@ elseif(HAIKU) if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_HAIKU 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/haiku/*.cc") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/haiku/*.cc" + "${SDL3_SOURCE_DIR}/src/video/haiku/*.h" + ) set(HAVE_SDL_VIDEO TRUE) if(SDL_OPENGL) @@ -2541,7 +2868,10 @@ elseif(HAIKU) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/haiku/*.cc") set(HAVE_SDL_LOCALE TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/haiku/*.cc") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/core/haiku/*.cc" + "${SDL3_SOURCE_DIR}/src/core/haiku/*.h" + ) CheckPTHREAD() sdl_link_dependency(base LIBS root be media game device textencoding tracker) @@ -2552,7 +2882,10 @@ elseif(RISCOS) if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_RISCOS 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/riscos/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/riscos/*.c" + "${SDL3_SOURCE_DIR}/src/video/riscos/*.h" + ) set(HAVE_SDL_VIDEO TRUE) endif() @@ -2592,12 +2925,17 @@ elseif(VITA) set_property(SOURCE "${SDL3_SOURCE_DIR}/src/atomic/SDL_spinlock.c" APPEND_STRING PROPERTY COMPILE_FLAGS " -marm") endif() - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/vita/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/misc/vita/*.c" + ) set(HAVE_SDL_MISC TRUE) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_VITA 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/vita/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/vita/*.c" + "${SDL3_SOURCE_DIR}/src/audio/vita/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() @@ -2624,10 +2962,14 @@ elseif(VITA) set(SDL_THREAD_VITA 1) sdl_sources( "${SDL3_SOURCE_DIR}/src/thread/vita/SDL_sysmutex.c" + "${SDL3_SOURCE_DIR}/src/thread/vita/SDL_sysmutex_c.h" "${SDL3_SOURCE_DIR}/src/thread/vita/SDL_syssem.c" "${SDL3_SOURCE_DIR}/src/thread/vita/SDL_systhread.c" + "${SDL3_SOURCE_DIR}/src/thread/vita/SDL_systhread_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c" ) set(HAVE_SDL_THREADS TRUE) @@ -2646,7 +2988,10 @@ elseif(VITA) if(SDL_SENSOR) set(SDL_SENSOR_VITA 1) set(HAVE_SDL_SENSORS TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/sensor/vita/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/sensor/vita/*.c" + "${SDL3_SOURCE_DIR}/src/sensor/vita/*.h" + ) endif() if(SDL_CAMERA) @@ -2657,7 +3002,10 @@ elseif(VITA) if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_VITA 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/vita/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/vita/*.c" + "${SDL3_SOURCE_DIR}/src/video/vita/*.h" + ) set(HAVE_SDL_VIDEO TRUE) if(VIDEO_VITA_PIB) @@ -2746,7 +3094,10 @@ elseif(PSP) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_PSP 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/psp/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/psp/*.c" + "${SDL3_SOURCE_DIR}/src/audio/psp/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() @@ -2773,9 +3124,12 @@ elseif(PSP) set(SDL_THREAD_PSP 1) sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock_c.h" "${SDL3_SOURCE_DIR}/src/thread/psp/*.c" + "${SDL3_SOURCE_DIR}/src/thread/psp/*.h" ) set(HAVE_SDL_THREADS TRUE) @@ -2793,7 +3147,10 @@ elseif(PSP) if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_PSP 1) set(SDL_VIDEO_RENDER_PSP 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/psp/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/psp/*.c" + "${SDL3_SOURCE_DIR}/src/video/psp/*.h" + ) set(SDL_VIDEO_OPENGL 1) set(HAVE_SDL_VIDEO TRUE) endif() @@ -2817,7 +3174,10 @@ elseif(PS2) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_PS2 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/ps2/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/ps2/*.c" + "${SDL3_SOURCE_DIR}/src/audio/ps2/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() @@ -2838,10 +3198,13 @@ elseif(PS2) set(SDL_THREAD_PS2 1) sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysmutex.c" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c" "${SDL3_SOURCE_DIR}/src/thread/ps2/*.c" + "${SDL3_SOURCE_DIR}/src/thread/ps2/*.h" ) set(HAVE_SDL_THREADS TRUE) @@ -2876,7 +3239,10 @@ elseif(N3DS) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_N3DS 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/n3ds/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/n3ds/*.c" + "${SDL3_SOURCE_DIR}/src/audio/n3ds/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() @@ -2899,11 +3265,16 @@ elseif(N3DS) endif() set(SDL_THREAD_N3DS 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/thread/n3ds/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/thread/n3ds/*.c" + "${SDL3_SOURCE_DIR}/src/thread/n3ds/*.h" + ) sdl_sources( "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock_c.h" ) set(HAVE_SDL_THREADS TRUE) @@ -2927,14 +3298,115 @@ elseif(N3DS) if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_N3DS 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/n3ds/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/n3ds/*.c" + "${SDL3_SOURCE_DIR}/src/video/n3ds/*.h" + ) set(HAVE_SDL_VIDEO TRUE) endif() sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/n3ds/*.c") set(HAVE_SDL_LOCALE TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/io/n3ds/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/io/n3ds/*.c" + "${SDL3_SOURCE_DIR}/src/io/n3ds/*.h" + ) + +elseif(NGAGE) + + enable_language(CXX) + + set(SDL_MAIN_USE_CALLBACKS 1) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/main/ngage/*.c" + "${SDL3_SOURCE_DIR}/src/main/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/main/ngage/*.hpp" + ) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/core/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/core/ngage/*.h" + ) + set(HAVE_SDL_MAIN_CALLBACKS TRUE) + + if(SDL_AUDIO) + set(SDL_AUDIO_DRIVER_NGAGE 1) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/ngage/*.c" + "${SDL3_SOURCE_DIR}/src/audio/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/audio/ngage/*.h" + "${SDL3_SOURCE_DIR}/src/audio/ngage/*.hpp" + ) + set(HAVE_SDL_AUDIO TRUE) + endif() + + set(SDL_FILESYSTEM_NGAGE 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/ngage/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/ngage/*.cpp") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/*.c") + set(HAVE_SDL_FILESYSTEM TRUE) + + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/ngage/*.cpp") + + if(SDL_RENDER) + set(SDL_VIDEO_RENDER_NGAGE 1) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/render/ngage/*.c" + "${SDL3_SOURCE_DIR}/src/render/ngage/*.h" + "${SDL3_SOURCE_DIR}/src/render/ngage/*.hpp" + ) + endif() + + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/ngage/*.cpp") + set(SDL_TIME_NGAGE 1) + + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/render/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/render/ngage/*.h" + ) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c") + + set(SDL_TIMER_NGAGE 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/ngage/*.cpp") + + set(SDL_FSOPS_POSIX 1) + + set(SDL_VIDEO_DRIVER_NGAGE 1) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/ngage/*.c" + "${SDL3_SOURCE_DIR}/src/video/ngage/*.h" + ) + set(HAVE_SDL_TIMERS TRUE) + + set_option(SDL_LEAN_AND_MEAN "Enable lean and mean" ON) + if(SDL_LEAN_AND_MEAN) + sdl_compile_definitions( + PRIVATE + SDL_LEAN_AND_MEAN + ) + endif() + + sdl_link_dependency(ngage + LINK_OPTIONS "SHELL:-s MAIN_COMPAT=0" + PKG_CONFIG_LINK_OPTIONS "-s;MAIN_COMPAT=0" + LIBS + NRenderer + 3dtypes + cone + libgcc + libgcc_ngage + mediaclientaudiostream + charconv + bitgdi + euser + estlib + ws32 + hal + fbscli + efsrv + scdv + gdi + ) endif() sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/SDL_dialog.c) @@ -2946,7 +3418,9 @@ if (SDL_DIALOG) elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_unixdialog.c) sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_portaldialog.c) + sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_portaldialog.h) sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_zenitydialog.c) + sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_zenitydialog.h) set(HAVE_SDL_DIALOG TRUE) elseif(HAIKU) sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/haiku/SDL_haikudialog.cc) @@ -2959,13 +3433,20 @@ if (SDL_DIALOG) set(HAVE_SDL_DIALOG TRUE) endif() endif() +if(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) + sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_zenitymessagebox.h) + sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_zenitymessagebox.c) +endif() sdl_sources("${SDL3_SOURCE_DIR}/src/process/SDL_process.c") if(WINDOWS) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/process/windows/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/process/windows/*.c" + "${SDL3_SOURCE_DIR}/src/process/windows/*.h" + ) set(SDL_PROCESS_WINDOWS 1) set(HAVE_SDL_PROCESS TRUE) -else() +elseif(NOT ANDROID) check_c_source_compiles(" #include #include @@ -3002,8 +3483,14 @@ int main(void) return 0; } " HAVE_POSIX_SPAWN) - if(HAVE_POSIX_SPAWN) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/process/posix/*.c") + if(NOT APPLE) + check_symbol_exists(vfork "unistd.h" LIBC_HAS_VFORK) + endif() + if(HAVE_POSIX_SPAWN AND (APPLE OR LIBC_HAS_VFORK)) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/process/posix/*.c" + "${SDL3_SOURCE_DIR}/src/process/posix/*.h" + ) set(SDL_PROCESS_POSIX 1) set(HAVE_SDL_PROCESS TRUE) endif() @@ -3014,7 +3501,10 @@ endif() if(SDL_VIDEO) if(SDL_OFFSCREEN) set(SDL_VIDEO_DRIVER_OFFSCREEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/offscreen/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/offscreen/*.c" + "${SDL3_SOURCE_DIR}/src/video/offscreen/*.h" + ) set(HAVE_OFFSCREEN TRUE) set(HAVE_SDL_VIDEO TRUE) endif() @@ -3023,22 +3513,23 @@ endif() sdl_glob_sources(${SDL3_SOURCE_DIR}/src/tray/*.c) if(SDL_GPU) - if(HAVE_D3D11_H) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/gpu/d3d11/*.c") - set(SDL_GPU_D3D11 1) - set(HAVE_SDL_GPU TRUE) - endif() - if(WINDOWS) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/gpu/d3d12/*.c") + if(HAVE_DXGI1_6_H) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/gpu/d3d12/*.c" + "${SDL3_SOURCE_DIR}/src/gpu/d3d12/*.h" + ) set(SDL_GPU_D3D12 1) set(HAVE_SDL_GPU TRUE) endif() if(SDL_VIDEO_VULKAN) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/gpu/vulkan/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/gpu/vulkan/*.c" + "${SDL3_SOURCE_DIR}/src/gpu/vulkan/*.h" + ) set(SDL_GPU_VULKAN 1) set(HAVE_SDL_GPU TRUE) endif() - if(SDL_RENDER_GPU) + if(SDL_RENDER_GPU AND HAVE_SDL_GPU) set(SDL_VIDEO_RENDER_GPU 1) set(HAVE_RENDER_GPU TRUE) endif() @@ -3054,35 +3545,59 @@ endif() # src/X/*.c does not get included. if(NOT HAVE_SDL_AUDIO) set(SDL_AUDIO_DRIVER_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/audio/dummy/*.h" + ) endif() if(NOT HAVE_SDL_VIDEO) set(SDL_VIDEO_DRIVER_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/video/dummy/*.h" + ) endif() if(NOT HAVE_SDL_JOYSTICK) set(SDL_JOYSTICK_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/dummy/*.h" + ) endif() if(NOT HAVE_SDL_HAPTIC) set(SDL_HAPTIC_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/haptic/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/haptic/dummy/*.h" + ) endif() if(NOT HAVE_SDL_SENSORS) set(SDL_SENSOR_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/sensor/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/sensor/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/sensor/dummy/*.h" + ) endif() if(NOT HAVE_SDL_LOADSO) set(SDL_LOADSO_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/loadso/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/loadso/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/loadso/dummy/*.h" + ) endif() if(NOT HAVE_SDL_FILESYSTEM) set(SDL_FILESYSTEM_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/filesystem/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/filesystem/dummy/*.h" + ) endif() if(NOT HAVE_SDL_STORAGE) set(SDL_STORAGE_GENERIC 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/generic/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/storage/generic/*.c" + "${SDL3_SOURCE_DIR}/src/storage/generic/*.h" + ) endif() if(NOT HAVE_SDL_FSOPS) set(SDL_FSOPS_DUMMY 1) @@ -3090,11 +3605,17 @@ if(NOT HAVE_SDL_FSOPS) endif() if(NOT HAVE_SDL_LOCALE) set(SDL_LOCALE_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/locale/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/locale/dummy/*.h" + ) endif() if(NOT HAVE_SDL_MISC) set(SDL_MISC_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/misc/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/misc/dummy/*.h" + ) endif() if(NOT HAVE_SDL_DIALOG) set(SDL_DIALOG_DUMMY 1) @@ -3110,15 +3631,21 @@ if(NOT HAVE_SDL_TRAY) endif() if(NOT HAVE_CAMERA) set(SDL_CAMERA_DRIVER_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/camera/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/camera/dummy/*.h" + ) endif() # We always need to have threads and timers around if(NOT HAVE_SDL_THREADS) - # The emscripten platform has been carefully vetted to work without threads - if(EMSCRIPTEN) + # The Emscripten and N-Gage platform has been carefully vetted to work without threads + if(EMSCRIPTEN OR NGAGE) set(SDL_THREADS_DISABLED 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/thread/generic/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/thread/generic/*.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/*.h" + ) else() message(FATAL_ERROR "Threads are needed by many SDL subsystems and may not be disabled") endif() @@ -3129,7 +3656,10 @@ endif() # Most platforms use this. if(NOT HAVE_SDL_MAIN_CALLBACKS) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/generic/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/main/generic/*.c" + "${SDL3_SOURCE_DIR}/src/main/generic/*.h" + ) endif() # config variables may contain generator expression, so we need to generate SDL_build_config.h in 2 steps: @@ -3155,14 +3685,15 @@ endforeach() if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/REVISION.txt") file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/REVISION.txt" revisions) list(GET revisions 0 revisions_0) - string(STRIP "${revisions_0}" SDL_REVISION) + string(STRIP "${revisions_0}" revisions_0_stripped) + set(SDL_REVISION "SDL-${revisions_0_stripped}") else() set(SDL_REVISION "" CACHE STRING "Custom SDL revision (only used when REVISION.txt does not exist)") endif() if(NOT SDL_REVISION) # If SDL_REVISION is not overrided, use git to describe git_describe(SDL_REVISION_GIT) - set(SDL_REVISION "SDL3-${SDL3_VERSION}-${SDL_REVISION_GIT}") + set(SDL_REVISION "SDL-${SDL3_VERSION}-${SDL_REVISION_GIT}") endif() execute_process(COMMAND "${CMAKE_COMMAND}" -E make_directory "${SDL3_BINARY_DIR}/include-revision/SDL3") @@ -3172,9 +3703,10 @@ list(APPEND SDL3_INCLUDE_FILES "${SDL3_BINARY_DIR}/include-revision/SDL3/SDL_rev if(SDL_FRAMEWORK) # With Apple frameworks, headers in the PUBLIC_HEADER property also need to be added as sources list(APPEND SDL3_INCLUDE_FILES ${SDL3_TEST_INCLUDE_FILES}) - sdl_sources(${SDL3_INCLUDE_FILES}) endif() +sdl_sources(${SDL3_INCLUDE_FILES}) + if((CMAKE_STATIC_LIBRARY_PREFIX STREQUAL "" AND CMAKE_STATIC_LIBRARY_SUFFIX STREQUAL ".lib") OR SDL_FRAMEWORK) # - Avoid conflict between the dll import library and the static library # - Create SDL3-static Apple Framework @@ -3298,10 +3830,6 @@ else() set(ARCH_64 FALSE) endif() -if(ANDROID) - sdl_include_directories(PRIVATE SYSTEM "${CMAKE_ANDROID_NDK}/sources/android/cpufeatures") -endif() - if(APPLE) cmake_push_check_state(RESET) check_c_compiler_flag(-fobjc-arc COMPILER_SUPPORTS_FOBJC_ARC) @@ -3471,6 +3999,8 @@ if(SDL_SHARED) set_property(TARGET SDL3-shared PROPERTY INTERFACE_SDL3_SHARED TRUE) set_property(TARGET SDL3-shared APPEND PROPERTY COMPATIBLE_INTERFACE_STRING "SDL_VERSION") set_property(TARGET SDL3-shared PROPERTY INTERFACE_SDL_VERSION "SDL${SDL3_VERSION_MAJOR}") + set_property(TARGET SDL3-shared APPEND PROPERTY EXPORT_PROPERTIES "SDL_FULL_VERSION") + set_property(TARGET SDL3-shared PROPERTY SDL_FULL_VERSION "${PROJECT_VERSION}") if(NOT CMAKE_VERSION VERSION_LESS "3.16") target_precompile_headers(SDL3-shared PRIVATE "$<$,$>:${PROJECT_SOURCE_DIR}/src/SDL_internal.h>") endif() @@ -3495,6 +4025,8 @@ if(SDL_STATIC) set_property(TARGET SDL3-static PROPERTY INTERFACE_SDL3_SHARED FALSE) set_property(TARGET SDL3-static APPEND PROPERTY COMPATIBLE_INTERFACE_STRING "SDL_VERSION") set_property(TARGET SDL3-static PROPERTY INTERFACE_SDL_VERSION "SDL${SDL3_VERSION_MAJOR}") + set_property(TARGET SDL3-static APPEND PROPERTY EXPORT_PROPERTIES "SDL_FULL_VERSION") + set_property(TARGET SDL3-static PROPERTY SDL_FULL_VERSION "${PROJECT_VERSION}") if(NOT CMAKE_VERSION VERSION_LESS "3.16") target_precompile_headers(SDL3-static PRIVATE "$<$,$>:${PROJECT_SOURCE_DIR}/src/SDL_internal.h>") endif() @@ -3510,7 +4042,7 @@ sdl_compile_definitions( ##### Tests ##### if(SDL_TEST_LIBRARY) - file(GLOB TEST_SOURCES "${SDL3_SOURCE_DIR}/src/test/*.c") + file(GLOB TEST_SOURCES "${SDL3_SOURCE_DIR}/src/test/*.c" "${SDL3_SOURCE_DIR}/src/test/*.h") target_sources(SDL3_test PRIVATE ${TEST_SOURCES}) if(APPLE) set_target_properties(SDL3_test PROPERTIES @@ -3782,6 +4314,7 @@ endif() ##### Uninstall target ##### if(SDL_UNINSTALL) + set(HAVE_UNINSTALL ON) if(NOT TARGET uninstall) configure_file(cmake/cmake_uninstall.cmake.in cmake_uninstall.cmake IMMEDIATE @ONLY) diff --git a/libs/SDL3/LICENSE.txt b/libs/SDL3/LICENSE.txt index 23abb73..e9adee4 100644 --- a/libs/SDL3/LICENSE.txt +++ b/libs/SDL3/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (C) 1997-2025 Sam Lantinga +Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/REVISION.txt b/libs/SDL3/REVISION.txt index b520912..b4fb27f 100644 --- a/libs/SDL3/REVISION.txt +++ b/libs/SDL3/REVISION.txt @@ -1 +1 @@ -release-3.2.20-0-g96292a5b4 +release-3.4.2-0-g683181b47 diff --git a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj index 96ca804..a1356ab 100644 --- a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj +++ b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj @@ -326,23 +326,25 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) - - - + + + + + + @@ -352,9 +354,11 @@ - - + + + + @@ -362,11 +366,13 @@ + + @@ -385,7 +391,6 @@ - @@ -407,7 +412,7 @@ - + @@ -424,6 +429,7 @@ + @@ -454,13 +460,16 @@ + + + @@ -496,7 +505,6 @@ - @@ -596,9 +604,11 @@ + + @@ -643,7 +653,7 @@ - + @@ -703,28 +713,39 @@ stdcpp17 stdcpp17 + + - + + + + + + + + + + @@ -756,6 +777,7 @@ + @@ -781,7 +803,6 @@ - @@ -791,7 +812,6 @@ - @@ -873,6 +893,7 @@ + @@ -880,12 +901,11 @@ - - + diff --git a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters index f760877..a2f431a 100644 --- a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -27,7 +27,7 @@ - + @@ -56,28 +56,39 @@ + + - + + + + + + + + + + @@ -91,6 +102,7 @@ + @@ -108,7 +120,6 @@ - @@ -118,7 +129,6 @@ - @@ -177,6 +187,7 @@ + @@ -184,12 +195,11 @@ - - + @@ -229,17 +239,21 @@ - - + + + + + + @@ -249,8 +263,11 @@ - + + + + @@ -258,11 +275,13 @@ + + @@ -281,10 +300,10 @@ - + @@ -299,9 +318,10 @@ + - + @@ -322,6 +342,7 @@ + @@ -343,13 +364,16 @@ + + + @@ -385,7 +409,6 @@ - @@ -439,9 +462,11 @@ + + diff --git a/libs/SDL3/VisualC-GDK/SDL_test/SDL_test.vcxproj b/libs/SDL3/VisualC-GDK/SDL_test/SDL_test.vcxproj index 8d5106c..84a29b5 100644 --- a/libs/SDL3/VisualC-GDK/SDL_test/SDL_test.vcxproj +++ b/libs/SDL3/VisualC-GDK/SDL_test/SDL_test.vcxproj @@ -192,10 +192,12 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) + + + diff --git a/libs/SDL3/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj b/libs/SDL3/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj index afdead2..8d91ee6 100644 --- a/libs/SDL3/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj +++ b/libs/SDL3/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj @@ -268,7 +268,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC-GDK/tests/testgdk/src/testgdk.cpp b/libs/SDL3/VisualC-GDK/tests/testgdk/src/testgdk.cpp index 51fc75b..b8fffd3 100644 --- a/libs/SDL3/VisualC-GDK/tests/testgdk/src/testgdk.cpp +++ b/libs/SDL3/VisualC-GDK/tests/testgdk/src/testgdk.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -56,8 +56,7 @@ static struct static SDL_AudioStream *stream; /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ -static void -quit(int rc) +static void quit(int rc) { SDL_free(sprites); SDL_DestroyAudioStream(stream); @@ -80,8 +79,7 @@ static int fillerup(void) return 0; } -void -UserLoggedIn(XUserHandle user) +static void UserLoggedIn(XUserHandle user) { HRESULT hr; char gamertag[128]; @@ -96,8 +94,7 @@ UserLoggedIn(XUserHandle user) XUserCloseHandle(user); } -void -AddUserUICallback(XAsyncBlock *asyncBlock) +static void AddUserUICallback(XAsyncBlock *asyncBlock) { HRESULT hr; XUserHandle user = NULL; @@ -123,8 +120,7 @@ AddUserUICallback(XAsyncBlock *asyncBlock) delete asyncBlock; } -void -AddUserUI() +static void AddUserUI() { HRESULT hr; XAsyncBlock *asyncBlock = new XAsyncBlock; @@ -141,8 +137,7 @@ AddUserUI() } } -void -AddUserSilentCallback(XAsyncBlock *asyncBlock) +static void AddUserSilentCallback(XAsyncBlock *asyncBlock) { HRESULT hr; XUserHandle user = NULL; @@ -168,8 +163,7 @@ AddUserSilentCallback(XAsyncBlock *asyncBlock) delete asyncBlock; } -void -AddUserSilent() +static void AddUserSilent() { HRESULT hr; XAsyncBlock *asyncBlock = new XAsyncBlock; @@ -186,30 +180,27 @@ AddUserSilent() } } -int -LoadSprite(const char *file) +static bool LoadSprite(const char *file) { int i; for (i = 0; i < state->num_windows; ++i) { /* This does the SDL_LoadBMP step repeatedly, but that's OK for test code. */ - sprites[i] = LoadTexture(state->renderers[i], file, true, &sprite_w, &sprite_h); + sprites[i] = LoadTexture(state->renderers[i], file, true); if (!sprites[i]) { - return -1; - } - if (!SDL_SetTextureBlendMode(sprites[i], blendMode)) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set blend mode: %s", SDL_GetError()); - SDL_DestroyTexture(sprites[i]); - return -1; + return false; } + sprite_w = sprites[i]->w; + sprite_h = sprites[i]->h; + + SDL_SetTextureBlendMode(sprites[i], blendMode); } /* We're ready to roll. :) */ - return 0; + return true; } -void -DrawSprites(SDL_Renderer * renderer, SDL_Texture * sprite) +static void DrawSprites(SDL_Renderer * renderer, SDL_Texture * sprite) { SDL_Rect viewport; SDL_FRect temp; @@ -300,8 +291,7 @@ DrawSprites(SDL_Renderer * renderer, SDL_Texture * sprite) SDL_RenderPresent(renderer); } -void -loop() +static void loop() { int i; SDL_Event event; @@ -329,8 +319,7 @@ loop() fillerup(); } -int -main(int argc, char *argv[]) +int main(int argc, char *argv[]) { int i; const char *icon = "icon.bmp"; @@ -413,7 +402,7 @@ main(int argc, char *argv[]) SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF); SDL_RenderClear(renderer); } - if (LoadSprite(icon) < 0) { + if (!LoadSprite(icon)) { quit(2); } diff --git a/libs/SDL3/VisualC-GDK/tests/testgdk/testgdk.vcxproj b/libs/SDL3/VisualC-GDK/tests/testgdk/testgdk.vcxproj index c16fade..4ffc370 100644 --- a/libs/SDL3/VisualC-GDK/tests/testgdk/testgdk.vcxproj +++ b/libs/SDL3/VisualC-GDK/tests/testgdk/testgdk.vcxproj @@ -292,7 +292,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) @@ -311,7 +310,7 @@ - + Copying %(Filename)%(Extension) Copying %(Filename)%(Extension) Copying %(Filename)%(Extension) diff --git a/libs/SDL3/VisualC-GDK/tests/testsprite/testsprite.vcxproj b/libs/SDL3/VisualC-GDK/tests/testsprite/testsprite.vcxproj index 15ea690..65422be 100644 --- a/libs/SDL3/VisualC-GDK/tests/testsprite/testsprite.vcxproj +++ b/libs/SDL3/VisualC-GDK/tests/testsprite/testsprite.vcxproj @@ -292,7 +292,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) @@ -311,7 +310,7 @@ - + Copying %(Filename)%(Extension) Copying %(Filename)%(Extension) Copying %(Filename)%(Extension) diff --git a/libs/SDL3/VisualC/SDL.sln b/libs/SDL3/VisualC/SDL.sln index 7c87fa5..2e0b762 100644 --- a/libs/SDL3/VisualC/SDL.sln +++ b/libs/SDL3/VisualC/SDL.sln @@ -69,7 +69,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "camera", "camera", "{AAEC83 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "01-read-and-draw", "examples\camera\01-read-and-draw\01-read-and-draw.vcxproj", "{510ACF0C-4012-4216-98EF-E4F155DE33CE}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "game", "game", "{D1BF59F6-22DC-493B-BDEB-451A50DA793D}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "demo", "demo", "{D1BF59F6-22DC-493B-BDEB-451A50DA793D}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "01-snake", "examples\demo\01-snake\01-snake.vcxproj", "{7820969A-5B7B-4046-BB0A-82905D457FC5}" EndProject @@ -115,6 +115,24 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "02-woodeneye-008", "example EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "03-infinite-monkeys", "examples\demo\03-infinite-monkeys\03-infinite-monkeys.vcxproj", "{75AEE75A-C016-4497-960B-D767B822237D}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "19-affine-textures", "examples\renderer\19-affine-textures\19-affine-textures.vcxproj", "{E21C50BF-54B4-434C-AA24-9A6469553987}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "04-multiple-streams", "examples\audio\04-multiple-streams\04-multiple-streams.vcxproj", "{7117A55C-BE4E-41DB-A4FC-4070E35A8B28}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "asyncio", "asyncio", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "input", "input", "{8DEAE483-FDE7-463F-9FD5-F597BBAED1F9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "01-load-bitmaps", "examples\asyncio\01-load-bitmaps\01-load-bitmaps.vcxproj", "{6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "04-bytepusher", "examples\demo\04-bytepusher\04-bytepusher.vcxproj", "{3DB9B219-769E-43AC-8B8B-319DB6045DCF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "01-joystick-polling", "examples\input\01-joystick-polling\01-joystick-polling.vcxproj", "{B3852DB7-E925-4026-8B9D-D2272EFEFF3C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "02-joystick-events", "examples\input\02-joystick-events\02-joystick-events.vcxproj", "{FCBDF2B2-1129-49AE-9406-3F219E65CA89}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testsoftwaretransparent", "tests\testsoftwaretransparent\testsoftwaretransparent.vcxproj", "{D91C45E2-274E-4C0F-89C7-9986F9A7E85A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -515,6 +533,62 @@ Global {75AEE75A-C016-4497-960B-D767B822237D}.Release|Win32.Build.0 = Release|Win32 {75AEE75A-C016-4497-960B-D767B822237D}.Release|x64.ActiveCfg = Release|x64 {75AEE75A-C016-4497-960B-D767B822237D}.Release|x64.Build.0 = Release|x64 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Debug|Win32.ActiveCfg = Debug|Win32 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Debug|Win32.Build.0 = Debug|Win32 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Debug|x64.ActiveCfg = Debug|x64 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Debug|x64.Build.0 = Debug|x64 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Release|Win32.ActiveCfg = Release|Win32 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Release|Win32.Build.0 = Release|Win32 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Release|x64.ActiveCfg = Release|x64 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Release|x64.Build.0 = Release|x64 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Debug|Win32.ActiveCfg = Debug|Win32 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Debug|Win32.Build.0 = Debug|Win32 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Debug|x64.ActiveCfg = Debug|x64 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Debug|x64.Build.0 = Debug|x64 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Release|Win32.ActiveCfg = Release|Win32 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Release|Win32.Build.0 = Release|Win32 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Release|x64.ActiveCfg = Release|x64 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Release|x64.Build.0 = Release|x64 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Debug|Win32.ActiveCfg = Debug|Win32 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Debug|Win32.Build.0 = Debug|Win32 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Debug|x64.ActiveCfg = Debug|x64 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Debug|x64.Build.0 = Debug|x64 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Release|Win32.ActiveCfg = Release|Win32 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Release|Win32.Build.0 = Release|Win32 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Release|x64.ActiveCfg = Release|x64 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Release|x64.Build.0 = Release|x64 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Debug|Win32.ActiveCfg = Debug|Win32 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Debug|Win32.Build.0 = Debug|Win32 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Debug|x64.ActiveCfg = Debug|x64 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Debug|x64.Build.0 = Debug|x64 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Release|Win32.ActiveCfg = Release|Win32 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Release|Win32.Build.0 = Release|Win32 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Release|x64.ActiveCfg = Release|x64 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Release|x64.Build.0 = Release|x64 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Debug|Win32.ActiveCfg = Debug|Win32 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Debug|Win32.Build.0 = Debug|Win32 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Debug|x64.ActiveCfg = Debug|x64 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Debug|x64.Build.0 = Debug|x64 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Release|Win32.ActiveCfg = Release|Win32 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Release|Win32.Build.0 = Release|Win32 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Release|x64.ActiveCfg = Release|x64 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Release|x64.Build.0 = Release|x64 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Debug|Win32.ActiveCfg = Debug|Win32 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Debug|Win32.Build.0 = Debug|Win32 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Debug|x64.ActiveCfg = Debug|x64 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Debug|x64.Build.0 = Debug|x64 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Release|Win32.ActiveCfg = Release|Win32 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Release|Win32.Build.0 = Release|Win32 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Release|x64.ActiveCfg = Release|x64 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Release|x64.Build.0 = Release|x64 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Debug|Win32.ActiveCfg = Debug|Win32 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Debug|Win32.Build.0 = Debug|Win32 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Debug|x64.ActiveCfg = Debug|x64 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Debug|x64.Build.0 = Debug|x64 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Release|Win32.ActiveCfg = Release|Win32 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Release|Win32.Build.0 = Release|Win32 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Release|x64.ActiveCfg = Release|x64 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -572,6 +646,15 @@ Global {608C6C67-7766-471F-BBFF-8B00086039AF} = {1B61A1B7-92DE-4C37-9151-D2928D6449AB} {A3F601E0-B54C-4DD8-8A97-FDEF7624EE60} = {D1BF59F6-22DC-493B-BDEB-451A50DA793D} {75AEE75A-C016-4497-960B-D767B822237D} = {D1BF59F6-22DC-493B-BDEB-451A50DA793D} + {E21C50BF-54B4-434C-AA24-9A6469553987} = {F91DDAF0-B74F-4516-A1A9-42ED8DFCBF6A} + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28} = {1B61A1B7-92DE-4C37-9151-D2928D6449AB} + {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {1498F0CD-F4DA-4847-9CB2-FB18D48061D5} + {8DEAE483-FDE7-463F-9FD5-F597BBAED1F9} = {1498F0CD-F4DA-4847-9CB2-FB18D48061D5} + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {3DB9B219-769E-43AC-8B8B-319DB6045DCF} = {D1BF59F6-22DC-493B-BDEB-451A50DA793D} + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C} = {8DEAE483-FDE7-463F-9FD5-F597BBAED1F9} + {FCBDF2B2-1129-49AE-9406-3F219E65CA89} = {8DEAE483-FDE7-463F-9FD5-F597BBAED1F9} + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A} = {D69D5741-611F-4E14-8541-1FEE94F50B5A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C320C9F2-1A8F-41D7-B02B-6338F872BCAD} diff --git a/libs/SDL3/VisualC/SDL/SDL.vcxproj b/libs/SDL3/VisualC/SDL/SDL.vcxproj index a15978a..2937791 100644 --- a/libs/SDL3/VisualC/SDL/SDL.vcxproj +++ b/libs/SDL3/VisualC/SDL/SDL.vcxproj @@ -82,16 +82,16 @@ C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Lib\x86;$(LibraryPath) - $(ProjectDir)/../../src;$(IncludePath) + $(ProjectDir)/../../src;$(ProjectDir)/../../src/core/windows;$(IncludePath) - $(ProjectDir)/../../src;$(IncludePath) + $(ProjectDir)/../../src;$(ProjectDir)/../../src/core/windows;$(IncludePath) - $(ProjectDir)/../../src;$(IncludePath) + $(ProjectDir)/../../src;$(ProjectDir)/../../src/core/windows;$(IncludePath) - $(ProjectDir)/../../src;$(IncludePath) + $(ProjectDir)/../../src;$(ProjectDir)/../../src/core/windows;$(IncludePath) @@ -239,23 +239,25 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) - - - + + + + + + @@ -265,9 +267,11 @@ - - + + + + @@ -275,11 +279,13 @@ + + @@ -298,7 +304,6 @@ - @@ -320,6 +325,7 @@ + @@ -335,6 +341,7 @@ + @@ -367,13 +374,16 @@ + + + @@ -408,7 +418,6 @@ - @@ -422,6 +431,16 @@ + + Create + Create + Create + Create + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + @@ -494,9 +513,11 @@ + + @@ -541,7 +562,12 @@ - + + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + @@ -573,28 +599,44 @@ + + - + + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + + + + + + + + + + @@ -608,6 +650,7 @@ + @@ -627,7 +670,6 @@ - @@ -637,7 +679,6 @@ - @@ -708,6 +749,7 @@ + @@ -715,12 +757,16 @@ - - + + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + diff --git a/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters b/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters index d653ee0..e101491 100644 --- a/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters +++ b/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters @@ -82,6 +82,9 @@ {ebc2fca3-3c26-45e3-815e-3e0581d5e226} + + {06DB01C0-65B5-4DE7-8ADC-C0B0CA3A1E69} + {47c445a2-7014-4e15-9660-7c89a27dddcf} @@ -219,42 +222,51 @@ - - API Headers - - - API Headers - - - API Headers - API Headers API Headers + + API Headers + API Headers API Headers + + API Headers + API Headers API Headers + + API Headers + API Headers + + API Headers + API Headers API Headers + + API Headers + + + API Headers + API Headers @@ -273,19 +285,28 @@ API Headers + + API Headers + API Headers API Headers - - API Headers - API Headers - + + API Headers + + + API Headers + + + API Headers + + API Headers @@ -309,15 +330,27 @@ API Headers + + API Headers + API Headers + + API Headers + + + API Headers + API Headers API Headers + + API Headers + API Headers @@ -372,9 +405,6 @@ API Headers - - API Headers - API Headers @@ -384,6 +414,9 @@ API Headers + + API Headers + API Headers @@ -420,15 +453,24 @@ API Headers + + API Headers + API Headers + + API Headers + API Headers API Headers + + API Headers + API Headers @@ -486,6 +528,9 @@ audio + + core + core\windows @@ -534,9 +579,6 @@ events - - events - events @@ -564,6 +606,9 @@ haptic + + haptic + haptic @@ -621,12 +666,18 @@ haptic\windows + + haptic\hidapi + joystick\hidapi joystick\hidapi + + joystick\hidapi + joystick\windows @@ -645,6 +696,9 @@ video + + video + video @@ -678,12 +732,15 @@ video - + video video + + video + video\dummy @@ -834,9 +891,6 @@ render\software - - render\software - render\software @@ -953,6 +1007,7 @@ + @@ -1040,7 +1095,7 @@ core - + core\windows @@ -1079,9 +1134,6 @@ events - - events - events @@ -1133,6 +1185,9 @@ loadso\windows + + misc + misc @@ -1163,21 +1218,36 @@ haptic\windows + + haptic\hidapi + + + haptic\hidapi + haptic\dummy joystick\dummy - + joystick\gdk + + joystick\hidapi + + + joystick\hidapi + joystick\hidapi joystick\hidapi + + joystick\hidapi + joystick\hidapi @@ -1196,6 +1266,9 @@ joystick\hidapi + + joystick\hidapi + joystick\hidapi @@ -1211,6 +1284,9 @@ joystick\hidapi + + joystick\hidapi + joystick\hidapi @@ -1223,9 +1299,18 @@ joystick\hidapi + + joystick\hidapi + + + joystick\hidapi + joystick\hidapi + + joystick\hidapi + joystick\windows @@ -1301,6 +1386,9 @@ video + + video + video @@ -1331,9 +1419,6 @@ video\dummy - - video\windows - video\windows @@ -1346,7 +1431,7 @@ video\windows - + video\windows @@ -1460,9 +1545,6 @@ sensor\windows - - render - render @@ -1514,9 +1596,6 @@ render\software - - render\software - render\software @@ -1585,11 +1664,15 @@ + + joystick\hidapi + + + + core\windows + - - - diff --git a/libs/SDL3/VisualC/SDL_test/SDL_test.vcxproj b/libs/SDL3/VisualC/SDL_test/SDL_test.vcxproj index 403fba2..428eb6f 100644 --- a/libs/SDL3/VisualC/SDL_test/SDL_test.vcxproj +++ b/libs/SDL3/VisualC/SDL_test/SDL_test.vcxproj @@ -158,10 +158,12 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) + + + diff --git a/libs/SDL3/VisualC/examples/asyncio/01-load-bitmaps/01-load-bitmaps.vcxproj b/libs/SDL3/VisualC/examples/asyncio/01-load-bitmaps/01-load-bitmaps.vcxproj new file mode 100644 index 0000000..7cdf986 --- /dev/null +++ b/libs/SDL3/VisualC/examples/asyncio/01-load-bitmaps/01-load-bitmaps.vcxproj @@ -0,0 +1,13 @@ + + + + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0} + + + + + + + + + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/audio/04-multiple-streams/04-multiple-streams.vcxproj b/libs/SDL3/VisualC/examples/audio/04-multiple-streams/04-multiple-streams.vcxproj new file mode 100644 index 0000000..b2e0a32 --- /dev/null +++ b/libs/SDL3/VisualC/examples/audio/04-multiple-streams/04-multiple-streams.vcxproj @@ -0,0 +1,13 @@ + + + + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28} + + + + + + + + + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/demo/04-bytepusher/04-bytepusher.vcxproj b/libs/SDL3/VisualC/examples/demo/04-bytepusher/04-bytepusher.vcxproj new file mode 100644 index 0000000..b7894b5 --- /dev/null +++ b/libs/SDL3/VisualC/examples/demo/04-bytepusher/04-bytepusher.vcxproj @@ -0,0 +1,13 @@ + + + + {3DB9B219-769E-43AC-8B8B-319DB6045DCF} + + + + + + + + + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/generate.py b/libs/SDL3/VisualC/examples/generate.py index e06110e..62614d2 100644 --- a/libs/SDL3/VisualC/examples/generate.py +++ b/libs/SDL3/VisualC/examples/generate.py @@ -47,7 +47,8 @@ def main(): for category in path.iterdir(): if category.is_dir(): for example in category.iterdir(): - generate(category.name, example.name, get_c_source_filename(example)) + if example.is_dir(): + generate(category.name, example.name, get_c_source_filename(example)) if __name__ == "__main__": diff --git a/libs/SDL3/VisualC/examples/input/01-joystick-polling/01-joystick-polling.vcxproj b/libs/SDL3/VisualC/examples/input/01-joystick-polling/01-joystick-polling.vcxproj new file mode 100644 index 0000000..e3fc671 --- /dev/null +++ b/libs/SDL3/VisualC/examples/input/01-joystick-polling/01-joystick-polling.vcxproj @@ -0,0 +1,13 @@ + + + + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C} + + + + + + + + + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/input/02-joystick-events/02-joystick-events.vcxproj b/libs/SDL3/VisualC/examples/input/02-joystick-events/02-joystick-events.vcxproj new file mode 100644 index 0000000..c119584 --- /dev/null +++ b/libs/SDL3/VisualC/examples/input/02-joystick-events/02-joystick-events.vcxproj @@ -0,0 +1,13 @@ + + + + {FCBDF2B2-1129-49AE-9406-3F219E65CA89} + + + + + + + + + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/06-textures/06-textures.vcxproj b/libs/SDL3/VisualC/examples/renderer/06-textures/06-textures.vcxproj index d99b62c..a2c43ff 100644 --- a/libs/SDL3/VisualC/examples/renderer/06-textures/06-textures.vcxproj +++ b/libs/SDL3/VisualC/examples/renderer/06-textures/06-textures.vcxproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/08-rotating-textures/08-rotating-textures.vcxproj b/libs/SDL3/VisualC/examples/renderer/08-rotating-textures/08-rotating-textures.vcxproj index 5e14c6d..f6a939c 100644 --- a/libs/SDL3/VisualC/examples/renderer/08-rotating-textures/08-rotating-textures.vcxproj +++ b/libs/SDL3/VisualC/examples/renderer/08-rotating-textures/08-rotating-textures.vcxproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/10-geometry/10-geometry.vcxproj b/libs/SDL3/VisualC/examples/renderer/10-geometry/10-geometry.vcxproj index 6013514..ba3fe99 100644 --- a/libs/SDL3/VisualC/examples/renderer/10-geometry/10-geometry.vcxproj +++ b/libs/SDL3/VisualC/examples/renderer/10-geometry/10-geometry.vcxproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/11-color-mods/11-color-mods.vcxproj b/libs/SDL3/VisualC/examples/renderer/11-color-mods/11-color-mods.vcxproj index c0af3e3..97b943c 100644 --- a/libs/SDL3/VisualC/examples/renderer/11-color-mods/11-color-mods.vcxproj +++ b/libs/SDL3/VisualC/examples/renderer/11-color-mods/11-color-mods.vcxproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/14-viewport/14-viewport.vcxproj b/libs/SDL3/VisualC/examples/renderer/14-viewport/14-viewport.vcxproj index 1ebbda5..0e20fb8 100644 --- a/libs/SDL3/VisualC/examples/renderer/14-viewport/14-viewport.vcxproj +++ b/libs/SDL3/VisualC/examples/renderer/14-viewport/14-viewport.vcxproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/15-cliprect/15-cliprect.vcxproj b/libs/SDL3/VisualC/examples/renderer/15-cliprect/15-cliprect.vcxproj index dbb7ca6..fb2a8fb 100644 --- a/libs/SDL3/VisualC/examples/renderer/15-cliprect/15-cliprect.vcxproj +++ b/libs/SDL3/VisualC/examples/renderer/15-cliprect/15-cliprect.vcxproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/17-read-pixels/17-read-pixels.vcxproj b/libs/SDL3/VisualC/examples/renderer/17-read-pixels/17-read-pixels.vcxproj index 674462e..68c6d0e 100644 --- a/libs/SDL3/VisualC/examples/renderer/17-read-pixels/17-read-pixels.vcxproj +++ b/libs/SDL3/VisualC/examples/renderer/17-read-pixels/17-read-pixels.vcxproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/19-affine-textures/19-affine-textures.vcxproj b/libs/SDL3/VisualC/examples/renderer/19-affine-textures/19-affine-textures.vcxproj new file mode 100644 index 0000000..b5575ec --- /dev/null +++ b/libs/SDL3/VisualC/examples/renderer/19-affine-textures/19-affine-textures.vcxproj @@ -0,0 +1,13 @@ + + + + {E21C50BF-54B4-434C-AA24-9A6469553987} + + + + + + + + + \ No newline at end of file diff --git a/libs/SDL3/VisualC/tests/checkkeys/checkkeys.vcxproj b/libs/SDL3/VisualC/tests/checkkeys/checkkeys.vcxproj index 6fc69d4..1f94227 100644 --- a/libs/SDL3/VisualC/tests/checkkeys/checkkeys.vcxproj +++ b/libs/SDL3/VisualC/tests/checkkeys/checkkeys.vcxproj @@ -189,7 +189,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/loopwave/loopwave.vcxproj b/libs/SDL3/VisualC/tests/loopwave/loopwave.vcxproj index 57053be..bde910b 100644 --- a/libs/SDL3/VisualC/tests/loopwave/loopwave.vcxproj +++ b/libs/SDL3/VisualC/tests/loopwave/loopwave.vcxproj @@ -189,7 +189,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testatomic/testatomic.vcxproj b/libs/SDL3/VisualC/tests/testatomic/testatomic.vcxproj index eacef20..b41357b 100644 --- a/libs/SDL3/VisualC/tests/testatomic/testatomic.vcxproj +++ b/libs/SDL3/VisualC/tests/testatomic/testatomic.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testautomation/testautomation.vcxproj b/libs/SDL3/VisualC/tests/testautomation/testautomation.vcxproj index 1a3df5e..97b622a 100644 --- a/libs/SDL3/VisualC/tests/testautomation/testautomation.vcxproj +++ b/libs/SDL3/VisualC/tests/testautomation/testautomation.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testcontroller/testcontroller.vcxproj b/libs/SDL3/VisualC/tests/testcontroller/testcontroller.vcxproj index 480412c..172c80e 100644 --- a/libs/SDL3/VisualC/tests/testcontroller/testcontroller.vcxproj +++ b/libs/SDL3/VisualC/tests/testcontroller/testcontroller.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testdialog/testdialog.vcxproj b/libs/SDL3/VisualC/tests/testdialog/testdialog.vcxproj index 29ae446..81356ad 100644 --- a/libs/SDL3/VisualC/tests/testdialog/testdialog.vcxproj +++ b/libs/SDL3/VisualC/tests/testdialog/testdialog.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testdraw/testdraw.vcxproj b/libs/SDL3/VisualC/tests/testdraw/testdraw.vcxproj index 8af9dec..019d994 100644 --- a/libs/SDL3/VisualC/tests/testdraw/testdraw.vcxproj +++ b/libs/SDL3/VisualC/tests/testdraw/testdraw.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testfile/testfile.vcxproj b/libs/SDL3/VisualC/tests/testfile/testfile.vcxproj index 602d6e1..4428b93 100644 --- a/libs/SDL3/VisualC/tests/testfile/testfile.vcxproj +++ b/libs/SDL3/VisualC/tests/testfile/testfile.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testgl/testgl.vcxproj b/libs/SDL3/VisualC/tests/testgl/testgl.vcxproj index 436c401..3e84955 100644 --- a/libs/SDL3/VisualC/tests/testgl/testgl.vcxproj +++ b/libs/SDL3/VisualC/tests/testgl/testgl.vcxproj @@ -187,7 +187,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testgles2/testgles2.vcxproj b/libs/SDL3/VisualC/tests/testgles2/testgles2.vcxproj index cf72558..5668e3d 100644 --- a/libs/SDL3/VisualC/tests/testgles2/testgles2.vcxproj +++ b/libs/SDL3/VisualC/tests/testgles2/testgles2.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testoverlay/testoverlay.vcxproj b/libs/SDL3/VisualC/tests/testoverlay/testoverlay.vcxproj index 5944782..a58f66e 100644 --- a/libs/SDL3/VisualC/tests/testoverlay/testoverlay.vcxproj +++ b/libs/SDL3/VisualC/tests/testoverlay/testoverlay.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testpen/testpen.vcxproj b/libs/SDL3/VisualC/tests/testpen/testpen.vcxproj index 8f21ca8..4a13fb7 100644 --- a/libs/SDL3/VisualC/tests/testpen/testpen.vcxproj +++ b/libs/SDL3/VisualC/tests/testpen/testpen.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testplatform/testplatform.vcxproj b/libs/SDL3/VisualC/tests/testplatform/testplatform.vcxproj index 847fcbb..3da5469 100644 --- a/libs/SDL3/VisualC/tests/testplatform/testplatform.vcxproj +++ b/libs/SDL3/VisualC/tests/testplatform/testplatform.vcxproj @@ -195,7 +195,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testpower/testpower.vcxproj b/libs/SDL3/VisualC/tests/testpower/testpower.vcxproj index f2417f2..262cff7 100644 --- a/libs/SDL3/VisualC/tests/testpower/testpower.vcxproj +++ b/libs/SDL3/VisualC/tests/testpower/testpower.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testrendertarget/testrendertarget.vcxproj b/libs/SDL3/VisualC/tests/testrendertarget/testrendertarget.vcxproj index d4bd6ac..afc4666 100644 --- a/libs/SDL3/VisualC/tests/testrendertarget/testrendertarget.vcxproj +++ b/libs/SDL3/VisualC/tests/testrendertarget/testrendertarget.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) @@ -202,7 +201,7 @@ - + Copying %(Filename)%(Extension) copy "%(FullPath)" "$(ProjectDir)\" @@ -220,7 +219,7 @@ $(ProjectDir)\%(Filename)%(Extension);%(Outputs) - + Copying %(Filename)%(Extension) copy "%(FullPath)" "$(ProjectDir)\" diff --git a/libs/SDL3/VisualC/tests/testrumble/testrumble.vcxproj b/libs/SDL3/VisualC/tests/testrumble/testrumble.vcxproj index 87b5145..ac4a845 100644 --- a/libs/SDL3/VisualC/tests/testrumble/testrumble.vcxproj +++ b/libs/SDL3/VisualC/tests/testrumble/testrumble.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testscale/testscale.vcxproj b/libs/SDL3/VisualC/tests/testscale/testscale.vcxproj index f03406b..07740dd 100644 --- a/libs/SDL3/VisualC/tests/testscale/testscale.vcxproj +++ b/libs/SDL3/VisualC/tests/testscale/testscale.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) @@ -202,7 +201,7 @@ - + Copying %(Filename)%(Extension) copy "%(FullPath)" "$(ProjectDir)\" @@ -220,7 +219,7 @@ $(ProjectDir)\%(Filename)%(Extension);%(Outputs) - + Copying %(Filename)%(Extension) copy "%(FullPath)" "$(ProjectDir)\" diff --git a/libs/SDL3/VisualC/tests/testsensor/testsensor.vcxproj b/libs/SDL3/VisualC/tests/testsensor/testsensor.vcxproj index f3ca278..a546eab 100644 --- a/libs/SDL3/VisualC/tests/testsensor/testsensor.vcxproj +++ b/libs/SDL3/VisualC/tests/testsensor/testsensor.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testshape/testshape.vcxproj b/libs/SDL3/VisualC/tests/testshape/testshape.vcxproj index ed7106d..03e26f6 100644 --- a/libs/SDL3/VisualC/tests/testshape/testshape.vcxproj +++ b/libs/SDL3/VisualC/tests/testshape/testshape.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testsoftwaretransparent/testsoftwaretransparent.vcxproj b/libs/SDL3/VisualC/tests/testsoftwaretransparent/testsoftwaretransparent.vcxproj new file mode 100644 index 0000000..1970a3b --- /dev/null +++ b/libs/SDL3/VisualC/tests/testsoftwaretransparent/testsoftwaretransparent.vcxproj @@ -0,0 +1,209 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A} + testsoftwaretransparent + 10.0 + + + + Application + $(DefaultPlatformToolset) + + + Application + $(DefaultPlatformToolset) + + + Application + $(DefaultPlatformToolset) + + + Application + $(DefaultPlatformToolset) + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/testsoftwaretransparent.tlb + + + %(AdditionalOptions) /utf-8 + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + Windows + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + .\Release/testsoftwaretransparent.tlb + + + %(AdditionalOptions) /utf-8 + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + Windows + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Debug/testsoftwaretransparent.tlb + + + %(AdditionalOptions) /utf-8 + Disabled + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + OldStyle + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + Windows + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + X64 + .\Debug/testsoftwaretransparent.tlb + + + %(AdditionalOptions) /utf-8 + Disabled + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + OldStyle + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + Windows + + + + + $(TreatWarningsAsError) + + + + + {81ce8daf-ebb2-4761-8e45-b71abcca8c68} + false + false + true + + + {da956fd3-e143-46f2-9fe5-c77bebc56b1a} + false + false + true + + + + + + + + + \ No newline at end of file diff --git a/libs/SDL3/VisualC/tests/testsprite/testsprite.vcxproj b/libs/SDL3/VisualC/tests/testsprite/testsprite.vcxproj index 866a6db..f6746b0 100644 --- a/libs/SDL3/VisualC/tests/testsprite/testsprite.vcxproj +++ b/libs/SDL3/VisualC/tests/testsprite/testsprite.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) @@ -202,7 +201,7 @@ - + Copying %(Filename)%(Extension) copy "%(FullPath)" "$(ProjectDir)\" diff --git a/libs/SDL3/VisualC/tests/testsurround/testsurround.vcxproj b/libs/SDL3/VisualC/tests/testsurround/testsurround.vcxproj index 13ded82..9e1b9c4 100644 --- a/libs/SDL3/VisualC/tests/testsurround/testsurround.vcxproj +++ b/libs/SDL3/VisualC/tests/testsurround/testsurround.vcxproj @@ -189,7 +189,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testvulkan/testvulkan.vcxproj b/libs/SDL3/VisualC/tests/testvulkan/testvulkan.vcxproj index 92c8edd..72cd50e 100644 --- a/libs/SDL3/VisualC/tests/testvulkan/testvulkan.vcxproj +++ b/libs/SDL3/VisualC/tests/testvulkan/testvulkan.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testwm/testwm.vcxproj b/libs/SDL3/VisualC/tests/testwm/testwm.vcxproj index 978087e..62874fa 100644 --- a/libs/SDL3/VisualC/tests/testwm/testwm.vcxproj +++ b/libs/SDL3/VisualC/tests/testwm/testwm.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testyuv/testyuv.vcxproj b/libs/SDL3/VisualC/tests/testyuv/testyuv.vcxproj index 852b010..46eac14 100644 --- a/libs/SDL3/VisualC/tests/testyuv/testyuv.vcxproj +++ b/libs/SDL3/VisualC/tests/testyuv/testyuv.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) @@ -202,7 +201,7 @@ - + Copying %(Filename)%(Extension) copy "%(FullPath)" "$(ProjectDir)\" diff --git a/libs/SDL3/WhatsNew.txt b/libs/SDL3/WhatsNew.txt index 0d10ee6..67a50be 100644 --- a/libs/SDL3/WhatsNew.txt +++ b/libs/SDL3/WhatsNew.txt @@ -1,16 +1,134 @@ This is a list of major changes in SDL's version history. +--------------------------------------------------------------------------- +3.4.0: +--------------------------------------------------------------------------- + +General: +* Added SDL_CreateAnimatedCursor() to create animated color cursors +* Added SDL_HINT_MOUSE_DPI_SCALE_CURSORS to automatically scale cursors based on the display scale +* Added SDL_SetWindowProgressState(), SDL_SetWindowProgressValue(), SDL_GetWindowProgressState(), and SDL_GetWindowProgressValue() to show progress in the window's taskbar icon on Windows and Linux +* Added GPU device creation properties to enable the GPU API on older hardware if you're not using these features: + - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN + - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN + - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN + - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN + - SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN +* Added SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER to enable configuring Vulkan features when creating a GPU device +* Added SDL_PROP_GPU_DEVICE_CREATE_VULKAN_REQUIRE_HARDWARE_ACCELERATION_BOOLEAN to allow requiring Vulkan hardware acceleration when creating a GPU device +* Added SDL_GetGPUDeviceProperties() to query information from a GPU device: + - SDL_PROP_GPU_DEVICE_NAME_STRING + - SDL_PROP_GPU_DEVICE_DRIVER_NAME_STRING + - SDL_PROP_GPU_DEVICE_DRIVER_VERSION_STRING + - SDL_PROP_GPU_DEVICE_DRIVER_INFO_STRING +* Added SDL_GetPixelFormatFromGPUTextureFormat() and SDL_GetGPUTextureFormatFromPixelFormat() +* Added SDL_CreateGPURenderer() and SDL_GetGPURendererDevice() to create a 2D renderer for use with GPU rendering. +* Added SDL_CreateGPURenderState(), SDL_SetGPURenderStateFragmentUniforms(), SDL_SetGPURenderState(), and SDL_DestroyGPURenderState() to use fragment shaders with a GPU 2D renderer +* Added SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_POINTER to create a 2D texture from an existing GPU texture +* Added SDL_PROP_TEXTURE_GPU_TEXTURE_POINTER to get the GPU texture from a 2D texture when using the GPU 2D renderer +* Added support for YUV textures and HDR colorspaces to the GPU 2D renderer +* Added support for textures with palettes, and SDL_GetTexturePalette() and SDL_SetTexturePalette() to interact with them +* Added SDL_RenderTexture9GridTiled() to do tiled instead of stretched 9-grid texture rendering +* Added SDL_GetDefaultTextureScaleMode() and SDL_SetDefaultTextureScaleMode() to set the texture scale mode for new textures +* Added SDL_GetRenderTextureAddressMode() and SDL_SetRenderTextureAddressMode() to change the texture addressing mode +* Added SDL_TEXTURE_ADDRESS_WRAP to allow wrapping of textures if the renderer has SDL_PROP_RENDERER_TEXTURE_WRAPPING_BOOLEAN set +* The default YUV colorspace is BT.601 limited range, for compatibility with SDL2 +* Added SDL_SCALEMODE_PIXELART as an improved scaling algorithm for pixel art without introducing blurring +* Added SDL_FLIP_HORIZONTAL_AND_VERTICAL to flip a surface both horizontally and vertically +* Added SDL_LoadPNG(), SDL_LoadPNG_IO(), SDL_SavePNG(), and SDL_SavePNG_IO() to load and save PNG images +* Added SDL_LoadSurface() and SDL_LoadSurface_IO() to detect BMP and PNG formats and load them as surfaces +* Added SDL_PROP_SURFACE_ROTATION_FLOAT to indicate the rotation needed to display camera images upright +* Added SDL_RotateSurface() to create a rotated copy of a surface +* SDL_EVENT_WINDOW_EXPOSED now sets data1 to true if it is sent during live resizing +* Added SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED, which is sent when the usable desktop bounds change +* Added SDL_EVENT_SCREEN_KEYBOARD_SHOWN, which is sent when the on-screen keyboard has been shown +* Added SDL_EVENT_SCREEN_KEYBOARD_HIDDEN, which is sent when the on-screen keyboard has been hidden +* Added pinch gesture events: SDL_EVENT_PINCH_BEGIN, SDL_EVENT_PINCH_UPDATE, SDL_EVENT_PINCH_END +* SDL_EVENT_AUDIO_DEVICE_ADDED will be sent during initialization for each audio device +* SDL_GetCameraPermissionState() returns SDL_CameraPermissionState instead of int +* Added SDL_PutAudioStreamDataNoCopy() to do more efficient audio stream processing in some cases +* Added SDL_PutAudioStreamPlanarData() to add planar audio data instead of interleaved data to an audio stream +* Added SDL_HINT_AUDIO_DEVICE_RAW_STREAM to signal that the OS shouldn't do further audio processing, useful for applications that handle noise canceling, etc. +* Added SDL_PROP_AUDIOSTREAM_AUTO_CLEANUP_BOOLEAN to allow streams that persist beyond the audio subsystem lifetime. +* Added enhanced support for 8BitDo controllers +* Added enhanced support for FlyDigi controllers +* Added enhanced support for Hand Held Legend SInput controllers +* Added support for wired Nintendo Switch 2 controllers when built with libusb +* Added SDL_hid_get_properties() to associate SDL properties with HID devices +* Added SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER to query the libusb handle from an SDL_hid_device, if it's been opened with libusb +* Added SDL_SetRelativeMouseTransform() to add custom mouse input transformation +* Added SDL_GetPenDeviceType() to determine whether a pen is on the screen or on a separate touchpad +* SDL_HINT_MAIN_CALLBACK_RATE may be set to a floating point callback rate +* Added SDL_GetEventDescription() to get an English description of an event, suitable for logging +* Added SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC_POINTER to allow custom freeing of the memory used by SDL_IOFromMem() and SDL_IOFromConstMem() +* Added SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING to set the working directory for new processes +* Added verbose log output when the DEBUG_INVOCATION environment variable is set to "1" +* Added SDL_AddAtomicU32() +* Added SDL_GetSystemPageSize() to get the system page size +* Added SDL_ALIGNED() to signal that data should have a specific alignment + +Windows: +* Added SDL_HINT_RENDER_DIRECT3D11_WARP to enable D3D11 software rasterization +* Using SDL_InsertGPUDebugLabel(), SDL_PushGPUDebugGroup(), and SDL_PopGPUDebugGroup() requires WinPixEventRuntime.dll to be in your PATH or in the same directory as your executable +* Added SDL_PROP_DISPLAY_WINDOWS_HMONITOR_POINTER so you can query the HMONITOR associated with a display +* SDL_HINT_AUDIO_DEVICE_STREAM_ROLE is used by the WASAPI audio driver to set the audio stream category +* Added SDL_HINT_AUDIO_DEVICE_RAW_STREAM to signal whether the OS audio driver should do additional signal processing +* Added SDL_HINT_WINDOWS_RAW_KEYBOARD_EXCLUDE_HOTKEYS to allow disabling some system hotkeys when in raw input mode +* SDL_HINT_WINDOWS_GAMEINPUT is disabled by default + +macOS: +* Added SDL_HINT_MAC_PRESS_AND_HOLD to control whether holding down a key will repeat the pressed key or open the accents menu + +Linux: +* Added atomic support for KMSDRM +* Added SDL_HINT_KMSDRM_ATOMIC to control whether KMSDRM will use atomic functionality +* Added SDL_PROP_DISPLAY_WAYLAND_WL_OUTPUT_POINTER so you can query the wl_output associated with a display + +Emscripten: +* Added SDL_WINDOW_FILL_DOCUMENT to indicate that windows expand to fill the whole browser window +* Added SDL_SetWindowFillDocument() to change whether windows expand to fill the whole browser window +* Added SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING to allow setting the SDL canvas ID, and SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING to query it on existing windows +* Added SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING to specify where keyboard input is bound, and SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING to query it on existing windows + +iOS: +* SDL now supports window scenes, fixing the warning "CLIENT OF UIKIT REQUIRES UPDATE" +* Added SDL_PROP_WINDOW_CREATE_WINDOWSCENE_POINTER to specify the window scene for a window + +visionOS: +* The default refresh rate has been increased to 90Hz +* SDL_SetWindowSize() changes the size of the window on Vision Pro headsets + +PlayStation 2: +* Added the following hints to control the display parameters: SDL_HINT_PS2_GS_WIDTH, SDL_HINT_PS2_GS_HEIGHT, SDL_HINT_PS2_GS_PROGRESSIVE, SDL_HINT_PS2_GS_MODE + +Note: On Unix platforms SDL provides ELF notes describing its non-mandatory library dependencies in the format described by https://systemd.io/ELF_DLOPEN_METADATA/. Some of these libraries are quite important, so distribution vendors who package SDL should parse the ELF notes and consider generating dependencies at the packaging level, for example by using https://github.com/systemd/package-notes. Other libraries and games can add similar ELF notes to describe their own dependencies by using the SDL_ELF_NOTE_DLOPEN macro. + + +--------------------------------------------------------------------------- +3.2.22: +--------------------------------------------------------------------------- +* SDL_HINT_JOYSTICK_WGI is disabled by default + + +--------------------------------------------------------------------------- +3.2.16: +--------------------------------------------------------------------------- +* SDL_HINT_JOYSTICK_RAWINPUT is disabled by default + + --------------------------------------------------------------------------- 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 5e724fc..1c6e701 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.20 + 3.4.2 CFBundleSignature SDLX CFBundleVersion - 3.2.20 + 3.4.2 diff --git a/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj index ae8a61b..2d437b8 100644 --- a/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 55; objects = { /* Begin PBXAggregateTarget section */ @@ -54,6 +54,7 @@ 00CFA89D106B4BA100758660 /* ForceFeedback.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00CFA89C106B4BA100758660 /* ForceFeedback.framework */; platformFilters = (macos, ); }; 00D0D08410675DD9004B05EF /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00D0D08310675DD9004B05EF /* CoreFoundation.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); settings = {ATTRIBUTES = (Required, ); }; }; 00D0D0D810675E46004B05EF /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 007317C10858E15000B2BC32 /* Carbon.framework */; platformFilters = (macos, ); }; + 02D6A1C228A84B8F00A7F002 /* SDL_hidapi_sinput.c in Sources */ = {isa = PBXBuildFile; fileRef = 02D6A1C128A84B8F00A7F001 /* SDL_hidapi_sinput.c */; }; 1485C3312BBA4AF30063985B /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1485C32F2BBA4A0C0063985B /* UniformTypeIdentifiers.framework */; platformFilters = (maccatalyst, macos, ); settings = {ATTRIBUTES = (Weak, ); }; }; 557D0CFA254586CA003913E3 /* CoreHaptics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F37DC5F225350EBC0002E6F7 /* CoreHaptics.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); settings = {ATTRIBUTES = (Weak, ); }; }; 557D0CFB254586D7003913E3 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A75FDABD23E28B6200529352 /* GameController.framework */; settings = {ATTRIBUTES = (Required, ); }; }; @@ -66,11 +67,16 @@ 566E26D8246274CC00718109 /* SDL_locale.c in Sources */ = {isa = PBXBuildFile; fileRef = 566E26CD246274CB00718109 /* SDL_locale.c */; }; 566E26E1246274CC00718109 /* SDL_syslocale.h in Headers */ = {isa = PBXBuildFile; fileRef = 566E26CE246274CC00718109 /* SDL_syslocale.h */; }; 56A2373329F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; }; + 63124A422E5C357500A53610 /* SDL_hidapi_zuiki.c in Sources */ = {isa = PBXBuildFile; fileRef = 63124A412E5C357500A53610 /* SDL_hidapi_zuiki.c */; }; 6312C66D2B42341400A7BB00 /* SDL_murmur3.c in Sources */ = {isa = PBXBuildFile; fileRef = 6312C66C2B42341400A7BB00 /* SDL_murmur3.c */; }; 63134A252A7902FD0021E9A6 /* SDL_pen_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 63134A232A7902FD0021E9A6 /* SDL_pen_c.h */; }; 63134A262A7902FD0021E9A6 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63134A242A7902FD0021E9A6 /* SDL_pen.c */; }; 75E0915A241EA924004729E1 /* SDL_virtualjoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */; }; 75E09163241EA924004729E1 /* SDL_virtualjoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */; }; + 89E5801E2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c in Sources */ = {isa = PBXBuildFile; fileRef = 89E5801D2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c */; }; + 89E580232D03606400DAF6D3 /* SDL_hidapihaptic.c in Sources */ = {isa = PBXBuildFile; fileRef = 89E5801F2D03606400DAF6D3 /* SDL_hidapihaptic.c */; }; + 89E580242D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c in Sources */ = {isa = PBXBuildFile; fileRef = 89E580212D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c */; }; + 89E580252D03606400DAF6D3 /* SDL_hidapihaptic_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 89E580202D03606400DAF6D3 /* SDL_hidapihaptic_c.h */; }; 9846B07C287A9020000C35C8 /* SDL_hidapi_shield.c in Sources */ = {isa = PBXBuildFile; fileRef = 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */; }; A1626A3E2617006A003F1973 /* SDL_triangle.c in Sources */ = {isa = PBXBuildFile; fileRef = A1626A3D2617006A003F1973 /* SDL_triangle.c */; }; A1626A522617008D003F1973 /* SDL_triangle.h in Headers */ = {isa = PBXBuildFile; fileRef = A1626A512617008C003F1973 /* SDL_triangle.h */; }; @@ -224,6 +230,7 @@ A7D8B54523E2514300DCD162 /* SDL_hidapijoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C423E2513E00DCD162 /* SDL_hidapijoystick.c */; }; A7D8B54B23E2514300DCD162 /* SDL_hidapi_xboxone.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C523E2513E00DCD162 /* SDL_hidapi_xboxone.c */; }; A7D8B55123E2514300DCD162 /* SDL_hidapi_switch.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C623E2513E00DCD162 /* SDL_hidapi_switch.c */; }; + A7D8B55123E2514300DCD163 /* SDL_hidapi_switch2.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C623E2513E00DCD163 /* SDL_hidapi_switch2.c */; }; A7D8B55723E2514300DCD162 /* SDL_hidapijoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A7C723E2513E00DCD162 /* SDL_hidapijoystick_c.h */; }; A7D8B55D23E2514300DCD162 /* SDL_hidapi_xbox360w.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C823E2513E00DCD162 /* SDL_hidapi_xbox360w.c */; }; A7D8B56323E2514300DCD162 /* SDL_hidapi_gamecube.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */; }; @@ -281,7 +288,6 @@ A7D8B9E323E2514400DCD162 /* SDL_drawline.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A8F123E2514000DCD162 /* SDL_drawline.c */; }; A7D8B9E923E2514400DCD162 /* SDL_blendline.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A8F223E2514000DCD162 /* SDL_blendline.h */; }; A7D8B9EF23E2514400DCD162 /* SDL_drawpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A8F323E2514000DCD162 /* SDL_drawpoint.h */; }; - A7D8B9F523E2514400DCD162 /* SDL_rotate.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A8F423E2514000DCD162 /* SDL_rotate.c */; }; A7D8B9FB23E2514400DCD162 /* SDL_render_sw_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A8F523E2514000DCD162 /* SDL_render_sw_c.h */; }; A7D8BA0123E2514400DCD162 /* SDL_blendfillrect.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A8F623E2514000DCD162 /* SDL_blendfillrect.h */; }; A7D8BA0723E2514400DCD162 /* SDL_drawline.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A8F723E2514000DCD162 /* SDL_drawline.h */; }; @@ -291,8 +297,6 @@ A7D8BA1F23E2514400DCD162 /* SDL_blendline.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A8FB23E2514000DCD162 /* SDL_blendline.c */; }; A7D8BA2523E2514400DCD162 /* SDL_drawpoint.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A8FC23E2514000DCD162 /* SDL_drawpoint.c */; }; A7D8BA2B23E2514400DCD162 /* SDL_blendfillrect.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A8FD23E2514000DCD162 /* SDL_blendfillrect.c */; }; - A7D8BA3123E2514400DCD162 /* SDL_rotate.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A8FE23E2514000DCD162 /* SDL_rotate.h */; }; - A7D8BA3723E2514400DCD162 /* SDL_d3dmath.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A8FF23E2514000DCD162 /* SDL_d3dmath.c */; }; A7D8BA4923E2514400DCD162 /* SDL_render_gles2.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A90423E2514000DCD162 /* SDL_render_gles2.c */; }; A7D8BA4F23E2514400DCD162 /* SDL_shaders_gles2.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A90523E2514000DCD162 /* SDL_shaders_gles2.h */; }; A7D8BA5523E2514400DCD162 /* SDL_gles2funcs.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A90623E2514000DCD162 /* SDL_gles2funcs.h */; }; @@ -384,6 +388,7 @@ F32DDAD42AB795A30041EAA5 /* SDL_audioresample.c in Sources */ = {isa = PBXBuildFile; fileRef = F32DDACE2AB795A30041EAA5 /* SDL_audioresample.c */; }; F338A1182D1B37D8007CDFDF /* SDL_tray.m in Sources */ = {isa = PBXBuildFile; fileRef = F338A1172D1B37D8007CDFDF /* SDL_tray.m */; }; F338A11A2D1B37E4007CDFDF /* SDL_tray.c in Sources */ = {isa = PBXBuildFile; fileRef = F338A1192D1B37E4007CDFDF /* SDL_tray.c */; }; + F3395BA82D9A5971007246C8 /* SDL_hidapi_8bitdo.c in Sources */ = {isa = PBXBuildFile; fileRef = F3395BA72D9A5971007246C8 /* SDL_hidapi_8bitdo.c */; }; F34400342D40217A003F26D7 /* LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = F373DA182D388A1E002158FA /* LICENSE.txt */; }; F34400362D40217A003F26D7 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = F373DA192D388A1E002158FA /* README.md */; }; F344003D2D4022E1003F26D7 /* INSTALL.md in Resources */ = {isa = PBXBuildFile; fileRef = F344003C2D4022E1003F26D7 /* INSTALL.md */; }; @@ -409,6 +414,8 @@ F386F6F02884663E001840AA /* SDL_utils_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F386F6E52884663E001840AA /* SDL_utils_c.h */; }; F386F6F92884663E001840AA /* SDL_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = F386F6E62884663E001840AA /* SDL_utils.c */; }; F388C95528B5F6F700661ECF /* SDL_hidapi_ps3.c in Sources */ = {isa = PBXBuildFile; fileRef = F388C95428B5F6F600661ECF /* SDL_hidapi_ps3.c */; }; + F38C72492CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c in Sources */ = {isa = PBXBuildFile; fileRef = F38C72482CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c */; }; + F39344CE2E99771B0056986F /* SDL_dlopennote.h in Headers */ = {isa = PBXBuildFile; fileRef = F39344CD2E99771B0056986F /* SDL_dlopennote.h */; settings = {ATTRIBUTES = (Public, ); }; }; F395BF6525633B2400942BFF /* SDL_crc32.c in Sources */ = {isa = PBXBuildFile; fileRef = F395BF6425633B2400942BFF /* SDL_crc32.c */; }; F395C1932569C68F00942BFF /* SDL_iokitjoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F395C1912569C68E00942BFF /* SDL_iokitjoystick_c.h */; }; F395C19C2569C68F00942BFF /* SDL_iokitjoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = F395C1922569C68E00942BFF /* SDL_iokitjoystick.c */; }; @@ -433,6 +440,7 @@ F3B439532C935C2C00792030 /* SDL_posixprocess.c in Sources */ = {isa = PBXBuildFile; fileRef = F3B439522C935C2C00792030 /* SDL_posixprocess.c */; }; F3B439562C937DAB00792030 /* SDL_process.c in Sources */ = {isa = PBXBuildFile; fileRef = F3B439542C937DAB00792030 /* SDL_process.c */; }; F3B439572C937DAB00792030 /* SDL_sysprocess.h in Headers */ = {isa = PBXBuildFile; fileRef = F3B439552C937DAB00792030 /* SDL_sysprocess.h */; }; + F3B6B80A2DC3EA54004954FD /* SDL_hidapi_gip.c in Sources */ = {isa = PBXBuildFile; fileRef = F3B6B8092DC3EA54004954FD /* SDL_hidapi_gip.c */; }; F3C1BD752D1F1A3000846529 /* SDL_tray_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C1BD742D1F1A3000846529 /* SDL_tray_utils.c */; }; F3C1BD762D1F1A3000846529 /* SDL_tray_utils.h in Headers */ = {isa = PBXBuildFile; fileRef = F3C1BD732D1F1A3000846529 /* SDL_tray_utils.h */; }; F3C2CB222C5DDDB2004D7998 /* SDL_categories_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3C2CB202C5DDDB2004D7998 /* SDL_categories_c.h */; }; @@ -514,10 +522,18 @@ 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 */; }; + F3DB66342EA9ACC300568044 /* SDL_rotate.c in Sources */ = {isa = PBXBuildFile; fileRef = F3DB66332EA9ACC300568044 /* SDL_rotate.c */; }; + F3DB66352EA9ACC300568044 /* SDL_rotate.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DB66322EA9ACC300568044 /* SDL_rotate.h */; }; + F3DC38C92E5FC60300CD73DE /* SDL_libusb.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DC38C72E5FC60300CD73DE /* SDL_libusb.h */; }; + F3DC38CA2E5FC60300CD73DE /* SDL_libusb.c in Sources */ = {isa = PBXBuildFile; fileRef = F3DC38C82E5FC60300CD73DE /* SDL_libusb.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 */; }; + F3E6C3932EE9F20000A6B39E /* SDL_report_descriptor.c in Sources */ = {isa = PBXBuildFile; fileRef = F3E6C3922EE9F20000A6B39E /* SDL_report_descriptor.c */; }; + F3E6C3942EE9F20000A6B39E /* SDL_hidapi_flydigi.h in Headers */ = {isa = PBXBuildFile; fileRef = F3E6C38F2EE9F20000A6B39E /* SDL_hidapi_flydigi.h */; }; + F3E6C3952EE9F20000A6B39E /* SDL_hidapi_sinput.h in Headers */ = {isa = PBXBuildFile; fileRef = F3E6C3902EE9F20000A6B39E /* SDL_hidapi_sinput.h */; }; + F3E6C3962EE9F20000A6B39E /* SDL_report_descriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = F3E6C3912EE9F20000A6B39E /* SDL_report_descriptor.h */; }; 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 */; }; @@ -535,6 +551,7 @@ F3FA5A232B59ACE000FEAD97 /* yuv_rgb_lsx.c in Sources */ = {isa = PBXBuildFile; fileRef = F3FA5A1A2B59ACE000FEAD97 /* yuv_rgb_lsx.c */; }; F3FA5A242B59ACE000FEAD97 /* yuv_rgb_lsx.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FA5A1B2B59ACE000FEAD97 /* yuv_rgb_lsx.h */; }; F3FA5A252B59ACE000FEAD97 /* yuv_rgb_common.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FA5A1C2B59ACE000FEAD97 /* yuv_rgb_common.h */; }; + F3FBB1082DDF93AB0000F99F /* SDL_hidapi_flydigi.c in Sources */ = {isa = PBXBuildFile; fileRef = F3395BA72D9A5971007246C9 /* SDL_hidapi_flydigi.c */; }; F3FD042E2C9B755700824C4C /* SDL_hidapi_nintendo.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FD042C2C9B755700824C4C /* SDL_hidapi_nintendo.h */; }; F3FD042F2C9B755700824C4C /* SDL_hidapi_steam_hori.c in Sources */ = {isa = PBXBuildFile; fileRef = F3FD042D2C9B755700824C4C /* SDL_hidapi_steam_hori.c */; }; FA73671D19A540EF004122E4 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA73671C19A540EF004122E4 /* CoreVideo.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); settings = {ATTRIBUTES = (Required, ); }; }; @@ -593,6 +610,7 @@ 007317C10858E15000B2BC32 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; 00CFA89C106B4BA100758660 /* ForceFeedback.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ForceFeedback.framework; path = System/Library/Frameworks/ForceFeedback.framework; sourceTree = SDKROOT; }; 00D0D08310675DD9004B05EF /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 02D6A1C128A84B8F00A7F001 /* SDL_hidapi_sinput.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_sinput.c; sourceTree = ""; }; 1485C32F2BBA4A0C0063985B /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; 5616CA49252BB2A5005D5928 /* SDL_url.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_url.c; sourceTree = ""; }; 5616CA4A252BB2A6005D5928 /* SDL_sysurl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_sysurl.h; sourceTree = ""; }; @@ -603,11 +621,16 @@ 566E26CD246274CB00718109 /* SDL_locale.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_locale.c; path = locale/SDL_locale.c; sourceTree = ""; }; 566E26CE246274CC00718109 /* SDL_syslocale.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_syslocale.h; path = locale/SDL_syslocale.h; sourceTree = ""; }; 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_sysrwlock.c; sourceTree = ""; }; + 63124A412E5C357500A53610 /* SDL_hidapi_zuiki.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_zuiki.c; sourceTree = ""; }; 6312C66C2B42341400A7BB00 /* SDL_murmur3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_murmur3.c; sourceTree = ""; }; 63134A232A7902FD0021E9A6 /* SDL_pen_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_pen_c.h; sourceTree = ""; }; 63134A242A7902FD0021E9A6 /* SDL_pen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_pen.c; sourceTree = ""; }; 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_virtualjoystick.c; sourceTree = ""; }; 75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_virtualjoystick_c.h; sourceTree = ""; }; + 89E5801D2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_lg4ff.c; sourceTree = ""; }; + 89E5801F2D03606400DAF6D3 /* SDL_hidapihaptic.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapihaptic.c; sourceTree = ""; }; + 89E580202D03606400DAF6D3 /* SDL_hidapihaptic_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_hidapihaptic_c.h; sourceTree = ""; }; + 89E580212D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapihaptic_lg4ff.c; sourceTree = ""; }; 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_shield.c; sourceTree = ""; }; A1626A3D2617006A003F1973 /* SDL_triangle.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_triangle.c; sourceTree = ""; }; A1626A512617008C003F1973 /* SDL_triangle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_triangle.h; sourceTree = ""; }; @@ -800,6 +823,7 @@ A7D8A7C423E2513E00DCD162 /* SDL_hidapijoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapijoystick.c; sourceTree = ""; }; A7D8A7C523E2513E00DCD162 /* SDL_hidapi_xboxone.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xboxone.c; sourceTree = ""; }; A7D8A7C623E2513E00DCD162 /* SDL_hidapi_switch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_switch.c; sourceTree = ""; }; + A7D8A7C623E2513E00DCD163 /* SDL_hidapi_switch2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_switch2.c; sourceTree = ""; }; A7D8A7C723E2513E00DCD162 /* SDL_hidapijoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hidapijoystick_c.h; sourceTree = ""; }; A7D8A7C823E2513E00DCD162 /* SDL_hidapi_xbox360w.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xbox360w.c; sourceTree = ""; }; A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_gamecube.c; sourceTree = ""; }; @@ -858,7 +882,6 @@ A7D8A8F123E2514000DCD162 /* SDL_drawline.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_drawline.c; sourceTree = ""; }; A7D8A8F223E2514000DCD162 /* SDL_blendline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_blendline.h; sourceTree = ""; }; A7D8A8F323E2514000DCD162 /* SDL_drawpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_drawpoint.h; sourceTree = ""; }; - A7D8A8F423E2514000DCD162 /* SDL_rotate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_rotate.c; sourceTree = ""; }; A7D8A8F523E2514000DCD162 /* SDL_render_sw_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_render_sw_c.h; sourceTree = ""; }; A7D8A8F623E2514000DCD162 /* SDL_blendfillrect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_blendfillrect.h; sourceTree = ""; }; A7D8A8F723E2514000DCD162 /* SDL_drawline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_drawline.h; sourceTree = ""; }; @@ -868,8 +891,6 @@ A7D8A8FB23E2514000DCD162 /* SDL_blendline.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_blendline.c; sourceTree = ""; }; A7D8A8FC23E2514000DCD162 /* SDL_drawpoint.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_drawpoint.c; sourceTree = ""; }; A7D8A8FD23E2514000DCD162 /* SDL_blendfillrect.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_blendfillrect.c; sourceTree = ""; }; - A7D8A8FE23E2514000DCD162 /* SDL_rotate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_rotate.h; sourceTree = ""; }; - A7D8A8FF23E2514000DCD162 /* SDL_d3dmath.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_d3dmath.c; sourceTree = ""; }; A7D8A90423E2514000DCD162 /* SDL_render_gles2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_render_gles2.c; sourceTree = ""; }; A7D8A90523E2514000DCD162 /* SDL_shaders_gles2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_shaders_gles2.h; sourceTree = ""; }; A7D8A90623E2514000DCD162 /* SDL_gles2funcs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_gles2funcs.h; sourceTree = ""; }; @@ -938,6 +959,8 @@ F32DDACE2AB795A30041EAA5 /* SDL_audioresample.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_audioresample.c; sourceTree = ""; }; F338A1172D1B37D8007CDFDF /* SDL_tray.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDL_tray.m; sourceTree = ""; }; F338A1192D1B37E4007CDFDF /* SDL_tray.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_tray.c; sourceTree = ""; }; + F3395BA72D9A5971007246C8 /* SDL_hidapi_8bitdo.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_8bitdo.c; sourceTree = ""; }; + F3395BA72D9A5971007246C9 /* SDL_hidapi_flydigi.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_flydigi.c; sourceTree = ""; }; F344003C2D4022E1003F26D7 /* INSTALL.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = INSTALL.md; sourceTree = ""; }; F362B9152B3349E200D30B94 /* controller_list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = controller_list.h; sourceTree = ""; }; F362B9162B3349E200D30B94 /* SDL_gamepad_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_gamepad_c.h; sourceTree = ""; }; @@ -979,6 +1002,8 @@ F386F6E52884663E001840AA /* SDL_utils_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_utils_c.h; sourceTree = ""; }; F386F6E62884663E001840AA /* SDL_utils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_utils.c; sourceTree = ""; }; F388C95428B5F6F600661ECF /* SDL_hidapi_ps3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_ps3.c; sourceTree = ""; }; + F38C72482CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_steam_triton.c; sourceTree = ""; }; + F39344CD2E99771B0056986F /* SDL_dlopennote.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_dlopennote.h; sourceTree = ""; }; F395BF6425633B2400942BFF /* SDL_crc32.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_crc32.c; sourceTree = ""; }; F395C1912569C68E00942BFF /* SDL_iokitjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_iokitjoystick_c.h; sourceTree = ""; }; F395C1922569C68E00942BFF /* SDL_iokitjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_iokitjoystick.c; sourceTree = ""; }; @@ -1002,6 +1027,7 @@ F3B439522C935C2C00792030 /* SDL_posixprocess.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_posixprocess.c; sourceTree = ""; }; F3B439542C937DAB00792030 /* SDL_process.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_process.c; sourceTree = ""; }; F3B439552C937DAB00792030 /* SDL_sysprocess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_sysprocess.h; sourceTree = ""; }; + F3B6B8092DC3EA54004954FD /* SDL_hidapi_gip.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_gip.c; sourceTree = ""; }; F3C1BD732D1F1A3000846529 /* SDL_tray_utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_tray_utils.h; sourceTree = ""; }; F3C1BD742D1F1A3000846529 /* SDL_tray_utils.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_tray_utils.c; sourceTree = ""; }; F3C2CB202C5DDDB2004D7998 /* SDL_categories_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_categories_c.h; sourceTree = ""; }; @@ -1083,10 +1109,18 @@ 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 = ""; }; + F3DB66322EA9ACC300568044 /* SDL_rotate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_rotate.h; sourceTree = ""; }; + F3DB66332EA9ACC300568044 /* SDL_rotate.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_rotate.c; sourceTree = ""; }; + F3DC38C72E5FC60300CD73DE /* SDL_libusb.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_libusb.h; sourceTree = ""; }; + F3DC38C82E5FC60300CD73DE /* SDL_libusb.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_libusb.c; 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 = ""; }; + F3E6C38F2EE9F20000A6B39E /* SDL_hidapi_flydigi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_flydigi.h; sourceTree = ""; }; + F3E6C3902EE9F20000A6B39E /* SDL_hidapi_sinput.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_sinput.h; sourceTree = ""; }; + F3E6C3912EE9F20000A6B39E /* SDL_report_descriptor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_report_descriptor.h; sourceTree = ""; }; + F3E6C3922EE9F20000A6B39E /* SDL_report_descriptor.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_report_descriptor.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 = ""; }; @@ -1243,6 +1277,7 @@ F3D46A8B2D20625800D9CBDF /* SDL_copying.h */, F3D46A8C2D20625800D9CBDF /* SDL_cpuinfo.h */, F3D46A8D2D20625800D9CBDF /* SDL_dialog.h */, + F39344CD2E99771B0056986F /* SDL_dlopennote.h */, F3D46A8E2D20625800D9CBDF /* SDL_egl.h */, F3D46A8F2D20625800D9CBDF /* SDL_endian.h */, F3D46A902D20625800D9CBDF /* SDL_error.h */, @@ -1396,6 +1431,8 @@ children = ( F3ADAB8C2576F08500A6B1D9 /* ios */, 5616CA48252BB285005D5928 /* macos */, + F3DC38C72E5FC60300CD73DE /* SDL_libusb.h */, + F3DC38C82E5FC60300CD73DE /* SDL_libusb.c */, 5616CA4A252BB2A6005D5928 /* SDL_sysurl.h */, 5616CA49252BB2A5005D5928 /* SDL_url.c */, ); @@ -1477,6 +1514,16 @@ path = virtual; sourceTree = ""; }; + 89E580222D03606400DAF6D3 /* hidapi */ = { + isa = PBXGroup; + children = ( + 89E5801F2D03606400DAF6D3 /* SDL_hidapihaptic.c */, + 89E580202D03606400DAF6D3 /* SDL_hidapihaptic_c.h */, + 89E580212D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c */, + ); + path = hidapi; + sourceTree = ""; + }; A75FDAA423E2790500529352 /* ios */ = { isa = PBXGroup; children = ( @@ -1535,6 +1582,7 @@ A7D8A5C223E2513D00DCD162 /* haptic */ = { isa = PBXGroup; children = ( + 89E580222D03606400DAF6D3 /* hidapi */, A7D8A5CD23E2513D00DCD162 /* darwin */, A7D8A5C323E2513D00DCD162 /* dummy */, A7D8A5C623E2513D00DCD162 /* SDL_haptic_c.h */, @@ -1623,6 +1671,8 @@ F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */, A7D8A61523E2513D00DCD162 /* SDL_RLEaccel.c */, A7D8A76723E2513E00DCD162 /* SDL_RLEaccel_c.h */, + F3DB66322EA9ACC300568044 /* SDL_rotate.h */, + F3DB66332EA9ACC300568044 /* SDL_rotate.c */, F3EFA5E92D5AB97300BCF22F /* SDL_stb.c */, F3EFA5EA2D5AB97300BCF22F /* SDL_stb_c.h */, A7D8A60323E2513D00DCD162 /* SDL_stretch.c */, @@ -1904,8 +1954,13 @@ A7D8A7BE23E2513E00DCD162 /* hidapi */ = { isa = PBXGroup; children = ( + F3395BA72D9A5971007246C8 /* SDL_hidapi_8bitdo.c */, F32305FE28939F6400E66D30 /* SDL_hidapi_combined.c */, + F3E6C38F2EE9F20000A6B39E /* SDL_hidapi_flydigi.h */, + F3395BA72D9A5971007246C9 /* SDL_hidapi_flydigi.c */, A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */, + F3B6B8092DC3EA54004954FD /* SDL_hidapi_gip.c */, + 89E5801D2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c */, F3F07D59269640160074468B /* SDL_hidapi_luna.c */, F3FD042C2C9B755700824C4C /* SDL_hidapi_nintendo.h */, F388C95428B5F6F600661ECF /* SDL_hidapi_ps3.c */, @@ -1914,17 +1969,24 @@ A75FDBC323EA380300529352 /* SDL_hidapi_rumble.h */, A75FDBC423EA380300529352 /* SDL_hidapi_rumble.c */, 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */, + F3E6C3902EE9F20000A6B39E /* SDL_hidapi_sinput.h */, + 02D6A1C128A84B8F00A7F001 /* SDL_hidapi_sinput.c */, F3984CCF25BCC92800374F43 /* SDL_hidapi_stadia.c */, A75FDAAC23E2795C00529352 /* SDL_hidapi_steam.c */, F3FD042D2C9B755700824C4C /* SDL_hidapi_steam_hori.c */, + F38C72482CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c */, A797456F2B2E9D39009D224A /* SDL_hidapi_steamdeck.c */, A7D8A7C623E2513E00DCD162 /* SDL_hidapi_switch.c */, + A7D8A7C623E2513E00DCD163 /* SDL_hidapi_switch2.c */, F3D60A8228C16A1800788A3A /* SDL_hidapi_wii.c */, A7D8A7C223E2513E00DCD162 /* SDL_hidapi_xbox360.c */, A7D8A7C823E2513E00DCD162 /* SDL_hidapi_xbox360w.c */, A7D8A7C523E2513E00DCD162 /* SDL_hidapi_xboxone.c */, + 63124A412E5C357500A53610 /* SDL_hidapi_zuiki.c */, A7D8A7C423E2513E00DCD162 /* SDL_hidapijoystick.c */, A7D8A7C723E2513E00DCD162 /* SDL_hidapijoystick_c.h */, + F3E6C3912EE9F20000A6B39E /* SDL_report_descriptor.h */, + F3E6C3922EE9F20000A6B39E /* SDL_report_descriptor.c */, ); path = hidapi; sourceTree = ""; @@ -2135,7 +2197,6 @@ A7D8A90C23E2514000DCD162 /* opengl */, A7D8A90323E2514000DCD162 /* opengles2 */, A7D8A8EF23E2514000DCD162 /* software */, - A7D8A8FF23E2514000DCD162 /* SDL_d3dmath.c */, A7D8A8DC23E2514000DCD162 /* SDL_d3dmath.h */, A7D8A8DB23E2514000DCD162 /* SDL_render.c */, E4F7981D2AD8D86A00669F54 /* SDL_render_unsupported.c */, @@ -2176,8 +2237,6 @@ A7D8A8F323E2514000DCD162 /* SDL_drawpoint.h */, A7D8A8F523E2514000DCD162 /* SDL_render_sw_c.h */, A7D8A8F923E2514000DCD162 /* SDL_render_sw.c */, - A7D8A8F423E2514000DCD162 /* SDL_rotate.c */, - A7D8A8FE23E2514000DCD162 /* SDL_rotate.h */, ); path = software; sourceTree = ""; @@ -2488,6 +2547,7 @@ A7D8AEEE23E2514100DCD162 /* SDL_cocoaopengles.h in Headers */, F3D46ACA2D20625800D9CBDF /* SDL_storage.h in Headers */, F3D46ACB2D20625800D9CBDF /* SDL_sensor.h in Headers */, + F3DB66352EA9ACC300568044 /* SDL_rotate.h in Headers */, F3D46ACC2D20625800D9CBDF /* SDL_properties.h in Headers */, F3D46ACD2D20625800D9CBDF /* SDL_bits.h in Headers */, F3D46ACE2D20625800D9CBDF /* SDL_keyboard.h in Headers */, @@ -2506,6 +2566,9 @@ F3D46ADB2D20625800D9CBDF /* SDL_pen.h in Headers */, F3D46ADC2D20625800D9CBDF /* SDL_render.h in Headers */, F3D46ADD2D20625800D9CBDF /* SDL_assert.h in Headers */, + F3E6C3942EE9F20000A6B39E /* SDL_hidapi_flydigi.h in Headers */, + F3E6C3952EE9F20000A6B39E /* SDL_hidapi_sinput.h in Headers */, + F3E6C3962EE9F20000A6B39E /* SDL_report_descriptor.h in Headers */, F3D46ADE2D20625800D9CBDF /* SDL_atomic.h in Headers */, F3D46ADF2D20625800D9CBDF /* SDL_begin_code.h in Headers */, F3D46AE02D20625800D9CBDF /* SDL_log.h in Headers */, @@ -2617,6 +2680,7 @@ F37E18642BAA40670098C111 /* SDL_time_c.h in Headers */, F31013C82C24E98200FBE946 /* SDL_keymap_c.h in Headers */, 63134A252A7902FD0021E9A6 /* SDL_pen_c.h in Headers */, + 89E580252D03606400DAF6D3 /* SDL_hidapihaptic_c.h in Headers */, F36C34312C0F876500991150 /* SDL_offscreenvulkan.h in Headers */, A7D8B2C023E2514200DCD162 /* SDL_pixels_c.h in Headers */, F37E18622BAA40090098C111 /* SDL_sysfilesystem.h in Headers */, @@ -2624,7 +2688,6 @@ F3DDCC5D2AFD42B600B0842B /* SDL_rect_impl.h in Headers */, A7D8B9FB23E2514400DCD162 /* SDL_render_sw_c.h in Headers */, E4F257972C81903800FCEAFC /* SDL_sysgpu.h in Headers */, - A7D8BA3123E2514400DCD162 /* SDL_rotate.h in Headers */, A7D8A98D23E2514000DCD162 /* SDL_sensor_c.h in Headers */, A7D8BA7323E2514400DCD162 /* SDL_shaders_gl.h in Headers */, A7D8BA4F23E2514400DCD162 /* SDL_shaders_gles2.h in Headers */, @@ -2712,8 +2775,10 @@ A7D8B28A23E2514200DCD162 /* vulkan_xlib_xrandr.h in Headers */, A7D8B3D423E2514300DCD162 /* yuv_rgb.h in Headers */, F3FA5A252B59ACE000FEAD97 /* yuv_rgb_common.h in Headers */, + F39344CE2E99771B0056986F /* SDL_dlopennote.h in Headers */, F3FA5A1D2B59ACE000FEAD97 /* yuv_rgb_internal.h in Headers */, F3D8BDFC2D6D2C7000B22FA1 /* SDL_eventwatch_c.h in Headers */, + F3DC38C92E5FC60300CD73DE /* SDL_libusb.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 */, @@ -2833,7 +2898,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Build an xcframework with both device and simulator files for all platforms.\n# Adapted from an answer in\n# https://developer.apple.com/forums/thread/666335?answerId=685927022#685927022\n\nif [ \"$XCODE_VERSION_ACTUAL\" -lt 1100 ]\nthen\n echo \"error: Building an xcframework requires Xcode 11 minimum.\"\n exit 1\nfi\n\nFRAMEWORK_NAME=\"SDL3\"\nPROJECT_NAME=\"SDL\"\nSCHEME=\"SDL3\"\n\nMACOS_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-macosx.xcarchive\"\nIOS_SIMULATOR_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphonesimulator.xcarchive\"\nIOS_DEVICE_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphoneos.xcarchive\"\nTVOS_SIMULATOR_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-appletvsimulator.xcarchive\"\nTVOS_DEVICE_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-appletvos.xcarchive\"\n\nOUTPUT_DIR=\"./build/\"\n\n# macOS\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${MACOS_ARCHIVE_PATH} \\\n -destination 'generic/platform=macOS,name=Any Mac' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n \n# iOS simulator\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${IOS_SIMULATOR_ARCHIVE_PATH} \\\n -destination 'generic/platform=iOS Simulator' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# iOS device\nxcodebuild archive \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${IOS_DEVICE_ARCHIVE_PATH} \\\n -destination 'generic/platform=iOS' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# tvOS simulator\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${TVOS_SIMULATOR_ARCHIVE_PATH} \\\n -destination 'generic/platform=tvOS Simulator' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# tvOS device\nxcodebuild archive \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${TVOS_DEVICE_ARCHIVE_PATH} \\\n -destination 'generic/platform=tvOS' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# Clean-up any existing instance of this xcframework from the Products directory\nrm -rf \"${OUTPUT_DIR}${FRAMEWORK_NAME}.xcframework\"\n\n# Create final xcframework\nxcodebuild -create-xcframework \\\n -framework \"${MACOS_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${IOS_DEVICE_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${IOS_SIMULATOR_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${TVOS_DEVICE_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${TVOS_SIMULATOR_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -output ${OUTPUT_DIR}/${FRAMEWORK_NAME}.xcframework\n\n# Ensure git doesn't pick up on our Products folder. \nrm -rf ${OUTPUT_DIR}/.gitignore\necho \"*\" >> ${OUTPUT_DIR}/.gitignore\n"; + shellScript = "# Build an xcframework with both device and simulator files for all platforms.\n# Adapted from an answer in\n# https://developer.apple.com/forums/thread/666335?answerId=685927022#685927022\n\nif [ \"$XCODE_VERSION_ACTUAL\" -lt 1100 ]\nthen\n echo \"error: Building an xcframework requires Xcode 11 minimum.\"\n exit 1\nfi\n\nFRAMEWORK_NAME=\"SDL3\"\nPROJECT_NAME=\"SDL\"\nSCHEME=\"SDL3\"\n\nMACOS_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-macosx.xcarchive\"\nIOS_SIMULATOR_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphonesimulator.xcarchive\"\nIOS_DEVICE_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphoneos.xcarchive\"\nTVOS_SIMULATOR_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-appletvsimulator.xcarchive\"\nTVOS_DEVICE_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-appletvos.xcarchive\"\n\nOUTPUT_DIR=\"./build/\"\n\n# macOS\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${MACOS_ARCHIVE_PATH} \\\n -destination 'generic/platform=macOS,name=Any Mac' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n \n# iOS simulator\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${IOS_SIMULATOR_ARCHIVE_PATH} \\\n -destination 'generic/platform=iOS Simulator' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# iOS device\nxcodebuild archive \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${IOS_DEVICE_ARCHIVE_PATH} \\\n -destination 'generic/platform=iOS' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# tvOS simulator\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${TVOS_SIMULATOR_ARCHIVE_PATH} \\\n -destination 'generic/platform=tvOS Simulator' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# tvOS device\nxcodebuild archive \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${TVOS_DEVICE_ARCHIVE_PATH} \\\n -destination 'generic/platform=tvOS' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# Clean-up any existing instance of this xcframework from the Products directory\nrm -rf \"${OUTPUT_DIR}${FRAMEWORK_NAME}.xcframework\"\n\n# Create final xcframework\nxcodebuild -create-xcframework \\\n -framework \"${MACOS_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -debug-symbols \"${MACOS_ARCHIVE_PATH}\"/dSYMs/$FRAMEWORK_NAME.framework.dSYM \\\n -framework \"${IOS_DEVICE_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -debug-symbols \"${IOS_DEVICE_ARCHIVE_PATH}\"/dSYMs/$FRAMEWORK_NAME.framework.dSYM \\\n -framework \"${IOS_SIMULATOR_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${TVOS_DEVICE_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -debug-symbols \"${TVOS_DEVICE_ARCHIVE_PATH}\"/dSYMs/$FRAMEWORK_NAME.framework.dSYM \\\n -framework \"${TVOS_SIMULATOR_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -output ${OUTPUT_DIR}/${FRAMEWORK_NAME}.xcframework\n\n# Ensure git doesn't pick up on our Products folder. \nrm -rf ${OUTPUT_DIR}/.gitignore\necho \"*\" >> ${OUTPUT_DIR}/.gitignore\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -2847,6 +2912,7 @@ A7D8B62F23E2514300DCD162 /* SDL_sysfilesystem.m in Sources */, A7D8B41C23E2514300DCD162 /* SDL_systls.c in Sources */, 9846B07C287A9020000C35C8 /* SDL_hidapi_shield.c in Sources */, + 02D6A1C228A84B8F00A7F002 /* SDL_hidapi_sinput.c in Sources */, F31013C72C24E98200FBE946 /* SDL_keymap.c in Sources */, F3A9AE992C8A13C100AAC390 /* SDL_render_gpu.c in Sources */, A7D8BBD923E2574800DCD162 /* SDL_uikitmessagebox.m in Sources */, @@ -2864,7 +2930,6 @@ A7D8AEC423E2514100DCD162 /* SDL_cocoaevents.m in Sources */, E479118F2BA9555500CE3B7F /* SDL_genericstorage.c in Sources */, A7D8B86623E2514400DCD162 /* SDL_audiocvt.c in Sources */, - A7D8B9F523E2514400DCD162 /* SDL_rotate.c in Sources */, A7D8BBE323E2574800DCD162 /* SDL_uikitvideo.m in Sources */, F338A1182D1B37D8007CDFDF /* SDL_tray.m in Sources */, 5616CA4E252BB2A6005D5928 /* SDL_sysurl.m in Sources */, @@ -2873,6 +2938,7 @@ F3C1BD752D1F1A3000846529 /* SDL_tray_utils.c in Sources */, F382071D284F362F004DD584 /* SDL_guid.c in Sources */, A7D8BB8D23E2514500DCD162 /* SDL_touch.c in Sources */, + F3E6C3932EE9F20000A6B39E /* SDL_report_descriptor.c in Sources */, F31A92D228D4CB39003BFD6A /* SDL_offscreenopengles.c in Sources */, A1626A3E2617006A003F1973 /* SDL_triangle.c in Sources */, A7D8B3F223E2514300DCD162 /* SDL_thread.c in Sources */, @@ -2892,6 +2958,7 @@ E4F257962C81903800FCEAFC /* SDL_gpu.c in Sources */, F3D60A8328C16A1900788A3A /* SDL_hidapi_wii.c in Sources */, A7D8B9DD23E2514400DCD162 /* SDL_blendpoint.c in Sources */, + F3DB66342EA9ACC300568044 /* SDL_rotate.c in Sources */, A7D8B4EE23E2514300DCD162 /* SDL_gamepad.c in Sources */, E4A568B62AF763940062EEC4 /* SDL_sysmain_callbacks.c in Sources */, F316ABD82B5C3185002EF551 /* SDL_memset.c in Sources */, @@ -2903,8 +2970,9 @@ A7D8BBE723E2574800DCD162 /* SDL_uikitviewcontroller.m in Sources */, A7D8ADF223E2514100DCD162 /* SDL_blit_A.c in Sources */, A7D8BBDD23E2574800DCD162 /* SDL_uikitmodes.m in Sources */, - A7D8BA3723E2514400DCD162 /* SDL_d3dmath.c in Sources */, F3A9AE9C2C8A13C100AAC390 /* SDL_pipeline_gpu.c in Sources */, + 89E580232D03606400DAF6D3 /* SDL_hidapihaptic.c in Sources */, + 89E580242D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c in Sources */, 75E0915A241EA924004729E1 /* SDL_virtualjoystick.c in Sources */, F338A11A2D1B37E4007CDFDF /* SDL_tray.c in Sources */, A7D8ABEB23E2514100DCD162 /* SDL_nullvideo.c in Sources */, @@ -2960,12 +3028,14 @@ F316ABD92B5C3185002EF551 /* SDL_memcpy.c in Sources */, A7D8B97A23E2514400DCD162 /* SDL_render.c in Sources */, A7D8ABD323E2514100DCD162 /* SDL_stretch.c in Sources */, + F38C72492CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c in Sources */, A7D8AC3923E2514100DCD162 /* SDL_blit_copy.c in Sources */, A7D8B5CF23E2514300DCD162 /* SDL_syspower.m in Sources */, F3B439512C935C2400792030 /* SDL_dummyprocess.c in Sources */, A7D8B76423E2514300DCD162 /* SDL_mixer.c in Sources */, A7D8BB5723E2514500DCD162 /* SDL_events.c in Sources */, A7D8ADE623E2514100DCD162 /* SDL_blit_0.c in Sources */, + 89E5801E2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c in Sources */, A7D8B8A823E2514400DCD162 /* SDL_diskaudio.c in Sources */, 56A2373329F9C113003CCA5F /* SDL_sysrwlock.c in Sources */, F3A9AE9A2C8A13C100AAC390 /* SDL_shaders_gpu.c in Sources */, @@ -3002,6 +3072,7 @@ F3FA5A222B59ACE000FEAD97 /* yuv_rgb_sse.c in Sources */, F3C2CB232C5DDDB2004D7998 /* SDL_categories.c in Sources */, A7D8B55123E2514300DCD162 /* SDL_hidapi_switch.c in Sources */, + A7D8B55123E2514300DCD163 /* SDL_hidapi_switch2.c in Sources */, A7D8B96223E2514400DCD162 /* SDL_strtokr.c in Sources */, A7D8BB7523E2514500DCD162 /* SDL_clipboardevents.c in Sources */, E4F798202AD8D87F00669F54 /* SDL_video_unsupported.c in Sources */, @@ -3029,6 +3100,7 @@ A7D8BA5B23E2514400DCD162 /* SDL_shaders_gles2.c in Sources */, A7D8B14023E2514200DCD162 /* SDL_blit_1.c in Sources */, A7D8BBDB23E2574800DCD162 /* SDL_uikitmetalview.m in Sources */, + F3B6B80A2DC3EA54004954FD /* SDL_hidapi_gip.c in Sources */, A7D8BB1523E2514500DCD162 /* SDL_mouse.c in Sources */, F395C19C2569C68F00942BFF /* SDL_iokitjoystick.c in Sources */, A7D8B4B223E2514300DCD162 /* SDL_sysjoystick.c in Sources */, @@ -3051,12 +3123,15 @@ 000028F8113A53F4333E0000 /* SDL_main_callbacks.c in Sources */, 000098E9DAA43EF6FF7F0000 /* SDL_camera.c in Sources */, F310138E2C1F2CB700FBE946 /* SDL_random.c in Sources */, + F3395BA82D9A5971007246C8 /* SDL_hidapi_8bitdo.c in Sources */, 00001B2471F503DD3C1B0000 /* SDL_camera_dummy.c in Sources */, 00002B20A48E055EB0350000 /* SDL_camera_coremedia.m in Sources */, 000080903BC03006F24E0000 /* SDL_filesystem.c in Sources */, + F3FBB1082DDF93AB0000F99F /* SDL_hidapi_flydigi.c in Sources */, 0000481D255AF155B42C0000 /* SDL_sysfsops.c in Sources */, 0000494CC93F3E624D3C0000 /* SDL_systime.c in Sources */, 000095FA1BDE436CF3AF0000 /* SDL_time.c in Sources */, + F3DC38CA2E5FC60300CD73DE /* SDL_libusb.c in Sources */, 0000140640E77F73F1DF0000 /* SDL_dialog_utils.c in Sources */, 0000D5B526B85DE7AB1C0000 /* SDL_cocoapen.m in Sources */, 6312C66D2B42341400A7BB00 /* SDL_murmur3.c in Sources */, @@ -3064,6 +3139,7 @@ 00004D0B73767647AD550000 /* SDL_asyncio_generic.c in Sources */, 0000A03C0F32C43816F40000 /* SDL_asyncio_windows_ioring.c in Sources */, 0000A877C7DB9FA935FC0000 /* SDL_uikitpen.m in Sources */, + 63124A422E5C357500A53610 /* SDL_hidapi_zuiki.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3085,8 +3161,8 @@ CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; DEPLOYMENT_POSTPROCESSING = YES; - DYLIB_COMPATIBILITY_VERSION = 201.0.0; - DYLIB_CURRENT_VERSION = 201.20.0; + DYLIB_COMPATIBILITY_VERSION = 401.0.0; + DYLIB_CURRENT_VERSION = 401.2.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_ALTIVEC_EXTENSIONS = YES; @@ -3121,7 +3197,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 3.2.20; + MARKETING_VERSION = 3.4.2; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3; PRODUCT_NAME = SDL3; @@ -3129,7 +3205,7 @@ SUPPORTED_PLATFORMS = "xrsimulator xros macosx iphonesimulator iphoneos appletvsimulator appletvos"; SUPPORTS_MACCATALYST = YES; TVOS_DEPLOYMENT_TARGET = 11.0; - XROS_DEPLOYMENT_TARGET = 1.0; + XROS_DEPLOYMENT_TARGET = 1.3; }; name = Release; }; @@ -3138,6 +3214,8 @@ baseConfigurationReference = F3F7BE3B2CBD79D200C984AF /* config.xcconfig */; buildSettings = { CLANG_LINK_OBJC_RUNTIME = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; OTHER_LDFLAGS = "-liconv"; SUPPORTS_MACCATALYST = YES; }; @@ -3149,8 +3227,8 @@ ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; - DYLIB_COMPATIBILITY_VERSION = 201.0.0; - DYLIB_CURRENT_VERSION = 201.20.0; + DYLIB_COMPATIBILITY_VERSION = 401.0.0; + DYLIB_CURRENT_VERSION = 401.2.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -3182,7 +3260,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 3.2.20; + MARKETING_VERSION = 3.4.2; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3; @@ -3191,7 +3269,7 @@ SUPPORTED_PLATFORMS = "xrsimulator xros macosx iphonesimulator iphoneos appletvsimulator appletvos"; SUPPORTS_MACCATALYST = YES; TVOS_DEPLOYMENT_TARGET = 11.0; - XROS_DEPLOYMENT_TARGET = 1.0; + XROS_DEPLOYMENT_TARGET = 1.3; }; name = Debug; }; @@ -3200,6 +3278,8 @@ baseConfigurationReference = F3F7BE3B2CBD79D200C984AF /* config.xcconfig */; buildSettings = { CLANG_LINK_OBJC_RUNTIME = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; OTHER_LDFLAGS = "-liconv"; SUPPORTS_MACCATALYST = YES; }; diff --git a/libs/SDL3/Xcode/SDL/pkg-support/SDL.info b/libs/SDL3/Xcode/SDL/pkg-support/SDL.info index 475a7cc..a82bea2 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.20 +Title SDL 3.4.2 Version 1 Description SDL Library for macOS (http://www.libsdl.org) DefaultLocation /Library/Frameworks diff --git a/libs/SDL3/Xcode/SDL/pkg-support/resources/CMake/SDL3Config.cmake b/libs/SDL3/Xcode/SDL/pkg-support/resources/CMake/SDL3Config.cmake index 784d27d..7961665 100644 --- a/libs/SDL3/Xcode/SDL/pkg-support/resources/CMake/SDL3Config.cmake +++ b/libs/SDL3/Xcode/SDL/pkg-support/resources/CMake/SDL3Config.cmake @@ -1,8 +1,9 @@ # SDL3 CMake configuration file: -# This file is meant to be placed in Resources/CMake of a SDL3 framework +# This file is meant to be placed in Resources/CMake of a SDL3 framework for macOS, +# or in the CMake directory of a SDL3 framework for iOS / tvOS / visionOS. # INTERFACE_LINK_OPTIONS needs CMake 3.12 -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.12...4.0) include(FeatureSummary) set_package_properties(SDL3 PROPERTIES @@ -31,16 +32,31 @@ endmacro() set(SDL3_FOUND TRUE) -# Compute the installation prefix relative to this file. -set(_sdl3_framework_path "${CMAKE_CURRENT_LIST_DIR}") # > /SDL3.framework/Resources/CMake/ -get_filename_component(_sdl3_framework_path "${_sdl3_framework_path}" REALPATH) # > /SDL3.framework/Versions/Current/Resources/CMake -get_filename_component(_sdl3_framework_path "${_sdl3_framework_path}" REALPATH) # > /SDL3.framework/Versions/A/Resources/CMake/ -get_filename_component(_sdl3_framework_path "${_sdl3_framework_path}" PATH) # > /SDL3.framework/Versions/A/Resources/ -get_filename_component(_sdl3_framework_path "${_sdl3_framework_path}" PATH) # > /SDL3.framework/Versions/A/ -get_filename_component(_sdl3_framework_path "${_sdl3_framework_path}" PATH) # > /SDL3.framework/Versions/ -get_filename_component(_sdl3_framework_path "${_sdl3_framework_path}" PATH) # > /SDL3.framework/ -get_filename_component(_sdl3_framework_parent_path "${_sdl3_framework_path}" PATH) # > / +# Compute the installation prefix relative to this file: +# search upwards for the .framework directory +set(_current_path "${CMAKE_CURRENT_LIST_DIR}") +get_filename_component(_current_path "${_current_path}" REALPATH) +set(_sdl3_framework_path "") +while(NOT _sdl3_framework_path) + if(IS_DIRECTORY "${_current_path}" AND "${_current_path}" MATCHES "/SDL3\\.framework$") + set(_sdl3_framework_path "${_current_path}") + break() + endif() + get_filename_component(_next_current_path "${_current_path}" DIRECTORY) + if("${_current_path}" STREQUAL "${_next_current_path}") + break() + endif() + set(_current_path "${_next_current_path}") +endwhile() +unset(_current_path) +unset(_next_current_path) + +if(NOT _sdl3_framework_path) + message(FATAL_ERROR "Could not find SDL3.framework root from ${CMAKE_CURRENT_LIST_DIR}") +endif() + +get_filename_component(_sdl3_framework_parent_path "${_sdl3_framework_path}" PATH) # All targets are created, even when some might not be requested though COMPONENTS. # This is done for compatibility with CMake generated SDL3-target.cmake files. diff --git a/libs/SDL3/Xcode/SDL/pkg-support/resources/CMake/SDL3ConfigVersion.cmake b/libs/SDL3/Xcode/SDL/pkg-support/resources/CMake/SDL3ConfigVersion.cmake index 74329c4..6f7589b 100644 --- a/libs/SDL3/Xcode/SDL/pkg-support/resources/CMake/SDL3ConfigVersion.cmake +++ b/libs/SDL3/Xcode/SDL/pkg-support/resources/CMake/SDL3ConfigVersion.cmake @@ -1,16 +1,26 @@ # based on the files generated by CMake's write_basic_package_version_file # SDL CMake version configuration file: -# This file is meant to be placed in Resources/CMake of a SDL3 framework +# This file is meant to be placed in Resources/CMake of a SDL3 framework for macOS, +# or in the CMake directory of a SDL3 framework for iOS / tvOS / visionOS. -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.12...4.0) -if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../Headers/SDL_version.h") - message(AUTHOR_WARNING "Could not find SDL_version.h. This script is meant to be placed in the Resources/CMake directory of SDL2.framework") +# Find SDL_version.h +set(_sdl_version_h_path "") +if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../Headers/SDL_version.h") + set(_sdl_version_h_path "${CMAKE_CURRENT_LIST_DIR}/../../Headers/SDL_version.h") +elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Headers/SDL_version.h") + set(_sdl_version_h_path "${CMAKE_CURRENT_LIST_DIR}/../Headers/SDL_version.h") +endif() + +if(NOT _sdl_version_h_path) + message(AUTHOR_WARNING "Could not find SDL_version.h. This script is meant to be placed in the Resources/CMake directory or the CMake directory of SDL3.framework.") + set(PACKAGE_VERSION_UNSUITABLE TRUE) return() endif() -file(READ "${CMAKE_CURRENT_LIST_DIR}/../../Headers/SDL_version.h" _sdl_version_h) +file(READ "${_sdl_version_h_path}" _sdl_version_h) string(REGEX MATCH "#define[ \t]+SDL_MAJOR_VERSION[ \t]+([0-9]+)" _sdl_major_re "${_sdl_version_h}") set(_sdl_major "${CMAKE_MATCH_1}") string(REGEX MATCH "#define[ \t]+SDL_MINOR_VERSION[ \t]+([0-9]+)" _sdl_minor_re "${_sdl_version_h}") @@ -21,9 +31,12 @@ if(_sdl_major_re AND _sdl_minor_re AND _sdl_micro_re) set(PACKAGE_VERSION "${_sdl_major}.${_sdl_minor}.${_sdl_micro}") else() message(AUTHOR_WARNING "Could not extract version from SDL_version.h.") + set(PACKAGE_VERSION_UNSUITABLE TRUE) return() endif() +unset(_sdl_version_h) +unset(_sdl_version_h_path) unset(_sdl_major_re) unset(_sdl_major) unset(_sdl_minor_re) diff --git a/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3ConfigVersion.cmake b/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3ConfigVersion.cmake index 6c833a7..86979ce 100644 --- a/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3ConfigVersion.cmake +++ b/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3ConfigVersion.cmake @@ -16,6 +16,7 @@ set(_sdl3_version_h "${_sdl3_framework}/Headers/SDL_version.h") if(NOT EXISTS "${_sdl3_version_h}") message(AUTHOR_WARNING "Cannot not find ${_sdl3_framework}. This script is meant to be placed in share/cmake/SDL3, next to SDL3.xcframework") + set(PACKAGE_VERSION_UNSUITABLE TRUE) return() endif() @@ -36,6 +37,7 @@ if(_sdl_major_re AND _sdl_minor_re AND _sdl_micro_re) set(PACKAGE_VERSION "${_sdl_major}.${_sdl_minor}.${_sdl_micro}") else() message(AUTHOR_WARNING "Could not extract version from SDL_version.h.") + set(PACKAGE_VERSION_UNSUITABLE TRUE) return() endif() diff --git a/libs/SDL3/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj b/libs/SDL3/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj index 3a2e59d..ce64b09 100644 --- a/libs/SDL3/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj +++ b/libs/SDL3/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj @@ -19,7 +19,6 @@ F35E56E72983133F00A43A5F /* PBXTargetDependency */, DB0F490517CA5249008798C5 /* PBXTargetDependency */, DB0F490717CA5249008798C5 /* PBXTargetDependency */, - DB166E9816A1D7CF00A1396C /* PBXTargetDependency */, DB166E9616A1D7CD00A1396C /* PBXTargetDependency */, DB166E6C16A1D72000A1396C /* PBXTargetDependency */, DB166E5616A1D6B800A1396C /* PBXTargetDependency */, @@ -88,7 +87,6 @@ 00794EF009D23739003FC8A1 /* utf8.txt in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E6309D20839003FC8A1 /* utf8.txt */; }; 00794EF709D237DE003FC8A1 /* moose.dat in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5E09D20839003FC8A1 /* moose.dat */; }; 453774A5120915E3002F0F45 /* testshape.c in Sources */ = {isa = PBXBuildFile; fileRef = 453774A4120915E3002F0F45 /* testshape.c */; }; - 66E88E8B203B778F0004D44E /* testyuv_cvt.c in Sources */ = {isa = PBXBuildFile; fileRef = 66E88E8A203B778F0004D44E /* testyuv_cvt.c */; }; A1A8594E2BC72FC20045DD6C /* testautomation_properties.c in Sources */ = {isa = PBXBuildFile; fileRef = A1A859482BC72FC20045DD6C /* testautomation_properties.c */; }; A1A859502BC72FC20045DD6C /* testautomation_subsystems.c in Sources */ = {isa = PBXBuildFile; fileRef = A1A859492BC72FC20045DD6C /* testautomation_subsystems.c */; }; A1A859522BC72FC20045DD6C /* testautomation_log.c in Sources */ = {isa = PBXBuildFile; fileRef = A1A8594A2BC72FC20045DD6C /* testautomation_log.c */; }; @@ -117,23 +115,21 @@ DB166D9F16A1D1A500A1396C /* SDL_test_log.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166D9016A1D1A500A1396C /* SDL_test_log.c */; }; DB166DA016A1D1A500A1396C /* SDL_test_md5.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166D9116A1D1A500A1396C /* SDL_test_md5.c */; }; DB166DD716A1D37800A1396C /* testmessage.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CBD16A1C74100A1396C /* testmessage.c */; }; - DB166DDB16A1D42F00A1396C /* icon.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; }; + DB166DDB16A1D42F00A1396C /* icon.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.png */; }; DB166DF016A1D52500A1396C /* testrelative.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CBF16A1C74100A1396C /* testrelative.c */; }; DB166E0716A1D59400A1396C /* testrendercopyex.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CC016A1C74100A1396C /* testrendercopyex.c */; }; DB166E1E16A1D5C300A1396C /* testrendertarget.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CC116A1C74100A1396C /* testrendertarget.c */; }; - DB166E2216A1D5EC00A1396C /* sample.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E6109D20839003FC8A1 /* sample.bmp */; }; - DB166E2316A1D60B00A1396C /* icon.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; }; - DB166E2516A1D61900A1396C /* icon.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; }; - DB166E2616A1D61900A1396C /* sample.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E6109D20839003FC8A1 /* sample.bmp */; }; + DB166E2216A1D5EC00A1396C /* sample.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E6109D20839003FC8A1 /* sample.png */; }; + DB166E2316A1D60B00A1396C /* icon.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.png */; }; + DB166E2516A1D61900A1396C /* icon.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.png */; }; + DB166E2616A1D61900A1396C /* sample.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E6109D20839003FC8A1 /* sample.png */; }; DB166E3C16A1D66500A1396C /* testrumble.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CC216A1C74100A1396C /* testrumble.c */; }; - DB166E4D16A1D69000A1396C /* icon.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; }; - DB166E4E16A1D69000A1396C /* sample.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E6109D20839003FC8A1 /* sample.bmp */; }; + DB166E4D16A1D69000A1396C /* icon.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.png */; }; + DB166E4E16A1D69000A1396C /* sample.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E6109D20839003FC8A1 /* sample.png */; }; DB166E5416A1D6A300A1396C /* testscale.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CC316A1C74100A1396C /* testscale.c */; }; DB166E6A16A1D70C00A1396C /* testshader.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CC416A1C74100A1396C /* testshader.c */; }; DB166E9316A1D7BC00A1396C /* testspriteminimal.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CC516A1C74100A1396C /* testspriteminimal.c */; }; - DB166E9416A1D7C700A1396C /* teststreaming.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CC616A1C74100A1396C /* teststreaming.c */; }; - DB166E9A16A1D7F700A1396C /* moose.dat in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5E09D20839003FC8A1 /* moose.dat */; }; - DB166E9C16A1D80900A1396C /* icon.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; }; + DB166E9C16A1D80900A1396C /* icon.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.png */; }; DB445EFB18184BB600B306B0 /* testdropfile.c in Sources */ = {isa = PBXBuildFile; fileRef = DB445EFA18184BB600B306B0 /* testdropfile.c */; }; DB89958418A19B130092407C /* testhotplug.c in Sources */ = {isa = PBXBuildFile; fileRef = DB89958318A19B130092407C /* testhotplug.c */; }; F35E56CF2983130F00A43A5F /* testautomation_main.c in Sources */ = {isa = PBXBuildFile; fileRef = F35E56B62983130A00A43A5F /* testautomation_main.c */; }; @@ -162,6 +158,7 @@ F36C34232C0F85DB00991150 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F36C342D2C0F869B00991150 /* testcamera.c in Sources */ = {isa = PBXBuildFile; fileRef = F36C342C2C0F869B00991150 /* testcamera.c */; }; F36C342E2C0F869B00991150 /* testcamera.c in Sources */ = {isa = PBXBuildFile; fileRef = F36C342C2C0F869B00991150 /* testcamera.c */; }; + F38908B72E81276900CE01D5 /* testautomation_blit.c in Sources */ = {isa = PBXBuildFile; fileRef = F38908B42E81276900CE01D5 /* testautomation_blit.c */; }; F399C64E2A78929400C86979 /* gamepadutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F399C6492A78929400C86979 /* gamepadutils.c */; }; F399C64F2A78929400C86979 /* gamepadutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F399C6492A78929400C86979 /* gamepadutils.c */; }; F399C6512A7892D800C86979 /* testautomation_intrinsics.c in Sources */ = {isa = PBXBuildFile; fileRef = F399C6502A7892D800C86979 /* testautomation_intrinsics.c */; }; @@ -182,13 +179,12 @@ F3C17C8228E4112900E1A26D /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; F3C17C8328E4124400E1A26D /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; F3C17C8428E4126400E1A26D /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; - F3C17C8528E4127D00E1A26D /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; F3C17CEB28E4177600E1A26D /* testgeometry.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17CD628E416AC00E1A26D /* testgeometry.c */; }; F3C17CEC28E417EB00E1A26D /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; F3C17D3928E424B800E1A26D /* sample.wav in Resources */ = {isa = PBXBuildFile; fileRef = 00794E6209D20839003FC8A1 /* sample.wav */; }; - F3C17D3B28E4252900E1A26D /* icon.bmp in Resources */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; }; + F3C17D3B28E4252900E1A26D /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.png */; }; F3C2CAC62C5C8BD6004D7998 /* unifont-15.1.05.hex in Resources */ = {isa = PBXBuildFile; fileRef = F3C2CAC52C5C8BD6004D7998 /* unifont-15.1.05.hex */; }; - F3C2CB072C5D3FB2004D7998 /* icon.bmp in Resources */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; }; + F3C2CB072C5D3FB2004D7998 /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.png */; }; F3CB56892A7895F800766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; F3CB568A2A7895F800766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F3CB568C2A7896BF00766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; @@ -267,8 +263,6 @@ F3CB56FB2A78983200766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F3CB56FD2A78983C00766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; F3CB56FE2A78983C00766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - F3CB57002A78984300766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; - F3CB57012A78984300766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F3CB57032A78984A00766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; F3CB57042A78984A00766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F3CB57062A78985400766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; @@ -279,6 +273,12 @@ F3CB570D2A78986000766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F3CB570F2A78986700766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; F3CB57102A78986700766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + F3DB65DF2E9DA90000568044 /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; + F3DB65E12E9DA90000568044 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; + F3DB65E52E9DA90000568044 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + F3DB65EE2E9DA95D00568044 /* testyuv.png in Resources */ = {isa = PBXBuildFile; fileRef = F3DB65ED2E9DA95D00568044 /* testyuv.png */; }; + F3DB65F12E9DA98E00568044 /* testyuv.c in Sources */ = {isa = PBXBuildFile; fileRef = F3DB65EF2E9DA98E00568044 /* testyuv.c */; }; + F3DB65F22E9DA9B400568044 /* testyuv_cvt.c in Sources */ = {isa = PBXBuildFile; fileRef = 66E88E8A203B778F0004D44E /* testyuv_cvt.c */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -569,13 +569,6 @@ remoteGlobalIDString = DB166E6D16A1D78400A1396C; remoteInfo = testspriteminimal; }; - DB166E9716A1D7CF00A1396C /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = DB166E8016A1D78C00A1396C; - remoteInfo = teststreaming; - }; F35E56E62983133F00A43A5F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; @@ -643,7 +636,7 @@ dstPath = ""; dstSubfolderSpec = 7; files = ( - DB166DDB16A1D42F00A1396C /* icon.bmp in CopyFiles */, + DB166DDB16A1D42F00A1396C /* icon.png in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -653,8 +646,8 @@ dstPath = ""; dstSubfolderSpec = 7; files = ( - DB166E2316A1D60B00A1396C /* icon.bmp in CopyFiles */, - DB166E2216A1D5EC00A1396C /* sample.bmp in CopyFiles */, + DB166E2316A1D60B00A1396C /* icon.png in CopyFiles */, + DB166E2216A1D5EC00A1396C /* sample.png in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -664,8 +657,8 @@ dstPath = ""; dstSubfolderSpec = 7; files = ( - DB166E2516A1D61900A1396C /* icon.bmp in CopyFiles */, - DB166E2616A1D61900A1396C /* sample.bmp in CopyFiles */, + DB166E2516A1D61900A1396C /* icon.png in CopyFiles */, + DB166E2616A1D61900A1396C /* sample.png in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -675,18 +668,8 @@ dstPath = ""; dstSubfolderSpec = 7; files = ( - DB166E4D16A1D69000A1396C /* icon.bmp in CopyFiles */, - DB166E4E16A1D69000A1396C /* sample.bmp in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DB166E9916A1D7EE00A1396C /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 7; - files = ( - DB166E9A16A1D7F700A1396C /* moose.dat in CopyFiles */, + DB166E4D16A1D69000A1396C /* icon.png in CopyFiles */, + DB166E4E16A1D69000A1396C /* sample.png in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -696,7 +679,7 @@ dstPath = ""; dstSubfolderSpec = 7; files = ( - DB166E9C16A1D80900A1396C /* icon.bmp in CopyFiles */, + DB166E9C16A1D80900A1396C /* icon.png in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1160,17 +1143,6 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; - F3CB57022A78984300766177 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - F3CB57012A78984300766177 /* SDL3.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; F3CB57052A78984A00766177 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1226,6 +1198,17 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + F3DB65E42E9DA90000568044 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + F3DB65E52E9DA90000568044 /* SDL3.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -1269,10 +1252,9 @@ 002F346A09CA204F00EBEB88 /* testplatform.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testplatform.app; sourceTree = BUILT_PRODUCTS_DIR; }; 002F346F09CA20A600EBEB88 /* testplatform.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = testplatform.c; sourceTree = ""; }; 003FA63A093FFD41000C53B3 /* SDL.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SDL.xcodeproj; path = ../SDL/SDL.xcodeproj; sourceTree = SOURCE_ROOT; }; - 00794E5D09D20839003FC8A1 /* icon.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = icon.bmp; sourceTree = ""; }; + 00794E5D09D20839003FC8A1 /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; 00794E5E09D20839003FC8A1 /* moose.dat */ = {isa = PBXFileReference; lastKnownFileType = file; path = moose.dat; sourceTree = ""; }; - 00794E5F09D20839003FC8A1 /* picture.xbm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = picture.xbm; sourceTree = ""; }; - 00794E6109D20839003FC8A1 /* sample.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = sample.bmp; sourceTree = ""; }; + 00794E6109D20839003FC8A1 /* sample.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = sample.png; sourceTree = ""; }; 00794E6209D20839003FC8A1 /* sample.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = sample.wav; sourceTree = ""; }; 00794E6309D20839003FC8A1 /* utf8.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = utf8.txt; sourceTree = ""; }; 083E4872006D84C97F000001 /* loopwave.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = loopwave.c; sourceTree = ""; }; @@ -1318,7 +1300,6 @@ DB166CC316A1C74100A1396C /* testscale.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = testscale.c; sourceTree = ""; }; DB166CC416A1C74100A1396C /* testshader.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = testshader.c; sourceTree = ""; }; DB166CC516A1C74100A1396C /* testspriteminimal.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = testspriteminimal.c; sourceTree = ""; }; - DB166CC616A1C74100A1396C /* teststreaming.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = teststreaming.c; sourceTree = ""; }; DB166D7F16A1D12400A1396C /* libSDL3_test.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSDL3_test.a; sourceTree = BUILT_PRODUCTS_DIR; }; DB166D8416A1D1A500A1396C /* SDL_test_assert.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_test_assert.c; sourceTree = ""; }; DB166D8516A1D1A500A1396C /* SDL_test_common.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_test_common.c; sourceTree = ""; }; @@ -1337,7 +1318,6 @@ DB166E5216A1D69000A1396C /* testscale.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testscale.app; sourceTree = BUILT_PRODUCTS_DIR; }; DB166E6816A1D6F300A1396C /* testshader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testshader.app; sourceTree = BUILT_PRODUCTS_DIR; }; DB166E7E16A1D78400A1396C /* testspriteminimal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testspriteminimal.app; sourceTree = BUILT_PRODUCTS_DIR; }; - DB166E9116A1D78C00A1396C /* teststreaming.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = teststreaming.app; sourceTree = BUILT_PRODUCTS_DIR; }; DB445EF818184B7000B306B0 /* testdropfile.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testdropfile.app; sourceTree = BUILT_PRODUCTS_DIR; }; DB445EFA18184BB600B306B0 /* testdropfile.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testdropfile.c; sourceTree = ""; }; DB89957E18A19ABA0092407C /* testhotplug.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testhotplug.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1367,6 +1347,9 @@ F35E56CD2983130F00A43A5F /* testautomation_mouse.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_mouse.c; sourceTree = ""; }; F36C34272C0F85DB00991150 /* testcamera.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testcamera.app; sourceTree = BUILT_PRODUCTS_DIR; }; F36C342C2C0F869B00991150 /* testcamera.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testcamera.c; sourceTree = ""; }; + F38908B42E81276900CE01D5 /* testautomation_blit.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = testautomation_blit.c; sourceTree = ""; }; + F38908B52E81276900CE01D5 /* testautomation_images.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = testautomation_images.h; sourceTree = ""; }; + F38908B62E81276900CE01D5 /* testautomation_suites.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = testautomation_suites.h; sourceTree = ""; }; 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; }; @@ -1377,6 +1360,10 @@ F3C17CD628E416AC00E1A26D /* testgeometry.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testgeometry.c; sourceTree = ""; }; F3C17CDC28E416CF00E1A26D /* testgeometry.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testgeometry.app; sourceTree = BUILT_PRODUCTS_DIR; }; F3C2CAC52C5C8BD6004D7998 /* unifont-15.1.05.hex */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "unifont-15.1.05.hex"; sourceTree = ""; }; + F3DB65E92E9DA90000568044 /* testyuv.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testyuv.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F3DB65ED2E9DA95D00568044 /* testyuv.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = testyuv.png; sourceTree = ""; }; + F3DB65EF2E9DA98E00568044 /* testyuv.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = testyuv.c; sourceTree = ""; }; + F3DB65F02E9DA98E00568044 /* testyuv_cvt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = testyuv_cvt.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1708,14 +1695,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DB166E8316A1D78C00A1396C /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - F3CB57002A78984300766177 /* SDL3.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; DB445EE918184B7000B306B0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1764,6 +1743,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F3DB65E02E9DA90000568044 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F3DB65E12E9DA90000568044 /* SDL3.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -1778,10 +1765,10 @@ 00794E4609D207B4003FC8A1 /* Resources */ = { isa = PBXGroup; children = ( - 00794E5D09D20839003FC8A1 /* icon.bmp */, + F3DB65ED2E9DA95D00568044 /* testyuv.png */, + 00794E5D09D20839003FC8A1 /* icon.png */, 00794E5E09D20839003FC8A1 /* moose.dat */, - 00794E5F09D20839003FC8A1 /* picture.xbm */, - 00794E6109D20839003FC8A1 /* sample.bmp */, + 00794E6109D20839003FC8A1 /* sample.png */, 00794E6209D20839003FC8A1 /* sample.wav */, F3C2CAC52C5C8BD6004D7998 /* unifont-15.1.05.hex */, 00794E6309D20839003FC8A1 /* utf8.txt */, @@ -1815,10 +1802,12 @@ 001795B01074222D00F5D044 /* testaudioinfo.c */, F35E56CC2983130F00A43A5F /* testautomation.c */, F35E56C42983130D00A43A5F /* testautomation_audio.c */, + F38908B42E81276900CE01D5 /* testautomation_blit.c */, F35E56BC2983130B00A43A5F /* testautomation_clipboard.c */, F35E56BB2983130B00A43A5F /* testautomation_events.c */, F35E56C92983130E00A43A5F /* testautomation_guid.c */, F35E56B72983130A00A43A5F /* testautomation_hints.c */, + F38908B52E81276900CE01D5 /* testautomation_images.h */, F35E56BF2983130C00A43A5F /* testautomation_images.c */, F399C6502A7892D800C86979 /* testautomation_intrinsics.c */, F35E56B92983130B00A43A5F /* testautomation_iostream.c */, @@ -1836,6 +1825,7 @@ F35E56C82983130E00A43A5F /* testautomation_sdltest.c */, F35E56BE2983130C00A43A5F /* testautomation_stdlib.c */, A1A859492BC72FC20045DD6C /* testautomation_subsystems.c */, + F38908B62E81276900CE01D5 /* testautomation_suites.h */, F35E56CB2983130F00A43A5F /* testautomation_surface.c */, A1A8594B2BC72FC20045DD6C /* testautomation_time.c */, F35E56BD2983130B00A43A5F /* testautomation_timer.c */, @@ -1880,12 +1870,13 @@ 453774A4120915E3002F0F45 /* testshape.c */, 0017991910743F5300F5D044 /* testsprite.c */, DB166CC516A1C74100A1396C /* testspriteminimal.c */, - DB166CC616A1C74100A1396C /* teststreaming.c */, 092D6D58FFB311A97F000001 /* testthread.c */, 083E4880006D86A17F000001 /* testtimer.c */, F3C17C7328E40ADE00E1A26D /* testutils.c */, 083E4882006D86A17F000001 /* testver.c */, 0017993B10743FEF00F5D044 /* testwm.c */, + F3DB65EF2E9DA98E00568044 /* testyuv.c */, + F3DB65F02E9DA98E00568044 /* testyuv_cvt.h */, 66E88E8A203B778F0004D44E /* testyuv_cvt.c */, 083E4887006D86A17F000001 /* torturethread.c */, ); @@ -1935,7 +1926,6 @@ DB166E5216A1D69000A1396C /* testscale.app */, DB166E6816A1D6F300A1396C /* testshader.app */, DB166E7E16A1D78400A1396C /* testspriteminimal.app */, - DB166E9116A1D78C00A1396C /* teststreaming.app */, DB0F48EC17CA51E5008798C5 /* testdrawchessboard.app */, DB0F490117CA5212008798C5 /* testfilesystem.app */, DB89957E18A19ABA0092407C /* testhotplug.app */, @@ -1944,6 +1934,7 @@ F35E56AA298312CB00A43A5F /* testautomation.app */, F36C34272C0F85DB00991150 /* testcamera.app */, F3B7FD6A2D73FC630086D1D0 /* testpen.app */, + F3DB65E92E9DA90000568044 /* testyuv.app */, ); name = Products; sourceTree = ""; @@ -2000,7 +1991,6 @@ dependencies = ( ); name = testatomic; - productName = testalpha; productReference = 0017958C10741F7900F5D044 /* testatomic.app */; productType = "com.apple.product-type.application"; }; @@ -2017,7 +2007,6 @@ dependencies = ( ); name = testaudioinfo; - productName = testalpha; productReference = 001795AD107421BF00F5D044 /* testaudioinfo.app */; productType = "com.apple.product-type.application"; }; @@ -2034,7 +2023,6 @@ dependencies = ( ); name = testgl; - productName = testalpha; productReference = 0017972110742F3200F5D044 /* testgl.app */; productType = "com.apple.product-type.application"; }; @@ -2051,7 +2039,6 @@ dependencies = ( ); name = testhaptic; - productName = testalpha; productReference = 00179748107430D600F5D044 /* testhaptic.app */; productType = "com.apple.product-type.application"; }; @@ -2068,7 +2055,6 @@ dependencies = ( ); name = testdraw; - productName = testalpha; productReference = 0017976E107431B300F5D044 /* testdraw.app */; productType = "com.apple.product-type.application"; }; @@ -2086,7 +2072,6 @@ dependencies = ( ); name = testime; - productName = testalpha; productReference = 0017978E107432AE00F5D044 /* testime.app */; productType = "com.apple.product-type.application"; }; @@ -2103,7 +2088,6 @@ dependencies = ( ); name = testintersections; - productName = testalpha; productReference = 001797AE1074334C00F5D044 /* testintersections.app */; productType = "com.apple.product-type.application"; }; @@ -2120,7 +2104,6 @@ dependencies = ( ); name = testloadso; - productName = testalpha; productReference = 001797D0107433C600F5D044 /* testloadso.app */; productType = "com.apple.product-type.application"; }; @@ -2138,7 +2121,6 @@ dependencies = ( ); name = testmultiaudio; - productName = testalpha; productReference = 001798121074355200F5D044 /* testmultiaudio.app */; productType = "com.apple.product-type.application"; }; @@ -2156,7 +2138,6 @@ dependencies = ( ); name = testnative; - productName = testalpha; productReference = 001798941074392D00F5D044 /* testnative.app */; productType = "com.apple.product-type.application"; }; @@ -2173,7 +2154,6 @@ dependencies = ( ); name = testpower; - productName = testalpha; productReference = 001798B5107439DF00F5D044 /* testpower.app */; productType = "com.apple.product-type.application"; }; @@ -2190,7 +2170,6 @@ dependencies = ( ); name = testresample; - productName = testalpha; productReference = 001798F210743BEC00F5D044 /* testresample.app */; productType = "com.apple.product-type.application"; }; @@ -2208,7 +2187,6 @@ dependencies = ( ); name = testsprite; - productName = testalpha; productReference = 0017991610743F1000F5D044 /* testsprite.app */; productType = "com.apple.product-type.application"; }; @@ -2225,7 +2203,6 @@ dependencies = ( ); name = testwm; - productName = testalpha; productReference = 0017993810743FB700F5D044 /* testwm.app */; productType = "com.apple.product-type.application"; }; @@ -2242,7 +2219,6 @@ dependencies = ( ); name = testfile; - productName = testalpha; productReference = 002F341209CA1BFF00EBEB88 /* testfile.app */; productType = "com.apple.product-type.application"; }; @@ -2260,7 +2236,6 @@ dependencies = ( ); name = testiconv; - productName = testalpha; productReference = 002F343109CA1F0300EBEB88 /* testiconv.app */; productType = "com.apple.product-type.application"; }; @@ -2278,7 +2253,6 @@ dependencies = ( ); name = testoverlay; - productName = testalpha; productReference = 002F344D09CA1FB300EBEB88 /* testoverlay.app */; productType = "com.apple.product-type.application"; }; @@ -2295,7 +2269,6 @@ dependencies = ( ); name = testplatform; - productName = testalpha; productReference = 002F346A09CA204F00EBEB88 /* testplatform.app */; productType = "com.apple.product-type.application"; }; @@ -2518,7 +2491,6 @@ dependencies = ( ); name = testdrawchessboard; - productName = testalpha; productReference = DB0F48EC17CA51E5008798C5 /* testdrawchessboard.app */; productType = "com.apple.product-type.application"; }; @@ -2535,7 +2507,6 @@ dependencies = ( ); name = testfilesystem; - productName = testalpha; productReference = DB0F490117CA5212008798C5 /* testfilesystem.app */; productType = "com.apple.product-type.application"; }; @@ -2569,7 +2540,6 @@ dependencies = ( ); name = testmessage; - productName = testalpha; productReference = DB166DD516A1D36A00A1396C /* testmessage.app */; productType = "com.apple.product-type.application"; }; @@ -2586,7 +2556,6 @@ dependencies = ( ); name = testrelative; - productName = testalpha; productReference = DB166DEE16A1D50C00A1396C /* testrelative.app */; productType = "com.apple.product-type.application"; }; @@ -2604,7 +2573,6 @@ dependencies = ( ); name = testrendercopyex; - productName = testalpha; productReference = DB166E0516A1D57C00A1396C /* testrendercopyex.app */; productType = "com.apple.product-type.application"; }; @@ -2622,7 +2590,6 @@ dependencies = ( ); name = testrendertarget; - productName = testalpha; productReference = DB166E1C16A1D5AD00A1396C /* testrendertarget.app */; productType = "com.apple.product-type.application"; }; @@ -2639,7 +2606,6 @@ dependencies = ( ); name = testrumble; - productName = testalpha; productReference = DB166E3816A1D64D00A1396C /* testrumble.app */; productType = "com.apple.product-type.application"; }; @@ -2657,7 +2623,6 @@ dependencies = ( ); name = testscale; - productName = testalpha; productReference = DB166E5216A1D69000A1396C /* testscale.app */; productType = "com.apple.product-type.application"; }; @@ -2696,24 +2661,6 @@ productReference = DB166E7E16A1D78400A1396C /* testspriteminimal.app */; productType = "com.apple.product-type.application"; }; - DB166E8016A1D78C00A1396C /* teststreaming */ = { - isa = PBXNativeTarget; - buildConfigurationList = DB166E8E16A1D78C00A1396C /* Build configuration list for PBXNativeTarget "teststreaming" */; - buildPhases = ( - DB166E8116A1D78C00A1396C /* Sources */, - DB166E8316A1D78C00A1396C /* Frameworks */, - DB166E9916A1D7EE00A1396C /* CopyFiles */, - F3CB57022A78984300766177 /* Embed Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = teststreaming; - productName = teststreaming; - productReference = DB166E9116A1D78C00A1396C /* teststreaming.app */; - productType = "com.apple.product-type.application"; - }; DB445EE618184B7000B306B0 /* testdropfile */ = { isa = PBXNativeTarget; buildConfigurationList = DB445EF518184B7000B306B0 /* Build configuration list for PBXNativeTarget "testdropfile" */; @@ -2744,7 +2691,6 @@ dependencies = ( ); name = testhotplug; - productName = testalpha; productReference = DB89957E18A19ABA0092407C /* testhotplug.app */; productType = "com.apple.product-type.application"; }; @@ -2761,7 +2707,6 @@ dependencies = ( ); name = testautomation; - productName = testalpha; productReference = F35E56AA298312CB00A43A5F /* testautomation.app */; productType = "com.apple.product-type.application"; }; @@ -2778,7 +2723,6 @@ dependencies = ( ); name = testcamera; - productName = testalpha; productReference = F36C34272C0F85DB00991150 /* testcamera.app */; productType = "com.apple.product-type.application"; }; @@ -2795,7 +2739,6 @@ dependencies = ( ); name = testpen; - productName = testalpha; productReference = F3B7FD6A2D73FC630086D1D0 /* testpen.app */; productType = "com.apple.product-type.application"; }; @@ -2816,6 +2759,23 @@ productReference = F3C17CDC28E416CF00E1A26D /* testgeometry.app */; productType = "com.apple.product-type.application"; }; + F3DB65DC2E9DA90000568044 /* testyuv */ = { + isa = PBXNativeTarget; + buildConfigurationList = F3DB65E62E9DA90000568044 /* Build configuration list for PBXNativeTarget "testyuv" */; + buildPhases = ( + F3DB65DD2E9DA90000568044 /* Sources */, + F3DB65E02E9DA90000568044 /* Frameworks */, + F3DB65E22E9DA90000568044 /* Resources */, + F3DB65E42E9DA90000568044 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = testyuv; + productReference = F3DB65E92E9DA90000568044 /* testyuv.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -2945,9 +2905,6 @@ DB166E6D16A1D78400A1396C = { ProvisioningStyle = Automatic; }; - DB166E8016A1D78C00A1396C = { - ProvisioningStyle = Automatic; - }; DB445EE618184B7000B306B0 = { ProvisioningStyle = Automatic; }; @@ -3028,11 +2985,11 @@ 4537749112091504002F0F45 /* testshape */, 001798FE10743F1000F5D044 /* testsprite */, DB166E6D16A1D78400A1396C /* testspriteminimal */, - DB166E8016A1D78C00A1396C /* teststreaming */, BEC567230761D90400A33029 /* testthread */, BEC5678D0761D90500A33029 /* testtimer */, BEC567A70761D90500A33029 /* testversion */, 0017992010743FB700F5D044 /* testwm */, + F3DB65DC2E9DA90000568044 /* testyuv */, BEC567EA0761D90600A33029 /* torturethread */, ); }; @@ -3061,7 +3018,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - F3C17D3B28E4252900E1A26D /* icon.bmp in Resources */, + F3C17D3B28E4252900E1A26D /* icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3069,11 +3026,19 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - F3C2CB072C5D3FB2004D7998 /* icon.bmp in Resources */, + F3C2CB072C5D3FB2004D7998 /* icon.png in Resources */, F3C2CAC62C5C8BD6004D7998 /* unifont-15.1.05.hex in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; + F3DB65E22E9DA90000568044 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F3DB65EE2E9DA95D00568044 /* testyuv.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -3216,7 +3181,6 @@ buildActionMask = 2147483647; files = ( 002F345409CA202000EBEB88 /* testoverlay.c in Sources */, - 66E88E8B203B778F0004D44E /* testyuv_cvt.c in Sources */, F3C17C7F28E4101000E1A26D /* testutils.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3432,15 +3396,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DB166E8116A1D78C00A1396C /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - DB166E9416A1D7C700A1396C /* teststreaming.c in Sources */, - F3C17C8528E4127D00E1A26D /* testutils.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; DB445EE718184B7000B306B0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -3478,6 +3433,7 @@ F35E56DE2983130F00A43A5F /* testautomation_joystick.c in Sources */, F35E56D82983130F00A43A5F /* testautomation_images.c in Sources */, F35E56DC2983130F00A43A5F /* testautomation_audio.c in Sources */, + F38908B72E81276900CE01D5 /* testautomation_blit.c in Sources */, F35E56D32983130F00A43A5F /* testautomation_math.c in Sources */, F35E56E02983130F00A43A5F /* testautomation_sdltest.c in Sources */, F35E56D42983130F00A43A5F /* testautomation_events.c in Sources */, @@ -3516,6 +3472,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F3DB65DD2E9DA90000568044 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F3DB65DF2E9DA90000568044 /* testutils.c in Sources */, + F3DB65F22E9DA9B400568044 /* testyuv_cvt.c in Sources */, + F3DB65F12E9DA98E00568044 /* testyuv.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -3725,11 +3691,6 @@ target = DB166E6D16A1D78400A1396C /* testspriteminimal */; targetProxy = DB166E9516A1D7CD00A1396C /* PBXContainerItemProxy */; }; - DB166E9816A1D7CF00A1396C /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = DB166E8016A1D78C00A1396C /* teststreaming */; - targetProxy = DB166E9716A1D7CF00A1396C /* PBXContainerItemProxy */; - }; F35E56E72983133F00A43A5F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F35E56A2298312CB00A43A5F /* testautomation */; @@ -4053,7 +4014,7 @@ GENERATE_INFOPLIST_FILE = YES; HEADER_SEARCH_PATHS = ../../include; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.9; @@ -4061,10 +4022,11 @@ ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; PRODUCT_BUNDLE_IDENTIFIER = "org.libsdl.$(PRODUCT_NAME)"; - SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos"; - SUPPORTS_MACCATALYST = NO; + SUPPORTED_PLATFORMS = "xrsimulator xros macosx iphonesimulator iphoneos appletvsimulator appletvos"; + SUPPORTS_MACCATALYST = YES; TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 9.0; + TVOS_DEPLOYMENT_TARGET = 11.0; + XROS_DEPLOYMENT_TARGET = 1.3; }; name = Debug; }; @@ -4227,17 +4189,18 @@ GENERATE_INFOPLIST_FILE = YES; HEADER_SEARCH_PATHS = ../../include; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.9; MARKETING_VERSION = 1.0; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; PRODUCT_BUNDLE_IDENTIFIER = "org.libsdl.$(PRODUCT_NAME)"; - SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos"; - SUPPORTS_MACCATALYST = NO; + SUPPORTED_PLATFORMS = "xrsimulator xros macosx iphonesimulator iphoneos appletvsimulator appletvos"; + SUPPORTS_MACCATALYST = YES; TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 9.0; + TVOS_DEPLOYMENT_TARGET = 11.0; + XROS_DEPLOYMENT_TARGET = 1.3; }; name = Release; }; @@ -4476,7 +4439,6 @@ ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; EXECUTABLE_PREFIX = lib; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos"; SUPPORTS_MACCATALYST = YES; }; name = Debug; @@ -4487,7 +4449,6 @@ ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; EXECUTABLE_PREFIX = lib; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos"; SUPPORTS_MACCATALYST = YES; }; name = Release; @@ -4652,26 +4613,6 @@ }; name = Release; }; - DB166E8F16A1D78C00A1396C /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = teststreaming; - PROVISIONING_PROFILE_SPECIFIER = ""; - }; - name = Debug; - }; - DB166E9016A1D78C00A1396C /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = teststreaming; - PROVISIONING_PROFILE_SPECIFIER = ""; - }; - name = Release; - }; DB445EF618184B7000B306B0 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -4756,6 +4697,25 @@ }; name = Release; }; + F37E49E22EB5250B00E508F7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + F37E49E32EB5250B00E508F7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; F3B7FD682D73FC630086D1D0 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -5186,15 +5146,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; - DB166E8E16A1D78C00A1396C /* Build configuration list for PBXNativeTarget "teststreaming" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - DB166E8F16A1D78C00A1396C /* Debug */, - DB166E9016A1D78C00A1396C /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; - }; DB445EF518184B7000B306B0 /* Build configuration list for PBXNativeTarget "testdropfile" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -5249,6 +5200,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; + F3DB65E62E9DA90000568044 /* Build configuration list for PBXNativeTarget "testyuv" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F37E49E22EB5250B00E508F7 /* Debug */, + F37E49E32EB5250B00E508F7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; /* End XCConfigurationList section */ }; rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; diff --git a/libs/SDL3/Xcode/SDLTest/config.xcconfig b/libs/SDL3/Xcode/SDLTest/config.xcconfig index 6447922..4a0292e 100644 --- a/libs/SDL3/Xcode/SDLTest/config.xcconfig +++ b/libs/SDL3/Xcode/SDLTest/config.xcconfig @@ -10,5 +10,7 @@ // This allows you to set DEVELOPMENT_TEAM for all targets, for example. #include? "build.xcconfig" +INFOPLIST_FILE = test-Info.plist + CONFIG_FRAMEWORK_LDFLAGS = -lSDL3_test diff --git a/libs/SDL3/Xcode/SDLTest/test-Info.plist b/libs/SDL3/Xcode/SDLTest/test-Info.plist new file mode 100644 index 0000000..83728a1 --- /dev/null +++ b/libs/SDL3/Xcode/SDLTest/test-Info.plist @@ -0,0 +1,20 @@ + + + + + UILaunchScreen + + UIColorName + + UIImageName + + + UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + + diff --git a/libs/SDL3/android-project/app/build.gradle b/libs/SDL3/android-project/app/build.gradle index f44cf26..b9d75b1 100644 --- a/libs/SDL3/android-project/app/build.gradle +++ b/libs/SDL3/android-project/app/build.gradle @@ -7,6 +7,7 @@ def buildWithCMake = project.hasProperty('BUILD_WITH_CMAKE'); android { namespace = "org.libsdl.app" compileSdkVersion 35 + ndkVersion = "28.2.13676358" defaultConfig { minSdkVersion 21 targetSdkVersion 35 @@ -19,7 +20,7 @@ android { abiFilters 'arm64-v8a' } cmake { - arguments "-DANDROID_PLATFORM=android-21", "-DANDROID_STL=c++_static", "-DAPP_SUPPORT_FLEXIBLE_PAGE_SIZES=true" + arguments "-DANDROID_PLATFORM=android-21", "-DANDROID_STL=c++_static" // abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' abiFilters 'arm64-v8a' } diff --git a/libs/SDL3/android-project/app/jni/Application.mk b/libs/SDL3/android-project/app/jni/Application.mk index 80b73fd..1f7c0c1 100644 --- a/libs/SDL3/android-project/app/jni/Application.mk +++ b/libs/SDL3/android-project/app/jni/Application.mk @@ -8,6 +8,3 @@ APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 # Min runtime API level 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 df0a4d0..0212837 100644 --- a/libs/SDL3/android-project/app/jni/src/CMakeLists.txt +++ b/libs/SDL3/android-project/app/jni/src/CMakeLists.txt @@ -3,32 +3,10 @@ cmake_minimum_required(VERSION 3.6) project(my_app) if(NOT TARGET SDL3::SDL3) - find_package(SDL3 CONFIG) -endif() - -if(NOT TARGET SDL3::SDL3) - find_library(SDL3_LIBRARY NAMES "SDL3") - find_path(SDL3_INCLUDE_DIR NAMES "SDL3/SDL.h") - add_library(SDL3::SDL3 UNKNOWN IMPORTED) - set_property(TARGET SDL3::SDL3 PROPERTY IMPORTED_LOCATION "${SDL3_LIBRARY}") - set_property(TARGET SDL3::SDL3 PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${SDL3_INCLUDE_DIR}") -endif() - -if(NOT TARGET SDL3::SDL3) - message(FATAL_ERROR "Cannot find SDL3. - -Possible ways to fix this: -- Use a SDL3 Android aar archive, and configure gradle to use it: prefab is required. -- Add add_subdirectory(path/to/SDL) to your CMake script, and make sure a vendored SDL is present there. -") + find_package(SDL3 CONFIG REQUIRED) 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 5f8ee6a..0fb7ae0 100644 --- a/libs/SDL3/android-project/app/proguard-rules.pro +++ b/libs/SDL3/android-project/app/proguard-rules.pro @@ -23,20 +23,18 @@ void clipboardSetText(java.lang.String); int createCustomCursor(int[], int, int, int, int); void destroyCustomCursor(int); - android.content.Context getContext(); + android.app.Activity getContext(); boolean getManifestEnvironmentVariables(); android.view.Surface getNativeSurface(); void initTouch(); boolean isAndroidTV(); boolean isChromebook(); boolean isDeXMode(); - boolean isScreenKeyboardShown(); boolean isTablet(); void manualBackButton(); int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]); void minimizeWindow(); boolean openURL(java.lang.String); - void onNativePen(int, int, int , float , float , float); void requestPermission(java.lang.String, int); boolean showToast(java.lang.String, int, int, int, int); boolean sendMessage(int, int); @@ -71,6 +69,7 @@ -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager { void pollInputDevices(); + void joystickSetLED(int, int, int, int); void pollHapticDevices(); void hapticRun(int, float, int); void hapticRumble(int, float, float, int); diff --git a/libs/SDL3/android-project/app/src/main/AndroidManifest.xml b/libs/SDL3/android-project/app/src/main/AndroidManifest.xml index f3a7cd5..ab43f76 100644 --- a/libs/SDL3/android-project/app/src/main/AndroidManifest.xml +++ b/libs/SDL3/android-project/app/src/main/AndroidManifest.xml @@ -71,6 +71,7 @@ android:icon="@mipmap/ic_launcher" android:allowBackup="true" android:theme="@style/AppTheme" + android:enableOnBackInvokedCallback="false" android:hardwareAccelerated="true" > \n\n"; + print $fh "# $envvartitle\n\n"; + + if (defined $envvardesc) { + my $desc = "$envvardesc"; + $desc =~ s/\\n/\n/g; # replace "\n" strings with actual newlines. + print $fh "$desc\n\n"; + } + + print $fh "## Environment Variable List\n\n"; + + foreach (sort keys %headersyms) { + my $sym = $_; + next if $headersymstype{$sym} != 2; # not a #define? skip it. + my $hint = "$_"; + next if not $hint =~ s/$envvarsymregex/$replace/ee; + + my $brief = $$briefsref{$sym}; + if (not defined $brief) { + $brief = ''; + } else { + $brief = "$brief"; + chomp($brief); + my $thiswikitype = defined $wikitypes{$sym} ? $wikitypes{$sym} : 'md'; # default to MarkDown for new stuff. + $brief = ": " . dewikify($thiswikitype, $brief); + } + print $fh "- [$hint]($sym)$brief\n"; + } + + print $fh "\n"; + + close($fh); + + rename($tmppath, $path) or die("Can't rename '$tmppath' to '$path': $!\n"); +} + + + + my $incpath = "$srcpath"; $incpath .= "/$incsubdir" if $incsubdir ne ''; -my $wikireadmepath = "$wikipath/$wikireadmesubdir"; my $readmepath = undef; if (defined $readmesubdir) { $readmepath = "$srcpath/$readmesubdir"; @@ -1091,7 +1165,7 @@ while (my $d = readdir(DH)) { } elsif ($ignoring_lines) { push @contents, $_; next; - } elsif (/\A\s*\#\s*ifndef\s+SDL_WIKI_DOCUMENTATION_SECTION\s*\Z/) { + } elsif (/\A\s*\#\s*ifndef\s+$wikidocsectionsym\s*\Z/) { $ignoring_lines = 1; push @contents, $_; next; @@ -1100,13 +1174,13 @@ while (my $d = readdir(DH)) { #print("CATEGORY FOR '$dent' CHANGED TO " . (defined($current_wiki_category) ? "'$current_wiki_category'" : '(undef)') . "\n"); push @contents, $_; next; - } elsif (/\A\s*extern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_|SDL_)?DECLSPEC/) { # a function declaration without a doxygen comment? + } elsif (/\A\s*extern\s+(?:$deprecatedsym\s+|)$declspecsym/) { # a function declaration without a doxygen comment? $symtype = 1; # function declaration @templines = (); $decl = $_; $str = ''; $has_doxygen = 0; - } elsif (/\A\s*SDL_FORCE_INLINE/) { # a (forced-inline) function declaration without a doxygen comment? + } elsif (/\A\s*$forceinlinesym/) { # a (forced-inline) function declaration without a doxygen comment? $symtype = 1; # function declaration @templines = (); $decl = $_; @@ -1173,9 +1247,9 @@ while (my $d = readdir(DH)) { $lineno++ if defined $decl; $decl = '' if not defined $decl; chomp($decl); - if ($decl =~ /\A\s*extern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_|SDL_)?DECLSPEC/) { + if ($decl =~ /\A\s*extern\s+(?:$deprecatedsym\s+|)$declspecsym/) { $symtype = 1; # function declaration - } elsif ($decl =~ /\A\s*SDL_FORCE_INLINE/) { + } elsif ($decl =~ /\A\s*$forceinlinesym/) { $symtype = 1; # (forced-inline) function declaration } elsif ($decl =~ /\A\s*\#\s*define\s+/) { $symtype = 2; # macro @@ -1212,7 +1286,7 @@ while (my $d = readdir(DH)) { } $headercategorydocs{$current_wiki_category} = $sym; } elsif ($symtype == 1) { # a function - my $is_forced_inline = ($decl =~ /\A\s*SDL_FORCE_INLINE/); + my $is_forced_inline = ($decl =~ /\A\s*$forceinlinesym/); if ($is_forced_inline) { if (not $decl =~ /\)\s*(\{.*|)\s*\Z/) { @@ -1249,14 +1323,14 @@ while (my $d = readdir(DH)) { my $paramsstr = undef; - if (!$is_forced_inline && $decl =~ /\A\s*extern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_|SDL_)?DECLSPEC\w*\s+(const\s+|)(unsigned\s+|)(.*?)([\*\s]+)(\*?)\s*SDLCALL\s+(.*?)\s*\((.*?)\);/) { - $sym = $8; - $rettype = "$3$4$5$6"; - $paramsstr = $9; - } elsif ($is_forced_inline && $decl =~ /\A\s*SDL_FORCE_INLINE\s+(SDL_DEPRECATED\s+|)(const\s+|)(unsigned\s+|)(.*?)([\*\s]+)(.*?)\s*\((.*?)\);/) { + if (!$is_forced_inline && $decl =~ /\A\s*extern\s+(?:$deprecatedsym\s+|)$declspecsym\w*\s+(const\s+|)(unsigned\s+|)(.*?)([\*\s]+)(\*?)\s*$callconvsym\s+(.*?)\s*\((.*?)\);/) { $sym = $6; - $rettype = "$2$3$4$5"; + $rettype = "$1$2$3$4$5"; $paramsstr = $7; + } elsif ($is_forced_inline && $decl =~ /\A\s*$forceinlinesym\s+(?:$deprecatedsym\s+|)(const\s+|)(unsigned\s+|)(.*?)([\*\s]+)(.*?)\s*\((.*?)\);/) { + $sym = $5; + $rettype = "$1$2$3$4"; + $paramsstr = $6; } else { #print "Found doxygen but no function sig:\n$str\n\n"; foreach (@templines) { @@ -1322,7 +1396,7 @@ while (my $d = readdir(DH)) { $decl = $_; $temp = $decl; - $temp =~ s/\Aextern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_|SDL_)?DECLSPEC\w*\s+(.*?)\s+(\*?)SDLCALL\s+/$3$4 /; + $temp =~ s/\Aextern\s+(?:$deprecatedsym\s+|)$declspecsym\w*\s+(.*?)\s+(\*?)$callconvsym\s+/$1$2 /; $shrink_length = length($decl) - length($temp); $decl = $temp; } else { @@ -1366,7 +1440,7 @@ while (my $d = readdir(DH)) { # update strings now that we know everything pending is to be applied to this declaration. Add pending blank lines and the new text. # At Sam's request, don't list property defines with functions. (See #9440) - my $is_property = /\A\s*\#\s*define\s+SDL_PROP_/; + my $is_property = (defined $apipropertyregex) ? /$apipropertyregex/ : 0; if (!$is_property) { if ($blank_lines > 0) { while ($blank_lines > 0) { @@ -1387,7 +1461,7 @@ while (my $d = readdir(DH)) { } $decl .= $additional_decl; } elsif ($symtype == 2) { # a macro - if ($decl =~ /\A\s*\#\s*define\s+(.*?)(\(.*?\)|)\s+/) { + if ($decl =~ /\A\s*\#\s*define\s+(.*?)(\(.*?\)|)(\s+|\Z)/) { $sym = $1; } else { #print "Found doxygen but no macro:\n$str\n\n"; @@ -2083,18 +2157,15 @@ if ($copy_direction == 1) { # --copy-to-headers } if (defined $readmepath) { - if ( -d $wikireadmepath ) { - mkdir($readmepath); # just in case - opendir(DH, $wikireadmepath) or die("Can't opendir '$wikireadmepath': $!\n"); - while (readdir(DH)) { - my $dent = $_; - if ($dent =~ /\A(.*?)\.md\Z/) { # we only bridge Markdown files here. - next if $1 eq 'FrontPage'; - filecopy("$wikireadmepath/$dent", "$readmepath/README-$dent", "\n"); - } + mkdir($readmepath); # just in case + opendir(DH, $wikipath) or die("Can't opendir '$wikipath': $!\n"); + while (readdir(DH)) { + my $dent = $_; + if ($dent =~ /\A(README|INTRO)\-.*?\.md\Z/) { # we only bridge Markdown files here that start with "README-" or "INTRO-". + filecopy("$wikipath/$dent", "$readmepath/$dent", "\n"); } - closedir(DH); } + closedir(DH); } } elsif ($copy_direction == -1) { # --copy-to-wiki @@ -2200,10 +2271,10 @@ if ($copy_direction == 1) { # --copy-to-headers $desc =~ s/[\s\n]+\Z//ms; - if (0) { # !!! FIXME: disabled because it's not currently suitable for general use, but for manually inspecting the output, it can be useful. - if (($desc =~ /\A[A-Z]/) && (not $desc =~ /\ASDL_/)) { - print STDERR "WARNING: $sym\'s '\\param $arg' text starts with a capital letter: '$desc'. Fixing.\n"; - $desc = lcfirst($desc); + if (0) { + if (($desc =~ /\A[a-z]/) && (not $desc =~ /$apiprefixregex/)) { + print STDERR "WARNING: $sym\'s '\\param $arg' text starts with a lowercase letter: '$desc'. Fixing.\n"; + $desc = ucfirst($desc); } } @@ -2247,8 +2318,8 @@ if ($copy_direction == 1) { # --copy-to-headers } $desc =~ s/[\s\n]+\Z//ms; - if (0) { # !!! FIXME: disabled because it's not currently suitable for general use, but for manually inspecting the output, it can be useful. - if (($desc =~ /\A[A-Z]/) && (not $desc =~ /\ASDL_/)) { + if (0) { + if (($desc =~ /\A[A-Z]/) && (not $desc =~ /$apiprefixregex/)) { print STDERR "WARNING: $sym\'s '\\returns' text starts with a capital letter: '$desc'. Fixing.\n"; $desc = lcfirst($desc); } @@ -2392,7 +2463,7 @@ if ($copy_direction == 1) { # --copy-to-headers } else { die("Unexpected symbol type $symtype!"); } - my $str = "This $symtypename is available since SDL 3.0.0."; + my $str = "This $symtypename is available since $projectshortname 3.0.0."; $sections{'Version'} = wordwrap(wikify($wikitype, $str)) . "\n"; } } @@ -2699,31 +2770,27 @@ __EOF__ # Write out READMEs... if (defined $readmepath) { if ( -d $readmepath ) { - mkdir($wikireadmepath); # just in case + mkdir($wikipath); # just in case opendir(DH, $readmepath) or die("Can't opendir '$readmepath': $!\n"); while (my $d = readdir(DH)) { my $dent = $d; - if ($dent =~ /\AREADME\-(.*?\.md)\Z/) { # we only bridge Markdown files here. - my $wikifname = $1; - next if $wikifname eq 'FrontPage.md'; - filecopy("$readmepath/$dent", "$wikireadmepath/$wikifname", "\n"); + if ($dent =~ /\A(README|INTRO)\-.*?\.md\Z/) { # we only bridge Markdown files here that start with "README-" or "INTRO". + filecopy("$readmepath/$dent", "$wikipath/$dent", "\n"); } } closedir(DH); my @pages = (); - opendir(DH, $wikireadmepath) or die("Can't opendir '$wikireadmepath': $!\n"); + opendir(DH, $wikipath) or die("Can't opendir '$wikipath': $!\n"); while (my $d = readdir(DH)) { my $dent = $d; - if ($dent =~ /\A(.*?)\.(mediawiki|md)\Z/) { - my $wikiname = $1; - next if $wikiname eq 'FrontPage'; - push @pages, $wikiname; + if ($dent =~ /\A((README|INTRO)\-.*?)\.md\Z/) { + push @pages, $1; } } closedir(DH); - open(FH, '>', "$wikireadmepath/FrontPage.md") or die("Can't open '$wikireadmepath/FrontPage.md': $!\n"); + open(FH, '>', "$wikipath/READMEs.md") or die("Can't open '$wikipath/READMEs.md': $!\n"); print FH "# All READMEs available here\n\n"; foreach (sort @pages) { my $wikiname = $_; @@ -2738,6 +2805,11 @@ __EOF__ generate_quickref(\%briefs, "$wikipath/QuickReference.md", 0); generate_quickref(\%briefs, "$wikipath/QuickReferenceNoUnicode.md", 1); } + + if ($envvarenabled and defined $envvarsymregex and defined $envvarsymreplace) { + generate_envvar_wiki_page(\%briefs, "$wikipath/EnvironmentVariables.md"); + } + } elsif ($copy_direction == -2) { # --copy-to-manpages # This only takes from the wiki data, since it has sections we omit from the headers, like code examples. @@ -2856,7 +2928,7 @@ __EOF__ $str .= ".\\\" Please report issues in this manpage's content at:\n"; $str .= ".\\\" $bugreporturl\n"; $str .= ".\\\" Please report issues in the generation of this manpage from the wiki at:\n"; - $str .= ".\\\" https://github.com/libsdl-org/SDL/issues/new?title=Misgenerated%20manpage%20for%20$sym\n"; + $str .= ".\\\" https://github.com/libsdl-org/SDL/issues/new?title=Misgenerated%20manpage%20for%20$sym\n"; # !!! FIXME: if this becomes a problem for other projects, we'll generalize this. $str .= ".\\\" $projectshortname can be found at $projecturl\n"; # Define a .URL macro. The "www.tmac" thing decides if we're using GNU roff (which has a .URL macro already), and if so, overrides the macro we just created. @@ -2981,10 +3053,12 @@ __EOF__ } if (defined $returns) { + # Check for md link in return type: ([SDL_Renderer](SDL_Renderer) *) + # This would've prevented the next regex from working properly (it'd leave " *)") + $returns =~ s/\A\(\[.*?\]\((.*?)\)/\($1/ms; # Chop datatype in parentheses off the front. - if(!($returns =~ s/\A\([^\[]*\[[^\]]*\]\([^\)]*\)[^\)]*\) //ms)) { - $returns =~ s/\A\([^\)]*\) //ms; - } + $returns =~ s/\A\(.*?\) //; + $returns = dewikify($wikitype, $returns); $str .= ".SH RETURN VALUE\n"; $str .= "$returns\n"; diff --git a/libs/SDL3/cmake/FindLibUSB.cmake b/libs/SDL3/cmake/FindLibUSB.cmake index 96d4428..50988ad 100644 --- a/libs/SDL3/cmake/FindLibUSB.cmake +++ b/libs/SDL3/cmake/FindLibUSB.cmake @@ -10,7 +10,7 @@ if(PKG_CONFIG_FOUND) endif() find_library(LibUSB_LIBRARY - NAMES usb-1.0 libusb-1.0 + NAMES usb-1.0 libusb-1.0 usb HINTS ${PC_LibUSB_LIBRARY_DIRS} ) diff --git a/libs/SDL3/cmake/GetGitRevisionDescription.cmake b/libs/SDL3/cmake/GetGitRevisionDescription.cmake index a08895c..70c554e 100644 --- a/libs/SDL3/cmake/GetGitRevisionDescription.cmake +++ b/libs/SDL3/cmake/GetGitRevisionDescription.cmake @@ -164,6 +164,22 @@ function(get_git_head_revision _refspecvar _hashvar) "${GIT_DATA}/grabRef.cmake" @ONLY) include("${GIT_DATA}/grabRef.cmake") + # Fallback for reftable or other storage formats + if(NOT HEAD_HASH OR HEAD_HASH STREQUAL "") + find_package(Git QUIET) + if(GIT_FOUND) + execute_process( + COMMAND "${GIT_EXECUTABLE}" rev-parse HEAD + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE HEAD_HASH + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(HEAD_HASH "") + endif() + endif() + endif() + set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) diff --git a/libs/SDL3/cmake/GetGitRevisionDescription.cmake.in b/libs/SDL3/cmake/GetGitRevisionDescription.cmake.in index 116efc4..81b4213 100644 --- a/libs/SDL3/cmake/GetGitRevisionDescription.cmake.in +++ b/libs/SDL3/cmake/GetGitRevisionDescription.cmake.in @@ -25,19 +25,21 @@ if(HEAD_CONTENTS MATCHES "ref") string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") if(EXISTS "@GIT_DIR@/${HEAD_REF}") configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) - else() + elseif(EXISTS "@GIT_DIR@/packed-refs") configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") set(HEAD_HASH "${CMAKE_MATCH_1}") endif() + elseif(EXISTS "@GIT_DIR@/reftable/tables.list") + configure_file("@GIT_DIR@/reftable/tables.list" "@GIT_DATA@/reftable-tables.list" COPYONLY) endif() else() # detached HEAD configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) endif() -if(NOT HEAD_HASH) +if(NOT HEAD_HASH AND EXISTS "@GIT_DATA@/head-ref") file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) string(STRIP "${HEAD_HASH}" HEAD_HASH) endif() diff --git a/libs/SDL3/cmake/PkgConfigHelper.cmake b/libs/SDL3/cmake/PkgConfigHelper.cmake index 7070fac..44d7431 100644 --- a/libs/SDL3/cmake/PkgConfigHelper.cmake +++ b/libs/SDL3/cmake/PkgConfigHelper.cmake @@ -1,16 +1,21 @@ # Helper for Find modules function(get_flags_from_pkg_config _library _pc_prefix _out_prefix) - if("${_library}" MATCHES "${CMAKE_STATIC_LIBRARY_SUFFIX}$") - set(_cflags ${_pc_prefix}_STATIC_CFLAGS_OTHER) - set(_link_libraries ${_pc_prefix}_STATIC_LIBRARIES) - set(_link_options ${_pc_prefix}_STATIC_LDFLAGS_OTHER) - set(_library_dirs ${_pc_prefix}_STATIC_LIBRARY_DIRS) + if(MINGW) + set(re_shared_suffix ".dll.a$") else() + set(re_shared_suffix "${CMAKE_SHARED_LIBRARY_SUFFIX}$") + endif() + if("${_library}" MATCHES "${re_shared_suffix}") set(_cflags ${_pc_prefix}_CFLAGS_OTHER) set(_link_libraries ${_pc_prefix}_LIBRARIES) set(_link_options ${_pc_prefix}_LDFLAGS_OTHER) set(_library_dirs ${_pc_prefix}_LIBRARY_DIRS) + else() + set(_cflags ${_pc_prefix}_STATIC_CFLAGS_OTHER) + set(_link_libraries ${_pc_prefix}_STATIC_LIBRARIES) + set(_link_options ${_pc_prefix}_STATIC_LDFLAGS_OTHER) + set(_library_dirs ${_pc_prefix}_STATIC_LIBRARY_DIRS) endif() # The *_LIBRARIES lists always start with the library itself diff --git a/libs/SDL3/cmake/PreseedEmscriptenCache.cmake b/libs/SDL3/cmake/PreseedEmscriptenCache.cmake index 5a34ab5..78504a5 100644 --- a/libs/SDL3/cmake/PreseedEmscriptenCache.cmake +++ b/libs/SDL3/cmake/PreseedEmscriptenCache.cmake @@ -4,6 +4,7 @@ if(EMSCRIPTEN) set(HAVE_ALLOCA_H "1" CACHE INTERNAL "Have include alloca.h") set(HAVE_LIBM "1" CACHE INTERNAL "Have library m") set(HAVE_MALLOC "1" CACHE INTERNAL "Have include malloc.h") + set(HAVE_MALLOC_H "1" CACHE INTERNAL "Have include malloc.h") set(LIBC_HAS_ABS "1" CACHE INTERNAL "Have symbol abs") set(LIBC_HAS_ACOS "1" CACHE INTERNAL "Have symbol acos") set(LIBC_HAS_ACOSF "1" CACHE INTERNAL "Have symbol acosf") @@ -152,7 +153,7 @@ if(EMSCRIPTEN) set(HAVE_O_CLOEXEC "1" CACHE INTERNAL "Test HAVE_O_CLOEXEC") set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR") set(COMPILER_SUPPORTS_GCC_ATOMICS "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_GCC_ATOMICS") - set(LINKER_SUPPORTS_VERSION_SCRIPT "" CACHE INTERNAL "Test LINKER_SUPPORTS_VERSION_SCRIPT") + set(HAVE_WL_VERSION_SCRIPT "" CACHE INTERNAL "Test HAVE_WL_VERSION_SCRIPT") set(LINKER_SUPPORTS_WL_NO_UNDEFINED "" CACHE INTERNAL "Test LINKER_SUPPORTS_WL_NO_UNDEFINED") set(ICONV_IN_LIBC "1" CACHE INTERNAL "Test ICONV_IN_LIBC") set(ICONV_IN_LIBICONV "" CACHE INTERNAL "Test ICONV_IN_LIBICONV") @@ -174,5 +175,11 @@ if(EMSCRIPTEN) set(HAVE_MEMFD_CREATE "" CACHE INTERNAL "Have symbol memfd_create") set(HAVE_POSIX_FALLOCATE "1" CACHE INTERNAL "Have symbol posix_fallocate") set(HAVE_DLOPEN_IN_LIBC "1" CACHE INTERNAL "Have symbol dlopen") + set(HAVE_FDATASYNC "1" CACHE INTERNAL "Have symbol fdatasync") + set(HAVE_GETHOSTNAME "1" CACHE INTERNAL "Have symbol gethostname") + set(HAVE_SIGTIMEDWAIT "1" CACHE INTERNAL "Have symbol sigtimedwait") + set(HAVE_PPOLL "" CACHE INTERNAL "Have symbol ppoll") + set(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR "" CACHE INTERNAL "Have symbol posix_spawn_file_actions_addchdir") + set(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP "1" CACHE INTERNAL "Have symbol posix_spawn_file_actions_addchdir_np") endfunction() endif() diff --git a/libs/SDL3/cmake/PreseedMSVCCache.cmake b/libs/SDL3/cmake/PreseedMSVCCache.cmake index 17495aa..81f7d01 100644 --- a/libs/SDL3/cmake/PreseedMSVCCache.cmake +++ b/libs/SDL3/cmake/PreseedMSVCCache.cmake @@ -1,183 +1,194 @@ if(MSVC) function(SDL_Preseed_CMakeCache) - set(COMPILER_SUPPORTS_W3 "1" CACHE INTERNAL "Test /W3") - set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS "" CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS") - set(HAVE_ALLOCA_H "" CACHE INTERNAL "Have include alloca.h") - set(HAVE_AUDIOCLIENT_H "1" CACHE INTERNAL "Have include audioclient.h") - set(HAVE_D3D11_H "1" CACHE INTERNAL "Have include d3d11_1.h") - set(HAVE_D3D9_H "1" CACHE INTERNAL "Have include d3d9.h") - set(HAVE_DDRAW_H "1" CACHE INTERNAL "Have include ddraw.h") - set(HAVE_DINPUT_H "1" CACHE INTERNAL "Have include dinput.h") - set(HAVE_DSOUND_H "1" CACHE INTERNAL "Have include dsound.h") - set(HAVE_DXGI_H "1" CACHE INTERNAL "Have include dxgi.h") - set(HAVE_LIBM "" CACHE INTERNAL "Have library m") - set(HAVE_MALLOC "1" CACHE INTERNAL "Have include malloc.h") - set(HAVE_MMDEVICEAPI_H "1" CACHE INTERNAL "Have include mmdeviceapi.h") - set(HAVE_SENSORSAPI_H "1" CACHE INTERNAL "Have include sensorsapi.h") - set(HAVE_SHELLSCALINGAPI_H "1" CACHE INTERNAL "Have include shellscalingapi.h") - set(HAVE_TPCSHRD_H "1" CACHE INTERNAL "Have include tpcshrd.h") - set(HAVE_WIN32_CC "1" CACHE INTERNAL "Test HAVE_WIN32_CC") - set(HAVE_XINPUT_H "1" CACHE INTERNAL "Test HAVE_XINPUT_H") - set(LIBC_HAS_ABS "1" CACHE INTERNAL "Have symbol abs") - set(LIBC_HAS_ACOS "1" CACHE INTERNAL "Have symbol acos") - set(LIBC_HAS_ACOSF "1" CACHE INTERNAL "Have symbol acosf") - set(LIBC_HAS_ASIN "1" CACHE INTERNAL "Have symbol asin") - set(LIBC_HAS_ASINF "1" CACHE INTERNAL "Have symbol asinf") - set(LIBC_HAS_ATAN "1" CACHE INTERNAL "Have symbol atan") - set(LIBC_HAS_ATAN2 "1" CACHE INTERNAL "Have symbol atan2") - set(LIBC_HAS_ATAN2F "1" CACHE INTERNAL "Have symbol atan2f") - set(LIBC_HAS_ATANF "1" CACHE INTERNAL "Have symbol atanf") - set(LIBC_HAS_ATOF "1" CACHE INTERNAL "Have symbol atof") - set(LIBC_HAS_ATOI "1" CACHE INTERNAL "Have symbol atoi") - set(LIBC_HAS_BCOPY "" CACHE INTERNAL "Have symbol bcopy") - set(LIBC_HAS_CALLOC "1" CACHE INTERNAL "Have symbol calloc") - set(LIBC_HAS_CEIL "1" CACHE INTERNAL "Have symbol ceil") - set(LIBC_HAS_CEILF "1" CACHE INTERNAL "Have symbol ceilf") - set(LIBC_HAS_COPYSIGN "1" CACHE INTERNAL "Have symbol copysign") - set(LIBC_HAS_COPYSIGNF "1" CACHE INTERNAL "Have symbol copysignf") - set(LIBC_HAS_COS "1" CACHE INTERNAL "Have symbol cos") - set(LIBC_HAS_COSF "1" CACHE INTERNAL "Have symbol cosf") - set(LIBC_HAS_EXP "1" CACHE INTERNAL "Have symbol exp") - set(LIBC_HAS_EXPF "1" CACHE INTERNAL "Have symbol expf") - set(LIBC_HAS_FABS "1" CACHE INTERNAL "Have symbol fabs") - set(LIBC_HAS_FABSF "1" CACHE INTERNAL "Have symbol fabsf") - set(LIBC_HAS_FLOAT_H "1" CACHE INTERNAL "Have include float.h") - set(LIBC_HAS_FLOOR "1" CACHE INTERNAL "Have symbol floor") - set(LIBC_HAS_FLOORF "1" CACHE INTERNAL "Have symbol floorf") - set(LIBC_HAS_FMOD "1" CACHE INTERNAL "Have symbol fmod") - set(LIBC_HAS_FMODF "1" CACHE INTERNAL "Have symbol fmodf") - set(LIBC_HAS_FOPEN64 "" CACHE INTERNAL "Have symbol fopen64") - set(LIBC_HAS_FREE "1" CACHE INTERNAL "Have symbol free") - set(LIBC_HAS_FSEEKO "" CACHE INTERNAL "Have symbol fseeko") - set(LIBC_HAS_FSEEKO64 "" CACHE INTERNAL "Have symbol fseeko64") - set(LIBC_HAS_GETENV "1" CACHE INTERNAL "Have symbol getenv") - set(LIBC_HAS_ICONV_H "" CACHE INTERNAL "Have include iconv.h") - set(LIBC_HAS_INDEX "" CACHE INTERNAL "Have symbol index") - set(LIBC_HAS_INTTYPES_H "1" CACHE INTERNAL "Have include inttypes.h") - set(LIBC_HAS_ISINF "1" CACHE INTERNAL "Have include isinf(double)") - set(LIBC_ISINF_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isinf(float)") - set(LIBC_HAS_ISINFF "" CACHE INTERNAL "Have include isinff(float)") - set(LIBC_HAS_ISNAN "1" CACHE INTERNAL "Have include isnan(double)") - set(LIBC_ISNAN_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isnan(float)") - set(LIBC_HAS_ISNANF "" CACHE INTERNAL "Have include isnanf(float)") - set(LIBC_HAS_ITOA "1" CACHE INTERNAL "Have symbol itoa") - set(LIBC_HAS_LIMITS_H "1" CACHE INTERNAL "Have include limits.h") - set(LIBC_HAS_LOG "1" CACHE INTERNAL "Have symbol log") - set(LIBC_HAS_LOG10 "1" CACHE INTERNAL "Have symbol log10") - set(LIBC_HAS_LOG10F "1" CACHE INTERNAL "Have symbol log10f") - set(LIBC_HAS_LOGF "1" CACHE INTERNAL "Have symbol logf") - set(LIBC_HAS_LROUND "1" CACHE INTERNAL "Have symbol lround") - set(LIBC_HAS_LROUNDF "1" CACHE INTERNAL "Have symbol lroundf") - set(LIBC_HAS_MALLOC "1" CACHE INTERNAL "Have symbol malloc") - set(LIBC_HAS_MALLOC_H "1" CACHE INTERNAL "Have include malloc.h") - set(LIBC_HAS_MATH_H "1" CACHE INTERNAL "Have include math.h") - set(LIBC_HAS_MEMCMP "1" CACHE INTERNAL "Have symbol memcmp") - set(LIBC_HAS_MEMCPY "1" CACHE INTERNAL "Have symbol memcpy") - set(LIBC_HAS_MEMMOVE "1" CACHE INTERNAL "Have symbol memmove") - set(LIBC_HAS_MEMORY_H "1" CACHE INTERNAL "Have include memory.h") - set(LIBC_HAS_MEMSET "1" CACHE INTERNAL "Have symbol memset") - set(LIBC_HAS_MODF "1" CACHE INTERNAL "Have symbol modf") - set(LIBC_HAS_MODFF "1" CACHE INTERNAL "Have symbol modff") - set(LIBC_HAS_POW "1" CACHE INTERNAL "Have symbol pow") - set(LIBC_HAS_POWF "1" CACHE INTERNAL "Have symbol powf") - set(LIBC_HAS_PUTENV "1" CACHE INTERNAL "Have symbol putenv") - set(LIBC_HAS_REALLOC "1" CACHE INTERNAL "Have symbol realloc") - set(LIBC_HAS_RINDEX "" CACHE INTERNAL "Have symbol rindex") - set(LIBC_HAS_ROUND "1" CACHE INTERNAL "Have symbol round") - set(LIBC_HAS_ROUNDF "1" CACHE INTERNAL "Have symbol roundf") - set(LIBC_HAS_SCALBN "1" CACHE INTERNAL "Have symbol scalbn") - set(LIBC_HAS_SCALBNF "1" CACHE INTERNAL "Have symbol scalbnf") - set(LIBC_HAS_SETENV "" CACHE INTERNAL "Have symbol setenv") - set(LIBC_HAS_SIGNAL_H "1" CACHE INTERNAL "Have include signal.h") - set(LIBC_HAS_SIN "1" CACHE INTERNAL "Have symbol sin") - set(LIBC_HAS_SINF "1" CACHE INTERNAL "Have symbol sinf") - set(LIBC_HAS_SQR "" CACHE INTERNAL "Have symbol sqr") - set(LIBC_HAS_SQRT "1" CACHE INTERNAL "Have symbol sqrt") - set(LIBC_HAS_SQRTF "1" CACHE INTERNAL "Have symbol sqrtf") - set(LIBC_HAS_SSCANF "1" CACHE INTERNAL "Have symbol sscanf") - set(LIBC_HAS_STDARG_H "1" CACHE INTERNAL "Have include stdarg.h") - set(LIBC_HAS_STDBOOL_H "1" CACHE INTERNAL "Have include stdbool.h") - set(LIBC_HAS_STDDEF_H "1" CACHE INTERNAL "Have include stddef.h") - set(LIBC_HAS_STDINT_H "1" CACHE INTERNAL "Have include stdint.h") - set(LIBC_HAS_STDIO_H "1" CACHE INTERNAL "Have include stdio.h") - set(LIBC_HAS_STDLIB_H "1" CACHE INTERNAL "Have include stdlib.h") - set(LIBC_HAS_STRCHR "1" CACHE INTERNAL "Have symbol strchr") - set(LIBC_HAS_STRCMP "1" CACHE INTERNAL "Have symbol strcmp") - set(LIBC_HAS_STRINGS_H "" CACHE INTERNAL "Have include strings.h") - set(LIBC_HAS_STRING_H "1" CACHE INTERNAL "Have include string.h") - set(LIBC_HAS_STRLCAT "" CACHE INTERNAL "Have symbol strlcat") - set(LIBC_HAS_STRLCPY "" CACHE INTERNAL "Have symbol strlcpy") - set(LIBC_HAS_STRLEN "1" CACHE INTERNAL "Have symbol strlen") - set(LIBC_HAS_STRNCMP "1" CACHE INTERNAL "Have symbol strncmp") - set(LIBC_HAS_STRNLEN "1" CACHE INTERNAL "Have symbol strnlen") - set(LIBC_HAS_STRNSTR "" CACHE INTERNAL "Have symbol strnstr") - set(LIBC_HAS_STRPBRK "1" CACHE INTERNAL "Have symbol strpbrk") - set(LIBC_HAS_STRRCHR "1" CACHE INTERNAL "Have symbol strrchr") - set(LIBC_HAS_STRSTR "1" CACHE INTERNAL "Have symbol strstr") - set(LIBC_HAS_STRTOD "1" CACHE INTERNAL "Have symbol strtod") - set(LIBC_HAS_STRTOK_R "" CACHE INTERNAL "Have symbol strtok_r") - set(LIBC_HAS_STRTOL "1" CACHE INTERNAL "Have symbol strtol") - set(LIBC_HAS_STRTOLL "1" CACHE INTERNAL "Have symbol strtoll") - set(LIBC_HAS_STRTOUL "1" CACHE INTERNAL "Have symbol strtoul") - set(LIBC_HAS_STRTOULL "1" CACHE INTERNAL "Have symbol strtoull") - set(LIBC_HAS_SYS_TYPES_H "1" CACHE INTERNAL "Have include sys/types.h") - set(LIBC_HAS_TAN "1" CACHE INTERNAL "Have symbol tan") - set(LIBC_HAS_TANF "1" CACHE INTERNAL "Have symbol tanf") - set(LIBC_HAS_TIME_H "1" CACHE INTERNAL "Have include time.h") - set(LIBC_HAS_TRUNC "1" CACHE INTERNAL "Have symbol trunc") - set(LIBC_HAS_TRUNCF "1" CACHE INTERNAL "Have symbol truncf") - set(LIBC_HAS_UNSETENV "" CACHE INTERNAL "Have symbol unsetenv") - set(LIBC_HAS_VSNPRINTF "1" CACHE INTERNAL "Have symbol vsnprintf") - set(LIBC_HAS_VSSCANF "1" CACHE INTERNAL "Have symbol vsscanf") - set(LIBC_HAS_WCHAR_H "1" CACHE INTERNAL "Have include wchar.h") - set(LIBC_HAS_WCSCMP "1" CACHE INTERNAL "Have symbol wcscmp") - set(LIBC_HAS_WCSDUP "1" CACHE INTERNAL "Have symbol wcsdup") - set(LIBC_HAS_WCSLCAT "" CACHE INTERNAL "Have symbol wcslcat") - set(LIBC_HAS_WCSLCPY "" CACHE INTERNAL "Have symbol wcslcpy") - set(LIBC_HAS_WCSLEN "1" CACHE INTERNAL "Have symbol wcslen") - set(LIBC_HAS_WCSNCMP "1" CACHE INTERNAL "Have symbol wcsncmp") - set(LIBC_HAS_WCSNLEN "1" CACHE INTERNAL "Have symbol wcsnlen") - set(LIBC_HAS_WCSSTR "1" CACHE INTERNAL "Have symbol wcsstr") - set(LIBC_HAS_WCSTOL "1" CACHE INTERNAL "Have symbol wcstol") - set(LIBC_HAS__EXIT "1" CACHE INTERNAL "Have symbol _Exit") - set(LIBC_HAS__I64TOA "1" CACHE INTERNAL "Have symbol _i64toa") - set(LIBC_HAS__LTOA "1" CACHE INTERNAL "Have symbol _ltoa") - set(LIBC_HAS__STRREV "1" CACHE INTERNAL "Have symbol _strrev") - set(LIBC_HAS__UI64TOA "1" CACHE INTERNAL "Have symbol _ui64toa") - set(LIBC_HAS__UITOA "" CACHE INTERNAL "Have symbol _uitoa") - set(LIBC_HAS__ULTOA "1" CACHE INTERNAL "Have symbol _ultoa") - set(LIBC_HAS__WCSDUP "1" CACHE INTERNAL "Have symbol _wcsdup") - set(LIBC_IS_GLIBC "" CACHE INTERNAL "Have symbol __GLIBC__") - set(_ALLOCA_IN_MALLOC_H "" CACHE INTERNAL "Have symbol _alloca") + check_c_source_compiles(" + #include + #if _WIN32_WINNT < 0x0A00 + #error Preseeding is only supported for MSVC supporting Windows 10 or higher + #endif + int main(int argc, char **argv) { return 0; } + " CAN_PRESEED + ) + if(CAN_PRESEED) + set(COMPILER_SUPPORTS_W3 "1" CACHE INTERNAL "Test /W3") + set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS "" CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS") + set(HAVE_ALLOCA_H "" CACHE INTERNAL "Have include alloca.h") + set(HAVE_AUDIOCLIENT_H "1" CACHE INTERNAL "Have include audioclient.h") + set(HAVE_D3D11_H "1" CACHE INTERNAL "Have include d3d11_1.h") + set(HAVE_D3D9_H "1" CACHE INTERNAL "Have include d3d9.h") + set(HAVE_DDRAW_H "1" CACHE INTERNAL "Have include ddraw.h") + set(HAVE_DINPUT_H "1" CACHE INTERNAL "Have include dinput.h") + set(HAVE_DSOUND_H "1" CACHE INTERNAL "Have include dsound.h") + set(HAVE_DXGI_H "1" CACHE INTERNAL "Have include dxgi.h") + set(HAVE_LIBM "" CACHE INTERNAL "Have library m") + set(HAVE_MALLOC "1" CACHE INTERNAL "Have include malloc.h") + set(HAVE_MALLOC_H "1" CACHE INTERNAL "Have include malloc.h") + set(HAVE_MMDEVICEAPI_H "1" CACHE INTERNAL "Have include mmdeviceapi.h") + set(HAVE_SENSORSAPI_H "1" CACHE INTERNAL "Have include sensorsapi.h") + set(HAVE_SHELLSCALINGAPI_H "1" CACHE INTERNAL "Have include shellscalingapi.h") + set(HAVE_TPCSHRD_H "1" CACHE INTERNAL "Have include tpcshrd.h") + set(HAVE_WIN32_CC "1" CACHE INTERNAL "Test HAVE_WIN32_CC") + set(HAVE_XINPUT_H "1" CACHE INTERNAL "Test HAVE_XINPUT_H") + set(LIBC_HAS_ABS "1" CACHE INTERNAL "Have symbol abs") + set(LIBC_HAS_ACOS "1" CACHE INTERNAL "Have symbol acos") + set(LIBC_HAS_ACOSF "1" CACHE INTERNAL "Have symbol acosf") + set(LIBC_HAS_ASIN "1" CACHE INTERNAL "Have symbol asin") + set(LIBC_HAS_ASINF "1" CACHE INTERNAL "Have symbol asinf") + set(LIBC_HAS_ATAN "1" CACHE INTERNAL "Have symbol atan") + set(LIBC_HAS_ATAN2 "1" CACHE INTERNAL "Have symbol atan2") + set(LIBC_HAS_ATAN2F "1" CACHE INTERNAL "Have symbol atan2f") + set(LIBC_HAS_ATANF "1" CACHE INTERNAL "Have symbol atanf") + set(LIBC_HAS_ATOF "1" CACHE INTERNAL "Have symbol atof") + set(LIBC_HAS_ATOI "1" CACHE INTERNAL "Have symbol atoi") + set(LIBC_HAS_BCOPY "" CACHE INTERNAL "Have symbol bcopy") + set(LIBC_HAS_CALLOC "1" CACHE INTERNAL "Have symbol calloc") + set(LIBC_HAS_CEIL "1" CACHE INTERNAL "Have symbol ceil") + set(LIBC_HAS_CEILF "1" CACHE INTERNAL "Have symbol ceilf") + set(LIBC_HAS_COPYSIGN "1" CACHE INTERNAL "Have symbol copysign") + set(LIBC_HAS_COPYSIGNF "1" CACHE INTERNAL "Have symbol copysignf") + set(LIBC_HAS_COS "1" CACHE INTERNAL "Have symbol cos") + set(LIBC_HAS_COSF "1" CACHE INTERNAL "Have symbol cosf") + set(LIBC_HAS_EXP "1" CACHE INTERNAL "Have symbol exp") + set(LIBC_HAS_EXPF "1" CACHE INTERNAL "Have symbol expf") + set(LIBC_HAS_FABS "1" CACHE INTERNAL "Have symbol fabs") + set(LIBC_HAS_FABSF "1" CACHE INTERNAL "Have symbol fabsf") + set(LIBC_HAS_FLOAT_H "1" CACHE INTERNAL "Have include float.h") + set(LIBC_HAS_FLOOR "1" CACHE INTERNAL "Have symbol floor") + set(LIBC_HAS_FLOORF "1" CACHE INTERNAL "Have symbol floorf") + set(LIBC_HAS_FMOD "1" CACHE INTERNAL "Have symbol fmod") + set(LIBC_HAS_FMODF "1" CACHE INTERNAL "Have symbol fmodf") + set(LIBC_HAS_FOPEN64 "" CACHE INTERNAL "Have symbol fopen64") + set(LIBC_HAS_FREE "1" CACHE INTERNAL "Have symbol free") + set(LIBC_HAS_FSEEKO "" CACHE INTERNAL "Have symbol fseeko") + set(LIBC_HAS_FSEEKO64 "" CACHE INTERNAL "Have symbol fseeko64") + set(LIBC_HAS_GETENV "1" CACHE INTERNAL "Have symbol getenv") + set(LIBC_HAS_ICONV_H "" CACHE INTERNAL "Have include iconv.h") + set(LIBC_HAS_INDEX "" CACHE INTERNAL "Have symbol index") + set(LIBC_HAS_INTTYPES_H "1" CACHE INTERNAL "Have include inttypes.h") + set(LIBC_HAS_ISINF "1" CACHE INTERNAL "Have include isinf(double)") + set(LIBC_ISINF_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isinf(float)") + set(LIBC_HAS_ISINFF "" CACHE INTERNAL "Have include isinff(float)") + set(LIBC_HAS_ISNAN "1" CACHE INTERNAL "Have include isnan(double)") + set(LIBC_ISNAN_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isnan(float)") + set(LIBC_HAS_ISNANF "" CACHE INTERNAL "Have include isnanf(float)") + set(LIBC_HAS_ITOA "1" CACHE INTERNAL "Have symbol itoa") + set(LIBC_HAS_LIMITS_H "1" CACHE INTERNAL "Have include limits.h") + set(LIBC_HAS_LOG "1" CACHE INTERNAL "Have symbol log") + set(LIBC_HAS_LOG10 "1" CACHE INTERNAL "Have symbol log10") + set(LIBC_HAS_LOG10F "1" CACHE INTERNAL "Have symbol log10f") + set(LIBC_HAS_LOGF "1" CACHE INTERNAL "Have symbol logf") + set(LIBC_HAS_LROUND "1" CACHE INTERNAL "Have symbol lround") + set(LIBC_HAS_LROUNDF "1" CACHE INTERNAL "Have symbol lroundf") + set(LIBC_HAS_MALLOC "1" CACHE INTERNAL "Have symbol malloc") + set(LIBC_HAS_MALLOC_H "1" CACHE INTERNAL "Have include malloc.h") + set(LIBC_HAS_MATH_H "1" CACHE INTERNAL "Have include math.h") + set(LIBC_HAS_MEMCMP "1" CACHE INTERNAL "Have symbol memcmp") + set(LIBC_HAS_MEMCPY "1" CACHE INTERNAL "Have symbol memcpy") + set(LIBC_HAS_MEMMOVE "1" CACHE INTERNAL "Have symbol memmove") + set(LIBC_HAS_MEMORY_H "1" CACHE INTERNAL "Have include memory.h") + set(LIBC_HAS_MEMSET "1" CACHE INTERNAL "Have symbol memset") + set(LIBC_HAS_MODF "1" CACHE INTERNAL "Have symbol modf") + set(LIBC_HAS_MODFF "1" CACHE INTERNAL "Have symbol modff") + set(LIBC_HAS_POW "1" CACHE INTERNAL "Have symbol pow") + set(LIBC_HAS_POWF "1" CACHE INTERNAL "Have symbol powf") + set(LIBC_HAS_PUTENV "1" CACHE INTERNAL "Have symbol putenv") + set(LIBC_HAS_REALLOC "1" CACHE INTERNAL "Have symbol realloc") + set(LIBC_HAS_RINDEX "" CACHE INTERNAL "Have symbol rindex") + set(LIBC_HAS_ROUND "1" CACHE INTERNAL "Have symbol round") + set(LIBC_HAS_ROUNDF "1" CACHE INTERNAL "Have symbol roundf") + set(LIBC_HAS_SCALBN "1" CACHE INTERNAL "Have symbol scalbn") + set(LIBC_HAS_SCALBNF "1" CACHE INTERNAL "Have symbol scalbnf") + set(LIBC_HAS_SETENV "" CACHE INTERNAL "Have symbol setenv") + set(LIBC_HAS_SIGNAL_H "1" CACHE INTERNAL "Have include signal.h") + set(LIBC_HAS_SIN "1" CACHE INTERNAL "Have symbol sin") + set(LIBC_HAS_SINF "1" CACHE INTERNAL "Have symbol sinf") + set(LIBC_HAS_SQR "" CACHE INTERNAL "Have symbol sqr") + set(LIBC_HAS_SQRT "1" CACHE INTERNAL "Have symbol sqrt") + set(LIBC_HAS_SQRTF "1" CACHE INTERNAL "Have symbol sqrtf") + set(LIBC_HAS_SSCANF "1" CACHE INTERNAL "Have symbol sscanf") + set(LIBC_HAS_STDARG_H "1" CACHE INTERNAL "Have include stdarg.h") + set(LIBC_HAS_STDBOOL_H "1" CACHE INTERNAL "Have include stdbool.h") + set(LIBC_HAS_STDDEF_H "1" CACHE INTERNAL "Have include stddef.h") + set(LIBC_HAS_STDINT_H "1" CACHE INTERNAL "Have include stdint.h") + set(LIBC_HAS_STDIO_H "1" CACHE INTERNAL "Have include stdio.h") + set(LIBC_HAS_STDLIB_H "1" CACHE INTERNAL "Have include stdlib.h") + set(LIBC_HAS_STRCHR "1" CACHE INTERNAL "Have symbol strchr") + set(LIBC_HAS_STRCMP "1" CACHE INTERNAL "Have symbol strcmp") + set(LIBC_HAS_STRINGS_H "" CACHE INTERNAL "Have include strings.h") + set(LIBC_HAS_STRING_H "1" CACHE INTERNAL "Have include string.h") + set(LIBC_HAS_STRLCAT "" CACHE INTERNAL "Have symbol strlcat") + set(LIBC_HAS_STRLCPY "" CACHE INTERNAL "Have symbol strlcpy") + set(LIBC_HAS_STRLEN "1" CACHE INTERNAL "Have symbol strlen") + set(LIBC_HAS_STRNCMP "1" CACHE INTERNAL "Have symbol strncmp") + set(LIBC_HAS_STRNLEN "1" CACHE INTERNAL "Have symbol strnlen") + set(LIBC_HAS_STRNSTR "" CACHE INTERNAL "Have symbol strnstr") + set(LIBC_HAS_STRPBRK "1" CACHE INTERNAL "Have symbol strpbrk") + set(LIBC_HAS_STRRCHR "1" CACHE INTERNAL "Have symbol strrchr") + set(LIBC_HAS_STRSTR "1" CACHE INTERNAL "Have symbol strstr") + set(LIBC_HAS_STRTOD "1" CACHE INTERNAL "Have symbol strtod") + set(LIBC_HAS_STRTOK_R "" CACHE INTERNAL "Have symbol strtok_r") + set(LIBC_HAS_STRTOL "1" CACHE INTERNAL "Have symbol strtol") + set(LIBC_HAS_STRTOLL "1" CACHE INTERNAL "Have symbol strtoll") + set(LIBC_HAS_STRTOUL "1" CACHE INTERNAL "Have symbol strtoul") + set(LIBC_HAS_STRTOULL "1" CACHE INTERNAL "Have symbol strtoull") + set(LIBC_HAS_SYS_TYPES_H "1" CACHE INTERNAL "Have include sys/types.h") + set(LIBC_HAS_TAN "1" CACHE INTERNAL "Have symbol tan") + set(LIBC_HAS_TANF "1" CACHE INTERNAL "Have symbol tanf") + set(LIBC_HAS_TIME_H "1" CACHE INTERNAL "Have include time.h") + set(LIBC_HAS_TRUNC "1" CACHE INTERNAL "Have symbol trunc") + set(LIBC_HAS_TRUNCF "1" CACHE INTERNAL "Have symbol truncf") + set(LIBC_HAS_UNSETENV "" CACHE INTERNAL "Have symbol unsetenv") + set(LIBC_HAS_VSNPRINTF "1" CACHE INTERNAL "Have symbol vsnprintf") + set(LIBC_HAS_VSSCANF "1" CACHE INTERNAL "Have symbol vsscanf") + set(LIBC_HAS_WCHAR_H "1" CACHE INTERNAL "Have include wchar.h") + set(LIBC_HAS_WCSCMP "1" CACHE INTERNAL "Have symbol wcscmp") + set(LIBC_HAS_WCSDUP "1" CACHE INTERNAL "Have symbol wcsdup") + set(LIBC_HAS_WCSLCAT "" CACHE INTERNAL "Have symbol wcslcat") + set(LIBC_HAS_WCSLCPY "" CACHE INTERNAL "Have symbol wcslcpy") + set(LIBC_HAS_WCSLEN "1" CACHE INTERNAL "Have symbol wcslen") + set(LIBC_HAS_WCSNCMP "1" CACHE INTERNAL "Have symbol wcsncmp") + set(LIBC_HAS_WCSNLEN "1" CACHE INTERNAL "Have symbol wcsnlen") + set(LIBC_HAS_WCSSTR "1" CACHE INTERNAL "Have symbol wcsstr") + set(LIBC_HAS_WCSTOL "1" CACHE INTERNAL "Have symbol wcstol") + set(LIBC_HAS__EXIT "1" CACHE INTERNAL "Have symbol _Exit") + set(LIBC_HAS__I64TOA "1" CACHE INTERNAL "Have symbol _i64toa") + set(LIBC_HAS__LTOA "1" CACHE INTERNAL "Have symbol _ltoa") + set(LIBC_HAS__STRREV "1" CACHE INTERNAL "Have symbol _strrev") + set(LIBC_HAS__UI64TOA "1" CACHE INTERNAL "Have symbol _ui64toa") + set(LIBC_HAS__UITOA "" CACHE INTERNAL "Have symbol _uitoa") + set(LIBC_HAS__ULTOA "1" CACHE INTERNAL "Have symbol _ultoa") + set(LIBC_HAS__WCSDUP "1" CACHE INTERNAL "Have symbol _wcsdup") + set(LIBC_IS_GLIBC "" CACHE INTERNAL "Have symbol __GLIBC__") + set(_ALLOCA_IN_MALLOC_H "1" CACHE INTERNAL "Have symbol _alloca") - if(CHECK_CPU_ARCHITECTURE_X86) - set(COMPILER_SUPPORTS_AVX "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX") - set(COMPILER_SUPPORTS_AVX2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX2") - set(COMPILER_SUPPORTS_MMX "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_MMX") - set(COMPILER_SUPPORTS_SSE "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE") - set(COMPILER_SUPPORTS_SSE2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE2") - set(COMPILER_SUPPORTS_SSE3 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE3") - set(COMPILER_SUPPORTS_SSE4_1 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_1") - set(COMPILER_SUPPORTS_SSE4_2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_2") - endif() + if(CHECK_CPU_ARCHITECTURE_X86) + set(COMPILER_SUPPORTS_AVX "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX") + set(COMPILER_SUPPORTS_AVX2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX2") + set(COMPILER_SUPPORTS_MMX "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_MMX") + set(COMPILER_SUPPORTS_SSE "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE") + set(COMPILER_SUPPORTS_SSE2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE2") + set(COMPILER_SUPPORTS_SSE3 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE3") + set(COMPILER_SUPPORTS_SSE4_1 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_1") + set(COMPILER_SUPPORTS_SSE4_2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_2") + endif() - if(CHECK_CPU_ARCHITECTURE_X64) - set(COMPILER_SUPPORTS_AVX "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX") - set(COMPILER_SUPPORTS_AVX2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX2") - set(COMPILER_SUPPORTS_MMX "" CACHE INTERNAL "Test COMPILER_SUPPORTS_MMX") - set(COMPILER_SUPPORTS_SSE "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE") - set(COMPILER_SUPPORTS_SSE2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE2") - set(COMPILER_SUPPORTS_SSE3 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE3") - set(COMPILER_SUPPORTS_SSE4_1 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_1") - set(COMPILER_SUPPORTS_SSE4_2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_2") - endif() + if(CHECK_CPU_ARCHITECTURE_X64) + set(COMPILER_SUPPORTS_AVX "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX") + set(COMPILER_SUPPORTS_AVX2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX2") + set(COMPILER_SUPPORTS_MMX "" CACHE INTERNAL "Test COMPILER_SUPPORTS_MMX") + set(COMPILER_SUPPORTS_SSE "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE") + set(COMPILER_SUPPORTS_SSE2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE2") + set(COMPILER_SUPPORTS_SSE3 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE3") + set(COMPILER_SUPPORTS_SSE4_1 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_1") + set(COMPILER_SUPPORTS_SSE4_2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_2") + endif() - if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL "19.1") - set(HAVE_ROAPI_H "1" CACHE INTERNAL "Have include roapi.h") - set(HAVE_WINDOWS_GAMING_INPUT_H "1" CACHE INTERNAL "Test HAVE_WINDOWS_GAMING_INPUT_H") - else() - set(HAVE_ROAPI_H "" CACHE INTERNAL "Have include roapi.h") - set(HAVE_WINDOWS_GAMING_INPUT_H "" CACHE INTERNAL "Test HAVE_WINDOWS_GAMING_INPUT_H") + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL "19.1") + set(HAVE_ROAPI_H "1" CACHE INTERNAL "Have include roapi.h") + set(HAVE_WINDOWS_GAMING_INPUT_H "1" CACHE INTERNAL "Test HAVE_WINDOWS_GAMING_INPUT_H") + else() + set(HAVE_ROAPI_H "" CACHE INTERNAL "Have include roapi.h") + set(HAVE_WINDOWS_GAMING_INPUT_H "" CACHE INTERNAL "Test HAVE_WINDOWS_GAMING_INPUT_H") + endif() endif() endfunction() endif() diff --git a/libs/SDL3/cmake/PreseedNokiaNGageCache.cmake b/libs/SDL3/cmake/PreseedNokiaNGageCache.cmake new file mode 100644 index 0000000..9873727 --- /dev/null +++ b/libs/SDL3/cmake/PreseedNokiaNGageCache.cmake @@ -0,0 +1,189 @@ +if(NGAGESDK) + function(SDL_Preseed_CMakeCache) + set(COMPILER_SUPPORTS_ARMNEON "" CACHE INTERNAL "Test COMPILER_SUPPORTS_ARMNEON") + set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS "" CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS") + set(COMPILER_SUPPORTS_SYNC_LOCK_TEST_AND_SET "" CACHE INTERNAL "Test COMPILER_SUPPORTS_SYNC_LOCK_TEST_AND_SET") + set(HAVE_CLANG_COMMENT_BLOCK_COMMANDS "" CACHE INTERNAL "Test HAVE_CLANG_COMMENT_BLOCK_COMMANDS") + set(HAVE_ALLOCA_H "" CACHE INTERNAL "Have include alloca.h") + set(HAVE_LIBM "1" CACHE INTERNAL "Have library m") + set(HAVE_POSIX_SPAWN "" CACHE INTERNAL "Have symbol posix_spawn") + set(HAVE_MALLOC "1" CACHE INTERNAL "Have include malloc.h") + set(LIBC_HAS_ABS "1" CACHE INTERNAL "Have symbol abs") + set(LIBC_HAS_ACOS "1" CACHE INTERNAL "Have symbol acos") + set(LIBC_HAS_ACOSF "" CACHE INTERNAL "Have symbol acosf") + set(LIBC_HAS_ASIN "1" CACHE INTERNAL "Have symbol asin") + set(LIBC_HAS_ASINF "" CACHE INTERNAL "Have symbol asinf") + set(LIBC_HAS_ATAN "1" CACHE INTERNAL "Have symbol atan") + set(LIBC_HAS_ATAN2 "1" CACHE INTERNAL "Have symbol atan2") + set(LIBC_HAS_ATAN2F "" CACHE INTERNAL "Have symbol atan2f") + set(LIBC_HAS_ATANF "" CACHE INTERNAL "Have symbol atanf") + set(LIBC_HAS_ATOF "" CACHE INTERNAL "Have symbol atof") + set(LIBC_HAS_ATOI "" CACHE INTERNAL "Have symbol atoi") + set(LIBC_HAS_BCOPY "1" CACHE INTERNAL "Have symbol bcopy") + set(LIBC_HAS_CALLOC "" CACHE INTERNAL "Have symbol calloc") + set(LIBC_HAS_CEIL "1" CACHE INTERNAL "Have symbol ceil") + set(LIBC_HAS_CEILF "" CACHE INTERNAL "Have symbol ceilf") + set(LIBC_HAS_COPYSIGN "1" CACHE INTERNAL "Have symbol copysign") + set(LIBC_HAS_COPYSIGNF "1" CACHE INTERNAL "Have symbol copysignf") + set(LIBC_HAS_COS "1" CACHE INTERNAL "Have symbol cos") + set(LIBC_HAS_COSF "" CACHE INTERNAL "Have symbol cosf") + set(LIBC_HAS_EXP "1" CACHE INTERNAL "Have symbol exp") + set(LIBC_HAS_EXPF "" CACHE INTERNAL "Have symbol expf") + set(LIBC_HAS_FABS "1" CACHE INTERNAL "Have symbol fabs") + set(LIBC_HAS_FABSF "1" CACHE INTERNAL "Have symbol fabsf") + set(LIBC_HAS_FLOAT_H "1" CACHE INTERNAL "Have include float.h") + set(LIBC_HAS_FLOOR "1" CACHE INTERNAL "Have symbol floor") + set(LIBC_HAS_FLOORF "" CACHE INTERNAL "Have symbol floorf") + set(LIBC_HAS_FMOD "" CACHE INTERNAL "Have symbol fmod") + set(LIBC_HAS_FMODF "" CACHE INTERNAL "Have symbol fmodf") + set(LIBC_HAS_FOPEN64 "" CACHE INTERNAL "Have symbol fopen64") + set(LIBC_HAS_FREE "1" CACHE INTERNAL "Have symbol free") + set(LIBC_HAS_FSEEKO "" CACHE INTERNAL "Have symbol fseeko") + set(LIBC_HAS_FSEEKO64 "" CACHE INTERNAL "Have symbol fseeko64") + set(LIBC_HAS_GETENV "" CACHE INTERNAL "Have symbol getenv") + set(LIBC_HAS_ICONV_H "" CACHE INTERNAL "Have include iconv.h") + set(LIBC_HAS_INDEX "1" CACHE INTERNAL "Have symbol index") + set(LIBC_HAS_INTTYPES_H "1" CACHE INTERNAL "Have include inttypes.h") + set(LIBC_HAS_ISINF "1" CACHE INTERNAL "Have include isinf(double)") + set(LIBC_ISINF_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isinf(float)") + set(LIBC_HAS_ISINFF "1" CACHE INTERNAL "Have include isinff(float)") + set(LIBC_HAS_ISNAN "1" CACHE INTERNAL "Have include isnan(double)") + set(LIBC_ISNAN_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isnan(float)") + set(LIBC_HAS_ISNANF "1" CACHE INTERNAL "Have include isnanf(float)") + set(LIBC_HAS_ITOA "" CACHE INTERNAL "Have symbol itoa") + set(LIBC_HAS_LIMITS_H "1" CACHE INTERNAL "Have include limits.h") + set(LIBC_HAS_LOG "1" CACHE INTERNAL "Have symbol log") + set(LIBC_HAS_LOG10 "" CACHE INTERNAL "Have symbol log10") + set(LIBC_HAS_LOG10F "" CACHE INTERNAL "Have symbol log10f") + set(LIBC_HAS_LOGF "" CACHE INTERNAL "Have symbol logf") + set(LIBC_HAS_LROUND "" CACHE INTERNAL "Have symbol lround") + set(LIBC_HAS_LROUNDF "" CACHE INTERNAL "Have symbol lroundf") + set(LIBC_HAS_MALLOC "1" CACHE INTERNAL "Have symbol malloc") + set(LIBC_HAS_MALLOC_H "" CACHE INTERNAL "Have include malloc.h") + set(LIBC_HAS_MATH_H "1" CACHE INTERNAL "Have include math.h") + set(LIBC_HAS_MEMCMP "1" CACHE INTERNAL "Have symbol memcmp") + set(LIBC_HAS_MEMCPY "" CACHE INTERNAL "Have symbol memcpy") + set(LIBC_HAS_MEMMOVE "" CACHE INTERNAL "Have symbol memmove") + set(LIBC_HAS_MEMORY_H "" CACHE INTERNAL "Have include memory.h") + set(LIBC_HAS_MEMSET "" CACHE INTERNAL "Have symbol memset") + set(LIBC_HAS_MODF "1" CACHE INTERNAL "Have symbol modf") + set(LIBC_HAS_MODFF "" CACHE INTERNAL "Have symbol modff") + set(LIBC_HAS_POW "1" CACHE INTERNAL "Have symbol pow") + set(LIBC_HAS_POWF "" CACHE INTERNAL "Have symbol powf") + set(LIBC_HAS_PUTENV "" CACHE INTERNAL "Have symbol putenv") + set(LIBC_HAS_REALLOC "" CACHE INTERNAL "Have symbol realloc") + set(LIBC_HAS_RINDEX "1" CACHE INTERNAL "Have symbol rindex") + set(LIBC_HAS_ROUND "" CACHE INTERNAL "Have symbol round") + set(LIBC_HAS_ROUNDF "" CACHE INTERNAL "Have symbol roundf") + set(LIBC_HAS_SCALBN "1" CACHE INTERNAL "Have symbol scalbn") + set(LIBC_HAS_SCALBNF "" CACHE INTERNAL "Have symbol scalbnf") + set(LIBC_HAS_SETENV "" CACHE INTERNAL "Have symbol setenv") + set(LIBC_HAS_SIGNAL_H "" CACHE INTERNAL "Have include signal.h") + set(LIBC_HAS_SIN "1" CACHE INTERNAL "Have symbol sin") + set(LIBC_HAS_SINF "" CACHE INTERNAL "Have symbol sinf") + set(LIBC_HAS_SQR "" CACHE INTERNAL "Have symbol sqr") + set(LIBC_HAS_SQRT "1" CACHE INTERNAL "Have symbol sqrt") + set(LIBC_HAS_SQRTF "" CACHE INTERNAL "Have symbol sqrtf") + set(LIBC_HAS_SSCANF "1" CACHE INTERNAL "Have symbol sscanf") + set(LIBC_HAS_STDARG_H "1" CACHE INTERNAL "Have include stdarg.h") + set(LIBC_HAS_STDBOOL_H "1" CACHE INTERNAL "Have include stdbool.h") + set(LIBC_HAS_STDDEF_H "1" CACHE INTERNAL "Have include stddef.h") + set(LIBC_HAS_STDINT_H "1" CACHE INTERNAL "Have include stdint.h") + set(LIBC_HAS_STDIO_H "1" CACHE INTERNAL "Have include stdio.h") + set(LIBC_HAS_STDLIB_H "1" CACHE INTERNAL "Have include stdlib.h") + set(LIBC_HAS_STRCASESTR "" CACHE INTERNAL "Have symbol strcasestr") + set(LIBC_HAS_STRCHR "1" CACHE INTERNAL "Have symbol strchr") + set(LIBC_HAS_STRCMP "1" CACHE INTERNAL "Have symbol strcmp") + set(LIBC_HAS_STRINGS_H "" CACHE INTERNAL "Have include strings.h") + set(LIBC_HAS_STRING_H "1" CACHE INTERNAL "Have include string.h") + set(LIBC_HAS_STRLCAT "" CACHE INTERNAL "Have symbol strlcat") + set(LIBC_HAS_STRLCPY "" CACHE INTERNAL "Have symbol strlcpy") + set(LIBC_HAS_STRLEN "1" CACHE INTERNAL "Have symbol strlen") + set(LIBC_HAS_STRNCMP "1" CACHE INTERNAL "Have symbol strncmp") + set(LIBC_HAS_STRNLEN "" CACHE INTERNAL "Have symbol strnlen") + set(LIBC_HAS_STRNSTR "" CACHE INTERNAL "Have symbol strnstr") + set(LIBC_HAS_STRPBRK "1" CACHE INTERNAL "Have symbol strpbrk") + set(LIBC_HAS_STRRCHR "1" CACHE INTERNAL "Have symbol strrchr") + set(LIBC_HAS_STRSTR "1" CACHE INTERNAL "Have symbol strstr") + set(LIBC_HAS_STRTOD "" CACHE INTERNAL "Have symbol strtod") + set(LIBC_HAS_STRTOK_R "" CACHE INTERNAL "Have symbol strtok_r") + set(LIBC_HAS_STRTOL "" CACHE INTERNAL "Have symbol strtol") + set(LIBC_HAS_STRTOLL "" CACHE INTERNAL "Have symbol strtoll") + set(LIBC_HAS_STRTOUL "" CACHE INTERNAL "Have symbol strtoul") + set(LIBC_HAS_STRTOULL "" CACHE INTERNAL "Have symbol strtoull") + set(LIBC_HAS_SYS_TYPES_H "1" CACHE INTERNAL "Have include sys/types.h") + set(LIBC_HAS_TAN "1" CACHE INTERNAL "Have symbol tan") + set(LIBC_HAS_TANF "" CACHE INTERNAL "Have symbol tanf") + set(LIBC_HAS_TIME_H "1" CACHE INTERNAL "Have include time.h") + set(LIBC_HAS_TRUNC "" CACHE INTERNAL "Have symbol trunc") + set(LIBC_HAS_TRUNCF "" CACHE INTERNAL "Have symbol truncf") + set(LIBC_HAS_UNSETENV "" CACHE INTERNAL "Have symbol unsetenv") + set(LIBC_HAS_VSNPRINTF "" CACHE INTERNAL "Have symbol vsnprintf") + set(LIBC_HAS_VSSCANF "" CACHE INTERNAL "Have symbol vsscanf") + set(LIBC_HAS_WCHAR_H "1" CACHE INTERNAL "Have include wchar.h") + set(LIBC_HAS_WCSCMP "" CACHE INTERNAL "Have symbol wcscmp") + set(LIBC_HAS_WCSDUP "" CACHE INTERNAL "Have symbol wcsdup") + set(LIBC_HAS_WCSLCAT "" CACHE INTERNAL "Have symbol wcslcat") + set(LIBC_HAS_WCSLCPY "" CACHE INTERNAL "Have symbol wcslcpy") + set(LIBC_HAS_WCSLEN "" CACHE INTERNAL "Have symbol wcslen") + set(LIBC_HAS_WCSNCMP "" CACHE INTERNAL "Have symbol wcsncmp") + set(LIBC_HAS_WCSNLEN "" CACHE INTERNAL "Have symbol wcsnlen") + set(LIBC_HAS_WCSSTR "" CACHE INTERNAL "Have symbol wcsstr") + set(LIBC_HAS_WCSTOL "" CACHE INTERNAL "Have symbol wcstol") + set(LIBC_HAS__EXIT "" CACHE INTERNAL "Have symbol _Exit") + set(LIBC_HAS__I64TOA "" CACHE INTERNAL "Have symbol _i64toa") + set(LIBC_HAS__LTOA "" CACHE INTERNAL "Have symbol _ltoa") + set(LIBC_HAS__STRREV "" CACHE INTERNAL "Have symbol _strrev") + set(LIBC_HAS__UI64TOA "" CACHE INTERNAL "Have symbol _ui64toa") + set(LIBC_HAS__UITOA "" CACHE INTERNAL "Have symbol _uitoa") + set(LIBC_HAS__ULTOA "" CACHE INTERNAL "Have symbol _ultoa") + set(LIBC_HAS__WCSDUP "" CACHE INTERNAL "Have symbol _wcsdup") + set(LIBC_IS_GLIBC "" CACHE INTERNAL "Have symbol __GLIBC__") + set(_ALLOCA_IN_MALLOC_H "" CACHE INTERNAL "Have symbol _alloca") + set(HAVE_GCC_WALL "1" CACHE INTERNAL "Test HAVE_GCC_WALL") + set(HAVE_GCC_WUNDEF "1" CACHE INTERNAL "Test HAVE_GCC_WUNDEF") + set(HAVE_GCC_WFLOAT_CONVERSION "" CACHE INTERNAL "Test HAVE_GCC_WFLOAT_CONVERSION") + set(HAVE_GCC_NO_STRICT_ALIASING "1" CACHE INTERNAL "Test HAVE_GCC_NO_STRICT_ALIASING") + set(HAVE_GCC_WDOCUMENTATION "" CACHE INTERNAL "Test HAVE_GCC_WDOCUMENTATION") + set(HAVE_GCC_WDOCUMENTATION_UNKNOWN_COMMAND "" CACHE INTERNAL "Test HAVE_GCC_WDOCUMENTATION_UNKNOWN_COMMAND") + set(HAVE_GCC_COMMENT_BLOCK_COMMANDS "" CACHE INTERNAL "Test HAVE_GCC_COMMENT_BLOCK_COMMANDS") + set(HAVE_GCC_WSHADOW "1" CACHE INTERNAL "Test HAVE_GCC_WSHADOW") + set(HAVE_GCC_WUNUSED_LOCAL_TYPEDEFS "" CACHE INTERNAL "Test HAVE_GCC_WUNUSED_LOCAL_TYPEDEFS") + set(HAVE_GCC_WIMPLICIT_FALLTHROUGH "" CACHE INTERNAL "Test HAVE_GCC_WIMPLICIT_FALLTHROUGH") + set(HAVE_GCC_FVISIBILITY "" CACHE INTERNAL "Test HAVE_GCC_FVISIBILITY") + set(HAVE_ST_MTIM "" CACHE INTERNAL "Test HAVE_ST_MTIM") + #set(HAVE_O_CLOEXEC "" CACHE INTERNAL "Test HAVE_O_CLOEXEC") + #set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR "" CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR") + set(COMPILER_SUPPORTS_GCC_ATOMICS "" CACHE INTERNAL "Test COMPILER_SUPPORTS_GCC_ATOMICS") + set(HAVE_WL_VERSION_SCRIPT "" CACHE INTERNAL "Test HAVE_WL_VERSION_SCRIPT") + set(LINKER_SUPPORTS_WL_NO_UNDEFINED "" CACHE INTERNAL "Test LINKER_SUPPORTS_WL_NO_UNDEFINED") + set(ICONV_IN_LIBC "" CACHE INTERNAL "Test ICONV_IN_LIBC") + set(ICONV_IN_LIBICONV "" CACHE INTERNAL "Test ICONV_IN_LIBICONV") + #set(LIBC_HAS_WORKING_LIBUNWIND "" CACHE INTERNAL "Test LIBC_HAS_WORKING_LIBUNWIND") + #set(LIBUNWIND_HAS_WORKINGLIBUNWIND "" CACHE INTERNAL "Test LIBUNWIND_HAS_WORKINGLIBUNWIND") + set(HAVE_GETPAGESIZE "" CACHE INTERNAL "Have symbol getpagesize") + set(HAVE_SIGACTION "" CACHE INTERNAL "Have symbol sigaction") + set(HAVE_SA_SIGACTION "" CACHE INTERNAL "Have symbol sa_sigaction") + set(HAVE_SETJMP "" CACHE INTERNAL "Have symbol setjmp") + set(HAVE_NANOSLEEP "" CACHE INTERNAL "Have symbol nanosleep") + set(HAVE_GMTIME_R "" CACHE INTERNAL "Have symbol gmtime_r") + set(HAVE_LOCALTIME_R "" CACHE INTERNAL "Have symbol localtime_r") + set(HAVE_NL_LANGINFO "" CACHE INTERNAL "Have symbol nl_langinfo") + set(HAVE_SYSCONF "" CACHE INTERNAL "Have symbol sysconf") + set(HAVE_SYSCTLBYNAME "" CACHE INTERNAL "Have symbol sysctlbyname") + set(HAVE_GETAUXVAL "" CACHE INTERNAL "Have symbol getauxval") + set(HAVE_ELF_AUX_INFO "" CACHE INTERNAL "Have symbol elf_aux_info") + set(HAVE_POLL "" CACHE INTERNAL "Have symbol poll") + set(HAVE_MEMFD_CREATE "" CACHE INTERNAL "Have symbol memfd_create") + set(HAVE_POSIX_FALLOCATE "" CACHE INTERNAL "Have symbol posix_fallocate") + set(HAVE_DLOPEN_IN_LIBC "" CACHE INTERNAL "Have symbol dlopen") + + set(HAVE_GETHOSTNAME "" CACHE INTERNAL "Have symbol gethostname") + set(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR "" CACHE INTERNAL "Have symbol addchdir") + set(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP "" CACHE INTERNAL "Have symbol addchdir_np") + set(HAVE_FDATASYNC "" CACHE INTERNAL "Have symbol fdatasync") + + set(HAVE_SDL_FSOPS "1" CACHE INTERNAL "Enable SDL_FSOPS") + set(HAVE_SDL_LOCALE "1" CACHE INTERNAL "Enable SDL_LOCALE") + endfunction() +endif() diff --git a/libs/SDL3/cmake/SDL3Config.cmake.in b/libs/SDL3/cmake/SDL3Config.cmake.in index 4c6f1b6..a1190aa 100644 --- a/libs/SDL3/cmake/SDL3Config.cmake.in +++ b/libs/SDL3/cmake/SDL3Config.cmake.in @@ -34,7 +34,7 @@ endmacro() if(_sdl3_framework) set(SDL3_SDL3-static_FOUND TRUE) find_sdl3_static_dependencies() - find_package(SDL3-static CONFIG) + find_package(SDL3-static CONFIG QUIET) if(SDL3_SDL3-static_FOUND AND SDL3-static_FOUND) set(SDL3_SDL3-static_FOUND TRUE) endif() @@ -62,7 +62,7 @@ endif() # Find SDL3::SDL3_test if(_sdl3_framework) - find_package(SDL3_test CONFIG) + find_package(SDL3_test CONFIG QUIET) if(SDL3_test_FOUND) enable_language(OBJC) set(SDL3_SDL3_test_FOUND TRUE) diff --git a/libs/SDL3/cmake/macros.cmake b/libs/SDL3/cmake/macros.cmake index 3885aba..4889197 100644 --- a/libs/SDL3/cmake/macros.cmake +++ b/libs/SDL3/cmake/macros.cmake @@ -296,15 +296,15 @@ endfunction() function(check_linker_supports_version_file VAR) SDL_detect_linker() if(CMAKE_C_COMPILER_LINKER_ID MATCHES "^(MSVC)$") - set(LINKER_SUPPORTS_VERSION_SCRIPT FALSE) + set(${VAR} FALSE) else() cmake_push_check_state(RESET) file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/dummy.sym" "n_0 {\n global:\n func;\n local: *;\n};\n") list(APPEND CMAKE_REQUIRED_LINK_OPTIONS "-Wl,--version-script=${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/dummy.sym") - check_c_source_compiles("int func(void) {return 0;} int main(int argc,char*argv[]){(void)argc;(void)argv;return func();}" LINKER_SUPPORTS_VERSION_SCRIPT FAIL_REGEX "(unsupported|syntax error|unrecognized option)") + check_c_source_compiles("int func(void) {return 0;} int main(int argc,char*argv[]){(void)argc;(void)argv;return func();}" ${VAR} FAIL_REGEX "(unsupported|syntax error|unrecognized option)") cmake_pop_check_state() endif() - set(${VAR} "${LINKER_SUPPORTS_VERSION_SCRIPT}" PARENT_SCOPE) + set(${VAR} "${${VAR}}" PARENT_SCOPE) endfunction() if(CMAKE_VERSION VERSION_LESS 3.18) @@ -334,6 +334,29 @@ if(APPLE) endif() endif() +function(PrintEnabledBackends _SUBSYS _REGEXP) + get_cmake_property(_ALLVARS VARIABLES) + foreach(_VAR IN LISTS _ALLVARS) + if(_VAR AND _VAR MATCHES "${_REGEXP}") + string(TOLOWER "${CMAKE_MATCH_1}" _LOWERED) + if(NOT _LOWERED MATCHES "available|default|dynamic") # a little hack + if(${_VAR}_DYNAMIC) + list(APPEND _ENABLED_BACKENDS "${_LOWERED}(dynamic)") + else() + list(APPEND _ENABLED_BACKENDS "${_LOWERED}") + endif() + endif() + endif() + endforeach() + + if(_ENABLED_BACKENDS STREQUAL "") + set(_SPACEDOUT "(none)") + else() + string(REPLACE ";" " " _SPACEDOUT "${_ENABLED_BACKENDS}") + endif() + message(STATUS " ${_SUBSYS}: ${_SPACEDOUT}") +endfunction() + function(SDL_PrintSummary) ##### Info output ##### message(STATUS "") @@ -362,13 +385,23 @@ function(SDL_PrintSummary) message(STATUS "") message(STATUS " Build Shared Library: ${SDL_SHARED}") message(STATUS " Build Static Library: ${SDL_STATIC}") - if(SDL_STATIC) - message(STATUS " Build Static Library with Position Independent Code: ${SDL_STATIC_PIC}") - endif() if(APPLE) message(STATUS " Build libraries as Apple Framework: ${SDL_FRAMEWORK}") endif() message(STATUS "") + + message(STATUS "Enabled backends:") + PrintEnabledBackends("Video drivers" "^SDL_VIDEO_DRIVER_([A-Z0-9]*)$") + if(SDL_VIDEO_DRIVER_X11) + PrintEnabledBackends("X11 libraries" "^SDL_VIDEO_DRIVER_X11_([A-Z0-9]*)$") + endif() + PrintEnabledBackends("Render drivers" "^SDL_VIDEO_RENDER_([A-Z0-9_]*)$") + PrintEnabledBackends("GPU drivers" "^SDL_GPU_([A-Z0-9]*)$") + PrintEnabledBackends("Audio drivers" "^SDL_AUDIO_DRIVER_([A-Z0-9]*)$") + PrintEnabledBackends("Joystick drivers" "^SDL_JOYSTICK_([A-Z0-9]*)$") + PrintEnabledBackends("Camera drivers" "^SDL_CAMERA_DRIVER_([A-Z0-9]*)$") + message(STATUS "") + if(UNIX) message(STATUS "If something was not detected, although the libraries") message(STATUS "were installed, then make sure you have set the") @@ -385,7 +418,7 @@ function(SDL_PrintSummary) "Most likely, this is not wanted." "\n" "On Linux, install the packages listed at " - "https://github.com/libsdl-org/SDL/blob/main/docs/README-linux.md#build-dependencies " + "https://wiki.libsdl.org/SDL3/README-linux#build-dependencies " "\n" "If you really don't need desktop windows, the documentation tells you how to skip this check. " "https://github.com/libsdl-org/SDL/blob/main/docs/README-cmake.md#cmake-fails-to-build-without-x11-or-wayland-support\n" @@ -395,6 +428,22 @@ function(SDL_PrintSummary) endif() endfunction() +function(SDL_missing_dependency NAME OPTION) + if(LINUX) + message( FATAL_ERROR + "Couldn't find dependency package for ${NAME}. Please install the needed packages or configure with -D${OPTION}=OFF" + "\n" + "The full set of dependencies is available at " + "https://wiki.libsdl.org/SDL3/README-linux#build-dependencies " + "\n" + ) + else() + message( FATAL_ERROR + "Couldn't find dependency package for ${NAME}. Please install the needed packages or configure with -D${OPTION}=OFF" + ) + endif() +endfunction() + function(SDL_install_pdb TARGET DIRECTORY) get_property(type TARGET ${TARGET} PROPERTY TYPE) if(type MATCHES "^(SHARED_LIBRARY|EXECUTABLE)$") diff --git a/libs/SDL3/cmake/sdlchecks.cmake b/libs/SDL3/cmake/sdlchecks.cmake index d321e71..bb8a97d 100644 --- a/libs/SDL3/cmake/sdlchecks.cmake +++ b/libs/SDL3/cmake/sdlchecks.cmake @@ -1,3 +1,10 @@ +macro(check_c_source_compiles_static SOURCE VAR) + set(saved_CMAKE_TRY_COMPILE_TARGET_TYPE "${CMAKE_TRY_COMPILE_TARGET_TYPE}") + set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY") + check_c_source_compiles("${SOURCE}" ${VAR} ${ARGN}) + set(CMAKE_TRY_COMPILE_TARGET_TYPE "${saved_CMAKE_TRY_COMPILE_TARGET_TYPE}") +endmacro() + macro(FindLibraryAndSONAME _LIB) cmake_parse_arguments(_FLAS "" "" "LIBDIRS" ${ARGN}) @@ -274,10 +281,11 @@ macro(CheckX11) set(Xrandr_PKG_CONFIG_SPEC xrandr) set(Xrender_PKG_CONFIG_SPEC xrender) set(Xss_PKG_CONFIG_SPEC xscrnsaver) + set(Xtst_PKG_CONFIG_SPEC xtst) find_package(X11) - foreach(_LIB X11 Xext Xcursor Xi Xfixes Xrandr Xrender Xss) + foreach(_LIB X11 Xext Xcursor Xi Xfixes Xrandr Xrender Xss Xtst) get_filename_component(_libdir "${X11_${_LIB}_LIB}" DIRECTORY) FindLibraryAndSONAME("${_LIB}" LIBDIRS ${_libdir}) endforeach() @@ -310,6 +318,7 @@ macro(CheckX11) find_file(HAVE_XSYNC_H NAMES "X11/extensions/sync.h" HINTS "${X11_INCLUDEDIR}") find_file(HAVE_XSS_H NAMES "X11/extensions/scrnsaver.h" HINTS "${X11_INCLUDEDIR}") find_file(HAVE_XSHAPE_H NAMES "X11/extensions/shape.h" HINTS "${X11_INCLUDEDIR}") + find_file(HAVE_XTEST_H NAMES "X11/extensions/XTest.h" HINTS "${X11_INCLUDEDIR}") find_file(HAVE_XDBE_H NAMES "X11/extensions/Xdbe.h" HINTS "${X11_INCLUDEDIR}") find_file(HAVE_XEXT_H NAMES "X11/extensions/Xext.h" HINTS "${X11_INCLUDEDIR}") @@ -367,7 +376,7 @@ macro(CheckX11) list(APPEND CMAKE_REQUIRED_LIBRARIES ${X11_LIB}) - check_c_source_compiles(" + check_c_source_compiles_static(" #include int main(int argc, char **argv) { Display *display; @@ -381,51 +390,90 @@ macro(CheckX11) set(SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1) endif() - check_symbol_exists(XkbLookupKeySym "X11/Xlib.h;X11/XKBlib.h" SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM) + check_include_file("X11/XKBlib.h" SDL_VIDEO_DRIVER_X11_HAS_XKBLIB) - if(SDL_X11_XCURSOR AND HAVE_XCURSOR_H AND XCURSOR_LIB) - set(HAVE_X11_XCURSOR TRUE) - if(HAVE_X11_SHARED) - set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR "\"${XCURSOR_LIB_SONAME}\"") + if(SDL_X11_XCURSOR) + if (HAVE_XCURSOR_H AND XCURSOR_LIB) + set(HAVE_X11_XCURSOR TRUE) + if(HAVE_X11_SHARED) + set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR "\"${XCURSOR_LIB_SONAME}\"") + else() + sdl_link_dependency(xcursor LIBS X11::Xcursor CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xcursor_PKG_CONFIG_SPEC}) + endif() + set(SDL_VIDEO_DRIVER_X11_XCURSOR 1) else() - sdl_link_dependency(xcursor LIBS X11::Xcursor CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xcursor_PKG_CONFIG_SPEC}) + SDL_missing_dependency(XCURSOR SDL_X11_XCURSOR) endif() - set(SDL_VIDEO_DRIVER_X11_XCURSOR 1) endif() - if(SDL_X11_XDBE AND HAVE_XDBE_H) - set(HAVE_X11_XDBE TRUE) - set(SDL_VIDEO_DRIVER_X11_XDBE 1) + if(SDL_X11_XDBE) + if(HAVE_XDBE_H) + set(HAVE_X11_XDBE TRUE) + set(SDL_VIDEO_DRIVER_X11_XDBE 1) + else() + SDL_missing_dependency(XDBE SDL_X11_XDBE) + endif() endif() - if(SDL_X11_XINPUT AND HAVE_XINPUT2_H AND XI_LIB) - set(HAVE_X11_XINPUT TRUE) - if(HAVE_X11_SHARED) - set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 "\"${XI_LIB_SONAME}\"") - else() - sdl_link_dependency(xi LIBS X11::Xi CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xi_PKG_CONFIG_SPEC}) - endif() - set(SDL_VIDEO_DRIVER_X11_XINPUT2 1) + if(SDL_X11_XINPUT) + if(HAVE_XINPUT2_H AND XI_LIB) + set(HAVE_X11_XINPUT TRUE) + if(HAVE_X11_SHARED) + set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 "\"${XI_LIB_SONAME}\"") + else() + sdl_link_dependency(xi LIBS X11::Xi CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xi_PKG_CONFIG_SPEC}) + endif() + set(SDL_VIDEO_DRIVER_X11_XINPUT2 1) - # Check for multitouch - check_c_source_compiles(" - #include - #include - #include - int event_type = XI_TouchBegin; - XITouchClassInfo *t; - Status XIAllowTouchEvents(Display *a,int b,unsigned int c,Window d,int f) { - return (Status)0; - } - int main(int argc, char **argv) { return 0; }" HAVE_XINPUT2_MULTITOUCH) - if(HAVE_XINPUT2_MULTITOUCH) - set(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1) + # Check for scroll info + check_c_source_compiles(" + #include + #include + #include + XIScrollClassInfo *s; + int main(int argc, char **argv) {}" HAVE_XINPUT2_SCROLLINFO) + if(HAVE_XINPUT2_SCROLLINFO) + set(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 1) + endif() + + # Check for multitouch + check_c_source_compiles_static(" + #include + #include + #include + int event_type = XI_TouchBegin; + XITouchClassInfo *t; + Status XIAllowTouchEvents(Display *a,int b,unsigned int c,Window d,int f) { + return (Status)0; + } + int main(int argc, char **argv) { return 0; }" HAVE_XINPUT2_MULTITOUCH) + if(HAVE_XINPUT2_MULTITOUCH) + set(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1) + endif() + + # Check for gesture + check_c_source_compiles(" + #include + #include + #include + int event_type = XI_GesturePinchBegin; + XIGesturePinchEvent *t; + Status XIAllowTouchEvents(Display *a,int b,unsigned int c,Window d,int f) { + return (Status)0; + } + int main(int argc, char **argv) { return 0; }" HAVE_XINPUT2_GESTURE) + if(HAVE_XINPUT2_GESTURE) + set(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE 1) + endif() + + else() + SDL_missing_dependency(XINPUT SDL_X11_XINPUT) endif() endif() # check along with XInput2.h because we use Xfixes with XIBarrierReleasePointer if(SDL_X11_XFIXES AND HAVE_XFIXES_H_ AND HAVE_XINPUT2_H) - check_c_source_compiles(" + check_c_source_compiles_static(" #include #include #include @@ -433,44 +481,78 @@ macro(CheckX11) BarrierEventID b; int main(int argc, char **argv) { return 0; }" HAVE_XFIXES_H) endif() - if(SDL_X11_XFIXES AND HAVE_XFIXES_H AND HAVE_XINPUT2_H AND XFIXES_LIB) - if(HAVE_X11_SHARED) - set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XFIXES "\"${XFIXES_LIB_SONAME}\"") + if(SDL_X11_XFIXES) + if (HAVE_XFIXES_H AND HAVE_XINPUT2_H AND XFIXES_LIB) + if(HAVE_X11_SHARED) + set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XFIXES "\"${XFIXES_LIB_SONAME}\"") + else() + sdl_link_dependency(xfixes LIBS X11::Xfixes CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xfixes_PKG_CONFIG_SPEC}) + endif() + set(SDL_VIDEO_DRIVER_X11_XFIXES 1) + set(HAVE_X11_XFIXES TRUE) else() - sdl_link_dependency(xfixes LIBS X11::Xfixes CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xfixes_PKG_CONFIG_SPEC}) + SDL_missing_dependency(XFIXES SDL_X11_XFIXES) endif() - set(SDL_VIDEO_DRIVER_X11_XFIXES 1) - set(HAVE_X11_XFIXES TRUE) endif() - if(SDL_X11_XSYNC AND HAVE_XSYNC_H AND XEXT_LIB) - set(SDL_VIDEO_DRIVER_X11_XSYNC 1) - set(HAVE_X11_XSYNC TRUE) - endif() - - if(SDL_X11_XRANDR AND HAVE_XRANDR_H AND XRANDR_LIB) - if(HAVE_X11_SHARED) - set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR "\"${XRANDR_LIB_SONAME}\"") + if(SDL_X11_XSYNC) + if(HAVE_XSYNC_H AND XEXT_LIB) + set(SDL_VIDEO_DRIVER_X11_XSYNC 1) + set(HAVE_X11_XSYNC TRUE) else() - sdl_link_dependency(xrandr LIBS X11::Xrandr CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xrandr_PKG_CONFIG_SPEC}) + SDL_missing_dependency(XSYNC SDL_X11_XSYNC) endif() - set(SDL_VIDEO_DRIVER_X11_XRANDR 1) - set(HAVE_X11_XRANDR TRUE) endif() - if(SDL_X11_XSCRNSAVER AND HAVE_XSS_H AND XSS_LIB) - if(HAVE_X11_SHARED) - set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS "\"${XSS_LIB_SONAME}\"") + if(SDL_X11_XRANDR) + if(HAVE_XRANDR_H AND XRANDR_LIB) + if(HAVE_X11_SHARED) + set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR "\"${XRANDR_LIB_SONAME}\"") + else() + sdl_link_dependency(xrandr LIBS X11::Xrandr CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xrandr_PKG_CONFIG_SPEC}) + endif() + set(SDL_VIDEO_DRIVER_X11_XRANDR 1) + set(HAVE_X11_XRANDR TRUE) else() - sdl_link_dependency(xss LIBS X11::Xss CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xss_PKG_CONFIG_SPEC}) + SDL_missing_dependency(XRANDR SDL_X11_XRANDR) endif() - set(SDL_VIDEO_DRIVER_X11_XSCRNSAVER 1) - set(HAVE_X11_XSCRNSAVER TRUE) endif() - if(SDL_X11_XSHAPE AND HAVE_XSHAPE_H) - set(SDL_VIDEO_DRIVER_X11_XSHAPE 1) - set(HAVE_X11_XSHAPE TRUE) + if(SDL_X11_XSCRNSAVER) + if(HAVE_XSS_H AND XSS_LIB) + if(HAVE_X11_SHARED) + set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS "\"${XSS_LIB_SONAME}\"") + else() + sdl_link_dependency(xss LIBS X11::Xss CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xss_PKG_CONFIG_SPEC}) + endif() + set(SDL_VIDEO_DRIVER_X11_XSCRNSAVER 1) + set(HAVE_X11_XSCRNSAVER TRUE) + else() + SDL_missing_dependency(XSCRNSAVER SDL_X11_XSCRNSAVER) + endif() + endif() + + if(SDL_X11_XSHAPE) + if(HAVE_XSHAPE_H) + set(SDL_VIDEO_DRIVER_X11_XSHAPE 1) + set(HAVE_X11_XSHAPE TRUE) + else() + SDL_missing_dependency(XSHAPE SDL_X11_XSHAPE) + endif() + endif() + + if(SDL_X11_XTEST) + if(HAVE_XTEST_H AND XTST_LIB) + if(HAVE_X11_SHARED) + set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST "\"${XTST_LIB_SONAME}\"") + else() + sdl_link_dependency(xtst LIBS X11::Xtst CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xtst_PKG_CONFIG_SPEC}) + endif() + set(SDL_VIDEO_DRIVER_X11_XTEST 1) + set(HAVE_X11_XTEST TRUE) + else() + SDL_missing_dependency(XTEST SDL_X11_XTEST) + endif() endif() endif() endif() @@ -481,6 +563,56 @@ macro(CheckX11) cmake_pop_check_state() endmacro() +macro(CheckFribidi) + if(SDL_FRIBIDI) + set(FRIBIDI_PKG_CONFIG_SPEC fribidi) + set(PC_FRIBIDI_FOUND FALSE) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FRIBIDI IMPORTED_TARGET ${FRIBIDI_PKG_CONFIG_SPEC}) + endif() + if(PC_FRIBIDI_FOUND) + set(HAVE_FRIBIDI TRUE) + set(HAVE_FRIBIDI_H 1) + if(SDL_FRIBIDI_SHARED AND NOT HAVE_SDL_LOADSO) + message(WARNING "You must have SDL_LoadObject() support for dynamic fribidi loading") + endif() + FindLibraryAndSONAME("fribidi" LIBDIRS ${PC_FRIBIDI_LIBRARY_DIRS}) + if(SDL_FRIBIDI_SHARED AND FRIBIDI_LIB AND HAVE_SDL_LOADSO) + set(SDL_FRIBIDI_DYNAMIC "\"${FRIBIDI_LIB_SONAME}\"") + set(HAVE_FRIBIDI_SHARED TRUE) + sdl_include_directories(PRIVATE SYSTEM $) + else() + sdl_link_dependency(fribidi LIBS PkgConfig::PC_FRIBIDI PKG_CONFIG_PREFIX PC_FRIBIDI PKG_CONFIG_SPECS ${FRIBIDI_PKG_CONFIG_SPEC}) + endif() + endif() + endif() +endmacro() + +macro(CheckLibThai) + if(SDL_LIBTHAI) + set(LIBTHAI_PKG_CONFIG_SPEC libthai) + set(PC_LIBTHAI_FOUND FALSE) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_LIBTHAI IMPORTED_TARGET ${LIBTHAI_PKG_CONFIG_SPEC}) + endif() + if(PC_LIBTHAI_FOUND) + set(HAVE_LIBTHAI TRUE) + set(HAVE_LIBTHAI_H 1) + if(SDL_LIBTHAI_SHARED AND NOT HAVE_SDL_LOADSO) + message(WARNING "You must have SDL_LoadObject() support for dynamic libthai loading") + endif() + FindLibraryAndSONAME("thai" LIBDIRS ${PC_LIBTHAI_LIBRARY_DIRS}) + if(SDL_LIBTHAI_SHARED AND THAI_LIB AND HAVE_SDL_LOADSO) + set(SDL_LIBTHAI_DYNAMIC "\"${THAI_LIB_SONAME}\"") + set(HAVE_LIBTHAI_SHARED TRUE) + sdl_include_directories(PRIVATE SYSTEM $) + else() + sdl_link_dependency(libthai LIBS PkgConfig::PC_LIBTHAI PKG_CONFIG_PREFIX PC_LIBTHAI PKG_CONFIG_SPECS ${LIBTHAI_PKG_CONFIG_SPEC}) + endif() + endif() + endif() +endmacro() + macro(WaylandProtocolGen _SCANNER _CODE_MODE _XML _PROTL) set(_WAYLAND_PROT_C_CODE "${CMAKE_CURRENT_BINARY_DIR}/wayland-generated-protocols/${_PROTL}-protocol.c") set(_WAYLAND_PROT_H_CODE "${CMAKE_CURRENT_BINARY_DIR}/wayland-generated-protocols/${_PROTL}-client-protocol.h") @@ -575,6 +707,18 @@ macro(CheckWayland) sdl_link_dependency(wayland LIBS PkgConfig::PC_WAYLAND PKG_CONFIG_PREFIX PC_WAYLAND PKG_CONFIG_SPECS ${WAYLAND_PKG_CONFIG_SPEC}) endif() + # xkbcommon doesn't provide internal version defines, so generate them here. + if (PC_WAYLAND_xkbcommon_VERSION MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)") + set(SDL_XKBCOMMON_VERSION_MAJOR ${CMAKE_MATCH_1}) + set(SDL_XKBCOMMON_VERSION_MINOR ${CMAKE_MATCH_2}) + set(SDL_XKBCOMMON_VERSION_PATCH ${CMAKE_MATCH_3}) + else() + message(WARNING "Failed to parse xkbcommon version; defaulting to lowest supported (0.5.0)") + set(SDL_XKBCOMMON_VERSION_MAJOR 0) + set(SDL_XKBCOMMON_VERSION_MINOR 5) + set(SDL_XKBCOMMON_VERSION_PATCH 0) + endif() + if(SDL_WAYLAND_LIBDECOR) set(LibDecor_PKG_CONFIG_SPEC libdecor-0) pkg_check_modules(PC_LIBDECOR IMPORTED_TARGET ${LibDecor_PKG_CONFIG_SPEC}) @@ -681,13 +825,29 @@ macro(CheckOpenVR) endif() endmacro() +# Requires +# - N/A +macro(FindOpenGLHeaders) + find_package(OpenGL MODULE) + # OPENGL_INCLUDE_DIRS is preferred over OPENGL_INCLUDE_DIR, but was only added in 3.29, + # If the CMake minimum version is changed to be >= 3.29, the second check should be removed. + if(OPENGL_INCLUDE_DIRS) + list(APPEND CMAKE_REQUIRED_INCLUDES ${OPENGL_INCLUDE_DIRS}) + elseif(OPENGL_INCLUDE_DIR) + list(APPEND CMAKE_REQUIRED_INCLUDES ${OPENGL_INCLUDE_DIR}) + endif() +endmacro() + # Requires: # - nada macro(CheckGLX) if(SDL_OPENGL) + cmake_push_check_state() + FindOpenGLHeaders() check_c_source_compiles(" #include int main(int argc, char** argv) { return 0; }" HAVE_OPENGL_GLX) + cmake_pop_check_state() if(HAVE_OPENGL_GLX AND NOT HAVE_ROCKCHIP) set(SDL_VIDEO_OPENGL_GLX 1) endif() @@ -721,10 +881,13 @@ endmacro() # - nada macro(CheckOpenGL) if(SDL_OPENGL) + cmake_push_check_state() + FindOpenGLHeaders() check_c_source_compiles(" #include #include int main(int argc, char** argv) { return 0; }" HAVE_OPENGL) + cmake_pop_check_state() if(HAVE_OPENGL) set(SDL_VIDEO_OPENGL 1) set(SDL_VIDEO_RENDER_OGL 1) @@ -737,6 +900,7 @@ endmacro() macro(CheckOpenGLES) if(SDL_OPENGLES) cmake_push_check_state() + FindOpenGLHeaders() list(APPEND CMAKE_REQUIRED_INCLUDES "${SDL3_SOURCE_DIR}/src/video/khronos") check_c_source_compiles(" #include @@ -795,7 +959,7 @@ endmacro() macro(CheckPTHREAD) cmake_push_check_state() if(SDL_PTHREADS) - if(ANDROID) + if(ANDROID OR SDL_PTHREADS_PRIVATE) # the android libc provides built-in support for pthreads, so no # additional linking or compile flags are necessary elseif(LINUX) @@ -842,6 +1006,9 @@ macro(CheckPTHREAD) set(PTHREAD_LDFLAGS "-pthread") elseif(QNX) # pthread support is baked in + elseif(HURD) + set(PTHREAD_CFLAGS "-D_REENTRANT") + set(PTHREAD_LDFLAGS "-pthread") else() set(PTHREAD_CFLAGS "-D_REENTRANT") set(PTHREAD_LDFLAGS "-lpthread") @@ -946,22 +1113,22 @@ macro(CheckUSBHID) cmake_push_check_state() check_library_exists(usbhid hid_init "" LIBUSBHID) if(LIBUSBHID) - check_include_file(usbhid.h HAVE_USBHID_H) + check_include_files("stdint.h;usbhid.h" HAVE_USBHID_H) if(HAVE_USBHID_H) set(USB_CFLAGS "-DHAVE_USBHID_H") endif() - check_include_file(libusbhid.h HAVE_LIBUSBHID_H) + check_include_files("stdint.h;libusbhid.h" HAVE_LIBUSBHID_H) if(HAVE_LIBUSBHID_H) string(APPEND USB_CFLAGS " -DHAVE_LIBUSBHID_H") endif() set(USB_LIBS ${USB_LIBS} usbhid) else() - check_include_file(usb.h HAVE_USB_H) + check_include_files("stdint.h;usb.h" HAVE_USB_H) if(HAVE_USB_H) set(USB_CFLAGS "-DHAVE_USB_H") endif() - check_include_file(libusb.h HAVE_LIBUSB_H) + check_include_files("stdint.h;libusb.h" HAVE_LIBUSB_H) if(HAVE_LIBUSB_H) string(APPEND USB_CFLAGS " -DHAVE_LIBUSB_H") endif() @@ -974,7 +1141,7 @@ macro(CheckUSBHID) string(APPEND CMAKE_REQUIRED_FLAGS " ${USB_CFLAGS}") list(APPEND CMAKE_REQUIRED_LIBRARIES ${USB_LIBS}) check_c_source_compiles(" - #include + #include #if defined(HAVE_USB_H) #include #endif @@ -1000,7 +1167,7 @@ macro(CheckUSBHID) }" HAVE_USBHID) if(HAVE_USBHID) check_c_source_compiles(" - #include + #include #if defined(HAVE_USB_H) #include #endif @@ -1028,7 +1195,7 @@ macro(CheckUSBHID) endif() check_c_source_compiles(" - #include + #include #if defined(HAVE_USB_H) #include #endif @@ -1093,7 +1260,7 @@ macro(CheckHIDAPI) if(LibUSB_FOUND) cmake_push_check_state() list(APPEND CMAKE_REQUIRED_LIBRARIES LibUSB::LibUSB) - check_c_source_compiles(" + check_c_source_compiles_static(" #include #include int main(int argc, char **argv) { @@ -1103,12 +1270,15 @@ macro(CheckHIDAPI) cmake_pop_check_state() if(HAVE_LIBUSB_H) set(HAVE_LIBUSB TRUE) - target_get_dynamic_library(dynamic_libusb LibUSB::LibUSB) - if(SDL_HIDAPI_LIBUSB_SHARED AND dynamic_libusb) - set(HAVE_HIDAPI_LIBUSB_SHARED ON) - set(SDL_LIBUSB_DYNAMIC "\"${dynamic_libusb}\"") - sdl_link_dependency(hidapi INCLUDES $) - else() + if(SDL_HIDAPI_LIBUSB_SHARED) + target_get_dynamic_library(dynamic_libusb LibUSB::LibUSB) + if(dynamic_libusb) + set(HAVE_HIDAPI_LIBUSB_SHARED ON) + set(SDL_LIBUSB_DYNAMIC "\"${dynamic_libusb}\"") + sdl_link_dependency(hidapi INCLUDES $) + endif() + endif() + if(NOT HAVE_HIDAPI_LIBUSB_SHARED) sdl_link_dependency(hidapi LIBS LibUSB::LibUSB PKG_CONFIG_SPECS "${LibUSB_PKG_CONFIG_SPEC}" CMAKE_MODULE LibUSB) endif() endif() @@ -1124,6 +1294,7 @@ macro(CheckHIDAPI) set(HAVE_SDL_JOYSTICK TRUE) set(HAVE_HIDAPI_JOYSTICK TRUE) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/hidapi/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/hidapi/*.c") endif() else() set(SDL_HIDAPI_DISABLED 1) diff --git a/libs/SDL3/cmake/sdltargets.cmake b/libs/SDL3/cmake/sdlcommands.cmake similarity index 94% rename from libs/SDL3/cmake/sdltargets.cmake rename to libs/SDL3/cmake/sdlcommands.cmake index d658eb3..58889d1 100644 --- a/libs/SDL3/cmake/sdltargets.cmake +++ b/libs/SDL3/cmake/sdlcommands.cmake @@ -1,18 +1,40 @@ add_library(SDL3-collector INTERFACE) add_library(SDL3_test-collector INTERFACE) +function(sdl_source_group prefix_directory) + set(prefixed_list) + file(TO_CMAKE_PATH ${prefix_directory} normalized_prefix_path) + foreach(file in ${ARGN}) + file(TO_CMAKE_PATH ${file} normalized_path) + string(FIND "${normalized_path}" ${normalized_prefix_path} position) + if("${position}" EQUAL 0) + list(APPEND prefixed_list ${file}) + endif() + endforeach() + if(prefixed_list) + source_group(TREE ${prefix_directory} FILES ${prefixed_list}) + endif() +endfunction() + # Use sdl_glob_sources to add glob sources to SDL3-shared, to SDL3-static, or to both. function(sdl_glob_sources) cmake_parse_arguments(ARGS "" "" "SHARED;STATIC" ${ARGN}) - file(GLOB shared_sources ${ARGS_SHARED}) - file(GLOB static_sources ${ARGS_STATIC}) - file(GLOB both_sources ${ARGS_UNPARSED_ARGUMENTS}) + if(ARGS_SHARED) + file(GLOB shared_sources CONFIGURE_DEPENDS ${ARGS_SHARED}) + endif() + if(ARGS_STATIC) + file(GLOB static_sources CONFIGURE_DEPENDS ${ARGS_STATIC}) + endif() + if(ARGS_UNPARSED_ARGUMENTS) + file(GLOB both_sources CONFIGURE_DEPENDS ${ARGS_UNPARSED_ARGUMENTS}) + endif() if(TARGET SDL3-shared) target_sources(SDL3-shared PRIVATE ${shared_sources} ${both_sources}) endif() if(TARGET SDL3-static) target_sources(SDL3-static PRIVATE ${static_sources} ${both_sources}) endif() + sdl_source_group(${PROJECT_SOURCE_DIR} ${shared_sources} ${shared_sources} ${both_sources}) set_property(TARGET SDL3-collector APPEND PROPERTY INTERFACE_SOURCES ${shared_sources} ${static_sources} ${both_sources}) endfunction() @@ -25,6 +47,7 @@ function(sdl_sources) if(TARGET SDL3-static) target_sources(SDL3-static PRIVATE ${ARGS_STATIC} ${ARGS_UNPARSED_ARGUMENTS}) endif() + sdl_source_group(${PROJECT_SOURCE_DIR} ${ARGS_SHARED} ${ARGS_STATIC} ${ARGS_UNPARSED_ARGUMENTS}) set_property(TARGET SDL3-collector APPEND PROPERTY INTERFACE_SOURCES ${ARGS_SHARED} ${ARGS_STATIC} ${ARGS_UNPARSED_ARGUMENTS}) endfunction() @@ -131,7 +154,7 @@ function(sdl_compile_options) target_compile_options(SDL3-static ${visibility} ${escaped_opts}) endif() if(NOT ARGS_NO_EXPORT AND (ARGS_PUBLIC OR ARGS_INTERFACE)) - set_property(TARGET SDL3-collector APPEND PROPERTY INTERFACE_COMPILE_COMPILE_OPTIONS "${ARGS_UNPARSED_ARGUMENTS}") + set_property(TARGET SDL3-collector APPEND PROPERTY INTERFACE_COMPILE_OPTIONS "${ARGS_UNPARSED_ARGUMENTS}") endif() endfunction() diff --git a/libs/SDL3/cmake/sdlcompilers.cmake b/libs/SDL3/cmake/sdlcompilers.cmake index c3d8c47..ab62c50 100644 --- a/libs/SDL3/cmake/sdlcompilers.cmake +++ b/libs/SDL3/cmake/sdlcompilers.cmake @@ -3,6 +3,7 @@ macro(SDL_DetectCompiler) set(USE_GCC FALSE) set(USE_INTELCC FALSE) set(USE_QCC FALSE) + set(USE_TCC FALSE) if(CMAKE_C_COMPILER_ID MATCHES "Clang|IntelLLVM") set(USE_CLANG TRUE) # Visual Studio 2019 v16.2 added support for Clang/LLVM. @@ -16,6 +17,8 @@ macro(SDL_DetectCompiler) set(USE_INTELCC TRUE) elseif(CMAKE_C_COMPILER_ID MATCHES "QCC") set(USE_QCC TRUE) + elseif(CMAKE_C_COMPILER_ID MATCHES "TinyCC") + set(USE_TCC TRUE) endif() endmacro() @@ -39,7 +42,7 @@ function(SDL_AddCommonCompilerFlags TARGET) cmake_pop_check_state() endif() - if(USE_GCC OR USE_CLANG OR USE_INTELCC OR USE_QCC) + if(USE_GCC OR USE_CLANG OR USE_INTELCC OR USE_QCC OR USE_TCC) if(MINGW) # See if GCC's -gdwarf-4 is supported # See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101377 for why this is needed on Windows @@ -159,4 +162,68 @@ function(SDL_AddCommonCompilerFlags TARGET) sdl_target_compile_option_all_languages(${TARGET} "-fdiagnostics-color=always") endif() endif() + + if(USE_TCC) + sdl_target_compile_option_all_languages(${TARGET} "-DSTBI_NO_SIMD") + endif() +endfunction() + +function(check_x86_source_compiles BODY VAR) + if(ARGN) + message(FATAL_ERROR "Unknown arguments: ${ARGN}") + endif() + if(APPLE_MULTIARCH AND (SDL_CPU_X86 OR SDL_CPU_X64)) + set(test_conditional 1) + else() + set(test_conditional 0) + endif() + check_c_source_compiles(" + #if ${test_conditional} + # if defined(__i386__) || defined(__x86_64__) + # define test_enabled 1 + # else + # define test_enabled 0 /* feign success in Apple multi-arch configs */ + # endif + #else /* test normally */ + # define test_enabled 1 + #endif + #if test_enabled + ${BODY} + #else + int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + return 0; + } + #endif" ${VAR}) +endfunction() + +function(check_arm_source_compiles BODY VAR) + if(ARGN) + message(FATAL_ERROR "Unknown arguments: ${ARGN}") + endif() + if(APPLE_MULTIARCH AND (SDL_CPU_ARM32 OR SDL_CPU_ARM64)) + set(test_conditional 1) + else() + set(test_conditional 0) + endif() + check_c_source_compiles(" + #if ${test_conditional} + # if defined(__arm__) || defined(__aarch64__) + # define test_enabled 1 + # else + # define test_enabled 0 /* feign success in Apple multi-arch configs */ + # endif + #else /* test normally */ + # define test_enabled 1 + #endif + #if test_enabled + ${BODY} + #else + int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + return 0; + } + #endif" ${VAR}) endfunction() diff --git a/libs/SDL3/cmake/sdlcpu.cmake b/libs/SDL3/cmake/sdlcpu.cmake index 0c2ca1f..a27e732 100644 --- a/libs/SDL3/cmake/sdlcpu.cmake +++ b/libs/SDL3/cmake/sdlcpu.cmake @@ -1,18 +1,18 @@ function(SDL_DetectTargetCPUArchitectures DETECTED_ARCHS) - set(known_archs EMSCRIPTEN ARM32 ARM64 ARM64EC LOONGARCH64 POWERPC32 POWERPC64 X86 X64) + set(known_archs EMSCRIPTEN ARM32 ARM64 ARM64EC LOONGARCH64 POWERPC32 POWERPC64 RISCV32 RISCV64 X86 X64) if(APPLE AND CMAKE_OSX_ARCHITECTURES) foreach(known_arch IN LISTS known_archs) - set(SDL_CPU_${known_arch} "0") + set(SDL_CPU_${known_arch} "0" PARENT_SCOPE) endforeach() set(detected_archs) foreach(osx_arch IN LISTS CMAKE_OSX_ARCHITECTURES) if(osx_arch STREQUAL "x86_64") - set(SDL_CPU_X64 "1") + set(SDL_CPU_X64 "1" PARENT_SCOPE) list(APPEND detected_archs "X64") elseif(osx_arch STREQUAL "arm64") - set(SDL_CPU_ARM64 "1") + set(SDL_CPU_ARM64 "1" PARENT_SCOPE) list(APPEND detected_archs "ARM64") endif() endforeach() @@ -39,6 +39,8 @@ function(SDL_DetectTargetCPUArchitectures DETECTED_ARCHS) set(arch_check_LOONGARCH64 "defined(__loongarch64)") set(arch_check_POWERPC32 "(defined(__PPC__) || defined(__powerpc__)) && !defined(__powerpc64__)") set(arch_check_POWERPC64 "defined(__PPC64__) || defined(__powerpc64__)") + set(arch_check_RISCV32 "defined(__riscv) && defined(__riscv_xlen) && __riscv_xlen == 32") + set(arch_check_RISCV64 "defined(__riscv) && defined(__riscv_xlen) && __riscv_xlen == 64") set(arch_check_X86 "defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) ||defined( __i386) || defined(_M_IX86)") set(arch_check_X64 "(defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64)) && !defined(_M_ARM64EC)") diff --git a/libs/SDL3/cmake/sdlplatform.cmake b/libs/SDL3/cmake/sdlplatform.cmake index 677b187..f16fe3f 100644 --- a/libs/SDL3/cmake/sdlplatform.cmake +++ b/libs/SDL3/cmake/sdlplatform.cmake @@ -22,8 +22,12 @@ function(SDL_DetectCMakePlatform) set(sdl_cmake_platform Haiku) elseif(NINTENDO_3DS) set(sdl_cmake_platform n3ds) + elseif(NGAGESDK) + set(sdl_cmake_platform ngage) elseif(PS2) set(sdl_cmake_platform ps2) + elseif(RISCOS) + set(sdl_cmake_platform RISCOS) elseif(VITA) set(sdl_cmake_platform Vita) elseif(CMAKE_SYSTEM_NAME MATCHES ".*Linux") @@ -34,8 +38,9 @@ function(SDL_DetectCMakePlatform) set(sdl_cmake_platform NetBSD) elseif(CMAKE_SYSTEM_NAME MATCHES "kOpenBSD.*|OpenBSD.*") set(sdl_cmake_platform OpenBSD) - elseif(CMAKE_SYSTEM_NAME MATCHES ".*GNU.*") - set(sdl_cmake_platform GNU) + elseif(CMAKE_SYSTEM_NAME STREQUAL "GNU") + # GNU/Hurd must be checked AFTER RISCOS + set(sdl_cmake_platform Hurd) elseif(CMAKE_SYSTEM_NAME MATCHES ".*BSDI.*") set(sdl_cmake_platform BSDi) elseif(CMAKE_SYSTEM_NAME MATCHES "DragonFly.*|FreeBSD") diff --git a/libs/SDL3/cmake/test/CMakeLists.txt b/libs/SDL3/cmake/test/CMakeLists.txt index e3766f0..ffaa197 100644 --- a/libs/SDL3/cmake/test/CMakeLists.txt +++ b/libs/SDL3/cmake/test/CMakeLists.txt @@ -96,12 +96,14 @@ if(TEST_STATIC) add_executable(gui-static WIN32 main_gui.c) target_link_libraries(gui-static PRIVATE SDL3::SDL3-static) - # Assume SDL library has been built with `set(CMAKE_POSITION_INDEPENDENT_CODE ON)` - add_library(sharedlib-static SHARED main_lib.c) - target_link_libraries(sharedlib-static PRIVATE SDL3::SDL3-static) - generate_export_header(sharedlib-static EXPORT_MACRO_NAME MYLIBRARY_EXPORT) - target_compile_definitions(sharedlib-static PRIVATE "EXPORT_HEADER=\"${CMAKE_CURRENT_BINARY_DIR}/sharedlib-static_export.h\"") - set_target_properties(sharedlib-static PROPERTIES C_VISIBILITY_PRESET "hidden") + if(TEST_SHARED) + # Assume SDL library has been built with `set(CMAKE_POSITION_INDEPENDENT_CODE ON)` + add_library(sharedlib-static SHARED main_lib.c) + target_link_libraries(sharedlib-static PRIVATE SDL3::SDL3-static) + generate_export_header(sharedlib-static EXPORT_MACRO_NAME MYLIBRARY_EXPORT) + target_compile_definitions(sharedlib-static PRIVATE "EXPORT_HEADER=\"${CMAKE_CURRENT_BINARY_DIR}/sharedlib-static_export.h\"") + set_target_properties(sharedlib-static PROPERTIES C_VISIBILITY_PRESET "hidden") + endif() if(TEST_TEST) add_executable(sdltest-static sdltest.c) diff --git a/libs/SDL3/cmake/test/main_gui.c b/libs/SDL3/cmake/test/main_gui.c index 18ed101..c0c4f90 100644 --- a/libs/SDL3/cmake/test/main_gui.c +++ b/libs/SDL3/cmake/test/main_gui.c @@ -1,24 +1,37 @@ -#include +#define SDL_MAIN_USE_CALLBACKS #include +#include -int main(int argc, char *argv[]) +static SDL_Window *window; + +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppIterate(void *appstate) { - SDL_Window *window = NULL; SDL_Surface *screenSurface = NULL; + screenSurface = SDL_GetWindowSurface(window); + SDL_FillSurfaceRect(screenSurface, NULL, SDL_MapSurfaceRGB(screenSurface, 0xff, 0xff, 0xff)); + SDL_UpdateWindowSurface(window); + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ if (!SDL_Init(SDL_INIT_VIDEO)) { SDL_Log("Could not initialize SDL: %s", SDL_GetError()); - return 1; + return SDL_APP_FAILURE; } window = SDL_CreateWindow("Hello SDL", 640, 480, 0); if (!window) { SDL_Log("could not create window: %s", SDL_GetError()); - return 1; + return SDL_APP_FAILURE; } - screenSurface = SDL_GetWindowSurface(window); - SDL_FillSurfaceRect(screenSurface, NULL, SDL_MapSurfaceRGB(screenSurface, 0xff, 0xff, 0xff)); - SDL_UpdateWindowSurface(window); - SDL_Delay(100); - SDL_DestroyWindow(window); - SDL_Quit(); - return 0; + return SDL_APP_CONTINUE; +} + +void SDL_AppQuit(void *appstate, SDL_AppResult result) { + SDL_DestroyWindow(window); } diff --git a/libs/SDL3/cmake/test/sdltest.c b/libs/SDL3/cmake/test/sdltest.c index f598a98..baf8e9b 100644 --- a/libs/SDL3/cmake/test/sdltest.c +++ b/libs/SDL3/cmake/test/sdltest.c @@ -1,9 +1,24 @@ +#define SDL_MAIN_USE_CALLBACKS #include +#include #include +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + return SDL_APP_SUCCESS; +} -int main(int argc, char *argv[]) { +SDL_AppResult SDL_AppIterate(void *appstate) +{ + return SDL_APP_SUCCESS; +} + +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ SDLTest_CommonState state; SDLTest_CommonDefaultArgs(&state, argc, argv); - return 0; + return SDL_APP_SUCCESS; +} + +void SDL_AppQuit(void *appstate, SDL_AppResult result) { } diff --git a/libs/SDL3/cmake/test/test_pkgconfig.sh b/libs/SDL3/cmake/test/test_pkgconfig.sh index 5bb84df..7362e74 100644 --- a/libs/SDL3/cmake/test/test_pkgconfig.sh +++ b/libs/SDL3/cmake/test/test_pkgconfig.sh @@ -30,12 +30,12 @@ SDL_LDFLAGS="$( pkg-config sdl3 --libs )" SDL_STATIC_LDFLAGS="$( pkg-config sdl3 --libs --static )" compile_cmd="$CC -c "$testdir/main_gui.c" -o main_gui_pkgconfig.c.o $SDL_CFLAGS $CFLAGS" -link_cmd="$CC main_gui_pkgconfig.c.o -o ${EXEPREFIX}main_gui_pkgconfig${EXESUFFIX} $SDL_LDFLAGS $LDFLAGS" -static_link_cmd="$CC main_gui_pkgconfig.c.o -o ${EXEPREFIX}main_gui_pkgconfig_static${EXESUFFIX} $SDL_STATIC_LDFLAGS $LDFLAGS" +link_cmd="$CC main_gui_pkgconfig.c.o -o ${EXEPREFIX}main_gui_pkgconfig${EXESUFFIX} $SDL_CFLAGS $CFLAGS $SDL_LDFLAGS $LDFLAGS" +static_link_cmd="$CC main_gui_pkgconfig.c.o -o ${EXEPREFIX}main_gui_pkgconfig_static${EXESUFFIX} $SDL_CFLAGS $CFLAGS $SDL_STATIC_LDFLAGS $LDFLAGS" echo "-- CC: $CC" echo "-- CFLAGS: $CFLAGS" -echo "-- LDFLASG: $LDFLAGS" +echo "-- LDFLAGS: $LDFLAGS" echo "-- SDL_CFLAGS: $SDL_CFLAGS" echo "-- SDL_LDFLAGS: $SDL_LDFLAGS" echo "-- SDL_STATIC_LDFLAGS: $SDL_STATIC_LDFLAGS" diff --git a/libs/SDL3/docs/INTRO-emscripten.md b/libs/SDL3/docs/INTRO-emscripten.md index a0541c8..db6f5cc 100644 --- a/libs/SDL3/docs/INTRO-emscripten.md +++ b/libs/SDL3/docs/INTRO-emscripten.md @@ -22,7 +22,7 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$") # This assumes the SDL source is available in vendored/SDL add_subdirectory(vendored/SDL EXCLUDE_FROM_ALL) -# on Web targets, we need CMake to generate a HTML webpage. +# on Web targets, we need CMake to generate a HTML webpage. if(EMSCRIPTEN) set(CMAKE_EXECUTABLE_SUFFIX ".html" CACHE INTERNAL "") endif() diff --git a/libs/SDL3/docs/INTRO-mingw.md b/libs/SDL3/docs/INTRO-mingw.md index d7c535d..d6b425a 100644 --- a/libs/SDL3/docs/INTRO-mingw.md +++ b/libs/SDL3/docs/INTRO-mingw.md @@ -1,6 +1,6 @@ # 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. +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. @@ -89,7 +89,7 @@ This should print out which library directory we'll need to use when compiling, 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 +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/README-android.md b/libs/SDL3/docs/README-android.md index e57ca81..66e5ea6 100644 --- a/libs/SDL3/docs/README-android.md +++ b/libs/SDL3/docs/README-android.md @@ -149,7 +149,7 @@ target_link_libraries(yourgame PRIVATE SDL3::SDL3) If you use ndk-build, add the following before `include $(BUILD_SHARED_LIBRARY)` to your `Android.mk`: ``` -LOCAL_SHARED_LIBARARIES := SDL3 SDL3-Headers +LOCAL_SHARED_LIBRARIES := SDL3 SDL3-Headers ``` And add the following at the bottom: ``` @@ -598,7 +598,7 @@ The only caveat is that the APK's support a single architecture. When configuring the CMake project, you need to use the Android NDK CMake toolchain, and pass the Android home path through `SDL_ANDROID_HOME`. ``` -cmake .. -DCMAKE_TOOLCHAIN_FILE= -DANDROID_ABI= -DSDL_ANDROID_HOME= -DANDROID_PLATFORM=23 -DSDL_TESTS=ON +cmake .. -DCMAKE_TOOLCHAIN_FILE= -DANDROID_ABI= -DSDL_ANDROID_HOME= -DANDROID_PLATFORM=21 -DSDL_TESTS=ON ``` Remarks: diff --git a/libs/SDL3/docs/README-bsd.md b/libs/SDL3/docs/README-bsd.md index d823060..0f94470 100644 --- a/libs/SDL3/docs/README-bsd.md +++ b/libs/SDL3/docs/README-bsd.md @@ -4,3 +4,4 @@ SDL is fully supported on BSD platforms, and is built using [CMake](README-cmake If you want to run on the console, you can take a look at [KMSDRM support on BSD](README-kmsbsd.md) +SDL is [not designed to be used in setuid or setgid executables](README-platforms.md#setuid). diff --git a/libs/SDL3/docs/README-cmake.md b/libs/SDL3/docs/README-cmake.md index f2e4759..7aea86e 100644 --- a/libs/SDL3/docs/README-cmake.md +++ b/libs/SDL3/docs/README-cmake.md @@ -157,7 +157,7 @@ flags to the compiler. - Use [`CMAKE_EXE_LINKER_FLAGS`](https://cmake.org/cmake/help/latest/variable/CMAKE_EXE_LINKER_FLAGS.html) to pass extra option to the linker for executables. - Use [`CMAKE_SHARED_LINKER_FLAGS`](https://cmake.org/cmake/help/latest/variable/CMAKE_SHARED_LINKER_FLAGS.html) to pass extra options to the linker for shared libraries. -#### Examples +#### Compile Options Examples - build a SDL library optimized for (more) modern x64 microprocessor architectures. @@ -240,7 +240,7 @@ Append with a version number to target a specific SDK revision: e.g. `iphoneos12 CMake documentation: [link](https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_SYSROOT.html) -#### Examples +#### Apple Examples - for macOS, building a dylib and/or static library for x86_64 and arm64: @@ -328,8 +328,8 @@ Configure your project with `-DSDL_LIBC=ON` to make use of sanitizers. ### CMake fails to build without X11 or Wayland support Install the required system packages prior to running CMake. -See [README-linux](linux#build-dependencies) for the list of dependencies on Linux. -Other unix operationg systems should provide similar packages. +See [README-linux.md](README-linux.md#build-dependencies) for the list of dependencies on Linux. +Other unix operating systems should provide similar packages. If you **really** don't need to show windows, add `-DSDL_UNIX_CONSOLE_BUILD=ON` to the CMake configure command. diff --git a/libs/SDL3/docs/README-documentation-rules.md b/libs/SDL3/docs/README-documentation-rules.md index 3151de7..2a4d96f 100644 --- a/libs/SDL3/docs/README-documentation-rules.md +++ b/libs/SDL3/docs/README-documentation-rules.md @@ -34,6 +34,12 @@ things, you might confuse it. This is to the benefit of documentation, though, where we would rather you not do surprising things. +## UTF-8 only! + +All text must be UTF-8 encoded. The wiki will refuse to update files that are +malformed. + + ## We _sort of_ write in Doxygen format. To document a symbol, we use something that looks like Doxygen (and Javadoc) @@ -156,6 +162,23 @@ wikiheaders will wordwrap header comments so they fit in 80 columns, so if you don't leave a blank line between paragraphs, they will smush into a single block of text when wordwrapping. +## Lists must be the start of a new paragraph. + +If you write this: + +``` +Here is some text without a blank line +before an unordered list! +- item a +- item b +- item c +``` + +...then wikiheaders will word wrap this as a single paragraph, mangling the list. + +Put a blank line before the list, and everything will format and wrap correctly. + +This is a limitation of wikiheaders. Don't get bit by it! ## Don't worry about word wrapping. @@ -251,6 +274,23 @@ comment. So don't mention the type a second time in the documentation if possible. It looks cluttered and repetitive to do so. +## Keep `\param` and `\returns` sections short. + +These strings end up in a table that we don't want to be bulky. +Try to keep these to one sentence/phrase where possible. If you need more +detail--even extremely common details, like "you need to free the returned +pointer"--put that information in the general Remarks section, where you +can be as verbose as you like. + +(One exception for SDL: the return value almost always notes that on error, +you should call SDL_GetError() to get more information. The documentation +is so saturated with this that it's just the standard now.) + +Convention at the moment is that pointer params that are permitted to +be NULL, which is somewhat uncommon, end with terse "May be NULL." sentence +at the end, and pointers that must be non-NULL (most of them) say nothing. +This is fine. + ## Code examples go in the wiki. We don't want the headers cluttered up with code examples. These live on the @@ -304,6 +344,21 @@ to the headers: - "Version" - "See Also" +## Unrecognized sections are removed from the headers! + +If you add Doxygen with a `##` (`###`, etc) section header, it'll +migrate to the wiki and be _removed_ from the headers. Generally +the correct thing to do is _never use section headers in the Doxygen_. + +## wikiheaders will reorder standard sections. + +The standard sections are always kept in a consistent order by +wikiheaders, both in the headers and the wiki. If they're placed in +a non-standard order, wikiheaders will reorder them. + +For sections that aren't standard, wikiheaders will place them at +the end of the wiki page, in the order they were seen when it loaded +the page for processing. ## It's okay to repeat yourself. @@ -318,7 +373,7 @@ through, header users can search for the function name. You might be reading this document on the wiki! Any `README-*.md` files in the docs directory are bridged to the wiki, so `docs/README-linux.md` lands -at https://wiki.libsdl.org/SDL3/README/linux ...these are just copied directly +at https://wiki.libsdl.org/SDL3/README-linux ...these are just copied directly without any further processing by wikiheaders, and changes go in both directions. @@ -408,3 +463,19 @@ Beyond stripping the initial ` * ` portion off each line, these comments are treated as pure Markdown. They don't support any Doxygen tags like `\sa` or `\since`. +## Enum/struct versioning + +If you have an enum or struct, it'll list its `\since` field as the first SDL +release it was available in. However, we might later add new values to an enum +or fields to a struct. These lines, arriving in a newer version, should have a +note about that, like this one on SDL_SCALEMODE_PIXELART: + +```c +typedef enum SDL_ScaleMode +{ + SDL_SCALEMODE_INVALID = -1, + SDL_SCALEMODE_NEAREST, /**< nearest pixel sampling */ + SDL_SCALEMODE_LINEAR, /**< linear filtering */ + SDL_SCALEMODE_PIXELART /**< nearest pixel sampling with improved scaling for pixel art (since SDL 3.3.0) */ +} SDL_ScaleMode; +``` diff --git a/libs/SDL3/docs/README-emscripten.md b/libs/SDL3/docs/README-emscripten.md index 2b81468..b9667f1 100644 --- a/libs/SDL3/docs/README-emscripten.md +++ b/libs/SDL3/docs/README-emscripten.md @@ -101,9 +101,9 @@ don't want any shutdown code that might be sitting below this code to actually run if main() were to continue on, since we're just getting started. -Another option is to use SDL' main callbacks, which handle this for you +Another option is to use SDL's main callbacks, which handle this for you without platform-specific code in your app. Please refer to -[the wiki](https://wiki.libsdl.org/SDL3/README/main-functions#main-callbacks-in-sdl3) +[the wiki](https://wiki.libsdl.org/SDL3/README-main-functions#main-callbacks-in-sdl3) or `docs/README-main-functions.md` in the SDL source code. @@ -169,31 +169,33 @@ for several reasons, not the least of which being that no one likes when a random browser tab suddenly starts making noise and the user has to scramble to figure out which and silence it. -SDL will allow you to open the audio device for playback in this -circumstance, and your audio callback will fire, but SDL will throw the audio -data away until the user interacts with the page. This helps apps that depend -on the audio callback to make progress, and also keeps audio playback in sync +SDL will allow you to open the audio device for playback in this circumstance, +and your audio streams will consume data, but SDL will throw the audio data +away until the user interacts with the page. This helps apps that depend on +the audio callback to make progress, and also keeps audio playback in sync once the app is finally allowed to make noise. There are two reasonable ways to deal with the silence at the app level: if you are writing some sort of media player thing, where the user expects there to be a volume control when you mouseover the canvas, just default that control to a muted state; if the user clicks on the control to unmute -it, on this first click, open the audio device. This allows the media to +it, on this first click, adjust your app's volume appropriately, and SDL will +also start actually feeding the data to the browser. This allows the media to play at start, and the user can reasonably opt-in to listening. -Many games do not have this sort of UI, and are more rigid about starting -audio along with everything else at the start of the process. For these, your -best bet is to write a little Javascript that puts up a "Click here to play!" -UI, and upon the user clicking, remove that UI and then call the Emscripten -app's main() function. As far as the application knows, the audio device was -available to be opened as soon as the program started, and since this magic -happens in a little Javascript, you don't have to change your C/C++ code at -all to make it happen. +Many games do not have this sort of UI. For these, your best bet might be to +write a little Javascript that puts up a "Click here to play!" UI, and upon +the user clicking, remove that UI and then call the Emscripten app's main() +function. As far as the application knows, audio was able to play as soon as +the program started, and since this magic happens in a little Javascript, you +don't have to change your C/C++ code at all to make it happen. Please see the discussion at https://github.com/libsdl-org/SDL/issues/6385 for some Javascript code to steal for this approach. +But if a game can just do without audio until the user clicks on the page, +it will still operate correctly, as if the page was merely muted before then. + ## Rendering @@ -208,12 +210,35 @@ Calling SDL_RenderPresent (or SDL_GL_SwapWindow) will not actually present anything on the screen until your return from your mainloop function. +Note that SDL attempts to default to vsync _off_ on all platforms. You almost +certainly do _not_ want this in Emscripten, however, as it will affect the +efficiency of the mainloop. If using OpenGL directly, you should call +SDL_GL_SetSwapInterval(1) sometime near startup; if using the 2D render API, +either create the renderer with with the property +SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER set to 1, or call +SDL_SetRenderVSync(renderer, 1). If you don't explicitly set vsync, you'll get +a higher (but perhaps unstable) framerate, and use more power, but it will +still work. Choosing a vsync of 1 will use requestAnimationFrame if possible. + +If you're using the SDL main callbacks, the mainloop defaults to using +requestAnimationFrame (effectively vsync), because it calls +emscripten_set_main_loop() with a zero fps. This is almost certainly what you +want to do! Do this even if you aren't using the main callbacks! +SDL will attempt to accomodate the app if it messes with vsync settings, or +doesn't use requestAnimationFrame, but modern thinking is that this is the +most efficient, consistent, and correct way to run a game in a web browser. + ## Building SDL/emscripten +Use the latest stable Emscripten release! -SDL currently requires at least Emscripten 3.16.0 to build. Newer versions -are likely to work, as well. +It's possible to build SDL with older Emscripten releases, such as 3.x, but +several things will be silently broken, as bugs got fixed and web standards +solidified over time. At the time of this writing, Emscripten 4.0.x is the +current stable release. You're encouraged to install the latest stable release +(`emsdk install latest ; emsdk activate latest` if using Emscripten's setup +script), and make sure you're reasonably up to date as time goes on. Build: @@ -230,7 +255,7 @@ tools. mkdir build cd build emcmake cmake .. -# you can also do `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command. +# you can also try `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command. emmake make -j4 ``` @@ -239,7 +264,7 @@ If you want to build with thread support, something like this works: ```bash mkdir build cd build -emcmake cmake -DSDL_THREADS=ON .. +emcmake cmake -DSDL_PTHREADS=ON .. # you can also do `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command. emmake make -j4 ``` @@ -322,6 +347,22 @@ all has to live in memory at runtime. gives other options and details, and is worth a read. +## Customizing index.html + +You don't have to use the HTML that Emscripten produces; the above examples +use `emcc -o index.html`, but you can `-o index.js` instead to just output +code without an HTML page, and then provide your own. This is desirable for +shipping products, even though the Emscripten-provided HTML is fine for +prototyping. Certain things _must_ be in the HTML file or your program will +not function correctly (or function at all). The specifics are beyond the +scope of this document, but it's likely best to start with the Emscripten HTML +and customize it, instead of starting from scratch. + +The `` element in the HTML _must not_ have a border or padding, or +things will break in unexpected ways. This can be surprising when customizing +the page's look. Plan accordingly. + + ## Debugging Debugging web apps is a mixed bag. You should compile and link with @@ -336,9 +377,9 @@ If you try debugging in Firefox and it doesn't work well for no apparent reason, try Chrome, and vice-versa. These tools are still relatively new, and improving all the time. -SDL_Log() (or even plain old printf) will write to the Javascript console, -and honestly I find printf-style debugging to be easier than setting up a build -for proper debugging, so use whatever tools work best for you. +SDL_Log() (or printf) will write to the Javascript console, +so printf-style debugging can be easier than setting up a build +for proper debugging. Use whatever tools work best for you. ## Questions? diff --git a/libs/SDL3/docs/README-ios.md b/libs/SDL3/docs/README-ios.md index 166d182..74f4ca6 100644 --- a/libs/SDL3/docs/README-ios.md +++ b/libs/SDL3/docs/README-ios.md @@ -12,9 +12,44 @@ Instructions: 2. Select your desired target, and hit build. -Using the Simple DirectMedia Layer for iOS +Using the Simple DirectMedia Layer for iOS with the SDL3 xcFramework ============================================================================== +The recommended way to use SDL for iOS is by including the SDL3.xcframework which is now a build target of the SDL.xcodeproj file. An xcframework is a new (Xcode 11) uber-framework which can handle any combination of processor type and target OS platform. +You can either build the SDL3.xcframework yourself or download the latest release disk image asset (*.dmg). + +In the past, iOS devices were always an ARM variant processor, and the simulator was always i386 or x86_64, and thus libraries could be combined into a single framework for both simulator and device. With the introduction of the Apple Silicon ARM-based machines, regular frameworks would collide as CPU type was no longer sufficient to differentiate the platform. So Apple created the new xcframework library package. + +The xcframework target builds into a Products directory alongside the SDL.xcodeproj file, as SDL3.xcframework. This can be brought in to any iOS project and will function properly for both simulator and device, no matter their CPUs. Note that Intel Macs cannot cross-compile for Apple Silicon Macs. If you need AS compatibility, perform this build on an Apple Silicon Mac. + +This target requires Xcode 11 or later. The target will simply fail to build if attempted on older Xcodes. + +In addition, on Apple platforms, main() cannot be in a dynamically loaded library. +However, unlike in SDL2, in SDL3 SDL_main is implemented inline in SDL_main.h, so you don't need to link against a static libSDL3main.lib, and you don't need to copy a .c file from the SDL3 source either. +This means that iOS apps which used the statically-linked libSDL3.lib and now link with the xcframwork can just `#include ` in the source file that contains their standard `int main(int argc, char *argv[])` function to get a header-only SDL_main implementation that calls the `SDL_RunApp()` with your standard main function. + +To use the SDL3.xcframework follow these steps: + +1. Run Xcode and create a new project using the iOS Game template, selecting the Objective C language and Metal game technology. +2. In the main view, delete all files except for Assets and LaunchScreen +3. Select the project in the main view, go to the "General" tab, scroll down to "Frameworks, Libraries, and Embedded Content", and drag and drop the SDL3.xcframework +4. Still in "Frameworks, Libraries, and Embedded Content", select "Embed & Sign" for the SDL3.xcframework. +5. Add the source files that you would normally have for an SDL program, making sure to have #include at the top of the file containing your main() function. +6. Add any assets that your application needs. +7. Enjoy! + +Using an xcFramework is similar to using a regular framework. However, issues have been seen with the build system not seeing the headers in the xcFramework. To remedy this, add the path to the xcFramework in your app's target ==> Build Settings ==> Framework Search Paths and mark it recursive (this is critical). Also critical is to remove "*.framework" from Build Settings ==> Sub-Directories to Exclude in Recursive Searches. Clean the build folder, and on your next build the build system should be able to see any of these in your code, as expected: + +#include "SDL3/SDL_main.h" +#include +#include + + +Using the Simple DirectMedia Layer for iOS by adding the SDL3 Xcode project +============================================================================== + +To maintain compatibility with older Xcode versions than version 11 you can add the SDL3 Xcode project file to your project: + 1. Run Xcode and create a new project using the iOS Game template, selecting the Objective C language and Metal game technology. 2. In the main view, delete all files except for Assets and LaunchScreen 3. Right click the project in the main view, select "Add Files...", and add the SDL project, Xcode/SDL/SDL.xcodeproj @@ -26,6 +61,18 @@ Using the Simple DirectMedia Layer for iOS 9. Add any assets that your application needs. 10. Enjoy! +Notes for distributing your iOS app on the AppStore when using the embedded SDL3 Xcode project: +Embedding the SDL3 Xcode project makes SDL3.framework a target of your app, so it will be included in the archive created during the "Archive" step required for App Store submission. As this prevents successful distribution, remove the framework via a script in the Build Phase after the "Embed & Sign" step. + +1. Select the project in the main view, go to the "Build Phases" tab, click on the big '+' in this tab and click "New Run Script Phase". +2. Scroll down to "Run Script" (after the "Embed SDL3 Framework") and enter the following script: +``` + if [ -d "$INSTALL_ROOT/Library" ]; then + echo "Removing SDL3.framework from INSTALL_ROOT for archiving" + rm -rf "$INSTALL_ROOT/Library" + fi +``` +3. Below the script entry uncheck the "Run Script:" options "For install builds only" and "Based on dependency analysis" TODO: Add information regarding App Store requirements such as icons, etc. @@ -154,26 +201,7 @@ More information on this subject is available here: http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Introduction/Introduction.html -Notes -- xcFramework -============================================================================== -The SDL.xcodeproj file now includes a target to build SDL3.xcframework. An xcframework is a new (Xcode 11) uber-framework which can handle any combination of processor type and target OS platform. - -In the past, iOS devices were always an ARM variant processor, and the simulator was always i386 or x86_64, and thus libraries could be combined into a single framework for both simulator and device. With the introduction of the Apple Silicon ARM-based machines, regular frameworks would collide as CPU type was no longer sufficient to differentiate the platform. So Apple created the new xcframework library package. - -The xcframework target builds into a Products directory alongside the SDL.xcodeproj file, as SDL3.xcframework. This can be brought in to any iOS project and will function properly for both simulator and device, no matter their CPUs. Note that Intel Macs cannot cross-compile for Apple Silicon Macs. If you need AS compatibility, perform this build on an Apple Silicon Mac. - -This target requires Xcode 11 or later. The target will simply fail to build if attempted on older Xcodes. - -In addition, on Apple platforms, main() cannot be in a dynamically loaded library. -However, unlike in SDL2, in SDL3 SDL_main is implemented inline in SDL_main.h, so you don't need to link against a static libSDL3main.lib, and you don't need to copy a .c file from the SDL3 source either. -This means that iOS apps which used the statically-linked libSDL3.lib and now link with the xcframwork can just `#include ` in the source file that contains their standard `int main(int argc, char *argv[])` function to get a header-only SDL_main implementation that calls the `SDL_RunApp()` with your standard main function. - -Using an xcFramework is similar to using a regular framework. However, issues have been seen with the build system not seeing the headers in the xcFramework. To remedy this, add the path to the xcFramework in your app's target ==> Build Settings ==> Framework Search Paths and mark it recursive (this is critical). Also critical is to remove "*.framework" from Build Settings ==> Sub-Directories to Exclude in Recursive Searches. Clean the build folder, and on your next build the build system should be able to see any of these in your code, as expected: - -#include "SDL_main.h" -#include -#include Notes -- iPhone SDL limitations diff --git a/libs/SDL3/docs/README-linux.md b/libs/SDL3/docs/README-linux.md index 3f2d2c0..edd7019 100644 --- a/libs/SDL3/docs/README-linux.md +++ b/libs/SDL3/docs/README-linux.md @@ -8,6 +8,7 @@ system does not have the XRandR libraries installed, it will be disabled at runtime, and you won't get a missing library error, at least with the default configuration parameters. +SDL is [not designed to be used in setuid or setgid executables](README-platforms.md#setuid). Build Dependencies -------------------------------------------------------------------------------- @@ -16,26 +17,28 @@ Ubuntu 18.04, all available features enabled: sudo apt-get install build-essential git make \ pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \ - libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \ - libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \ + libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \ + libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-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 + libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev libthai-dev Ubuntu 22.04+ can also add `libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev` to that command line. Fedora 35, all available features enabled: - sudo yum install gcc git-core make cmake \ - alsa-lib-devel pulseaudio-libs-devel nas-devel pipewire-devel \ + sudo dnf install gcc git-core make cmake \ + alsa-lib-devel fribidi-devel pulseaudio-libs-devel pipewire-devel \ libX11-devel libXext-devel libXrandr-devel libXcursor-devel libXfixes-devel \ - libXi-devel libXScrnSaver-devel dbus-devel ibus-devel \ + libXi-devel libXScrnSaver-devel libXtst-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 \ - pipewire-jack-audio-connection-kit-devel \ + libdrm-devel mesa-libgbm-devel libusb1-devel libdecor-devel \ + pipewire-jack-audio-connection-kit-devel libthai-devel Fedora 39+ can also add `liburing-devel` to that command line. +Fedora 40+ needs `zlib-ng-compat-static` to be added to that command line. + NOTES: - The sndio audio target is unavailable on Fedora (but probably not what you should want to use anyhow). @@ -43,10 +46,12 @@ NOTES: 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 + libgbm-devel pipewire-devel libpulse-devel sndio-devel Mesa-libEGL-devel alsa-devel xwayland-devel \ + wayland-devel wayland-protocols-devel libthai-devel fribidi-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 + + sudo pacman -S alsa-lib cmake hidapi ibus jack libdecor libthai fribidi libgl libpulse libusb libx11 libxcursor libxext libxfixes libxi libxinerama libxkbcommon libxrandr libxrender libxss libxtst 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 e5c75c1..147174c 100644 --- a/libs/SDL3/docs/README-macos.md +++ b/libs/SDL3/docs/README-macos.md @@ -73,6 +73,8 @@ NSApplicationDelegate implementation: } ``` +SDL is [not designed to be used in setuid or setgid executables](README-platforms.md#setuid). + # Using the Simple DirectMedia Layer with a traditional Makefile An existing build system for your SDL app has good chances to work almost diff --git a/libs/SDL3/docs/README-main-functions.md b/libs/SDL3/docs/README-main-functions.md index 75b9e2c..7574cff 100644 --- a/libs/SDL3/docs/README-main-functions.md +++ b/libs/SDL3/docs/README-main-functions.md @@ -206,6 +206,13 @@ The SDL_AppResult value that terminated the app is provided here, in case it's useful to know if this was a successful or failing run of the app. +## Using main functions from other languages + +If you're not using C/C++, using SDL's entry points is still possible but is +more complex. Please refer to https://wiki.libsdl.org/SDL3/NonstandardStartup +for the technical details. + + ## Summary and Best Practices - **Always Include SDL_main.h in One Source File:** When working with SDL, diff --git a/libs/SDL3/docs/README-migration.md b/libs/SDL3/docs/README-migration.md index deca6d0..1a8fbaa 100644 --- a/libs/SDL3/docs/README-migration.md +++ b/libs/SDL3/docs/README-migration.md @@ -611,6 +611,9 @@ The following enums have been renamed: The following structures have been renamed: * SDL_GameController => SDL_Gamepad +The following structures have been removed: +* SDL_GameControllerButtonBind - replaced with SDL_GamepadBinding + The following functions have been renamed: * SDL_GameControllerAddMapping() => SDL_AddGamepadMapping() * SDL_GameControllerAddMappingsFromFile() => SDL_AddGamepadMappingsFromFile() @@ -1459,7 +1462,7 @@ The following symbols have been removed: * SDL_RENDERER_PRESENTVSYNC - replaced with SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER during renderer creation and SDL_PROP_RENDERER_VSYNC_NUMBER after renderer creation * SDL_RENDERER_SOFTWARE - you can check whether the name of the renderer is `SDL_SOFTWARE_RENDERER` * SDL_RENDERER_TARGETTEXTURE - all renderers support target texture functionality -* SDL_ScaleModeBest = use SDL_SCALEMODE_LINEAR instead +* SDL_ScaleModeBest - use SDL_SCALEMODE_LINEAR instead ## SDL_rwops.h diff --git a/libs/SDL3/docs/README-ngage.md b/libs/SDL3/docs/README-ngage.md index 84192b0..beed7af 100644 --- a/libs/SDL3/docs/README-ngage.md +++ b/libs/SDL3/docs/README-ngage.md @@ -1,5 +1,64 @@ -Support for the Nokia N-Gage has been removed from SDL3 (but will make a -comeback when newer compilers are available for the platform). +# Nokia N-Gage -SDL2 still supports this platform. +SDL port for the Nokia N-Gage +[Homebrew toolchain](https://github.com/ngagesdk/ngage-toolchain) +contributed by: +- [Michael Fitzmayer](https://github.com/mupfdev) + +- [Anonymous Maarten](https://github.com/madebr) + +Many thanks to: + +- icculus and slouken for always making room for us, even when we show up in 2025 + still waving the N-Gage flag. + +- The Nokia N-Gage [Discord community](https://discord.gg/dbUzqJ26vs) + who keeps the platform alive. + +- The staff and supporters of the + [Suomen pelimuseo](https://www.vapriikki.fi/nayttelyt/fantastinen-floppi/), and + to Heikki Jungmann, for their ongoing love and dedication for the Nokia N-Gage -- + you guys are awesome! + +## History + +When SDL support was discontinued due to the lack of C99 support at the time, +this version was rebuilt from the ground up after resolving the compiler issues. + +In contrast to the earlier SDL2 port, this version features a dedicated rendering +backend and a functional, albeit limited, audio interface. Support for the +software renderer has been removed. + +The outcome is a significantly leaner and more efficient SDL port, which we hope +will breathe new life into this beloved yet obscure platform. + +## To the Stubborn Legends of the DC Scene + +This port is lovingly dedicated to the ever-nostalgic Dreamcast homebrew scene -- +because if we managed to pull this off for the N-Gage (yes, the N-Gage), surely +you guys can stop clinging to SDL2 like it's a rare Shenmue prototype and finally +make the leap to SDL3. It's 2025, not 1999 -- and let's be honest, you're rocking +a state-of-the-art C23 compiler. The irony writes itself. + +## Existing Issues and Limitations + +- For now, the new + [SDL3 main callbacks](https://wiki.libsdl.org/SDL3/README-main-functions#main-callbacks-in-sdl3) + are not optional and must be used. This is important as the callbacks + are optional on other platforms. + +- If the application is put in the background while sound is playing, + some of the audio is looped until the app is back in focus. + +- It is recommended initialising SDLs audio sub-system even when it + is not required. The backend is started at a higher level. Initialising + SDLs audio sub-system ensures that the backend is properly deinitialised. + +- Because the audio sample rate can change during phone calls, the sample + rate is currently fixed at 8kHz to ensure stable behavior. Although + dynamically adjusting the sample rate is theoretically possible, the + current implementation doesn't support it yet. This limitation is + expected to be resolved in a future update. + +- Dependency tracking is currently non-functional. diff --git a/libs/SDL3/docs/README-platforms.md b/libs/SDL3/docs/README-platforms.md index 46a054a..fe7a51b 100644 --- a/libs/SDL3/docs/README-platforms.md +++ b/libs/SDL3/docs/README-platforms.md @@ -2,39 +2,61 @@ ## Supported Platforms +SDL3 has been known to work on the following platforms at some point: + - [Android](README-android.md) -- [Emscripten](README-emscripten.md) +- [Emscripten](README-emscripten.md) (Web browsers) - [FreeBSD](README-bsd.md) - [Haiku OS](README-haiku.md) - [iOS](README-ios.md) - [Linux](README-linux.md) -- [macOS](README-macos.md) +- [macOS](README-macos.md) (10.14 and later) - [NetBSD](README-bsd.md) -- [Nintendo Switch](README-switch.md) -- [Nintendo 3DS](README-n3ds.md) +- [Nintendo Switch](README-switch.md) (Separate NDA-only fork) +- [Nintendo 3DS](README-n3ds.md) (Homebrew) +- [Nokia N-Gage](README-ngage.md) - [OpenBSD](README-bsd.md) -- [PlayStation 2](README-ps2.md) -- [PlayStation 4](README-ps4.md) -- [PlayStation 5](README-ps5.md) -- [PlayStation Portable](README-psp.md) -- [PlayStation Vita](README-vita.md) +- [PlayStation 2](README-ps2.md) (Homebrew) +- [PlayStation 4](README-ps4.md) (Separate NDA-only fork) +- [PlayStation 5](README-ps5.md) (Separate NDA-only fork) +- [PlayStation Portable](README-psp.md) (Homebrew) +- [PlayStation Vita](README-vita.md) (Homebrew) - [RISC OS](README-riscos.md) - [SteamOS](README-steamos.md) - [tvOS](README-ios.md) -- [Windows](README-windows.md) +- [visionOS](README-ios.md) +- [Windows](README-windows.md) (XP and later) - [Windows GDK](README-gdk.md) - [Xbox](README-gdk.md) +Note that the SDL maintainers do not test on all these platforms; if a less-common system breaks, [please let us know](https://github.com/libsdl-org/SDL/issues/new) and send patches if you can. + +If you'd like to port SDL to a new platform, feel free to get in touch! [A guide to porting SDL2](https://discourse.libsdl.org/t/port-sdl-2-0-to-bios/25453/2) was written a while ago, and most of it still applies to SDL3. + ## Unsupported Platforms If your favorite system is listed below, we aren't working on it. However, if you send reasonable patches and are willing to support the port in the long term, we are happy to take a look! All of these still work with [SDL2](/SDL2), which is an incompatible API, but an option if you need to support these platforms still. +- QNX - Google Stadia - NaCL -- Nokia N-Gage - OS/2 -- QNX - WinPhone - WinRT/UWP + +## General notes for Unix platforms + +Some aspects of SDL functionality are common to all Unix-based platforms. + +### Privileged processes (setuid, setgid, setcap) + +SDL is not designed to be used in programs with elevated privileges, +such as setuid (`chmod u+s`) or setgid (`chmod g+s`) executables, +or executables with file-based capabilities +(`setcap cap_sys_nice+ep` or similar). +It does not make any attempt to avoid trusting environment variables +or other aspects of the inherited execution environment. +Programs running with elevated privileges in an attacker-controlled +execution environment should not call SDL functions. diff --git a/libs/SDL3/docs/README-ps2.md b/libs/SDL3/docs/README-ps2.md index d358565..6d3e79a 100644 --- a/libs/SDL3/docs/README-ps2.md +++ b/libs/SDL3/docs/README-ps2.md @@ -11,11 +11,20 @@ Credit to ## Building To build SDL library for the PS2, make sure you have the latest PS2Dev status and run: ```bash -cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=$PS2DEV/ps2sdk/ps2dev.cmake +export PS2DEV=/usr/local/ps2dev # or wherever your ps2dev installation is +export PS2SDK=$PS2DEV/ps2sdk +export PATH=$PATH:$PS2DEV/bin:$PS2DEV/ee/bin:$PS2DEV/iop/bin:$PS2DEV/dvp/bin:$PS2SDK/bin +cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=$PS2DEV/share/ps2dev.cmake cmake --build build cmake --install build ``` +## Hints +- `SDL_HINT_PS2_GS_WIDTH`: Width of the framebuffer. Defaults to 640. +- `SDL_HINT_PS2_GS_HEIGHT`: Height of the framebuffer. Defaults to 448. +- `SDL_HINT_PS2_GS_PROGRESSIVE`: Whether to use progressive, instead of interlaced. Defaults to 0. +- `SDL_HINT_PS2_GS_MODE`: Regional standard of the signal. "NTSC" (60hz), "PAL" (50hz) or "" (the console's region, default). + ## Notes If you trying to debug a SDL app through [ps2client](https://github.com/ps2dev/ps2client) you need to avoid the IOP reset, otherwise you will lose the connection with your computer. So to avoid the reset of the IOP CPU, you need to call to the macro `SDL_PS2_SKIP_IOP_RESET();`. diff --git a/libs/SDL3/docs/README-psp.md b/libs/SDL3/docs/README-psp.md index 65d2af2..1dd75ce 100644 --- a/libs/SDL3/docs/README-psp.md +++ b/libs/SDL3/docs/README-psp.md @@ -1,13 +1,13 @@ PSP ====== SDL port for the Sony PSP contributed by: -- Captian Lex +- Captain Lex - Francisco Javier Trujillo Mata - Wouter Wijsman Credit to - Marcus R.Brown,Jim Paris,Matthew H for the original SDL 1.2 for PSP + Marcus R.Brown, Jim Paris, Matthew H for the original SDL 1.2 for PSP Geecko for his PSP GU lib "Glib2d" ## Building diff --git a/libs/SDL3/docs/README-switch.md b/libs/SDL3/docs/README-switch.md index 16b7acd..bbcbd11 100644 --- a/libs/SDL3/docs/README-switch.md +++ b/libs/SDL3/docs/README-switch.md @@ -1,3 +1,3 @@ # Nintendo Switch -SDL3 runs on the Nintendo Switch! There are commercial games shipping with this port. This port is kept in a separate repository, but is available for free, under the zlib license, to anyone that is under NDA for Switch development with Nintendo. Please contact Ryan (icculus at icculus dot org) for details. +SDL3 runs on the Nintendo Switch! Both Switch and Switch 2 are supported. There are commercial games shipping with this port. This port is kept in a separate repository, but is available for free, under the zlib license, to anyone that is under the appropriate NDAs with Nintendo. Please contact Ryan (icculus at icculus dot org) for details. diff --git a/libs/SDL3/docs/README-wayland.md b/libs/SDL3/docs/README-wayland.md index 75a9b90..7208070 100644 --- a/libs/SDL3/docs/README-wayland.md +++ b/libs/SDL3/docs/README-wayland.md @@ -47,8 +47,15 @@ encounter limitations or behavior that is different from other windowing systems ### Warping the mouse cursor to or from a point outside the window doesn't work -- The cursor can be warped only within the window with mouse focus, provided that the `zwp_pointer_confinement_v1` - protocol is supported by the compositor. +- Warping the cursor on Wayland requires that either the `wp_pointer_warp_v1` or `zwp_pointer_confinement_v1` protocol + is supported by the compositor. Compositors typically restrict pointer warps to be within the window that currently + has mouse focus. + +### Minimize/Restored window events are not sent, and the ```SDL_WINDOW_MINIMIZED``` flag is not set. + +- Wayland windows do not currently report the minimized state, aside from when it is activated programmatically via + ```SDL_MinimizeWindow()```. Minimizing a window from the window controls or a desktop shortcut will not send a + minimized event or flag the window as being minimized. ### The application icon can't be set via ```SDL_SetWindowIcon()``` @@ -59,6 +66,15 @@ 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`. +### The application progress bar can't be set via ```SDL_SetWindowProgressState()``` or ```SDL_SetWindowProgressValue()``` + +- Only some Desktop Environemnts support the underlying API. Known compatible DEs: Unity, KDE +- The underlying API requires a desktop entry file, aka a `.desktop` file. + Please see the [Desktop Entry Specification](https://specifications.freedesktop.org/desktop-entry-spec/latest/) for + more information on the format of this file. Note that if your application manually sets the application ID via the + `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. diff --git a/libs/SDL3/docs/hello.c b/libs/SDL3/docs/hello.c index a825ea1..aff0e97 100644 --- a/libs/SDL3/docs/hello.c +++ b/libs/SDL3/docs/hello.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/docs/release_checklist.md b/libs/SDL3/docs/release_checklist.md index 56fb23d..5c19fc4 100644 --- a/libs/SDL3/docs/release_checklist.md +++ b/libs/SDL3/docs/release_checklist.md @@ -15,6 +15,8 @@ * Create a GitHub release and attach the archives you just generated. +* If this is a feature release, also tag the sdlwiki with the same tag. + ## New feature release * Update `WhatsNew.txt` diff --git a/libs/SDL3/examples/CMakeLists.txt b/libs/SDL3/examples/CMakeLists.txt index 2a13a03..1955455 100644 --- a/libs/SDL3/examples/CMakeLists.txt +++ b/libs/SDL3/examples/CMakeLists.txt @@ -16,13 +16,6 @@ else() endif() set(HAVE_EXAMPLES_LINK_SHARED "${SDL_EXAMPLES_LINK_SHARED}" PARENT_SCOPE) -# CMake incorrectly detects opengl32.lib being present on MSVC ARM64 -if(NOT (MSVC AND SDL_CPU_ARM64)) - # Prefer GLVND, if present - set(OpenGL_GL_PREFERENCE GLVND) - find_package(OpenGL) -endif() - set(SDL_EXAMPLE_EXECUTABLES) if(CMAKE_RUNTIME_OUTPUT_DIRECTORY) @@ -38,7 +31,7 @@ if(NOT CMAKE_VERSION VERSION_LESS 3.20) set(example_bin_dir "${example_bin_dir}$<$:/$>") endif() -file(GLOB RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/*.bmp ${CMAKE_CURRENT_SOURCE_DIR}/../test/*.wav ${CMAKE_CURRENT_SOURCE_DIR}/../test/*.hex) +file(GLOB RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/*.png ${CMAKE_CURRENT_SOURCE_DIR}/../test/*.wav ${CMAKE_CURRENT_SOURCE_DIR}/../test/*.hex) set(RESOURCE_FILE_NAMES) set(RESOURCE_FILES_BINDIR) @@ -64,17 +57,12 @@ macro(add_sdl_example_executable TARGET) if(NOT AST_SOURCES) message(FATAL_ERROR "add_sdl_example_executable needs at least one source") endif() - set(EXTRA_SOURCES "") - if(AST_DATAFILES) - list(APPEND EXTRA_SOURCES ${DATAFILES}) - endif() if(ANDROID) - add_library(${TARGET} SHARED ${AST_SOURCES} ${EXTRA_SOURCES}) + add_library(${TARGET} SHARED ${AST_SOURCES} ${AST_DATAFILES}) else() - add_executable(${TARGET} ${AST_SOURCES} ${EXTRA_SOURCES}) + add_executable(${TARGET} ${AST_SOURCES} ${AST_DATAFILES}) endif() SDL_AddCommonCompilerFlags(${TARGET}) - target_include_directories(${TARGET} PRIVATE "${SDL3_SOURCE_DIR}/src/video/khronos") target_link_libraries(${TARGET} PRIVATE SDL3::${sdl_name_component}) list(APPEND SDL_EXAMPLE_EXECUTABLES ${TARGET}) @@ -85,13 +73,9 @@ macro(add_sdl_example_executable TARGET) COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different ${AST_DATAFILES} $/sdl-${TARGET} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" ) - else() + elseif(NOT APPLE AND NOT N3DS) add_dependencies(${TARGET} copy-sdl-example-resources) endif() - if(APPLE) - # Make sure resource files get installed into macOS/iOS .app bundles. - set_target_properties(${TARGET} PROPERTIES RESOURCE "${AST_DATAFILES}") - endif() if(EMSCRIPTEN) foreach(res IN LISTS AST_DATAFILES) get_filename_component(res_name "${res}" NAME) @@ -101,24 +85,45 @@ macro(add_sdl_example_executable TARGET) set_property(TARGET ${TARGET} APPEND PROPERTY ADDITIONAL_CLEAN_FILES "$/$$/>") endif() - if(WINDOWS) + if(APPLE) + # Set Apple App ID / Bundle ID. This is needed to launch apps on some Apple + # platforms (iOS, for example). + set_target_properties(${TARGET} PROPERTIES + RESOURCES "${AST_DATAFILES}" + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_GUI_IDENTIFIER "org.libsdl.${TARGET}" + MACOSX_BUNDLE_BUNDLE_VERSION "${SDL3_VERSION}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${SDL3_VERSION}" + ) + set_property(SOURCE ${AST_DATAFILES} PROPERTY MACOSX_PACKAGE_LOCATION "Resources") + elseif(WINDOWS) # CET support was added in VS 16.7 if(MSVC_VERSION GREATER 1926 AND CMAKE_GENERATOR_PLATFORM MATCHES "Win32|x64") set_property(TARGET ${TARGET} APPEND_STRING PROPERTY LINK_FLAGS " -CETCOMPAT") endif() - elseif(PSP) - target_link_libraries(${TARGET} PRIVATE GL) elseif(EMSCRIPTEN) set_property(TARGET ${TARGET} PROPERTY SUFFIX ".html") target_link_options(${TARGET} PRIVATE -sALLOW_MEMORY_GROWTH=1) + elseif(N3DS) + set(ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/romfs/${TARGET}") + file(MAKE_DIRECTORY "${ROMFS_DIR}") + file(COPY ${AST_DATAFILES} DESTINATION "${ROMFS_DIR}") + ctr_generate_smdh("${TARGET}.smdh" + NAME "SDL-${TARGET}" + DESCRIPTION "SDL3 example application" + AUTHOR "SDL3 Contributors" + ICON "${CMAKE_CURRENT_SOURCE_DIR}/../test/n3ds/logo48x48.png" + ) + ctr_create_3dsx( + ${TARGET} + ROMFS "${ROMFS_DIR}" + SMDH "${TARGET}.smdh" + ) + elseif(NGAGE) + string(MD5 TARGET_MD5 "${TARGET}") + string(SUBSTRING "${TARGET_MD5}" 0 8 TARGET_MD5_8) + target_link_options(${TARGET} PRIVATE "SHELL:-s UID3=0x${TARGET_MD5_8}") endif() - - if(OPENGL_FOUND) - target_compile_definitions(${TARGET} PRIVATE HAVE_OPENGL) - endif() - - # FIXME: only add "${SDL3_BINARY_DIR}/include-config-$>" + include paths of external dependencies - target_include_directories(${TARGET} PRIVATE "$") endmacro() add_sdl_example_executable(renderer-clear SOURCES renderer/01-clear/clear.c) @@ -126,25 +131,31 @@ add_sdl_example_executable(renderer-primitives SOURCES renderer/02-primitives/pr add_sdl_example_executable(renderer-lines SOURCES renderer/03-lines/lines.c) add_sdl_example_executable(renderer-points SOURCES renderer/04-points/points.c) add_sdl_example_executable(renderer-rectangles SOURCES renderer/05-rectangles/rectangles.c) -add_sdl_example_executable(renderer-textures SOURCES renderer/06-textures/textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) +add_sdl_example_executable(renderer-textures SOURCES renderer/06-textures/textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) add_sdl_example_executable(renderer-streaming-textures SOURCES renderer/07-streaming-textures/streaming-textures.c) -add_sdl_example_executable(renderer-rotating-textures SOURCES renderer/08-rotating-textures/rotating-textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) -add_sdl_example_executable(renderer-scaling-textures SOURCES renderer/09-scaling-textures/scaling-textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) -add_sdl_example_executable(renderer-geometry SOURCES renderer/10-geometry/geometry.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) -add_sdl_example_executable(renderer-color-mods SOURCES renderer/11-color-mods/color-mods.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) -add_sdl_example_executable(renderer-viewport SOURCES renderer/14-viewport/viewport.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) -add_sdl_example_executable(renderer-cliprect SOURCES renderer/15-cliprect/cliprect.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) -add_sdl_example_executable(renderer-read-pixels SOURCES renderer/17-read-pixels/read-pixels.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) +add_sdl_example_executable(renderer-rotating-textures SOURCES renderer/08-rotating-textures/rotating-textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) +add_sdl_example_executable(renderer-scaling-textures SOURCES renderer/09-scaling-textures/scaling-textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) +add_sdl_example_executable(renderer-geometry SOURCES renderer/10-geometry/geometry.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) +add_sdl_example_executable(renderer-color-mods SOURCES renderer/11-color-mods/color-mods.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) +add_sdl_example_executable(renderer-viewport SOURCES renderer/14-viewport/viewport.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) +add_sdl_example_executable(renderer-cliprect SOURCES renderer/15-cliprect/cliprect.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) +add_sdl_example_executable(renderer-read-pixels SOURCES renderer/17-read-pixels/read-pixels.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) add_sdl_example_executable(renderer-debug-text SOURCES renderer/18-debug-text/debug-text.c) +add_sdl_example_executable(renderer-affine-textures SOURCES renderer/19-affine-textures/affine-textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) add_sdl_example_executable(audio-simple-playback SOURCES audio/01-simple-playback/simple-playback.c) add_sdl_example_executable(audio-simple-playback-callback SOURCES audio/02-simple-playback-callback/simple-playback-callback.c) add_sdl_example_executable(audio-load-wav SOURCES audio/03-load-wav/load-wav.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.wav) add_sdl_example_executable(audio-multiple-streams SOURCES audio/04-multiple-streams/multiple-streams.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.wav ${CMAKE_CURRENT_SOURCE_DIR}/../test/sword.wav) +add_sdl_example_executable(audio-planar-data SOURCES audio/05-planar-data/planar-data.c) add_sdl_example_executable(input-joystick-polling SOURCES input/01-joystick-polling/joystick-polling.c) add_sdl_example_executable(input-joystick-events SOURCES input/02-joystick-events/joystick-events.c) +add_sdl_example_executable(input-gamepad-polling SOURCES input/03-gamepad-polling/gamepad-polling.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/gamepad_front.png) +add_sdl_example_executable(input-gamepad-events SOURCES input/04-gamepad-events/gamepad-events.c) add_sdl_example_executable(camera-read-and-draw SOURCES camera/01-read-and-draw/read-and-draw.c) add_sdl_example_executable(pen-drawing-lines SOURCES pen/01-drawing-lines/drawing-lines.c) -add_sdl_example_executable(asyncio-load-bitmaps SOURCES asyncio/01-load-bitmaps/load-bitmaps.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp ${CMAKE_CURRENT_SOURCE_DIR}/../test/gamepad_front.bmp ${CMAKE_CURRENT_SOURCE_DIR}/../test/speaker.bmp ${CMAKE_CURRENT_SOURCE_DIR}/../test/icon2x.bmp) +add_sdl_example_executable(asyncio-load-bitmaps SOURCES asyncio/01-load-bitmaps/load-bitmaps.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png ${CMAKE_CURRENT_SOURCE_DIR}/../test/gamepad_front.png ${CMAKE_CURRENT_SOURCE_DIR}/../test/speaker.png ${CMAKE_CURRENT_SOURCE_DIR}/../test/icon2x.png) +add_sdl_example_executable(misc-power SOURCES misc/01-power/power.c) +add_sdl_example_executable(misc-clipboard SOURCES misc/02-clipboard/clipboard.c) add_sdl_example_executable(demo-snake SOURCES demo/01-snake/snake.c) add_sdl_example_executable(demo-woodeneye-008 SOURCES demo/02-woodeneye-008/woodeneye-008.c) add_sdl_example_executable(demo-infinite-monkeys SOURCES demo/03-infinite-monkeys/infinite-monkeys.c) @@ -154,7 +165,7 @@ add_sdl_example_executable(demo-bytepusher SOURCES demo/04-bytepusher/bytepusher # - Add a new example in examples/ # - Run python VisualC/examples/generate.py # - Take note of the newly generated .vcxproj files -# - Modify the .vcxproj files if necessary (adding content such as BMP or WAV files) +# - Modify the .vcxproj files if necessary (adding content such as PNG or WAV files) # - Open VisualC/SDL.sln in Visual Studio or JetBrains Rider # - Locate the appropriate folder in the Solution Explorer # - Add the newly generated projects: Right click -> Add -> Existing project... @@ -175,28 +186,6 @@ if(PSP) endforeach() endif() -if(N3DS) - set(ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/romfs") - file(MAKE_DIRECTORY "${ROMFS_DIR}") - file(COPY ${RESOURCE_FILES} DESTINATION "${ROMFS_DIR}") - - foreach(APP ${SDL_EXAMPLE_EXECUTABLES}) - get_target_property(TARGET_BINARY_DIR ${APP} BINARY_DIR) - set(SMDH_FILE "${TARGET_BINARY_DIR}/${APP}.smdh") - ctr_generate_smdh("${SMDH_FILE}" - NAME "SDL-${APP}" - DESCRIPTION "SDL3 Test suite" - AUTHOR "SDL3 Contributors" - ICON "${CMAKE_CURRENT_SOURCE_DIR}/../test/n3ds/logo48x48.png" - ) - ctr_create_3dsx( - ${APP} - ROMFS "${ROMFS_DIR}" - SMDH "${SMDH_FILE}" - ) - endforeach() -endif() - if(RISCOS) set(SDL_EXAMPLE_EXECUTABLES_AIF) foreach(APP ${SDL_EXAMPLE_EXECUTABLES}) @@ -211,18 +200,6 @@ if(RISCOS) endforeach() endif() -# Set Apple App ID / Bundle ID. This is needed to launch apps on some Apple -# platforms (iOS, for example). -if(APPLE) - foreach(CURRENT_TARGET ${SDL_EXAMPLE_EXECUTABLES}) - set_target_properties("${CURRENT_TARGET}" PROPERTIES - MACOSX_BUNDLE_GUI_IDENTIFIER "org.libsdl.${CURRENT_TARGET}" - MACOSX_BUNDLE_BUNDLE_VERSION "${SDL3_VERSION}" - MACOSX_BUNDLE_SHORT_VERSION_STRING "${SDL3_VERSION}" - ) - endforeach() -endif() - if(SDL_INSTALL_EXAMPLES) if(RISCOS) install( diff --git a/libs/SDL3/examples/README.md b/libs/SDL3/examples/README.md index 872e087..261e485 100644 --- a/libs/SDL3/examples/README.md +++ b/libs/SDL3/examples/README.md @@ -65,3 +65,30 @@ If writing new examples, this is the skeleton code we start from, to keep everything consistent. You can ignore it. +## How are the thumbnails/onmouseover media created? + +(Since I have to figure this out every time.) + +This is how Ryan is doing it currently. + +- `rm -f frame*.png` +- Temporarily add `#include "../../save-rendering-to-bitmaps.h"` after any SDL + includes in the example program. +- Launch the example app, interact with it, let it run for a few seconds, quit. +- This will dump a "frameX.png" file for each frame rendered. +- Make a video in webp format from the bitmaps (this assumes the bitmaps were + stored at 60fps, you might have to tweak). + + ```bash + ffmpeg -framerate 60 -pattern_type glob -i 'frame*.png' -loop 0 -quality 40 -r 10 -frames:v 40 onmouseover.webp + ``` + + You might need to start in the middle of the video, or mess with quality or + number of frames to generate, ymmv. +- Pick a frame for the thumbnail, make it a .png, and run that png through + pngquant for massive file size reduction without any obvious loss in quality: + + ```bash + convert frame00000.png cvt.png ; pngquant cvt.png --output thumbnail.png ; rm -f cvt.png + ``` + diff --git a/libs/SDL3/examples/asyncio/01-load-bitmaps/load-bitmaps.c b/libs/SDL3/examples/asyncio/01-load-bitmaps/load-bitmaps.c index 44c2abf..aebb96e 100644 --- a/libs/SDL3/examples/asyncio/01-load-bitmaps/load-bitmaps.c +++ b/libs/SDL3/examples/asyncio/01-load-bitmaps/load-bitmaps.c @@ -14,7 +14,7 @@ static SDL_Renderer *renderer = NULL; static SDL_AsyncIOQueue *queue = NULL; #define TOTAL_TEXTURES 4 -static const char * const bmps[TOTAL_TEXTURES] = { "sample.bmp", "gamepad_front.bmp", "speaker.bmp", "icon2x.bmp" }; +static const char * const pngs[TOTAL_TEXTURES] = { "sample.png", "gamepad_front.png", "speaker.png", "icon2x.png" }; static SDL_Texture *textures[TOTAL_TEXTURES]; static const SDL_FRect texture_rects[TOTAL_TEXTURES] = { { 116, 156, 408, 167 }, @@ -33,10 +33,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/asyncio/load-bitmaps", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/asyncio/load-bitmaps", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create window/renderer!", SDL_GetError(), NULL); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); queue = SDL_CreateAsyncIOQueue(); if (!queue) { @@ -44,12 +45,12 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - /* Load some .bmp files asynchronously from wherever the app is being run from, put them in the same queue. */ - for (i = 0; i < SDL_arraysize(bmps); i++) { + /* Load some .png files asynchronously from wherever the app is being run from, put them in the same queue. */ + for (i = 0; i < SDL_arraysize(pngs); i++) { char *path = NULL; - SDL_asprintf(&path, "%s%s", SDL_GetBasePath(), bmps[i]); /* allocate a string of the full file path */ + SDL_asprintf(&path, "%s%s", SDL_GetBasePath(), pngs[i]); /* allocate a string of the full file path */ /* you _should) check for failure, but we'll just go on without files here. */ - SDL_LoadFileAsync(path, queue, (void *) bmps[i]); /* attach the filename as app-specific data, so we can see it later. */ + SDL_LoadFileAsync(path, queue, (void *) pngs[i]); /* attach the filename as app-specific data, so we can see it later. */ SDL_free(path); } @@ -72,18 +73,18 @@ SDL_AppResult SDL_AppIterate(void *appstate) SDL_AsyncIOOutcome outcome; int i; - if (SDL_GetAsyncIOResult(queue, &outcome)) { /* a .bmp file load has finished? */ + if (SDL_GetAsyncIOResult(queue, &outcome)) { /* a .png file load has finished? */ if (outcome.result == SDL_ASYNCIO_COMPLETE) { - /* this might be _any_ of the bmps; they might finish loading in any order. */ - for (i = 0; i < SDL_arraysize(bmps); i++) { + /* this might be _any_ of the pngs; they might finish loading in any order. */ + for (i = 0; i < SDL_arraysize(pngs); i++) { /* this doesn't need a strcmp because we gave the pointer from this array to SDL_LoadFileAsync */ - if (outcome.userdata == bmps[i]) { + if (outcome.userdata == pngs[i]) { break; } } - if (i < SDL_arraysize(bmps)) { /* (just in case.) */ - SDL_Surface *surface = SDL_LoadBMP_IO(SDL_IOFromConstMem(outcome.buffer, (size_t) outcome.bytes_transferred), true); + if (i < SDL_arraysize(pngs)) { /* (just in case.) */ + SDL_Surface *surface = SDL_LoadPNG_IO(SDL_IOFromConstMem(outcome.buffer, (size_t) outcome.bytes_transferred), true); if (surface) { /* the renderer is not multithreaded, so create the texture here once the data loads. */ textures[i] = SDL_CreateTextureFromSurface(renderer, surface); if (!textures[i]) { 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 15126e5..69b7891 100644 --- a/libs/SDL3/examples/audio/01-simple-playback/simple-playback.c +++ b/libs/SDL3/examples/audio/01-simple-playback/simple-playback.c @@ -29,10 +29,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) } /* we don't _need_ a window for audio-only things but it's good policy to have one. */ - if (!SDL_CreateWindowAndRenderer("examples/audio/simple-playback", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/audio/simple-playback", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* We're just playing a single thing here, so we'll use the simplified option. We are always going to feed audio in as mono, float32 data at 8000Hz. diff --git a/libs/SDL3/examples/audio/02-simple-playback-callback/simple-playback-callback.c b/libs/SDL3/examples/audio/02-simple-playback-callback/simple-playback-callback.c index c011c72..186ae24 100644 --- a/libs/SDL3/examples/audio/02-simple-playback-callback/simple-playback-callback.c +++ b/libs/SDL3/examples/audio/02-simple-playback-callback/simple-playback-callback.c @@ -63,10 +63,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) } /* we don't _need_ a window for audio-only things but it's good policy to have one. */ - if (!SDL_CreateWindowAndRenderer("examples/audio/simple-playback-callback", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/audio/simple-playback-callback", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* We're just playing a single thing here, so we'll use the simplified option. We are always going to feed audio in as mono, float32 data at 8000Hz. 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 c517e5d..a8de071 100644 --- a/libs/SDL3/examples/audio/03-load-wav/load-wav.c +++ b/libs/SDL3/examples/audio/03-load-wav/load-wav.c @@ -39,10 +39,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) } /* we don't _need_ a window for audio-only things but it's good policy to have one. */ - if (!SDL_CreateWindowAndRenderer("examples/audio/load-wav", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/audio/load-wav", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Load the .wav file from wherever the app is being run from. */ SDL_asprintf(&wav_path, "%ssample.wav", SDL_GetBasePath()); /* allocate a string of the full file path */ diff --git a/libs/SDL3/examples/audio/04-multiple-streams/README.txt b/libs/SDL3/examples/audio/04-multiple-streams/README.txt index 9ef2275..ad7f5f8 100644 --- a/libs/SDL3/examples/audio/04-multiple-streams/README.txt +++ b/libs/SDL3/examples/audio/04-multiple-streams/README.txt @@ -1,5 +1,5 @@ If you're running this in a web browser, you need to click the window before you'll hear anything! -This example code loads two .wav files, puts them an audio streams and binds +This example code loads two .wav files, puts them in audio streams and binds them for playback, repeating both sounds on loop. This shows several streams mixing into a single playback device. 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 8d3bfaa..a10ae09 100644 --- a/libs/SDL3/examples/audio/04-multiple-streams/multiple-streams.c +++ b/libs/SDL3/examples/audio/04-multiple-streams/multiple-streams.c @@ -1,5 +1,5 @@ /* - * This example code loads two .wav files, puts them an audio streams and + * This example code loads two .wav files, puts them in audio streams and * binds them for playback, repeating both sounds on loop. This shows several * streams mixing into a single playback device. * @@ -65,10 +65,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/audio/multiple-streams", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/audio/multiple-streams", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* open the default audio device in whatever format it prefers; our audio streams will adjust to it. */ audio_device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL); diff --git a/libs/SDL3/examples/audio/05-planar-data/README.txt b/libs/SDL3/examples/audio/05-planar-data/README.txt new file mode 100644 index 0000000..c7bd0ab --- /dev/null +++ b/libs/SDL3/examples/audio/05-planar-data/README.txt @@ -0,0 +1,7 @@ +This example code draws two clickable buttons. Each causes a sound to play, +fed to either the left or right audio channel through separate (planar) +arrays. + +Planar audio can feed both channels at the same time from different arrays, +as well, but this example only uses one channel at a time for clarity. A +NULL array will supply silence for that channel. diff --git a/libs/SDL3/examples/audio/05-planar-data/onmouseover.webp b/libs/SDL3/examples/audio/05-planar-data/onmouseover.webp new file mode 100644 index 0000000..90880c9 Binary files /dev/null and b/libs/SDL3/examples/audio/05-planar-data/onmouseover.webp differ diff --git a/libs/SDL3/examples/audio/05-planar-data/planar-data.c b/libs/SDL3/examples/audio/05-planar-data/planar-data.c new file mode 100644 index 0000000..7fe291d --- /dev/null +++ b/libs/SDL3/examples/audio/05-planar-data/planar-data.c @@ -0,0 +1,368 @@ +/* + * This example code draws two clickable buttons. Each causes a sound to play, + * fed to either the left or right audio channel through separate ("planar") + * arrays. + * + * This code is public domain. Feel free to use it for any purpose! + */ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static SDL_AudioStream *stream = NULL; + +/* location of buttons on the screen. */ +static const SDL_FRect rect_left_button = { 100, 170, 100, 100 }; +static const SDL_FRect rect_right_button = { 440, 170, 100, 100 }; + +/* -1 if we're currently playing left, 1 if playing right, 0 if not playing. */ +static int playing_sound = 0; + +/* Raw audio data. These arrays are at the end of the source file. */ +static const Uint8 left[1870]; +static const Uint8 right[1777]; + + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + const SDL_AudioSpec spec = { SDL_AUDIO_U8, 2, 4000 }; /* Uint8 data, stereo, 4000Hz. */ + + SDL_SetAppMetadata("Example Audio Planar Data", "1.0", "com.example.audio-planar-data"); + + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_CreateWindowAndRenderer("examples/audio/planar-data", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); + + stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL, NULL); + if (!stream) { + SDL_Log("Couldn't open audio device stream: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + SDL_ResumeAudioStreamDevice(stream); /* SDL_OpenAudioDeviceStream starts the device paused. Resume it! */ + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + SDL_ConvertEventToRenderCoordinates(renderer, event); + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) { + if (playing_sound == 0) { /* nothing currently playing? */ + const SDL_FPoint point = { event->button.x, event->button.y }; + if (SDL_PointInRectFloat(&point, &rect_left_button)) { /* clicked left button? */ + const Uint8 *planes[] = { left, NULL }; /* specify NULL to say "this specific channel is silent" */ + SDL_PutAudioStreamPlanarData(stream, (const void * const *) planes, -1, SDL_arraysize(left)); + SDL_FlushAudioStream(stream); /* that's all we're playing until it completes. */ + playing_sound = -1; /* left is playing */ + } else if (SDL_PointInRectFloat(&point, &rect_right_button)) { /* clicked right button? */ + const Uint8 *planes[] = { NULL, right }; /* specify NULL to say "this specific channel is silent" */ + SDL_PutAudioStreamPlanarData(stream, (const void * const *) planes, -1, SDL_arraysize(right)); + SDL_FlushAudioStream(stream); /* that's all we're playing until it completes. */ + playing_sound = 1; /* right is playing */ + } + } + } + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +static void render_button(const SDL_FRect *rect, const char *str, int button_value) +{ + float x, y; + + if (playing_sound == button_value) { + SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); /* green while playing */ + } else { + SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); /* blue while not playing */ + } + + SDL_RenderFillRect(renderer, rect); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + + x = rect->x + ((rect->w - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(str))) / 2.0f); + y = rect->y + ((rect->h - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) / 2.0f); + SDL_RenderDebugText(renderer, x, y, str); +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + if (playing_sound) { + if (SDL_GetAudioStreamQueued(stream) == 0) { /* sound is done? We can play a new sound now. */ + playing_sound = 0; + } + } + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + + render_button(&rect_left_button, "LEFT", -1); + render_button(&rect_right_button, "RIGHT", 1); + + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + SDL_DestroyAudioStream(stream); + /* SDL will clean up the window/renderer for us. */ +} + + + +/* This is the audio data, as raw PCM samples (Uint8, 1 channel, 4000Hz) packed into C byte arrays for convenience. */ + +static const Uint8 left[1870] = { + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x81, 0x80, 0x81, 0x82, 0x82, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x81, 0x80, 0x80, 0x80, 0x7f, 0x7e, 0x7e, 0x7e, 0x7d, + 0x7b, 0x7b, 0x7b, 0x7b, 0x7c, 0x7d, 0x7d, 0x7e, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x85, 0x84, + 0x84, 0x83, 0x81, 0x7f, 0x7d, 0x7c, 0x7a, 0x7a, 0x7a, 0x77, 0x77, 0x77, 0x76, 0x76, 0x76, 0x77, + 0x78, 0x7d, 0x82, 0x89, 0x8e, 0x92, 0x95, 0x95, 0x91, 0x8b, 0x84, 0x7d, 0x77, 0x73, 0x72, 0x72, + 0x74, 0x75, 0x75, 0x75, 0x76, 0x74, 0x73, 0x73, 0x74, 0x79, 0x81, 0x89, 0x8f, 0x96, 0x9b, 0x9c, + 0x98, 0x91, 0x88, 0x7e, 0x77, 0x74, 0x73, 0x74, 0x77, 0x7b, 0x7c, 0x7a, 0x77, 0x73, 0x6d, 0x69, + 0x68, 0x6a, 0x73, 0x7f, 0x87, 0x8e, 0x99, 0xa1, 0x9e, 0x97, 0x90, 0x86, 0x7c, 0x76, 0x77, 0x7b, + 0x80, 0x89, 0x91, 0x93, 0x91, 0x8e, 0x87, 0x7c, 0x71, 0x6b, 0x65, 0x60, 0x5d, 0x5f, 0x60, 0x61, + 0x6b, 0x7b, 0x84, 0x8d, 0xa0, 0xae, 0xae, 0xa8, 0xa1, 0x94, 0x81, 0x73, 0x6f, 0x70, 0x74, 0x7e, + 0x8d, 0x95, 0x97, 0x98, 0x92, 0x83, 0x72, 0x69, 0x61, 0x5a, 0x56, 0x59, 0x5d, 0x5f, 0x65, 0x75, + 0x82, 0x87, 0x95, 0xaa, 0xb4, 0xb0, 0xaa, 0xa0, 0x8d, 0x77, 0x6c, 0x6c, 0x6d, 0x72, 0x81, 0x91, + 0x98, 0x9a, 0x9a, 0x8f, 0x7a, 0x6a, 0x61, 0x58, 0x4f, 0x50, 0x57, 0x5b, 0x61, 0x74, 0x85, 0x8a, + 0x96, 0xab, 0xb4, 0xae, 0xa5, 0x9c, 0x88, 0x71, 0x67, 0x69, 0x6c, 0x73, 0x85, 0x96, 0x9d, 0xa1, + 0xa3, 0x96, 0x7f, 0x6e, 0x63, 0x56, 0x4c, 0x4d, 0x52, 0x53, 0x58, 0x6b, 0x80, 0x86, 0x92, 0xaa, + 0xb8, 0xb4, 0xac, 0xa5, 0x90, 0x75, 0x69, 0x6a, 0x6c, 0x73, 0x86, 0x98, 0x9c, 0xa2, 0xa7, 0x99, + 0x7f, 0x6e, 0x61, 0x54, 0x4c, 0x4b, 0x4d, 0x4f, 0x54, 0x66, 0x7c, 0x85, 0x90, 0xa9, 0xbc, 0xba, + 0xb4, 0xac, 0x95, 0x78, 0x69, 0x67, 0x67, 0x71, 0x86, 0x99, 0x9d, 0xa4, 0xab, 0x9b, 0x7f, 0x6e, + 0x5f, 0x50, 0x4b, 0x4e, 0x4e, 0x4e, 0x54, 0x60, 0x77, 0x86, 0x8e, 0xa4, 0xbb, 0xbf, 0xb9, 0xb3, + 0x9e, 0x7d, 0x68, 0x65, 0x63, 0x6b, 0x84, 0x9a, 0x9d, 0xa3, 0xb0, 0x9f, 0x83, 0x71, 0x5f, 0x4d, + 0x4c, 0x51, 0x51, 0x51, 0x56, 0x5a, 0x64, 0x7d, 0x90, 0x99, 0xad, 0xc3, 0xc2, 0xb5, 0xaa, 0x92, + 0x71, 0x62, 0x65, 0x6a, 0x78, 0x92, 0xa2, 0xa1, 0xa7, 0xa8, 0x91, 0x78, 0x66, 0x55, 0x4a, 0x50, + 0x54, 0x50, 0x50, 0x58, 0x5a, 0x65, 0x8b, 0x9b, 0x9b, 0xb7, 0xc9, 0xb3, 0xa6, 0xa2, 0x7d, 0x5a, + 0x66, 0x6f, 0x70, 0x94, 0xa2, 0x90, 0x9b, 0xa5, 0x8f, 0x82, 0x77, 0x5c, 0x58, 0x60, 0x50, 0x46, + 0x56, 0x49, 0x3a, 0x54, 0x97, 0xbe, 0xa9, 0xb0, 0xad, 0x91, 0xa7, 0xb3, 0x83, 0x6f, 0x6c, 0x5b, + 0x71, 0x91, 0x9c, 0xac, 0x98, 0x78, 0x8a, 0xa6, 0xad, 0x9e, 0x72, 0x4d, 0x4e, 0x4f, 0x4e, 0x4a, + 0x48, 0x46, 0x42, 0x4e, 0x99, 0xd5, 0xae, 0xb0, 0xb1, 0x8a, 0xb3, 0xbd, 0x82, 0x6b, 0x53, 0x56, + 0x8b, 0x97, 0xa7, 0xaf, 0x74, 0x6b, 0x92, 0xaf, 0xc1, 0x8f, 0x55, 0x47, 0x4e, 0x60, 0x5e, 0x45, + 0x4a, 0x4f, 0x3a, 0x44, 0x9f, 0xdf, 0xac, 0xa8, 0x93, 0x79, 0xbf, 0xc3, 0x92, 0x67, 0x36, 0x5a, + 0x90, 0x9b, 0xb6, 0xa1, 0x6b, 0x68, 0x8d, 0xc3, 0xca, 0x83, 0x4f, 0x3d, 0x53, 0x72, 0x63, 0x46, + 0x44, 0x55, 0x4f, 0x4c, 0x78, 0xcb, 0xbb, 0x93, 0x99, 0x79, 0xad, 0xd0, 0x9f, 0x70, 0x37, 0x4f, + 0x90, 0x9e, 0xaf, 0x94, 0x73, 0x71, 0x89, 0xc0, 0xc0, 0x8f, 0x5b, 0x45, 0x62, 0x79, 0x6f, 0x5b, + 0x46, 0x56, 0x54, 0x53, 0x59, 0x90, 0xd8, 0x95, 0x8c, 0x8c, 0x88, 0xd6, 0xb8, 0x83, 0x4c, 0x2f, + 0x80, 0xa2, 0xaa, 0x9c, 0x69, 0x74, 0x80, 0xb0, 0xc6, 0x99, 0x78, 0x54, 0x69, 0x80, 0x7c, 0x69, + 0x4b, 0x4e, 0x57, 0x4e, 0x4c, 0x5f, 0xae, 0xc3, 0x82, 0x86, 0x83, 0xac, 0xd9, 0xa3, 0x6a, 0x31, + 0x50, 0xa0, 0xad, 0xa6, 0x6d, 0x59, 0x7f, 0x9e, 0xc8, 0xaf, 0x81, 0x74, 0x70, 0x8b, 0x83, 0x76, + 0x58, 0x50, 0x56, 0x59, 0x58, 0x49, 0x62, 0x7c, 0xce, 0x99, 0x71, 0x9c, 0x8d, 0xd4, 0xb1, 0x6c, + 0x4f, 0x37, 0x95, 0xab, 0x9b, 0x7f, 0x4b, 0x82, 0xa2, 0xba, 0xb5, 0x7b, 0x7d, 0x7d, 0x8d, 0x8b, + 0x71, 0x62, 0x54, 0x5b, 0x4e, 0x5d, 0x4c, 0x5e, 0x57, 0x9c, 0xd4, 0x67, 0x94, 0x83, 0xa2, 0xd8, + 0x83, 0x70, 0x2e, 0x59, 0xb5, 0x9d, 0xa1, 0x51, 0x55, 0x97, 0xad, 0xcb, 0x86, 0x77, 0x78, 0x95, + 0xa1, 0x76, 0x6d, 0x58, 0x67, 0x5b, 0x4f, 0x66, 0x55, 0x67, 0x4e, 0x67, 0xd9, 0x88, 0x89, 0x86, + 0x6f, 0xcd, 0x9b, 0x89, 0x4e, 0x39, 0x9f, 0xa0, 0xa9, 0x7a, 0x47, 0x88, 0x99, 0xbe, 0xac, 0x6b, + 0x88, 0x87, 0xaf, 0x9a, 0x67, 0x71, 0x63, 0x74, 0x62, 0x55, 0x5c, 0x5e, 0x65, 0x5c, 0x54, 0xb1, + 0xb0, 0x79, 0x8d, 0x6f, 0xac, 0xb7, 0x8e, 0x73, 0x44, 0x7b, 0xa1, 0x99, 0x90, 0x5a, 0x70, 0x97, + 0xa0, 0xb4, 0x89, 0x83, 0x8e, 0x96, 0xa3, 0x7e, 0x6f, 0x6c, 0x6a, 0x6b, 0x5b, 0x5a, 0x61, 0x5e, + 0x5d, 0x63, 0x66, 0xa0, 0xa6, 0x7c, 0x8d, 0x83, 0xa4, 0xad, 0x88, 0x7b, 0x58, 0x75, 0x95, 0x91, + 0x92, 0x70, 0x75, 0x93, 0x9c, 0xab, 0x92, 0x84, 0x8d, 0x91, 0x96, 0x81, 0x70, 0x6b, 0x6c, 0x68, + 0x62, 0x59, 0x5e, 0x69, 0x5a, 0x5a, 0x68, 0x5f, 0xa2, 0xb0, 0x6d, 0x87, 0x7e, 0xa0, 0xba, 0x89, + 0x78, 0x53, 0x73, 0xa6, 0x9b, 0x95, 0x6c, 0x65, 0x8e, 0x9a, 0xab, 0x97, 0x7b, 0x85, 0x8e, 0x9a, + 0x91, 0x71, 0x6b, 0x68, 0x65, 0x6e, 0x58, 0x5d, 0x70, 0x5d, 0x6d, 0x67, 0x5e, 0x80, 0x78, 0x94, + 0x98, 0x7c, 0x96, 0x90, 0xa1, 0xa5, 0x82, 0x7f, 0x70, 0x7e, 0x94, 0x87, 0x87, 0x80, 0x88, 0x92, + 0x8e, 0x96, 0x8c, 0x89, 0x84, 0x73, 0x72, 0x6f, 0x71, 0x6d, 0x5e, 0x61, 0x6a, 0x70, 0x77, 0x6f, + 0x6d, 0x79, 0x76, 0x7f, 0x77, 0x75, 0x7e, 0x90, 0xa8, 0x8c, 0x85, 0x98, 0x9b, 0xa7, 0x93, 0x79, + 0x78, 0x79, 0x91, 0x94, 0x87, 0x86, 0x85, 0x86, 0x8b, 0x89, 0x82, 0x7c, 0x74, 0x6d, 0x6c, 0x75, + 0x75, 0x6f, 0x64, 0x69, 0x74, 0x7e, 0x83, 0x76, 0x75, 0x85, 0x8a, 0x89, 0x88, 0x78, 0x81, 0x88, + 0x83, 0x85, 0x7e, 0x80, 0x88, 0x89, 0x8c, 0x8d, 0x8a, 0x8b, 0x88, 0x88, 0x89, 0x85, 0x81, 0x81, + 0x7e, 0x7c, 0x7c, 0x77, 0x7d, 0x76, 0x6f, 0x7d, 0x7f, 0x78, 0x73, 0x76, 0x83, 0x84, 0x80, 0x7f, + 0x82, 0x86, 0x80, 0x81, 0x83, 0x81, 0x81, 0x7e, 0x7d, 0x7b, 0x83, 0x8b, 0x85, 0x7a, 0x76, 0x83, + 0x87, 0x82, 0x7d, 0x76, 0x7b, 0x80, 0x83, 0x81, 0x7a, 0x79, 0x7d, 0x82, 0x81, 0x82, 0x82, 0x83, + 0x86, 0x80, 0x80, 0x81, 0x7e, 0x80, 0x7d, 0x7a, 0x7e, 0x81, 0x7e, 0x7e, 0x80, 0x7f, 0x81, 0x82, + 0x80, 0x81, 0x82, 0x7f, 0x7f, 0x7d, 0x7c, 0x7f, 0x7b, 0x7b, 0x7d, 0x7a, 0x7a, 0x7e, 0x7e, 0x7c, + 0x7c, 0x7f, 0x80, 0x7f, 0x80, 0x82, 0x81, 0x81, 0x80, 0x7e, 0x80, 0x7f, 0x81, 0x7b, 0x7c, 0x7f, + 0x7f, 0x81, 0x7f, 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x83, 0x7e, 0x7f, 0x85, 0x81, 0x83, + 0x84, 0x80, 0x84, 0x81, 0x81, 0x83, 0x81, 0x83, 0x80, 0x84, 0x80, 0x80, 0x85, 0x80, 0x81, 0x7f, + 0x82, 0x82, 0x81, 0x81, 0x80, 0x81, 0x80, 0x87, 0x81, 0x7c, 0x80, 0x7f, 0x80, 0x7d, 0x7c, 0x7d, + 0x80, 0x80, 0x80, 0x82, 0x7d, 0x81, 0x82, 0x7e, 0x82, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x82, + 0x7f, 0x80, 0x7f, 0x7f, 0x81, 0x7f, 0x80, 0x7e, 0x81, 0x80, 0x7e, 0x80, 0x7e, 0x7f, 0x80, 0x80, + 0x82, 0x7f, 0x83, 0x83, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x81, 0x80, 0x7f, 0x7f, 0x7f, 0x7e, 0x7f, 0x7e, 0x7d, 0x7e, 0x7d, 0x7c, 0x7d, 0x7c, 0x7c, + 0x7d, 0x7c, 0x7d, 0x7e, 0x7f, 0x7e, 0x7e, 0x7f, 0x7d, 0x7f, 0x7f, 0x80, 0x7f, 0x7e, 0x7f, 0x80, + 0x7e, 0x80, 0x7e, 0x7e, 0x80, 0x7e, 0x80, 0x7e, 0x7f, 0x7e, 0x7d, 0x7f, 0x7d, 0x7d, 0x7d, 0x7d, + 0x7d, 0x7d, 0x7e, 0x7f, 0x7f, 0x7d, 0x7e, 0x7f, 0x7e, 0x80, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x80, + 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x81, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, + 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x80, 0x7f, 0x7f, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80, + 0x81, 0x80, 0x82, 0x83, 0x81, 0x82, 0x81, 0x82, 0x82, 0x82, 0x81, 0x81, 0x83, 0x82, 0x82, 0x82, + 0x81, 0x83, 0x82, 0x81, 0x81, 0x80, 0x80, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7e, 0x80, 0x7d, 0x80, + 0x81, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x81, 0x81, 0x80, 0x81, 0x81, 0x80, + 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7e, 0x7f, 0x81, 0x80, 0x7f, 0x81, 0x81, 0x82, 0x81, + 0x80, 0x82, 0x82, 0x80, 0x81, 0x81, 0x80, 0x80, 0x7e, 0x7d, 0x7f, 0x7e, 0x81, 0x81, 0x7e, 0x7f, + 0x82, 0x7f, 0x7d, 0x7f, 0x7d, 0x81, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x80, + 0x7f, 0x7e, 0x7f, 0x7f, 0x7e, 0x7c, 0x7d, 0x7e, 0x7d, 0x7d, 0x7e, 0x7d, 0x7e, 0x7c, 0x7e, 0x7e, + 0x7c, 0x7e, 0x7d, 0x7e, 0x7e, 0x7e, 0x7d, 0x7d, 0x7c, 0x7b, 0x7c, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, + 0x7b, 0x7c, 0x7c, 0x7d, 0x7c, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7c, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, + 0x7d, 0x7d, 0x7d, 0x7d, 0x7e, 0x7d, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x82, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x81, + 0x81, 0x81, 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x81, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x81, 0x80, 0x83, 0x80, 0x80, 0x80, 0x84, 0x84, + 0x7b, 0x7e, 0x80, 0x80, 0x7e, 0x80, 0x7e, 0x7f, 0x81, 0x81, 0x80, 0x7f, 0x80, 0x7f, 0x7e, 0x7e, + 0x7f, 0x80, 0x80, 0x7f, 0x81, 0x82, 0x80, 0x80, 0x81, 0x81, 0x80, 0x81, 0x7f, 0x80, 0x80, 0x81, + 0x81, 0x81, 0x81, 0x84, 0x83, 0x7f, 0x7f, 0x80, 0x80, 0x7f, 0x81, 0x7e, 0x7e, 0x7f, 0x81, 0x7f, + 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x81, 0x82, 0x82, 0x80, 0x7f, 0x80, + 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, 0x7d, 0x7e, 0x7e, 0x7f, 0x80, + 0x80, 0x80, 0x80, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, 0x80, 0x80, 0x81, 0x7f, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x7e, 0x7e, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7f, + 0x7e, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7e, 0x80, 0x7e, 0x7f, 0x7f, + 0x7e, 0x7f, 0x7e, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7e, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x81, 0x80, 0x7f, 0x80, 0x80, 0x82, + 0x81, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x81, 0x81, 0x80, 0x81, 0x80, 0x82, 0x7f, + 0x7f, 0x7e, 0x7e, 0x80, 0x7e, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x81, 0x80, + 0x81, 0x80, 0x80, 0x81, 0x80, 0x83, 0x80, 0x80, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7e, 0x80, 0x7f, + 0x7f, 0x80, 0x7f, 0x82, 0x80, 0x81, 0x7f, 0x7e, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, + 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x80, 0x7f, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x80, 0x81, 0x80, 0x80, 0x80, 0x81, 0x81, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7e, 0x7e, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x81, 0x7f, 0x80, 0x7e, 0x7f, 0x7f, + 0x7e, 0x80, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7d, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, 0x80, + 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x81, 0x81, 0x81, 0x80, 0x80, 0x7f, 0x7f, 0x80, 0x7f, + 0x7f, 0x7f, 0x81, 0x81, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x7f, 0x7f, + 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x80, 0x81, 0x80, 0x80, 0x7f, 0x7e, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x7e, 0x7f, 0x7e +}; + +static const Uint8 right[1777] = { + 0x7f, 0x7e, 0x7e, 0x7f, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x82, 0x83, 0x83, 0x83, + 0x82, 0x81, 0x81, 0x80, 0x7f, 0x7e, 0x7c, 0x7b, 0x7a, 0x7a, 0x79, 0x79, 0x79, 0x7a, 0x7a, 0x7b, + 0x7c, 0x7e, 0x80, 0x82, 0x84, 0x86, 0x88, 0x89, 0x89, 0x89, 0x88, 0x87, 0x84, 0x82, 0x80, 0x7e, + 0x7c, 0x7b, 0x7a, 0x7a, 0x79, 0x78, 0x77, 0x75, 0x76, 0x77, 0x78, 0x78, 0x78, 0x7b, 0x81, 0x87, + 0x8c, 0x8e, 0x90, 0x92, 0x91, 0x8d, 0x87, 0x81, 0x7d, 0x7b, 0x7a, 0x79, 0x79, 0x7a, 0x79, 0x78, + 0x75, 0x74, 0x75, 0x75, 0x75, 0x76, 0x76, 0x76, 0x76, 0x7b, 0x83, 0x88, 0x8b, 0x8f, 0x95, 0x98, + 0x95, 0x8d, 0x86, 0x83, 0x80, 0x7e, 0x7c, 0x7c, 0x7e, 0x7e, 0x7c, 0x79, 0x78, 0x76, 0x75, 0x72, + 0x73, 0x74, 0x72, 0x6f, 0x6d, 0x72, 0x7e, 0x87, 0x8b, 0x90, 0x98, 0x9f, 0x9b, 0x91, 0x85, 0x7f, + 0x7b, 0x78, 0x79, 0x7f, 0x87, 0x8b, 0x8a, 0x89, 0x89, 0x86, 0x81, 0x79, 0x75, 0x74, 0x73, 0x73, + 0x6f, 0x6d, 0x6e, 0x6e, 0x6e, 0x6f, 0x72, 0x77, 0x82, 0x8f, 0x95, 0x99, 0x9c, 0x9e, 0x99, 0x8c, + 0x7f, 0x74, 0x71, 0x70, 0x74, 0x7e, 0x8a, 0x92, 0x91, 0x8f, 0x8f, 0x8d, 0x85, 0x7c, 0x76, 0x75, + 0x76, 0x75, 0x71, 0x6d, 0x6b, 0x68, 0x64, 0x64, 0x66, 0x6e, 0x83, 0x8f, 0x93, 0x9b, 0xa3, 0xa4, + 0x97, 0x86, 0x76, 0x6f, 0x6d, 0x6e, 0x78, 0x87, 0x94, 0x98, 0x96, 0x94, 0x91, 0x89, 0x7e, 0x74, + 0x6f, 0x70, 0x74, 0x72, 0x6e, 0x6b, 0x67, 0x62, 0x60, 0x60, 0x69, 0x84, 0x91, 0x95, 0xa1, 0xae, + 0xb0, 0x9b, 0x84, 0x74, 0x6a, 0x65, 0x67, 0x78, 0x8b, 0x98, 0x9f, 0x9e, 0x9a, 0x90, 0x86, 0x7c, + 0x71, 0x6a, 0x6c, 0x73, 0x74, 0x6d, 0x69, 0x65, 0x5e, 0x5c, 0x60, 0x6f, 0x8b, 0x95, 0x9b, 0xac, + 0xb3, 0xa5, 0x89, 0x7a, 0x6b, 0x5c, 0x5f, 0x70, 0x88, 0x97, 0xa5, 0xac, 0xa1, 0x95, 0x8e, 0x86, + 0x76, 0x6a, 0x6b, 0x72, 0x72, 0x6c, 0x67, 0x5e, 0x55, 0x52, 0x56, 0x78, 0x9c, 0x91, 0x9c, 0xbc, + 0xb8, 0x98, 0x83, 0x7f, 0x5e, 0x4c, 0x6c, 0x83, 0x8a, 0x9a, 0xb7, 0xae, 0x8a, 0x8f, 0x93, 0x79, + 0x69, 0x76, 0x76, 0x69, 0x70, 0x70, 0x5b, 0x50, 0x51, 0x57, 0x52, 0x77, 0xb2, 0x90, 0x95, 0xc8, + 0xb1, 0x8d, 0x89, 0x8a, 0x55, 0x4e, 0x87, 0x7f, 0x82, 0xb3, 0xb9, 0x8f, 0x8c, 0x9d, 0x79, 0x71, + 0x80, 0x6a, 0x61, 0x7b, 0x70, 0x51, 0x63, 0x62, 0x3e, 0x50, 0x61, 0x9a, 0xad, 0x7e, 0xba, 0xb5, + 0x94, 0x9f, 0x93, 0x75, 0x4b, 0x7b, 0x79, 0x6c, 0xab, 0xaf, 0x9f, 0x93, 0x8e, 0x7a, 0x7f, 0x89, + 0x6a, 0x6e, 0x71, 0x66, 0x5e, 0x63, 0x5c, 0x53, 0x53, 0x50, 0x5a, 0xb8, 0xbd, 0x6d, 0xc3, 0xb2, + 0x8a, 0xa7, 0xa1, 0x70, 0x4c, 0x88, 0x63, 0x7d, 0xb1, 0xa1, 0xa6, 0x8e, 0x6a, 0x7c, 0x95, 0x8b, + 0x84, 0x72, 0x5c, 0x5c, 0x67, 0x64, 0x61, 0x56, 0x65, 0x52, 0x44, 0x80, 0xda, 0x8a, 0x88, 0xc9, + 0x89, 0x96, 0xb1, 0x92, 0x4a, 0x6f, 0x6d, 0x78, 0xa5, 0xa7, 0xa0, 0x98, 0x66, 0x6e, 0xa6, 0x9d, + 0x95, 0x70, 0x52, 0x57, 0x73, 0x69, 0x72, 0x5a, 0x55, 0x52, 0x50, 0x3d, 0xb8, 0xdb, 0x5d, 0xa9, + 0xab, 0x82, 0xad, 0xc3, 0x65, 0x4c, 0x6c, 0x6d, 0x98, 0xac, 0x9f, 0x97, 0x74, 0x5a, 0xa0, 0xb1, + 0x9e, 0x7e, 0x52, 0x54, 0x74, 0x71, 0x6a, 0x6a, 0x5a, 0x53, 0x4b, 0x46, 0x5e, 0xe5, 0xaa, 0x62, + 0xab, 0x8f, 0x97, 0xcb, 0xa5, 0x4b, 0x4f, 0x67, 0x88, 0xa6, 0xa4, 0x98, 0x84, 0x61, 0x80, 0xb7, + 0xb4, 0x98, 0x64, 0x4e, 0x64, 0x77, 0x72, 0x72, 0x55, 0x54, 0x4e, 0x52, 0x3c, 0x96, 0xf0, 0x69, + 0x7f, 0xa2, 0x80, 0xc1, 0xc8, 0x75, 0x46, 0x4d, 0x74, 0xa4, 0x9e, 0x95, 0x8a, 0x6a, 0x73, 0xa6, + 0xb7, 0xb4, 0x81, 0x60, 0x5e, 0x71, 0x7c, 0x74, 0x6b, 0x54, 0x54, 0x48, 0x4f, 0x44, 0xc3, 0xcb, + 0x5b, 0x9b, 0x86, 0x99, 0xd4, 0xa3, 0x71, 0x3e, 0x4b, 0x91, 0x99, 0x9d, 0x95, 0x70, 0x72, 0x85, + 0xb0, 0xbd, 0xa5, 0x7e, 0x67, 0x67, 0x78, 0x77, 0x6e, 0x63, 0x53, 0x5b, 0x39, 0x50, 0x48, 0xb5, + 0xc2, 0x6a, 0xa5, 0x77, 0xa8, 0xbd, 0x98, 0x89, 0x3a, 0x60, 0x83, 0x85, 0xa9, 0x87, 0x87, 0x74, + 0x77, 0xac, 0xa9, 0xb9, 0x8a, 0x71, 0x6b, 0x6d, 0x81, 0x6d, 0x66, 0x51, 0x60, 0x3c, 0x50, 0x4a, + 0x91, 0xbf, 0x83, 0xae, 0x7a, 0xa4, 0xa1, 0x97, 0x92, 0x4b, 0x73, 0x68, 0x86, 0x8e, 0x8c, 0x95, + 0x79, 0x83, 0x86, 0xa2, 0xab, 0xa6, 0x8d, 0x79, 0x6a, 0x75, 0x68, 0x74, 0x56, 0x5c, 0x4e, 0x4c, + 0x49, 0x5d, 0xb1, 0x88, 0xb9, 0x8d, 0xa4, 0x90, 0x94, 0x8b, 0x66, 0x72, 0x69, 0x83, 0x7c, 0x91, + 0x82, 0x89, 0x79, 0x87, 0x8a, 0xa1, 0x9f, 0xa5, 0x95, 0x8d, 0x7a, 0x6f, 0x6f, 0x61, 0x62, 0x58, + 0x5f, 0x52, 0x52, 0x4f, 0x80, 0x90, 0xa1, 0xa6, 0xa3, 0x9c, 0x90, 0x86, 0x74, 0x6d, 0x6c, 0x7a, + 0x83, 0x8a, 0x8c, 0x88, 0x7f, 0x80, 0x82, 0x8f, 0x99, 0x9e, 0xa3, 0x9a, 0x93, 0x84, 0x73, 0x68, + 0x5d, 0x5e, 0x5d, 0x5f, 0x5e, 0x5d, 0x52, 0x6a, 0x7d, 0x8d, 0x9f, 0xa6, 0xac, 0xa0, 0x95, 0x7d, + 0x6e, 0x64, 0x6a, 0x76, 0x81, 0x8e, 0x98, 0x94, 0x8e, 0x84, 0x84, 0x84, 0x86, 0x91, 0x98, 0x9d, + 0x9a, 0x8c, 0x7b, 0x67, 0x5c, 0x58, 0x58, 0x5e, 0x5e, 0x64, 0x60, 0x67, 0x75, 0x7f, 0x8e, 0x99, + 0xa2, 0xa5, 0xa2, 0x99, 0x87, 0x74, 0x6a, 0x67, 0x6e, 0x7b, 0x87, 0x96, 0x97, 0x97, 0x94, 0x8e, + 0x8c, 0x8a, 0x8b, 0x8a, 0x8a, 0x88, 0x84, 0x7b, 0x72, 0x66, 0x5e, 0x58, 0x57, 0x5a, 0x60, 0x64, + 0x6c, 0x78, 0x81, 0x8c, 0x96, 0x9d, 0x9f, 0xa1, 0x99, 0x8f, 0x80, 0x76, 0x70, 0x6c, 0x70, 0x76, + 0x81, 0x8a, 0x93, 0x97, 0x98, 0x94, 0x91, 0x8c, 0x8a, 0x87, 0x81, 0x7c, 0x74, 0x71, 0x6b, 0x68, + 0x65, 0x62, 0x60, 0x61, 0x63, 0x67, 0x6c, 0x77, 0x82, 0x8a, 0x96, 0x9c, 0xa4, 0xa5, 0xa0, 0x95, + 0x86, 0x7b, 0x71, 0x6e, 0x6e, 0x73, 0x79, 0x82, 0x8b, 0x94, 0x99, 0x98, 0x95, 0x8e, 0x88, 0x81, + 0x7b, 0x77, 0x73, 0x6f, 0x6c, 0x6a, 0x68, 0x67, 0x66, 0x69, 0x69, 0x6e, 0x70, 0x77, 0x81, 0x88, + 0x91, 0x97, 0x9f, 0xa1, 0xa2, 0x9b, 0x92, 0x82, 0x77, 0x6c, 0x6a, 0x6b, 0x71, 0x79, 0x83, 0x8d, + 0x93, 0x97, 0x96, 0x95, 0x8f, 0x8b, 0x84, 0x7d, 0x76, 0x71, 0x6a, 0x68, 0x67, 0x68, 0x6b, 0x6d, + 0x71, 0x73, 0x76, 0x79, 0x7e, 0x83, 0x8a, 0x8f, 0x94, 0x97, 0x97, 0x95, 0x8f, 0x87, 0x7f, 0x79, + 0x76, 0x76, 0x78, 0x7a, 0x7e, 0x81, 0x86, 0x8a, 0x8c, 0x8e, 0x8d, 0x8a, 0x86, 0x80, 0x7c, 0x78, + 0x74, 0x71, 0x70, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, 0x80, 0x82, 0x82, 0x81, 0x80, + 0x7e, 0x7f, 0x81, 0x84, 0x88, 0x8b, 0x8d, 0x8d, 0x8b, 0x87, 0x83, 0x7d, 0x7a, 0x77, 0x78, 0x7a, + 0x7e, 0x81, 0x83, 0x84, 0x84, 0x84, 0x82, 0x80, 0x7f, 0x7d, 0x7b, 0x7a, 0x78, 0x78, 0x77, 0x78, + 0x78, 0x79, 0x7c, 0x7e, 0x81, 0x83, 0x83, 0x84, 0x83, 0x82, 0x81, 0x80, 0x80, 0x7f, 0x7f, 0x80, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x84, 0x84, 0x83, 0x83, 0x81, 0x80, 0x7f, 0x7f, 0x80, 0x80, 0x80, + 0x7f, 0x7e, 0x7d, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7f, 0x80, 0x80, 0x81, 0x80, 0x7f, 0x7e, 0x7d, + 0x7d, 0x7d, 0x7e, 0x80, 0x80, 0x81, 0x82, 0x82, 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x81, 0x80, + 0x81, 0x80, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x81, 0x80, 0x80, 0x7e, 0x7d, 0x7d, 0x7e, 0x7e, 0x7f, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x80, 0x80, 0x80, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x82, 0x82, 0x82, 0x81, 0x80, + 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7e, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x82, + 0x81, 0x81, 0x80, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x81, 0x80, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7d, 0x7e, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x82, 0x82, 0x81, 0x81, 0x80, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x80, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x80, + 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7f, 0x87, 0x83, 0x7d, 0x81, 0x80, 0x7e, 0x81, 0x7b, + 0x7d, 0x84, 0x7f, 0x81, 0x83, 0x82, 0x7f, 0x80, 0x7c, 0x7b, 0x7d, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x7e, 0x7f, 0x7e, 0x7f, 0x80, 0x80, 0x81, 0x82, 0x82, 0x82, 0x82, 0x80, 0x80, 0x7f, 0x7f, + 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x82, 0x80, 0x7f, 0x80, 0x81, 0x80, 0x81, 0x7f, + 0x83, 0x85, 0x7f, 0x80, 0x84, 0x83, 0x7d, 0x7c, 0x7d, 0x80, 0x7d, 0x7d, 0x7e, 0x7e, 0x7d, 0x83, + 0x81, 0x7d, 0x7d, 0x81, 0x7f, 0x7c, 0x7c, 0x7c, 0x7d, 0x7c, 0x83, 0x80, 0x84, 0x84, 0x82, 0x7d, + 0x7f, 0x7d, 0x7c, 0x7e, 0x7e, 0x7f, 0x81, 0x84, 0x82, 0x81, 0x7e, 0x7f, 0x7f, 0x7f, 0x7e, 0x80, + 0x81, 0x80, 0x7f, 0x80, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, + 0x7f, 0x7f, 0x7f, 0x7f, 0x81, 0x80, 0x82, 0x81, 0x83, 0x81, 0x82, 0x80, 0x80, 0x7f, 0x7f, 0x80, + 0x7d, 0x80, 0x7e, 0x81, 0x7f, 0x81, 0x7f, 0x80, 0x7f, 0x7f, 0x7e, 0x7d, 0x81, 0x80, 0x82, 0x7f, + 0x81, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x7e, 0x80, 0x7f, 0x81, 0x7f, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x82, 0x81, 0x81, 0x80, 0x81, 0x7f, 0x80, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x7e, 0x7f, 0x7f, 0x7e, + 0x7f, 0x7e, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7e, 0x80, 0x7f, 0x82, 0x80, 0x81, 0x81, 0x80, + 0x81, 0x80, 0x81, 0x80, 0x81, 0x80, 0x81, 0x80, 0x80, 0x7e, 0x7f, 0x7e, 0x7d, 0x7e, 0x7e, 0x7f, + 0x7f, 0x7f, 0x7e, 0x7d, 0x80, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x81, 0x7e, 0x81, 0x81, 0x83, + 0x80, 0x81, 0x80, 0x80, 0x81, 0x7f, 0x80, 0x80, 0x80, 0x81, 0x7f, 0x80, 0x7f, 0x80, 0x7d, 0x80, + 0x7e, 0x7d, 0x80, 0x80, 0x83, 0x7f, 0x83, 0x7e, 0x83, 0x7f, 0x80, 0x7f, 0x7e, 0x81, 0x7f, 0x7f, + 0x80, 0x80, 0x81, 0x7e, 0x7f, 0x7f, 0x80, 0x81, 0x80, 0x83, 0x7f, 0x82, 0x7f, 0x82, 0x7f, 0x80, + 0x80, 0x7e, 0x7f, 0x7d, 0x7e, 0x7d, 0x7e, 0x7e, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x81, + 0x82, 0x80, 0x82, 0x80, 0x82, 0x80, 0x7f, 0x7e, 0x7f, 0x7e, 0x7f, 0x80, 0x7e, 0x80, 0x80, 0x81, + 0x7f, 0x7f, 0x7e, 0x80, 0x7d, 0x7e, 0x7e, 0x7f, 0x80, 0x7f, 0x80, 0x7e, 0x81, 0x7e, 0x81, 0x7f, + 0x80, 0x7f, 0x80, 0x81, 0x7f, 0x80, 0x7e, 0x81, 0x7e, 0x80, 0x7d, 0x80, 0x80, 0x80, 0x81, 0x80, + 0x82, 0x7e, 0x83, 0x7d, 0x80, 0x7c, 0x7d, 0x7e, 0x7c, 0x7e, 0x7d, 0x7e, 0x7f, 0x7e, 0x7e, 0x80, + 0x7e, 0x81, 0x7e, 0x81, 0x7f, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x83, 0x80, 0x81, 0x80, + 0x80, 0x80, 0x80, 0x7f, 0x80, 0x7e, 0x7f, 0x7f, 0x81, 0x7f, 0x80, 0x80, 0x7f, 0x7e, 0x80, 0x80, + 0x81, 0x82, 0x81, 0x82, 0x81, 0x81, 0x81, 0x82, 0x80, 0x80, 0x7e, 0x82, 0x80, 0x84, 0x81, 0x80, + 0x7f, 0x81, 0x80, 0x7f, 0x80, 0x7d, 0x80, 0x7d, 0x81, 0x7f, 0x81, 0x80, 0x81, 0x81, 0x80, 0x80, + 0x7e, 0x80, 0x7f, 0x81, 0x7f, 0x81, 0x81, 0x81, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x81, 0x80, 0x80, 0x7e, 0x81, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7e, 0x81, + 0x7e, 0x7f, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x81, 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x7f, 0x80, + 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7e, 0x7d, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x81, + 0x80 +}; + diff --git a/libs/SDL3/examples/audio/05-planar-data/thumbnail.png b/libs/SDL3/examples/audio/05-planar-data/thumbnail.png new file mode 100644 index 0000000..3a040df Binary files /dev/null and b/libs/SDL3/examples/audio/05-planar-data/thumbnail.png differ diff --git a/libs/SDL3/examples/camera/01-read-and-draw/read-and-draw.c b/libs/SDL3/examples/camera/01-read-and-draw/read-and-draw.c index 989fd54..7682da9 100644 --- a/libs/SDL3/examples/camera/01-read-and-draw/read-and-draw.c +++ b/libs/SDL3/examples/camera/01-read-and-draw/read-and-draw.c @@ -33,7 +33,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/camera/read-and-draw", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/camera/read-and-draw", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } @@ -83,6 +83,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) the window when we get a first frame from the camera. */ if (!texture) { SDL_SetWindowSize(window, frame->w, frame->h); /* Resize the window to match */ + SDL_SetRenderLogicalPresentation(renderer, frame->w, frame->h, SDL_LOGICAL_PRESENTATION_LETTERBOX); texture = SDL_CreateTexture(renderer, frame->format, SDL_TEXTUREACCESS_STREAMING, frame->w, frame->h); } diff --git a/libs/SDL3/examples/categories.txt b/libs/SDL3/examples/categories.txt index 8a9b5ec..bbe30f2 100644 --- a/libs/SDL3/examples/categories.txt +++ b/libs/SDL3/examples/categories.txt @@ -10,4 +10,5 @@ audio camera asyncio pen +misc demo diff --git a/libs/SDL3/examples/demo/01-snake/snake.c b/libs/SDL3/examples/demo/01-snake/snake.c index 0aca862..5c63251 100644 --- a/libs/SDL3/examples/demo/01-snake/snake.c +++ b/libs/SDL3/examples/demo/01-snake/snake.c @@ -18,9 +18,12 @@ #define SNAKE_GAME_HEIGHT 18U #define SNAKE_MATRIX_SIZE (SNAKE_GAME_WIDTH * SNAKE_GAME_HEIGHT) -#define THREE_BITS 0x7U /* ~CHAR_MAX >> (CHAR_BIT - SNAKE_CELL_MAX_BITS) */ +#define SNAKE_CELL_MAX_BITS 3U /* floor(log2(SNAKE_CELL_FOOD)) + 1 */ +#define SNAKE_CELL_SET_BITS (~(~0u << SNAKE_CELL_MAX_BITS)) #define SHIFT(x, y) (((x) + ((y) * SNAKE_GAME_WIDTH)) * SNAKE_CELL_MAX_BITS) +static SDL_Joystick *joystick = NULL; + typedef enum { SNAKE_CELL_NOTHING = 0U, @@ -31,8 +34,6 @@ typedef enum SNAKE_CELL_FOOD = 5U } SnakeCell; -#define SNAKE_CELL_MAX_BITS 3U /* floor(log2(SNAKE_CELL_FOOD)) + 1 */ - typedef enum { SNAKE_DIR_RIGHT, @@ -66,7 +67,7 @@ SnakeCell snake_cell_at(const SnakeContext *ctx, char x, char y) const int shift = SHIFT(x, y); unsigned short range; SDL_memcpy(&range, ctx->cells + (shift / 8), sizeof(range)); - return (SnakeCell)((range >> (shift % 8)) & THREE_BITS); + return (SnakeCell)((range >> (shift % 8)) & SNAKE_CELL_SET_BITS); } static void set_rect_xy_(SDL_FRect *r, short x, short y) @@ -82,8 +83,8 @@ static void put_cell_at_(SnakeContext *ctx, char x, char y, SnakeCell ct) unsigned char *const pos = ctx->cells + (shift / 8); unsigned short range; SDL_memcpy(&range, pos, sizeof(range)); - range &= ~(THREE_BITS << adjust); /* clear bits */ - range |= (ct & THREE_BITS) << adjust; + range &= ~(SNAKE_CELL_SET_BITS << adjust); /* clear bits */ + range |= (ct & SNAKE_CELL_SET_BITS) << adjust; SDL_memcpy(pos, &range, sizeof(range)); } @@ -186,6 +187,8 @@ void snake_step(SnakeContext *ctx) case SNAKE_DIR_DOWN: ++ctx->head_ypos; break; + default: + break; } wrap_around_(&ctx->head_xpos, SNAKE_GAME_WIDTH); wrap_around_(&ctx->head_ypos, SNAKE_GAME_HEIGHT); @@ -238,6 +241,26 @@ static SDL_AppResult handle_key_event_(SnakeContext *ctx, SDL_Scancode key_code) return SDL_APP_CONTINUE; } +static SDL_AppResult handle_hat_event_(SnakeContext *ctx, Uint8 hat) { + switch (hat) { + case SDL_HAT_RIGHT: + snake_redir(ctx, SNAKE_DIR_RIGHT); + break; + case SDL_HAT_UP: + snake_redir(ctx, SNAKE_DIR_UP); + break; + case SDL_HAT_LEFT: + snake_redir(ctx, SNAKE_DIR_LEFT); + break; + case SDL_HAT_DOWN: + snake_redir(ctx, SNAKE_DIR_DOWN); + break; + default: + break; + } + return SDL_APP_CONTINUE; +} + SDL_AppResult SDL_AppIterate(void *appstate) { AppState *as = (AppState *)appstate; @@ -305,7 +328,8 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) } } - if (!SDL_Init(SDL_INIT_VIDEO)) { + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); return SDL_APP_FAILURE; } @@ -316,9 +340,10 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) *appstate = as; - if (!SDL_CreateWindowAndRenderer("examples/demo/snake", SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT, 0, &as->window, &as->renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/demo/snake", SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &as->window, &as->renderer)) { return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(as->renderer, SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); snake_initialize(&as->snake_ctx); @@ -333,14 +358,35 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) switch (event->type) { case SDL_EVENT_QUIT: return SDL_APP_SUCCESS; + case SDL_EVENT_JOYSTICK_ADDED: + if (joystick == NULL) { + joystick = SDL_OpenJoystick(event->jdevice.which); + if (!joystick) { + SDL_Log("Failed to open joystick ID %u: %s", (unsigned int) event->jdevice.which, SDL_GetError()); + } + } + break; + case SDL_EVENT_JOYSTICK_REMOVED: + if (joystick && (SDL_GetJoystickID(joystick) == event->jdevice.which)) { + SDL_CloseJoystick(joystick); + joystick = NULL; + } + break; + case SDL_EVENT_JOYSTICK_HAT_MOTION: + return handle_hat_event_(ctx, event->jhat.value); case SDL_EVENT_KEY_DOWN: return handle_key_event_(ctx, event->key.scancode); + default: + break; } return SDL_APP_CONTINUE; } void SDL_AppQuit(void *appstate, SDL_AppResult result) { + if (joystick) { + SDL_CloseJoystick(joystick); + } if (appstate != NULL) { AppState *as = (AppState *)appstate; SDL_DestroyRenderer(as->renderer); diff --git a/libs/SDL3/examples/demo/02-woodeneye-008/woodeneye-008.c b/libs/SDL3/examples/demo/02-woodeneye-008/woodeneye-008.c index b97b06a..97a7201 100644 --- a/libs/SDL3/examples/demo/02-woodeneye-008/woodeneye-008.c +++ b/libs/SDL3/examples/demo/02-woodeneye-008/woodeneye-008.c @@ -265,7 +265,7 @@ static void draw(SDL_Renderer *renderer, const float (*edges)[6], const Player p SDL_RenderLine(renderer, hor_origin-10, ver_origin, hor_origin+10, ver_origin); } } - SDL_SetRenderClipRect(renderer, 0); + SDL_SetRenderClipRect(renderer, NULL); SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_RenderDebugText(renderer, 0, 0, debug_string); SDL_RenderPresent(renderer); @@ -347,7 +347,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) if (!SDL_Init(SDL_INIT_VIDEO)) { return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/demo/woodeneye-008", 640, 480, 0, &as->window, &as->renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/demo/woodeneye-008", 640, 480, SDL_WINDOW_RESIZABLE, &as->window, &as->renderer)) { 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 acb2ea4..e5c4e5c 100644 --- a/libs/SDL3/examples/demo/04-bytepusher/bytepusher.c +++ b/libs/SDL3/examples/demo/04-bytepusher/bytepusher.c @@ -27,13 +27,12 @@ typedef struct { Uint8 ram[RAM_SIZE + 8]; - Uint8 screenbuf[SCREEN_W * SCREEN_H]; Uint64 last_tick; Uint64 tick_acc; SDL_Window* window; SDL_Renderer* renderer; - SDL_Surface* screen; - SDL_Texture* screentex; + SDL_Palette* palette; + SDL_Texture* texture; SDL_Texture* rendertarget; /* we need this render target for text to look good */ SDL_AudioStream* audiostream; char status[SCREEN_W / 8]; @@ -131,16 +130,12 @@ static void print(BytePusher* vm, int x, int y, const char* str) { SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { BytePusher* vm; - SDL_Palette* palette; SDL_Rect usable_bounds; SDL_AudioSpec audiospec = { SDL_AUDIO_S8, 1, SAMPLES_PER_FRAME * FRAMES_PER_SECOND }; SDL_DisplayID primary_display; - SDL_PropertiesID texprops; int zoom = 2; int i; Uint8 r, g, b; - (void)argc; - (void)argv; if (!SDL_SetAppMetadata("SDL 3 BytePusher", "1.0", "com.example.SDL3BytePusher")) { return SDL_APP_FAILURE; @@ -186,13 +181,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { return SDL_APP_FAILURE; } - if (!(vm->screen = SDL_CreateSurfaceFrom( - SCREEN_W, SCREEN_H, SDL_PIXELFORMAT_INDEX8, vm->screenbuf, SCREEN_W - ))) { - return SDL_APP_FAILURE; - } - - if (!(palette = SDL_CreateSurfacePalette(vm->screen))) { + if (!(vm->palette = SDL_CreatePalette(256))) { return SDL_APP_FAILURE; } i = 0; @@ -200,27 +189,22 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { for (g = 0; g < 6; ++g) { for (b = 0; b < 6; ++b, ++i) { SDL_Color color = { (Uint8)(r * 0x33), (Uint8)(g * 0x33), (Uint8)(b * 0x33), SDL_ALPHA_OPAQUE }; - palette->colors[i] = color; + vm->palette->colors[i] = color; } } } for (; i < 256; ++i) { SDL_Color color = { 0, 0, 0, SDL_ALPHA_OPAQUE }; - palette->colors[i] = color; + vm->palette->colors[i] = color; } - texprops = SDL_CreateProperties(); - SDL_SetNumberProperty(texprops, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING); - SDL_SetNumberProperty(texprops, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, SCREEN_W); - SDL_SetNumberProperty(texprops, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, SCREEN_H); - vm->screentex = SDL_CreateTextureWithProperties(vm->renderer, texprops); - SDL_SetNumberProperty(texprops, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_TARGET); - vm->rendertarget = SDL_CreateTextureWithProperties(vm->renderer, texprops); - SDL_DestroyProperties(texprops); - if (!vm->screentex || !vm->rendertarget) { + vm->texture = SDL_CreateTexture(vm->renderer, SDL_PIXELFORMAT_INDEX8, SDL_TEXTUREACCESS_STREAMING, SCREEN_W, SCREEN_H); + vm->rendertarget = SDL_CreateTexture(vm->renderer, SDL_PIXELFORMAT_UNKNOWN, SDL_TEXTUREACCESS_TARGET, SCREEN_W, SCREEN_H); + if (!vm->texture || !vm->rendertarget) { return SDL_APP_FAILURE; } - SDL_SetTextureScaleMode(vm->screentex, SDL_SCALEMODE_NEAREST); + SDL_SetTexturePalette(vm->texture, vm->palette); + SDL_SetTextureScaleMode(vm->texture, SDL_SCALEMODE_NEAREST); SDL_SetTextureScaleMode(vm->rendertarget, SDL_SCALEMODE_NEAREST); if (!(vm->audiostream = SDL_OpenAudioDeviceStream( @@ -236,6 +220,10 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { vm->last_tick = SDL_GetTicksNS(); vm->tick_acc = NS_PER_SECOND; + if (argc > 1) { + load_file(vm, argv[1]); + } + return SDL_APP_CONTINUE; } @@ -284,30 +272,23 @@ SDL_AppResult SDL_AppIterate(void* appstate) { } if (updated) { - SDL_Surface *tex; + const void *pixels = &vm->ram[(Uint32)vm->ram[IO_SCREEN_PAGE] << 16]; + SDL_UpdateTexture(vm->texture, NULL, pixels, SCREEN_W); SDL_SetRenderTarget(vm->renderer, vm->rendertarget); + SDL_RenderTexture(vm->renderer, vm->texture, NULL, NULL); - if (!SDL_LockTextureToSurface(vm->screentex, NULL, &tex)) { - return SDL_APP_FAILURE; + if (vm->display_help) { + print(vm, 4, 4, "Drop a BytePusher file in this"); + print(vm, 8, 12, "window to load and run it!"); + print(vm, 4, 28, "Press ENTER to switch between"); + print(vm, 8, 36, "positional and symbolic input."); } - vm->screen->pixels = &vm->ram[(Uint32)vm->ram[IO_SCREEN_PAGE] << 16]; - SDL_BlitSurface(vm->screen, NULL, tex, NULL); - SDL_UnlockTexture(vm->screentex); - SDL_RenderTexture(vm->renderer, vm->screentex, NULL, NULL); - } - - if (vm->display_help) { - print(vm, 4, 4, "Drop a BytePusher file in this"); - print(vm, 8, 12, "window to load and run it!"); - print(vm, 4, 28, "Press ENTER to switch between"); - print(vm, 8, 36, "positional and symbolic input."); - } - - if (vm->status_ticks > 0) { - vm->status_ticks -= 1; - print(vm, 4, SCREEN_H - 12, vm->status); + if (vm->status_ticks > 0) { + vm->status_ticks -= 1; + print(vm, 4, SCREEN_H - 12, vm->status); + } } SDL_SetRenderTarget(vm->renderer, NULL); @@ -407,8 +388,8 @@ void SDL_AppQuit(void* appstate, SDL_AppResult result) { BytePusher* vm = (BytePusher*)appstate; SDL_DestroyAudioStream(vm->audiostream); SDL_DestroyTexture(vm->rendertarget); - SDL_DestroyTexture(vm->screentex); - SDL_DestroySurface(vm->screen); + SDL_DestroyTexture(vm->texture); + SDL_DestroyPalette(vm->palette); SDL_DestroyRenderer(vm->renderer); SDL_DestroyWindow(vm->window); SDL_free(vm); 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 6eb23b8..2eb8466 100644 --- a/libs/SDL3/examples/input/01-joystick-polling/joystick-polling.c +++ b/libs/SDL3/examples/input/01-joystick-polling/joystick-polling.c @@ -13,8 +13,8 @@ and knows how to map arbitrary buttons and such to look like an Xbox/PlayStation/etc gamepad. This is easier, and better, for many games, but isn't necessarily a good fit for complex apps and hardware. A flight - simulator, a realistic racing game, etc, might want this interface instead - of gamepads. */ + simulator, a realistic racing game, etc, might want the joystick interface + instead of gamepads. */ /* SDL can handle multiple joysticks, but for simplicity, this program only deals with the first stick it sees. */ @@ -41,7 +41,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/input/joystick-polling", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/input/joystick-polling", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } diff --git a/libs/SDL3/examples/input/02-joystick-events/joystick-events.c b/libs/SDL3/examples/input/02-joystick-events/joystick-events.c index cc01d84..2dbe058 100644 --- a/libs/SDL3/examples/input/02-joystick-events/joystick-events.c +++ b/libs/SDL3/examples/input/02-joystick-events/joystick-events.c @@ -13,8 +13,8 @@ and knows how to map arbitrary buttons and such to look like an Xbox/PlayStation/etc gamepad. This is easier, and better, for many games, but isn't necessarily a good fit for complex apps and hardware. A flight - simulator, a realistic racing game, etc, might want this interface instead - of gamepads. */ + simulator, a realistic racing game, etc, might want the joystick interface + instead of gamepads. */ #define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ #include @@ -111,7 +111,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/input/joystick-events", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/input/joystick-events", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } diff --git a/libs/SDL3/examples/input/02-joystick-events/onmouseover.webp b/libs/SDL3/examples/input/02-joystick-events/onmouseover.webp index 05a9b42..08f4035 100644 Binary files a/libs/SDL3/examples/input/02-joystick-events/onmouseover.webp and b/libs/SDL3/examples/input/02-joystick-events/onmouseover.webp differ diff --git a/libs/SDL3/examples/input/03-gamepad-polling/README.txt b/libs/SDL3/examples/input/03-gamepad-polling/README.txt new file mode 100644 index 0000000..d7e68b9 --- /dev/null +++ b/libs/SDL3/examples/input/03-gamepad-polling/README.txt @@ -0,0 +1,11 @@ +This example code looks for the current gamepad state once per frame, +and draws a visual representation of it. See 01-joystick-polling for the +equivalent example code for the lower-level joystick API. + +Please note that on the web, gamepads don't show up until you interact with +them, so press a button to "connect" the controller. + +Also note that on the web, gamepad triggers are treated as buttons (either +pressed or not) instead of axes (pressed 0 to 100 percent). This is a web +issue, not an SDL limitation. + diff --git a/libs/SDL3/examples/input/03-gamepad-polling/gamepad-polling.c b/libs/SDL3/examples/input/03-gamepad-polling/gamepad-polling.c new file mode 100644 index 0000000..a1d2258 --- /dev/null +++ b/libs/SDL3/examples/input/03-gamepad-polling/gamepad-polling.c @@ -0,0 +1,221 @@ +/* + * This example code looks for the current gamepad state once per frame, + * and draws a visual representation of it. See 01-joystick-polling for the + * equivalent example code for the lower-level joystick API. + * + * This code is public domain. Feel free to use it for any purpose! + */ + +/* Joysticks are low-level interfaces: there's something with a bunch of + buttons, axes and hats, in no understood order or position. This is + a flexible interface, but you'll need to build some sort of configuration + UI to let people tell you what button, etc, does what. On top of this + interface, SDL offers the "gamepad" API, which works with lots of devices, + and knows how to map arbitrary buttons and such to look like an + Xbox/PlayStation/etc gamepad. This is easier, and better, for many games, + but isn't necessarily a good fit for complex apps and hardware. A flight + simulator, a realistic racing game, etc, might want the joystick interface + instead of gamepads. */ + +/* SDL can handle multiple gamepads, but for simplicity, this program only + deals with the first gamepad it sees. */ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static SDL_Texture *texture = NULL; +static SDL_Gamepad *gamepad = NULL; + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + char *png_path = NULL; + SDL_Surface *surface = NULL; + + SDL_SetAppMetadata("Example Input Gamepad Polling", "1.0", "com.example.input-gamepad-polling"); + + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_CreateWindowAndRenderer("examples/input/gamepad-polling", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_STRETCH)) { + return SDL_APP_FAILURE; + } + + /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D + engines refer to these as "sprites." We'll do a static texture (upload once, draw many + times) with data from a bitmap file. */ + + /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%sgamepad_front.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); + if (!surface) { + SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + SDL_free(png_path); /* done with this, the file is loaded. */ + + texture = SDL_CreateTextureFromSurface(renderer, surface); + if (!texture) { + SDL_Log("Couldn't create static texture: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + SDL_DestroySurface(surface); /* done with this, the texture has a copy of the pixels now. */ + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } else if (event->type == SDL_EVENT_GAMEPAD_ADDED) { + /* this event is sent for each hotplugged gamepad, but also each already-connected gamepad during SDL_Init(). */ + if (gamepad == NULL) { /* we don't have a stick yet and one was added, open it! */ + gamepad = SDL_OpenGamepad(event->gdevice.which); + if (!gamepad) { + SDL_Log("Failed to open gamepad ID %u: %s", (unsigned int) event->gdevice.which, SDL_GetError()); + } + } + } else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) { + if (gamepad && (SDL_GetGamepadID(gamepad) == event->gdevice.which)) { + SDL_CloseGamepad(gamepad); /* our controller was unplugged. */ + gamepad = NULL; + } + } + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + const char *text = "Plug in a gamepad, please."; + static Uint64 leftthumblast = 0xFFFFFFFF; + static Uint64 rightthumblast = 0xFFFFFFFF; + const Uint64 now = SDL_GetTicks(); + Sint16 axis_x, axis_y; + float x, y; + int i; + + if (gamepad) { /* we have a stick opened? */ + text = SDL_GetGamepadName(gamepad); + } + + SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); /* white */ + SDL_RenderClear(renderer); + + /* note that you can get input as events, instead of polling, which is + better since it won't miss button presses if the system is lagging, + but often times checking the current state per-frame is good enough, + and maybe better if you'd rather _drop_ inputs due to lag. */ + + if (gamepad) { /* we have a stick opened? */ + /* where to draw the buttons */ + const SDL_FRect buttons[] = { + { 497, 266, 38, 38 }, /* SDL_GAMEPAD_BUTTON_SOUTH */ + { 550, 217, 38, 38 }, /* SDL_GAMEPAD_BUTTON_EAST */ + { 445, 221, 38, 38 }, /* SDL_GAMEPAD_BUTTON_WEST */ + { 499, 173, 38, 38 }, /* SDL_GAMEPAD_BUTTON_NORTH */ + { 235, 228, 32, 29 }, /* SDL_GAMEPAD_BUTTON_BACK */ + { 287, 195, 69, 69 }, /* SDL_GAMEPAD_BUTTON_GUIDE */ + { 377, 228, 32, 29 }, /* SDL_GAMEPAD_BUTTON_START */ + { 91, 234, 63, 63 }, /* SDL_GAMEPAD_BUTTON_LEFT_STICK */ + { 381, 354, 63, 63 }, /* SDL_GAMEPAD_BUTTON_RIGHT_STICK */ + { 74, 73, 102, 29 }, /* SDL_GAMEPAD_BUTTON_LEFT_SHOULDER */ + { 468, 73, 102, 29 }, /* SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER */ + { 207, 316, 32, 32 }, /* SDL_GAMEPAD_BUTTON_DPAD_UP */ + { 207, 384, 32, 32 }, /* SDL_GAMEPAD_BUTTON_DPAD_DOWN */ + { 173, 351, 32, 32 }, /* SDL_GAMEPAD_BUTTON_DPAD_LEFT */ + { 242, 351, 32, 32 }, /* SDL_GAMEPAD_BUTTON_DPAD_RIGHT */ + { 310, 286, 23, 27 }, /* SDL_GAMEPAD_BUTTON_MISC1 */ + /* there are other buttons: paddles on the back of the gamepad, touchpads, etc, but this is good enough for now. */ + }; + + SDL_RenderTexture(renderer, texture, NULL, NULL); /* draw the gamepad picture to the whole window. */ + + /* draw green boxes over buttons that are currently pressed. */ + SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0xFF); /* green */ + for (i = 0; i < SDL_arraysize(buttons); i++) { + if (SDL_GetGamepadButton(gamepad, (SDL_GamepadButton) i)) { + SDL_RenderFillRect(renderer, &buttons[i]); + } + } + + SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0x00, 0xFF); /* yellow */ + + /* left thumb axis. */ + axis_x = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX); + axis_y = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY); + if ((SDL_abs(axis_x) > 1000) || (SDL_abs(axis_y) > 1000)) { /* zero means centered, but it might be a little off zero... */ + leftthumblast = now; /* keep drawing, we're still moving. */ + } + if ((now - leftthumblast) < 500) { /* draw if there was movement in the last half-second. */ + const SDL_FRect box = { 107 + ((axis_x / 32767.0f) * 30.0f), 252 + ((axis_y / 32767.0f) * 30.0f), 30, 30 }; + SDL_RenderFillRect(renderer, &box); + } + + /* right thumb axis. */ + axis_x = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX); + axis_y = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY); + if ((SDL_abs(axis_x) > 1000) || (SDL_abs(axis_y) > 1000)) { /* zero means centered, but it might be a little off zero... */ + rightthumblast = now; /* keep drawing, we're still moving. */ + } + if ((now - rightthumblast) < 500) { /* draw if there was movement in the last half-second. */ + const SDL_FRect box = { 397 + ((axis_x / 32767.0f) * 30.0f), 370 + ((axis_y / 32767.0f) * 30.0f), 30, 30 }; + SDL_RenderFillRect(renderer, &box); + } + + /* left trigger. */ + axis_y = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER); + if (axis_y > 1000) { /* zero means unpressed, but it might be a little off zero... */ + const float height = ((axis_y / 32767.0f) * 65.0f); + const SDL_FRect box = { 127, 1 + (65.0f - height), 37, height }; + SDL_RenderFillRect(renderer, &box); + } + + /* right trigger. */ + axis_y = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); + if (axis_y > 1000) { /* zero means unpressed, but it might be a little off zero... */ + const float height = ((axis_y / 32767.0f) * 65.0f); + const SDL_FRect box = { 481, 1 + (65.0f - height), 37, height }; + SDL_RenderFillRect(renderer, &box); + } + } + + x = (((float) WINDOW_WIDTH) - (SDL_strlen(text) * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE)) / 2.0f; + if (gamepad) { + y = (float) (WINDOW_HEIGHT - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE + 2)); + } else { + y = (((float) WINDOW_HEIGHT) - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) / 2.0f; + } + SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0xFF, 0xFF); /* blue */ + SDL_RenderDebugText(renderer, x, y, text); + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + SDL_DestroyTexture(texture); + SDL_CloseGamepad(gamepad); + /* SDL will clean up the window/renderer for us. */ +} diff --git a/libs/SDL3/examples/input/03-gamepad-polling/onmouseover.webp b/libs/SDL3/examples/input/03-gamepad-polling/onmouseover.webp new file mode 100644 index 0000000..91c7bb0 Binary files /dev/null and b/libs/SDL3/examples/input/03-gamepad-polling/onmouseover.webp differ diff --git a/libs/SDL3/examples/input/03-gamepad-polling/thumbnail.png b/libs/SDL3/examples/input/03-gamepad-polling/thumbnail.png new file mode 100644 index 0000000..c5bc6e6 Binary files /dev/null and b/libs/SDL3/examples/input/03-gamepad-polling/thumbnail.png differ diff --git a/libs/SDL3/examples/input/04-gamepad-events/README.txt b/libs/SDL3/examples/input/04-gamepad-events/README.txt new file mode 100644 index 0000000..42f62d7 --- /dev/null +++ b/libs/SDL3/examples/input/04-gamepad-events/README.txt @@ -0,0 +1,2 @@ +This example code looks for gamepad input in the event handler, and +reports any changes as a flood of info. diff --git a/libs/SDL3/examples/input/04-gamepad-events/gamepad-events.c b/libs/SDL3/examples/input/04-gamepad-events/gamepad-events.c new file mode 100644 index 0000000..bcdd576 --- /dev/null +++ b/libs/SDL3/examples/input/04-gamepad-events/gamepad-events.c @@ -0,0 +1,212 @@ +/* + * This example code looks for gamepad input in the event handler, and + * reports any changes as a flood of info. + * + * This code is public domain. Feel free to use it for any purpose! + */ + +/* Joysticks are low-level interfaces: there's something with a bunch of + buttons, axes and hats, in no understood order or position. This is + a flexible interface, but you'll need to build some sort of configuration + UI to let people tell you what button, etc, does what. On top of this + interface, SDL offers the "gamepad" API, which works with lots of devices, + and knows how to map arbitrary buttons and such to look like an + Xbox/PlayStation/etc gamepad. This is easier, and better, for many games, + but isn't necessarily a good fit for complex apps and hardware. A flight + simulator, a realistic racing game, etc, might want the joystick interface + instead of gamepads. */ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static SDL_Color colors[64]; + +#define MOTION_EVENT_COOLDOWN 40 + +typedef struct EventMessage +{ + char *str; + SDL_Color color; + Uint64 start_ticks; + struct EventMessage *next; +} EventMessage; + +static EventMessage messages; +static EventMessage *messages_tail = &messages; + +static const char *battery_state_string(SDL_PowerState state) +{ + switch (state) { + case SDL_POWERSTATE_ERROR: return "ERROR"; + case SDL_POWERSTATE_UNKNOWN: return "UNKNOWN"; + case SDL_POWERSTATE_ON_BATTERY: return "ON BATTERY"; + case SDL_POWERSTATE_NO_BATTERY: return "NO BATTERY"; + case SDL_POWERSTATE_CHARGING: return "CHARGING"; + case SDL_POWERSTATE_CHARGED: return "CHARGED"; + default: break; + } + return "UNKNOWN"; +} + +static void add_message(SDL_JoystickID jid, const char *fmt, ...) +{ + const SDL_Color *color = &colors[((size_t) jid) % SDL_arraysize(colors)]; + EventMessage *msg = NULL; + char *str = NULL; + va_list ap; + + msg = (EventMessage *) SDL_calloc(1, sizeof (*msg)); + if (!msg) { + return; // oh well. + } + + va_start(ap, fmt); + SDL_vasprintf(&str, fmt, ap); + va_end(ap); + if (!str) { + SDL_free(msg); + return; // oh well. + } + + msg->str = str; + SDL_copyp(&msg->color, color); + msg->start_ticks = SDL_GetTicks(); + msg->next = NULL; + + messages_tail->next = msg; + messages_tail = msg; +} + + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + int i; + + SDL_SetAppMetadata("Example Input Gamepad Events", "1.0", "com.example.input-gamepad-events"); + + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_CreateWindowAndRenderer("examples/input/gamepad-events", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + colors[0].r = colors[0].g = colors[0].b = colors[0].a = 255; + for (i = 1; i < SDL_arraysize(colors); i++) { + colors[i].r = SDL_rand(255); + colors[i].g = SDL_rand(255); + colors[i].b = SDL_rand(255); + colors[i].a = 255; + } + + add_message(0, "Please plug in a gamepad."); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } else if (event->type == SDL_EVENT_GAMEPAD_ADDED) { + /* this event is sent for each hotplugged stick, but also each already-connected gamepad during SDL_Init(). */ + const SDL_JoystickID which = event->gdevice.which; + SDL_Gamepad *gamepad = SDL_OpenGamepad(which); + if (!gamepad) { + add_message(which, "Gamepad #%u add, but not opened: %s", (unsigned int) which, SDL_GetError()); + } else { + char *mapping = SDL_GetGamepadMapping(gamepad); + add_message(which, "Gamepad #%u ('%s') added", (unsigned int) which, SDL_GetGamepadName(gamepad)); + if (mapping) { + add_message(which, "Gamepad #%u mapping: %s", (unsigned int) which, mapping); + SDL_free(mapping); + } + } + } else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) { + const SDL_JoystickID which = event->gdevice.which; + SDL_Gamepad *gamepad = SDL_GetGamepadFromID(which); + if (gamepad) { + SDL_CloseGamepad(gamepad); /* the gamepad was unplugged. */ + } + add_message(which, "Gamepad #%u removed", (unsigned int) which); + } else if (event->type == SDL_EVENT_GAMEPAD_AXIS_MOTION) { + static Uint64 axis_motion_cooldown_time = 0; /* these are spammy, only show every X milliseconds. */ + const Uint64 now = SDL_GetTicks(); + if (now >= axis_motion_cooldown_time) { + const SDL_JoystickID which = event->gaxis.which; + axis_motion_cooldown_time = now + MOTION_EVENT_COOLDOWN; + add_message(which, "Gamepad #%u axis %s -> %d", (unsigned int) which, SDL_GetGamepadStringForAxis((SDL_GamepadAxis) event->gaxis.axis), (int) event->gaxis.value); + } + } else if ((event->type == SDL_EVENT_GAMEPAD_BUTTON_UP) || (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN)) { + const SDL_JoystickID which = event->gbutton.which; + add_message(which, "Gamepad #%u button %s -> %s", (unsigned int) which, SDL_GetGamepadStringForButton((SDL_GamepadButton) event->gbutton.button), event->gbutton.down ? "PRESSED" : "RELEASED"); + } else if (event->type == SDL_EVENT_JOYSTICK_BATTERY_UPDATED) { + const SDL_JoystickID which = event->jbattery.which; + if (SDL_IsGamepad(which)) { /* this is only reported for joysticks, so make sure this joystick is _actually_ a gamepad. */ + add_message(which, "Gamepad #%u battery -> %s - %d%%", (unsigned int) which, battery_state_string(event->jbattery.state), event->jbattery.percent); + } + } + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + const Uint64 now = SDL_GetTicks(); + const float msg_lifetime = 3500.0f; /* milliseconds a message lives for. */ + EventMessage *msg = messages.next; + float prev_y = 0.0f; + int winw = 640, winh = 480; + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + SDL_GetWindowSize(window, &winw, &winh); + + while (msg) { + float x, y; + const float life_percent = ((float) (now - msg->start_ticks)) / msg_lifetime; + if (life_percent >= 1.0f) { /* msg is done. */ + messages.next = msg->next; + if (messages_tail == msg) { + messages_tail = &messages; + } + SDL_free(msg->str); + SDL_free(msg); + msg = messages.next; + continue; + } + x = (((float) winw) - (SDL_strlen(msg->str) * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE)) / 2.0f; + y = ((float) winh) * life_percent; + if ((prev_y != 0.0f) && ((prev_y - y) < ((float) SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE))) { + msg->start_ticks = now; + break; // wait for the previous message to tick up a little. + } + + SDL_SetRenderDrawColor(renderer, msg->color.r, msg->color.g, msg->color.b, (Uint8) (((float) msg->color.a) * (1.0f - life_percent))); + SDL_RenderDebugText(renderer, x, y, msg->str); + + prev_y = y; + msg = msg->next; + } + + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + SDL_Quit(); + /* SDL will clean up the window/renderer for us. We let the gamepads leak. */ +} diff --git a/libs/SDL3/examples/input/04-gamepad-events/onmouseover.webp b/libs/SDL3/examples/input/04-gamepad-events/onmouseover.webp new file mode 100644 index 0000000..b427739 Binary files /dev/null and b/libs/SDL3/examples/input/04-gamepad-events/onmouseover.webp differ diff --git a/libs/SDL3/examples/input/04-gamepad-events/thumbnail.png b/libs/SDL3/examples/input/04-gamepad-events/thumbnail.png new file mode 100644 index 0000000..1c817a5 Binary files /dev/null and b/libs/SDL3/examples/input/04-gamepad-events/thumbnail.png differ diff --git a/libs/SDL3/examples/misc/01-power/README.txt b/libs/SDL3/examples/misc/01-power/README.txt new file mode 100644 index 0000000..800153e --- /dev/null +++ b/libs/SDL3/examples/misc/01-power/README.txt @@ -0,0 +1,4 @@ +This example code reports power status (plugged in, battery level, etc). + +Note that only Chrome-based browsers support this API currently. Firefox and +Safari will report this as unknown, but this may change later! diff --git a/libs/SDL3/examples/misc/01-power/onmouseover.webp b/libs/SDL3/examples/misc/01-power/onmouseover.webp new file mode 100644 index 0000000..a99b8d6 Binary files /dev/null and b/libs/SDL3/examples/misc/01-power/onmouseover.webp differ diff --git a/libs/SDL3/examples/misc/01-power/power.c b/libs/SDL3/examples/misc/01-power/power.c new file mode 100644 index 0000000..44a8964 --- /dev/null +++ b/libs/SDL3/examples/misc/01-power/power.c @@ -0,0 +1,155 @@ +/* + * This example code reports power status (plugged in, battery level, etc). + * + * This code is public domain. Feel free to use it for any purpose! + */ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + SDL_SetAppMetadata("Example Misc Power", "1.0", "com.example.misc-power"); + + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_CreateWindowAndRenderer("examples/misc/power", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + const SDL_FRect frame = { 100, 200, 440, 80 }; /* the percentage bar dimensions. */ + + /* Query for battery info */ + int seconds = 0, percent = 0; + const SDL_PowerState state = SDL_GetPowerInfo(&seconds, &percent); + + /* We set up different drawing details for each power state, then + run it all through the same drawing code. */ + int clearr = 0, clearg = 0, clearb = 0; /* clear window to this color. */ + int textr = 255, textg = 255, textb = 255; /* draw messages in this color. */ + int framer = 255, frameg = 255, frameb = 255; /* draw a percentage bar frame in this color. */ + int barr = 0, barg = 0, barb = 0; /* draw a percentage bar in this color. */ + const char *msg = NULL; + const char *msg2 = NULL; + + switch (state) { + case SDL_POWERSTATE_ERROR: + msg2 = "ERROR GETTING POWER STATE"; + msg = SDL_GetError(); + clearr = 255; /* red background */ + break; + + default: /* in case this does something unexpected later, treat it as unknown. */ + case SDL_POWERSTATE_UNKNOWN: + msg = "Power state is unknown."; + clearr = clearb = clearg = 50; /* grey background */ + break; + + case SDL_POWERSTATE_ON_BATTERY: + msg = "Running on battery."; + barr = 255; /* draw in red */ + break; + + case SDL_POWERSTATE_NO_BATTERY: + msg = "Plugged in, no battery available."; + clearg = 50; /* green background */ + break; + + case SDL_POWERSTATE_CHARGING: + msg = "Charging."; + barb = barg = 255; /* draw in cyan */ + break; + + case SDL_POWERSTATE_CHARGED: + msg = "Charged."; + barg = 255; /* draw in green */ + break; + } + + SDL_SetRenderDrawColor(renderer, clearr, clearg, clearb, 255); + SDL_RenderClear(renderer); + + if (percent >= 0) { + float x, y; + SDL_FRect pctrect; + char remainstr[64]; + char msgbuf[128]; + + SDL_copyp(&pctrect, &frame); + pctrect.w *= percent / 100.0f; + + if (seconds < 0) { + SDL_strlcpy(remainstr, "unknown time", sizeof (remainstr)); + } else { + int hours, minutes; + hours = seconds / (60 * 60); + seconds -= hours * (60 * 60); + minutes = seconds / 60; + seconds -= minutes * 60; + SDL_snprintf(remainstr, sizeof (remainstr), "%02d:%02d:%02d", hours, minutes, seconds); + } + + SDL_snprintf(msgbuf, sizeof (msgbuf), "Battery: %3d percent, %s remaining", percent, remainstr); + x = frame.x + ((frame.w - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(msgbuf))) / 2.0f); + y = frame.y + frame.h + SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE; + + SDL_SetRenderDrawColor(renderer, barr, barg, barb, 255); /* draw percent bar. */ + SDL_RenderFillRect(renderer, &pctrect); + SDL_SetRenderDrawColor(renderer, framer, frameg, frameb, 255); /* draw frame on top of bar. */ + SDL_RenderRect(renderer, &frame); + SDL_SetRenderDrawColor(renderer, textr, textg, textb, 255); + SDL_RenderDebugText(renderer, x, y, msgbuf); /* draw text about battery level */ + } + + if (msg) { + const float x = frame.x + ((frame.w - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(msg))) / 2.0f); + const float y = frame.y - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * 2); + SDL_SetRenderDrawColor(renderer, textr, textg, textb, 255); + SDL_RenderDebugText(renderer, x, y, msg); + } + + if (msg2) { + const float x = frame.x + ((frame.w - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(msg2))) / 2.0f); + const float y = frame.y - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * 4); + SDL_SetRenderDrawColor(renderer, textr, textg, textb, 255); + SDL_RenderDebugText(renderer, x, y, msg2); + } + + /* put the new rendering on the screen. */ + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + /* SDL will clean up the window/renderer for us. */ +} + diff --git a/libs/SDL3/examples/misc/01-power/thumbnail.png b/libs/SDL3/examples/misc/01-power/thumbnail.png new file mode 100644 index 0000000..970bdff Binary files /dev/null and b/libs/SDL3/examples/misc/01-power/thumbnail.png differ diff --git a/libs/SDL3/examples/misc/02-clipboard/README.txt b/libs/SDL3/examples/misc/02-clipboard/README.txt new file mode 100644 index 0000000..b272199 --- /dev/null +++ b/libs/SDL3/examples/misc/02-clipboard/README.txt @@ -0,0 +1,6 @@ +This example code lets the user copy and paste with the system clipboard. + +This only handles text, but SDL supports other data types, too. + +Note that only Chrome-based browsers support this API currently. This uses a +new Javascript API, so hopefully this will be available everywhere soon! diff --git a/libs/SDL3/examples/misc/02-clipboard/clipboard.c b/libs/SDL3/examples/misc/02-clipboard/clipboard.c new file mode 100644 index 0000000..a40a24f --- /dev/null +++ b/libs/SDL3/examples/misc/02-clipboard/clipboard.c @@ -0,0 +1,235 @@ +/* + * This example code lets the user copy and paste with the system clipboard. + * + * This only handles text, but SDL supports other data types, too. + * + * This code is public domain. Feel free to use it for any purpose! + */ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static const char *copybuttonstr = "Click here to copy!"; +static const char *pastebuttonstr = "Click here to paste!"; +static SDL_FRect currenttimerect; +static SDL_FRect copybuttonrect; +static SDL_FRect pastetextrect; +static SDL_FRect pastebuttonrect; +static bool copy_pressed = false; +static bool paste_pressed = false; +static char current_time[64]; +static char *pasted_str = NULL; + +static void CalculateCurrentTimeString(void) +{ + SDL_Time ticks = 0; + SDL_DateTime dt; + if (!SDL_GetCurrentTime(&ticks) || !SDL_TimeToDateTime(ticks, &dt, true)) { + SDL_snprintf(current_time, sizeof (current_time), "(Don't know the current time, sorry.)"); + } else { + static const char *month[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; + static const char *day[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; + SDL_snprintf(current_time, sizeof (current_time), "%s, %s %d, %d %02d:%02d:%02d", day[dt.day_of_week], month[dt.month-1], dt.day, dt.year, dt.hour, dt.minute, dt.second); + } +} + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + SDL_SetAppMetadata("Example Misc Clipboard", "1.0", "com.example.misc-clipboard"); + + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_CreateWindowAndRenderer("examples/misc/clipboard", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); + + CalculateCurrentTimeString(); + + /* set up the locations where we'll draw stuff. */ + currenttimerect.x = 30; + currenttimerect.y = 10; + currenttimerect.w = 390; + currenttimerect.h = SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE + 10; + + copybuttonrect.x = currenttimerect.x + currenttimerect.w + 30; + copybuttonrect.y = currenttimerect.y; + copybuttonrect.w = (float) ((SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(copybuttonstr)) + 10); + copybuttonrect.h = currenttimerect.h; + + pastetextrect.x = 10; + pastetextrect.y = currenttimerect.y + currenttimerect.h + 10; + pastetextrect.w = 620; + pastetextrect.h = ((480 - pastetextrect.y) - copybuttonrect.h) - 20; + + pastebuttonrect.w = (float) ((SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(pastebuttonstr)) + 10); + pastebuttonrect.x = (640 - pastebuttonrect.w) / 2.0f; + pastebuttonrect.y = pastetextrect.y + pastetextrect.h + 10; + pastebuttonrect.h = copybuttonrect.h; + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + SDL_ConvertEventToRenderCoordinates(renderer, event); + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) { + if (event->button.button == SDL_BUTTON_LEFT) { + const SDL_FPoint p = { event->button.x, event->button.y }; + copy_pressed = SDL_PointInRectFloat(&p, ©buttonrect); + paste_pressed = SDL_PointInRectFloat(&p, &pastebuttonrect); + } + } else if (event->type == SDL_EVENT_MOUSE_BUTTON_UP) { + if (event->button.button == SDL_BUTTON_LEFT) { + const SDL_FPoint p = { event->button.x, event->button.y }; + if (copy_pressed && SDL_PointInRectFloat(&p, ©buttonrect)) { + SDL_SetClipboardText(current_time); + } else if (paste_pressed && SDL_PointInRectFloat(&p, &pastebuttonrect)) { + SDL_free(pasted_str); + pasted_str = SDL_GetClipboardText(); + } + copy_pressed = paste_pressed = false; + } + } + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +static void RenderPastedText(void) +{ + char *str = pasted_str; + if (str) { + float x = pastetextrect.x + 5; + float y = pastetextrect.y + 5; + const float w = pastetextrect.w - 10; + const float h = pastetextrect.h; + const size_t max_chars_per_line = (size_t) (w / SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE); + char *newline; + size_t slen; + char ch; + + /* this doesn't wordwrap, or deal with Unicode....this is just a simple example app! */ + while ((newline = SDL_strchr(str, '\n')) != NULL) { + const bool ignore_cr = ((newline > str) && (newline[-1] == '\r')); + + if (ignore_cr) { + newline[-1] = '\0'; + } + *newline = '\0'; + + slen = SDL_strlen(str); /* length to end of line. */ + slen = SDL_min(slen, max_chars_per_line); + ch = str[slen]; + str[slen] = '\0'; + SDL_RenderDebugText(renderer, x, y, str); + str[slen] = ch; + + if (ignore_cr) { + newline[-1] = '\r'; + } + *newline = '\n'; + + str = newline + 1; + y += (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE + 2); + if ((h - y) < SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) { + break; // no space for another line, stop here. + } + } + + /* last text after newline, if there's room. */ + if ((h - y) >= SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) { + slen = SDL_strlen(str); /* length to end of line. */ + slen = SDL_min(slen, max_chars_per_line); + ch = str[slen]; + str[slen] = '\0'; + SDL_RenderDebugText(renderer, x, y, str); + str[slen] = ch; + } + } +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + float x, y; + + CalculateCurrentTimeString(); + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); /* black */ + SDL_RenderClear(renderer); + + /* draw a frame around the current time. */ + SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); + SDL_RenderFillRect(renderer, ¤ttimerect); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderRect(renderer, ¤ttimerect); + + /* draw the current time inside the frame. */ + x = currenttimerect.x + ((currenttimerect.w - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(current_time))) / 2.0f); + y = currenttimerect.y + 5; + SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255); + SDL_RenderDebugText(renderer, x, y, current_time); + + /* draw a frame for the "copy the current time to the clipboard" button. */ + if (copy_pressed) { + SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); + } else { + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); + } + SDL_RenderFillRect(renderer, ©buttonrect); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderRect(renderer, ©buttonrect); + + /* draw the "copy this text" button string. */ + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderDebugText(renderer, copybuttonrect.x + 5, copybuttonrect.y + 5, copybuttonstr); + + /* draw a frame for the pasted text area. */ + SDL_SetRenderDrawColor(renderer, 0, 53, 25, 255); + SDL_RenderFillRect(renderer, &pastetextrect); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderRect(renderer, &pastetextrect); + + /* draw pasted text. */ + SDL_SetRenderDrawColor(renderer, 0, 219, 107, 255); + RenderPastedText(); + + /* draw a frame for the "paste from the clipboard" button. */ + if (paste_pressed) { + SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); + } else { + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); + } + SDL_RenderFillRect(renderer, &pastebuttonrect); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderRect(renderer, &pastebuttonrect); + + /* draw the "paste some text" button string. */ + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderDebugText(renderer, pastebuttonrect.x + 5, pastebuttonrect.y + 5, pastebuttonstr); + + /* put the new rendering on the screen. */ + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + SDL_free(pasted_str); + /* SDL will clean up the window/renderer for us. */ +} + diff --git a/libs/SDL3/examples/misc/02-clipboard/onmouseover.webp b/libs/SDL3/examples/misc/02-clipboard/onmouseover.webp new file mode 100644 index 0000000..73476b4 Binary files /dev/null and b/libs/SDL3/examples/misc/02-clipboard/onmouseover.webp differ diff --git a/libs/SDL3/examples/misc/02-clipboard/thumbnail.png b/libs/SDL3/examples/misc/02-clipboard/thumbnail.png new file mode 100644 index 0000000..76bc9d5 Binary files /dev/null and b/libs/SDL3/examples/misc/02-clipboard/thumbnail.png differ diff --git a/libs/SDL3/examples/misc/description.txt b/libs/SDL3/examples/misc/description.txt new file mode 100644 index 0000000..bd99b89 --- /dev/null +++ b/libs/SDL3/examples/misc/description.txt @@ -0,0 +1 @@ +Various examples from smaller subsystems \ No newline at end of file diff --git a/libs/SDL3/examples/renderer/01-clear/clear.c b/libs/SDL3/examples/renderer/01-clear/clear.c index 5b2b7dc..8a87139 100644 --- a/libs/SDL3/examples/renderer/01-clear/clear.c +++ b/libs/SDL3/examples/renderer/01-clear/clear.c @@ -24,10 +24,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/clear", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/clear", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); return SDL_APP_CONTINUE; /* carry on with the program! */ } diff --git a/libs/SDL3/examples/renderer/02-primitives/primitives.c b/libs/SDL3/examples/renderer/02-primitives/primitives.c index 5cfd731..5afcb60 100644 --- a/libs/SDL3/examples/renderer/02-primitives/primitives.c +++ b/libs/SDL3/examples/renderer/02-primitives/primitives.c @@ -26,10 +26,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/primitives", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/primitives", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* set up some random points */ for (i = 0; i < SDL_arraysize(points); i++) { diff --git a/libs/SDL3/examples/renderer/03-lines/lines.c b/libs/SDL3/examples/renderer/03-lines/lines.c index 54f7eb1..25af0ed 100644 --- a/libs/SDL3/examples/renderer/03-lines/lines.c +++ b/libs/SDL3/examples/renderer/03-lines/lines.c @@ -23,10 +23,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/lines", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/lines", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); return SDL_APP_CONTINUE; /* carry on with the program! */ } @@ -76,8 +77,9 @@ SDL_AppResult SDL_AppIterate(void *appstate) const float size = 30.0f; const float x = 320.0f; const float y = 95.0f - (size / 2.0f); + const float r = (float) i * (SDL_PI_F / 180.0f); SDL_SetRenderDrawColor(renderer, SDL_rand(256), SDL_rand(256), SDL_rand(256), SDL_ALPHA_OPAQUE); - SDL_RenderLine(renderer, x, y, x + SDL_sinf((float) i) * size, y + SDL_cosf((float) i) * size); + SDL_RenderLine(renderer, x, y, x + SDL_cosf(r) * size, y + SDL_sinf(r) * size); } SDL_RenderPresent(renderer); /* put it all on the screen! */ diff --git a/libs/SDL3/examples/renderer/04-points/points.c b/libs/SDL3/examples/renderer/04-points/points.c index b7b5c21..d07b85b 100644 --- a/libs/SDL3/examples/renderer/04-points/points.c +++ b/libs/SDL3/examples/renderer/04-points/points.c @@ -43,10 +43,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/points", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/points", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* set up the data for a bunch of points. */ for (i = 0; i < SDL_arraysize(points); i++) { diff --git a/libs/SDL3/examples/renderer/05-rectangles/rectangles.c b/libs/SDL3/examples/renderer/05-rectangles/rectangles.c index 3aa7242..e326a40 100644 --- a/libs/SDL3/examples/renderer/05-rectangles/rectangles.c +++ b/libs/SDL3/examples/renderer/05-rectangles/rectangles.c @@ -26,10 +26,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/rectangles", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/rectangles", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); return SDL_APP_CONTINUE; /* carry on with the program! */ } diff --git a/libs/SDL3/examples/renderer/06-textures/README.txt b/libs/SDL3/examples/renderer/06-textures/README.txt index 21c3f0b..8dc42b7 100644 --- a/libs/SDL3/examples/renderer/06-textures/README.txt +++ b/libs/SDL3/examples/renderer/06-textures/README.txt @@ -1,3 +1,3 @@ This example creates an SDL window and renderer, loads a texture from a -.bmp file, and then draws it a few times each frame. +.png file, and then draws it a few times each frame. diff --git a/libs/SDL3/examples/renderer/06-textures/textures.c b/libs/SDL3/examples/renderer/06-textures/textures.c index f4ad707..0c742bc 100644 --- a/libs/SDL3/examples/renderer/06-textures/textures.c +++ b/libs/SDL3/examples/renderer/06-textures/textures.c @@ -23,7 +23,7 @@ static int texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Textures", "1.0", "com.example.renderer-textures"); @@ -32,25 +32,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/textures", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/textures", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many - times) with data from a bitmap file. */ + times) with data from a png file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { - SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); + SDL_Log("Couldn't load png: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/renderer/07-streaming-textures/streaming-textures.c b/libs/SDL3/examples/renderer/07-streaming-textures/streaming-textures.c index bf309bc..27161f1 100644 --- a/libs/SDL3/examples/renderer/07-streaming-textures/streaming-textures.c +++ b/libs/SDL3/examples/renderer/07-streaming-textures/streaming-textures.c @@ -29,10 +29,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/streaming-textures", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/streaming-textures", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, TEXTURE_SIZE, TEXTURE_SIZE); if (!texture) { @@ -65,7 +66,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) /* To update a streaming texture, you need to lock it first. This gets you access to the pixels. Note that this is considered a _write-only_ operation: the buffer you get from locking - might not acutally have the existing contents of the texture, and you have to write to every + might not actually have the existing contents of the texture, and you have to write to every locked pixel! */ /* You can use SDL_LockTexture() to get an array of raw pixels, but we're going to use diff --git a/libs/SDL3/examples/renderer/08-rotating-textures/README.txt b/libs/SDL3/examples/renderer/08-rotating-textures/README.txt index 4ae46bc..3768cd8 100644 --- a/libs/SDL3/examples/renderer/08-rotating-textures/README.txt +++ b/libs/SDL3/examples/renderer/08-rotating-textures/README.txt @@ -1,3 +1,3 @@ -This example creates an SDL window and renderer, loads a texture from a .bmp +This example creates an SDL window and renderer, loads a texture from a .png file, and then draws it, rotating around the center of the screen. diff --git a/libs/SDL3/examples/renderer/08-rotating-textures/rotating-textures.c b/libs/SDL3/examples/renderer/08-rotating-textures/rotating-textures.c index bf318ef..22bed07 100644 --- a/libs/SDL3/examples/renderer/08-rotating-textures/rotating-textures.c +++ b/libs/SDL3/examples/renderer/08-rotating-textures/rotating-textures.c @@ -23,7 +23,7 @@ static int texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Rotating Textures", "1.0", "com.example.renderer-rotating-textures"); @@ -32,25 +32,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/rotating-textures", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/rotating-textures", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/renderer/09-scaling-textures/README.txt b/libs/SDL3/examples/renderer/09-scaling-textures/README.txt index e13a6ec..51eed0f 100644 --- a/libs/SDL3/examples/renderer/09-scaling-textures/README.txt +++ b/libs/SDL3/examples/renderer/09-scaling-textures/README.txt @@ -1,3 +1,3 @@ -This example creates an SDL window and renderer, loads a texture from a .bmp +This example creates an SDL window and renderer, loads a texture from a .png file, and then draws it, scaling it up and down. 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 66060ed..199433f 100644 --- a/libs/SDL3/examples/renderer/09-scaling-textures/scaling-textures.c +++ b/libs/SDL3/examples/renderer/09-scaling-textures/scaling-textures.c @@ -23,7 +23,7 @@ static int texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Scaling Textures", "1.0", "com.example.renderer-scaling-textures"); @@ -32,25 +32,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/scaling-textures", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/scaling-textures", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/renderer/10-geometry/README.txt b/libs/SDL3/examples/renderer/10-geometry/README.txt index d76d0cc..e03f015 100644 --- a/libs/SDL3/examples/renderer/10-geometry/README.txt +++ b/libs/SDL3/examples/renderer/10-geometry/README.txt @@ -1,3 +1,3 @@ -This example creates an SDL window and renderer, loads a texture from a .bmp +This example creates an SDL window and renderer, loads a texture from a .png file, and then draws geometry (arbitrary polygons) using it. diff --git a/libs/SDL3/examples/renderer/10-geometry/geometry.c b/libs/SDL3/examples/renderer/10-geometry/geometry.c index 77ff863..028134d 100644 --- a/libs/SDL3/examples/renderer/10-geometry/geometry.c +++ b/libs/SDL3/examples/renderer/10-geometry/geometry.c @@ -23,7 +23,7 @@ static int texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Geometry", "1.0", "com.example.renderer-geometry"); @@ -32,25 +32,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/geometry", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/geometry", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/renderer/11-color-mods/README.txt b/libs/SDL3/examples/renderer/11-color-mods/README.txt index 66f233b..5f7e110 100644 --- a/libs/SDL3/examples/renderer/11-color-mods/README.txt +++ b/libs/SDL3/examples/renderer/11-color-mods/README.txt @@ -1,3 +1,3 @@ This example creates an SDL window and renderer, loads a texture from a -.bmp file, and then draws it a few times each frame, adjusting the colors. +.png file, and then draws it a few times each frame, adjusting the colors. diff --git a/libs/SDL3/examples/renderer/11-color-mods/color-mods.c b/libs/SDL3/examples/renderer/11-color-mods/color-mods.c index 8877232..8c3ed49 100644 --- a/libs/SDL3/examples/renderer/11-color-mods/color-mods.c +++ b/libs/SDL3/examples/renderer/11-color-mods/color-mods.c @@ -23,7 +23,7 @@ static int texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Color Mods", "1.0", "com.example.renderer-color-mods"); @@ -32,25 +32,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/color-mods", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/color-mods", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/renderer/14-viewport/README.txt b/libs/SDL3/examples/renderer/14-viewport/README.txt index 9da7c7a..ddcf270 100644 --- a/libs/SDL3/examples/renderer/14-viewport/README.txt +++ b/libs/SDL3/examples/renderer/14-viewport/README.txt @@ -1,4 +1,4 @@ This example creates an SDL window and renderer, loads a texture -from a .bmp file, and then draws it a few times each frame, adjusting +from a .png file, and then draws it a few times each frame, adjusting the viewport before each draw. diff --git a/libs/SDL3/examples/renderer/14-viewport/viewport.c b/libs/SDL3/examples/renderer/14-viewport/viewport.c index 0a6c015..2889fb2 100644 --- a/libs/SDL3/examples/renderer/14-viewport/viewport.c +++ b/libs/SDL3/examples/renderer/14-viewport/viewport.c @@ -23,7 +23,7 @@ static int texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Viewport", "1.0", "com.example.renderer-viewport"); @@ -32,25 +32,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/viewport", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/viewport", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/renderer/15-cliprect/README.txt b/libs/SDL3/examples/renderer/15-cliprect/README.txt index 48c9f1f..d9a6ce1 100644 --- a/libs/SDL3/examples/renderer/15-cliprect/README.txt +++ b/libs/SDL3/examples/renderer/15-cliprect/README.txt @@ -1,5 +1,5 @@ This example creates an SDL window and renderer, loads a texture -from a .bmp file, and stretches it across the window. Each frame, we move +from a .png file, and stretches it across the window. Each frame, we move the clipping rectangle around, so only a small square of the texture is actually drawn. diff --git a/libs/SDL3/examples/renderer/15-cliprect/cliprect.c b/libs/SDL3/examples/renderer/15-cliprect/cliprect.c index 058072c..1e7fb45 100644 --- a/libs/SDL3/examples/renderer/15-cliprect/cliprect.c +++ b/libs/SDL3/examples/renderer/15-cliprect/cliprect.c @@ -30,7 +30,7 @@ static Uint64 last_time = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Clipping Rectangle", "1.0", "com.example.renderer-cliprect"); @@ -39,10 +39,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/cliprect", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/cliprect", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); cliprect_direction.x = cliprect_direction.y = 1.0f; @@ -53,15 +54,15 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture = SDL_CreateTextureFromSurface(renderer, surface); if (!texture) { @@ -93,20 +94,20 @@ SDL_AppResult SDL_AppIterate(void *appstate) /* Set a new clipping rectangle position */ cliprect_position.x += distance * cliprect_direction.x; - if (cliprect_position.x < 0.0f) { - cliprect_position.x = 0.0f; + if (cliprect_position.x < -CLIPRECT_SIZE) { + cliprect_position.x = -CLIPRECT_SIZE; cliprect_direction.x = 1.0f; - } else if (cliprect_position.x >= (WINDOW_WIDTH - CLIPRECT_SIZE)) { - cliprect_position.x = (WINDOW_WIDTH - CLIPRECT_SIZE) - 1; + } else if (cliprect_position.x >= WINDOW_WIDTH) { + cliprect_position.x = WINDOW_WIDTH - 1; cliprect_direction.x = -1.0f; } cliprect_position.y += distance * cliprect_direction.y; - if (cliprect_position.y < 0.0f) { - cliprect_position.y = 0.0f; + if (cliprect_position.y < -CLIPRECT_SIZE) { + cliprect_position.y = -CLIPRECT_SIZE; cliprect_direction.y = 1.0f; - } else if (cliprect_position.y >= (WINDOW_HEIGHT - CLIPRECT_SIZE)) { - cliprect_position.y = (WINDOW_HEIGHT - CLIPRECT_SIZE) - 1; + } else if (cliprect_position.y >= WINDOW_HEIGHT) { + cliprect_position.y = WINDOW_HEIGHT - 1; cliprect_direction.y = -1.0f; } SDL_SetRenderClipRect(renderer, &cliprect); diff --git a/libs/SDL3/examples/renderer/17-read-pixels/read-pixels.c b/libs/SDL3/examples/renderer/17-read-pixels/read-pixels.c index 556b490..3775069 100644 --- a/libs/SDL3/examples/renderer/17-read-pixels/read-pixels.c +++ b/libs/SDL3/examples/renderer/17-read-pixels/read-pixels.c @@ -32,7 +32,7 @@ static int converted_texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Read Pixels", "1.0", "com.example.renderer-read-pixels"); @@ -41,25 +41,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/read-pixels", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/read-pixels", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/renderer/18-debug-text/debug-text.c b/libs/SDL3/examples/renderer/18-debug-text/debug-text.c index 62005e6..f5150d2 100644 --- a/libs/SDL3/examples/renderer/18-debug-text/debug-text.c +++ b/libs/SDL3/examples/renderer/18-debug-text/debug-text.c @@ -26,10 +26,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/debug-text", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/debug-text", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); return SDL_APP_CONTINUE; /* carry on with the program! */ } diff --git a/libs/SDL3/examples/renderer/19-affine-textures/README.txt b/libs/SDL3/examples/renderer/19-affine-textures/README.txt new file mode 100644 index 0000000..34e341b --- /dev/null +++ b/libs/SDL3/examples/renderer/19-affine-textures/README.txt @@ -0,0 +1,3 @@ +This example creates an SDL window and renderer, and uses +SDL_RenderTextureAffine to draw a 3D cube using only 2D rendering operations. + diff --git a/libs/SDL3/examples/renderer/19-affine-textures/affine-textures.c b/libs/SDL3/examples/renderer/19-affine-textures/affine-textures.c new file mode 100644 index 0000000..b7e0d65 --- /dev/null +++ b/libs/SDL3/examples/renderer/19-affine-textures/affine-textures.c @@ -0,0 +1,146 @@ +/* affine-textures.c ... */ + +/* +* This example creates an SDL window and renderer, and then draws a cube +* using affine-transformed textures every frame. +* +* This code is public domain. Feel free to use it for any purpose! +*/ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static SDL_Texture *texture = NULL; +static int texture_width = 0; +static int texture_height = 0; + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + SDL_Surface *surface = NULL; + char *png_path = NULL; + + SDL_SetAppMetadata("Example Renderer Affine Textures", "1.0", "com.example.renderer-affine-textures"); + + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_CreateWindowAndRenderer("examples/renderer/affine-textures", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); + + /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D + engines refer to these as "sprites." We'll do a static texture (upload once, draw many + times) with data from a bitmap file. */ + + /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); + if (!surface) { + SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + SDL_free(png_path); /* done with this, the file is loaded. */ + + texture_width = surface->w; + texture_height = surface->h; + + texture = SDL_CreateTextureFromSurface(renderer, surface); + if (!texture) { + SDL_Log("Couldn't create static texture: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + SDL_DestroySurface(surface); /* done with this, the texture has a copy of the pixels now. */ + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + const float x0 = 0.5f * WINDOW_WIDTH; + const float y0 = 0.5f * WINDOW_HEIGHT; + const float px = SDL_min(WINDOW_WIDTH, WINDOW_HEIGHT) / SDL_sqrtf(3.0f); + + const Uint64 now = SDL_GetTicks(); + const float rad = (((float) ((int) (now % 2000))) / 2000.0f) * SDL_PI_F * 2; + const float cos = SDL_cosf(rad); + const float sin = SDL_sinf(rad); + const float k[3] = { 3.0f / SDL_sqrtf(50.0f), 4.0f / SDL_sqrtf(50.0f), 5.0f / SDL_sqrtf(50.0f)}; + float mat[9] = { + cos + (1.0f-cos)*k[0]*k[0], -sin*k[2] + (1.0f-cos)*k[0]*k[1], sin*k[1] + (1.0f-cos)*k[0]*k[2], + sin*k[2] + (1.0f-cos)*k[0]*k[1], cos + (1.0f-cos)*k[1]*k[1], -sin*k[0] + (1.0f-cos)*k[1]*k[2], + -sin*k[1] + (1.0f-cos)*k[0]*k[2], sin*k[0] + (1.0f-cos)*k[1]*k[2], cos + (1.0f-cos)*k[2]*k[2], + }; + + float corners[16]; + int i; + + for (i = 0; i < 8; i++) { + const float x = (i & 1) ? -0.5f : 0.5f; + const float y = (i & 2) ? -0.5f : 0.5f; + const float z = (i & 4) ? -0.5f : 0.5f; + corners[0 + 2*i] = mat[0]*x + mat[1]*y + mat[2]*z; + corners[1 + 2*i] = mat[3]*x + mat[4]*y + mat[5]*z; + } + + SDL_SetRenderDrawColor(renderer, 0x42, 0x87, 0xf5, SDL_ALPHA_OPAQUE); // light blue background. + SDL_RenderClear(renderer); + + for (i = 1; i < 7; i++) { + const int dir = 3 & ((i & 4) ? ~i : i); + const int odd = (i & 1) ^ ((i & 2) >> 1) ^ ((i & 4) >> 2); + if (0 < (odd ? 1.0f : -1.0f) * mat[5 + dir]) continue; + int origin_index = (1 << ((dir - 1) % 3)); + int right_index = (1 << ((dir + odd) % 3)) | origin_index; + int down_index = (1 << ((dir + (odd^1)) % 3)) | origin_index; + if (!odd) { + origin_index ^= 7; + right_index ^= 7; + down_index ^= 7; + } + SDL_FPoint origin, right, down; + origin.x = x0 + px*corners[0 + 2*origin_index]; + origin.y = y0 + px*corners[1 + 2*origin_index]; + right.x = x0 + px*corners[0 + 2*right_index]; + right.y = y0 + px*corners[1 + 2*right_index]; + down.x = x0 + px*corners[0 + 2*down_index]; + down.y = y0 + px*corners[1 + 2*down_index]; + SDL_RenderTextureAffine(renderer, texture, NULL, &origin, &right, &down); + } + + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + SDL_DestroyTexture(texture); + /* SDL will clean up the window/renderer for us. */ +} + diff --git a/libs/SDL3/examples/renderer/19-affine-textures/onmouseover.webp b/libs/SDL3/examples/renderer/19-affine-textures/onmouseover.webp new file mode 100644 index 0000000..31e450d Binary files /dev/null and b/libs/SDL3/examples/renderer/19-affine-textures/onmouseover.webp differ diff --git a/libs/SDL3/examples/renderer/19-affine-textures/thumbnail.png b/libs/SDL3/examples/renderer/19-affine-textures/thumbnail.png new file mode 100644 index 0000000..a81265b Binary files /dev/null and b/libs/SDL3/examples/renderer/19-affine-textures/thumbnail.png differ diff --git a/libs/SDL3/examples/save-rendering-to-bitmaps.h b/libs/SDL3/examples/save-rendering-to-bitmaps.h new file mode 100644 index 0000000..bf3eab8 --- /dev/null +++ b/libs/SDL3/examples/save-rendering-to-bitmaps.h @@ -0,0 +1,49 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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. +*/ + +/* +This is for generating thumbnails and videos of examples. Just include it +temporarily and let it override SDL_RenderPresent, etc, and it'll dump each +frame rendered to a new .png file. +*/ + +static bool SAVERENDERING_SDL_RenderPresent(SDL_Renderer *renderer) +{ + static unsigned int framenum = 0; + SDL_Surface *surface = SDL_RenderReadPixels(renderer, NULL); + if (!surface) { + SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Failed to read pixels for frame #%u! (%s)", framenum, SDL_GetError()); + } else { + char fname[64]; + SDL_snprintf(fname, sizeof (fname), "frame%05u.png", framenum); + if (!SDL_SavePNG(surface, fname)) { + SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Failed to save png for frame #%u! (%s)", framenum, SDL_GetError()); + } + SDL_DestroySurface(surface); + } + + framenum++; + + return SDL_RenderPresent(renderer); +} + +#define SDL_RenderPresent SAVERENDERING_SDL_RenderPresent + diff --git a/libs/SDL3/examples/template-category.html b/libs/SDL3/examples/template-category.html index 909b2c6..21f737d 100644 --- a/libs/SDL3/examples/template-category.html +++ b/libs/SDL3/examples/template-category.html @@ -6,6 +6,9 @@ @project_name@ Examples: @category_description@ + +@preload_images_html@ + -
@@ -31,7 +33,7 @@

@project_name@ examples: @category_description@

diff --git a/libs/SDL3/examples/template-homepage.html b/libs/SDL3/examples/template-homepage.html index 46951b2..f02afb6 100644 --- a/libs/SDL3/examples/template-homepage.html +++ b/libs/SDL3/examples/template-homepage.html @@ -6,6 +6,9 @@ @project_name@ Examples + +@preload_images_html@ +

@project_name@ examples

diff --git a/libs/SDL3/examples/template.c b/libs/SDL3/examples/template.c index 6719cc9..11932b1 100644 --- a/libs/SDL3/examples/template.c +++ b/libs/SDL3/examples/template.c @@ -23,10 +23,12 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/CATEGORY/NAME", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/CATEGORY/NAME", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); + return SDL_APP_CONTINUE; /* carry on with the program! */ } diff --git a/libs/SDL3/examples/template.html b/libs/SDL3/examples/template.html index 3e43946..8cfd569 100644 --- a/libs/SDL3/examples/template.html +++ b/libs/SDL3/examples/template.html @@ -9,7 +9,7 @@ - + @@ -92,7 +92,7 @@ #output-container:hover, #output-container:focus-within { - top: 50%; + top: 20%; } #output-container:focus-within { @@ -151,7 +151,7 @@ #source-code:hover, #source-code:focus-within { - top: 50%; + top: 20%; } #source-code:focus-within { @@ -202,9 +202,9 @@

@@ -241,7 +241,7 @@ return function(text) { var elem = document.getElementById('output-container'); if (elem.style['top'] == '') { - elem.style['top'] = '50%'; + elem.style['top'] = '20%'; setTimeout(function() { elem.style['top'] = ''; }, 3000); } diff --git a/libs/SDL3/include/SDL3/SDL.h b/libs/SDL3/include/SDL3/SDL.h index 587b541..fd13b83 100644 --- a/libs/SDL3/include/SDL3/SDL.h +++ b/libs/SDL3/include/SDL3/SDL.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -20,7 +20,7 @@ */ /** - * Main include header for the SDL library, version 3.2.20 + * Main include header for the SDL library, version 3.4.2 * * It is almost always best to include just this one header instead of * picking out individual headers included here. There are exceptions to @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include diff --git a/libs/SDL3/include/SDL3/SDL_assert.h b/libs/SDL3/include/SDL3/SDL_assert.h index 053af13..fb67462 100644 --- a/libs/SDL3/include/SDL3/SDL_assert.h +++ b/libs/SDL3/include/SDL3/SDL_assert.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -126,20 +126,17 @@ extern "C" { */ #define SDL_TriggerBreakpoint() TriggerABreakpointInAPlatformSpecificManner -#elif defined(_MSC_VER) && _MSC_VER >= 1310 +#elif defined(__MINGW32__) || (defined(_MSC_VER) && _MSC_VER >= 1310) /* Don't include intrin.h here because it contains C++ code */ extern void __cdecl __debugbreak(void); #define SDL_TriggerBreakpoint() __debugbreak() #elif defined(_MSC_VER) && defined(_M_IX86) #define SDL_TriggerBreakpoint() { _asm { int 0x03 } } -#elif defined(ANDROID) - #include - #define SDL_TriggerBreakpoint() assert(0) #elif SDL_HAS_BUILTIN(__builtin_debugtrap) #define SDL_TriggerBreakpoint() __builtin_debugtrap() #elif SDL_HAS_BUILTIN(__builtin_trap) #define SDL_TriggerBreakpoint() __builtin_trap() -#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) +#elif (defined(__GNUC__) || defined(__clang__) || defined(__TINYC__) || defined(__slimcc__)) && (defined(__i386__) || defined(__x86_64__)) #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "int $3\n\t" ) #elif (defined(__GNUC__) || defined(__clang__)) && defined(__riscv) #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "ebreak\n\t" ) @@ -163,7 +160,7 @@ extern "C" { #ifdef SDL_WIKI_DOCUMENTATION_SECTION /** - * A macro that reports the current function being compiled. + * A constant that contains the current function being compiled. * * If SDL can't figure how the compiler reports this, it will use "???". * @@ -171,20 +168,58 @@ extern "C" { */ #define SDL_FUNCTION __FUNCTION__ -#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 supports __func__ as a standard. */ +#elif !defined(SDL_FUNCTION) +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 supports __func__ as a standard. */ # define SDL_FUNCTION __func__ #elif ((defined(__GNUC__) && (__GNUC__ >= 2)) || defined(_MSC_VER) || defined (__WATCOMC__)) # define SDL_FUNCTION __FUNCTION__ #else # define SDL_FUNCTION "???" #endif +#endif + +#ifdef SDL_WIKI_DOCUMENTATION_SECTION /** * A macro that reports the current file being compiled. * + * This macro is only defined if it isn't already defined, so to override it + * (perhaps with something that doesn't provide path information at all, so + * build machine information doesn't leak into public binaries), apps can + * define this macro before including SDL.h or SDL_assert.h. + * * \since This macro is available since SDL 3.2.0. */ +#define SDL_FILE __FILE_NAME__ + +#elif !defined(SDL_FILE) +#ifdef __FILE_NAME__ +#define SDL_FILE __FILE_NAME__ +#else #define SDL_FILE __FILE__ +#endif +#endif + +#ifdef SDL_WIKI_DOCUMENTATION_SECTION + +/** + * A macro that reports the current file being compiled, for use in + * assertions. + * + * This macro is only defined if it isn't already defined, so to override it + * (perhaps with something that doesn't provide path information at all, so + * build machine information doesn't leak into public binaries), apps can + * define this macro before including SDL_assert.h. For example, defining this + * to `""` will make sure no source path information is included in asserts. + * + * \since This macro is available since SDL 3.4.0. + */ +#define SDL_ASSERT_FILE SDL_FILE + +#elif !defined(SDL_ASSERT_FILE) +#define SDL_ASSERT_FILE SDL_FILE +#endif + /** * A macro that reports the current line number of the file being compiled. @@ -363,7 +398,7 @@ extern SDL_DECLSPEC SDL_AssertState SDLCALL SDL_ReportAssertion(SDL_AssertData * do { \ while ( !(condition) ) { \ 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); \ + const SDL_AssertState sdl_assert_state = SDL_ReportAssertion(&sdl_assert_data, SDL_FUNCTION, SDL_ASSERT_FILE, SDL_LINE); \ if (sdl_assert_state == SDL_ASSERTION_RETRY) { \ continue; /* go again. */ \ } else if (sdl_assert_state == SDL_ASSERTION_BREAK) { \ diff --git a/libs/SDL3/include/SDL3/SDL_asyncio.h b/libs/SDL3/include/SDL3/SDL_asyncio.h index b36cb07..3c894d6 100644 --- a/libs/SDL3/include/SDL3/SDL_asyncio.h +++ b/libs/SDL3/include/SDL3/SDL_asyncio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -213,6 +213,8 @@ typedef struct SDL_AsyncIOQueue SDL_AsyncIOQueue; * \returns a pointer to the SDL_AsyncIO 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_CloseAsyncIO @@ -531,6 +533,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_SignalAsyncIOQueue(SDL_AsyncIOQueue *queue) * \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_LoadFile_IO diff --git a/libs/SDL3/include/SDL3/SDL_atomic.h b/libs/SDL3/include/SDL3/SDL_atomic.h index 78b5e0f..1ec0753 100644 --- a/libs/SDL3/include/SDL3/SDL_atomic.h +++ b/libs/SDL3/include/SDL3/SDL_atomic.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -596,6 +596,24 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_SetAtomicU32(SDL_AtomicU32 *a, Uint32 v); */ extern SDL_DECLSPEC Uint32 SDLCALL SDL_GetAtomicU32(SDL_AtomicU32 *a); +/** + * Add to an atomic variable. + * + * This function also acts as a full memory barrier. + * + * ***Note: If you don't know what this function is for, you shouldn't use + * it!*** + * + * \param a a pointer to an SDL_AtomicU32 variable to be modified. + * \param v the desired value to add or subtract. + * \returns the previous value of the atomic variable. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC Uint32 SDLCALL SDL_AddAtomicU32(SDL_AtomicU32 *a, int v); + /** * Set a pointer to a new value if it is currently an old value. * diff --git a/libs/SDL3/include/SDL3/SDL_audio.h b/libs/SDL3/include/SDL3/SDL_audio.h index 51af40e..da797c6 100644 --- a/libs/SDL3/include/SDL3/SDL_audio.h +++ b/libs/SDL3/include/SDL3/SDL_audio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -577,6 +577,15 @@ extern SDL_DECLSPEC SDL_AudioDeviceID * SDLCALL SDL_GetAudioRecordingDevices(int /** * Get the human-readable name of a specific audio device. * + * **WARNING**: this function will work with SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK + * and SDL_AUDIO_DEVICE_DEFAULT_RECORDING, returning the current default + * physical devices' names. However, as the default device may change at any + * time, it is likely better to show a generic name to the user, like "System + * default audio device" or perhaps "default [currently %s]". Do not store + * this name to disk to reidentify the device in a later run of the program, + * as the default might change in general, and the string will be the name of + * a specific device and not the abstract system default. + * * \param devid the instance ID of the device to query. * \returns the name of the audio device, or NULL on failure; call * SDL_GetError() for more information. @@ -1024,7 +1033,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnbindAudioStream(SDL_AudioStream *stream); /** * Query an audio stream for its currently-bound device. * - * This reports the logical 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. @@ -1066,6 +1076,17 @@ extern SDL_DECLSPEC SDL_AudioStream * SDLCALL SDL_CreateAudioStream(const SDL_Au /** * Get the properties associated with an audio stream. * + * The application can hang any data it wants here, but the following + * properties are understood by SDL: + * + * - `SDL_PROP_AUDIOSTREAM_AUTO_CLEANUP_BOOLEAN`: if true (the default), the + * stream be automatically cleaned up when the audio subsystem quits. If set + * to false, the streams will persist beyond that. This property is ignored + * for streams created through SDL_OpenAudioDeviceStream(), and will always + * be cleaned up. Streams that are not cleaned up will still be unbound from + * devices when the audio subsystem quits. This property was added in SDL + * 3.4.0. + * * \param stream the SDL_AudioStream to query. * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. @@ -1076,6 +1097,9 @@ extern SDL_DECLSPEC SDL_AudioStream * SDLCALL SDL_CreateAudioStream(const SDL_Au */ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetAudioStreamProperties(SDL_AudioStream *stream); +#define SDL_PROP_AUDIOSTREAM_AUTO_CLEANUP_BOOLEAN "SDL.audiostream.auto_cleanup" + + /** * Query the current format of an audio stream. * @@ -1152,14 +1176,14 @@ extern SDL_DECLSPEC float SDLCALL SDL_GetAudioStreamFrequencyRatio(SDL_AudioStre * * The frequency ratio is used to adjust the rate at which input data is * consumed. Changing this effectively modifies the speed and pitch of the - * audio. A value greater than 1.0 will play the audio faster, and at a higher - * pitch. A value less than 1.0 will play the audio slower, and at a lower - * pitch. + * audio. A value greater than 1.0f will play the audio faster, and at a + * higher pitch. A value less than 1.0f will play the audio slower, and at a + * lower pitch. 1.0f means play at normal speed. * * This is applied during SDL_GetAudioStreamData, and can be continuously * changed to create various effects. * - * \param stream the stream the frequency ratio is being changed. + * \param stream the stream on which the frequency ratio is being changed. * \param ratio the frequency ratio. 1.0 is normal speed. Must be between 0.01 * and 100. * \returns true on success or false on failure; call SDL_GetError() for more @@ -1321,7 +1345,7 @@ extern SDL_DECLSPEC int * SDLCALL SDL_GetAudioStreamOutputChannelMap(SDL_AudioSt * \threadsafety It is safe to call this function from any thread, as it holds * a stream-specific mutex while running. Don't change the * stream's format to have a different number of channels from a - * a different thread at the same time, though! + * different thread at the same time, though! * * \since This function is available since SDL 3.2.0. * @@ -1335,7 +1359,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetAudioStreamInputChannelMap(SDL_AudioStre * Channel maps are optional; most things do not need them, instead passing * data in the [order that SDL expects](CategoryAudio#channel-layouts). * - * The output channel map reorders data that leaving a stream via + * The output channel map reorders data that is leaving a stream via * SDL_GetAudioStreamData. * * Each item in the array represents an input channel, and its value is the @@ -1417,6 +1441,136 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetAudioStreamOutputChannelMap(SDL_AudioStr */ extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len); +/** + * A callback that fires for completed SDL_PutAudioStreamDataNoCopy() data. + * + * When using SDL_PutAudioStreamDataNoCopy() to provide data to an + * SDL_AudioStream, it's not safe to dispose of the data until the stream has + * completely consumed it. Often times it's difficult to know exactly when + * this has happened. + * + * This callback fires once when the stream no longer needs the buffer, + * allowing the app to easily free or reuse it. + * + * \param userdata an opaque pointer provided by the app for their personal + * use. + * \param buf the pointer provided to SDL_PutAudioStreamDataNoCopy(). + * \param buflen the size of buffer, in bytes, provided to + * SDL_PutAudioStreamDataNoCopy(). + * + * \threadsafety This callbacks may run from any thread, so if you need to + * protect shared data, you should use SDL_LockAudioStream to + * serialize access; this lock will be held before your callback + * is called, so your callback does not need to manage the lock + * explicitly. + * + * \since This datatype is available since SDL 3.4.0. + * + * \sa SDL_SetAudioStreamGetCallback + * \sa SDL_SetAudioStreamPutCallback + */ +typedef void (SDLCALL *SDL_AudioStreamDataCompleteCallback)(void *userdata, const void *buf, int buflen); + +/** + * Add external data to an audio stream without copying it. + * + * Unlike SDL_PutAudioStreamData(), this function does not make a copy of the + * provided data, instead storing the provided pointer. This means that the + * put operation does not need to allocate and copy the data, but the original + * data must remain available until the stream is done with it, either by + * being read from the stream in its entirety, or a call to + * SDL_ClearAudioStream() or SDL_DestroyAudioStream(). + * + * The data must match the format/channels/samplerate specified in the latest + * call to SDL_SetAudioStreamFormat, or the format specified when creating the + * stream if it hasn't been changed. + * + * An optional callback may be provided, which is called when the stream no + * longer needs the data. Once this callback fires, the stream will not access + * the data again. This callback will fire for any reason the data is no + * longer needed, including clearing or destroying the stream. + * + * Note that there is still an allocation to store tracking information, so + * this function is more efficient for larger blocks of data. If you're + * planning to put a few samples at a time, it will be more efficient to use + * SDL_PutAudioStreamData(), which allocates and buffers in blocks. + * + * \param stream the stream the audio data is being added to. + * \param buf a pointer to the audio data to add. + * \param len the number of bytes to add to the stream. + * \param callback the callback function to call when the data is no longer + * needed by the stream. May be NULL. + * \param userdata an opaque pointer provided to the callback for its own + * personal use. + * \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, but if the + * stream has a callback set, the caller might need to manage + * extra locking. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_ClearAudioStream + * \sa SDL_FlushAudioStream + * \sa SDL_GetAudioStreamData + * \sa SDL_GetAudioStreamQueued + */ +extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamDataNoCopy(SDL_AudioStream *stream, const void *buf, int len, SDL_AudioStreamDataCompleteCallback callback, void *userdata); + +/** + * Add data to the stream with each channel in a separate array. + * + * This data must match the format/channels/samplerate specified in the latest + * call to SDL_SetAudioStreamFormat, or the format specified when creating the + * stream if it hasn't been changed. + * + * The data will be interleaved and queued. Note that SDL_AudioStream only + * operates on interleaved data, so this is simply a convenience function for + * easily queueing data from sources that provide separate arrays. There is no + * equivalent function to retrieve planar data. + * + * The arrays in `channel_buffers` are ordered as they are to be interleaved; + * the first array will be the first sample in the interleaved data. Any + * individual array may be NULL; in this case, silence will be interleaved for + * that channel. + * + * `num_channels` specifies how many arrays are in `channel_buffers`. This can + * be used as a safety to prevent overflow, in case the stream format has + * changed elsewhere. If more channels are specified than the current input + * spec, they are ignored. If less channels are specified, the missing arrays + * are treated as if they are NULL (silence is written to those channels). If + * the count is -1, SDL will assume the array count matches the current input + * spec. + * + * Note that `num_samples` is the number of _samples per array_. This can also + * be thought of as the number of _sample frames_ to be queued. A value of 1 + * with stereo arrays will queue two samples to the stream. This is different + * than SDL_PutAudioStreamData, which wants the size of a single array in + * bytes. + * + * \param stream the stream the audio data is being added to. + * \param channel_buffers a pointer to an array of arrays, one array per + * channel. + * \param num_channels the number of arrays in `channel_buffers` or -1. + * \param num_samples the number of _samples_ per array to write to the + * stream. + * \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, but if the + * stream has a callback set, the caller might need to manage + * extra locking. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_ClearAudioStream + * \sa SDL_FlushAudioStream + * \sa SDL_GetAudioStreamData + * \sa SDL_GetAudioStreamQueued + */ +extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *channel_buffers, int num_channels, int num_samples); + /** * Get converted/resampled data from the stream. * @@ -1586,8 +1740,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_PauseAudioStreamDevice(SDL_AudioStream *str * previously been paused. Once unpaused, any bound audio streams will begin * to progress again, and audio can be generated. * - * Remember, SDL_OpenAudioDeviceStream opens device in a paused state, so this - * function call is required for audio playback to begin on such device. + * SDL_OpenAudioDeviceStream opens audio devices in a paused state, so this + * function call is required for audio playback to begin on such devices. * * \param stream the audio stream associated with the audio device to resume. * \returns true on success or false on failure; call SDL_GetError() for more @@ -1844,7 +1998,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroyAudioStream(SDL_AudioStream *stream) * Also unlike other functions, the audio device begins paused. This is to map * more closely to SDL2-style behavior, since there is no extra step here to * bind a stream to begin audio flowing. The audio device should be resumed - * with `SDL_ResumeAudioStreamDevice(stream);` + * with SDL_ResumeAudioStreamDevice(). * * This function works with both playback and recording devices. * diff --git a/libs/SDL3/include/SDL3/SDL_begin_code.h b/libs/SDL3/include/SDL3/SDL_begin_code.h index a6b47cf..94f7909 100644 --- a/libs/SDL3/include/SDL3/SDL_begin_code.h +++ b/libs/SDL3/include/SDL3/SDL_begin_code.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -261,9 +261,9 @@ * * On compilers without restrict support, this is defined to nothing. * - * \since This macro is available since SDL 3.2.0. + * \since This macro is available since SDL 3.4.0. */ -#define SDL_RESTRICT __restrict__ +#define SDL_RESTRICT __restrict /** * Check if the compiler supports a given builtin functionality. @@ -281,9 +281,61 @@ */ #define SDL_HAS_BUILTIN(x) __has_builtin(x) +/** + * A macro to specify data alignment. + * + * This informs the compiler that a given datatype or variable must be aligned + * to a specific byte count. + * + * For example: + * + * ```c + * // make sure this is struct is aligned to 16 bytes for SIMD access. + * typedef struct { + * float x, y, z, w; + * } SDL_ALIGNED(16) MySIMDAlignedData; + * + * // make sure this one field in a struct is aligned to 16 bytes for SIMD access. + * typedef struct { + * SomeStuff stuff; + * float SDL_ALIGNED(16) position[4]; + * SomeOtherStuff other_stuff; + * } MyStruct; + * + * // make sure this variable is aligned to 32 bytes. + * int SDL_ALIGNED(32) myval = 0; + * ``` + * + * Alignment is only guaranteed for things the compiler places: local + * variables on the stack and global/static variables. To dynamically allocate + * something that respects this alignment, use SDL_aligned_alloc() or some + * other mechanism. + * + * On compilers without alignment support, this macro is defined to an invalid + * symbol, to make it clear that the current compiler is likely to generate + * incorrect code when it sees this macro. + * + * \param x the byte count to align to, so the data's address will be a + * multiple of this value. + * + * \since This macro is available since SDL 3.4.0. + */ +#define SDL_ALIGNED(x) __attribute__((aligned(x))) + /* end of wiki documentation section. */ #endif +/* `restrict` is from C99, but __restrict works with both Visual Studio and GCC. */ +#ifndef SDL_RESTRICT +# if defined(restrict) || ((defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L))) +# define SDL_RESTRICT restrict +# elif defined(_MSC_VER) || defined(__GNUC__) || defined(__clang__) +# define SDL_RESTRICT __restrict +# else +# define SDL_RESTRICT +# endif +#endif + #ifndef SDL_HAS_BUILTIN #ifdef __has_builtin #define SDL_HAS_BUILTIN(x) __has_builtin(x) @@ -379,7 +431,7 @@ #endif /* SDL_INLINE not defined */ #ifndef SDL_FORCE_INLINE -#ifdef _MSC_VER +#if defined(_MSC_VER) && (_MSC_VER >= 1200) #define SDL_FORCE_INLINE __forceinline #elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) #define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ @@ -389,7 +441,7 @@ #endif /* SDL_FORCE_INLINE not defined */ #ifndef SDL_NORETURN -#ifdef __GNUC__ +#if defined(__GNUC__) #define SDL_NORETURN __attribute__((noreturn)) #elif defined(_MSC_VER) #define SDL_NORETURN __declspec(noreturn) @@ -484,3 +536,18 @@ #define SDL_ALLOC_SIZE2(p1, p2) #endif #endif /* SDL_ALLOC_SIZE2 not defined */ + +#ifndef SDL_ALIGNED +#if defined(__clang__) || defined(__GNUC__) +#define SDL_ALIGNED(x) __attribute__((aligned(x))) +#elif defined(_MSC_VER) +#define SDL_ALIGNED(x) __declspec(align(x)) +#elif defined(__cplusplus) && (__cplusplus >= 201103L) +#define SDL_ALIGNED(x) alignas(x) +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +#define SDL_ALIGNED(x) _Alignas(x) +#else +#define SDL_ALIGNED(x) PLEASE_DEFINE_SDL_ALIGNED +#endif +#endif /* SDL_ALIGNED not defined */ + diff --git a/libs/SDL3/include/SDL3/SDL_bits.h b/libs/SDL3/include/SDL3/SDL_bits.h index 7435ce6..ddb5cdb 100644 --- a/libs/SDL3/include/SDL3/SDL_bits.h +++ b/libs/SDL3/include/SDL3/SDL_bits.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_blendmode.h b/libs/SDL3/include/SDL3/SDL_blendmode.h index 8f00cbc..84cf622 100644 --- a/libs/SDL3/include/SDL3/SDL_blendmode.h +++ b/libs/SDL3/include/SDL3/SDL_blendmode.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_camera.h b/libs/SDL3/include/SDL3/SDL_camera.h index 5f3911f..14ea647 100644 --- a/libs/SDL3/include/SDL3/SDL_camera.h +++ b/libs/SDL3/include/SDL3/SDL_camera.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -119,7 +119,7 @@ typedef struct SDL_CameraSpec int width; /**< Frame width */ int height; /**< Frame height */ int framerate_numerator; /**< Frame rate numerator ((num / denom) == FPS, (denom / num) == duration in seconds) */ - int framerate_denominator; /**< Frame rate demoninator ((num / denom) == FPS, (denom / num) == duration in seconds) */ + int framerate_denominator; /**< Frame rate denominator ((num / denom) == FPS, (denom / num) == duration in seconds) */ } SDL_CameraSpec; /** @@ -136,6 +136,20 @@ typedef enum SDL_CameraPosition SDL_CAMERA_POSITION_BACK_FACING } SDL_CameraPosition; +/** + * The current state of a request for camera access. + * + * \since This enum is available since SDL 3.4.0. + * + * \sa SDL_GetCameraPermissionState + */ +typedef enum SDL_CameraPermissionState +{ + SDL_CAMERA_PERMISSION_STATE_DENIED = -1, + SDL_CAMERA_PERMISSION_STATE_PENDING, + SDL_CAMERA_PERMISSION_STATE_APPROVED, +} SDL_CameraPermissionState; + /** * Use this function to get the number of built-in camera drivers. @@ -346,8 +360,9 @@ extern SDL_DECLSPEC SDL_Camera * SDLCALL SDL_OpenCamera(SDL_CameraID instance_id * on others the approval might be implicit and not alert the user at all. * * This function can be used to check the status of that approval. It will - * return 0 if still waiting for user response, 1 if the camera is approved - * for use, and -1 if the user denied access. + * return SDL_CAMERA_PERMISSION_STATE_PENDING if waiting for user response, + * SDL_CAMERA_PERMISSION_STATE_APPROVED if the camera is approved for use, and + * SDL_CAMERA_PERMISSION_STATE_DENIED if the user denied access. * * Instead of polling with this function, you can wait for a * SDL_EVENT_CAMERA_DEVICE_APPROVED (or SDL_EVENT_CAMERA_DEVICE_DENIED) event @@ -358,8 +373,9 @@ extern SDL_DECLSPEC SDL_Camera * SDLCALL SDL_OpenCamera(SDL_CameraID instance_id * SDL_CloseCamera() to dispose of it. * * \param camera the opened camera device to query. - * \returns -1 if user denied access to the camera, 1 if user approved access, - * 0 if no decision has been made yet. + * \returns an SDL_CameraPermissionState value indicating if access is + * granted, or `SDL_CAMERA_PERMISSION_STATE_PENDING` if the decision + * is still pending. * * \threadsafety It is safe to call this function from any thread. * @@ -368,7 +384,7 @@ extern SDL_DECLSPEC SDL_Camera * SDLCALL SDL_OpenCamera(SDL_CameraID instance_id * \sa SDL_OpenCamera * \sa SDL_CloseCamera */ -extern SDL_DECLSPEC int SDLCALL SDL_GetCameraPermissionState(SDL_Camera *camera); +extern SDL_DECLSPEC SDL_CameraPermissionState SDLCALL SDL_GetCameraPermissionState(SDL_Camera *camera); /** * Get the instance ID of an opened camera. diff --git a/libs/SDL3/include/SDL3/SDL_clipboard.h b/libs/SDL3/include/SDL3/SDL_clipboard.h index 3ca56a4..6968253 100644 --- a/libs/SDL3/include/SDL3/SDL_clipboard.h +++ b/libs/SDL3/include/SDL3/SDL_clipboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -198,11 +198,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasPrimarySelectionText(void); * \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 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. + * or setting the length to 0 will cause zero length data to be sent + * to the "receiver", which should be able to handle this. The + * returned data will not be freed, so it needs to be retained and + * dealt with internally. * * \since This function is available since SDL 3.2.0. * @@ -211,8 +210,8 @@ 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 when new - * data is set. + * Callback function that will be called when the clipboard is cleared, or + * when new data is set. * * \param userdata a pointer to the provided user data. * @@ -239,7 +238,8 @@ 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. SDL copies the given list. + * \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. diff --git a/libs/SDL3/include/SDL3/SDL_close_code.h b/libs/SDL3/include/SDL3/SDL_close_code.h index da1dea7..ad9daa6 100644 --- a/libs/SDL3/include/SDL3/SDL_close_code.h +++ b/libs/SDL3/include/SDL3/SDL_close_code.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_copying.h b/libs/SDL3/include/SDL3/SDL_copying.h index 747bd35..dd9dbc7 100644 --- a/libs/SDL3/include/SDL3/SDL_copying.h +++ b/libs/SDL3/include/SDL3/SDL_copying.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_cpuinfo.h b/libs/SDL3/include/SDL3/SDL_cpuinfo.h index 1745bd9..5669c23 100644 --- a/libs/SDL3/include/SDL3/SDL_cpuinfo.h +++ b/libs/SDL3/include/SDL3/SDL_cpuinfo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -344,6 +344,27 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetSystemRAM(void); */ extern SDL_DECLSPEC size_t SDLCALL SDL_GetSIMDAlignment(void); +/** + * Report the size of a page of memory. + * + * Different platforms might have different memory page sizes. In current + * times, 4 kilobytes is not unusual, but newer systems are moving to larger + * page sizes, and esoteric platforms might have any unexpected size. + * + * Note that this function can return 0, which means SDL can't determine the + * page size on this platform. It will _not_ set an error string to be + * retrieved with SDL_GetError() in this case! In this case, defaulting to + * 4096 is often a reasonable option. + * + * \returns the size of a single page of memory, in bytes, or 0 if SDL can't + * determine this information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC int SDLCALL SDL_GetSystemPageSize(void); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/libs/SDL3/include/SDL3/SDL_dialog.h b/libs/SDL3/include/SDL3/SDL_dialog.h index ddb9e24..1e01347 100644 --- a/libs/SDL3/include/SDL3/SDL_dialog.h +++ b/libs/SDL3/include/SDL3/SDL_dialog.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -139,10 +139,12 @@ typedef void (SDLCALL *SDL_DialogFileCallback)(void *userdata, const char * cons * it will be invoked. * \param window the window that the dialog should be modal for, may be NULL. * Not all platforms support this option. - * \param filters a list of filters, may be NULL. Not all platforms support - * this option, and platforms that do support it may allow the - * user to ignore the filters. If non-NULL, it must remain - * valid at least until the callback is invoked. + * \param filters a list of filters, may be NULL. See the + * [`SDL_DialogFileFilter`](SDL_DialogFileFilter#code-examples) + * documentation for examples]. Not all platforms support this + * option, and platforms that do support it may allow the user + * to ignore the filters. If non-NULL, it must remain valid at + * least until the callback is invoked. * \param nfilters the number of filters. Ignored if filters is NULL. * \param default_location the default folder or file to start the dialog at, * may be NULL. Not all platforms support this option. diff --git a/libs/SDL3/include/SDL3/SDL_dlopennote.h b/libs/SDL3/include/SDL3/SDL_dlopennote.h new file mode 100644 index 0000000..299435a --- /dev/null +++ b/libs/SDL3/include/SDL3/SDL_dlopennote.h @@ -0,0 +1,234 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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. +*/ + +/* WIKI CATEGORY: DlopenNotes */ + +/** + * # CategoryDlopenNotes + * + * This header allows you to annotate your code so external tools know about + * dynamic shared library dependencies. + * + * If you determine that your toolchain doesn't support dlopen notes, you can + * disable this feature by defining `SDL_DISABLE_DLOPEN_NOTES`. You can use + * this CMake snippet to check for support: + * + * ```cmake + * include(CheckCSourceCompiles) + * find_package(SDL3 REQUIRED CONFIG COMPONENTS Headers) + * list(APPEND CMAKE_REQUIRED_LIBRARIES SDL3::Headers) + * check_c_source_compiles([==[ + * #include + * SDL_ELF_NOTE_DLOPEN("sdl-video", + * "Support for video through SDL", + * SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + * "libSDL-1.2.so.0", "libSDL-2.0.so.0", "libSDL3.so.0" + * ) + * int main(int argc, char *argv[]) { + * return argc + argv[0][1]; + * } + * ]==] COMPILER_SUPPORTS_SDL_ELF_NOTE_DLOPEN) + * if(NOT COMPILER_SUPPORTS_SDL_ELF_NOTE_DLOPEN) + * add_compile_definitions(-DSDL_DISABLE_DLOPEN_NOTE) + * endif() + * ``` + */ + +#ifndef SDL_dlopennote_h +#define SDL_dlopennote_h + +/** + * Use this macro with SDL_ELF_NOTE_DLOPEN() to note that a dynamic shared + * library dependency is optional. + * + * Optional functionality uses the dependency, the binary will work and the + * dependency is only needed for full-featured installations. + * + * \since This macro is available since SDL 3.4.0. + * + * \sa SDL_ELF_NOTE_DLOPEN + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED + */ +#define SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED "suggested" + +/** + * Use this macro with SDL_ELF_NOTE_DLOPEN() to note that a dynamic shared + * library dependency is recommended. + * + * Important functionality needs the dependency, the binary will work but in + * most cases the dependency should be provided. + * + * \since This macro is available since SDL 3.4.0. + * + * \sa SDL_ELF_NOTE_DLOPEN + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED + */ +#define SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED "recommended" + +/** + * Use this macro with SDL_ELF_NOTE_DLOPEN() to note that a dynamic shared + * library dependency is required. + * + * Core functionality needs the dependency, the binary will not work if it + * cannot be found. + * + * \since This macro is available since SDL 3.4.0. + * + * \sa SDL_ELF_NOTE_DLOPEN + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED + */ +#define SDL_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED "required" + + +#if !defined(SDL_PLATFORM_UNIX) || defined(SDL_PLATFORM_ANDROID) +/* The dlopen note functionality isn't used on this platform */ +#ifndef SDL_DISABLE_DLOPEN_NOTES +#define SDL_DISABLE_DLOPEN_NOTES +#endif +#elif defined(__GNUC__) && (__GNUC__ < 3 || (__GNUC__ == 3 && __GNUC_MINOR__ < 1)) +/* gcc < 3.1 too old */ +#ifndef SDL_DISABLE_DLOPEN_NOTES +#define SDL_DISABLE_DLOPEN_NOTES +#endif +#endif /* SDL_PLATFORM_UNIX || SDL_PLATFORM_ANDROID */ + +#if defined(__ELF__) && !defined(SDL_DISABLE_DLOPEN_NOTES) + +#include + +#define SDL_ELF_NOTE_DLOPEN_VENDOR "FDO" +#define SDL_ELF_NOTE_DLOPEN_TYPE 0x407c0c0aU + +#define SDL_ELF_NOTE_INTERNAL2(json, variable_name) \ + __attribute__((aligned(4), used, section(".note.dlopen"))) \ + static const struct { \ + struct { \ + Uint32 n_namesz; \ + Uint32 n_descsz; \ + Uint32 n_type; \ + } nhdr; \ + char name[4]; \ + __attribute__((aligned(4))) char dlopen_json[sizeof(json)]; \ + } variable_name = { \ + { \ + sizeof(SDL_ELF_NOTE_DLOPEN_VENDOR), \ + sizeof(json), \ + SDL_ELF_NOTE_DLOPEN_TYPE \ + }, \ + SDL_ELF_NOTE_DLOPEN_VENDOR, \ + json \ + } + +#define SDL_ELF_NOTE_INTERNAL(json, variable_name) \ + SDL_ELF_NOTE_INTERNAL2(json, variable_name) + +#define SDL_DLNOTE_JSON_ARRAY1(N1) "[\"" N1 "\"]" +#define SDL_DLNOTE_JSON_ARRAY2(N1,N2) "[\"" N1 "\",\"" N2 "\"]" +#define SDL_DLNOTE_JSON_ARRAY3(N1,N2,N3) "[\"" N1 "\",\"" N2 "\",\"" N3 "\"]" +#define SDL_DLNOTE_JSON_ARRAY4(N1,N2,N3,N4) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\"]" +#define SDL_DLNOTE_JSON_ARRAY5(N1,N2,N3,N4,N5) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\"]" +#define SDL_DLNOTE_JSON_ARRAY6(N1,N2,N3,N4,N5,N6) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\",\"" N6 "\"]" +#define SDL_DLNOTE_JSON_ARRAY7(N1,N2,N3,N4,N5,N6,N7) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\",\"" N6 "\",\"" N7 "\"]" +#define SDL_DLNOTE_JSON_ARRAY8(N1,N2,N3,N4,N5,N6,N7,N8) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\",\"" N6 "\",\"" N7 "\",\"" N8 "\"]" +#define SDL_DLNOTE_JSON_ARRAY_GET(N1,N2,N3,N4,N5,N6,N7,N8,NAME,...) NAME +#define SDL_DLNOTE_JSON_ARRAY(...) \ + SDL_DLNOTE_JSON_ARRAY_GET( \ + __VA_ARGS__, \ + SDL_DLNOTE_JSON_ARRAY8, \ + SDL_DLNOTE_JSON_ARRAY7, \ + SDL_DLNOTE_JSON_ARRAY6, \ + SDL_DLNOTE_JSON_ARRAY5, \ + SDL_DLNOTE_JSON_ARRAY4, \ + SDL_DLNOTE_JSON_ARRAY3, \ + SDL_DLNOTE_JSON_ARRAY2, \ + SDL_DLNOTE_JSON_ARRAY1 \ + )(__VA_ARGS__) + +/* Create "unique" variable name using __LINE__, + * so creating multiple elf notes on the same line is not supported + */ +#define SDL_DLNOTE_JOIN2(A,B) A##B +#define SDL_DLNOTE_JOIN(A,B) SDL_DLNOTE_JOIN2(A,B) +#define SDL_DLNOTE_UNIQUE_NAME SDL_DLNOTE_JOIN(s_SDL_dlopen_note_, __LINE__) + +/** + * Add a note that your application has dynamic shared library dependencies. + * + * You can do this by adding the following to the global scope: + * + * ```c + * SDL_ELF_NOTE_DLOPEN( + * "png", + * "Support for loading PNG images using libpng (required for APNG)", + * SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + * "libpng12.so.0" + * ) + * ``` + * + * A trailing semicolon is not needed. + * + * Or if you support multiple versions of a library, you can list them: + * + * ```c + * // Our app supports SDL1, SDL2, and SDL3 by dynamically loading them + * SDL_ELF_NOTE_DLOPEN( + * "SDL", + * "Create windows through SDL video backend", + * SDL_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED + * "libSDL-1.2.so.0", "libSDL2-2.0.so.0", "libSDL3.so.0" + * ) + * ``` + * + * This macro is not available for compilers that do not support variadic + * macro's. + * + * \since This macro is available since SDL 3.4.0. + * + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED + */ +#define SDL_ELF_NOTE_DLOPEN(feature, description, priority, ...) \ + SDL_ELF_NOTE_INTERNAL( \ + "[{\"feature\":\"" feature \ + "\",\"description\":\"" description \ + "\",\"priority\":\"" priority \ + "\",\"soname\":" SDL_DLNOTE_JSON_ARRAY(__VA_ARGS__) "}]", \ + SDL_DLNOTE_UNIQUE_NAME); + +#elif defined(__GNUC__) && __GNUC__ < 3 + +#define SDL_ELF_NOTE_DLOPEN(args...) + +#elif defined(_MSC_VER) && _MSC_VER < 1400 + +/* Variadic macros are not supported */ + +#else + +#define SDL_ELF_NOTE_DLOPEN(...) + +#endif + +#endif /* SDL_dlopennote_h */ diff --git a/libs/SDL3/include/SDL3/SDL_egl.h b/libs/SDL3/include/SDL3/SDL_egl.h index 65d4e96..1371c4d 100644 --- a/libs/SDL3/include/SDL3/SDL_egl.h +++ b/libs/SDL3/include/SDL3/SDL_egl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_endian.h b/libs/SDL3/include/SDL3/SDL_endian.h index 2a9b8a3..957ea20 100644 --- a/libs/SDL3/include/SDL3/SDL_endian.h +++ b/libs/SDL3/include/SDL3/SDL_endian.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -46,7 +46,7 @@ #if defined(_MSC_VER) && (_MSC_VER >= 1400) /* As of Clang 11, '_m_prefetchw' is conflicting with the winnt.h's version, so we define the needed '_m_prefetch' here as a pseudo-header, until the issue is fixed. */ -#ifdef __clang__ +#if defined(__clang__) && !SDL_HAS_BUILTIN(_m_prefetch) #ifndef __PRFCHWINTRIN_H #define __PRFCHWINTRIN_H static __inline__ void __attribute__((__always_inline__, __nodebug__)) @@ -128,7 +128,7 @@ _m_prefetch(void *__P) * \sa SDL_BIG_ENDIAN */ #define SDL_BYTEORDER SDL_LIL_ENDIAN___or_maybe___SDL_BIG_ENDIAN -#elif defined(SDL_PLATFORM_LINUX) +#elif defined(SDL_PLATFORM_LINUX) || defined(__GLIBC__) #include #define SDL_BYTEORDER __BYTE_ORDER #elif defined(SDL_PLATFORM_SOLARIS) @@ -252,7 +252,7 @@ SDL_FORCE_INLINE Uint16 SDL_Swap16(Uint16 x) #elif defined(__x86_64__) SDL_FORCE_INLINE Uint16 SDL_Swap16(Uint16 x) { - __asm__("xchgb %b0,%h0": "=Q"(x):"0"(x)); + __asm__("xchgb %b0,%h0": "=abcd"(x):"0"(x)); return x; } #elif (defined(__powerpc__) || defined(__ppc__)) diff --git a/libs/SDL3/include/SDL3/SDL_error.h b/libs/SDL3/include/SDL3/SDL_error.h index 934967c..3453818 100644 --- a/libs/SDL3/include/SDL3/SDL_error.h +++ b/libs/SDL3/include/SDL3/SDL_error.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_events.h b/libs/SDL3/include/SDL3/SDL_events.h index d267f05..d82a615 100644 --- a/libs/SDL3/include/SDL3/SDL_events.h +++ b/libs/SDL3/include/SDL3/SDL_events.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -30,7 +30,7 @@ * coming and going, the system changing in some way, etc. * * An app generally takes a moment, perhaps at the start of a new frame, to - * examine any events that have occured since the last time and process or + * examine any events that have occurred since the last time and process or * ignore them. This is generally done by calling SDL_PollEvent() in a loop * until it returns false (or, if using the main callbacks, events are * provided one at a time in calls to SDL_AppEvent() before the next call to @@ -127,15 +127,17 @@ typedef enum SDL_EventType SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED, /**< Display has changed desktop mode */ SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED, /**< Display has changed current mode */ SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED, /**< Display has changed content scale */ + SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED, /**< Display has changed usable bounds */ SDL_EVENT_DISPLAY_FIRST = SDL_EVENT_DISPLAY_ORIENTATION, - SDL_EVENT_DISPLAY_LAST = SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED, + SDL_EVENT_DISPLAY_LAST = SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED, /* Window events */ /* 0x200 was SDL_WINDOWEVENT, 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 */ + SDL_EVENT_WINDOW_EXPOSED, /**< Window has been exposed and should be redrawn, and can be redrawn directly from event watchers for this event. + data1 is 1 for live-resize expose events, 0 otherwise. */ SDL_EVENT_WINDOW_MOVED, /**< Window has been moved to data1, data2 */ SDL_EVENT_WINDOW_RESIZED, /**< Window has been resized to data1xdata2 */ SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED,/**< The pixel size of the window has changed to data1xdata2 */ @@ -174,6 +176,8 @@ typedef enum SDL_EventType SDL_EVENT_KEYBOARD_ADDED, /**< A new keyboard has been inserted into the system */ SDL_EVENT_KEYBOARD_REMOVED, /**< A keyboard has been removed */ SDL_EVENT_TEXT_EDITING_CANDIDATES, /**< Keyboard text editing candidates */ + SDL_EVENT_SCREEN_KEYBOARD_SHOWN, /**< The on-screen keyboard has been shown */ + SDL_EVENT_SCREEN_KEYBOARD_HIDDEN, /**< The on-screen keyboard has been hidden */ /* Mouse events */ SDL_EVENT_MOUSE_MOTION = 0x400, /**< Mouse moved */ @@ -214,10 +218,15 @@ typedef enum SDL_EventType SDL_EVENT_FINGER_MOTION, SDL_EVENT_FINGER_CANCELED, + /* Pinch events */ + SDL_EVENT_PINCH_BEGIN = 0x710, /**< Pinch gesture started */ + SDL_EVENT_PINCH_UPDATE, /**< Pinch gesture updated */ + SDL_EVENT_PINCH_END, /**< Pinch gesture ended */ + /* 0x800, 0x801, and 0x802 were the Gesture events from SDL2. Do not reuse these values! sdl2-compat needs them! */ /* Clipboard events */ - SDL_EVENT_CLIPBOARD_UPDATE = 0x900, /**< The clipboard or primary selection changed */ + SDL_EVENT_CLIPBOARD_UPDATE = 0x900, /**< The clipboard changed */ /* Drag and drop events */ SDL_EVENT_DROP_FILE = 0x1000, /**< The system requests a file open */ @@ -298,7 +307,7 @@ typedef struct SDL_CommonEvent */ typedef struct SDL_DisplayEvent { - SDL_EventType type; /**< SDL_DISPLAYEVENT_* */ + SDL_EventType type; /**< SDL_EVENT_DISPLAY_* */ Uint32 reserved; Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ SDL_DisplayID displayID;/**< The associated display */ @@ -704,6 +713,10 @@ typedef struct SDL_GamepadSensorEvent /** * Audio device event structure (event.adevice.*) * + * Note that SDL will send a SDL_EVENT_AUDIO_DEVICE_ADDED event for every + * device it discovers during initialization. After that, this event will only + * arrive when a device is hotplugged during the program's run. + * * \since This struct is available since SDL 3.2.0. */ typedef struct SDL_AudioDeviceEvent @@ -781,7 +794,19 @@ typedef struct SDL_TouchFingerEvent } SDL_TouchFingerEvent; /** - * Pressure-sensitive pen proximity event structure (event.pmotion.*) + * Pinch event structure (event.pinch.*) + */ +typedef struct SDL_PinchFingerEvent +{ + SDL_EventType type; /**< ::SDL_EVENT_PINCH_BEGIN or ::SDL_EVENT_PINCH_UPDATE or ::SDL_EVENT_PINCH_END */ + Uint32 reserved; + Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ + float scale; /**< The scale change since the last SDL_EVENT_PINCH_UPDATE. Scale < 1 is "zoom out". Scale > 1 is "zoom in". */ + SDL_WindowID windowID; /**< The window underneath the finger, if any */ +} SDL_PinchFingerEvent; + +/** + * Pressure-sensitive pen proximity event structure (event.pproximity.*) * * When a pen becomes visible to the system (it is close enough to a tablet, * etc), SDL will send an SDL_EVENT_PEN_PROXIMITY_IN event with the new pen's @@ -793,6 +818,9 @@ typedef struct SDL_TouchFingerEvent * is there." The pen touching and lifting off from the tablet while not * leaving the area are handled by SDL_EVENT_PEN_DOWN and SDL_EVENT_PEN_UP. * + * Not all platforms have a window associated with the pen during proximity + * events. Some wait until motion/button/etc events to offer this info. + * * \since This struct is available since SDL 3.2.0. */ typedef struct SDL_PenProximityEvent @@ -967,7 +995,7 @@ typedef struct SDL_QuitEvent */ typedef struct SDL_UserEvent { - Uint32 type; /**< SDL_EVENT_USER through SDL_EVENT_LAST-1, Uint32 because these are not in the SDL_EventType enumeration */ + Uint32 type; /**< SDL_EVENT_USER through SDL_EVENT_LAST, Uint32 because these are not in the SDL_EventType enumeration */ Uint32 reserved; Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ SDL_WindowID windowID; /**< The associated window if any */ @@ -1017,6 +1045,7 @@ typedef union SDL_Event SDL_QuitEvent quit; /**< Quit request event data */ SDL_UserEvent user; /**< Custom event data */ SDL_TouchFingerEvent tfinger; /**< Touch finger event data */ + SDL_PinchFingerEvent pinch; /**< Pinch event data */ SDL_PenProximityEvent pproximity; /**< Pen proximity event data */ SDL_PenTouchEvent ptouch; /**< Pen tip touching event data */ SDL_PenMotionEvent pmotion; /**< Pen motion event data */ @@ -1043,7 +1072,7 @@ typedef union SDL_Event } SDL_Event; /* Make sure we haven't broken binary compatibility */ -SDL_COMPILE_TIME_ASSERT(SDL_Event, sizeof(SDL_Event) == sizeof(((SDL_Event *)NULL)->padding)); +SDL_COMPILE_TIME_ASSERT(SDL_Event, sizeof(SDL_Event) == sizeof((SDL_static_cast(SDL_Event *, NULL))->padding)); /* Function prototypes */ @@ -1227,15 +1256,13 @@ extern SDL_DECLSPEC void SDLCALL SDL_FlushEvents(Uint32 minType, Uint32 maxType) * Poll for currently pending events. * * If `event` is not NULL, the next event is removed from the queue and stored - * in the SDL_Event structure pointed to by `event`. The 1 returned refers to - * this event, immediately stored in the SDL Event structure -- not an event - * to follow. + * in the SDL_Event structure pointed to by `event`. * - * If `event` is NULL, it simply returns 1 if there is an event in the queue, + * If `event` is NULL, it simply returns true if there is an event in the queue, * but will not remove it from the queue. * * As this function may implicitly call SDL_PumpEvents(), you can only call - * this function in the thread that set the video mode. + * this function in the thread that initialized the video subsystem. * * SDL_PollEvent() is the favored way of receiving system events since it can * be done from the main loop and does not suspend the main loop while waiting @@ -1255,6 +1282,13 @@ extern SDL_DECLSPEC void SDLCALL SDL_FlushEvents(Uint32 minType, Uint32 maxType) * } * ``` * + * Note that Windows (and possibly other platforms) has a quirk about how it + * handles events while dragging/resizing a window, which can cause this + * function to block for significant amounts of time. Technical explanations + * and solutions are discussed on the wiki: + * + * https://wiki.libsdl.org/SDL3/AppFreezeDuringDrag + * * \param event the SDL_Event structure to be filled with the next event from * the queue, or NULL. * \returns true if this got an event or false if there are none available. @@ -1391,7 +1425,10 @@ typedef bool (SDLCALL *SDL_EventFilter)(void *userdata, SDL_Event *event); * allows selective filtering of dynamically arriving events. * * **WARNING**: Be very careful of what you do in the event filter function, - * as it may run in a different thread! + * as it may run in a different thread! The exception is handling of + * SDL_EVENT_WINDOW_EXPOSED, which is guaranteed to be sent from the OS on the + * main thread and you are expected to redraw your window in response to this + * event. * * On platforms that support it, if the quit event is generated by an * interrupt signal (e.g. pressing Ctrl-C), it will be delivered to the @@ -1404,7 +1441,7 @@ typedef bool (SDLCALL *SDL_EventFilter)(void *userdata, SDL_Event *event); * the event filter, but events pushed onto the queue with SDL_PeepEvents() do * not. * - * \param filter an SDL_EventFilter function to call when an event happens. + * \param filter a function to call when an event happens. * \param userdata a pointer that is passed to `filter`. * * \threadsafety It is safe to call this function from any thread. @@ -1567,6 +1604,38 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_RegisterEvents(int numevents); */ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowFromEvent(const SDL_Event *event); +/** + * Generate an English description of an event. + * + * This will fill `buf` with a null-terminated string that might look + * something like this: + * + * ``` + * SDL_EVENT_MOUSE_MOTION (timestamp=1140256324 windowid=2 which=0 state=0 x=492.99 y=139.09 xrel=52 yrel=6) + * ``` + * + * The exact format of the string is not guaranteed; it is intended for + * logging purposes, to be read by a human, and not parsed by a computer. + * + * The returned value follows the same rules as SDL_snprintf(): `buf` will + * always be NULL-terminated (unless `buflen` is zero), and will be truncated + * if `buflen` is too small. The return code is the number of bytes needed for + * the complete string, not counting the NULL-terminator, whether the string + * was truncated or not. Unlike SDL_snprintf(), though, this function never + * returns -1. + * + * \param event an event to describe. May be NULL. + * \param buf the buffer to fill with the description string. May be NULL. + * \param buflen the maximum bytes that can be written to `buf`. + * \returns number of bytes needed for the full string, not counting the + * null-terminator byte. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC int SDLCALL SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/libs/SDL3/include/SDL3/SDL_filesystem.h b/libs/SDL3/include/SDL3/SDL_filesystem.h index af3ca27..0062935 100644 --- a/libs/SDL3/include/SDL3/SDL_filesystem.h +++ b/libs/SDL3/include/SDL3/SDL_filesystem.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -77,6 +77,9 @@ extern "C" { * - `parent`: the containing directory of the bundle. For example: * `/Applications/SDLApp/` * + * **Android Specific Functionality**: This function returns "./", which + * allows filesystem operations to use internal storage and the asset system. + * * **Nintendo 3DS Specific Functionality**: This function returns "romfs" * directory of the application as it is uncommon to store resources outside * the executable. As such it is not a writable directory. @@ -89,6 +92,8 @@ extern "C" { * doesn't implement this functionality, 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_GetPrefPath @@ -134,6 +139,12 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetBasePath(void); * - ...only use letters, numbers, and spaces. Avoid punctuation like "Game * Name 2: Bad Guy's Revenge!" ... "Game Name 2" is sufficient. * + * Due to historical mistakes, `org` is allowed to be NULL or "". In such + * cases, SDL will omit the org subdirectory, including on platforms where it + * shouldn't, and including on platforms where this would make your app fail + * certification for an app store. New apps should definitely specify a real + * string for `org`. + * * The returned path is guaranteed to end with a path separator ('\\' on * Windows, '/' on most other platforms). * @@ -144,6 +155,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetBasePath(void); * etc.). This should be freed with SDL_free() when it is no longer * needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetBasePath @@ -216,6 +229,8 @@ typedef enum SDL_Folder * \returns either a null-terminated C string containing the full path to the * folder, or NULL if an error happened. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC const char * SDLCALL SDL_GetUserFolder(SDL_Folder folder); @@ -227,8 +242,7 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetUserFolder(SDL_Folder folder); * Types of filesystem entries. * * Note that there may be other sorts of items on a filesystem: devices, - * symlinks, named pipes, etc. They are currently reported as - * SDL_PATHTYPE_OTHER. + * named pipes, etc. They are currently reported as SDL_PATHTYPE_OTHER. * * \since This enum is available since SDL 3.2.0. * @@ -283,6 +297,8 @@ typedef Uint32 SDL_GlobFlags; * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_CreateDirectory(const char *path); @@ -346,6 +362,8 @@ typedef SDL_EnumerationResult (SDLCALL *SDL_EnumerateDirectoryCallback)(void *us * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback callback, void *userdata); @@ -360,6 +378,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_EnumerateDirectory(const char *path, SDL_En * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_RemovePath(const char *path); @@ -367,7 +387,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RemovePath(const char *path); /** * Rename a file or directory. * - * If the file at `newpath` already exists, it will replaced. + * If the file at `newpath` already exists, it will be replaced. * * Note that this will not copy files across filesystems/drives/volumes, as * that is a much more complicated (and possibly time-consuming) operation. @@ -383,6 +403,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RemovePath(const char *path); * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_RenamePath(const char *oldpath, const char *newpath); @@ -423,6 +445,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenamePath(const char *oldpath, const char * \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, but this + * operation is not atomic, so the app might need to protect + * access to specific paths from other threads if appropriate. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_CopyFile(const char *oldpath, const char *newpath); @@ -430,12 +456,18 @@ extern SDL_DECLSPEC bool SDLCALL SDL_CopyFile(const char *oldpath, const char *n /** * Get information about a filesystem path. * + * Symlinks, on filesystems that support them, are always followed, so you will + * always get information on what the symlink eventually points to, and not the + * symlink itself. + * * \param path the path to query. * \param info a pointer filled in with information about the path, or NULL to * check for the existence of a file. * \returns true on success or false if the file doesn't exist, or another * 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 bool SDLCALL SDL_GetPathInfo(const char *path, SDL_PathInfo *info); @@ -444,10 +476,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetPathInfo(const char *path, SDL_PathInfo * Enumerate a directory tree, filtered by pattern, and return a list. * * Files are filtered out if they don't match the string in `pattern`, which - * may contain wildcard characters '\*' (match everything) and '?' (match one + * may contain wildcard characters `*` (match everything) and `?` (match one * character). If pattern is NULL, no filtering is done and all results are * returned. Subdirectories are permitted, and are specified with a path - * separator of '/'. Wildcard characters '\*' and '?' never match a path + * separator of `/`. Wildcard characters `*` and `?` never match a path * separator. * * `flags` may be set to SDL_GLOB_CASEINSENSITIVE to make the pattern matching @@ -490,6 +522,8 @@ extern SDL_DECLSPEC char ** SDLCALL SDL_GlobDirectory(const char *path, const ch * platform-dependent notation. NULL if there's a problem. This * should be freed with SDL_free() when it is no longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC char * SDLCALL SDL_GetCurrentDirectory(void); diff --git a/libs/SDL3/include/SDL3/SDL_gamepad.h b/libs/SDL3/include/SDL3/SDL_gamepad.h index 99f8b65..1134525 100644 --- a/libs/SDL3/include/SDL3/SDL_gamepad.h +++ b/libs/SDL3/include/SDL3/SDL_gamepad.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -48,6 +48,9 @@ * SDL_INIT_GAMEPAD flag. This causes SDL to scan the system for gamepads, and * load appropriate drivers. * + * If you're using SDL gamepad support in a Steam game, you must call + * SteamAPI_InitEx() before calling SDL_Init(). + * * If you would like to receive gamepad updates while the application is in * the background, you should set the following hint before calling * SDL_Init(): SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS @@ -118,6 +121,7 @@ typedef enum SDL_GamepadType SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT, SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT, SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR, + SDL_GAMEPAD_TYPE_GAMECUBE, SDL_GAMEPAD_TYPE_COUNT } SDL_GamepadType; @@ -127,8 +131,9 @@ typedef enum SDL_GamepadType * For controllers that use a diamond pattern for the face buttons, the * south/east/west/north buttons below correspond to the locations in the * diamond pattern. For Xbox controllers, this would be A/B/X/Y, for Nintendo - * Switch controllers, this would be B/A/Y/X, for PlayStation controllers this - * would be Cross/Circle/Square/Triangle. + * Switch controllers, this would be B/A/Y/X, for GameCube controllers this + * would be A/X/B/Y, for PlayStation controllers this would be + * Cross/Circle/Square/Triangle. * * For controllers that don't use a diamond pattern for the face buttons, the * south/east/west/north buttons indicate the buttons labeled A, B, C, D, or @@ -163,14 +168,14 @@ typedef enum SDL_GamepadButton SDL_GAMEPAD_BUTTON_DPAD_LEFT, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, SDL_GAMEPAD_BUTTON_MISC1, /**< Additional button (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button, Google Stadia capture button) */ - SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1, /**< Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1) */ - SDL_GAMEPAD_BUTTON_LEFT_PADDLE1, /**< Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3) */ - SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2, /**< Lower or secondary paddle, under your right hand (e.g. Xbox Elite paddle P2) */ - SDL_GAMEPAD_BUTTON_LEFT_PADDLE2, /**< Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4) */ + SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1, /**< Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1, DualSense Edge RB button, Right Joy-Con SR button) */ + SDL_GAMEPAD_BUTTON_LEFT_PADDLE1, /**< Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3, DualSense Edge LB button, Left Joy-Con SL button) */ + SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2, /**< Lower or secondary paddle, under your right hand (e.g. Xbox Elite paddle P2, DualSense Edge right Fn button, Right Joy-Con SL button) */ + SDL_GAMEPAD_BUTTON_LEFT_PADDLE2, /**< Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4, DualSense Edge left Fn button, Left Joy-Con SR button) */ SDL_GAMEPAD_BUTTON_TOUCHPAD, /**< PS4/PS5 touchpad button */ SDL_GAMEPAD_BUTTON_MISC2, /**< Additional button */ - SDL_GAMEPAD_BUTTON_MISC3, /**< Additional button */ - SDL_GAMEPAD_BUTTON_MISC4, /**< Additional button */ + SDL_GAMEPAD_BUTTON_MISC3, /**< Additional button (e.g. Nintendo GameCube left trigger click) */ + SDL_GAMEPAD_BUTTON_MISC4, /**< Additional button (e.g. Nintendo GameCube right trigger click) */ SDL_GAMEPAD_BUTTON_MISC5, /**< Additional button */ SDL_GAMEPAD_BUTTON_MISC6, /**< Additional button */ SDL_GAMEPAD_BUTTON_COUNT @@ -422,6 +427,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_AddGamepadMappingsFromFile(const char *file) * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_ReloadGamepadMappings(void); @@ -436,6 +443,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReloadGamepadMappings(void); * single allocation that should be freed with SDL_free() when it is * no longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC char ** SDLCALL SDL_GetGamepadMappings(int *count); @@ -448,6 +457,8 @@ extern SDL_DECLSPEC char ** SDLCALL SDL_GetGamepadMappings(int *count); * information. This should be freed with SDL_free() when it is no * longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickGUIDForID @@ -465,6 +476,8 @@ extern SDL_DECLSPEC char * SDLCALL SDL_GetGamepadMappingForGUID(SDL_GUID guid); * available; call SDL_GetError() for more information. This should * be freed with SDL_free() when it is no longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_AddGamepadMapping @@ -485,6 +498,8 @@ extern SDL_DECLSPEC char * SDLCALL SDL_GetGamepadMapping(SDL_Gamepad *gamepad); * \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_AddGamepadMapping @@ -497,6 +512,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetGamepadMapping(SDL_JoystickID instance_i * * \returns true if a gamepad is connected, 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_GetGamepads @@ -512,6 +529,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasGamepad(void); * call SDL_GetError() for more information. This should be freed * with SDL_free() when it is no longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_HasGamepad @@ -526,6 +545,8 @@ extern SDL_DECLSPEC SDL_JoystickID * SDLCALL SDL_GetGamepads(int *count); * \returns true if the given joystick is supported by the gamepad interface, * false if it isn't or it's an invalid index. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoysticks @@ -542,6 +563,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_IsGamepad(SDL_JoystickID instance_id); * \returns the name of the selected gamepad. If no name can be found, this * function returns NULL; 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_GetGamepadName @@ -558,6 +581,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadNameForID(SDL_JoystickID * \returns the path of the selected gamepad. If no path can be found, this * function returns NULL; 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_GetGamepadPath @@ -573,6 +598,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadPathForID(SDL_JoystickID * \param instance_id the joystick instance ID. * \returns the player index of a gamepad, or -1 if it's not available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadPlayerIndex @@ -589,6 +616,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetGamepadPlayerIndexForID(SDL_JoystickID in * \returns the GUID of the selected gamepad. If called on an invalid index, * this function returns a zero GUID. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GUIDToString @@ -606,6 +635,8 @@ extern SDL_DECLSPEC SDL_GUID SDLCALL SDL_GetGamepadGUIDForID(SDL_JoystickID inst * \returns the USB vendor ID of the selected gamepad. If called on an invalid * index, this function returns zero. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadVendor @@ -623,6 +654,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadVendorForID(SDL_JoystickID inst * \returns the USB product ID of the selected gamepad. If called on an * invalid index, this function returns zero. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadProduct @@ -640,6 +673,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadProductForID(SDL_JoystickID ins * \returns the product version of the selected gamepad. If called on an * invalid index, this function returns zero. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadProductVersion @@ -655,6 +690,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadProductVersionForID(SDL_Joystic * \param instance_id the joystick instance ID. * \returns the gamepad type. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadType @@ -671,6 +708,8 @@ extern SDL_DECLSPEC SDL_GamepadType SDLCALL SDL_GetGamepadTypeForID(SDL_Joystick * \param instance_id the joystick instance ID. * \returns the gamepad type. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadTypeForID @@ -688,6 +727,8 @@ extern SDL_DECLSPEC SDL_GamepadType SDLCALL SDL_GetRealGamepadTypeForID(SDL_Joys * \returns the mapping string. Returns NULL if no mapping is available. This * should be freed with SDL_free() when it is no longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepads @@ -702,6 +743,8 @@ extern SDL_DECLSPEC char * SDLCALL SDL_GetGamepadMappingForID(SDL_JoystickID ins * \returns a gamepad identifier or NULL if an error occurred; 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_CloseGamepad @@ -717,6 +760,8 @@ extern SDL_DECLSPEC SDL_Gamepad * SDLCALL SDL_OpenGamepad(SDL_JoystickID instanc * \returns an SDL_Gamepad on success or NULL on failure or if it hasn't been * opened yet; 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_Gamepad * SDLCALL SDL_GetGamepadFromID(SDL_JoystickID instance_id); @@ -727,6 +772,8 @@ extern SDL_DECLSPEC SDL_Gamepad * SDLCALL SDL_GetGamepadFromID(SDL_JoystickID in * \param player_index the player index, which different from the instance ID. * \returns the SDL_Gamepad associated with a player index. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadPlayerIndex @@ -757,6 +804,8 @@ extern SDL_DECLSPEC SDL_Gamepad * SDLCALL SDL_GetGamepadFromPlayerIndex(int play * \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_GetGamepadProperties(SDL_Gamepad *gamepad); @@ -775,6 +824,8 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetGamepadProperties(SDL_Gamepa * \returns the instance ID of the specified gamepad 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_JoystickID SDLCALL SDL_GetGamepadID(SDL_Gamepad *gamepad); @@ -787,6 +838,8 @@ extern SDL_DECLSPEC SDL_JoystickID SDLCALL SDL_GetGamepadID(SDL_Gamepad *gamepad * \returns the implementation dependent name for the gamepad, or NULL if * there is no name or the identifier passed is invalid. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadNameForID @@ -801,6 +854,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadName(SDL_Gamepad *gamepad * \returns the implementation dependent path for the gamepad, or NULL if * there is no path or the identifier passed is invalid. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadPathForID @@ -814,6 +869,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadPath(SDL_Gamepad *gamepad * \returns the gamepad type, or SDL_GAMEPAD_TYPE_UNKNOWN if it's not * available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadTypeForID @@ -827,6 +884,8 @@ extern SDL_DECLSPEC SDL_GamepadType SDLCALL SDL_GetGamepadType(SDL_Gamepad *game * \returns the gamepad type, or SDL_GAMEPAD_TYPE_UNKNOWN if it's not * available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetRealGamepadTypeForID @@ -841,6 +900,8 @@ extern SDL_DECLSPEC SDL_GamepadType SDLCALL SDL_GetRealGamepadType(SDL_Gamepad * * \param gamepad the gamepad object to query. * \returns the player index for gamepad, or -1 if it's not available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetGamepadPlayerIndex @@ -856,6 +917,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetGamepadPlayerIndex(SDL_Gamepad *gamepad); * \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_GetGamepadPlayerIndex @@ -870,6 +933,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetGamepadPlayerIndex(SDL_Gamepad *gamepad, * \param gamepad the gamepad object to query. * \returns the USB vendor ID, or zero if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadVendorForID @@ -884,6 +949,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadVendor(SDL_Gamepad *gamepad); * \param gamepad the gamepad object to query. * \returns the USB product ID, or zero if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadProductForID @@ -898,6 +965,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadProduct(SDL_Gamepad *gamepad); * \param gamepad the gamepad object to query. * \returns the USB product version, or zero if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadProductVersionForID @@ -912,6 +981,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadProductVersion(SDL_Gamepad *gam * \param gamepad the gamepad object to query. * \returns the gamepad firmware version, or zero if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadFirmwareVersion(SDL_Gamepad *gamepad); @@ -924,6 +995,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadFirmwareVersion(SDL_Gamepad *ga * \param gamepad the gamepad object to query. * \returns the serial number, or NULL if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadSerial(SDL_Gamepad *gamepad); @@ -937,6 +1010,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadSerial(SDL_Gamepad *gamep * \param gamepad the gamepad object to query. * \returns the gamepad handle, or 0 if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC Uint64 SDLCALL SDL_GetGamepadSteamHandle(SDL_Gamepad *gamepad); @@ -949,6 +1024,8 @@ extern SDL_DECLSPEC Uint64 SDLCALL SDL_GetGamepadSteamHandle(SDL_Gamepad *gamepa * `SDL_JOYSTICK_CONNECTION_INVALID` 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_JoystickConnectionState SDLCALL SDL_GetGamepadConnectionState(SDL_Gamepad *gamepad); @@ -969,6 +1046,8 @@ extern SDL_DECLSPEC SDL_JoystickConnectionState SDLCALL SDL_GetGamepadConnection * battery. * \returns the current battery state. * + * \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_PowerState SDLCALL SDL_GetGamepadPowerInfo(SDL_Gamepad *gamepad, int *percent); @@ -981,6 +1060,8 @@ extern SDL_DECLSPEC SDL_PowerState SDLCALL SDL_GetGamepadPowerInfo(SDL_Gamepad * * \returns true if the gamepad has been opened and is currently connected, or * false if not. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadConnected(SDL_Gamepad *gamepad); @@ -1001,6 +1082,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadConnected(SDL_Gamepad *gamepad); * \returns an SDL_Joystick object, 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. */ extern SDL_DECLSPEC SDL_Joystick * SDLCALL SDL_GetGamepadJoystick(SDL_Gamepad *gamepad); @@ -1013,6 +1096,8 @@ extern SDL_DECLSPEC SDL_Joystick * SDLCALL SDL_GetGamepadJoystick(SDL_Gamepad *g * * \param enabled whether to process gamepad events or not. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GamepadEventsEnabled @@ -1028,6 +1113,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetGamepadEventsEnabled(bool enabled); * * \returns true if gamepad events are being processed, 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_SetGamepadEventsEnabled @@ -1044,6 +1131,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadEventsEnabled(void); * single allocation that should be freed with SDL_free() when it is * no longer needed. * + * \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_GamepadBinding ** SDLCALL SDL_GetGamepadBindings(SDL_Gamepad *gamepad, int *count); @@ -1055,6 +1144,8 @@ extern SDL_DECLSPEC SDL_GamepadBinding ** SDLCALL SDL_GetGamepadBindings(SDL_Gam * enabled. Under such circumstances, it will not be necessary to call this * function. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_UpdateGamepads(void); @@ -1071,6 +1162,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UpdateGamepads(void); * \returns the SDL_GamepadType enum corresponding to the input string, or * `SDL_GAMEPAD_TYPE_UNKNOWN` if no match was found. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadStringForType @@ -1085,6 +1178,8 @@ extern SDL_DECLSPEC SDL_GamepadType SDLCALL SDL_GetGamepadTypeFromString(const c * specified. The string returned is of the format used by * SDL_Gamepad mapping strings. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadTypeFromString @@ -1107,6 +1202,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadStringForType(SDL_Gamepad * \returns the SDL_GamepadAxis enum corresponding to the input string, or * `SDL_GAMEPAD_AXIS_INVALID` if no match was found. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadStringForAxis @@ -1121,6 +1218,8 @@ extern SDL_DECLSPEC SDL_GamepadAxis SDLCALL SDL_GetGamepadAxisFromString(const c * specified. The string returned is of the format used by * SDL_Gamepad mapping strings. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadAxisFromString @@ -1137,6 +1236,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadStringForAxis(SDL_Gamepad * \param axis an axis enum value (an SDL_GamepadAxis value). * \returns true if the gamepad has this axis, 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_GamepadHasButton @@ -1156,10 +1257,14 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadHasAxis(SDL_Gamepad *gamepad, SDL_Ga * return a negative value. Note that this differs from the value reported by * the lower-level SDL_GetJoystickAxis(), which normally uses the full range. * + * Note that for invalid gamepads or axes, this will return 0. Zero is also a + * valid value in normal operation; usually it means a centered axis. + * * \param gamepad a gamepad. * \param axis an axis index (one of the SDL_GamepadAxis values). - * \returns axis state (including 0) on success or 0 (also) on failure; call - * SDL_GetError() for more information. + * \returns axis state. + * + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. * @@ -1176,10 +1281,12 @@ extern SDL_DECLSPEC Sint16 SDLCALL SDL_GetGamepadAxis(SDL_Gamepad *gamepad, SDL_ * You do not normally need to call this function unless you are parsing * SDL_Gamepad mappings in your own code. * - * \param str string representing a SDL_Gamepad axis. + * \param str string representing a SDL_Gamepad button. * \returns the SDL_GamepadButton enum corresponding to the input string, or * `SDL_GAMEPAD_BUTTON_INVALID` if no match was found. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadStringForButton @@ -1194,6 +1301,8 @@ extern SDL_DECLSPEC SDL_GamepadButton SDLCALL SDL_GetGamepadButtonFromString(con * specified. The string returned is of the format used by * SDL_Gamepad mapping strings. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadButtonFromString @@ -1210,6 +1319,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadStringForButton(SDL_Gamep * \param button a button enum value (an SDL_GamepadButton value). * \returns true if the gamepad has this button, 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_GamepadHasAxis @@ -1223,6 +1334,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadHasButton(SDL_Gamepad *gamepad, SDL_ * \param button a button index (one of the SDL_GamepadButton values). * \returns true if the button is pressed, 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_GamepadHasButton @@ -1237,6 +1350,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetGamepadButton(SDL_Gamepad *gamepad, SDL_ * \param button a button index (one of the SDL_GamepadButton values). * \returns the SDL_GamepadButtonLabel enum corresponding to the button label. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadButtonLabel @@ -1250,6 +1365,8 @@ extern SDL_DECLSPEC SDL_GamepadButtonLabel SDLCALL SDL_GetGamepadButtonLabelForT * \param button a button index (one of the SDL_GamepadButton values). * \returns the SDL_GamepadButtonLabel enum corresponding to the button label. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadButtonLabelForType @@ -1262,6 +1379,8 @@ extern SDL_DECLSPEC SDL_GamepadButtonLabel SDLCALL SDL_GetGamepadButtonLabel(SDL * \param gamepad a gamepad. * \returns number of touchpads. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetNumGamepadTouchpadFingers @@ -1276,6 +1395,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumGamepadTouchpads(SDL_Gamepad *gamepad) * \param touchpad a touchpad. * \returns number of supported simultaneous fingers. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadTouchpadFinger @@ -1299,6 +1420,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumGamepadTouchpadFingers(SDL_Gamepad *ga * \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_GetNumGamepadTouchpadFingers @@ -1312,6 +1435,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetGamepadTouchpadFinger(SDL_Gamepad *gamep * \param type the type of sensor to query. * \returns true if the sensor exists, 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_GetGamepadSensorData @@ -1329,6 +1454,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadHasSensor(SDL_Gamepad *gamepad, SDL_ * \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_GamepadHasSensor @@ -1343,6 +1470,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetGamepadSensorEnabled(SDL_Gamepad *gamepa * \param type the type of sensor to query. * \returns true if the sensor is 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_SetGamepadSensorEnabled @@ -1356,6 +1485,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadSensorEnabled(SDL_Gamepad *gamepad, * \param type the type of sensor to query. * \returns the data rate, or 0.0f if the data rate is not available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC float SDLCALL SDL_GetGamepadSensorDataRate(SDL_Gamepad *gamepad, SDL_SensorType type); @@ -1364,7 +1495,7 @@ extern SDL_DECLSPEC float SDLCALL SDL_GetGamepadSensorDataRate(SDL_Gamepad *game * Get the current state of a gamepad sensor. * * The number of values and interpretation of the data is sensor dependent. - * See SDL_sensor.h for the details for each type of sensor. + * See the remarks in SDL_SensorType for details for each type of sensor. * * \param gamepad the gamepad to query. * \param type the type of sensor to query. @@ -1373,6 +1504,8 @@ extern SDL_DECLSPEC float SDLCALL SDL_GetGamepadSensorDataRate(SDL_Gamepad *game * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetGamepadSensorData(SDL_Gamepad *gamepad, SDL_SensorType type, float *data, int num_values); @@ -1395,6 +1528,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetGamepadSensorData(SDL_Gamepad *gamepad, * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_RumbleGamepad(SDL_Gamepad *gamepad, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms); @@ -1421,6 +1556,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RumbleGamepad(SDL_Gamepad *gamepad, Uint16 * \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_RumbleGamepad @@ -1443,6 +1580,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RumbleGamepadTriggers(SDL_Gamepad *gamepad, * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_SetGamepadLED(SDL_Gamepad *gamepad, Uint8 red, Uint8 green, Uint8 blue); @@ -1456,6 +1595,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetGamepadLED(SDL_Gamepad *gamepad, Uint8 r * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_SendGamepadEffect(SDL_Gamepad *gamepad, const void *data, int size); @@ -1466,6 +1607,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SendGamepadEffect(SDL_Gamepad *gamepad, con * \param gamepad a gamepad identifier previously returned by * SDL_OpenGamepad(). * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_OpenGamepad @@ -1480,6 +1623,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_CloseGamepad(SDL_Gamepad *gamepad); * \param button a button on the gamepad. * \returns the sfSymbolsName or NULL if the name can't be found. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadAppleSFSymbolsNameForAxis @@ -1493,6 +1638,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadAppleSFSymbolsNameForButt * \param axis an axis on the gamepad. * \returns the sfSymbolsName or NULL if the name can't be found. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadAppleSFSymbolsNameForButton diff --git a/libs/SDL3/include/SDL3/SDL_gpu.h b/libs/SDL3/include/SDL3/SDL_gpu.h index 4a5e32f..b9e19ab 100644 --- a/libs/SDL3/include/SDL3/SDL_gpu.h +++ b/libs/SDL3/include/SDL3/SDL_gpu.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -206,14 +206,20 @@ * underlying graphics API. While it's possible that we have done something * inefficiently, it's very unlikely especially if you are relatively * inexperienced with GPU rendering. Please see the performance tips above and - * make sure you are following them. Additionally, tools like RenderDoc can be - * very helpful for diagnosing incorrect behavior and performance issues. + * make sure you are following them. Additionally, tools like + * [RenderDoc](https://renderdoc.org/) + * can be very helpful for diagnosing incorrect behavior and performance + * issues. * * ## System Requirements * - * **Vulkan:** Supported on Windows, Linux, Nintendo Switch, and certain - * Android devices. Requires Vulkan 1.0 with the following extensions and - * device features: + * ### Vulkan + * + * SDL driver name: "vulkan" (for use in SDL_CreateGPUDevice() and + * SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING) + * + * Supported on Windows, Linux, Nintendo Switch, and certain Android devices. + * Requires Vulkan 1.0 with the following extensions and device features: * * - `VK_KHR_swapchain` * - `VK_KHR_maintenance1` @@ -222,13 +228,37 @@ * - `depthClamp` * - `shaderClipDistance` * - `drawIndirectFirstInstance` + * - `sampleRateShading` * - * **D3D12:** Supported on Windows 10 or newer, Xbox One (GDK), and Xbox - * Series X|S (GDK). Requires a GPU that supports DirectX 12 Feature Level - * 11_1. + * You can remove some of these requirements to increase compatibility with + * Android devices by using these properties when creating the GPU device with + * SDL_CreateGPUDeviceWithProperties(): * - * **Metal:** Supported on macOS 10.14+ and iOS/tvOS 13.0+. Hardware - * requirements vary by operating system: + * - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN + * - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN + * - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN + * - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN + * + * ### D3D12 + * + * SDL driver name: "direct3d12" + * + * Supported on Windows 10 or newer, Xbox One (GDK), and Xbox Series X|S + * (GDK). Requires a GPU that supports DirectX 12 Feature Level 11_0 and + * Resource Binding Tier 2 or above. + * + * You can remove the Tier 2 resource binding requirement to support Intel + * Haswell and Broadwell GPUs by using this property when creating the GPU + * device with SDL_CreateGPUDeviceWithProperties(): + * + * - SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN + * + * ### Metal + * + * SDL driver name: "metal" + * + * Supported on macOS 10.14+ and iOS/tvOS 13.0+. Hardware requirements vary by + * operating system: * * - macOS requires an Apple Silicon or * [Intel Mac2 family](https://developer.apple.com/documentation/metal/mtlfeatureset/mtlfeatureset_macos_gpufamily2_v1?language=objc) @@ -236,6 +266,26 @@ * - iOS/tvOS requires an A9 GPU or newer * - iOS Simulator and tvOS Simulator are unsupported * + * ## Coordinate System + * + * The GPU API uses a left-handed coordinate system, following the convention + * of D3D12 and Metal. Specifically: + * + * - **Normalized Device Coordinates:** The lower-left corner has an x,y + * coordinate of `(-1.0, -1.0)`. The upper-right corner is `(1.0, 1.0)`. Z + * values range from `[0.0, 1.0]` where 0 is the near plane. + * - **Viewport Coordinates:** The top-left corner has an x,y coordinate of + * `(0, 0)` and extends to the bottom-right corner at `(viewportWidth, + * viewportHeight)`. +Y is down. + * - **Texture Coordinates:** The top-left corner has an x,y coordinate of + * `(0, 0)` and extends to the bottom-right corner at `(1.0, 1.0)`. +Y is + * down. + * + * If the backend driver differs from this convention (e.g. Vulkan, which has + * an NDC that assumes +Y is down), SDL will automatically convert the + * coordinate system behind the scenes, so you don't need to perform any + * coordinate flipping logic in your shaders. + * * ## Uniform Data * * Uniforms are for passing data to shaders. The uniform data will be constant @@ -301,6 +351,39 @@ * unreferenced data in a bound resource without cycling, but overwriting a * section of data that has already been referenced will produce unexpected * results. + * + * ## Debugging + * + * At some point of your GPU journey, you will probably encounter issues that + * are not traceable with regular debugger - for example, your code compiles + * but you get an empty screen, or your shader fails in runtime. + * + * For debugging such cases, there are tools that allow visually inspecting + * the whole GPU frame, every drawcall, every bound resource, memory buffers, + * etc. They are the following, per platform: + * + * * For Windows/Linux, use + * [RenderDoc](https://renderdoc.org/) + * * For MacOS (Metal), use Xcode built-in debugger (Open XCode, go to Debug > + * Debug Executable..., select your application, set "GPU Frame Capture" to + * "Metal" in scheme "Options" window, run your app, and click the small + * Metal icon on the bottom to capture a frame) + * + * Aside from that, you may want to enable additional debug layers to receive + * more detailed error messages, based on your GPU backend: + * + * * For D3D12, the debug layer is an optional feature that can be installed + * via "Windows Settings -> System -> Optional features" and adding the + * "Graphics Tools" optional feature. + * * For Vulkan, you will need to install Vulkan SDK on Windows, and on Linux, + * you usually have some sort of `vulkan-validation-layers` system package + * that should be installed. + * * For Metal, it should be enough just to run the application from XCode to + * receive detailed errors or warnings in the output. + * + * Don't hesitate to use tools as RenderDoc when encountering runtime issues + * or unexpected output on screen, quick GPU frame inspection can usually help + * you fix the majority of such problems. */ #ifndef SDL_gpu_h_ @@ -1091,7 +1174,7 @@ typedef enum SDL_GPUCompareOp SDL_GPU_COMPAREOP_LESS_OR_EQUAL, /**< The comparison evaluates reference <= test. */ SDL_GPU_COMPAREOP_GREATER, /**< The comparison evaluates reference > test. */ SDL_GPU_COMPAREOP_NOT_EQUAL, /**< The comparison evaluates reference != test. */ - SDL_GPU_COMPAREOP_GREATER_OR_EQUAL, /**< The comparison evalutes reference >= test. */ + SDL_GPU_COMPAREOP_GREATER_OR_EQUAL, /**< The comparison evaluates reference >= test. */ SDL_GPU_COMPAREOP_ALWAYS /**< The comparison always evaluates true. */ } SDL_GPUCompareOp; @@ -1310,6 +1393,18 @@ typedef struct SDL_GPUViewport * A structure specifying parameters related to transferring data to or from a * texture. * + * If either of `pixels_per_row` or `rows_per_layer` is zero, then width and + * height of passed SDL_GPUTextureRegion to SDL_UploadToGPUTexture or + * SDL_DownloadFromGPUTexture are used as default values respectively and data + * is considered to be tightly packed. + * + * **WARNING**: On some older/integrated hardware, Direct3D 12 requires texture + * data row pitch to be 256 byte aligned, and offsets to be aligned to 512 bytes. + * If they are not, SDL will make a temporary copy of the data that is properly + * aligned, but this adds overhead to the transfer process. Apps can avoid this + * by aligning their data appropriately, or using a different GPU backend than + * Direct3D 12. + * * \since This struct is available since SDL 3.2.0. * * \sa SDL_UploadToGPUTexture @@ -1391,7 +1486,7 @@ typedef struct SDL_GPUTextureRegion */ typedef struct SDL_GPUBlitRegion { - SDL_GPUTexture *texture; /**< The texture. */ + SDL_GPUTexture *texture; /**< The texture. */ Uint32 mip_level; /**< The mip level index of the region. */ Uint32 layer_or_depth_plane; /**< The layer index or depth plane of the region. This value is treated as a layer index on 2D array and cube textures, and as a depth plane on 3D textures. */ Uint32 x; /**< The left offset of the region. */ @@ -1520,8 +1615,8 @@ typedef struct SDL_GPUSamplerCreateInfo SDL_GPUCompareOp compare_op; /**< The comparison operator to apply to fetched data before filtering. */ float min_lod; /**< Clamps the minimum of the computed LOD value. */ float max_lod; /**< Clamps the maximum of the computed LOD value. */ - bool enable_anisotropy; /**< true to enable anisotropic filtering. */ - bool enable_compare; /**< true to enable comparison against a reference value during lookups. */ + bool enable_anisotropy; /**< true to enable anisotropic filtering. */ + bool enable_compare; /**< true to enable comparison against a reference value during lookups. */ Uint8 padding1; Uint8 padding2; @@ -1613,6 +1708,9 @@ typedef struct SDL_GPUStencilOpState * \since This struct is available since SDL 3.2.0. * * \sa SDL_GPUColorTargetDescription + * \sa SDL_GPUBlendFactor + * \sa SDL_GPUBlendOp + * \sa SDL_GPUColorComponentFlags */ typedef struct SDL_GPUColorTargetBlendState { @@ -1623,8 +1721,8 @@ typedef struct SDL_GPUColorTargetBlendState SDL_GPUBlendFactor dst_alpha_blendfactor; /**< The value to be multiplied by the destination alpha. */ SDL_GPUBlendOp alpha_blend_op; /**< The blend operation for the alpha component. */ SDL_GPUColorComponentFlags color_write_mask; /**< A bitmask specifying which of the RGBA components are enabled for writing. Writes to all channels if enable_color_write_mask is false. */ - bool enable_blend; /**< Whether blending is enabled for the color target. */ - bool enable_color_write_mask; /**< Whether the color write mask is enabled. */ + bool enable_blend; /**< Whether blending is enabled for the color target. */ + bool enable_color_write_mask; /**< Whether the color write mask is enabled. */ Uint8 padding1; Uint8 padding2; } SDL_GPUColorTargetBlendState; @@ -1636,6 +1734,8 @@ typedef struct SDL_GPUColorTargetBlendState * \since This struct is available since SDL 3.2.0. * * \sa SDL_CreateGPUShader + * \sa SDL_GPUShaderFormat + * \sa SDL_GPUShaderStage */ typedef struct SDL_GPUShaderCreateInfo { @@ -1741,8 +1841,8 @@ typedef struct SDL_GPURasterizerState float depth_bias_constant_factor; /**< A scalar factor controlling the depth value added to each fragment. */ float depth_bias_clamp; /**< The maximum depth bias of a fragment. */ float depth_bias_slope_factor; /**< A scalar factor applied to a fragment's slope in depth calculations. */ - bool enable_depth_bias; /**< true to bias fragment depth values. */ - bool enable_depth_clip; /**< true to enable depth clip, false to enable depth clamp. */ + bool enable_depth_bias; /**< true to bias fragment depth values. */ + bool enable_depth_clip; /**< true to enable depth clip, false to enable depth clamp. */ Uint8 padding1; Uint8 padding2; } SDL_GPURasterizerState; @@ -1759,8 +1859,8 @@ typedef struct SDL_GPUMultisampleState { SDL_GPUSampleCount sample_count; /**< The number of samples to be used in rasterization. */ 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; + bool enable_mask; /**< Reserved for future use. Must be set to false. */ + bool enable_alpha_to_coverage; /**< true enables the alpha-to-coverage feature. */ Uint8 padding2; Uint8 padding3; } SDL_GPUMultisampleState; @@ -1780,9 +1880,9 @@ typedef struct SDL_GPUDepthStencilState SDL_GPUStencilOpState front_stencil_state; /**< The stencil op state for front-facing triangles. */ Uint8 compare_mask; /**< Selects the bits of the stencil values participating in the stencil test. */ Uint8 write_mask; /**< Selects the bits of the stencil values updated by the stencil test. */ - bool enable_depth_test; /**< true enables the depth test. */ - bool enable_depth_write; /**< true enables depth writes. Depth writes are always disabled when enable_depth_test is false. */ - bool enable_stencil_test; /**< true enables the stencil test. */ + bool enable_depth_test; /**< true enables the depth test. */ + bool enable_depth_write; /**< true enables depth writes. Depth writes are always disabled when enable_depth_test is false. */ + bool enable_stencil_test; /**< true enables the stencil test. */ Uint8 padding1; Uint8 padding2; Uint8 padding3; @@ -1817,7 +1917,7 @@ typedef struct SDL_GPUGraphicsPipelineTargetInfo const SDL_GPUColorTargetDescription *color_target_descriptions; /**< A pointer to an array of color target descriptions. */ Uint32 num_color_targets; /**< The number of color target descriptions in the above array. */ SDL_GPUTextureFormat depth_stencil_format; /**< The pixel format of the depth-stencil target. Ignored if has_depth_stencil_target is false. */ - bool has_depth_stencil_target; /**< true specifies that the pipeline uses a depth-stencil target. */ + bool has_depth_stencil_target; /**< true specifies that the pipeline uses a depth-stencil target. */ Uint8 padding1; Uint8 padding2; Uint8 padding3; @@ -1912,6 +2012,7 @@ typedef struct SDL_GPUComputePipelineCreateInfo * \since This struct is available since SDL 3.2.0. * * \sa SDL_BeginGPURenderPass + * \sa SDL_FColor */ typedef struct SDL_GPUColorTargetInfo { @@ -1924,8 +2025,8 @@ typedef struct SDL_GPUColorTargetInfo SDL_GPUTexture *resolve_texture; /**< The texture that will receive the results of a multisample resolve operation. Ignored if a RESOLVE* store_op is not used. */ Uint32 resolve_mip_level; /**< The mip level of the resolve texture to use for the resolve operation. Ignored if a RESOLVE* store_op is not used. */ Uint32 resolve_layer; /**< The layer index of the resolve texture to use for the resolve operation. Ignored if a RESOLVE* store_op is not used. */ - bool cycle; /**< true cycles the texture if the texture is bound and load_op is not LOAD */ - bool cycle_resolve_texture; /**< true cycles the resolve texture if the resolve texture is bound. Ignored if a RESOLVE* store_op is not used. */ + bool cycle; /**< true cycles the texture if the texture is bound and load_op is not LOAD */ + bool cycle_resolve_texture; /**< true cycles the resolve texture if the resolve texture is bound. Ignored if a RESOLVE* store_op is not used. */ Uint8 padding1; Uint8 padding2; } SDL_GPUColorTargetInfo; @@ -1970,6 +2071,9 @@ typedef struct SDL_GPUColorTargetInfo * * Note that depth/stencil targets do not support multisample resolves. * + * Due to ABI limitations, depth textures with more than 255 layers are not + * supported. + * * \since This struct is available since SDL 3.2.0. * * \sa SDL_BeginGPURenderPass @@ -1982,10 +2086,10 @@ typedef struct SDL_GPUDepthStencilTargetInfo SDL_GPUStoreOp store_op; /**< What is done with the depth results of the render pass. */ SDL_GPULoadOp stencil_load_op; /**< What is done with the stencil contents at the beginning of the render pass. */ SDL_GPUStoreOp stencil_store_op; /**< What is done with the stencil results of the render pass. */ - bool cycle; /**< true cycles the texture if the texture is bound and any load ops are not LOAD */ + bool cycle; /**< true cycles the texture if the texture is bound and any load ops are not LOAD */ Uint8 clear_stencil; /**< The value to clear the stencil component to at the beginning of the render pass. Ignored if SDL_GPU_LOADOP_CLEAR is not used. */ - Uint8 padding1; - Uint8 padding2; + Uint8 mip_level; /**< The mip level to use as the depth stencil target. */ + Uint8 layer; /**< The layer index to use as the depth stencil target. */ } SDL_GPUDepthStencilTargetInfo; /** @@ -2002,7 +2106,7 @@ typedef struct SDL_GPUBlitInfo { SDL_FColor clear_color; /**< The color to clear the destination region to before the blit. Ignored if load_op is not SDL_GPU_LOADOP_CLEAR. */ SDL_FlipMode flip_mode; /**< The flip mode for the source region. */ SDL_GPUFilter filter; /**< The filter mode used when blitting. */ - bool cycle; /**< true cycles the destination texture if it is already bound. */ + bool cycle; /**< true cycles the destination texture if it is already bound. */ Uint8 padding1; Uint8 padding2; Uint8 padding3; @@ -2031,6 +2135,8 @@ typedef struct SDL_GPUBufferBinding * * \sa SDL_BindGPUVertexSamplers * \sa SDL_BindGPUFragmentSamplers + * \sa SDL_GPUTexture + * \sa SDL_GPUSampler */ typedef struct SDL_GPUTextureSamplerBinding { @@ -2111,6 +2217,13 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GPUSupportsProperties( /** * Creates a GPU context. * + * The GPU driver name can be one of the following: + * + * - "vulkan": [Vulkan](CategoryGPU#vulkan) + * - "direct3d12": [D3D12](CategoryGPU#d3d12) + * - "metal": [Metal](CategoryGPU#metal) + * - NULL: let SDL pick the optimal driver + * * \param format_flags a bitflag indicating which shader formats the app is * able to provide. * \param debug_mode enable debug mode properties and validations. @@ -2121,6 +2234,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GPUSupportsProperties( * * \since This function is available since SDL 3.2.0. * + * \sa SDL_CreateGPUDeviceWithProperties * \sa SDL_GetGPUShaderFormats * \sa SDL_GetGPUDeviceDriver * \sa SDL_DestroyGPUDevice @@ -2140,8 +2254,31 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( * properties and validations, defaults to true. * - `SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN`: enable to prefer * energy efficiency over maximum GPU performance, defaults to false. + * - `SDL_PROP_GPU_DEVICE_CREATE_VERBOSE_BOOLEAN`: enable to automatically log + * useful debug information on device creation, defaults to true. * - `SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING`: the name of the GPU driver to * use, if a specific one is desired. + * - `SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN`: Enable Vulkan + * device feature shaderClipDistance. If disabled, clip distances are not + * supported in shader code: gl_ClipDistance[] built-ins of GLSL, + * SV_ClipDistance0/1 semantics of HLSL and [[clip_distance]] attribute of + * Metal. Disabling optional features allows the application to run on some + * older Android devices. Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN`: Enable + * Vulkan device feature depthClamp. If disabled, there is no depth clamp + * support and enable_depth_clip in SDL_GPURasterizerState must always be + * set to true. Disabling optional features allows the application to run on + * some older Android devices. Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN`: + * Enable Vulkan device feature drawIndirectFirstInstance. If disabled, the + * argument first_instance of SDL_GPUIndirectDrawCommand must be set to + * zero. Disabling optional features allows the application to run on some + * older Android devices. Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN`: Enable Vulkan + * device feature samplerAnisotropy. If disabled, enable_anisotropy of + * SDL_GPUSamplerCreateInfo must be set to false. Disabling optional + * features allows the application to run on some older Android devices. + * Defaults to true. * * These are the current shader format properties: * @@ -2158,10 +2295,56 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( * - `SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN`: The app is able to * provide Metal shader libraries if applicable. * - * With the D3D12 renderer: + * With the D3D12 backend: * * - `SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING`: the prefix to * use for all vertex semantics, default is "TEXCOORD". + * - `SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN`: By + * default, Resourcing Binding Tier 2 is required for D3D12 support. + * However, an application can set this property to true to enable Tier 1 + * support, if (and only if) the application uses 8 or fewer storage + * resources across all shader stages. As of writing, this property is + * useful for targeting Intel Haswell and Broadwell GPUs; other hardware + * either supports Tier 2 Resource Binding or does not support D3D12 in any + * capacity. Defaults to false. + * - `SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_VERSION_NUMBER`: Certain + * feature checks are only possible on Windows 11 by default. By setting + * this alongside `SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_PATH_STRING` + * and vendoring D3D12Core.dll from the D3D12 Agility SDK, you can make + * those feature checks possible on older platforms. The version you provide + * must match the one given in the DLL. + * - `SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_PATH_STRING`: Certain + * feature checks are only possible on Windows 11 by default. By setting + * this alongside + * `SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_VERSION_NUMBER` and + * vendoring D3D12Core.dll from the D3D12 Agility SDK, you can make those + * feature checks possible on older platforms. The path you provide must be + * relative to the executable path of your app. Be sure not to put the DLL + * in the same directory as the exe; Microsoft strongly advises against + * this! + * + * With the Vulkan backend: + * + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_REQUIRE_HARDWARE_ACCELERATION_BOOLEAN`: + * By default, Vulkan device enumeration includes drivers of all types, + * including software renderers (for example, the Lavapipe Mesa driver). + * This can be useful if your application _requires_ SDL_GPU, but if you can + * provide your own fallback renderer (for example, an OpenGL renderer) this + * property can be set to true. Defaults to false. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER`: a pointer to an + * SDL_GPUVulkanOptions structure to be processed during device creation. + * This allows configuring a variety of Vulkan-specific options such as + * increasing the API version and opting into extensions aside from the + * minimal set SDL requires. + * + * With the Metal backend: - + * `SDL_PROP_GPU_DEVICE_CREATE_METAL_ALLOW_MACFAMILY1_BOOLEAN`: By default, + * macOS support requires what Apple calls "MTLGPUFamilyMac2" hardware or + * newer. However, an application can set this property to true to enable + * support for "MTLGPUFamilyMac1" hardware, if (and only if) the application + * does not write to sRGB textures. (For history's sake: MacFamily1 also does + * not support indirect command buffers, MSAA depth resolve, and stencil + * resolve/feedback, but these are not exposed features in SDL_GPU.) * * \param props the properties to use. * \returns a GPU context on success or NULL on failure; call SDL_GetError() @@ -2177,16 +2360,55 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDeviceWithProperties( SDL_PropertiesID props); -#define SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN "SDL.gpu.device.create.debugmode" -#define SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN "SDL.gpu.device.create.preferlowpower" -#define SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING "SDL.gpu.device.create.name" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOLEAN "SDL.gpu.device.create.shaders.private" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN "SDL.gpu.device.create.shaders.spirv" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN "SDL.gpu.device.create.shaders.dxbc" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN "SDL.gpu.device.create.shaders.dxil" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN "SDL.gpu.device.create.shaders.msl" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN "SDL.gpu.device.create.shaders.metallib" -#define SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING "SDL.gpu.device.create.d3d12.semantic" +#define SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN "SDL.gpu.device.create.debugmode" +#define SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN "SDL.gpu.device.create.preferlowpower" +#define SDL_PROP_GPU_DEVICE_CREATE_VERBOSE_BOOLEAN "SDL.gpu.device.create.verbose" +#define SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING "SDL.gpu.device.create.name" +#define SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN "SDL.gpu.device.create.feature.clip_distance" +#define SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN "SDL.gpu.device.create.feature.depth_clamping" +#define SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN "SDL.gpu.device.create.feature.indirect_draw_first_instance" +#define SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN "SDL.gpu.device.create.feature.anisotropy" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOLEAN "SDL.gpu.device.create.shaders.private" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN "SDL.gpu.device.create.shaders.spirv" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN "SDL.gpu.device.create.shaders.dxbc" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN "SDL.gpu.device.create.shaders.dxil" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN "SDL.gpu.device.create.shaders.msl" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN "SDL.gpu.device.create.shaders.metallib" +#define SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN "SDL.gpu.device.create.d3d12.allowtier1resourcebinding" +#define SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING "SDL.gpu.device.create.d3d12.semantic" +#define SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_VERSION_NUMBER "SDL.gpu.device.create.d3d12.agility_sdk_version" +#define SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_PATH_STRING "SDL.gpu.device.create.d3d12.agility_sdk_path" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_REQUIRE_HARDWARE_ACCELERATION_BOOLEAN "SDL.gpu.device.create.vulkan.requirehardwareacceleration" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER "SDL.gpu.device.create.vulkan.options" +#define SDL_PROP_GPU_DEVICE_CREATE_METAL_ALLOW_MACFAMILY1_BOOLEAN "SDL.gpu.device.create.metal.allowmacfamily1" + + +/** + * A structure specifying additional options when using Vulkan. + * + * When no such structure is provided, SDL will use Vulkan API version 1.0 and + * a minimal set of features. The requested API version influences how the + * feature_list is processed by SDL. When requesting API version 1.0, the + * feature_list is ignored. Only the vulkan_10_physical_device_features and + * the extension lists are used. When requesting API version 1.1, the + * feature_list is scanned for feature structures introduced in Vulkan 1.1. + * When requesting Vulkan 1.2 or higher, the feature_list is additionally + * scanned for compound feature structs such as + * VkPhysicalDeviceVulkan11Features. The device and instance extension lists, + * as well as vulkan_10_physical_device_features, are always processed. + * + * \since This struct is available since SDL 3.4.0. + */ +typedef struct SDL_GPUVulkanOptions +{ + Uint32 vulkan_api_version; /**< The Vulkan API version to request for the instance. Use Vulkan's VK_MAKE_VERSION or VK_MAKE_API_VERSION. */ + void *feature_list; /**< Pointer to the first element of a chain of Vulkan feature structs. (Requires API version 1.1 or higher.)*/ + void *vulkan_10_physical_device_features; /**< Pointer to a VkPhysicalDeviceFeatures struct to enable additional Vulkan 1.0 features. */ + Uint32 device_extension_count; /**< Number of additional device extensions to require. */ + const char **device_extension_names; /**< Pointer to a list of additional device extensions to require. */ + Uint32 instance_extension_count; /**< Number of additional instance extensions to require. */ + const char **instance_extension_names; /**< Pointer to a list of additional instance extensions to require. */ +} SDL_GPUVulkanOptions; /** * Destroys a GPU context previously returned by SDL_CreateGPUDevice. @@ -2250,6 +2472,116 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGPUDeviceDriver(SDL_GPUDevice *d */ extern SDL_DECLSPEC SDL_GPUShaderFormat SDLCALL SDL_GetGPUShaderFormats(SDL_GPUDevice *device); +/** + * Get the properties associated with a GPU device. + * + * All properties are optional and may differ between GPU backends and SDL + * versions. + * + * The following properties are provided by SDL: + * + * `SDL_PROP_GPU_DEVICE_NAME_STRING`: Contains the name of the underlying + * device as reported by the system driver. This string has no standardized + * format, is highly inconsistent between hardware devices and drivers, and is + * able to change at any time. Do not attempt to parse this string as it is + * bound to fail at some point in the future when system drivers are updated, + * new hardware devices are introduced, or when SDL adds new GPU backends or + * modifies existing ones. + * + * Strings that have been found in the wild include: + * + * - GTX 970 + * - GeForce GTX 970 + * - NVIDIA GeForce GTX 970 + * - Microsoft Direct3D12 (NVIDIA GeForce GTX 970) + * - NVIDIA Graphics Device + * - GeForce GPU + * - P106-100 + * - AMD 15D8:C9 + * - AMD Custom GPU 0405 + * - AMD Radeon (TM) Graphics + * - ASUS Radeon RX 470 Series + * - Intel(R) Arc(tm) A380 Graphics (DG2) + * - Virtio-GPU Venus (NVIDIA TITAN V) + * - SwiftShader Device (LLVM 16.0.0) + * - llvmpipe (LLVM 15.0.4, 256 bits) + * - Microsoft Basic Render Driver + * - unknown device + * + * The above list shows that the same device can have different formats, the + * vendor name may or may not appear in the string, the included vendor name + * may not be the vendor of the chipset on the device, some manufacturers + * include pseudo-legal marks while others don't, some devices may not use a + * marketing name in the string, the device string may be wrapped by the name + * of a translation interface, the device may be emulated in software, or the + * string may contain generic text that does not identify the device at all. + * + * `SDL_PROP_GPU_DEVICE_DRIVER_NAME_STRING`: Contains the self-reported name + * of the underlying system driver. + * + * Strings that have been found in the wild include: + * + * - Intel Corporation + * - Intel open-source Mesa driver + * - Qualcomm Technologies Inc. Adreno Vulkan Driver + * - MoltenVK + * - Mali-G715 + * - venus + * + * `SDL_PROP_GPU_DEVICE_DRIVER_VERSION_STRING`: Contains the self-reported + * version of the underlying system driver. This is a relatively short version + * string in an unspecified format. If SDL_PROP_GPU_DEVICE_DRIVER_INFO_STRING + * is available then that property should be preferred over this one as it may + * contain additional information that is useful for identifying the exact + * driver version used. + * + * Strings that have been found in the wild include: + * + * - 53.0.0 + * - 0.405.2463 + * - 32.0.15.6614 + * + * `SDL_PROP_GPU_DEVICE_DRIVER_INFO_STRING`: Contains the detailed version + * information of the underlying system driver as reported by the driver. This + * is an arbitrary string with no standardized format and it may contain + * newlines. This property should be preferred over + * SDL_PROP_GPU_DEVICE_DRIVER_VERSION_STRING if it is available as it usually + * contains the same information but in a format that is easier to read. + * + * Strings that have been found in the wild include: + * + * - 101.6559 + * - 1.2.11 + * - Mesa 21.2.2 (LLVM 12.0.1) + * - Mesa 22.2.0-devel (git-f226222 2022-04-14 impish-oibaf-ppa) + * - v1.r53p0-00eac0.824c4f31403fb1fbf8ee1042422c2129 + * + * This string has also been observed to be a multiline string (which has a + * trailing newline): + * + * ``` + * Driver Build: 85da404, I46ff5fc46f, 1606794520 + * Date: 11/30/20 + * Compiler Version: EV031.31.04.01 + * Driver Branch: promo490_3_Google + * ``` + * + * \param device a GPU context 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.4.0. + */ +extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetGPUDeviceProperties(SDL_GPUDevice *device); + +#define SDL_PROP_GPU_DEVICE_NAME_STRING "SDL.gpu.device.name" +#define SDL_PROP_GPU_DEVICE_DRIVER_NAME_STRING "SDL.gpu.device.driver_name" +#define SDL_PROP_GPU_DEVICE_DRIVER_VERSION_STRING "SDL.gpu.device.driver_version" +#define SDL_PROP_GPU_DEVICE_DRIVER_INFO_STRING "SDL.gpu.device.driver_info" + + /* State Creation */ /** @@ -2440,7 +2772,8 @@ extern SDL_DECLSPEC SDL_GPUShader * SDLCALL SDL_CreateGPUShader( * Creates a texture object to be used in graphics or compute workflows. * * The contents of this texture are undefined until data is written to the - * texture. + * texture, either via SDL_UploadToGPUTexture or by performing a render or + * compute pass with this texture as a target. * * Note that certain combinations of usage flags are invalid. For example, a * texture cannot have both the SAMPLER and GRAPHICS_STORAGE_READ flags. @@ -2482,6 +2815,8 @@ extern SDL_DECLSPEC SDL_GPUShader * SDLCALL SDL_CreateGPUShader( * * \sa SDL_UploadToGPUTexture * \sa SDL_DownloadFromGPUTexture + * \sa SDL_BeginGPURenderPass + * \sa SDL_BeginGPUComputePass * \sa SDL_BindGPUVertexSamplers * \sa SDL_BindGPUVertexStorageTextures * \sa SDL_BindGPUFragmentSamplers @@ -2638,6 +2973,12 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetGPUTextureName( * * Useful for debugging. * + * On Direct3D 12, using SDL_InsertGPUDebugLabel requires + * WinPixEventRuntime.dll to be in your PATH or in the same directory as your + * executable. See + * [here](https://devblogs.microsoft.com/pix/winpixeventruntime/) + * for instructions on how to obtain it. + * * \param command_buffer a command buffer. * \param text a UTF-8 string constant to insert as the label. * @@ -2648,7 +2989,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_InsertGPUDebugLabel( const char *text); /** - * Begins a debug group with an arbitary name. + * Begins a debug group with an arbitrary name. * * Used for denoting groups of calls when viewing the command buffer * callstream in a graphics debugging tool. @@ -2656,6 +2997,11 @@ extern SDL_DECLSPEC void SDLCALL SDL_InsertGPUDebugLabel( * Each call to SDL_PushGPUDebugGroup must have a corresponding call to * SDL_PopGPUDebugGroup. * + * On Direct3D 12, using SDL_PushGPUDebugGroup requires WinPixEventRuntime.dll + * to be in your PATH or in the same directory as your executable. See + * [here](https://devblogs.microsoft.com/pix/winpixeventruntime/) + * for instructions on how to obtain it. + * * On some backends (e.g. Metal), pushing a debug group during a * render/blit/compute pass will create a group that is scoped to the native * pass rather than the command buffer. For best results, if you push a debug @@ -2675,6 +3021,11 @@ extern SDL_DECLSPEC void SDLCALL SDL_PushGPUDebugGroup( /** * Ends the most-recently pushed debug group. * + * On Direct3D 12, using SDL_PopGPUDebugGroup requires WinPixEventRuntime.dll + * to be in your PATH or in the same directory as your executable. See + * [here](https://devblogs.microsoft.com/pix/winpixeventruntime/) + * for instructions on how to obtain it. + * * \param command_buffer a command buffer. * * \since This function is available since SDL 3.2.0. @@ -2816,12 +3167,15 @@ extern SDL_DECLSPEC SDL_GPUCommandBuffer * SDLCALL SDL_AcquireGPUCommandBuffer( /** * Pushes data to a vertex uniform slot on the command buffer. * - * Subsequent draw calls will use this uniform data. + * Subsequent draw calls in this command buffer will use this uniform data. * * The data being pushed must respect std140 layout conventions. In practical * terms this means you must ensure that vec3 and vec4 fields are 16-byte * aligned. * + * For detailed information about accessing uniform data from a shader, please + * refer to SDL_CreateGPUShader. + * * \param command_buffer a command buffer. * \param slot_index the vertex uniform slot to push data to. * \param data client data to write. @@ -2838,7 +3192,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_PushGPUVertexUniformData( /** * Pushes data to a fragment uniform slot on the command buffer. * - * Subsequent draw calls will use this uniform data. + * Subsequent draw calls in this command buffer will use this uniform data. * * The data being pushed must respect std140 layout conventions. In practical * terms this means you must ensure that vec3 and vec4 fields are 16-byte @@ -2860,7 +3214,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_PushGPUFragmentUniformData( /** * Pushes data to a uniform slot on the command buffer. * - * Subsequent draw calls will use this uniform data. + * Subsequent draw calls in this command buffer will use this uniform data. * * The data being pushed must respect std140 layout conventions. In practical * terms this means you must ensure that vec3 and vec4 fields are 16-byte @@ -2892,6 +3246,14 @@ extern SDL_DECLSPEC void SDLCALL SDL_PushGPUComputeUniformData( * is called. You cannot begin another render pass, or begin a compute pass or * copy pass until you have ended the render pass. * + * Using SDL_GPU_LOADOP_LOAD before any contents have been written to the + * texture subresource will result in undefined behavior. SDL_GPU_LOADOP_CLEAR + * will set the contents of the texture subresource to a single value before + * any rendering is performed. It's fine to do an empty render pass using + * SDL_GPU_STOREOP_STORE to clear a texture, but in general it's better to + * think of clearing not as an independent operation but as something that's + * done as the beginning of a render pass. + * * \param command_buffer a command buffer. * \param color_target_infos an array of texture subresources with * corresponding clear values and load/store ops. @@ -3338,7 +3700,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputePipeline( * The textures must have been created with SDL_GPU_TEXTUREUSAGE_SAMPLER. * * Be sure your shader is set up according to the requirements documented in - * SDL_CreateGPUShader(). + * SDL_CreateGPUComputePipeline(). * * \param compute_pass a compute pass handle. * \param first_slot the compute sampler slot to begin binding from. @@ -3349,7 +3711,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputePipeline( * * \since This function is available since SDL 3.2.0. * - * \sa SDL_CreateGPUShader + * \sa SDL_CreateGPUComputePipeline */ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputeSamplers( SDL_GPUComputePass *compute_pass, @@ -3364,7 +3726,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputeSamplers( * SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ. * * Be sure your shader is set up according to the requirements documented in - * SDL_CreateGPUShader(). + * SDL_CreateGPUComputePipeline(). * * \param compute_pass a compute pass handle. * \param first_slot the compute storage texture slot to begin binding from. @@ -3373,7 +3735,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputeSamplers( * * \since This function is available since SDL 3.2.0. * - * \sa SDL_CreateGPUShader + * \sa SDL_CreateGPUComputePipeline */ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputeStorageTextures( SDL_GPUComputePass *compute_pass, @@ -3388,7 +3750,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputeStorageTextures( * SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_READ. * * Be sure your shader is set up according to the requirements documented in - * SDL_CreateGPUShader(). + * SDL_CreateGPUComputePipeline(). * * \param compute_pass a compute pass handle. * \param first_slot the compute storage buffer slot to begin binding from. @@ -3397,7 +3759,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputeStorageTextures( * * \since This function is available since SDL 3.2.0. * - * \sa SDL_CreateGPUShader + * \sa SDL_CreateGPUComputePipeline */ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputeStorageBuffers( SDL_GPUComputePass *compute_pass, @@ -3514,6 +3876,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnmapGPUTransferBuffer( * \returns a copy pass handle. * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_EndGPUCopyPass */ extern SDL_DECLSPEC SDL_GPUCopyPass * SDLCALL SDL_BeginGPUCopyPass( SDL_GPUCommandBuffer *command_buffer); @@ -3567,6 +3931,10 @@ extern SDL_DECLSPEC void SDLCALL SDL_UploadToGPUBuffer( * This copy occurs on the GPU timeline. You may assume the copy has finished * in subsequent commands. * + * This function does not support copying between depth and color textures. + * For those, copy the texture to a buffer and then to the destination + * texture. + * * \param copy_pass a copy pass handle. * \param source a source texture region. * \param destination a destination texture region. @@ -3775,7 +4143,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_ReleaseWindowFromGPUDevice( * supported via SDL_WindowSupportsGPUPresentMode / * SDL_WindowSupportsGPUSwapchainComposition prior to calling this function. * - * SDL_GPU_PRESENTMODE_VSYNC with SDL_GPU_SWAPCHAINCOMPOSITION_SDR are always + * SDL_GPU_PRESENTMODE_VSYNC with SDL_GPU_SWAPCHAINCOMPOSITION_SDR is always * supported. * * \param device a GPU context. @@ -3849,7 +4217,9 @@ extern SDL_DECLSPEC SDL_GPUTextureFormat SDLCALL SDL_GetGPUSwapchainTextureForma * buffer used to acquire it. * * This function will fill the swapchain texture handle with NULL if too many - * frames are in flight. This is not an error. + * frames are in flight. This is not an error. This NULL pointer should not be + * passed back into SDL. Instead, it should be considered as an indication to + * wait until the swapchain is available. * * If you use this function, it is possible to create a situation where many * command buffers are allocated while the rendering context waits for the GPU @@ -4171,6 +4541,29 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_CalculateGPUTextureFormatSize( Uint32 height, Uint32 depth_or_layer_count); +/** + * Get the SDL pixel format corresponding to a GPU texture format. + * + * \param format a texture format. + * \returns the corresponding pixel format, or SDL_PIXELFORMAT_UNKNOWN if + * there is no corresponding pixel format. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC SDL_PixelFormat SDLCALL SDL_GetPixelFormatFromGPUTextureFormat(SDL_GPUTextureFormat format); + +/** + * Get the GPU texture format corresponding to an SDL pixel format. + * + * \param format a pixel format. + * \returns the corresponding GPU texture format, or + * SDL_GPU_TEXTUREFORMAT_INVALID if there is no corresponding GPU + * texture format. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC SDL_GPUTextureFormat SDLCALL SDL_GetGPUTextureFormatFromPixelFormat(SDL_PixelFormat format); + #ifdef SDL_PLATFORM_GDK /** diff --git a/libs/SDL3/include/SDL3/SDL_guid.h b/libs/SDL3/include/SDL3/SDL_guid.h index 312c42c..cf9df31 100644 --- a/libs/SDL3/include/SDL3/SDL_guid.h +++ b/libs/SDL3/include/SDL3/SDL_guid.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_haptic.h b/libs/SDL3/include/SDL3/SDL_haptic.h index a45335b..1cd9ddc 100644 --- a/libs/SDL3/include/SDL3/SDL_haptic.h +++ b/libs/SDL3/include/SDL3/SDL_haptic.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -70,7 +70,7 @@ * { * SDL_Haptic *haptic; * SDL_HapticEffect effect; - * int effect_id; + * SDL_HapticEffectID effect_id; * * // Open the device * haptic = SDL_OpenHapticFromJoystick(joystick); @@ -149,6 +149,19 @@ extern "C" { */ typedef struct SDL_Haptic SDL_Haptic; +/* + * Misc defines. + */ + +/** + * Used to play a device an infinite number of times. + * + * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_RunHapticEffect + */ +#define SDL_HAPTIC_INFINITY 4294967295U + /** * \name Haptic features @@ -162,6 +175,11 @@ typedef struct SDL_Haptic SDL_Haptic; */ /* @{ */ +/** + * Type of haptic effect. + */ +typedef Uint16 SDL_HapticEffectType; + /** * Constant effect supported. * @@ -383,6 +401,11 @@ typedef struct SDL_Haptic SDL_Haptic; */ /* @{ */ +/** + * Type of coordinates used for haptic direction. + */ +typedef Uint8 SDL_HapticDirectionType; + /** * Uses polar coordinates for the direction. * @@ -426,18 +449,15 @@ typedef struct SDL_Haptic SDL_Haptic; /* @} *//* Haptic features */ -/* - * Misc defines. - */ /** - * Used to play a device an infinite number of times. + * ID for haptic effects. * - * \since This macro is available since SDL 3.2.0. + * This is -1 if the ID is invalid. * - * \sa SDL_RunHapticEffect + * \sa SDL_CreateHapticEffect */ -#define SDL_HAPTIC_INFINITY 4294967295U +typedef int SDL_HapticEffectID; /** @@ -545,8 +565,8 @@ typedef struct SDL_Haptic SDL_Haptic; */ typedef struct SDL_HapticDirection { - Uint8 type; /**< The type of encoding. */ - Sint32 dir[3]; /**< The encoded direction. */ + SDL_HapticDirectionType type; /**< The type of encoding. */ + Sint32 dir[3]; /**< The encoded direction. */ } SDL_HapticDirection; @@ -566,7 +586,7 @@ typedef struct SDL_HapticDirection typedef struct SDL_HapticConstant { /* Header */ - Uint16 type; /**< SDL_HAPTIC_CONSTANT */ + SDL_HapticEffectType type; /**< SDL_HAPTIC_CONSTANT */ SDL_HapticDirection direction; /**< Direction of the effect. */ /* Replay */ @@ -652,9 +672,9 @@ typedef struct SDL_HapticConstant typedef struct SDL_HapticPeriodic { /* Header */ - Uint16 type; /**< SDL_HAPTIC_SINE, SDL_HAPTIC_SQUARE - SDL_HAPTIC_TRIANGLE, SDL_HAPTIC_SAWTOOTHUP or - SDL_HAPTIC_SAWTOOTHDOWN */ + SDL_HapticEffectType type; /**< SDL_HAPTIC_SINE, SDL_HAPTIC_SQUARE + SDL_HAPTIC_TRIANGLE, SDL_HAPTIC_SAWTOOTHUP or + SDL_HAPTIC_SAWTOOTHDOWN */ SDL_HapticDirection direction; /**< Direction of the effect. */ /* Replay */ @@ -708,8 +728,8 @@ typedef struct SDL_HapticPeriodic typedef struct SDL_HapticCondition { /* Header */ - Uint16 type; /**< SDL_HAPTIC_SPRING, SDL_HAPTIC_DAMPER, - SDL_HAPTIC_INERTIA or SDL_HAPTIC_FRICTION */ + SDL_HapticEffectType type; /**< SDL_HAPTIC_SPRING, SDL_HAPTIC_DAMPER, + SDL_HAPTIC_INERTIA or SDL_HAPTIC_FRICTION */ SDL_HapticDirection direction; /**< Direction of the effect. */ /* Replay */ @@ -747,7 +767,7 @@ typedef struct SDL_HapticCondition typedef struct SDL_HapticRamp { /* Header */ - Uint16 type; /**< SDL_HAPTIC_RAMP */ + SDL_HapticEffectType type; /**< SDL_HAPTIC_RAMP */ SDL_HapticDirection direction; /**< Direction of the effect. */ /* Replay */ @@ -786,7 +806,7 @@ typedef struct SDL_HapticRamp typedef struct SDL_HapticLeftRight { /* Header */ - Uint16 type; /**< SDL_HAPTIC_LEFTRIGHT */ + SDL_HapticEffectType type; /**< SDL_HAPTIC_LEFTRIGHT */ /* Replay */ Uint32 length; /**< Duration of the effect in milliseconds. */ @@ -816,7 +836,7 @@ typedef struct SDL_HapticLeftRight typedef struct SDL_HapticCustom { /* Header */ - Uint16 type; /**< SDL_HAPTIC_CUSTOM */ + SDL_HapticEffectType type; /**< SDL_HAPTIC_CUSTOM */ SDL_HapticDirection direction; /**< Direction of the effect. */ /* Replay */ @@ -915,7 +935,7 @@ typedef struct SDL_HapticCustom typedef union SDL_HapticEffect { /* Common for all force feedback effects */ - Uint16 type; /**< Effect type. */ + SDL_HapticEffectType type; /**< Effect type. */ SDL_HapticConstant constant; /**< Constant effect. */ SDL_HapticPeriodic periodic; /**< Periodic effect. */ SDL_HapticCondition condition; /**< Condition effect. */ @@ -1193,7 +1213,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HapticEffectSupported(SDL_Haptic *haptic, c * \sa SDL_RunHapticEffect * \sa SDL_UpdateHapticEffect */ -extern SDL_DECLSPEC int SDLCALL SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect); +extern SDL_DECLSPEC SDL_HapticEffectID SDLCALL SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect); /** * Update the properties of an effect. @@ -1215,7 +1235,7 @@ extern SDL_DECLSPEC int SDLCALL SDL_CreateHapticEffect(SDL_Haptic *haptic, const * \sa SDL_CreateHapticEffect * \sa SDL_RunHapticEffect */ -extern SDL_DECLSPEC bool SDLCALL SDL_UpdateHapticEffect(SDL_Haptic *haptic, int effect, const SDL_HapticEffect *data); +extern SDL_DECLSPEC bool SDLCALL SDL_UpdateHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect, const SDL_HapticEffect *data); /** * Run the haptic effect on its associated haptic device. @@ -1239,7 +1259,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_UpdateHapticEffect(SDL_Haptic *haptic, int * \sa SDL_StopHapticEffect * \sa SDL_StopHapticEffects */ -extern SDL_DECLSPEC bool SDLCALL SDL_RunHapticEffect(SDL_Haptic *haptic, int effect, Uint32 iterations); +extern SDL_DECLSPEC bool SDLCALL SDL_RunHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect, Uint32 iterations); /** * Stop the haptic effect on its associated haptic device. @@ -1254,7 +1274,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RunHapticEffect(SDL_Haptic *haptic, int eff * \sa SDL_RunHapticEffect * \sa SDL_StopHapticEffects */ -extern SDL_DECLSPEC bool SDLCALL SDL_StopHapticEffect(SDL_Haptic *haptic, int effect); +extern SDL_DECLSPEC bool SDLCALL SDL_StopHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect); /** * Destroy a haptic effect on the device. @@ -1269,7 +1289,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_StopHapticEffect(SDL_Haptic *haptic, int ef * * \sa SDL_CreateHapticEffect */ -extern SDL_DECLSPEC void SDLCALL SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect); +extern SDL_DECLSPEC void SDLCALL SDL_DestroyHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect); /** * Get the status of the current effect on the specified haptic device. @@ -1285,7 +1305,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroyHapticEffect(SDL_Haptic *haptic, int * * \sa SDL_GetHapticFeatures */ -extern SDL_DECLSPEC bool SDLCALL SDL_GetHapticEffectStatus(SDL_Haptic *haptic, int effect); +extern SDL_DECLSPEC bool SDLCALL SDL_GetHapticEffectStatus(SDL_Haptic *haptic, SDL_HapticEffectID effect); /** * Set the global gain of the specified haptic device. diff --git a/libs/SDL3/include/SDL3/SDL_hidapi.h b/libs/SDL3/include/SDL3/SDL_hidapi.h index 131b037..90e574c 100644 --- a/libs/SDL3/include/SDL3/SDL_hidapi.h +++ b/libs/SDL3/include/SDL3/SDL_hidapi.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -55,6 +55,7 @@ #include #include +#include #include /* Set up for C function definitions, even when using C++ */ @@ -283,6 +284,24 @@ extern SDL_DECLSPEC SDL_hid_device * SDLCALL SDL_hid_open(unsigned short vendor_ */ extern SDL_DECLSPEC SDL_hid_device * SDLCALL SDL_hid_open_path(const char *path); +/** + * Get the properties associated with an SDL_hid_device. + * + * The following read-only properties are provided by SDL: + * + * - `SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER`: the libusb_device_handle + * associated with the device, if it was opened using libusb. + * + * \param dev a device handle returned from SDL_hid_open(). + * \returns a valid property ID on success or 0 on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_hid_get_properties(SDL_hid_device *dev); + +#define SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER "SDL.hidapi.libusb.device.handle" + /** * Write an Output report to a HID device. * diff --git a/libs/SDL3/include/SDL3/SDL_hints.h b/libs/SDL3/include/SDL3/SDL_hints.h index a081535..857dfce 100644 --- a/libs/SDL3/include/SDL3/SDL_hints.h +++ b/libs/SDL3/include/SDL3/SDL_hints.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -391,12 +391,46 @@ extern "C" { * concept, so it applies to a physical audio device in this case, and not an * SDL_AudioStream, nor an SDL logical audio device. * + * For Windows WASAPI audio, the following roles are supported, and map to + * `AUDIO_STREAM_CATEGORY`: + * + * - "Other" (default) + * - "Communications" - Real-time communications, such as VOIP or chat + * - "Game" - Game audio + * - "GameChat" - Game chat audio, similar to "Communications" except that + * this will not attenuate other audio streams + * - "Movie" - Music or sound with dialog + * - "Media" - Music or sound without dialog + * + * If your application applies its own echo cancellation, gain control, and + * noise reduction it should also set SDL_HINT_AUDIO_DEVICE_RAW_STREAM. + * * This hint should be set before an audio device is opened. * * \since This hint is available since SDL 3.2.0. */ #define SDL_HINT_AUDIO_DEVICE_STREAM_ROLE "SDL_AUDIO_DEVICE_STREAM_ROLE" +/** + * Specify whether this audio device should do audio processing. + * + * Some operating systems perform echo cancellation, gain control, and noise + * reduction as needed. If your application already handles these, you can set + * this hint to prevent the OS from doing additional audio processing. + * + * This corresponds to the WASAPI audio option `AUDCLNT_STREAMOPTIONS_RAW`. + * + * The variable can be set to the following values: + * + * - "0": audio processing can be done by the OS. (default) + * - "1": audio processing is done by the application. + * + * This hint should be set before an audio device is opened. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_AUDIO_DEVICE_RAW_STREAM "SDL_AUDIO_DEVICE_RAW_STREAM" + /** * Specify the input file when recording audio using the disk audio driver. * @@ -595,7 +629,7 @@ extern "C" { * A variable that limits what CPU features are available. * * By default, SDL marks all features the current CPU supports as available. - * This hint allows to limit these to a subset. + * This hint allows the enabled features to be limited to a subset. * * When the hint is unset, or empty, SDL will enable all detected CPU * features. @@ -685,6 +719,21 @@ extern "C" { */ #define SDL_HINT_DISPLAY_USABLE_BOUNDS "SDL_DISPLAY_USABLE_BOUNDS" +/** + * Set the level of checking for invalid parameters passed to SDL functions. + * + * The variable can be set to the following values: + * + * - "1": Enable fast parameter error checking, e.g. quick NULL checks, etc. + * - "2": Enable full parameter error checking, e.g. validating objects are + * the correct type, etc. (default) + * + * This hint can be set anytime. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_INVALID_PARAM_CHECKS "SDL_INVALID_PARAM_CHECKS" + /** * Disable giving back control to the browser automatically when running with * asyncify. @@ -711,8 +760,6 @@ extern "C" { * * This hint only applies to the emscripten platform. * - * The default value is "#canvas" - * * This hint should be set before creating a window. * * \since This hint is available since SDL 3.2.0. @@ -726,7 +773,7 @@ extern "C" { * * The variable can be one of: * - * - "#window": the javascript window object (default) + * - "#window": the javascript window object * - "#document": the javascript document object * - "#screen": the javascript window.screen object * - "#canvas": the WebGL canvas element @@ -1037,6 +1084,21 @@ extern "C" { */ #define SDL_HINT_HIDAPI_LIBUSB "SDL_HIDAPI_LIBUSB" + +/** + * A variable to control whether HIDAPI uses libusb for GameCube adapters. + * + * The variable can be set to the following values: + * + * - "0": HIDAPI will not use libusb for GameCube adapters. + * - "1": HIDAPI will use libusb for GameCube adapters if available. (default) + * + * This hint should be set before SDL is initialized. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_HIDAPI_LIBUSB_GAMECUBE "SDL_HIDAPI_LIBUSB_GAMECUBE" + /** * A variable to control whether HIDAPI uses libusb only for whitelisted * devices. @@ -1143,8 +1205,8 @@ extern "C" { #define SDL_HINT_IME_IMPLEMENTED_UI "SDL_IME_IMPLEMENTED_UI" /** - * A variable controlling whether the home indicator bar on iPhone X should be - * hidden. + * A variable controlling whether the home indicator bar on iPhone X and later + * should be hidden. * * The variable can be set to the following values: * @@ -1716,13 +1778,106 @@ extern "C" { * A variable controlling whether the HIDAPI driver for HORI licensed Steam * controllers should be used. * - * This variable can be set to the following values: "0" - HIDAPI driver is - * not used "1" - HIDAPI driver is used + * The variable can be set to the following values: * - * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.2.0. */ #define SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI "SDL_JOYSTICK_HIDAPI_STEAM_HORI" +/** + * A variable controlling whether the HIDAPI driver for some Logitech wheels + * should be used. + * + * The variable can be set to the following values: + * + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_LG4FF "SDL_JOYSTICK_HIDAPI_LG4FF" + +/** + * A variable controlling whether the HIDAPI driver for 8BitDo controllers + * should be used. + * + * The variable can be set to the following values: + * + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_8BITDO "SDL_JOYSTICK_HIDAPI_8BITDO" + +/** + * A variable controlling whether the HIDAPI driver for SInput controllers + * should be used. + * + * More info - https://github.com/HandHeldLegend/SInput-HID + * + * The variable can be set to the following values: + * + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_SINPUT "SDL_JOYSTICK_HIDAPI_SINPUT" + +/** + * A variable controlling whether the HIDAPI driver for ZUIKI controllers + * should be used. + * + * The variable can be set to the following values: + * + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_ZUIKI "SDL_JOYSTICK_HIDAPI_ZUIKI" + +/** + * A variable controlling whether the HIDAPI driver for Flydigi controllers + * should be used. + * + * The variable can be set to the following values: + * + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI "SDL_JOYSTICK_HIDAPI_FLYDIGI" + /** * A variable controlling whether the HIDAPI driver for Nintendo Switch * controllers should be used. @@ -1774,6 +1929,23 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED "SDL_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED" +/** + * A variable controlling whether the HIDAPI driver for Nintendo Switch 2 + * controllers should be used. + * + * The variable can be set to the following values: + * + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH2 "SDL_JOYSTICK_HIDAPI_SWITCH2" + /** * A variable controlling whether Nintendo Switch Joy-Con controllers will be * in vertical mode when using the HIDAPI driver. @@ -1926,6 +2098,41 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED "SDL_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED" +/** + * A variable controlling whether the new HIDAPI driver for wired Xbox One + * (GIP) controllers should be used. + * + * The variable can be set to the following values: + * + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_GIP "SDL_JOYSTICK_HIDAPI_GIP" + +/** + * A variable controlling whether the new HIDAPI driver for wired Xbox One + * (GIP) controllers should reset the controller if it can't get the metadata + * from the controller. + * + * The variable can be set to the following values: + * + * - "0": Assume this is a generic controller. + * - "1": Reset the controller to get metadata. + * + * By default the controller is not reset. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA "SDL_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA" + /** * A variable controlling whether IOKit should be used for controller * handling. @@ -2126,8 +2333,8 @@ extern "C" { * * The variable can be set to the following values: * - * - "0": WGI is not used. - * - "1": WGI is used. (default) + * - "0": WGI is not used. (default) + * - "1": WGI is used. * * This hint should be set before SDL is initialized. * @@ -2228,9 +2435,9 @@ extern "C" { * pressing the 1 key would yield the keycode SDLK_1, or '1', instead of * SDLK_AMPERSAND, or '&' * - "latin_letters": For keyboards using non-Latin letters, such as Russian - * or Thai, the letter keys generate keycodes as though it had an en_US - * layout. e.g. pressing the key associated with SDL_SCANCODE_A on a Russian - * keyboard would yield 'a' instead of a Cyrillic letter. + * or Thai, the letter keys generate keycodes as though it had an English + * QWERTY layout. e.g. pressing the key associated with SDL_SCANCODE_A on a + * Russian keyboard would yield 'a' instead of a Cyrillic letter. * * The default value for this hint is "french_numbers,latin_letters" * @@ -2289,6 +2496,27 @@ extern "C" { */ #define SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER "SDL_KMSDRM_REQUIRE_DRM_MASTER" +/** + * A variable that controls whether KMSDRM will use "atomic" functionality. + * + * The KMSDRM backend can use atomic commits, if both DRM_CLIENT_CAP_ATOMIC + * and DRM_CLIENT_CAP_UNIVERSAL_PLANES is supported by the system. As of SDL + * 3.4.0, it will favor this functionality, but in case this doesn't work well + * on a given system or other surprises, this hint can be used to disable it. + * + * This hint can not enable the functionality if it isn't available. + * + * The variable can be set to the following values: + * + * - "0": SDL will not use the KMSDRM "atomic" functionality. + * - "1": SDL will allow usage of the KMSDRM "atomic" functionality. (default) + * + * This hint should be set before SDL is initialized. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_KMSDRM_ATOMIC "SDL_KMSDRM_ATOMIC" + /** * A variable controlling the default SDL log levels. * @@ -2309,6 +2537,11 @@ extern "C" { * * `app=info,assert=warn,test=verbose,*=error` * + * If the `DEBUG_INVOCATION` environment variable is set to "1", the default + * log levels are equivalent to: + * + * `assert=warn,test=verbose,*=debug` + * * This hint can be set anytime. * * \since This hint is available since SDL 3.2.0. @@ -2410,6 +2643,21 @@ extern "C" { */ #define SDL_HINT_MAC_SCROLL_MOMENTUM "SDL_MAC_SCROLL_MOMENTUM" +/** + * A variable controlling whether holding down a key will repeat the pressed + * key or open the accents menu on macOS. + * + * The variable can be set to the following values: + * + * - "0": Holding a key will repeat the pressed key. + * - "1": Holding a key will open the accents menu for that key. (default) + * + * This hint needs to be set before SDL_Init(). + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_MAC_PRESS_AND_HOLD "SDL_MAC_PRESS_AND_HOLD" + /** * Request SDL_AppIterate() be called at a specific rate. * @@ -2430,6 +2678,10 @@ extern "C" { * This defaults to 0, and specifying NULL for the hint's value will restore * the default. * + * This doesn't have to be an integer value. For example, "59.94" won't be + * rounded to an integer rate; the digits after the decimal are actually + * respected. + * * This hint can be set anytime. * * \since This hint is available since SDL 3.2.0. @@ -2485,6 +2737,24 @@ extern "C" { */ #define SDL_HINT_MOUSE_DEFAULT_SYSTEM_CURSOR "SDL_MOUSE_DEFAULT_SYSTEM_CURSOR" +/** + * A variable setting whether we should scale cursors by the current display + * scale. + * + * The variable can be set to the following values: + * + * - "0": Cursors will not change size based on the display content scale. + * (default) + * - "1": Cursors will automatically match the display content scale (e.g. a + * 2x sized cursor will be used when the window is on a monitor with 200% + * scale). This is currently implemented on Windows and Wayland. + * + * This hint needs to be set before creating cursors. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_MOUSE_DPI_SCALE_CURSORS "SDL_MOUSE_DPI_SCALE_CURSORS" + /** * A variable controlling whether warping a hidden mouse cursor will activate * relative mouse mode. @@ -2493,7 +2763,7 @@ extern "C" { * the window center occur within a short time period, SDL will emulate mouse * warps using relative mouse mode. This can provide smoother and more * reliable mouse motion for some older games, which continuously calculate - * the distance travelled by the mouse pointer and warp it back to the center + * the distance traveled by the mouse pointer and warp it back to the center * of the window, rather than using relative mouse motion. * * Note that relative mouse mode may have different mouse acceleration @@ -2736,6 +3006,46 @@ extern "C" { */ #define SDL_HINT_OPENGL_ES_DRIVER "SDL_OPENGL_ES_DRIVER" +/** + * A variable controlling whether to force an sRGB-capable OpenGL context. + * + * At OpenGL context creation time, some platforms can request an sRGB-capable + * context. However, sometimes any form of the request can cause surprising + * results on some drivers, platforms, and hardware. Usually the surprise is + * in the form of rendering that is either a little darker or a little + * brighter than intended. + * + * This hint allows the user to override the app's sRGB requests and either + * force a specific value, or avoid requesting anything at all, depending on + * what makes things work correctly for their system. + * + * This is meant as a fail-safe; apps should probably not explicitly set this, + * and most users should not, either. + * + * Note that some platforms cannot make this request at all, and on all + * platforms this request can be denied by the operating system. + * + * In addition to attempting to obtain the type of sRGB-capable OpenGL context + * requested by this hint, SDL will try to force the state of + * GL_FRAMEBUFFER_SRGB on the new context, if appropriate. + * + * The variable can be set to the following values: + * + * - "0": Force a request for an OpenGL context that is _not_ sRGB-capable. + * - "1": Force a request for an OpenGL context that _is_ sRGB-capable. + * - "skip": Don't make any request for an sRGB-capable context + * (don't specify the attribute at all during context creation time). + * - any other string is undefined behavior. + * + * If unset, or set to an empty string, SDL will make a request using the + * value the app specified with the SDL_GL_FRAMEBUFFER_SRGB_CAPABLE attribute. + * + * This hint should be set before an OpenGL context is created. + * + * \since This hint is available since SDL 3.4.2. + */ +#define SDL_HINT_OPENGL_FORCE_SRGB_FRAMEBUFFER "SDL_OPENGL_FORCE_SRGB_FRAMEBUFFER" + /** * Mechanism to specify openvr_api library location * @@ -2859,6 +3169,24 @@ extern "C" { */ #define SDL_HINT_RENDER_DIRECT3D11_DEBUG "SDL_RENDER_DIRECT3D11_DEBUG" +/** + * A variable controlling whether to use the Direct3D 11 WARP software + * rasterizer. + * + * For more information, see: + * https://learn.microsoft.com/en-us/windows/win32/direct3darticles/directx-warp + * + * The variable can be set to the following values: + * + * - "0": Disable WARP rasterizer. (default) + * - "1": Enable WARP rasterizer. + * + * This hint should be set before creating a renderer. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_RENDER_DIRECT3D11_WARP "SDL_RENDER_DIRECT3D11_WARP" + /** * A variable controlling whether to enable Vulkan Validation Layers. * @@ -3041,6 +3369,49 @@ extern "C" { */ #define SDL_HINT_ROG_GAMEPAD_MICE_EXCLUDED "SDL_ROG_GAMEPAD_MICE_EXCLUDED" +/** + * A variable controlling the width of the PS2's framebuffer in pixels. + * + * By default, the variable is "640". + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_PS2_GS_WIDTH "SDL_PS2_GS_WIDTH" + +/** + * A variable controlling the height of the PS2's framebuffer in pixels. + * + * By default, the variable is "448". + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_PS2_GS_HEIGHT "SDL_PS2_GS_HEIGHT" + +/** + * A variable controlling whether the signal is interlaced or progressive. + * + * The variable can be set to the following values: + * + * - "0": Image is interlaced. (default) + * - "1": Image is progressive. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_PS2_GS_PROGRESSIVE "SDL_PS2_GS_PROGRESSIVE" + +/** + * A variable controlling the video mode of the console. + * + * The variable can be set to the following values: + * + * - "": Console-native. (default) + * - "NTSC": 60hz region. + * - "PAL": 50hz region. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_PS2_GS_MODE "SDL_PS2_GS_MODE" + /** * A variable controlling which Dispmanx layer to use on a Raspberry PI. * @@ -3267,10 +3638,12 @@ extern "C" { * prioritized in the list of displays, as exposed by calling * SDL_GetDisplays(), with the first listed becoming the primary display. The * naming convention can vary depending on the environment, but it is usually - * a connector name (e.g. 'DP-1', 'DP-2', 'HDMI-A-1',etc...). + * a connector name (e.g. 'DP-1', 'DP-2', 'HDMI-A-1', etc...). * - * On Wayland and X11 desktops, the connector names associated with displays - * can typically be found by using the `xrandr` utility. + * On Wayland desktops, the connector names associated with displays can be + * found in the `name` property of the info output from `wayland-info -i + * wl_output`. On X11 desktops, the `xrandr` utility can be used to retrieve + * the connector names associated with displays. * * This hint is currently supported on the following drivers: * @@ -3407,6 +3780,43 @@ extern "C" { */ #define SDL_HINT_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY "SDL_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY" +/** + * A variable indicating whether the metal layer drawable size should be + * updated for the SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED event on macOS. + * + * The variable can be set to the following values: + * + * - "0": the metal layer drawable size will not be updated on the + * SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED event. + * - "1": the metal layer drawable size will be updated on the + * SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED event. (default) + * + * This hint should be set before SDL_Metal_CreateView called. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_VIDEO_METAL_AUTO_RESIZE_DRAWABLE "SDL_VIDEO_METAL_AUTO_RESIZE_DRAWABLE" + +/** + * A variable controlling whether SDL will attempt to automatically set the + * destination display to a mode most closely matching that of the previous + * display if an exclusive fullscreen window is moved onto it. + * + * The variable can be set to the following values: + * + * - "0": SDL will not attempt to automatically set a matching mode on the + * destination display. If an exclusive fullscreen window is moved to a new + * display, the window will become fullscreen desktop. + * - "1": SDL will attempt to automatically set a mode on the destination + * display that most closely matches the mode of the display that the + * exclusive fullscreen window was previously on. (default) + * + * This hint can be set anytime. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_VIDEO_MATCH_EXCLUSIVE_MODE_ON_MOVE "SDL_VIDEO_MATCH_EXCLUSIVE_MODE_ON_MOVE" + /** * A variable controlling whether fullscreen windows are minimized when they * lose focus. @@ -3414,8 +3824,10 @@ extern "C" { * The variable can be set to the following values: * * - "0": Fullscreen windows will not be minimized when they lose focus. - * (default) * - "1": Fullscreen windows are minimized when they lose focus. + * - "auto": Fullscreen windows are minimized when they lose focus if they use + * exclusive fullscreen modes, so the desktop video mode is restored. + * (default) * * This hint can be set anytime. * @@ -4061,15 +4473,14 @@ extern "C" { * * The variable can be set to the following values: * - * - "0": GameInput is not used for raw keyboard and mouse events. + * - "0": GameInput is not used for raw keyboard and mouse events. (default) * - "1": GameInput is used for raw keyboard and mouse events, if available. - * (default) * * This hint should be set before SDL is initialized. * * \since This hint is available since SDL 3.2.0. */ -#define SDL_HINT_WINDOWS_GAMEINPUT "SDL_WINDOWS_GAMEINPUT" +#define SDL_HINT_WINDOWS_GAMEINPUT "SDL_WINDOWS_GAMEINPUT" /** * A variable controlling whether raw keyboard events are used on Windows. @@ -4085,6 +4496,28 @@ extern "C" { */ #define SDL_HINT_WINDOWS_RAW_KEYBOARD "SDL_WINDOWS_RAW_KEYBOARD" +/** + * A variable controlling whether or not the RIDEV_NOHOTKEYS flag is set when + * enabling Windows raw keyboard events. + * + * This blocks any hotkeys that have been registered by applications from + * having any effect beyond generating raw WM_INPUT events. + * + * This flag does not affect system-hotkeys like ALT-TAB or CTRL-ALT-DEL, but + * does affect the Windows Logo key since it is a userland hotkey registered + * by explorer.exe. + * + * The variable can be set to the following values: + * + * - "0": Hotkeys are not excluded. (default) + * - "1": Hotkeys are excluded. + * + * This hint can be set anytime. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_WINDOWS_RAW_KEYBOARD_EXCLUDE_HOTKEYS "SDL_WINDOWS_RAW_KEYBOARD_EXCLUDE_HOTKEYS" + /** * A variable controlling whether SDL uses Kernel Semaphores on Windows. * @@ -4114,7 +4547,7 @@ extern "C" { * * \since This hint is available since SDL 3.2.0. */ -#define SDL_HINT_WINDOWS_INTRESOURCE_ICON "SDL_WINDOWS_INTRESOURCE_ICON" +#define SDL_HINT_WINDOWS_INTRESOURCE_ICON "SDL_WINDOWS_INTRESOURCE_ICON" /** * A variable to specify custom icon resource id from RC file on Windows @@ -4287,7 +4720,6 @@ extern "C" { */ #define SDL_HINT_PEN_TOUCH_EVENTS "SDL_PEN_TOUCH_EVENTS" - /** * An enumeration of hint priorities. * @@ -4386,19 +4818,14 @@ extern SDL_DECLSPEC void SDLCALL SDL_ResetHints(void); * \param name the hint to query. * \returns the string value of a hint or NULL if the hint isn't set. * - * \threadsafety It is safe to call this function from any thread, however the - * return value only remains valid until the hint is changed; if - * another thread might do so, the app should supply locks - * and/or make a copy of the string. Note that using a hint - * callback instead is always thread-safe, as SDL holds a lock - * on the thread subsystem during the callback. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetHint * \sa SDL_SetHintWithPriority */ -extern SDL_DECLSPEC const char * SDLCALL SDL_GetHint(const char *name); +extern SDL_DECLSPEC const char *SDLCALL SDL_GetHint(const char *name); /** * Get the boolean value of a hint variable. @@ -4474,8 +4901,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_AddHintCallback(const char *name, SDL_HintC * \sa SDL_AddHintCallback */ extern SDL_DECLSPEC void SDLCALL SDL_RemoveHintCallback(const char *name, - SDL_HintCallback callback, - void *userdata); + SDL_HintCallback callback, + void *userdata); /* Ends C function definitions when using C++ */ #ifdef __cplusplus diff --git a/libs/SDL3/include/SDL3/SDL_init.h b/libs/SDL3/include/SDL3/SDL_init.h index 27ebe4b..d75ccc3 100644 --- a/libs/SDL3/include/SDL3/SDL_init.h +++ b/libs/SDL3/include/SDL3/SDL_init.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -101,7 +101,7 @@ typedef Uint32 SDL_InitFlags; * to run. * * See - * [Main callbacks in SDL3](https://wiki.libsdl.org/SDL3/README/main-functions#main-callbacks-in-sdl3) + * [Main callbacks in SDL3](https://wiki.libsdl.org/SDL3/README-main-functions#main-callbacks-in-sdl3) * for complete details. * * \since This enum is available since SDL 3.2.0. @@ -224,6 +224,8 @@ typedef void (SDLCALL *SDL_AppQuit_func)(void *appstate, SDL_AppResult result); * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetAppMetadata @@ -244,6 +246,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_Init(SDL_InitFlags flags); * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_Init @@ -260,6 +264,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_InitSubSystem(SDL_InitFlags flags); * * \param flags any of the flags used by SDL_Init(); see SDL_Init for details. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_InitSubSystem @@ -274,6 +280,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_QuitSubSystem(SDL_InitFlags flags); * \returns a mask of all initialized subsystems if `flags` is 0, otherwise it * returns the initialization status of the specified subsystems. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_Init @@ -292,6 +300,8 @@ extern SDL_DECLSPEC SDL_InitFlags SDLCALL SDL_WasInit(SDL_InitFlags flags); * application is shutdown, but it is not wise to do this from a library or * other dynamically loaded code. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_Init diff --git a/libs/SDL3/include/SDL3/SDL_intrin.h b/libs/SDL3/include/SDL3/SDL_intrin.h index bac6d7a..59a3831 100644 --- a/libs/SDL3/include/SDL3/SDL_intrin.h +++ b/libs/SDL3/include/SDL3/SDL_intrin.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -268,6 +268,7 @@ _m_prefetch(void *__P) #endif /* compiler version */ #ifdef SDL_WIKI_DOCUMENTATION_SECTION + /** * A macro to decide if the compiler supports `__attribute__((target))`. * @@ -280,12 +281,14 @@ _m_prefetch(void *__P) * \sa SDL_TARGETING */ #define SDL_HAS_TARGET_ATTRIBS - +#elif defined(__loongarch64) && defined(__GNUC__) && (__GNUC__ >= 15) +/* LoongArch requires GCC 15+ for target attribute support */ +# define SDL_HAS_TARGET_ATTRIBS #elif defined(__clang__) && defined(__has_attribute) # if __has_attribute(target) # define SDL_HAS_TARGET_ATTRIBS # endif -#elif defined(__GNUC__) && (__GNUC__ + (__GNUC_MINOR__ >= 9) > 4) /* gcc >= 4.9 */ +#elif defined(__GNUC__) && !defined(__loongarch64) && (__GNUC__ + (__GNUC_MINOR__ >= 9) > 4) /* gcc >= 4.9 */ # define SDL_HAS_TARGET_ATTRIBS #elif defined(__ICC) && __ICC >= 1600 # define SDL_HAS_TARGET_ATTRIBS diff --git a/libs/SDL3/include/SDL3/SDL_iostream.h b/libs/SDL3/include/SDL3/SDL_iostream.h index 4ca1609..f369fde 100644 --- a/libs/SDL3/include/SDL3/SDL_iostream.h +++ b/libs/SDL3/include/SDL3/SDL_iostream.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -111,7 +111,7 @@ typedef struct SDL_IOStreamInterface /** * Read up to `size` bytes from the data stream to the area pointed - * at by `ptr`. + * at by `ptr`. `size` will always be > 0. * * On an incomplete read, you should set `*status` to a value from the * SDL_IOStatus enum. You do not have to explicitly set this on @@ -123,7 +123,7 @@ typedef struct SDL_IOStreamInterface /** * Write exactly `size` bytes from the area pointed at by `ptr` - * to data stream. + * to data stream. `size` will always be > 0. * * On an incomplete write, you should set `*status` to a value from the * SDL_IOStatus enum. You do not have to explicitly set this on @@ -203,6 +203,8 @@ typedef struct SDL_IOStream SDL_IOStream; * - "w": Create an empty file for writing. If a file with the same name * already exists its content is erased and the file is treated as a new * empty file. + * - "wx": Create an empty file for writing. If a file with the same name + * already exists, the call fails. * - "a": Append to a file. Writing operations append data at the end of the * file. The file is created if it does not exist. * - "r+": Open a file for update both reading and writing. The file must @@ -210,6 +212,8 @@ typedef struct SDL_IOStream SDL_IOStream; * - "w+": Create an empty file for both reading and writing. If a file with * the same name already exists its content is erased and the file is * treated as a new empty file. + * - "w+x": Create an empty file for both reading and writing. If a file with + * the same name already exists, the call fails. * - "a+": Open a file for reading and appending. All writing operations are * performed at the end of the file, protecting the previous content to be * overwritten. You can reposition (fseek, rewind) the internal pointer to @@ -260,7 +264,7 @@ typedef struct SDL_IOStream SDL_IOStream; * \returns a pointer to the SDL_IOStream structure that is created or NULL on * failure; call SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. * @@ -286,8 +290,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromFile(const char *file, cons * certain size, for both read and write access. * * This memory buffer is not copied by the SDL_IOStream; the pointer you - * provide must remain valid until you close the stream. Closing the stream - * will not free the original buffer. + * provide must remain valid until you close the stream. * * If you need to make sure the SDL_IOStream never writes to the memory * buffer, you should use SDL_IOFromConstMem() with a read-only buffer of @@ -300,6 +303,13 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromFile(const char *file, cons * - `SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER`: this will be the `size` parameter * that was passed to this function. * + * Additionally, the following properties are recognized: + * + * - `SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC_POINTER`: if this property is set to + * a non-NULL value it will be interpreted as a function of SDL_free_func + * type and called with the passed `mem` pointer when closing the stream. By + * default it is unset, i.e., the memory will not be freed. + * * \param mem a pointer to a buffer to feed an SDL_IOStream stream. * \param size the buffer size, in bytes. * \returns a pointer to a new SDL_IOStream structure or NULL on failure; call @@ -321,6 +331,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromMem(void *mem, size_t size) #define SDL_PROP_IOSTREAM_MEMORY_POINTER "SDL.iostream.memory.base" #define SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER "SDL.iostream.memory.size" +#define SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC_POINTER "SDL.iostream.memory.free" /** * Use this function to prepare a read-only memory buffer for use with @@ -333,8 +344,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromMem(void *mem, size_t size) * without writing to the memory buffer. * * This memory buffer is not copied by the SDL_IOStream; the pointer you - * provide must remain valid until you close the stream. Closing the stream - * will not free the original buffer. + * provide must remain valid until you close the stream. * * If you need to write to a memory buffer, you should use SDL_IOFromMem() * with a writable buffer of memory instead. @@ -346,6 +356,13 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromMem(void *mem, size_t size) * - `SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER`: this will be the `size` parameter * that was passed to this function. * + * Additionally, the following properties are recognized: + * + * - `SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC_POINTER`: if this property is set to + * a non-NULL value it will be interpreted as a function of SDL_free_func + * type and called with the passed `mem` pointer when closing the stream. By + * default it is unset, i.e., the memory will not be freed. + * * \param mem a pointer to a read-only buffer to feed an SDL_IOStream stream. * \param size the buffer size, in bytes. * \returns a pointer to a new SDL_IOStream structure or NULL on failure; call @@ -452,7 +469,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_OpenIO(const SDL_IOStreamInterfac * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -467,7 +484,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_CloseIO(SDL_IOStream *context); * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -487,7 +504,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetIOProperties(SDL_IOStream *c * \param context the SDL_IOStream to query. * \returns an SDL_IOStatus enum with the current state. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -501,7 +518,7 @@ extern SDL_DECLSPEC SDL_IOStatus SDLCALL SDL_GetIOStatus(SDL_IOStream *context); * negative error code on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -528,7 +545,7 @@ extern SDL_DECLSPEC Sint64 SDLCALL SDL_GetIOSize(SDL_IOStream *context); * \returns the final offset in the data stream after the seek or -1 on * failure; call SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -548,7 +565,7 @@ extern SDL_DECLSPEC Sint64 SDLCALL SDL_SeekIO(SDL_IOStream *context, Sint64 offs * \returns the current offset in the stream, or -1 if the information can not * be determined. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -567,13 +584,17 @@ extern SDL_DECLSPEC Sint64 SDLCALL SDL_TellIO(SDL_IOStream *context); * the stream is not at EOF, SDL_GetIOStatus() will return a different error * value and SDL_GetError() will offer a human-readable message. * + * A request for zero bytes on a valid stream will return zero immediately + * without accessing the stream, so the stream status (EOF, err, etc) will not + * change. + * * \param context a pointer to an SDL_IOStream structure. * \param ptr a pointer to a buffer to read data into. * \param size the number of bytes to read from the data source. * \returns the number of bytes read, or 0 on end of file or other failure; * call SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -596,13 +617,17 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_ReadIO(SDL_IOStream *context, void *ptr, * recoverable, such as a non-blocking write that can simply be retried later, * or a fatal error. * + * A request for zero bytes on a valid stream will return zero immediately + * without accessing the stream, so the stream status (EOF, err, etc) will not + * change. + * * \param context a pointer to an SDL_IOStream structure. * \param ptr a pointer to a buffer containing data to write. * \param size the number of bytes to write. * \returns the number of bytes written, which will be less than `size` on * failure; call SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -626,7 +651,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_WriteIO(SDL_IOStream *context, const void * \returns the number of bytes written or 0 on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -646,7 +671,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_IOprintf(SDL_IOStream *context, SDL_PRINT * \returns the number of bytes written or 0 on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -666,7 +691,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_IOvprintf(SDL_IOStream *context, SDL_PRIN * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -692,7 +717,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_FlushIO(SDL_IOStream *context); * \returns the data or NULL on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -715,7 +740,7 @@ extern SDL_DECLSPEC void * SDLCALL SDL_LoadFile_IO(SDL_IOStream *src, size_t *da * \returns the data or NULL on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. * @@ -736,7 +761,7 @@ extern SDL_DECLSPEC void * SDLCALL SDL_LoadFile(const char *file, size_t *datasi * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -755,7 +780,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SaveFile_IO(SDL_IOStream *src, const void * * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. * @@ -784,7 +809,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SaveFile(const char *file, const void *data * \returns true on success or false on failure or EOF; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -803,7 +828,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU8(SDL_IOStream *src, Uint8 *value); * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -823,10 +848,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS8(SDL_IOStream *src, Sint8 *value); * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -846,10 +871,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU16LE(SDL_IOStream *src, Uint16 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -869,10 +894,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS16LE(SDL_IOStream *src, Sint16 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -892,10 +917,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU16BE(SDL_IOStream *src, Uint16 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -915,10 +940,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS16BE(SDL_IOStream *src, Sint16 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -938,10 +963,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU32LE(SDL_IOStream *src, Uint32 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -961,10 +986,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS32LE(SDL_IOStream *src, Sint32 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -984,10 +1009,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU32BE(SDL_IOStream *src, Uint32 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1007,10 +1032,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS32BE(SDL_IOStream *src, Sint32 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1030,10 +1055,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU64LE(SDL_IOStream *src, Uint64 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1053,10 +1078,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS64LE(SDL_IOStream *src, Sint64 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1076,10 +1101,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU64BE(SDL_IOStream *src, Uint64 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1101,7 +1126,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS64BE(SDL_IOStream *src, Sint64 *value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1115,7 +1140,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU8(SDL_IOStream *dst, Uint8 value); * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1134,7 +1159,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS8(SDL_IOStream *dst, Sint8 value); * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1153,7 +1178,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU16LE(SDL_IOStream *dst, Uint16 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1171,7 +1196,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS16LE(SDL_IOStream *dst, Sint16 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1189,7 +1214,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU16BE(SDL_IOStream *dst, Uint16 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1208,7 +1233,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS16BE(SDL_IOStream *dst, Sint16 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1227,7 +1252,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU32LE(SDL_IOStream *dst, Uint32 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1245,7 +1270,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS32LE(SDL_IOStream *dst, Sint32 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1263,7 +1288,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU32BE(SDL_IOStream *dst, Uint32 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1282,7 +1307,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS32BE(SDL_IOStream *dst, Sint32 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1301,7 +1326,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU64LE(SDL_IOStream *dst, Uint64 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1319,7 +1344,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS64LE(SDL_IOStream *dst, Sint64 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1337,7 +1362,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU64BE(SDL_IOStream *dst, Uint64 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ diff --git a/libs/SDL3/include/SDL3/SDL_joystick.h b/libs/SDL3/include/SDL3/SDL_joystick.h index d15668b..c93b352 100644 --- a/libs/SDL3/include/SDL3/SDL_joystick.h +++ b/libs/SDL3/include/SDL3/SDL_joystick.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -29,8 +29,8 @@ * instead. * * The term "instance_id" is the current instantiation of a joystick device in - * the system, if the joystick is removed and then re-inserted then it will - * get a new instance_id, instance_id's are monotonically increasing + * the system. If the joystick is removed and then re-inserted then it will + * get a new instance_id. instance_id's are monotonically increasing * identifiers of a joystick plugged in. * * The term "player_index" is the number assigned to a player on a specific @@ -48,6 +48,14 @@ * If you would like to receive joystick updates while the application is in * the background, you should set the following hint before calling * SDL_Init(): SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS + * + * SDL can provide virtual joysticks as well: the app defines an imaginary + * controller with SDL_AttachVirtualJoystick(), and then can provide inputs + * for it via SDL_SetJoystickVirtualAxis(), SDL_SetJoystickVirtualButton(), + * etc. As this data is supplied, it will look like a normal joystick to SDL, + * just not backed by a hardware driver. This has been used to make unusual + * devices, like VR headset controllers, look like normal joysticks, or + * provide recording/playback of game inputs, etc. */ #ifndef SDL_joystick_h_ @@ -107,6 +115,10 @@ typedef Uint32 SDL_JoystickID; * This is by no means a complete list of everything that can be plugged into * a computer. * + * You may refer to + * [XInput Controller Types](https://learn.microsoft.com/en-us/windows/win32/xinput/xinput-and-controller-subtypes) + * table for a general understanding of each joystick type. + * * \since This enum is available since SDL 3.2.0. */ typedef enum SDL_JoystickType @@ -170,6 +182,8 @@ typedef enum SDL_JoystickConnectionState * joysticks while processing to guarantee that the joystick list won't change * and joystick and gamepad events will not be delivered. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_LockJoysticks(void) SDL_ACQUIRE(SDL_joystick_lock); @@ -177,6 +191,9 @@ extern SDL_DECLSPEC void SDLCALL SDL_LockJoysticks(void) SDL_ACQUIRE(SDL_joystic /** * Unlocking for atomic access to the joystick API. * + * \threadsafety This should be called from the same thread that called + * SDL_LockJoysticks(). + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_UnlockJoysticks(void) SDL_RELEASE(SDL_joystick_lock); @@ -186,6 +203,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnlockJoysticks(void) SDL_RELEASE(SDL_joyst * * \returns true if a joystick is connected, 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_GetJoysticks @@ -201,6 +220,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasJoystick(void); * call SDL_GetError() for more information. This should be freed * with SDL_free() when it is no longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_HasJoystick @@ -217,6 +238,8 @@ extern SDL_DECLSPEC SDL_JoystickID * SDLCALL SDL_GetJoysticks(int *count); * \returns the name of the selected joystick. If no name can be found, this * function returns NULL; 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_GetJoystickName @@ -233,6 +256,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetJoystickNameForID(SDL_JoystickID * \returns the path of the selected joystick. If no path can be found, this * function returns NULL; 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_GetJoystickPath @@ -248,6 +273,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetJoystickPathForID(SDL_JoystickID * \param instance_id the joystick instance ID. * \returns the player index of a joystick, or -1 if it's not available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickPlayerIndex @@ -264,6 +291,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetJoystickPlayerIndexForID(SDL_JoystickID i * \returns the GUID of the selected joystick. If called with an invalid * instance_id, this function returns a zero GUID. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickGUID @@ -281,6 +310,8 @@ extern SDL_DECLSPEC SDL_GUID SDLCALL SDL_GetJoystickGUIDForID(SDL_JoystickID ins * \returns the USB vendor ID of the selected joystick. If called with an * invalid instance_id, this function returns 0. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickVendor @@ -298,6 +329,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickVendorForID(SDL_JoystickID ins * \returns the USB product ID of the selected joystick. If called with an * invalid instance_id, this function returns 0. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickProduct @@ -315,6 +348,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickProductForID(SDL_JoystickID in * \returns the product version of the selected joystick. If called with an * invalid instance_id, this function returns 0. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickProductVersion @@ -332,6 +367,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickProductVersionForID(SDL_Joysti * invalid instance_id, this function returns * `SDL_JOYSTICK_TYPE_UNKNOWN`. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickType @@ -349,6 +386,8 @@ extern SDL_DECLSPEC SDL_JoystickType SDLCALL SDL_GetJoystickTypeForID(SDL_Joysti * \returns a joystick identifier 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_CloseJoystick @@ -362,6 +401,8 @@ extern SDL_DECLSPEC SDL_Joystick * SDLCALL SDL_OpenJoystick(SDL_JoystickID insta * \returns an SDL_Joystick on success or NULL on failure or if it hasn't been * opened yet; 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_Joystick * SDLCALL SDL_GetJoystickFromID(SDL_JoystickID instance_id); @@ -373,6 +414,8 @@ extern SDL_DECLSPEC SDL_Joystick * SDLCALL SDL_GetJoystickFromID(SDL_JoystickID * \returns an SDL_Joystick on success 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_GetJoystickPlayerIndex @@ -465,13 +508,33 @@ SDL_COMPILE_TIME_ASSERT(SDL_VirtualJoystickDesc_SIZE, /** * Attach a new virtual joystick. * + * Apps can create virtual joysticks, that exist without hardware directly + * backing them, and have program-supplied inputs. Once attached, a virtual + * joystick looks like any other joystick that SDL can access. These can be + * used to make other things look like joysticks, or provide pre-recorded + * input, etc. + * + * Once attached, the app can send joystick inputs to the new virtual joystick + * using SDL_SetJoystickVirtualAxis(), etc. + * + * When no longer needed, the virtual joystick can be removed by calling + * SDL_DetachVirtualJoystick(). + * * \param desc joystick description, initialized using SDL_INIT_INTERFACE(). * \returns the joystick instance ID, 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. * * \sa SDL_DetachVirtualJoystick + * \sa SDL_SetJoystickVirtualAxis + * \sa SDL_SetJoystickVirtualButton + * \sa SDL_SetJoystickVirtualBall + * \sa SDL_SetJoystickVirtualHat + * \sa SDL_SetJoystickVirtualTouchpad + * \sa SDL_SetJoystickVirtualSensorData */ extern SDL_DECLSPEC SDL_JoystickID SDLCALL SDL_AttachVirtualJoystick(const SDL_VirtualJoystickDesc *desc); @@ -483,6 +546,8 @@ extern SDL_DECLSPEC SDL_JoystickID SDLCALL SDL_AttachVirtualJoystick(const SDL_V * \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_AttachVirtualJoystick @@ -495,6 +560,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_DetachVirtualJoystick(SDL_JoystickID instan * \param instance_id the joystick instance ID. * \returns true if the joystick is virtual, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_IsJoystickVirtual(SDL_JoystickID instance_id); @@ -518,7 +585,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_IsJoystickVirtual(SDL_JoystickID instance_i * \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_SetJoystickVirtualButton + * \sa SDL_SetJoystickVirtualBall + * \sa SDL_SetJoystickVirtualHat + * \sa SDL_SetJoystickVirtualTouchpad + * \sa SDL_SetJoystickVirtualSensorData */ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value); @@ -538,7 +613,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualAxis(SDL_Joystick *joysti * \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_SetJoystickVirtualAxis + * \sa SDL_SetJoystickVirtualButton + * \sa SDL_SetJoystickVirtualHat + * \sa SDL_SetJoystickVirtualTouchpad + * \sa SDL_SetJoystickVirtualSensorData */ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualBall(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel); @@ -557,7 +640,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualBall(SDL_Joystick *joysti * \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_SetJoystickVirtualAxis + * \sa SDL_SetJoystickVirtualBall + * \sa SDL_SetJoystickVirtualHat + * \sa SDL_SetJoystickVirtualTouchpad + * \sa SDL_SetJoystickVirtualSensorData */ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, int button, bool down); @@ -576,7 +667,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualButton(SDL_Joystick *joys * \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_SetJoystickVirtualAxis + * \sa SDL_SetJoystickVirtualButton + * \sa SDL_SetJoystickVirtualBall + * \sa SDL_SetJoystickVirtualTouchpad + * \sa SDL_SetJoystickVirtualSensorData */ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value); @@ -602,7 +701,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualHat(SDL_Joystick *joystic * \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_SetJoystickVirtualAxis + * \sa SDL_SetJoystickVirtualButton + * \sa SDL_SetJoystickVirtualBall + * \sa SDL_SetJoystickVirtualHat + * \sa SDL_SetJoystickVirtualSensorData */ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualTouchpad(SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure); @@ -624,7 +731,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualTouchpad(SDL_Joystick *jo * \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_SetJoystickVirtualAxis + * \sa SDL_SetJoystickVirtualButton + * \sa SDL_SetJoystickVirtualBall + * \sa SDL_SetJoystickVirtualHat + * \sa SDL_SetJoystickVirtualTouchpad */ extern SDL_DECLSPEC bool SDLCALL SDL_SendJoystickVirtualSensorData(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values); @@ -648,6 +763,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SendJoystickVirtualSensorData(SDL_Joystick * \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_GetJoystickProperties(SDL_Joystick *joystick); @@ -665,6 +782,8 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetJoystickProperties(SDL_Joyst * \returns the name of the selected joystick. If no name can be found, this * function returns NULL; 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_GetJoystickNameForID @@ -678,6 +797,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetJoystickName(SDL_Joystick *joyst * \returns the path of the selected joystick. If no path can be found, this * function returns NULL; 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_GetJoystickPathForID @@ -693,6 +814,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetJoystickPath(SDL_Joystick *joyst * \param joystick the SDL_Joystick obtained from SDL_OpenJoystick(). * \returns the player index, or -1 if it's not available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetJoystickPlayerIndex @@ -708,6 +831,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetJoystickPlayerIndex(SDL_Joystick *joystic * \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_GetJoystickPlayerIndex @@ -724,6 +849,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickPlayerIndex(SDL_Joystick *joysti * this function returns a zero GUID; 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_GetJoystickGUIDForID @@ -739,6 +866,8 @@ extern SDL_DECLSPEC SDL_GUID SDLCALL SDL_GetJoystickGUID(SDL_Joystick *joystick) * \param joystick the SDL_Joystick obtained from SDL_OpenJoystick(). * \returns the USB vendor ID of the selected joystick, or 0 if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickVendorForID @@ -753,6 +882,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickVendor(SDL_Joystick *joystick) * \param joystick the SDL_Joystick obtained from SDL_OpenJoystick(). * \returns the USB product ID of the selected joystick, or 0 if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickProductForID @@ -767,6 +898,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickProduct(SDL_Joystick *joystick * \param joystick the SDL_Joystick obtained from SDL_OpenJoystick(). * \returns the product version of the selected joystick, or 0 if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickProductVersionForID @@ -782,6 +915,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickProductVersion(SDL_Joystick *j * \returns the firmware version of the selected joystick, or 0 if * unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickFirmwareVersion(SDL_Joystick *joystick); @@ -795,6 +930,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickFirmwareVersion(SDL_Joystick * * \returns the serial number of the selected joystick, or NULL if * unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC const char * SDLCALL SDL_GetJoystickSerial(SDL_Joystick *joystick); @@ -805,6 +942,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetJoystickSerial(SDL_Joystick *joy * \param joystick the SDL_Joystick obtained from SDL_OpenJoystick(). * \returns the SDL_JoystickType of the selected joystick. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickTypeForID @@ -824,6 +963,8 @@ extern SDL_DECLSPEC SDL_JoystickType SDLCALL SDL_GetJoystickType(SDL_Joystick *j * \param crc16 a pointer filled in with a CRC used to distinguish different * products with the same VID/PID, or 0 if not available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickGUIDForID @@ -837,6 +978,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_GetJoystickGUIDInfo(SDL_GUID guid, Uint16 * * \returns true if the joystick has been opened, false if it has not; 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 bool SDLCALL SDL_JoystickConnected(SDL_Joystick *joystick); @@ -848,6 +991,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_JoystickConnected(SDL_Joystick *joystick); * \returns the instance ID of the specified joystick 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_JoystickID SDLCALL SDL_GetJoystickID(SDL_Joystick *joystick); @@ -863,6 +1008,8 @@ extern SDL_DECLSPEC SDL_JoystickID SDLCALL SDL_GetJoystickID(SDL_Joystick *joyst * \returns the number of axis controls/number of axes on success or -1 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_GetJoystickAxis @@ -884,6 +1031,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumJoystickAxes(SDL_Joystick *joystick); * \returns the number of trackballs on success or -1 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_GetJoystickBall @@ -900,6 +1049,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumJoystickBalls(SDL_Joystick *joystick); * \returns the number of POV hats on success or -1 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_GetJoystickHat @@ -916,6 +1067,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumJoystickHats(SDL_Joystick *joystick); * \returns the number of buttons on success or -1 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_GetJoystickButton @@ -934,6 +1087,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumJoystickButtons(SDL_Joystick *joystick * * \param enabled whether to process joystick events or not. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_JoystickEventsEnabled @@ -950,6 +1105,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetJoystickEventsEnabled(bool enabled); * * \returns true if joystick events are being processed, 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_SetJoystickEventsEnabled @@ -962,6 +1119,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_JoystickEventsEnabled(void); * This is called automatically by the event loop if any joystick events are * enabled. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_UpdateJoysticks(void); @@ -984,6 +1143,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UpdateJoysticks(void); * \returns a 16-bit signed integer representing the current position of the * axis 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. * * \sa SDL_GetNumJoystickAxes @@ -1002,6 +1163,8 @@ extern SDL_DECLSPEC Sint16 SDLCALL SDL_GetJoystickAxis(SDL_Joystick *joystick, i * \param state upon return, the initial value is supplied here. * \returns true if this axis has any initial value, or false if not. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetJoystickAxisInitialState(SDL_Joystick *joystick, int axis, Sint16 *state); @@ -1021,6 +1184,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetJoystickAxisInitialState(SDL_Joystick *j * \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_GetNumJoystickBalls @@ -1036,6 +1201,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetJoystickBall(SDL_Joystick *joystick, int * \param hat the hat index to get the state from; indices start at index 0. * \returns the current hat position. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetNumJoystickHats @@ -1060,6 +1227,8 @@ extern SDL_DECLSPEC Uint8 SDLCALL SDL_GetJoystickHat(SDL_Joystick *joystick, int * index 0. * \returns true if the button is pressed, 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_GetNumJoystickButtons @@ -1083,6 +1252,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetJoystickButton(SDL_Joystick *joystick, i * \param duration_ms the duration of the rumble effect, in milliseconds. * \returns true, or false if rumble isn't supported on this joystick. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_RumbleJoystick(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms); @@ -1110,6 +1281,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RumbleJoystick(SDL_Joystick *joystick, Uint * \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_RumbleJoystick @@ -1132,6 +1305,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RumbleJoystickTriggers(SDL_Joystick *joysti * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue); @@ -1145,6 +1320,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickLED(SDL_Joystick *joystick, Uint * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_SendJoystickEffect(SDL_Joystick *joystick, const void *data, int size); @@ -1154,6 +1331,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SendJoystickEffect(SDL_Joystick *joystick, * * \param joystick the joystick device to close. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_OpenJoystick @@ -1168,6 +1347,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_CloseJoystick(SDL_Joystick *joystick); * `SDL_JOYSTICK_CONNECTION_INVALID` 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_JoystickConnectionState SDLCALL SDL_GetJoystickConnectionState(SDL_Joystick *joystick); @@ -1189,6 +1370,8 @@ extern SDL_DECLSPEC SDL_JoystickConnectionState SDLCALL SDL_GetJoystickConnectio * \returns the current battery state or `SDL_POWERSTATE_ERROR` 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_PowerState SDLCALL SDL_GetJoystickPowerInfo(SDL_Joystick *joystick, int *percent); diff --git a/libs/SDL3/include/SDL3/SDL_keyboard.h b/libs/SDL3/include/SDL3/SDL_keyboard.h index afa77b6..d14ab6f 100644 --- a/libs/SDL3/include/SDL3/SDL_keyboard.h +++ b/libs/SDL3/include/SDL3/SDL_keyboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -174,8 +174,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_ResetKeyboard(void); /** * Get the current key modifier state for the keyboard. * - * \returns an OR'd combination of the modifier keys for the keyboard. See - * SDL_Keymod for details. + * \returns an OR'd combination of the modifier keys for the keyboard. * * \threadsafety It is safe to call this function from any thread. * diff --git a/libs/SDL3/include/SDL3/SDL_keycode.h b/libs/SDL3/include/SDL3/SDL_keycode.h index 61b68e7..1818ee3 100644 --- a/libs/SDL3/include/SDL3/SDL_keycode.h +++ b/libs/SDL3/include/SDL3/SDL_keycode.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -45,12 +45,16 @@ * `SDLK_*` constant for those keys that do not generate characters. * * A special exception is the number keys at the top of the keyboard which map - * to SDLK_0...SDLK_9 on AZERTY layouts. + * by default to SDLK_0...SDLK_9 on AZERTY layouts. * * Keys with the `SDLK_EXTENDED_MASK` bit set do not map to a scancode or - * unicode code point. + * Unicode code point. + * + * Many common keycodes are listed below, but this list is not exhaustive. * * \since This datatype is available since SDL 3.2.0. + * + * \sa SDL_HINT_KEYCODE_OPTIONS */ typedef Uint32 SDL_Keycode; diff --git a/libs/SDL3/include/SDL3/SDL_loadso.h b/libs/SDL3/include/SDL3/SDL_loadso.h index f8649d7..51d0d88 100644 --- a/libs/SDL3/include/SDL3/SDL_loadso.h +++ b/libs/SDL3/include/SDL3/SDL_loadso.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_locale.h b/libs/SDL3/include/SDL3/SDL_locale.h index 902843e..9f03aa9 100644 --- a/libs/SDL3/include/SDL3/SDL_locale.h +++ b/libs/SDL3/include/SDL3/SDL_locale.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -102,6 +102,8 @@ typedef struct SDL_Locale * allocation that 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. */ extern SDL_DECLSPEC SDL_Locale ** SDLCALL SDL_GetPreferredLocales(int *count); diff --git a/libs/SDL3/include/SDL3/SDL_log.h b/libs/SDL3/include/SDL3/SDL_log.h index 3fd7ec2..2ae0963 100644 --- a/libs/SDL3/include/SDL3/SDL_log.h +++ b/libs/SDL3/include/SDL3/SDL_log.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -206,6 +206,9 @@ extern SDL_DECLSPEC void SDLCALL SDL_ResetLogPriorities(void); * SDL_LOG_PRIORITY_WARN and higher have a prefix showing their priority, e.g. * "WARNING: ". * + * This function makes a copy of its string argument, **prefix**, so it is not + * necessary to keep the value of **prefix** alive after the call returns. + * * \param priority the SDL_LogPriority to modify. * \param prefix the prefix to use for that log priority, or NULL to use no * prefix. @@ -263,7 +266,6 @@ extern SDL_DECLSPEC void SDLCALL SDL_Log(SDL_PRINTF_FORMAT_STRING const char *fm * \sa SDL_LogInfo * \sa SDL_LogMessage * \sa SDL_LogMessageV - * \sa SDL_LogTrace * \sa SDL_LogVerbose * \sa SDL_LogWarn */ diff --git a/libs/SDL3/include/SDL3/SDL_main.h b/libs/SDL3/include/SDL3/SDL_main.h index 905d78e..ee4d9ca 100644 --- a/libs/SDL3/include/SDL3/SDL_main.h +++ b/libs/SDL3/include/SDL3/SDL_main.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -47,7 +47,7 @@ * * For more information, see: * - * https://wiki.libsdl.org/SDL3/README/main-functions + * https://wiki.libsdl.org/SDL3/README-main-functions */ #ifndef SDL_main_h_ @@ -68,7 +68,7 @@ * proper entry point for the platform, and all the other magic details * needed, like manually calling SDL_SetMainReady. * - * Please see [README/main-functions](README/main-functions), (or + * Please see [README-main-functions](README-main-functions), (or * docs/README-main-functions.md in the source tree) for a more detailed * explanation. * @@ -85,7 +85,7 @@ * SDL_AppQuit. The app should not provide a `main` function in this case, and * doing so will likely cause the build to fail. * - * Please see [README/main-functions](README/main-functions), (or + * Please see [README-main-functions](README-main-functions), (or * docs/README-main-functions.md in the source tree) for a more detailed * explanation. * @@ -110,7 +110,8 @@ * Even if available, an app can define SDL_MAIN_HANDLED and provide their * own, if they know what they're doing. * - * This macro is used internally by SDL, and apps probably shouldn't rely on it. + * This macro is used internally by SDL, and apps probably shouldn't rely on + * it. * * \since This macro is available since SDL 3.2.0. */ @@ -125,10 +126,11 @@ * This macro is defined by `SDL_main.h`, which is not automatically included * by `SDL.h`. * - * Even if required, an app can define SDL_MAIN_HANDLED and provide their - * own, if they know what they're doing. + * Even if required, an app can define SDL_MAIN_HANDLED and provide their own, + * if they know what they're doing. * - * This macro is used internally by SDL, and apps probably shouldn't rely on it. + * This macro is used internally by SDL, and apps probably shouldn't rely on + * it. * * \since This macro is available since SDL 3.2.0. */ @@ -165,12 +167,10 @@ */ #define SDL_MAIN_NEEDED - #elif defined(SDL_PLATFORM_IOS) - /* On iOS SDL provides a main function that creates an application delegate - and starts the iOS application run loop. + #elif defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS) + /* On iOS and tvOS SDL provides a main function that creates an application delegate and starts the application run loop. - To use it, just #include SDL_main.h in the source file that contains your - main() function. + To use it, just #include in the source file that contains your main() function. See src/video/uikit/SDL_uikitappdelegate.m for more details. */ @@ -333,6 +333,8 @@ extern "C" { * \returns SDL_APP_FAILURE to terminate with an error, SDL_APP_SUCCESS to * terminate with success, SDL_APP_CONTINUE to continue. * + * \threadsafety This function is called once by SDL, at startup, on a single thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_AppIterate @@ -347,10 +349,10 @@ extern SDLMAIN_DECLSPEC SDL_AppResult SDLCALL SDL_AppInit(void **appstate, int a * Apps implement this function when using SDL_MAIN_USE_CALLBACKS. If using a * standard "main" function, you should not supply this. * - * This function is called repeatedly by SDL after SDL_AppInit returns 0. The - * function should operate as a single iteration the program's primary loop; - * it should update whatever state it needs and draw a new frame of video, - * usually. + * This function is called repeatedly by SDL after SDL_AppInit returns + * SDL_APP_CONTINUE. The function should operate as a single iteration the + * program's primary loop; it should update whatever state it needs and draw a + * new frame of video, usually. * * On some platforms, this function will be called at the refresh rate of the * display (which might change during the life of your app!). There are no @@ -449,8 +451,8 @@ extern SDLMAIN_DECLSPEC SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_E * * This function is called once by SDL before terminating the program. * - * This function will be called no matter what, even if SDL_AppInit requests - * termination. + * This function will be called in all cases, even if SDL_AppInit requests + * termination at startup. * * This function should not go into an infinite mainloop; it should * deinitialize any resources necessary, perform whatever shutdown activities, @@ -512,7 +514,7 @@ typedef int (SDLCALL *SDL_main_func)(int argc, char *argv[]); * SDL_MAIN_USE_CALLBACKS. * * Program startup is a surprisingly complex topic. Please see - * [README/main-functions](README/main-functions), (or + * [README-main-functions](README-main-functions), (or * docs/README-main-functions.md in the source tree) for a more detailed * explanation. * @@ -537,6 +539,8 @@ extern SDLMAIN_DECLSPEC int SDLCALL SDL_main(int argc, char *argv[]); * will not be changed it is necessary to define SDL_MAIN_HANDLED before * including SDL.h. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_Init @@ -553,6 +557,9 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetMainReady(void); * using SDL_main (like when using SDL_MAIN_HANDLED). When using this, you do * *not* need SDL_SetMainReady(). * + * If `argv` is NULL, SDL will provide command line arguments, either by + * querying the OS for them if possible, or supplying a filler array if not. + * * \param argc the argc parameter from the application's main() function, or 0 * if the platform's main-equivalent has no argc. * \param argv the argv parameter from the application's main() function, or @@ -615,16 +622,19 @@ extern SDL_DECLSPEC int SDLCALL SDL_EnterAppMainCallbacks(int argc, char *argv[] * Most applications do not need to, and should not, call this directly; SDL * will call it when initializing the video subsystem. * + * If `name` is NULL, SDL currently uses `(CS_BYTEALIGNCLIENT | CS_OWNDC)` for + * the style, regardless of what is specified here. + * * \param name the window class name, in UTF-8 encoding. If NULL, SDL * currently uses "SDL_app" but this isn't guaranteed. - * \param style the value to use in WNDCLASSEX::style. If `name` is NULL, SDL - * currently uses `(CS_BYTEALIGNCLIENT | CS_OWNDC)` regardless of - * what is specified here. + * \param style the value to use in WNDCLASSEX::style. * \param hInst the HINSTANCE to use in WNDCLASSEX::hInstance. If zero, SDL * will use `GetModuleHandle(NULL)` instead. * \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_RegisterApp(const char *name, Uint32 style, void *hInst); @@ -642,6 +652,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RegisterApp(const char *name, Uint32 style, * deregistered when the registration counter in SDL_RegisterApp decrements to * zero through calls to this function. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_UnregisterApp(void); @@ -654,6 +666,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnregisterApp(void); * This function is only needed for Xbox GDK support; all other platforms will * do nothing and set an "unsupported" error message. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void); diff --git a/libs/SDL3/include/SDL3/SDL_main_impl.h b/libs/SDL3/include/SDL3/SDL_main_impl.h index 14ebb42..bf5f583 100644 --- a/libs/SDL3/include/SDL3/SDL_main_impl.h +++ b/libs/SDL3/include/SDL3/SDL_main_impl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_messagebox.h b/libs/SDL3/include/SDL3/SDL_messagebox.h index 365ae36..af604e2 100644 --- a/libs/SDL3/include/SDL3/SDL_messagebox.h +++ b/libs/SDL3/include/SDL3/SDL_messagebox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -168,6 +168,8 @@ typedef struct SDL_MessageBoxData * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_ShowSimpleMessageBox @@ -210,6 +212,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ShowMessageBox(const SDL_MessageBoxData *me * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_ShowMessageBox diff --git a/libs/SDL3/include/SDL3/SDL_metal.h b/libs/SDL3/include/SDL3/SDL_metal.h index 14b1bc8..6b0e171 100644 --- a/libs/SDL3/include/SDL3/SDL_metal.h +++ b/libs/SDL3/include/SDL3/SDL_metal.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -65,6 +65,8 @@ typedef void *SDL_MetalView; * \param window the window. * \returns handle NSView or UIView. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_Metal_DestroyView @@ -80,6 +82,8 @@ extern SDL_DECLSPEC SDL_MetalView SDLCALL SDL_Metal_CreateView(SDL_Window *windo * * \param view the SDL_MetalView object. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_Metal_CreateView @@ -92,6 +96,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_Metal_DestroyView(SDL_MetalView view); * \param view the SDL_MetalView object. * \returns a pointer. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void * SDLCALL SDL_Metal_GetLayer(SDL_MetalView view); diff --git a/libs/SDL3/include/SDL3/SDL_misc.h b/libs/SDL3/include/SDL3/SDL_misc.h index 3dd6fcd..654c005 100644 --- a/libs/SDL3/include/SDL3/SDL_misc.h +++ b/libs/SDL3/include/SDL3/SDL_misc.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -65,6 +65,8 @@ extern "C" { * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_OpenURL(const char *url); diff --git a/libs/SDL3/include/SDL3/SDL_mouse.h b/libs/SDL3/include/SDL3/SDL_mouse.h index 864135d..fb03c01 100644 --- a/libs/SDL3/include/SDL3/SDL_mouse.h +++ b/libs/SDL3/include/SDL3/SDL_mouse.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -130,6 +130,17 @@ typedef enum SDL_MouseWheelDirection SDL_MOUSEWHEEL_FLIPPED /**< The scroll direction is flipped / natural */ } SDL_MouseWheelDirection; +/** + * Animated cursor frame info. + * + * \since This struct is available since SDL 3.4.0. + */ +typedef struct SDL_CursorFrameInfo +{ + SDL_Surface *surface; /**< The surface data for this frame */ + Uint32 duration; /**< The frame duration in milliseconds (a duration of 0 is infinite) */ +} SDL_CursorFrameInfo; + /** * A bitmask of pressed mouse buttons, as reported by SDL_GetMouseState, etc. * @@ -160,6 +171,44 @@ typedef Uint32 SDL_MouseButtonFlags; #define SDL_BUTTON_X1MASK SDL_BUTTON_MASK(SDL_BUTTON_X1) #define SDL_BUTTON_X2MASK SDL_BUTTON_MASK(SDL_BUTTON_X2) +/** + * A callback used to transform mouse motion delta from raw values. + * + * This is called during SDL's handling of platform mouse events to scale the + * values of the resulting motion delta. + * + * \param userdata what was passed as `userdata` to + * SDL_SetRelativeMouseTransform(). + * \param timestamp the associated time at which this mouse motion event was + * received. + * \param window the associated window to which this mouse motion event was + * addressed. + * \param mouseID the associated mouse from which this mouse motion event was + * emitted. + * \param x pointer to a variable that will be treated as the resulting x-axis + * motion. + * \param y pointer to a variable that will be treated as the resulting y-axis + * motion. + * + * \threadsafety This callback is called by SDL's internal mouse input + * processing procedure, which may be a thread separate from the + * main event loop that is run at realtime priority. Stalling + * this thread with too much work in the callback can therefore + * potentially freeze the entire system. Care should be taken + * with proper synchronization practices when adding other side + * effects beyond mutation of the x and y values. + * + * \since This datatype is available since SDL 3.4.0. + * + * \sa SDL_SetRelativeMouseTransform + */ +typedef void (SDLCALL *SDL_MouseMotionTransformCallback)( + void *userdata, + Uint64 timestamp, + SDL_Window *window, + SDL_MouseID mouseID, + float *x, float *y +); /* Function prototypes */ @@ -380,6 +429,24 @@ extern SDL_DECLSPEC void SDLCALL SDL_WarpMouseInWindow(SDL_Window *window, */ extern SDL_DECLSPEC bool SDLCALL SDL_WarpMouseGlobal(float x, float y); +/** + * Set a user-defined function by which to transform relative mouse inputs. + * + * This overrides the relative system scale and relative speed scale hints. + * Should be called prior to enabling relative mouse mode, fails otherwise. + * + * \param callback a callback used to transform relative mouse motion, or NULL + * for default behavior. + * \param userdata a pointer that will be passed to `callback`. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetRelativeMouseTransform(SDL_MouseMotionTransformCallback callback, void *userdata); + /** * Set relative mouse mode for a window. * @@ -509,6 +576,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_CaptureMouse(bool enabled); * * \since This function is available since SDL 3.2.0. * + * \sa SDL_CreateAnimatedCursor * \sa SDL_CreateColorCursor * \sa SDL_CreateSystemCursor * \sa SDL_DestroyCursor @@ -522,15 +590,17 @@ extern SDL_DECLSPEC SDL_Cursor * SDLCALL SDL_CreateCursor(const Uint8 *data, /** * Create a color cursor. * - * If this function is passed a surface with alternate representations, the - * surface will be interpreted as the content to be used for 100% display - * scale, and the alternate representations will be used for high DPI - * situations. For example, if the original surface is 32x32, then on a 2x - * macOS display or 200% display scale on Windows, a 64x64 version of the - * image will be used, if available. If a matching version of the image isn't - * available, the closest larger size image will be downscaled to the - * appropriate size and be used instead, if available. Otherwise, the closest - * smaller image will be upscaled and be used instead. + * If this function is passed a surface with alternate representations added + * with SDL_AddSurfaceAlternateImage(), the surface will be interpreted as the + * content to be used for 100% display scale, and the alternate + * representations will be used for high DPI situations if + * SDL_HINT_MOUSE_DPI_SCALE_CURSORS is enabled. For example, if the original + * surface is 32x32, then on a 2x macOS display or 200% display scale on + * Windows, a 64x64 version of the image will be used, if available. If a + * matching version of the image isn't available, the closest larger size + * image will be downscaled to the appropriate size and be used instead, if + * available. Otherwise, the closest smaller image will be upscaled and be + * used instead. * * \param surface an SDL_Surface structure representing the cursor image. * \param hot_x the x position of the cursor hot spot. @@ -542,6 +612,8 @@ extern SDL_DECLSPEC SDL_Cursor * SDLCALL SDL_CreateCursor(const Uint8 *data, * * \since This function is available since SDL 3.2.0. * + * \sa SDL_AddSurfaceAlternateImage + * \sa SDL_CreateAnimatedCursor * \sa SDL_CreateCursor * \sa SDL_CreateSystemCursor * \sa SDL_DestroyCursor @@ -551,6 +623,57 @@ extern SDL_DECLSPEC SDL_Cursor * SDLCALL SDL_CreateColorCursor(SDL_Surface *surf int hot_x, int hot_y); +/** + * Create an animated color cursor. + * + * Animated cursors are composed of a sequential array of frames, specified as + * surfaces and durations in an array of SDL_CursorFrameInfo structs. The hot + * spot coordinates are universal to all frames, and all frames must have the + * same dimensions. + * + * Frame durations are specified in milliseconds. A duration of 0 implies an + * infinite frame time, and the animation will stop on that frame. To create a + * one-shot animation, set the duration of the last frame in the sequence to + * 0. + * + * If this function is passed surfaces with alternate representations added + * with SDL_AddSurfaceAlternateImage(), the surfaces will be interpreted as + * the content to be used for 100% display scale, and the alternate + * representations will be used for high DPI situations. For example, if the + * original surfaces are 32x32, then on a 2x macOS display or 200% display + * scale on Windows, a 64x64 version of the image will be used, if available. + * If a matching version of the image isn't available, the closest larger size + * image will be downscaled to the appropriate size and be used instead, if + * available. Otherwise, the closest smaller image will be upscaled and be + * used instead. + * + * If the underlying platform does not support animated cursors, this function + * will fall back to creating a static color cursor using the first frame in + * the sequence. + * + * \param frames an array of cursor images composing the animation. + * \param frame_count the number of frames in the sequence. + * \param hot_x the x position of the cursor hot spot. + * \param hot_y the y position of the cursor hot spot. + * \returns the new cursor on success or NULL on failure; call SDL_GetError() + * for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_AddSurfaceAlternateImage + * \sa SDL_CreateCursor + * \sa SDL_CreateColorCursor + * \sa SDL_CreateSystemCursor + * \sa SDL_DestroyCursor + * \sa SDL_SetCursor + */ +extern SDL_DECLSPEC SDL_Cursor *SDLCALL SDL_CreateAnimatedCursor(SDL_CursorFrameInfo *frames, + int frame_count, + int hot_x, + int hot_y); + /** * Create a system cursor. * @@ -608,7 +731,7 @@ extern SDL_DECLSPEC SDL_Cursor * SDLCALL SDL_GetCursor(void); * You do not have to call SDL_DestroyCursor() on the return value, but it is * safe to do so. * - * \returns the default cursor on success or NULL on failuree; call + * \returns the default cursor on success or NULL on failure; call * SDL_GetError() for more information. * * \threadsafety This function should only be called on the main thread. @@ -629,6 +752,7 @@ extern SDL_DECLSPEC SDL_Cursor * SDLCALL SDL_GetDefaultCursor(void); * * \since This function is available since SDL 3.2.0. * + * \sa SDL_CreateAnimatedCursor * \sa SDL_CreateColorCursor * \sa SDL_CreateCursor * \sa SDL_CreateSystemCursor diff --git a/libs/SDL3/include/SDL3/SDL_mutex.h b/libs/SDL3/include/SDL3/SDL_mutex.h index c88ec15..e477e2d 100644 --- a/libs/SDL3/include/SDL3/SDL_mutex.h +++ b/libs/SDL3/include/SDL3/SDL_mutex.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -308,6 +308,8 @@ typedef struct SDL_Mutex SDL_Mutex; * \returns the initialized and unlocked mutex 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_DestroyMutex @@ -334,6 +336,8 @@ extern SDL_DECLSPEC SDL_Mutex * SDLCALL SDL_CreateMutex(void); * * \param mutex the mutex to lock. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_TryLockMutex @@ -355,12 +359,14 @@ extern SDL_DECLSPEC void SDLCALL SDL_LockMutex(SDL_Mutex *mutex) SDL_ACQUIRE(mut * \param mutex the mutex to try to lock. * \returns true on success, false if the mutex would block. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LockMutex * \sa SDL_UnlockMutex */ -extern SDL_DECLSPEC bool SDLCALL SDL_TryLockMutex(SDL_Mutex *mutex) SDL_TRY_ACQUIRE(0, mutex); +extern SDL_DECLSPEC bool SDLCALL SDL_TryLockMutex(SDL_Mutex *mutex) SDL_TRY_ACQUIRE(true, mutex); /** * Unlock the mutex. @@ -374,6 +380,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_TryLockMutex(SDL_Mutex *mutex) SDL_TRY_ACQU * * \param mutex the mutex to unlock. * + * \threadsafety This call must be paired with a previous locking call on the same thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LockMutex @@ -392,6 +400,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnlockMutex(SDL_Mutex *mutex) SDL_RELEASE(m * * \param mutex the mutex to destroy. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CreateMutex @@ -457,6 +467,8 @@ typedef struct SDL_RWLock SDL_RWLock; * \returns the initialized and unlocked read/write lock 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_DestroyRWLock @@ -497,6 +509,8 @@ extern SDL_DECLSPEC SDL_RWLock * SDLCALL SDL_CreateRWLock(void); * * \param rwlock the read/write lock to lock. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LockRWLockForWriting @@ -528,6 +542,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_LockRWLockForReading(SDL_RWLock *rwlock) SD * * \param rwlock the read/write lock to lock. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LockRWLockForReading @@ -553,13 +569,15 @@ extern SDL_DECLSPEC void SDLCALL SDL_LockRWLockForWriting(SDL_RWLock *rwlock) SD * \param rwlock the rwlock to try to lock. * \returns true on success, false if the lock would block. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LockRWLockForReading * \sa SDL_TryLockRWLockForWriting * \sa SDL_UnlockRWLock */ -extern SDL_DECLSPEC bool SDLCALL SDL_TryLockRWLockForReading(SDL_RWLock *rwlock) SDL_TRY_ACQUIRE_SHARED(0, rwlock); +extern SDL_DECLSPEC bool SDLCALL SDL_TryLockRWLockForReading(SDL_RWLock *rwlock) SDL_TRY_ACQUIRE_SHARED(true, rwlock); /** * Try to lock a read/write lock _for writing_ without blocking. @@ -583,13 +601,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_TryLockRWLockForReading(SDL_RWLock *rwlock) * \param rwlock the rwlock to try to lock. * \returns true on success, false if the lock would block. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LockRWLockForWriting * \sa SDL_TryLockRWLockForReading * \sa SDL_UnlockRWLock */ -extern SDL_DECLSPEC bool SDLCALL SDL_TryLockRWLockForWriting(SDL_RWLock *rwlock) SDL_TRY_ACQUIRE(0, rwlock); +extern SDL_DECLSPEC bool SDLCALL SDL_TryLockRWLockForWriting(SDL_RWLock *rwlock) SDL_TRY_ACQUIRE(true, rwlock); /** * Unlock the read/write lock. @@ -607,6 +627,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_TryLockRWLockForWriting(SDL_RWLock *rwlock) * * \param rwlock the rwlock to unlock. * + * \threadsafety This call must be paired with a previous locking call on the same thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LockRWLockForReading @@ -627,6 +649,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnlockRWLock(SDL_RWLock *rwlock) SDL_RELEAS * * \param rwlock the rwlock to destroy. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CreateRWLock @@ -670,6 +694,8 @@ typedef struct SDL_Semaphore SDL_Semaphore; * \returns a new semaphore 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_DestroySemaphore @@ -689,6 +715,8 @@ extern SDL_DECLSPEC SDL_Semaphore * SDLCALL SDL_CreateSemaphore(Uint32 initial_v * * \param sem the semaphore to destroy. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CreateSemaphore @@ -707,6 +735,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroySemaphore(SDL_Semaphore *sem); * * \param sem the semaphore wait on. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SignalSemaphore @@ -726,6 +756,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_WaitSemaphore(SDL_Semaphore *sem); * \param sem the semaphore to wait on. * \returns true if the wait succeeds, false if the wait would block. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SignalSemaphore @@ -746,6 +778,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_TryWaitSemaphore(SDL_Semaphore *sem); * indefinitely. * \returns true if the wait succeeds or false if the wait times out. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SignalSemaphore @@ -759,6 +793,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WaitSemaphoreTimeout(SDL_Semaphore *sem, Si * * \param sem the semaphore to increment. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_TryWaitSemaphore @@ -773,6 +809,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_SignalSemaphore(SDL_Semaphore *sem); * \param sem the semaphore to query. * \returns the current value of the semaphore. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC Uint32 SDLCALL SDL_GetSemaphoreValue(SDL_Semaphore *sem); @@ -806,6 +844,8 @@ typedef struct SDL_Condition SDL_Condition; * \returns a new condition variable 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_BroadcastCondition @@ -821,6 +861,8 @@ extern SDL_DECLSPEC SDL_Condition * SDLCALL SDL_CreateCondition(void); * * \param cond the condition variable to destroy. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CreateCondition @@ -942,7 +984,7 @@ typedef enum SDL_InitStatus * Here is an example of using this: * * ```c - * static SDL_AtomicInitState init; + * static SDL_InitState init; * * bool InitSystem(void) * { diff --git a/libs/SDL3/include/SDL3/SDL_oldnames.h b/libs/SDL3/include/SDL3/SDL_oldnames.h index c93607e..b9f88b2 100644 --- a/libs/SDL3/include/SDL3/SDL_oldnames.h +++ b/libs/SDL3/include/SDL3/SDL_oldnames.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_opengl.h b/libs/SDL3/include/SDL3/SDL_opengl.h index d1b8b02..733f2b5 100644 --- a/libs/SDL3/include/SDL3/SDL_opengl.h +++ b/libs/SDL3/include/SDL3/SDL_opengl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_opengles.h b/libs/SDL3/include/SDL3/SDL_opengles.h index 4fb9a4b..227f51b 100644 --- a/libs/SDL3/include/SDL3/SDL_opengles.h +++ b/libs/SDL3/include/SDL3/SDL_opengles.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_opengles2.h b/libs/SDL3/include/SDL3/SDL_opengles2.h index 365898a..2c74614 100644 --- a/libs/SDL3/include/SDL3/SDL_opengles2.h +++ b/libs/SDL3/include/SDL3/SDL_opengles2.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_pen.h b/libs/SDL3/include/SDL3/SDL_pen.h index 5182eeb..978e0b6 100644 --- a/libs/SDL3/include/SDL3/SDL_pen.h +++ b/libs/SDL3/include/SDL3/SDL_pen.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -28,12 +28,37 @@ * handling, e.g., for input and drawing tablets or suitably equipped mobile / * tablet devices. * - * To get started with pens, simply handle SDL_EVENT_PEN_* events. When a pen - * starts providing input, SDL will assign it a unique SDL_PenID, which will - * remain for the life of the process, as long as the pen stays connected. + * To get started with pens, simply handle pen events: + * + * - SDL_EVENT_PEN_PROXIMITY_IN, SDL_EVENT_PEN_PROXIMITY_OUT + * (SDL_PenProximityEvent) + * - SDL_EVENT_PEN_DOWN, SDL_EVENT_PEN_UP (SDL_PenTouchEvent) + * - SDL_EVENT_PEN_MOTION (SDL_PenMotionEvent) + * - SDL_EVENT_PEN_BUTTON_DOWN, SDL_EVENT_PEN_BUTTON_UP (SDL_PenButtonEvent) + * - SDL_EVENT_PEN_AXIS (SDL_PenAxisEvent) * * Pens may provide more than simple touch input; they might have other axes, * such as pressure, tilt, rotation, etc. + * + * When a pen starts providing input, SDL will assign it a unique SDL_PenID, + * which will remain for the life of the process, as long as the pen stays + * connected. A pen leaving proximity (being taken far enough away from the + * digitizer tablet that it no longer reponds) and then coming back should + * fire proximity events, but the SDL_PenID should remain consistent. + * Unplugging the digitizer and reconnecting may cause future input to have a + * new SDL_PenID, as SDL may not know that this is the same hardware. + * + * Please note that various platforms vary wildly in how (and how well) they + * support pen input. If your pen supports some piece of functionality but SDL + * doesn't seem to, it might actually be the operating system's fault. For + * example, some platforms can manage multiple devices at the same time, but + * others will make any connected pens look like a single logical device, much + * how all USB mice connected to a computer will move the same system cursor. + * cursor. Other platforms might not support pen buttons, or the distance + * axis, etc. Very few platforms can even report _what_ functionality the pen + * supports in the first place, so best practices is to either build UI to let + * the user configure their pens, or be prepared to handle new functionality + * for a pen the first time an event is reported. */ #ifndef SDL_pen_h_ @@ -43,6 +68,7 @@ #include #include +#include /* Set up for C function definitions, even when using C++ */ #ifdef __cplusplus extern "C" { @@ -55,7 +81,12 @@ extern "C" { * * These show up in pen events when SDL sees input from them. They remain * consistent as long as SDL can recognize a tool to be the same pen; but if a - * pen physically leaves the area and returns, it might get a new ID. + * pen's digitizer table is physically detached from the computer, it might + * get a new ID when reconnected, as SDL won't know it's the same device. + * + * These IDs are only stable within a single run of a program; the next time a + * program is run, the pen's ID will likely be different, even if the hardware + * hasn't been disconnected, etc. * * \since This datatype is available since SDL 3.2.0. */ @@ -75,7 +106,6 @@ typedef Uint32 SDL_PenID; */ #define SDL_PEN_TOUCHID ((SDL_TouchID)-2) - /** * Pen input flags, as reported by various pen events' `pen_state` field. * @@ -83,13 +113,14 @@ typedef Uint32 SDL_PenID; */ typedef Uint32 SDL_PenInputFlags; -#define SDL_PEN_INPUT_DOWN (1u << 0) /**< pen is pressed down */ -#define SDL_PEN_INPUT_BUTTON_1 (1u << 1) /**< button 1 is pressed */ -#define SDL_PEN_INPUT_BUTTON_2 (1u << 2) /**< button 2 is pressed */ -#define SDL_PEN_INPUT_BUTTON_3 (1u << 3) /**< button 3 is pressed */ -#define SDL_PEN_INPUT_BUTTON_4 (1u << 4) /**< button 4 is pressed */ -#define SDL_PEN_INPUT_BUTTON_5 (1u << 5) /**< button 5 is pressed */ -#define SDL_PEN_INPUT_ERASER_TIP (1u << 30) /**< eraser tip is used */ +#define SDL_PEN_INPUT_DOWN (1u << 0) /**< pen is pressed down */ +#define SDL_PEN_INPUT_BUTTON_1 (1u << 1) /**< button 1 is pressed */ +#define SDL_PEN_INPUT_BUTTON_2 (1u << 2) /**< button 2 is pressed */ +#define SDL_PEN_INPUT_BUTTON_3 (1u << 3) /**< button 3 is pressed */ +#define SDL_PEN_INPUT_BUTTON_4 (1u << 4) /**< button 4 is pressed */ +#define SDL_PEN_INPUT_BUTTON_5 (1u << 5) /**< button 5 is pressed */ +#define SDL_PEN_INPUT_ERASER_TIP (1u << 30) /**< eraser tip is used */ +#define SDL_PEN_INPUT_IN_PROXIMITY (1u << 31) /**< pen is in proximity (since SDL 3.4.0) */ /** * Pen axis indices. @@ -118,10 +149,50 @@ typedef enum SDL_PenAxis SDL_PEN_AXIS_COUNT /**< Total known pen axis types in this version of SDL. This number may grow in future releases! */ } SDL_PenAxis; +/** + * An enum that describes the type of a pen device. + * + * A "direct" device is a pen that touches a graphic display (like an Apple + * Pencil on an iPad's screen). "Indirect" devices touch an external tablet + * surface that is connected to the machine but is not a display (like a + * lower-end Wacom tablet connected over USB). + * + * Apps may use this information to decide if they should draw a cursor; if + * the pen is touching the screen directly, a cursor doesn't make sense and + * can be in the way, but becomes necessary for indirect devices to know where + * on the display they are interacting. + * + * \since This enum is available since SDL 3.4.0. + */ +typedef enum SDL_PenDeviceType +{ + SDL_PEN_DEVICE_TYPE_INVALID = -1, /**< Not a valid pen device. */ + SDL_PEN_DEVICE_TYPE_UNKNOWN, /**< Don't know specifics of this pen. */ + SDL_PEN_DEVICE_TYPE_DIRECT, /**< Pen touches display. */ + SDL_PEN_DEVICE_TYPE_INDIRECT /**< Pen touches something that isn't the display. */ +} SDL_PenDeviceType; + +/** + * Get the device type of the given pen. + * + * Many platforms do not supply this information, so an app must always be + * prepared to get an SDL_PEN_DEVICE_TYPE_UNKNOWN result. + * + * \param instance_id the pen instance ID. + * \returns the device type of the given pen, or SDL_PEN_DEVICE_TYPE_INVALID + * 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.4.0. + */ +extern SDL_DECLSPEC SDL_PenDeviceType SDLCALL SDL_GetPenDeviceType(SDL_PenID instance_id); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } #endif +#include #endif /* SDL_pen_h_ */ diff --git a/libs/SDL3/include/SDL3/SDL_pixels.h b/libs/SDL3/include/SDL3/SDL_pixels.h index 39596c1..475e5f9 100644 --- a/libs/SDL3/include/SDL3/SDL_pixels.h +++ b/libs/SDL3/include/SDL3/SDL_pixels.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -451,7 +451,7 @@ typedef enum SDL_PackedLayout * expressions with side-effects here. * * \param format an SDL_PixelFormat to check. - * \returns true if the format is 10-bit, false otherwise. + * \returns true if the format is a floating point, false otherwise. * * \threadsafety It is safe to call this macro from any thread. * @@ -1096,7 +1096,7 @@ typedef enum SDL_Colorspace SDL_CHROMA_LOCATION_LEFT), */ SDL_COLORSPACE_RGB_DEFAULT = SDL_COLORSPACE_SRGB, /**< The default colorspace for RGB surfaces if no colorspace is specified */ - SDL_COLORSPACE_YUV_DEFAULT = SDL_COLORSPACE_JPEG /**< The default colorspace for YUV surfaces if no colorspace is specified */ + SDL_COLORSPACE_YUV_DEFAULT = SDL_COLORSPACE_BT601_LIMITED /**< The default colorspace for YUV surfaces if no colorspace is specified */ } SDL_Colorspace; /** @@ -1379,7 +1379,7 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_MapRGBA(const SDL_PixelFormatDetails *for * (e.g., a completely white pixel in 16-bit RGB565 format would return [0xff, * 0xff, 0xff] not [0xf8, 0xfc, 0xf8]). * - * \param pixel a pixel value. + * \param pixelvalue a pixel value. * \param format a pointer to SDL_PixelFormatDetails describing the pixel * format. * \param palette an optional palette for indexed formats, may be NULL. @@ -1397,7 +1397,7 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_MapRGBA(const SDL_PixelFormatDetails *for * \sa SDL_MapRGB * \sa SDL_MapRGBA */ -extern SDL_DECLSPEC void SDLCALL SDL_GetRGB(Uint32 pixel, const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 *r, Uint8 *g, Uint8 *b); +extern SDL_DECLSPEC void SDLCALL SDL_GetRGB(Uint32 pixelvalue, const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 *r, Uint8 *g, Uint8 *b); /** * Get RGBA values from a pixel in the specified format. @@ -1410,7 +1410,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_GetRGB(Uint32 pixel, const SDL_PixelFormatD * If the surface has no alpha component, the alpha will be returned as 0xff * (100% opaque). * - * \param pixel a pixel value. + * \param pixelvalue a pixel value. * \param format a pointer to SDL_PixelFormatDetails describing the pixel * format. * \param palette an optional palette for indexed formats, may be NULL. @@ -1429,7 +1429,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_GetRGB(Uint32 pixel, const SDL_PixelFormatD * \sa SDL_MapRGB * \sa SDL_MapRGBA */ -extern SDL_DECLSPEC void SDLCALL SDL_GetRGBA(Uint32 pixel, const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a); +extern SDL_DECLSPEC void SDLCALL SDL_GetRGBA(Uint32 pixelvalue, const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a); /* Ends C function definitions when using C++ */ diff --git a/libs/SDL3/include/SDL3/SDL_platform.h b/libs/SDL3/include/SDL3/SDL_platform.h index e40f009..9ad380b 100644 --- a/libs/SDL3/include/SDL3/SDL_platform.h +++ b/libs/SDL3/include/SDL3/SDL_platform.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -51,6 +51,8 @@ extern "C" { * \returns the name of the platform. If the correct platform name is not * available, returns a string beginning with the text "Unknown". * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC const char * SDLCALL SDL_GetPlatform(void); diff --git a/libs/SDL3/include/SDL3/SDL_platform_defines.h b/libs/SDL3/include/SDL3/SDL_platform_defines.h index 6b240a8..7963149 100644 --- a/libs/SDL3/include/SDL3/SDL_platform_defines.h +++ b/libs/SDL3/include/SDL3/SDL_platform_defines.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -190,7 +190,7 @@ #if TARGET_OS_VISION /** - * A preprocessor macro that is only defined if compiling for VisionOS. + * A preprocessor macro that is only defined if compiling for visionOS. * * \since This macro is available since SDL 3.2.0. * @@ -202,7 +202,7 @@ #if TARGET_OS_IPHONE /** - * A preprocessor macro that is only defined if compiling for iOS. + * A preprocessor macro that is only defined if compiling for iOS or visionOS. * * \since This macro is available since SDL 3.2.0. * @@ -317,7 +317,7 @@ #define SDL_PLATFORM_CYGWIN 1 #endif -#if defined(_WIN32) || defined(SDL_PLATFORM_CYGWIN) +#if (defined(_WIN32) || defined(SDL_PLATFORM_CYGWIN)) && !defined(__NGAGE__) /** * A preprocessor macro that is only defined if compiling for Windows. @@ -473,4 +473,25 @@ #define SDL_PLATFORM_3DS 1 #endif +#ifdef __NGAGE__ + +/** + * A preprocessor macro that is only defined if compiling for the Nokia + * N-Gage. + * + * \since This macro is available since SDL 3.4.0. + */ +#define SDL_PLATFORM_NGAGE 1 +#endif + +#ifdef __GNU__ + +/** + * A preprocessor macro that is only defined if compiling for GNU/Hurd. + * + * \since This macro is available since SDL 3.4.0. + */ +#define SDL_PLATFORM_HURD 1 +#endif + #endif /* SDL_platform_defines_h_ */ diff --git a/libs/SDL3/include/SDL3/SDL_power.h b/libs/SDL3/include/SDL3/SDL_power.h index 694fb09..0616cc2 100644 --- a/libs/SDL3/include/SDL3/SDL_power.h +++ b/libs/SDL3/include/SDL3/SDL_power.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -88,11 +88,13 @@ typedef enum SDL_PowerState * can't determine a value or there is no battery. * \param percent a pointer filled in with the percentage of battery life * left, between 0 and 100, or NULL to ignore. This will be - * filled in with -1 we can't determine a value or there is no - * battery. + * filled in with -1 when we can't determine a value or there + * is no battery. * \returns the current battery state or `SDL_POWERSTATE_ERROR` 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 SDL_PowerState SDLCALL SDL_GetPowerInfo(int *seconds, int *percent); diff --git a/libs/SDL3/include/SDL3/SDL_process.h b/libs/SDL3/include/SDL3/SDL_process.h index 511b2f9..b797890 100644 --- a/libs/SDL3/include/SDL3/SDL_process.h +++ b/libs/SDL3/include/SDL3/SDL_process.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -166,6 +166,9 @@ typedef enum SDL_ProcessIO * - `SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER`: an SDL_Environment * pointer. If this property is set, it will be the entire environment for * the process, otherwise the current environment is used. + * - `SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING`: a UTF-8 encoded + * string representing the working directory for the process, defaults to + * the current working directory. * - `SDL_PROP_PROCESS_CREATE_STDIN_NUMBER`: an SDL_ProcessIO value describing * where standard input for the process comes from, defaults to * `SDL_PROCESS_STDIO_NULL`. @@ -192,6 +195,12 @@ typedef enum SDL_ProcessIO * run in the background. In this case the default input and output is * `SDL_PROCESS_STDIO_NULL` and the exitcode of the process is not * available, and will always be 0. + * - `SDL_PROP_PROCESS_CREATE_CMDLINE_STRING`: a string containing the program + * to run and any parameters. This string is passed directly to + * `CreateProcess` on Windows, and does nothing on other platforms. This + * property is only important if you want to start programs that does + * non-standard command-line processing, and in most cases using + * `SDL_PROP_PROCESS_CREATE_ARGS_POINTER` is sufficient. * * On POSIX platforms, wait() and waitpid(-1, ...) should not be called, and * SIGCHLD should not be ignored or handled because those would prevent SDL @@ -219,6 +228,7 @@ extern SDL_DECLSPEC SDL_Process * SDLCALL SDL_CreateProcessWithProperties(SDL_Pr #define SDL_PROP_PROCESS_CREATE_ARGS_POINTER "SDL.process.create.args" #define SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER "SDL.process.create.environment" +#define SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING "SDL.process.create.working_directory" #define SDL_PROP_PROCESS_CREATE_STDIN_NUMBER "SDL.process.create.stdin_option" #define SDL_PROP_PROCESS_CREATE_STDIN_POINTER "SDL.process.create.stdin_source" #define SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER "SDL.process.create.stdout_option" @@ -227,6 +237,7 @@ extern SDL_DECLSPEC SDL_Process * SDLCALL SDL_CreateProcessWithProperties(SDL_Pr #define SDL_PROP_PROCESS_CREATE_STDERR_POINTER "SDL.process.create.stderr_source" #define SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN "SDL.process.create.stderr_to_stdout" #define SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN "SDL.process.create.background" +#define SDL_PROP_PROCESS_CREATE_CMDLINE_STRING "SDL.process.create.cmdline" /** * Get the properties associated with a process. diff --git a/libs/SDL3/include/SDL3/SDL_properties.h b/libs/SDL3/include/SDL3/SDL_properties.h index 1f47d5f..abd7b2a 100644 --- a/libs/SDL3/include/SDL3/SDL_properties.h +++ b/libs/SDL3/include/SDL3/SDL_properties.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -59,7 +59,7 @@ extern "C" { #endif /** - * SDL properties ID + * An ID that represents a properties set. * * \since This datatype is available since SDL 3.2.0. */ @@ -80,12 +80,39 @@ typedef enum SDL_PropertyType SDL_PROPERTY_TYPE_BOOLEAN } SDL_PropertyType; +/** + * A generic property for naming things. + * + * This property is intended to be added to any SDL_PropertiesID that needs a + * generic name associated with the property set. It is not guaranteed that + * any property set will include this key, but it is convenient to have a + * standard key that any piece of code could reasonably agree to use. + * + * For example, the properties associated with an SDL_Texture might have a + * name string of "player sprites", or an SDL_AudioStream might have + * "background music", etc. This might also be useful for an SDL_IOStream to + * list the path to its asset. + * + * There is no format for the value set with this key; it is expected to be + * human-readable and informational in nature, possibly for logging or + * debugging purposes. + * + * SDL does not currently set this property on any objects it creates, but + * this may change in later versions; it is currently expected that apps and + * external libraries will take advantage of it, when appropriate. + * + * \since This macro is available since SDL 3.4.0. + */ +#define SDL_PROP_NAME_STRING "SDL.name" + /** * Get the global SDL properties. * * \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_GetGlobalProperties(void); @@ -119,7 +146,9 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_CreateProperties(void); * \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. + * \threadsafety It is safe to call this function from any thread. This + * function acquires simultaneous mutex locks on both the source + * and destination property sets. * * \since This function is available since SDL 3.2.0. */ diff --git a/libs/SDL3/include/SDL3/SDL_rect.h b/libs/SDL3/include/SDL3/SDL_rect.h index eb2d34a..883ca8d 100644 --- a/libs/SDL3/include/SDL3/SDL_rect.h +++ b/libs/SDL3/include/SDL3/SDL_rect.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -88,8 +88,11 @@ typedef struct SDL_Rect /** - * A rectangle, with the origin at the upper left (using floating point - * values). + * A rectangle stored using floating point values. + * + * The origin of the coordinate space is in the top-left, with increasing + * values moving down and right. The properties `x` and `y` represent the + * coordinates of the top-left corner of the rectangle. * * \since This struct is available since SDL 3.2.0. * @@ -125,10 +128,10 @@ typedef struct SDL_FRect */ SDL_FORCE_INLINE void SDL_RectToFRect(const SDL_Rect *rect, SDL_FRect *frect) { - frect->x = (float)rect->x; - frect->y = (float)rect->y; - frect->w = (float)rect->w; - frect->h = (float)rect->h; + frect->x = SDL_static_cast(float, rect->x); + frect->y = SDL_static_cast(float, rect->y); + frect->w = SDL_static_cast(float, rect->w); + frect->h = SDL_static_cast(float, rect->h); } /** @@ -234,6 +237,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasRectIntersection(const SDL_Rect *A, cons * rectangles `A` and `B`. * \returns true if there is an intersection, 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_HasRectIntersection @@ -250,6 +255,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectIntersection(const SDL_Rect *A, cons * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectUnion(const SDL_Rect *A, const SDL_Rect *B, SDL_Rect *result); @@ -269,6 +276,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectUnion(const SDL_Rect *A, const SDL_R * \returns true if any points were enclosed or false if all the points were * outside of the clipping rectangle. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectEnclosingPoints(const SDL_Point *points, int count, const SDL_Rect *clip, SDL_Rect *result); @@ -289,6 +298,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectEnclosingPoints(const SDL_Point *poi * \param Y2 a pointer to the ending Y-coordinate of the line. * \returns true if there is an intersection, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectAndLineIntersection(const SDL_Rect *rect, int *X1, int *Y1, int *X2, int *Y2); @@ -324,7 +335,7 @@ SDL_FORCE_INLINE bool SDL_PointInRectFloat(const SDL_FPoint *p, const SDL_FRect } /** - * Determine whether a floating point rectangle can contain any point. + * Determine whether a floating point rectangle takes no space. * * A rectangle is considered "empty" for this function if `r` is NULL, or if * `r`'s width and/or height are < 0.0f. @@ -420,6 +431,8 @@ SDL_FORCE_INLINE bool SDL_RectsEqualFloat(const SDL_FRect *a, const SDL_FRect *b * \param B an SDL_FRect structure representing the second rectangle. * \returns true if there is an intersection, 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_GetRectIntersection @@ -437,6 +450,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasRectIntersectionFloat(const SDL_FRect *A * rectangles `A` and `B`. * \returns true if there is an intersection, 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_HasRectIntersectionFloat @@ -453,6 +468,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectIntersectionFloat(const SDL_FRect *A * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectUnionFloat(const SDL_FRect *A, const SDL_FRect *B, SDL_FRect *result); @@ -473,6 +490,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectUnionFloat(const SDL_FRect *A, const * \returns true if any points were enclosed or false if all the points were * outside of the clipping rectangle. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectEnclosingPointsFloat(const SDL_FPoint *points, int count, const SDL_FRect *clip, SDL_FRect *result); @@ -494,6 +513,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectEnclosingPointsFloat(const SDL_FPoin * \param Y2 a pointer to the ending Y-coordinate of the line. * \returns true if there is an intersection, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectAndLineIntersectionFloat(const SDL_FRect *rect, float *X1, float *Y1, float *X2, float *Y2); diff --git a/libs/SDL3/include/SDL3/SDL_render.h b/libs/SDL3/include/SDL3/SDL_render.h index c9d184c..87257d1 100644 --- a/libs/SDL3/include/SDL3/SDL_render.h +++ b/libs/SDL3/include/SDL3/SDL_render.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -39,9 +39,9 @@ * may also be stretched with linear interpolation. * * This API is designed to accelerate simple 2D operations. You may want more - * functionality such as polygons and particle effects and in that case you - * should use SDL's OpenGL/Direct3D support, the SDL3 GPU API, or one of the - * many good 3D engines. + * functionality such as 3D polygons and particle effects, and in that case + * you should use SDL's OpenGL/Direct3D support, the SDL3 GPU API, or one of + * the many good 3D engines. * * These functions must be called from the main thread. See this bug for * details: https://github.com/libsdl-org/SDL/issues/986 @@ -59,6 +59,7 @@ #include #include #include +#include #include /* Set up for C function definitions, even when using C++ */ @@ -73,6 +74,13 @@ extern "C" { */ #define SDL_SOFTWARE_RENDERER "software" +/** + * The name of the GPU renderer. + * + * \since This macro is available since SDL 3.4.0. + */ +#define SDL_GPU_RENDERER "gpu" + /** * Vertex structure. * @@ -97,6 +105,25 @@ typedef enum SDL_TextureAccess SDL_TEXTUREACCESS_TARGET /**< Texture can be used as a render target */ } SDL_TextureAccess; +/** + * The addressing mode for a texture when used in SDL_RenderGeometry(). + * + * This affects how texture coordinates are interpreted outside of [0, 1] + * + * Texture wrapping is always supported for power of two texture sizes, and is + * supported for other texture sizes if + * SDL_PROP_RENDERER_TEXTURE_WRAPPING_BOOLEAN is set to true. + * + * \since This enum is available since SDL 3.4.0. + */ +typedef enum SDL_TextureAddressMode +{ + SDL_TEXTURE_ADDRESS_INVALID = -1, + SDL_TEXTURE_ADDRESS_AUTO, /**< Wrapping is enabled if texture coordinates are outside [0, 1], this is the default */ + SDL_TEXTURE_ADDRESS_CLAMP, /**< Texture coordinates are clamped to the [0, 1] range */ + SDL_TEXTURE_ADDRESS_WRAP /**< The texture is repeated (tiled) */ +} SDL_TextureAddressMode; + /** * How the logical size is mapped to the output. * @@ -106,7 +133,7 @@ typedef enum SDL_RendererLogicalPresentation { SDL_LOGICAL_PRESENTATION_DISABLED, /**< There is no logical size in effect */ SDL_LOGICAL_PRESENTATION_STRETCH, /**< The rendered content is stretched to the output resolution */ - SDL_LOGICAL_PRESENTATION_LETTERBOX, /**< The rendered content is fit to the largest dimension and the other dimension is letterboxed with black bars */ + SDL_LOGICAL_PRESENTATION_LETTERBOX, /**< The rendered content is fit to the largest dimension and the other dimension is letterboxed with the clear color */ SDL_LOGICAL_PRESENTATION_OVERSCAN, /**< The rendered content is fit to the smallest dimension and the other dimension extends beyond the output bounds */ SDL_LOGICAL_PRESENTATION_INTEGER_SCALE /**< The rendered content is scaled up by integer multiples to fit the output resolution */ } SDL_RendererLogicalPresentation; @@ -267,6 +294,17 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window *window * present synchronized with the refresh rate. This property can take any * value that is supported by SDL_SetRenderVSync() for the renderer. * + * With the SDL GPU renderer (since SDL 3.4.0): + * + * - `SDL_PROP_RENDERER_CREATE_GPU_DEVICE_POINTER`: the device to use with the + * renderer, optional. + * - `SDL_PROP_RENDERER_CREATE_GPU_SHADERS_SPIRV_BOOLEAN`: the app is able to + * provide SPIR-V shaders to SDL_GPURenderState, optional. + * - `SDL_PROP_RENDERER_CREATE_GPU_SHADERS_DXIL_BOOLEAN`: the app is able to + * provide DXIL shaders to SDL_GPURenderState, optional. + * - `SDL_PROP_RENDERER_CREATE_GPU_SHADERS_MSL_BOOLEAN`: the app is able to + * provide MSL shaders to SDL_GPURenderState, optional. + * * With the vulkan renderer: * * - `SDL_PROP_RENDERER_CREATE_VULKAN_INSTANCE_POINTER`: the VkInstance to use @@ -303,6 +341,10 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRendererWithProperties(SDL_ #define SDL_PROP_RENDERER_CREATE_SURFACE_POINTER "SDL.renderer.create.surface" #define SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER "SDL.renderer.create.output_colorspace" #define SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER "SDL.renderer.create.present_vsync" +#define SDL_PROP_RENDERER_CREATE_GPU_DEVICE_POINTER "SDL.renderer.create.gpu.device" +#define SDL_PROP_RENDERER_CREATE_GPU_SHADERS_SPIRV_BOOLEAN "SDL.renderer.create.gpu.shaders_spirv" +#define SDL_PROP_RENDERER_CREATE_GPU_SHADERS_DXIL_BOOLEAN "SDL.renderer.create.gpu.shaders_dxil" +#define SDL_PROP_RENDERER_CREATE_GPU_SHADERS_MSL_BOOLEAN "SDL.renderer.create.gpu.shaders_msl" #define SDL_PROP_RENDERER_CREATE_VULKAN_INSTANCE_POINTER "SDL.renderer.create.vulkan.instance" #define SDL_PROP_RENDERER_CREATE_VULKAN_SURFACE_NUMBER "SDL.renderer.create.vulkan.surface" #define SDL_PROP_RENDERER_CREATE_VULKAN_PHYSICAL_DEVICE_POINTER "SDL.renderer.create.vulkan.physical_device" @@ -310,6 +352,53 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRendererWithProperties(SDL_ #define SDL_PROP_RENDERER_CREATE_VULKAN_GRAPHICS_QUEUE_FAMILY_INDEX_NUMBER "SDL.renderer.create.vulkan.graphics_queue_family_index" #define SDL_PROP_RENDERER_CREATE_VULKAN_PRESENT_QUEUE_FAMILY_INDEX_NUMBER "SDL.renderer.create.vulkan.present_queue_family_index" +/** + * Create a 2D GPU rendering context. + * + * The GPU device to use is passed in as a parameter. If this is NULL, then a + * device will be created normally and can be retrieved using + * SDL_GetGPURendererDevice(). + * + * The window to use is passed in as a parameter. If this is NULL, then this + * will become an offscreen renderer. In that case, you should call + * SDL_SetRenderTarget() to setup rendering to a texture, and then call + * SDL_RenderPresent() normally to complete drawing a frame. + * + * \param device the GPU device to use with the renderer, or NULL to create a + * device. + * \param window the window where rendering is displayed, or NULL to create an + * offscreen renderer. + * \returns a valid rendering context or NULL if there was an error; call + * SDL_GetError() for more information. + * + * \threadsafety If this function is called with a valid GPU device, it should + * be called on the thread that created the device. If this + * function is called with a valid window, it should be called + * on the thread that created the window. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreateRendererWithProperties + * \sa SDL_GetGPURendererDevice + * \sa SDL_CreateGPUShader + * \sa SDL_CreateGPURenderState + * \sa SDL_SetGPURenderState + */ +extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateGPURenderer(SDL_GPUDevice *device, SDL_Window *window); + +/** + * Return the GPU device used by a renderer. + * + * \param renderer the rendering context. + * \returns the GPU device used by the renderer, or NULL if the renderer is + * not a GPU renderer; 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. + */ +extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_GetGPURendererDevice(SDL_Renderer *renderer); + /** * Create a 2D software rendering context for a surface. * @@ -323,7 +412,7 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRendererWithProperties(SDL_ * \returns a valid rendering context or NULL if there was an error; call * SDL_GetError() for more information. * - * \threadsafety This function should only be called on the main thread. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. * @@ -389,6 +478,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetRendererName(SDL_Renderer *rende * - `SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER`: a (const SDL_PixelFormat *) * array of pixel formats, terminated with SDL_PIXELFORMAT_UNKNOWN, * representing the available texture formats for this renderer. + * - `SDL_PROP_RENDERER_TEXTURE_WRAPPING_BOOLEAN`: true if the renderer + * supports SDL_TEXTURE_ADDRESS_WRAP on non-power-of-two textures. * - `SDL_PROP_RENDERER_OUTPUT_COLORSPACE_NUMBER`: an SDL_Colorspace value * describing the colorspace for output to the display, defaults to * SDL_COLORSPACE_SRGB. @@ -465,6 +556,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetRendererProperties(SDL_Rende #define SDL_PROP_RENDERER_VSYNC_NUMBER "SDL.renderer.vsync" #define SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER "SDL.renderer.max_texture_size" #define SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER "SDL.renderer.texture_formats" +#define SDL_PROP_RENDERER_TEXTURE_WRAPPING_BOOLEAN "SDL.renderer.texture_wrapping" #define SDL_PROP_RENDERER_OUTPUT_COLORSPACE_NUMBER "SDL.renderer.output_colorspace" #define SDL_PROP_RENDERER_HDR_ENABLED_BOOLEAN "SDL.renderer.HDR_enabled" #define SDL_PROP_RENDERER_SDR_WHITE_POINT_FLOAT "SDL.renderer.SDR_white_point" @@ -601,6 +693,9 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureFromSurface(SDL_Rende * pixels, required * - `SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER`: the height of the texture in * pixels, required + * - `SDL_PROP_TEXTURE_CREATE_PALETTE_POINTER`: an SDL_Palette to use with + * palettized texture formats. This can be set later with + * SDL_SetTexturePalette() * - `SDL_PROP_TEXTURE_CREATE_SDR_WHITE_POINT_FLOAT`: for HDR10 and floating * point textures, this defines the value of 100% diffuse white, with higher * values being displayed in the High Dynamic Range headroom. This defaults @@ -673,9 +768,24 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureFromSurface(SDL_Rende * * With the vulkan renderer: * - * - `SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER`: the VkImage with layout - * VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL associated with the texture, if - * you want to wrap an existing texture. + * - `SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER`: the VkImage associated + * with the texture, if you want to wrap an existing texture. + * - `SDL_PROP_TEXTURE_CREATE_VULKAN_LAYOUT_NUMBER`: the VkImageLayout for the + * VkImage, defaults to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL. + * + * With the GPU renderer: + * + * - `SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_POINTER`: the SDL_GPUTexture + * associated with the texture, if you want to wrap an existing texture. + * - `SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_UV_NUMBER`: the SDL_GPUTexture + * associated with the UV plane of an NV12 texture, if you want to wrap an + * existing texture. + * - `SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_U_NUMBER`: the SDL_GPUTexture + * associated with the U plane of a YUV texture, if you want to wrap an + * existing texture. + * - `SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_V_NUMBER`: the SDL_GPUTexture + * associated with the V plane of a YUV texture, if you want to wrap an + * existing texture. * * \param renderer the rendering context. * \param props the properties to use. @@ -695,29 +805,35 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureFromSurface(SDL_Rende */ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_PropertiesID props); -#define SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER "SDL.texture.create.colorspace" -#define SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER "SDL.texture.create.format" -#define SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER "SDL.texture.create.access" -#define SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER "SDL.texture.create.width" -#define SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER "SDL.texture.create.height" -#define SDL_PROP_TEXTURE_CREATE_SDR_WHITE_POINT_FLOAT "SDL.texture.create.SDR_white_point" -#define SDL_PROP_TEXTURE_CREATE_HDR_HEADROOM_FLOAT "SDL.texture.create.HDR_headroom" -#define SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_POINTER "SDL.texture.create.d3d11.texture" -#define SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_U_POINTER "SDL.texture.create.d3d11.texture_u" -#define SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_V_POINTER "SDL.texture.create.d3d11.texture_v" -#define SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_POINTER "SDL.texture.create.d3d12.texture" -#define SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_U_POINTER "SDL.texture.create.d3d12.texture_u" -#define SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_V_POINTER "SDL.texture.create.d3d12.texture_v" -#define SDL_PROP_TEXTURE_CREATE_METAL_PIXELBUFFER_POINTER "SDL.texture.create.metal.pixelbuffer" -#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_NUMBER "SDL.texture.create.opengl.texture" -#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_UV_NUMBER "SDL.texture.create.opengl.texture_uv" -#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_U_NUMBER "SDL.texture.create.opengl.texture_u" -#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_V_NUMBER "SDL.texture.create.opengl.texture_v" -#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER "SDL.texture.create.opengles2.texture" -#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_UV_NUMBER "SDL.texture.create.opengles2.texture_uv" -#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_U_NUMBER "SDL.texture.create.opengles2.texture_u" -#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_V_NUMBER "SDL.texture.create.opengles2.texture_v" -#define SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER "SDL.texture.create.vulkan.texture" +#define SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER "SDL.texture.create.colorspace" +#define SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER "SDL.texture.create.format" +#define SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER "SDL.texture.create.access" +#define SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER "SDL.texture.create.width" +#define SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER "SDL.texture.create.height" +#define SDL_PROP_TEXTURE_CREATE_PALETTE_POINTER "SDL.texture.create.palette" +#define SDL_PROP_TEXTURE_CREATE_SDR_WHITE_POINT_FLOAT "SDL.texture.create.SDR_white_point" +#define SDL_PROP_TEXTURE_CREATE_HDR_HEADROOM_FLOAT "SDL.texture.create.HDR_headroom" +#define SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_POINTER "SDL.texture.create.d3d11.texture" +#define SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_U_POINTER "SDL.texture.create.d3d11.texture_u" +#define SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_V_POINTER "SDL.texture.create.d3d11.texture_v" +#define SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_POINTER "SDL.texture.create.d3d12.texture" +#define SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_U_POINTER "SDL.texture.create.d3d12.texture_u" +#define SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_V_POINTER "SDL.texture.create.d3d12.texture_v" +#define SDL_PROP_TEXTURE_CREATE_METAL_PIXELBUFFER_POINTER "SDL.texture.create.metal.pixelbuffer" +#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_NUMBER "SDL.texture.create.opengl.texture" +#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_UV_NUMBER "SDL.texture.create.opengl.texture_uv" +#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_U_NUMBER "SDL.texture.create.opengl.texture_u" +#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_V_NUMBER "SDL.texture.create.opengl.texture_v" +#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER "SDL.texture.create.opengles2.texture" +#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_UV_NUMBER "SDL.texture.create.opengles2.texture_uv" +#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_U_NUMBER "SDL.texture.create.opengles2.texture_u" +#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_V_NUMBER "SDL.texture.create.opengles2.texture_v" +#define SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER "SDL.texture.create.vulkan.texture" +#define SDL_PROP_TEXTURE_CREATE_VULKAN_LAYOUT_NUMBER "SDL.texture.create.vulkan.layout" +#define SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_POINTER "SDL.texture.create.gpu.texture" +#define SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_UV_POINTER "SDL.texture.create.gpu.texture_uv" +#define SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_U_POINTER "SDL.texture.create.gpu.texture_u" +#define SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_V_POINTER "SDL.texture.create.gpu.texture_v" /** * Get the properties associated with a texture. @@ -797,6 +913,17 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureWithProperties(SDL_Re * - `SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_TARGET_NUMBER`: the GLenum for the * texture target (`GL_TEXTURE_2D`, `GL_TEXTURE_EXTERNAL_OES`, etc) * + * With the gpu renderer: + * + * - `SDL_PROP_TEXTURE_GPU_TEXTURE_POINTER`: the SDL_GPUTexture associated + * with the texture + * - `SDL_PROP_TEXTURE_GPU_TEXTURE_UV_POINTER`: the SDL_GPUTexture associated + * with the UV plane of an NV12 texture + * - `SDL_PROP_TEXTURE_GPU_TEXTURE_U_POINTER`: the SDL_GPUTexture associated + * with the U plane of a YUV texture + * - `SDL_PROP_TEXTURE_GPU_TEXTURE_V_POINTER`: the SDL_GPUTexture associated + * with the V plane of a YUV texture + * * \param texture the texture to query. * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. @@ -833,6 +960,10 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetTextureProperties(SDL_Textur #define SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_V_NUMBER "SDL.texture.opengles2.texture_v" #define SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_TARGET_NUMBER "SDL.texture.opengles2.target" #define SDL_PROP_TEXTURE_VULKAN_TEXTURE_NUMBER "SDL.texture.vulkan.texture" +#define SDL_PROP_TEXTURE_GPU_TEXTURE_POINTER "SDL.texture.gpu.texture" +#define SDL_PROP_TEXTURE_GPU_TEXTURE_UV_POINTER "SDL.texture.gpu.texture_uv" +#define SDL_PROP_TEXTURE_GPU_TEXTURE_U_POINTER "SDL.texture.gpu.texture_u" +#define SDL_PROP_TEXTURE_GPU_TEXTURE_V_POINTER "SDL.texture.gpu.texture_v" /** * Get the renderer that created an SDL_Texture. @@ -864,6 +995,43 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_GetRendererFromTexture(SDL_Textur */ extern SDL_DECLSPEC bool SDLCALL SDL_GetTextureSize(SDL_Texture *texture, float *w, float *h); +/** + * Set the palette used by a texture. + * + * Setting the palette keeps an internal reference to the palette, which can + * be safely destroyed afterwards. + * + * A single palette can be shared with many textures. + * + * \param texture the texture to update. + * \param palette the SDL_Palette structure to use. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreatePalette + * \sa SDL_GetTexturePalette + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetTexturePalette(SDL_Texture *texture, SDL_Palette *palette); + +/** + * Get the palette used by a texture. + * + * \param texture the texture to query. + * \returns a pointer to the palette used by the texture, or NULL if there is + * no palette used. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_SetTexturePalette + */ +extern SDL_DECLSPEC SDL_Palette * SDLCALL SDL_GetTexturePalette(SDL_Texture *texture); + /** * Set an additional color value multiplied into render copy operations. * @@ -1386,14 +1554,6 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_GetRenderTarget(SDL_Renderer *rend * specific dimensions but to make fonts look sharp, the app turns off logical * presentation while drawing text, for example. * - * 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(). * @@ -1418,15 +1578,16 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderLogicalPresentation(SDL_Renderer * * Get device independent resolution and presentation mode for rendering. * * 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. + * 0 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. - * \param mode the presentation mode used. + * \param w an int filled with the logical presentation width. + * \param h an int filled with the logical presentation height. + * \param mode a variable filled with the logical presentation mode being + * used. * \returns true on success or false on failure; call SDL_GetError() for more * information. * @@ -1545,8 +1706,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderCoordinatesToWindow(SDL_Renderer *ren * * \param renderer the rendering context. * \param event the event to modify. - * \returns true on success or false on failure; call SDL_GetError() for more - * information. + * \returns true if the event is converted or doesn't need conversion, or + * false on failure; call SDL_GetError() for more information. * * \threadsafety This function should only be called on the main thread. * @@ -2231,9 +2392,48 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTextureTiled(SDL_Renderer *renderer, * \since This function is available since SDL 3.2.0. * * \sa SDL_RenderTexture + * \sa SDL_RenderTexture9GridTiled */ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect); +/** + * Perform a scaled copy using the 9-grid algorithm to the current rendering + * target at subpixel precision. + * + * The pixels in the texture are split into a 3x3 grid, using the different + * corner sizes for each corner, and the sides and center making up the + * remaining pixels. The corners are then scaled using `scale` and fit into + * the corners of the destination rectangle. The sides and center are then + * tiled into place to cover the remaining destination rectangle. + * + * \param renderer the renderer which should copy parts of a texture. + * \param texture the source texture. + * \param srcrect the SDL_Rect structure representing the rectangle to be used + * for the 9-grid, or NULL to use the entire texture. + * \param left_width the width, in pixels, of the left corners in `srcrect`. + * \param right_width the width, in pixels, of the right corners in `srcrect`. + * \param top_height the height, in pixels, of the top corners in `srcrect`. + * \param bottom_height the height, in pixels, of the bottom corners in + * `srcrect`. + * \param scale the scale used to transform the corner of `srcrect` into the + * corner of `dstrect`, or 0.0f for an unscaled copy. + * \param dstrect a pointer to the destination rectangle, or NULL for the + * entire rendering target. + * \param tileScale the scale used to transform the borders and center of + * `srcrect` into the borders and middle of `dstrect`, or + * 1.0f for an unscaled copy. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_RenderTexture + * \sa SDL_RenderTexture9Grid + */ +extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture9GridTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect, float tileScale); + /** * Render a list of triangles, optionally using a texture and indices into the * vertex array Color and alpha modulation is done per vertex @@ -2255,6 +2455,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture9Grid(SDL_Renderer *renderer, * \since This function is available since SDL 3.2.0. * * \sa SDL_RenderGeometryRaw + * \sa SDL_SetRenderTextureAddressMode */ extern SDL_DECLSPEC bool SDLCALL SDL_RenderGeometry(SDL_Renderer *renderer, SDL_Texture *texture, @@ -2287,6 +2488,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderGeometry(SDL_Renderer *renderer, * \since This function is available since SDL 3.2.0. * * \sa SDL_RenderGeometry + * \sa SDL_SetRenderTextureAddressMode */ extern SDL_DECLSPEC bool SDLCALL SDL_RenderGeometryRaw(SDL_Renderer *renderer, SDL_Texture *texture, @@ -2296,6 +2498,48 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderGeometryRaw(SDL_Renderer *renderer, int num_vertices, const void *indices, int num_indices, int size_indices); +/** + * Set the texture addressing mode used in SDL_RenderGeometry(). + * + * \param renderer the rendering context. + * \param u_mode the SDL_TextureAddressMode to use for horizontal texture + * coordinates in SDL_RenderGeometry(). + * \param v_mode the SDL_TextureAddressMode to use for vertical texture + * coordinates in SDL_RenderGeometry(). + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_RenderGeometry + * \sa SDL_RenderGeometryRaw + * \sa SDL_GetRenderTextureAddressMode + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderTextureAddressMode(SDL_Renderer *renderer, SDL_TextureAddressMode u_mode, SDL_TextureAddressMode v_mode); + +/** + * Get the texture addressing mode used in SDL_RenderGeometry(). + * + * \param renderer the rendering context. + * \param u_mode a pointer filled in with the SDL_TextureAddressMode to use + * for horizontal texture coordinates in SDL_RenderGeometry(), + * may be NULL. + * \param v_mode a pointer filled in with the SDL_TextureAddressMode to use + * for vertical texture coordinates in SDL_RenderGeometry(), may + * be NULL. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_SetRenderTextureAddressMode + */ +extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderTextureAddressMode(SDL_Renderer *renderer, SDL_TextureAddressMode *u_mode, SDL_TextureAddressMode *v_mode); + /** * Read pixels from the current rendering target. * @@ -2347,8 +2591,7 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_RenderReadPixels(SDL_Renderer *ren * should not be done; you are only required to change back the rendering * target to default via `SDL_SetRenderTarget(renderer, NULL)` afterwards, as * textures by themselves do not have a concept of backbuffers. Calling - * SDL_RenderPresent while rendering to a texture will still update the screen - * with any current drawing that has been done _to the window itself_. + * SDL_RenderPresent while rendering to a texture will fail. * * \param renderer the rendering context. * \returns true on success or false on failure; call SDL_GetError() for more @@ -2577,8 +2820,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderVSync(SDL_Renderer *renderer, int * Among these limitations: * * - It accepts UTF-8 strings, but will only renders ASCII characters. - * - It has a single, tiny size (8x8 pixels). One can use logical presentation - * or scaling to adjust it, but it will be blurry. + * - It has a single, tiny size (8x8 pixels). You can use logical presentation + * or SDL_SetRenderScale() to adjust it. * - It uses a simple, hardcoded bitmap font. It does not allow different font * selections and it does not support truetype, for proper scaling. * - It does no word-wrapping and does not treat newline characters as a line @@ -2612,7 +2855,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugText(SDL_Renderer *renderer, flo * Draw debug text to an SDL_Renderer. * * This function will render a printf()-style format string to a renderer. - * Note that this is a convinence function for debugging, with severe + * Note that this is a convenience function for debugging, with severe * limitations, and is not intended to be used for production apps and games. * * For the full list of limitations and other useful information, see @@ -2636,6 +2879,148 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugText(SDL_Renderer *renderer, flo */ extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugTextFormat(SDL_Renderer *renderer, float x, float y, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(4); +/** + * Set default scale mode for new textures for given renderer. + * + * When a renderer is created, scale_mode defaults to SDL_SCALEMODE_LINEAR. + * + * \param renderer the renderer to update. + * \param scale_mode the scale mode to change to for new textures. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_GetDefaultTextureScaleMode + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetDefaultTextureScaleMode(SDL_Renderer *renderer, SDL_ScaleMode scale_mode); + +/** + * Get default texture scale mode of the given renderer. + * + * \param renderer the renderer to get data from. + * \param scale_mode a SDL_ScaleMode filled with current default scale mode. + * See SDL_SetDefaultTextureScaleMode() for the meaning of + * the value. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_SetDefaultTextureScaleMode + */ +extern SDL_DECLSPEC bool SDLCALL SDL_GetDefaultTextureScaleMode(SDL_Renderer *renderer, SDL_ScaleMode *scale_mode); + +/** + * A structure specifying the parameters of a GPU render state. + * + * \since This struct is available since SDL 3.4.0. + * + * \sa SDL_CreateGPURenderState + */ +typedef struct SDL_GPURenderStateCreateInfo +{ + SDL_GPUShader *fragment_shader; /**< The fragment shader to use when this render state is active */ + + Sint32 num_sampler_bindings; /**< The number of additional fragment samplers to bind when this render state is active */ + const SDL_GPUTextureSamplerBinding *sampler_bindings; /**< Additional fragment samplers to bind when this render state is active */ + + Sint32 num_storage_textures; /**< The number of storage textures to bind when this render state is active */ + SDL_GPUTexture *const *storage_textures; /**< Storage textures to bind when this render state is active */ + + Sint32 num_storage_buffers; /**< The number of storage buffers to bind when this render state is active */ + SDL_GPUBuffer *const *storage_buffers; /**< Storage buffers to bind when this render state is active */ + + SDL_PropertiesID props; /**< A properties ID for extensions. Should be 0 if no extensions are needed. */ +} SDL_GPURenderStateCreateInfo; + +/** + * A custom GPU render state. + * + * \since This struct is available since SDL 3.4.0. + * + * \sa SDL_CreateGPURenderState + * \sa SDL_SetGPURenderStateFragmentUniforms + * \sa SDL_SetGPURenderState + * \sa SDL_DestroyGPURenderState + */ +typedef struct SDL_GPURenderState SDL_GPURenderState; + +/** + * Create custom GPU render state. + * + * \param renderer the renderer to use. + * \param createinfo a struct describing the GPU render state to create. + * \returns a custom GPU render state or NULL on failure; call SDL_GetError() + * for more information. + * + * \threadsafety This function should be called on the thread that created the + * renderer. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_SetGPURenderStateFragmentUniforms + * \sa SDL_SetGPURenderState + * \sa SDL_DestroyGPURenderState + */ +extern SDL_DECLSPEC SDL_GPURenderState * SDLCALL SDL_CreateGPURenderState(SDL_Renderer *renderer, const SDL_GPURenderStateCreateInfo *createinfo); + +/** + * Set fragment shader uniform variables in a custom GPU render state. + * + * The data is copied and will be pushed using + * SDL_PushGPUFragmentUniformData() during draw call execution. + * + * \param state the state to modify. + * \param slot_index the fragment uniform slot to push data to. + * \param data client data to write. + * \param length the length of the data to write. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * renderer. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetGPURenderStateFragmentUniforms(SDL_GPURenderState *state, Uint32 slot_index, const void *data, Uint32 length); + +/** + * Set custom GPU render state. + * + * This function sets custom GPU render state for subsequent draw calls. This + * allows using custom shaders with the GPU renderer. + * + * \param renderer the renderer to use. + * \param state the state to to use, or NULL to clear custom GPU render state. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * renderer. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetGPURenderState(SDL_Renderer *renderer, SDL_GPURenderState *state); + +/** + * Destroy custom GPU render state. + * + * \param state the state to destroy. + * + * \threadsafety This function should be called on the thread that created the + * renderer. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreateGPURenderState + */ +extern SDL_DECLSPEC void SDLCALL SDL_DestroyGPURenderState(SDL_GPURenderState *state); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/libs/SDL3/include/SDL3/SDL_revision.h b/libs/SDL3/include/SDL3/SDL_revision.h index bcccca1..3441b11 100644 --- a/libs/SDL3/include/SDL3/SDL_revision.h +++ b/libs/SDL3/include/SDL3/SDL_revision.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -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.20-0-g96292a5b4 (" SDL_VENDOR_INFO ")" +#define SDL_REVISION "SDL-release-3.4.2-0-g683181b47 (" SDL_VENDOR_INFO ")" #else -#define SDL_REVISION "release-3.2.20-0-g96292a5b4" +#define SDL_REVISION "SDL-release-3.4.2-0-g683181b47" #endif #endif /* SDL_revision_h_ */ diff --git a/libs/SDL3/include/SDL3/SDL_scancode.h b/libs/SDL3/include/SDL3/SDL_scancode.h index 6e9be47..cd5a137 100644 --- a/libs/SDL3/include/SDL3/SDL_scancode.h +++ b/libs/SDL3/include/SDL3/SDL_scancode.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_sensor.h b/libs/SDL3/include/SDL3/SDL_sensor.h index b220f05..bf89047 100644 --- a/libs/SDL3/include/SDL3/SDL_sensor.h +++ b/libs/SDL3/include/SDL3/SDL_sensor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -138,7 +138,8 @@ typedef enum SDL_SensorType SDL_SENSOR_ACCEL_L, /**< Accelerometer for left Joy-Con controller and Wii nunchuk */ SDL_SENSOR_GYRO_L, /**< Gyroscope for left Joy-Con controller */ SDL_SENSOR_ACCEL_R, /**< Accelerometer for right Joy-Con controller */ - SDL_SENSOR_GYRO_R /**< Gyroscope for right Joy-Con controller */ + SDL_SENSOR_GYRO_R, /**< Gyroscope for right Joy-Con controller */ + SDL_SENSOR_COUNT } SDL_SensorType; diff --git a/libs/SDL3/include/SDL3/SDL_stdinc.h b/libs/SDL3/include/SDL3/SDL_stdinc.h index 7df253f..1c4d8da 100644 --- a/libs/SDL3/include/SDL3/SDL_stdinc.h +++ b/libs/SDL3/include/SDL3/SDL_stdinc.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -49,10 +49,37 @@ #include #include -#include #include #include +/* Most everything except Visual Studio 2008 and earlier has stdint.h now */ +#if defined(_MSC_VER) && (_MSC_VER < 1600) +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; +#ifndef _INTPTR_T_DEFINED +#ifdef _WIN64 +typedef __int64 intptr_t; +#else +typedef int intptr_t; +#endif +#endif +#ifndef _UINTPTR_T_DEFINED +#ifdef _WIN64 +typedef unsigned __int64 uintptr_t; +#else +typedef unsigned int uintptr_t; +#endif +#endif +#else +#include +#endif + #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ defined(SDL_INCLUDE_INTTYPES_H) #include @@ -227,12 +254,6 @@ void *alloca(size_t); /** * Macro useful for building other macros with strings in them. * - * For example: - * - * ```c - * #define LOG_ERROR(X) OutputDebugString(SDL_STRINGIFY_ARG(__FUNCTION__) ": " X "\n")` - * ``` - * * \param arg the text to turn into a string literal. * * \since This macro is available since SDL 3.2.0. @@ -492,7 +513,7 @@ typedef uint64_t Uint64; * and SDL_SECONDS_TO_NS(), and between Windows FILETIME values with * SDL_TimeToWindows() and SDL_TimeFromWindows(). * - * \since This macro is available since SDL 3.2.0. + * \since This datatype is available since SDL 3.2.0. * * \sa SDL_MAX_SINT64 * \sa SDL_MIN_SINT64 @@ -1164,7 +1185,7 @@ typedef struct SDL_alignment_test void *b; } SDL_alignment_test; SDL_COMPILE_TIME_ASSERT(struct_alignment, sizeof(SDL_alignment_test) == (2 * sizeof(void *))); -SDL_COMPILE_TIME_ASSERT(two_s_complement, (int)~(int)0 == (int)(-1)); +SDL_COMPILE_TIME_ASSERT(two_s_complement, SDL_static_cast(int, ~SDL_static_cast(int, 0)) == SDL_static_cast(int, -1)); #endif /* DOXYGEN_SHOULD_IGNORE_THIS */ /** \endcond */ @@ -2119,7 +2140,7 @@ extern SDL_DECLSPEC int SDLCALL SDL_abs(int x); * * \param x the first value to compare. * \param y the second value to compare. - * \returns the lesser of `x` and `y`. + * \returns the greater of `x` and `y`. * * \threadsafety It is safe to call this macro from any thread. * @@ -2640,7 +2661,7 @@ extern SDL_DECLSPEC void * SDLCALL SDL_memset4(void *dst, Uint32 val, size_t dwo * \since This macro is available since SDL 3.2.0. * * \sa SDL_zero - * \sa SDL_zeroa + * \sa SDL_zerop */ #define SDL_zeroa(x) SDL_memset((x), 0, sizeof((x))) @@ -3426,7 +3447,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_utf8strnlen(const char *str, size_t bytes * Convert an integer into a string. * * This requires a radix to specified for string format. Specifying 10 - * produces a decimal number, 16 hexidecimal, etc. Must be in the range of 2 + * produces a decimal number, 16 hexadecimal, etc. Must be in the range of 2 * to 36. * * Note that this function will overflow a buffer if `str` is not large enough @@ -3454,7 +3475,7 @@ extern SDL_DECLSPEC char * SDLCALL SDL_itoa(int value, char *str, int radix); * Convert an unsigned integer into a string. * * This requires a radix to specified for string format. Specifying 10 - * produces a decimal number, 16 hexidecimal, etc. Must be in the range of 2 + * produces a decimal number, 16 hexadecimal, etc. Must be in the range of 2 * to 36. * * Note that this function will overflow a buffer if `str` is not large enough @@ -3482,7 +3503,7 @@ extern SDL_DECLSPEC char * SDLCALL SDL_uitoa(unsigned int value, char *str, int * Convert a long integer into a string. * * This requires a radix to specified for string format. Specifying 10 - * produces a decimal number, 16 hexidecimal, etc. Must be in the range of 2 + * produces a decimal number, 16 hexadecimal, etc. Must be in the range of 2 * to 36. * * Note that this function will overflow a buffer if `str` is not large enough @@ -3510,7 +3531,7 @@ extern SDL_DECLSPEC char * SDLCALL SDL_ltoa(long value, char *str, int radix); * Convert an unsigned long integer into a string. * * This requires a radix to specified for string format. Specifying 10 - * produces a decimal number, 16 hexidecimal, etc. Must be in the range of 2 + * produces a decimal number, 16 hexadecimal, etc. Must be in the range of 2 * to 36. * * Note that this function will overflow a buffer if `str` is not large enough @@ -3540,7 +3561,7 @@ extern SDL_DECLSPEC char * SDLCALL SDL_ultoa(unsigned long value, char *str, int * Convert a long long integer into a string. * * This requires a radix to specified for string format. Specifying 10 - * produces a decimal number, 16 hexidecimal, etc. Must be in the range of 2 + * produces a decimal number, 16 hexadecimal, etc. Must be in the range of 2 * to 36. * * Note that this function will overflow a buffer if `str` is not large enough @@ -3568,7 +3589,7 @@ extern SDL_DECLSPEC char * SDLCALL SDL_lltoa(long long value, char *str, int rad * Convert an unsigned long long integer into a string. * * This requires a radix to specified for string format. Specifying 10 - * produces a decimal number, 16 hexidecimal, etc. Must be in the range of 2 + * produces a decimal number, 16 hexadecimal, etc. Must be in the range of 2 * to 36. * * Note that this function will overflow a buffer if `str` is not large enough @@ -3923,7 +3944,7 @@ extern SDL_DECLSPEC int SDLCALL SDL_strcasecmp(const char *str1, const char *str extern SDL_DECLSPEC int SDLCALL SDL_strncasecmp(const char *str1, const char *str2, size_t maxlen); /** - * Searches a string for the first occurence of any character contained in a + * Searches a string for the first occurrence of any character contained in a * breakset, and returns a pointer from the string to that character. * * \param str The null-terminated string to be searched. Must not be NULL, and @@ -3931,7 +3952,7 @@ extern SDL_DECLSPEC int SDLCALL SDL_strncasecmp(const char *str1, const char *st * \param breakset A null-terminated string containing the list of characters * to look for. Must not be NULL, and must not overlap with * `str`. - * \returns A pointer to the location, in str, of the first occurence of a + * \returns A pointer to the location, in str, of the first occurrence of a * character present in the breakset, or NULL if none is found. * * \threadsafety It is safe to call this function from any thread. @@ -4722,7 +4743,7 @@ extern SDL_DECLSPEC float SDLCALL SDL_atan2f(float y, float x); /** * Compute the ceiling of `x`. * - * The ceiling of `x` is the smallest integer `y` such that `y > x`, i.e `x` + * The ceiling of `x` is the smallest integer `y` such that `y >= x`, i.e `x` * rounded up to the nearest integer. * * Domain: `-INF <= x <= INF` @@ -4750,7 +4771,7 @@ extern SDL_DECLSPEC double SDLCALL SDL_ceil(double x); /** * Compute the ceiling of `x`. * - * The ceiling of `x` is the smallest integer `y` such that `y > x`, i.e `x` + * The ceiling of `x` is the smallest integer `y` such that `y >= x`, i.e `x` * rounded up to the nearest integer. * * Domain: `-INF <= x <= INF` @@ -4992,7 +5013,7 @@ extern SDL_DECLSPEC float SDLCALL SDL_fabsf(float x); /** * Compute the floor of `x`. * - * The floor of `x` is the largest integer `y` such that `y > x`, i.e `x` + * The floor of `x` is the largest integer `y` such that `y <= x`, i.e `x` * rounded down to the nearest integer. * * Domain: `-INF <= x <= INF` @@ -5020,7 +5041,7 @@ extern SDL_DECLSPEC double SDLCALL SDL_floor(double x); /** * Compute the floor of `x`. * - * The floor of `x` is the largest integer `y` such that `y > x`, i.e `x` + * The floor of `x` is the largest integer `y` such that `y <= x`, i.e `x` * rounded down to the nearest integer. * * Domain: `-INF <= x <= INF` @@ -5794,6 +5815,8 @@ typedef struct SDL_iconv_data_t *SDL_iconv_t; * \returns a handle that must be freed with SDL_iconv_close, or * SDL_ICONV_ERROR on failure. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_iconv @@ -5809,6 +5832,8 @@ extern SDL_DECLSPEC SDL_iconv_t SDLCALL SDL_iconv_open(const char *tocode, * \param cd The character set conversion handle. * \returns 0 on success, or -1 on failure. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_iconv @@ -5821,7 +5846,7 @@ extern SDL_DECLSPEC int SDLCALL SDL_iconv_close(SDL_iconv_t cd); * This function converts text between encodings, reading from and writing to * a buffer. * - * It returns the number of succesful conversions on success. On error, + * It returns the number of successful conversions on success. On error, * SDL_ICONV_E2BIG is returned when the output buffer is too small, or * SDL_ICONV_EILSEQ is returned when an invalid input sequence is encountered, * or SDL_ICONV_EINVAL is returned when an incomplete input sequence is @@ -5847,6 +5872,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_iconv_close(SDL_iconv_t cd); * \param outbytesleft The number of bytes in the output buffer. * \returns the number of conversions on success, or a negative error code. * + * \threadsafety Do not use the same SDL_iconv_t from two threads at once. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_iconv_open @@ -5882,6 +5909,8 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_iconv(SDL_iconv_t cd, const char **inbuf, * \param inbytesleft the size of the input string _in bytes_. * \returns a new string, converted to the new encoding, or NULL on error. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_iconv_open @@ -5905,6 +5934,8 @@ extern SDL_DECLSPEC char * SDLCALL SDL_iconv_string(const char *tocode, * \param S the string to convert. * \returns a new string, converted to the new encoding, or NULL on error. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ #define SDL_iconv_utf8_locale(S) SDL_iconv_string("", "UTF-8", S, SDL_strlen(S)+1) @@ -5919,9 +5950,11 @@ extern SDL_DECLSPEC char * SDLCALL SDL_iconv_string(const char *tocode, * \param S the string to convert. * \returns a new string, converted to the new encoding, or NULL on error. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ -#define SDL_iconv_utf8_ucs2(S) (Uint16 *)SDL_iconv_string("UCS-2", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_utf8_ucs2(S) SDL_reinterpret_cast(Uint16 *, SDL_iconv_string("UCS-2", "UTF-8", S, SDL_strlen(S)+1)) /** * Convert a UTF-8 string to UCS-4. @@ -5933,9 +5966,11 @@ extern SDL_DECLSPEC char * SDLCALL SDL_iconv_string(const char *tocode, * \param S the string to convert. * \returns a new string, converted to the new encoding, or NULL on error. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ -#define SDL_iconv_utf8_ucs4(S) (Uint32 *)SDL_iconv_string("UCS-4", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_utf8_ucs4(S) SDL_reinterpret_cast(Uint32 *, SDL_iconv_string("UCS-4", "UTF-8", S, SDL_strlen(S)+1)) /** * Convert a wchar_t string to UTF-8. @@ -5947,9 +5982,11 @@ extern SDL_DECLSPEC char * SDLCALL SDL_iconv_string(const char *tocode, * \param S the string to convert. * \returns a new string, converted to the new encoding, or NULL on error. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ -#define SDL_iconv_wchar_utf8(S) SDL_iconv_string("UTF-8", "WCHAR_T", (char *)S, (SDL_wcslen(S)+1)*sizeof(wchar_t)) +#define SDL_iconv_wchar_utf8(S) SDL_iconv_string("UTF-8", "WCHAR_T", SDL_reinterpret_cast(const char *, S), (SDL_wcslen(S)+1)*sizeof(wchar_t)) /* force builds using Clang's static analysis tools to use literal C runtime @@ -5974,6 +6011,10 @@ 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 +#if !defined(HAVE_STRTOK_R) && !defined(strtok_r) +char *strtok_r(char *str, const char *delim, char **saveptr); +#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 diff --git a/libs/SDL3/include/SDL3/SDL_storage.h b/libs/SDL3/include/SDL3/SDL_storage.h index 6837eba..71d403c 100644 --- a/libs/SDL3/include/SDL3/SDL_storage.h +++ b/libs/SDL3/include/SDL3/SDL_storage.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -334,6 +334,10 @@ typedef struct SDL_Storage SDL_Storage; /** * Opens up a read-only container for the application's filesystem. * + * By default, SDL_OpenTitleStorage uses the generic storage implementation. + * When the path override is not provided, the generic implementation will use + * the output of SDL_GetBasePath as the base path. + * * \param override a path to override the backend's default title root. * \param props a property list that may contain backend-specific information. * \returns a title storage container on success or NULL on failure; call diff --git a/libs/SDL3/include/SDL3/SDL_surface.h b/libs/SDL3/include/SDL3/SDL_surface.h index 15fce04..885f8f5 100644 --- a/libs/SDL3/include/SDL3/SDL_surface.h +++ b/libs/SDL3/include/SDL3/SDL_surface.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -29,12 +29,16 @@ * provides a reasonable toolbox for transforming the data, including copying * between surfaces, filling rectangles in the image data, etc. * - * There is also a simple .bmp loader, SDL_LoadBMP(). SDL itself does not - * provide loaders for various other file formats, but there are several - * excellent external libraries that do, including its own satellite library, - * SDL_image: + * There is also a simple .bmp loader, SDL_LoadBMP(), and a simple .png + * loader, SDL_LoadPNG(). SDL itself does not provide loaders for other file + * formats, but there are several excellent external libraries that do, + * including its own satellite library, + * [SDL_image](https://wiki.libsdl.org/SDL3_image) + * . * - * https://github.com/libsdl-org/SDL_image + * In general these functions are thread-safe in that they can be called on + * different threads with different surfaces. You should not try to modify any + * surface from two threads simultaneously. */ #ifndef SDL_surface_h_ @@ -83,8 +87,9 @@ 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_NEAREST, /**< nearest pixel sampling */ + SDL_SCALEMODE_LINEAR, /**< linear filtering */ + SDL_SCALEMODE_PIXELART /**< nearest pixel sampling with improved scaling for pixel art, available since SDL 3.4.0 */ } SDL_ScaleMode; /** @@ -94,9 +99,10 @@ typedef enum SDL_ScaleMode */ typedef enum SDL_FlipMode { - SDL_FLIP_NONE, /**< Do not flip */ - SDL_FLIP_HORIZONTAL, /**< flip horizontally */ - SDL_FLIP_VERTICAL /**< flip vertically */ + SDL_FLIP_NONE, /**< Do not flip */ + SDL_FLIP_HORIZONTAL, /**< flip horizontally */ + SDL_FLIP_VERTICAL, /**< flip vertically */ + SDL_FLIP_HORIZONTAL_AND_VERTICAL = (SDL_FLIP_HORIZONTAL | SDL_FLIP_VERTICAL) /**< flip horizontally and vertically (not a diagonal flip) */ } SDL_FlipMode; #ifndef SDL_INTERNAL @@ -235,6 +241,12 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroySurface(SDL_Surface *surface); * 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. + * - `SDL_PROP_SURFACE_ROTATION_FLOAT`: the number of degrees a surface's data + * is meant to be rotated clockwise to make the image right-side up. Default + * 0. This is used by the camera API, if a mobile device is oriented + * differently than what its camera provides (i.e. - the camera always + * provides portrait images but the phone is being held in landscape + * orientation). Since SDL 3.4.0. * * \param surface the SDL_Surface structure to query. * \returns a valid property ID on success or 0 on failure; call @@ -251,6 +263,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetSurfaceProperties(SDL_Surfac #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" +#define SDL_PROP_SURFACE_ROTATION_FLOAT "SDL.surface.rotation" /** * Set the colorspace used by a surface. @@ -264,7 +277,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -283,7 +297,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -313,7 +328,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -324,6 +340,9 @@ extern SDL_DECLSPEC SDL_Palette * SDLCALL SDL_CreateSurfacePalette(SDL_Surface * /** * Set the palette used by a surface. * + * Setting the palette keeps an internal reference to the palette, which can + * be safely destroyed afterwards. + * * A single palette can be shared with many surfaces. * * \param surface the SDL_Surface structure to update. @@ -331,7 +350,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -372,7 +392,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -415,7 +436,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -433,7 +455,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -459,9 +482,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. + * \threadsafety This function can be called on different threads with + * different surfaces. 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. * @@ -485,6 +509,46 @@ extern SDL_DECLSPEC bool SDLCALL SDL_LockSurface(SDL_Surface *surface); */ extern SDL_DECLSPEC void SDLCALL SDL_UnlockSurface(SDL_Surface *surface); +/** + * Load a BMP or PNG image from a seekable SDL data stream. + * + * The new surface should be freed with SDL_DestroySurface(). Not doing so + * will result in a memory leak. + * + * \param src the data stream for the surface. + * \param closeio if true, calls SDL_CloseIO() on `src` before returning, even + * in the case of an error. + * \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.4.0. + * + * \sa SDL_DestroySurface + * \sa SDL_LoadSurface + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadSurface_IO(SDL_IOStream *src, bool closeio); + +/** + * Load a BMP or PNG image from a file. + * + * The new surface should be freed with SDL_DestroySurface(). Not doing so + * will result in a memory leak. + * + * \param file the file to load. + * \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.4.0. + * + * \sa SDL_DestroySurface + * \sa SDL_LoadSurface_IO + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadSurface(const char *file); + /** * Load a BMP image from a seekable SDL data stream. * @@ -543,7 +607,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -553,7 +618,7 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadBMP(const char *file); extern SDL_DECLSPEC bool SDLCALL SDL_SaveBMP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio); /** - * Save a surface to a file. + * Save a surface to a file in BMP format. * * Surfaces with a 24-bit, 32-bit and paletted 8-bit format get saved in the * BMP directly. Other RGB formats with 8-bit or higher get converted to a @@ -566,7 +631,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -575,6 +641,94 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SaveBMP_IO(SDL_Surface *surface, SDL_IOStre */ extern SDL_DECLSPEC bool SDLCALL SDL_SaveBMP(SDL_Surface *surface, const char *file); +/** + * Load a PNG image from a seekable SDL data stream. + * + * This is intended as a convenience function for loading images from trusted + * sources. If you want to load arbitrary images you should use libpng or + * another image loading library designed with security in mind. + * + * The new surface should be freed with SDL_DestroySurface(). Not doing so + * will result in a memory leak. + * + * \param src the data stream for the surface. + * \param closeio if true, calls SDL_CloseIO() on `src` before returning, even + * in the case of an error. + * \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.4.0. + * + * \sa SDL_DestroySurface + * \sa SDL_LoadPNG + * \sa SDL_SavePNG_IO + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadPNG_IO(SDL_IOStream *src, bool closeio); + +/** + * Load a PNG image from a file. + * + * This is intended as a convenience function for loading images from trusted + * sources. If you want to load arbitrary images you should use libpng or + * another image loading library designed with security in mind. + * + * The new surface should be freed with SDL_DestroySurface(). Not doing so + * will result in a memory leak. + * + * \param file the PNG file to load. + * \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.4.0. + * + * \sa SDL_DestroySurface + * \sa SDL_LoadPNG_IO + * \sa SDL_SavePNG + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadPNG(const char *file); + +/** + * Save a surface to a seekable SDL data stream in PNG format. + * + * \param surface the SDL_Surface structure containing the image to be saved. + * \param dst a data stream to save to. + * \param closeio if true, calls SDL_CloseIO() on `dst` before returning, even + * in the case of an error. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function can be called on different threads with + * different surfaces. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_LoadPNG_IO + * \sa SDL_SavePNG + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio); + +/** + * Save a surface to a file in PNG format. + * + * \param surface the SDL_Surface structure containing the image to be saved. + * \param file a file to save to. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function can be called on different threads with + * different surfaces. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_LoadPNG + * \sa SDL_SavePNG_IO + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SavePNG(SDL_Surface *surface, const char *file); + /** * Set the RLE acceleration hint for a surface. * @@ -586,7 +740,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -628,7 +783,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -693,7 +849,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -713,7 +870,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -735,7 +893,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -773,7 +932,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -812,7 +972,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -833,7 +994,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -849,12 +1011,42 @@ 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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_FlipSurface(SDL_Surface *surface, SDL_FlipMode flip); +/** + * Return a copy of a surface rotated clockwise a number of degrees. + * + * The angle of rotation can be negative for counter-clockwise rotation. + * + * When the rotation isn't a multiple of 90 degrees, the resulting surface is + * larger than the original, with the background filled in with the colorkey, + * if available, or RGBA 255/255/255/0 if not. + * + * If `surface` has the SDL_PROP_SURFACE_ROTATION_FLOAT property set on it, + * the new copy will have the adjusted value set: if the rotation property is + * 90 and `angle` was 30, the new surface will have a property value of 60 + * (that is: to be upright vs gravity, this surface needs to rotate 60 more + * degrees). However, note that further rotations on the new surface in this + * example will produce unexpected results, since the image will have resized + * and padded to accommodate the not-90 degree angle. + * + * \param surface the surface to rotate. + * \param angle the rotation angle, in degrees. + * \returns a rotated copy of the surface or NULL on failure; call + * SDL_GetError() for more information. + * + * \threadsafety This function can be called on different threads with + * different surfaces. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_RotateSurface(SDL_Surface *surface, float angle); + /** * Creates a new surface identical to the existing surface. * @@ -867,7 +1059,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -888,7 +1081,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -915,7 +1109,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -943,7 +1138,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -1046,7 +1242,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. */ @@ -1058,7 +1255,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_PremultiplySurfaceAlpha(SDL_Surface *surfac * This function handles all surface formats, and ignores any clip rectangle. * * If the surface is YUV, the color is assumed to be in the sRGB colorspace, - * otherwise the color is assumed to be in the colorspace of the suface. + * otherwise the color is assumed to be in the colorspace of the surface. * * \param surface the SDL_Surface to clear. * \param r the red component of the pixel, normally in the range 0-1. @@ -1068,7 +1265,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. */ @@ -1093,7 +1291,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -1120,7 +1319,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -1411,7 +1611,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -1444,7 +1645,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -1475,7 +1677,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. */ @@ -1501,7 +1704,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. */ @@ -1526,7 +1730,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. */ @@ -1548,7 +1753,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. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. */ diff --git a/libs/SDL3/include/SDL3/SDL_system.h b/libs/SDL3/include/SDL3/SDL_system.h index 294089f..ec23a1f 100644 --- a/libs/SDL3/include/SDL3/SDL_system.h +++ b/libs/SDL3/include/SDL3/SDL_system.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -86,6 +86,8 @@ typedef bool (SDLCALL *SDL_WindowsMessageHook)(void *userdata, MSG *msg); * \param callback the SDL_WindowsMessageHook function to call. * \param userdata a pointer to pass to every iteration of `callback`. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_WindowsMessageHook @@ -169,6 +171,8 @@ typedef bool (SDLCALL *SDL_X11EventHook)(void *userdata, XEvent *xevent); * \param callback the SDL_X11EventHook function to call. * \param userdata a pointer to pass to every iteration of `callback`. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata); @@ -186,6 +190,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetX11EventHook(SDL_X11EventHook callback, * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriority(Sint64 threadID, int priority); @@ -202,6 +208,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriority(Sint64 threadID, int * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy); @@ -247,14 +255,14 @@ typedef void (SDLCALL *SDL_iOSAnimationCallback)(void *userdata); * * For more information see: * - * https://wiki.libsdl.org/SDL3/README/ios + * https://wiki.libsdl.org/SDL3/README-ios * * Note that if you use the "main callbacks" instead of a standard C `main` * function, you don't have to use this API, as SDL will manage this for you. * * Details on main callbacks are here: * - * https://wiki.libsdl.org/SDL3/README/main-functions + * https://wiki.libsdl.org/SDL3/README-main-functions * * \param window the window for which the animation callback should be set. * \param interval the number of frames after which **callback** will be @@ -264,6 +272,8 @@ typedef void (SDLCALL *SDL_iOSAnimationCallback)(void *userdata); * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetiOSEventPump @@ -277,6 +287,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetiOSAnimationCallback(SDL_Window *window, * * \param enabled true to enable the event pump, false to disable it. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetiOSAnimationCallback @@ -370,6 +382,8 @@ extern SDL_DECLSPEC void * SDLCALL SDL_GetAndroidActivity(void); * * \returns the Android API level. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC int SDLCALL SDL_GetAndroidSDKVersion(void); @@ -379,6 +393,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetAndroidSDKVersion(void); * * \returns true if this is a Chromebook, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_IsChromebook(void); @@ -388,6 +404,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_IsChromebook(void); * * \returns true if this is a DeX docking station, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_IsDeXMode(void); @@ -605,6 +623,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SendAndroidMessage(Uint32 command, int para * * \returns true if the device is a tablet, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_IsTablet(void); @@ -616,6 +636,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_IsTablet(void); * * \returns true if the device is a TV, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_IsTV(void); diff --git a/libs/SDL3/include/SDL3/SDL_test.h b/libs/SDL3/include/SDL3/SDL_test.h index 8b28580..59a3dd8 100644 --- a/libs/SDL3/include/SDL3/SDL_test.h +++ b/libs/SDL3/include/SDL3/SDL_test.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_test_assert.h b/libs/SDL3/include/SDL3/SDL_test_assert.h index c067608..ff41b31 100644 --- a/libs/SDL3/include/SDL3/SDL_test_assert.h +++ b/libs/SDL3/include/SDL3/SDL_test_assert.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_test_common.h b/libs/SDL3/include/SDL3/SDL_test_common.h index 91efe8a..6afd857 100644 --- a/libs/SDL3/include/SDL3/SDL_test_common.h +++ b/libs/SDL3/include/SDL3/SDL_test_common.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -153,6 +153,10 @@ typedef struct SDL_Rect confine; bool hide_cursor; + /* Misc. */ + int quit_after_ms_interval; + SDL_TimerID quit_after_ms_timer; + /* Options info */ SDLTest_ArgumentParser common_argparser; SDLTest_ArgumentParser video_argparser; diff --git a/libs/SDL3/include/SDL3/SDL_test_compare.h b/libs/SDL3/include/SDL3/SDL_test_compare.h index 7ff2477..58e5513 100644 --- a/libs/SDL3/include/SDL3/SDL_test_compare.h +++ b/libs/SDL3/include/SDL3/SDL_test_compare.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -52,6 +52,7 @@ extern "C" { * \returns 0 if comparison succeeded, >0 (=number of pixels for which the comparison failed) if comparison failed, -1 if any of the surfaces were NULL, -2 if the surface sizes differ. */ int SDLCALL SDLTest_CompareSurfaces(SDL_Surface *surface, SDL_Surface *referenceSurface, int allowable_error); +int SDLCALL SDLTest_CompareSurfacesIgnoreTransparentPixels(SDL_Surface *surface, SDL_Surface *referenceSurface, int allowable_error); /** * Compares 2 memory blocks for equality diff --git a/libs/SDL3/include/SDL3/SDL_test_crc32.h b/libs/SDL3/include/SDL3/SDL_test_crc32.h index 94fe1a3..f563d5d 100644 --- a/libs/SDL3/include/SDL3/SDL_test_crc32.h +++ b/libs/SDL3/include/SDL3/SDL_test_crc32.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_test_font.h b/libs/SDL3/include/SDL3/SDL_test_font.h index fb40794..f5b5674 100644 --- a/libs/SDL3/include/SDL3/SDL_test_font.h +++ b/libs/SDL3/include/SDL3/SDL_test_font.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_test_fuzzer.h b/libs/SDL3/include/SDL3/SDL_test_fuzzer.h index caf11f2..e8ebc45 100644 --- a/libs/SDL3/include/SDL3/SDL_test_fuzzer.h +++ b/libs/SDL3/include/SDL3/SDL_test_fuzzer.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_test_harness.h b/libs/SDL3/include/SDL3/SDL_test_harness.h index 5d4963e..3771045 100644 --- a/libs/SDL3/include/SDL3/SDL_test_harness.h +++ b/libs/SDL3/include/SDL3/SDL_test_harness.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_test_log.h b/libs/SDL3/include/SDL3/SDL_test_log.h index 0d48044..13933da 100644 --- a/libs/SDL3/include/SDL3/SDL_test_log.h +++ b/libs/SDL3/include/SDL3/SDL_test_log.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -42,6 +42,14 @@ extern "C" { #endif +/** + * Prints given message with a timestamp in the TEST category and given priority. + * + * \param priority Priority of the message + * \param fmt Message to be logged + */ +void SDLCALL SDLTest_LogMessage(SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, ...); + /** * Prints given message with a timestamp in the TEST category and INFO priority. * diff --git a/libs/SDL3/include/SDL3/SDL_test_md5.h b/libs/SDL3/include/SDL3/SDL_test_md5.h index e9d9639..7b86b05 100644 --- a/libs/SDL3/include/SDL3/SDL_test_md5.h +++ b/libs/SDL3/include/SDL3/SDL_test_md5.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_test_memory.h b/libs/SDL3/include/SDL3/SDL_test_memory.h index 1bd9466..f07f1cb 100644 --- a/libs/SDL3/include/SDL3/SDL_test_memory.h +++ b/libs/SDL3/include/SDL3/SDL_test_memory.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_thread.h b/libs/SDL3/include/SDL3/SDL_thread.h index e981b54..f74317c 100644 --- a/libs/SDL3/include/SDL3/SDL_thread.h +++ b/libs/SDL3/include/SDL3/SDL_thread.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -37,7 +37,8 @@ * will report failure without doing anything. * * If you're going to work with threads, you almost certainly need to have a - * good understanding of [CategoryMutex](CategoryMutex) as well. + * good understanding of thread safety measures: locking and synchronization + * mechanisms are handled by the functions in SDL_mutex.h. */ #include @@ -207,6 +208,8 @@ typedef int (SDLCALL *SDL_ThreadFunction) (void *data); * new thread could not be created; 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_CreateThreadWithProperties @@ -273,6 +276,8 @@ extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThread(SDL_ThreadFunction fn, * new thread could not be created; 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_CreateThread @@ -328,6 +333,8 @@ extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThreadWithProperties(SDL_Prop * new thread could not be created; 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_Thread * SDLCALL SDL_CreateThreadRuntime(SDL_ThreadFunction fn, const char *name, void *data, SDL_FunctionPointer pfnBeginThread, SDL_FunctionPointer pfnEndThread); @@ -342,6 +349,8 @@ extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThreadRuntime(SDL_ThreadFunct * new thread could not be created; 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_Thread * SDLCALL SDL_CreateThreadWithPropertiesRuntime(SDL_PropertiesID props, SDL_FunctionPointer pfnBeginThread, SDL_FunctionPointer pfnEndThread); @@ -362,6 +371,8 @@ extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThreadWithPropertiesRuntime(S * \returns a pointer to a UTF-8 string that names the specified thread, or * NULL if it doesn't have a name. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC const char * SDLCALL SDL_GetThreadName(SDL_Thread *thread); @@ -378,6 +389,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetThreadName(SDL_Thread *thread); * * \returns the ID of the current thread. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetThreadID @@ -395,6 +408,8 @@ extern SDL_DECLSPEC SDL_ThreadID SDLCALL SDL_GetCurrentThreadID(void); * \returns the ID of the specified thread, or the ID of the current thread if * `thread` is NULL. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetCurrentThreadID @@ -412,6 +427,8 @@ extern SDL_DECLSPEC SDL_ThreadID SDLCALL SDL_GetThreadID(SDL_Thread *thread); * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_SetCurrentThreadPriority(SDL_ThreadPriority priority); @@ -444,6 +461,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetCurrentThreadPriority(SDL_ThreadPriority * function by its 'return', or -1 if the thread has been * detached or isn't valid, may be NULL. * + * \threadsafety It is safe to call this function from any thread, but only + * a single thread can wait any specific thread to finish. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CreateThread @@ -458,6 +478,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_WaitThread(SDL_Thread *thread, int *status) * \returns the current state of a thread, or SDL_THREAD_UNKNOWN if the thread * isn't valid. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_ThreadState @@ -490,6 +512,8 @@ extern SDL_DECLSPEC SDL_ThreadState SDLCALL SDL_GetThreadState(SDL_Thread *threa * * It is safe to pass NULL to this function; it is a no-op. * + * \threadsafety It is safe to call this function from any thread. + * * \param thread the SDL_Thread pointer that was returned from the * SDL_CreateThread() call that started this thread. * diff --git a/libs/SDL3/include/SDL3/SDL_time.h b/libs/SDL3/include/SDL3/SDL_time.h index b6d3f6d..2ae6022 100644 --- a/libs/SDL3/include/SDL3/SDL_time.h +++ b/libs/SDL3/include/SDL3/SDL_time.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer -Copyright (C) 1997-2025 Sam Lantinga +Copyright (C) 1997-2026 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 @@ -106,6 +106,8 @@ typedef enum SDL_TimeFormat * \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_GetDateTimeLocalePreferences(SDL_DateFormat *dateFormat, SDL_TimeFormat *timeFormat); @@ -118,6 +120,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetDateTimeLocalePreferences(SDL_DateFormat * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetCurrentTime(SDL_Time *ticks); @@ -134,6 +138,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetCurrentTime(SDL_Time *ticks); * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_TimeToDateTime(SDL_Time ticks, SDL_DateTime *dt, bool localTime); @@ -149,6 +155,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_TimeToDateTime(SDL_Time ticks, SDL_DateTime * \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. */ extern SDL_DECLSPEC bool SDLCALL SDL_DateTimeToTime(const SDL_DateTime *dt, SDL_Time *ticks); @@ -165,6 +173,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_DateTimeToTime(const SDL_DateTime *dt, SDL_ * \param dwHighDateTime a pointer filled in with the high portion of the * Windows FILETIME value. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_TimeToWindows(SDL_Time ticks, Uint32 *dwLowDateTime, Uint32 *dwHighDateTime); @@ -180,6 +190,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_TimeToWindows(SDL_Time ticks, Uint32 *dwLow * \param dwHighDateTime the high portion of the Windows FILETIME value. * \returns the converted SDL time. * + * \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_Time SDLCALL SDL_TimeFromWindows(Uint32 dwLowDateTime, Uint32 dwHighDateTime); @@ -192,6 +204,8 @@ extern SDL_DECLSPEC SDL_Time SDLCALL SDL_TimeFromWindows(Uint32 dwLowDateTime, U * \returns the number of days in the requested month or -1 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 int SDLCALL SDL_GetDaysInMonth(int year, int month); @@ -205,6 +219,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetDaysInMonth(int year, int month); * \returns the day of year [0-365] if the date is valid or -1 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 int SDLCALL SDL_GetDayOfYear(int year, int month, int day); @@ -218,6 +234,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetDayOfYear(int year, int month, int day); * \returns a value between 0 and 6 (0 being Sunday) if the date is valid or * -1 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 int SDLCALL SDL_GetDayOfWeek(int year, int month, int day); diff --git a/libs/SDL3/include/SDL3/SDL_timer.h b/libs/SDL3/include/SDL3/SDL_timer.h index cf94881..dfeec31 100644 --- a/libs/SDL3/include/SDL3/SDL_timer.h +++ b/libs/SDL3/include/SDL3/SDL_timer.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -34,7 +34,7 @@ * This category covers measuring time elapsed (SDL_GetTicks(), * SDL_GetPerformanceCounter()), putting a thread to sleep for a certain * amount of time (SDL_Delay(), SDL_DelayNS(), SDL_DelayPrecise()), and firing - * a callback function after a certain amount of time has elasped + * a callback function after a certain amount of time has elapsed * (SDL_AddTimer(), etc). * * There are also useful macros to convert between time units, like @@ -185,14 +185,18 @@ extern "C" { #define SDL_NS_TO_US(NS) ((NS) / SDL_NS_PER_US) /** - * Get the number of milliseconds since SDL library initialization. + * Get the number of milliseconds that have elapsed since the SDL library + * initialization. * - * \returns an unsigned 64-bit value representing the number of milliseconds - * since the SDL library initialized. + * \returns an unsigned 64‑bit integer that represents the number of + * milliseconds that have elapsed since the SDL library was + * initialized (typically via a call to SDL_Init). * * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_GetTicksNS */ extern SDL_DECLSPEC Uint64 SDLCALL SDL_GetTicks(void); diff --git a/libs/SDL3/include/SDL3/SDL_touch.h b/libs/SDL3/include/SDL3/SDL_touch.h index 64845a1..e5576a5 100644 --- a/libs/SDL3/include/SDL3/SDL_touch.h +++ b/libs/SDL3/include/SDL3/SDL_touch.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_tray.h b/libs/SDL3/include/SDL3/SDL_tray.h index 1780b0b..688278a 100644 --- a/libs/SDL3/include/SDL3/SDL_tray.h +++ b/libs/SDL3/include/SDL3/SDL_tray.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/SDL3/SDL_version.h b/libs/SDL3/include/SDL3/SDL_version.h index 7443d52..7587a6e 100644 --- a/libs/SDL3/include/SDL3/SDL_version.h +++ b/libs/SDL3/include/SDL3/SDL_version.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -53,7 +53,7 @@ extern "C" { * * \since This macro is available since SDL 3.2.0. */ -#define SDL_MINOR_VERSION 2 +#define SDL_MINOR_VERSION 4 /** * The current micro (or patchlevel) version of the SDL headers. @@ -62,7 +62,7 @@ extern "C" { * * \since This macro is available since SDL 3.2.0. */ -#define SDL_MICRO_VERSION 20 +#define SDL_MICRO_VERSION 2 /** * This macro turns the version numbers into a numeric value. @@ -85,6 +85,8 @@ extern "C" { * * \param version the version number. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ #define SDL_VERSIONNUM_MAJOR(version) ((version) / 1000000) @@ -96,6 +98,8 @@ extern "C" { * * \param version the version number. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ #define SDL_VERSIONNUM_MINOR(version) (((version) / 1000) % 1000) @@ -107,6 +111,8 @@ extern "C" { * * \param version the version number. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ #define SDL_VERSIONNUM_MICRO(version) ((version) % 1000) @@ -114,6 +120,8 @@ extern "C" { /** * This is the version number macro for the current SDL version. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. * * \sa SDL_GetVersion @@ -124,6 +132,8 @@ extern "C" { /** * This macro will evaluate to true if compiled with SDL at least X.Y.Z. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ #define SDL_VERSION_ATLEAST(X, Y, Z) \ @@ -141,6 +151,8 @@ extern "C" { * * \returns the version of the linked library. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetRevision @@ -148,13 +160,14 @@ extern "C" { extern SDL_DECLSPEC int SDLCALL SDL_GetVersion(void); /** - * Get the code revision of SDL that is linked against your program. + * Get the code revision of the SDL library that is linked against your + * program. * - * This value is the revision of the code you are linked with and may be + * This value is the revision of the code you are linking against and may be * different from the code you are compiling with, which is found in the - * constant SDL_REVISION. + * constant SDL_REVISION if you explicitly include SDL_revision.h * - * The revision is arbitrary string (a hash value) uniquely identifying the + * The revision is an arbitrary string (a hash value) uniquely identifying the * exact revision of the SDL library in use, and is only useful in comparing * against other revisions. It is NOT an incrementing number. * @@ -167,6 +180,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetVersion(void); * \returns an arbitrary string, uniquely identifying the exact revision of * the SDL library in use. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetVersion diff --git a/libs/SDL3/include/SDL3/SDL_video.h b/libs/SDL3/include/SDL3/SDL_video.h index 3b3676d..2f06295 100644 --- a/libs/SDL3/include/SDL3/SDL_video.h +++ b/libs/SDL3/include/SDL3/SDL_video.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -97,6 +97,8 @@ typedef Uint32 SDL_WindowID; * uninitialized will either return the user provided value, if one was set * prior to initialization, or NULL. See docs/README-wayland.md for more * information. + * + * \since This macro is available since SDL 3.2.0. */ #define SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER "SDL.video.wayland.wl_display" @@ -180,6 +182,12 @@ typedef struct SDL_Window SDL_Window; * changed on existing windows by the app, and some of it might be altered by * the user or system outside of the app's control. * + * When creating windows with `SDL_WINDOW_RESIZABLE`, SDL will constrain + * resizable windows to the dimensions recommended by the compositor to fit it + * within the usable desktop space, although some compositors will do this + * automatically without intervention as well. Use `SDL_SetWindowResizable` + * after creation instead if you wish to create a window with a specific size. + * * \since This datatype is available since SDL 3.2.0. * * \sa SDL_GetWindowFlags @@ -207,6 +215,7 @@ typedef Uint64 SDL_WindowFlags; #define SDL_WINDOW_TOOLTIP SDL_UINT64_C(0x0000000000040000) /**< window should be treated as a tooltip and does not get mouse or keyboard focus, requires a parent window */ #define SDL_WINDOW_POPUP_MENU SDL_UINT64_C(0x0000000000080000) /**< window should be treated as a popup menu, requires a parent window */ #define SDL_WINDOW_KEYBOARD_GRABBED SDL_UINT64_C(0x0000000000100000) /**< window has grabbed keyboard input */ +#define SDL_WINDOW_FILL_DOCUMENT SDL_UINT64_C(0x0000000000200000) /**< window is in fill-document mode (Emscripten only), since SDL 3.4.0 */ #define SDL_WINDOW_VULKAN SDL_UINT64_C(0x0000000010000000) /**< window usable for Vulkan surface */ #define SDL_WINDOW_METAL SDL_UINT64_C(0x0000000020000000) /**< window usable for Metal view */ #define SDL_WINDOW_TRANSPARENT SDL_UINT64_C(0x0000000040000000) /**< window with transparent buffer */ @@ -220,6 +229,8 @@ typedef Uint64 SDL_WindowFlags; * SDL_WINDOWPOS_UNDEFINED or SDL_WINDOWPOS_UNDEFINED_DISPLAY. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_SetWindowPosition */ #define SDL_WINDOWPOS_UNDEFINED_MASK 0x1FFF0000u @@ -232,6 +243,8 @@ typedef Uint64 SDL_WindowFlags; * \param X the SDL_DisplayID of the display to use. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_SetWindowPosition */ #define SDL_WINDOWPOS_UNDEFINED_DISPLAY(X) (SDL_WINDOWPOS_UNDEFINED_MASK|(X)) @@ -241,6 +254,8 @@ typedef Uint64 SDL_WindowFlags; * This always uses the primary display. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_SetWindowPosition */ #define SDL_WINDOWPOS_UNDEFINED SDL_WINDOWPOS_UNDEFINED_DISPLAY(0) @@ -250,6 +265,8 @@ typedef Uint64 SDL_WindowFlags; * \param X the window position value. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_SetWindowPosition */ #define SDL_WINDOWPOS_ISUNDEFINED(X) (((X)&0xFFFF0000) == SDL_WINDOWPOS_UNDEFINED_MASK) @@ -260,6 +277,8 @@ typedef Uint64 SDL_WindowFlags; * SDL_WINDOWPOS_CENTERED or SDL_WINDOWPOS_CENTERED_DISPLAY. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_SetWindowPosition */ #define SDL_WINDOWPOS_CENTERED_MASK 0x2FFF0000u @@ -272,6 +291,8 @@ typedef Uint64 SDL_WindowFlags; * \param X the SDL_DisplayID of the display to use. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_SetWindowPosition */ #define SDL_WINDOWPOS_CENTERED_DISPLAY(X) (SDL_WINDOWPOS_CENTERED_MASK|(X)) @@ -281,6 +302,8 @@ typedef Uint64 SDL_WindowFlags; * This always uses the primary display. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_SetWindowPosition */ #define SDL_WINDOWPOS_CENTERED SDL_WINDOWPOS_CENTERED_DISPLAY(0) @@ -290,6 +313,8 @@ typedef Uint64 SDL_WindowFlags; * \param X the window position value. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_GetWindowPosition */ #define SDL_WINDOWPOS_ISCENTERED(X) \ (((X)&0xFFFF0000) == SDL_WINDOWPOS_CENTERED_MASK) @@ -307,12 +332,30 @@ typedef enum SDL_FlashOperation SDL_FLASH_UNTIL_FOCUSED /**< Flash the window until it gets focus */ } SDL_FlashOperation; +/** + * Window progress state + * + * \since This enum is available since SDL 3.2.8. + */ +typedef enum SDL_ProgressState +{ + SDL_PROGRESS_STATE_INVALID = -1, /**< An invalid progress state indicating an error; check SDL_GetError() */ + SDL_PROGRESS_STATE_NONE, /**< No progress bar is shown */ + SDL_PROGRESS_STATE_INDETERMINATE, /**< The progress bar is shown in a indeterminate state */ + SDL_PROGRESS_STATE_NORMAL, /**< The progress bar is shown in a normal state */ + SDL_PROGRESS_STATE_PAUSED, /**< The progress bar is shown in a paused state */ + SDL_PROGRESS_STATE_ERROR /**< The progress bar is shown in a state indicating the application had an error */ +} SDL_ProgressState; + /** * An opaque handle to an OpenGL context. * * \since This datatype is available since SDL 3.2.0. * * \sa SDL_GL_CreateContext + * \sa SDL_GL_SetAttribute + * \sa SDL_GL_MakeCurrent + * \sa SDL_GL_DestroyContext */ typedef struct SDL_GLContextState *SDL_GLContext; @@ -448,7 +491,7 @@ typedef enum SDL_GLAttr SDL_GL_CONTEXT_FLAGS, /**< some combination of 0 or more of elements of the SDL_GLContextFlag enumeration; defaults to 0. */ SDL_GL_CONTEXT_PROFILE_MASK, /**< type of GL context (Core, Compatibility, ES). See SDL_GLProfile; default value depends on platform. */ SDL_GL_SHARE_WITH_CURRENT_CONTEXT, /**< OpenGL context sharing; defaults to 0. */ - SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, /**< requests sRGB capable visual; defaults to 0. */ + SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, /**< requests sRGB-capable visual if 1. Defaults to -1 ("don't care"). This is a request; GL drivers might not comply! */ SDL_GL_CONTEXT_RELEASE_BEHAVIOR, /**< sets context the release behavior. See SDL_GLContextReleaseFlag; defaults to FLUSH. */ SDL_GL_CONTEXT_RESET_NOTIFICATION, /**< set context reset notification. See SDL_GLContextResetNotification; defaults to NO_NOTIFICATION. */ SDL_GL_CONTEXT_NO_ERROR, @@ -530,7 +573,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumVideoDrivers(void); * to be proper names. * * \param index the index of a video driver. - * \returns the name of the video driver with the given **index**. + * \returns the name of the video driver with the given **index**, or NULL if + * index is out of bounds. * * \threadsafety This function should only be called on the main thread. * @@ -617,6 +661,16 @@ extern SDL_DECLSPEC SDL_DisplayID SDLCALL SDL_GetPrimaryDisplay(void); * responsible for any coordinate transformations needed to conform to the * requested display orientation. * + * On Wayland: + * + * - `SDL_PROP_DISPLAY_WAYLAND_WL_OUTPUT_POINTER`: the wl_output associated + * with the display + * + * On Windows: + * + * - `SDL_PROP_DISPLAY_WINDOWS_HMONITOR_POINTER`: the monitor handle + * (HMONITOR) associated with the display + * * \param displayID the instance ID of the display to query. * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. @@ -629,6 +683,8 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetDisplayProperties(SDL_Displa #define SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN "SDL.display.HDR_enabled" #define SDL_PROP_DISPLAY_KMSDRM_PANEL_ORIENTATION_NUMBER "SDL.display.KMSDRM.panel_orientation" +#define SDL_PROP_DISPLAY_WAYLAND_WL_OUTPUT_POINTER "SDL.display.wayland.wl_output" +#define SDL_PROP_DISPLAY_WINDOWS_HMONITOR_POINTER "SDL.display.windows.hmonitor" /** * Get the name of a display in UTF-8 encoding. @@ -1049,8 +1105,6 @@ extern SDL_DECLSPEC SDL_Window ** SDLCALL SDL_GetWindows(int *count); * * - `SDL_WINDOW_FULLSCREEN`: fullscreen window at desktop resolution * - `SDL_WINDOW_OPENGL`: window usable with an OpenGL context - * - `SDL_WINDOW_OCCLUDED`: window partially or completely obscured by another - * window * - `SDL_WINDOW_HIDDEN`: window is not visible * - `SDL_WINDOW_BORDERLESS`: no window decoration * - `SDL_WINDOW_RESIZABLE`: window can be resized @@ -1078,7 +1132,8 @@ extern SDL_DECLSPEC SDL_Window ** SDLCALL SDL_GetWindows(int *count); * - `SDL_WINDOW_TRANSPARENT`: window with transparent buffer * - `SDL_WINDOW_NOT_FOCUSABLE`: window should not be focusable * - * The SDL_Window is implicitly shown if SDL_WINDOW_HIDDEN is not set. + * The SDL_Window will be shown if SDL_WINDOW_HIDDEN is not set. If hidden at + * creation time, SDL_ShowWindow() can be used to show it later. * * On Apple's macOS, you **must** set the NSHighResolutionCapable Info.plist * property to YES, otherwise you will not receive a High-DPI OpenGL canvas. @@ -1167,14 +1222,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 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. + * 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 @@ -1216,9 +1272,10 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren * 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_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 @@ -1275,12 +1332,18 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren * - `SDL_PROP_WINDOW_CREATE_COCOA_VIEW_POINTER`: the `(__unsafe_unretained)` * NSView associated with the window, defaults to `[window contentView]` * + * These are additional supported properties on iOS, tvOS, and visionOS: + * + * - `SDL_PROP_WINDOW_CREATE_WINDOWSCENE_POINTER`: the `(__unsafe_unretained)` + * UIWindowScene associated with the window, defaults to the active window + * scene. + * * These are additional supported properties on Wayland: * * - `SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN` - true if * the application wants to use the Wayland surface for a custom role and * does not want it attached to an XDG toplevel window. See - * [README/wayland](README/wayland) for more information on using custom + * [README-wayland](README-wayland) for more information on using custom * surfaces. * - `SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN` - true if the * application wants an associated `wl_egl_window` object to be created and @@ -1288,7 +1351,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren * property or `SDL_WINDOW_OPENGL` flag set. * - `SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER` - the wl_surface * associated with the window, if you want to wrap an existing window. See - * [README/wayland](README/wayland) for more information. + * [README-wayland](README-wayland) for more information. * * These are additional supported properties on Windows: * @@ -1304,8 +1367,22 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren * * The window is implicitly shown if the "hidden" property is not set. * - * Windows with the "tooltip" and "menu" properties are popup windows and have - * the behaviors and guidelines outlined in SDL_CreatePopupWindow(). + * These are additional supported properties with Emscripten: + * + * - `SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING`: the id given to the + * canvas element. This should start with a '#' sign + * - `SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING`: override the + * binding element for keyboard inputs for this canvas. The variable can be + * one of: + * - "#window": the javascript window object (default) + * - "#document": the javascript document object + * - "#screen": the javascript window.screen object + * - "#canvas": the WebGL canvas element + * - "#none": Don't bind anything at all + * - any other string without a leading # sign applies to the element on the + * page with that ID. Windows with the "tooltip" and "menu" properties are + * popup windows and have the behaviors and guidelines outlined in + * SDL_CreatePopupWindow(). * * If this window is being created to be used with an SDL_Renderer, you should * not add a graphics API specific property @@ -1360,12 +1437,15 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowWithProperties(SDL_Prop #define SDL_PROP_WINDOW_CREATE_Y_NUMBER "SDL.window.create.y" #define SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER "SDL.window.create.cocoa.window" #define SDL_PROP_WINDOW_CREATE_COCOA_VIEW_POINTER "SDL.window.create.cocoa.view" +#define SDL_PROP_WINDOW_CREATE_WINDOWSCENE_POINTER "SDL.window.create.uikit.windowscene" #define SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN "SDL.window.create.wayland.surface_role_custom" #define SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN "SDL.window.create.wayland.create_egl_window" #define SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER "SDL.window.create.wayland.wl_surface" #define SDL_PROP_WINDOW_CREATE_WIN32_HWND_POINTER "SDL.window.create.win32.hwnd" #define SDL_PROP_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER "SDL.window.create.win32.pixel_format_hwnd" #define SDL_PROP_WINDOW_CREATE_X11_WINDOW_NUMBER "SDL.window.create.x11.window" +#define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING "SDL.window.create.emscripten.canvas_id" +#define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING "SDL.window.create.emscripten.keyboard_element" /** * Get the numeric ID of a window. @@ -1473,12 +1553,12 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *window) * - `SDL_PROP_WINDOW_COCOA_WINDOW_POINTER`: the `(__unsafe_unretained)` * NSWindow associated with the window * - `SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER`: the NSInteger tag - * assocated with metal views on the window + * associated with metal views on the window * * On OpenVR: * - * - `SDL_PROP_WINDOW_OPENVR_OVERLAY_ID`: the OpenVR Overlay Handle ID for the - * associated overlay window. + * - `SDL_PROP_WINDOW_OPENVR_OVERLAY_ID_NUMBER`: the OpenVR Overlay Handle ID + * for the associated overlay window. * * On Vivante: * @@ -1530,6 +1610,13 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *window) * - `SDL_PROP_WINDOW_X11_WINDOW_NUMBER`: the X11 Window associated with the * window * + * On Emscripten: + * + * - `SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING`: the id the canvas element + * will have + * - `SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING`: the keyboard + * element that associates keyboard events to this window + * * \param window the window to query. * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. @@ -1556,7 +1643,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window #define SDL_PROP_WINDOW_KMSDRM_GBM_DEVICE_POINTER "SDL.window.kmsdrm.gbm_dev" #define SDL_PROP_WINDOW_COCOA_WINDOW_POINTER "SDL.window.cocoa.window" #define SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER "SDL.window.cocoa.metal_view_tag" -#define SDL_PROP_WINDOW_OPENVR_OVERLAY_ID "SDL.window.openvr.overlay_id" +#define SDL_PROP_WINDOW_OPENVR_OVERLAY_ID_NUMBER "SDL.window.openvr.overlay_id" #define SDL_PROP_WINDOW_VIVANTE_DISPLAY_POINTER "SDL.window.vivante.display" #define SDL_PROP_WINDOW_VIVANTE_WINDOW_POINTER "SDL.window.vivante.window" #define SDL_PROP_WINDOW_VIVANTE_SURFACE_POINTER "SDL.window.vivante.surface" @@ -1575,6 +1662,8 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window #define SDL_PROP_WINDOW_X11_DISPLAY_POINTER "SDL.window.x11.display" #define SDL_PROP_WINDOW_X11_SCREEN_NUMBER "SDL.window.x11.screen" #define SDL_PROP_WINDOW_X11_WINDOW_NUMBER "SDL.window.x11.window" +#define SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING "SDL.window.emscripten.canvas_id" +#define SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING "SDL.window.emscripten.keyboard_element" /** * Get the window flags. @@ -1592,6 +1681,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window * \sa SDL_MinimizeWindow * \sa SDL_SetWindowFullscreen * \sa SDL_SetWindowMouseGrab + * \sa SDL_SetWindowFillDocument * \sa SDL_ShowWindow */ extern SDL_DECLSPEC SDL_WindowFlags SDLCALL SDL_GetWindowFlags(SDL_Window *window); @@ -1632,15 +1722,16 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetWindowTitle(SDL_Window *window); /** * Set the icon for a window. * - * If this function is passed a surface with alternate representations, the - * surface will be interpreted as the content to be used for 100% display - * scale, and the alternate representations will be used for high DPI - * situations. For example, if the original surface is 32x32, then on a 2x - * macOS display or 200% display scale on Windows, a 64x64 version of the - * image will be used, if available. If a matching version of the image isn't - * available, the closest larger size image will be downscaled to the - * appropriate size and be used instead, if available. Otherwise, the closest - * smaller image will be upscaled and be used instead. + * If this function is passed a surface with alternate representations added + * using SDL_AddSurfaceAlternateImage(), the surface will be interpreted as + * the content to be used for 100% display scale, and the alternate + * representations will be used for high DPI situations. For example, if the + * original surface is 32x32, then on a 2x macOS display or 200% display scale + * on Windows, a 64x64 version of the image will be used, if available. If a + * matching version of the image isn't available, the closest larger size + * image will be downscaled to the appropriate size and be used instead, if + * available. Otherwise, the closest smaller image will be upscaled and be + * used instead. * * \param window the window to change. * \param icon an SDL_Surface structure containing the icon for the window. @@ -1650,6 +1741,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetWindowTitle(SDL_Window *window); * \threadsafety This function should only be called on the main thread. * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_AddSurfaceAlternateImage */ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowIcon(SDL_Window *window, SDL_Surface *icon); @@ -1776,6 +1869,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowSize(SDL_Window *window, int w, in * \sa SDL_GetRenderOutputSize * \sa SDL_GetWindowSizeInPixels * \sa SDL_SetWindowSize + * \sa SDL_EVENT_WINDOW_RESIZED */ extern SDL_DECLSPEC bool SDLCALL SDL_GetWindowSize(SDL_Window *window, int *w, int *h); @@ -1787,7 +1881,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetWindowSize(SDL_Window *window, int *w, i * notches, TV overscan, etc. This function provides the area of the window * which is safe to have interactable content. You should continue rendering * into the rest of the window, but it should not contain visually important - * or interactible content. + * or interactable content. * * \param window the window to query. * \param rect a pointer filled in with the client area that is safe for @@ -1843,7 +1937,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetWindowSafeArea(SDL_Window *window, SDL_R extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowAspectRatio(SDL_Window *window, float min_aspect, float max_aspect); /** - * Get the size of a window's client area. + * Get the aspect ratio of a window's client area. * * \param window the window to query the width and height from. * \param min_aspect a pointer filled in with the minimum aspect ratio of the @@ -2057,6 +2151,37 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowResizable(SDL_Window *window, bool */ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowAlwaysOnTop(SDL_Window *window, bool on_top); +/** + * Set the window to fill the current document space (Emscripten only). + * + * This will add or remove the window's `SDL_WINDOW_FILL_DOCUMENT` flag. + * + * Currently this flag only applies to the Emscripten target. + * + * When enabled, the canvas element fills the entire document. Resize events + * will be generated as the browser window is resized, as that will adjust the + * canvas size as well. The canvas will cover anything else on the page, + * including any controls provided by Emscripten in its generated HTML file + * (in fact, any elements on the page that aren't the canvas will be moved + * into a hidden `div` element). + * + * Often times this is desirable for a browser-based game, but it means + * several things that we expect of an SDL window on other platforms might not + * work as expected, such as minimum window sizes and aspect ratios. + * + * \param window the window of which to change the fill-document state. + * \param fill true to set the window to fill the document, false to disable. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_GetWindowFlags + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowFillDocument(SDL_Window *window, bool fill); + /** * Show a window. * @@ -2470,7 +2595,6 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowKeyboardGrab(SDL_Window *window, b * * \sa SDL_GetWindowMouseRect * \sa SDL_SetWindowMouseRect - * \sa SDL_SetWindowMouseGrab * \sa SDL_SetWindowKeyboardGrab */ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowMouseGrab(SDL_Window *window, bool grabbed); @@ -2815,6 +2939,62 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowShape(SDL_Window *window, SDL_Surf */ extern SDL_DECLSPEC bool SDLCALL SDL_FlashWindow(SDL_Window *window, SDL_FlashOperation operation); +/** + * Sets the state of the progress bar for the given window’s taskbar icon. + * + * \param window the window whose progress state is to be modified. + * \param state the progress state. `SDL_PROGRESS_STATE_NONE` stops displaying + * the progress bar. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowProgressState(SDL_Window *window, SDL_ProgressState state); + +/** + * Get the state of the progress bar for the given window’s taskbar icon. + * + * \param window the window to get the current progress state from. + * \returns the progress state, or `SDL_PROGRESS_STATE_INVALID` on failure; + * call SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC SDL_ProgressState SDLCALL SDL_GetWindowProgressState(SDL_Window *window); + +/** + * Sets the value of the progress bar for the given window’s taskbar icon. + * + * \param window the window whose progress value is to be modified. + * \param value the progress value in the range of [0.0f - 1.0f]. If the value + * is outside the valid range, it gets clamped. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowProgressValue(SDL_Window *window, float value); + +/** + * Get the value of the progress bar for the given window’s taskbar icon. + * + * \param window the window to get the current progress value from. + * \returns the progress value in the range of [0.0f - 1.0f], or -1.0f on + * failure; call SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC float SDLCALL SDL_GetWindowProgressValue(SDL_Window *window); + /** * Destroy a window. * @@ -3050,8 +3230,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_GL_ResetAttributes(void); * SDL_GL_GetAttribute() to check the values after creating the OpenGL * context, since the values obtained can differ from the requested ones. * - * \param attr an SDL_GLAttr enum value specifying the OpenGL attribute to - * set. + * \param attr an enum value specifying the OpenGL attribute to set. * \param value the desired value for the attribute. * \returns true on success or false on failure; call SDL_GetError() for more * information. @@ -3060,6 +3239,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_GL_ResetAttributes(void); * * \since This function is available since SDL 3.2.0. * + * \sa SDL_GL_CreateContext * \sa SDL_GL_GetAttribute * \sa SDL_GL_ResetAttributes */ @@ -3086,6 +3266,12 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GL_GetAttribute(SDL_GLAttr attr, int *value /** * Create an OpenGL context for an OpenGL window, and make it current. * + * The OpenGL context will be created with the current states set through + * SDL_GL_SetAttribute(). + * + * The SDL_Window specified must have been created with the SDL_WINDOW_OPENGL + * flag, or context creation will fail. + * * Windows users new to OpenGL should note that, for historical reasons, GL * functions added after OpenGL version 1.1 are not available by default. * Those functions must be loaded at run-time, either with an OpenGL diff --git a/libs/SDL3/include/build_config/SDL_build_config.h b/libs/SDL3/include/build_config/SDL_build_config.h index 83031b7..a1658c2 100644 --- a/libs/SDL3/include/build_config/SDL_build_config.h +++ b/libs/SDL3/include/build_config/SDL_build_config.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/build_config/SDL_build_config.h.cmake b/libs/SDL3/include/build_config/SDL_build_config.h.cmake index 6ce491e..5d4e071 100644 --- a/libs/SDL3/include/build_config/SDL_build_config.h.cmake +++ b/libs/SDL3/include/build_config/SDL_build_config.h.cmake @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -31,6 +31,12 @@ /* General platform specific identifiers */ #include +#cmakedefine SDL_PLATFORM_PRIVATE 1 + +#ifdef SDL_PLATFORM_PRIVATE +#include "SDL_begin_config_private.h" +#endif + #cmakedefine HAVE_GCC_ATOMICS 1 #cmakedefine HAVE_GCC_SYNC_LOCK_TEST_AND_SET 1 @@ -174,6 +180,7 @@ #cmakedefine HAVE_MEMFD_CREATE 1 #cmakedefine HAVE_POSIX_FALLOCATE 1 #cmakedefine HAVE_SIGACTION 1 +#cmakedefine HAVE_SIGTIMEDWAIT 1 #cmakedefine HAVE_SA_SIGACTION 1 #cmakedefine HAVE_ST_MTIM 1 #cmakedefine HAVE_SETJMP 1 @@ -192,8 +199,10 @@ #cmakedefine HAVE_SEM_TIMEDWAIT 1 #cmakedefine HAVE_GETAUXVAL 1 #cmakedefine HAVE_ELF_AUX_INFO 1 -#cmakedefine HAVE_POLL 1 +#cmakedefine HAVE_PPOLL 1 #cmakedefine HAVE__EXIT 1 +#cmakedefine HAVE_GETRESUID 1 +#cmakedefine HAVE_GETRESGID 1 #endif /* HAVE_LIBC */ @@ -209,6 +218,10 @@ #cmakedefine HAVE_LIBUDEV_H 1 #cmakedefine HAVE_LIBDECOR_H 1 #cmakedefine HAVE_LIBURING_H 1 +#cmakedefine HAVE_FRIBIDI_H 1 +#cmakedefine SDL_FRIBIDI_DYNAMIC @SDL_FRIBIDI_DYNAMIC@ +#cmakedefine HAVE_LIBTHAI_H 1 +#cmakedefine SDL_LIBTHAI_DYNAMIC @SDL_LIBTHAI_DYNAMIC@ #cmakedefine HAVE_DDRAW_H 1 #cmakedefine HAVE_DSOUND_H 1 @@ -217,6 +230,7 @@ #cmakedefine HAVE_WINDOWS_GAMING_INPUT_H 1 #cmakedefine HAVE_GAMEINPUT_H 1 #cmakedefine HAVE_DXGI_H 1 +#cmakedefine HAVE_DXGI1_5_H 1 #cmakedefine HAVE_DXGI1_6_H 1 #cmakedefine HAVE_MMDEVICEAPI_H 1 @@ -225,6 +239,10 @@ #cmakedefine HAVE_SHELLSCALINGAPI_H 1 #cmakedefine USE_POSIX_SPAWN 1 +#cmakedefine HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR 1 +#cmakedefine HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP 1 + +#cmakedefine SDL_DISABLE_DLOPEN_NOTES 1 /* SDL internal assertion support */ #cmakedefine SDL_DEFAULT_ASSERT_LEVEL_CONFIGURED 1 @@ -272,8 +290,11 @@ #cmakedefine SDL_AUDIO_DRIVER_PSP 1 #cmakedefine SDL_AUDIO_DRIVER_PS2 1 #cmakedefine SDL_AUDIO_DRIVER_N3DS 1 +#cmakedefine SDL_AUDIO_DRIVER_NGAGE 1 #cmakedefine SDL_AUDIO_DRIVER_QNX 1 +#cmakedefine SDL_AUDIO_DRIVER_PRIVATE 1 + /* Enable various input drivers */ #cmakedefine SDL_INPUT_LINUXEV 1 #cmakedefine SDL_INPUT_LINUXKD 1 @@ -299,11 +320,17 @@ #cmakedefine SDL_JOYSTICK_VITA 1 #cmakedefine SDL_JOYSTICK_WGI 1 #cmakedefine SDL_JOYSTICK_XINPUT 1 + +#cmakedefine SDL_JOYSTICK_PRIVATE 1 + #cmakedefine SDL_HAPTIC_DUMMY 1 #cmakedefine SDL_HAPTIC_LINUX 1 #cmakedefine SDL_HAPTIC_IOKIT 1 #cmakedefine SDL_HAPTIC_DINPUT 1 #cmakedefine SDL_HAPTIC_ANDROID 1 + +#cmakedefine SDL_HAPTIC_PRIVATE 1 + #cmakedefine SDL_LIBUSB_DYNAMIC @SDL_LIBUSB_DYNAMIC@ #cmakedefine SDL_UDEV_DYNAMIC @SDL_UDEV_DYNAMIC@ @@ -312,6 +339,8 @@ #cmakedefine SDL_PROCESS_POSIX 1 #cmakedefine SDL_PROCESS_WINDOWS 1 +#cmakedefine SDL_PROCESS_PRIVATE 1 + /* Enable various sensor drivers */ #cmakedefine SDL_SENSOR_ANDROID 1 #cmakedefine SDL_SENSOR_COREMOTION 1 @@ -319,12 +348,17 @@ #cmakedefine SDL_SENSOR_DUMMY 1 #cmakedefine SDL_SENSOR_VITA 1 #cmakedefine SDL_SENSOR_N3DS 1 +#cmakedefine SDL_SENSOR_EMSCRIPTEN 1 + +#cmakedefine SDL_SENSOR_PRIVATE 1 /* Enable various shared object loading systems */ #cmakedefine SDL_LOADSO_DLOPEN 1 #cmakedefine SDL_LOADSO_DUMMY 1 #cmakedefine SDL_LOADSO_WINDOWS 1 +#cmakedefine SDL_LOADSO_PRIVATE 1 + /* Enable various threading systems */ #cmakedefine SDL_THREAD_GENERIC_COND_SUFFIX 1 #cmakedefine SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1 @@ -337,6 +371,8 @@ #cmakedefine SDL_THREAD_PS2 1 #cmakedefine SDL_THREAD_N3DS 1 +#cmakedefine SDL_THREAD_PRIVATE 1 + /* Enable various RTC systems */ #cmakedefine SDL_TIME_UNIX 1 #cmakedefine SDL_TIME_WINDOWS 1 @@ -344,6 +380,9 @@ #cmakedefine SDL_TIME_PSP 1 #cmakedefine SDL_TIME_PS2 1 #cmakedefine SDL_TIME_N3DS 1 +#cmakedefine SDL_TIME_NGAGE 1 + +#cmakedefine SDL_TIME_PRIVATE 1 /* Enable various timer systems */ #cmakedefine SDL_TIMER_HAIKU 1 @@ -354,6 +393,8 @@ #cmakedefine SDL_TIMER_PS2 1 #cmakedefine SDL_TIMER_N3DS 1 +#cmakedefine SDL_TIMER_PRIVATE 1 + /* Enable various video drivers */ #cmakedefine SDL_VIDEO_DRIVER_ANDROID 1 #cmakedefine SDL_VIDEO_DRIVER_COCOA 1 @@ -364,6 +405,7 @@ #cmakedefine SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC @SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC@ #cmakedefine SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC_GBM @SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC_GBM@ #cmakedefine SDL_VIDEO_DRIVER_N3DS 1 +#cmakedefine SDL_VIDEO_DRIVER_NGAGE 1 #cmakedefine SDL_VIDEO_DRIVER_OFFSCREEN 1 #cmakedefine SDL_VIDEO_DRIVER_PS2 1 #cmakedefine SDL_VIDEO_DRIVER_PSP 1 @@ -390,19 +432,25 @@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 @SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR @SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS @SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS@ -#cmakedefine SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM 1 +#cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST @SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST@ +#cmakedefine SDL_VIDEO_DRIVER_X11_HAS_XKBLIB 1 #cmakedefine SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XCURSOR 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XDBE 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XFIXES 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XINPUT2 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1 +#cmakedefine SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 1 +#cmakedefine SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE @SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE@ #cmakedefine SDL_VIDEO_DRIVER_X11_XRANDR 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XSCRNSAVER 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XSHAPE 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XSYNC 1 +#cmakedefine SDL_VIDEO_DRIVER_X11_XTEST 1 #cmakedefine SDL_VIDEO_DRIVER_QNX 1 +#cmakedefine SDL_VIDEO_DRIVER_PRIVATE 1 + #cmakedefine SDL_VIDEO_RENDER_D3D 1 #cmakedefine SDL_VIDEO_RENDER_D3D11 1 #cmakedefine SDL_VIDEO_RENDER_D3D12 1 @@ -411,10 +459,13 @@ #cmakedefine SDL_VIDEO_RENDER_VULKAN 1 #cmakedefine SDL_VIDEO_RENDER_OGL 1 #cmakedefine SDL_VIDEO_RENDER_OGL_ES2 1 +#cmakedefine SDL_VIDEO_RENDER_NGAGE 1 #cmakedefine SDL_VIDEO_RENDER_PS2 1 #cmakedefine SDL_VIDEO_RENDER_PSP 1 #cmakedefine SDL_VIDEO_RENDER_VITA_GXM 1 +#cmakedefine SDL_VIDEO_RENDER_PRIVATE 1 + /* Enable OpenGL support */ #cmakedefine SDL_VIDEO_OPENGL 1 #cmakedefine SDL_VIDEO_OPENGL_ES 1 @@ -424,6 +475,8 @@ #cmakedefine SDL_VIDEO_OPENGL_WGL 1 #cmakedefine SDL_VIDEO_OPENGL_EGL 1 +#cmakedefine SDL_VIDEO_STATIC_ANGLE 1 + /* Enable Vulkan support */ #cmakedefine SDL_VIDEO_VULKAN 1 @@ -436,6 +489,8 @@ #cmakedefine SDL_GPU_VULKAN 1 #cmakedefine SDL_GPU_METAL 1 +#cmakedefine SDL_GPU_PRIVATE 1 + /* Enable system power support */ #cmakedefine SDL_POWER_ANDROID 1 #cmakedefine SDL_POWER_LINUX 1 @@ -449,6 +504,8 @@ #cmakedefine SDL_POWER_PSP 1 #cmakedefine SDL_POWER_N3DS 1 +#cmakedefine SDL_POWER_PRIVATE 1 + /* Enable system filesystem support */ #cmakedefine SDL_FILESYSTEM_ANDROID 1 #cmakedefine SDL_FILESYSTEM_HAIKU 1 @@ -463,14 +520,20 @@ #cmakedefine SDL_FILESYSTEM_PS2 1 #cmakedefine SDL_FILESYSTEM_N3DS 1 +#cmakedefine SDL_FILESYSTEM_PRIVATE 1 + /* Enable system storage support */ #cmakedefine SDL_STORAGE_STEAM @SDL_STORAGE_STEAM@ +#cmakedefine SDL_STORAGE_PRIVATE 1 + /* Enable system FSops support */ #cmakedefine SDL_FSOPS_POSIX 1 #cmakedefine SDL_FSOPS_WINDOWS 1 #cmakedefine SDL_FSOPS_DUMMY 1 +#cmakedefine SDL_FSOPS_PRIVATE 1 + /* Enable camera subsystem */ #cmakedefine SDL_CAMERA_DRIVER_DUMMY 1 /* !!! FIXME: for later cmakedefine SDL_CAMERA_DRIVER_DISK 1 */ @@ -483,9 +546,14 @@ #cmakedefine SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC @SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC@ #cmakedefine SDL_CAMERA_DRIVER_VITA 1 +#cmakedefine SDL_CAMERA_DRIVER_PRIVATE 1 + /* Enable dialog subsystem */ #cmakedefine SDL_DIALOG_DUMMY 1 +/* Enable tray subsystem */ +#cmakedefine SDL_TRAY_DUMMY 1 + /* Enable assembly routines */ #cmakedefine SDL_ALTIVEC_BLITTERS 1 @@ -505,6 +573,11 @@ #cmakedefine SDL_VIDEO_VITA_PVR 1 #cmakedefine SDL_VIDEO_VITA_PVR_OGL 1 +/* xkbcommon version info */ +#define SDL_XKBCOMMON_VERSION_MAJOR @SDL_XKBCOMMON_VERSION_MAJOR@ +#define SDL_XKBCOMMON_VERSION_MINOR @SDL_XKBCOMMON_VERSION_MINOR@ +#define SDL_XKBCOMMON_VERSION_PATCH @SDL_XKBCOMMON_VERSION_PATCH@ + /* Libdecor version info */ #define SDL_LIBDECOR_VERSION_MAJOR @SDL_LIBDECOR_VERSION_MAJOR@ #define SDL_LIBDECOR_VERSION_MINOR @SDL_LIBDECOR_VERSION_MINOR@ @@ -545,4 +618,8 @@ typedef unsigned int uintptr_t; #cmakedefine SDL_DISABLE_LASX 1 #cmakedefine SDL_DISABLE_NEON 1 +#ifdef SDL_PLATFORM_PRIVATE +#include "SDL_end_config_private.h" +#endif + #endif /* SDL_build_config_h_ */ diff --git a/libs/SDL3/include/build_config/SDL_build_config_android.h b/libs/SDL3/include/build_config/SDL_build_config_android.h index a0727f5..ce6f3e2 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_android.h +++ b/libs/SDL3/include/build_config/SDL_build_config_android.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -208,6 +208,9 @@ #define SDL_CAMERA_DRIVER_ANDROID 1 #endif /* SDL_CAMERA_DISABLED */ +/* Enable tray subsystem */ +#define SDL_TRAY_DUMMY 1 + /* Enable nl_langinfo and high-res file times on version 26 and higher. */ #if __ANDROID_API__ >= 26 #define HAVE_NL_LANGINFO 1 diff --git a/libs/SDL3/include/build_config/SDL_build_config_ios.h b/libs/SDL3/include/build_config/SDL_build_config_ios.h index be1ec29..308270b 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_ios.h +++ b/libs/SDL3/include/build_config/SDL_build_config_ios.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -214,7 +214,7 @@ #define SDL_FSOPS_POSIX 1 /* enable camera support */ -#ifndef SDL_PLATFORM_TVOS +#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) #define SDL_CAMERA_DRIVER_COREMEDIA 1 #endif @@ -223,4 +223,7 @@ /* Enable dialog subsystem */ #define SDL_DIALOG_DUMMY 1 +/* Enable tray subsystem */ +#define SDL_TRAY_DUMMY 1 + #endif /* SDL_build_config_ios_h_ */ diff --git a/libs/SDL3/include/build_config/SDL_build_config_macos.h b/libs/SDL3/include/build_config/SDL_build_config_macos.h index 1a66449..1890c9a 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_macos.h +++ b/libs/SDL3/include/build_config/SDL_build_config_macos.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/include/build_config/SDL_build_config_minimal.h b/libs/SDL3/include/build_config/SDL_build_config_minimal.h index d5fe736..e557d92 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_minimal.h +++ b/libs/SDL3/include/build_config/SDL_build_config_minimal.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -98,4 +98,7 @@ typedef unsigned int uintptr_t; /* Enable dialog subsystem */ #define SDL_DIALOG_DUMMY 1 +/* Enable tray subsystem */ +#define SDL_TRAY_DUMMY 1 + #endif /* SDL_build_config_minimal_h_ */ 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 98079cb..07eecb4 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_windows.h +++ b/libs/SDL3/include/build_config/SDL_build_config_windows.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -85,6 +85,7 @@ typedef unsigned int uintptr_t; #define HAVE_DXGI_H 1 #define HAVE_XINPUT_H 1 #if defined(_WIN32_MAXVER) && _WIN32_MAXVER >= 0x0A00 /* Windows 10 SDK */ +#define HAVE_DXGI1_5_H 1 #define HAVE_DXGI1_6_H 1 #define HAVE_WINDOWS_GAMING_INPUT_H 1 #endif diff --git a/libs/SDL3/include/build_config/SDL_build_config_wingdk.h b/libs/SDL3/include/build_config/SDL_build_config_wingdk.h index 502eb92..ee06c1c 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_wingdk.h +++ b/libs/SDL3/include/build_config/SDL_build_config_wingdk.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -37,6 +37,7 @@ #define HAVE_DSOUND_H 1 /* No SDK version checks needed for these because the SDK has to be new. */ #define HAVE_DXGI_H 1 +#define HAVE_DXGI1_5_H 1 #define HAVE_DXGI1_6_H 1 #define HAVE_XINPUT_H 1 #define HAVE_WINDOWS_GAMING_INPUT_H 1 @@ -145,7 +146,7 @@ #define HAVE_TRUNC 1 #define HAVE_TRUNCF 1 #define HAVE__FSEEKI64 1 -#endif /* _MSC_VER */ +#endif /* _MSC_VER */ /* Enable various audio drivers */ #if defined(HAVE_MMDEVICEAPI_H) && defined(HAVE_AUDIOCLIENT_H) diff --git a/libs/SDL3/include/build_config/SDL_build_config_xbox.h b/libs/SDL3/include/build_config/SDL_build_config_xbox.h index 5834e71..0841a1c 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_xbox.h +++ b/libs/SDL3/include/build_config/SDL_build_config_xbox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -144,7 +144,7 @@ #define HAVE_TRUNC 1 #define HAVE_TRUNCF 1 #define HAVE__FSEEKI64 1 -#endif /* _MSC_VER */ +#endif /* _MSC_VER */ /* Enable various audio drivers */ #if defined(HAVE_MMDEVICEAPI_H) && defined(HAVE_AUDIOCLIENT_H) @@ -220,4 +220,7 @@ /* Enable dialog subsystem */ #define SDL_DIALOG_DUMMY 1 +/* Enable tray subsystem */ +#define SDL_TRAY_DUMMY 1 + #endif /* SDL_build_config_wingdk_h_ */ diff --git a/libs/SDL3/include/build_config/SDL_revision.h.cmake b/libs/SDL3/include/build_config/SDL_revision.h.cmake index a7e6a29..4dda571 100644 --- a/libs/SDL3/include/build_config/SDL_revision.h.cmake +++ b/libs/SDL3/include/build_config/SDL_revision.h.cmake @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -33,9 +33,9 @@ #cmakedefine SDL_VENDOR_INFO "@SDL_VENDOR_INFO@" #if defined(SDL_VENDOR_INFO) -#define SDL_REVISION "release-3.2.20-0-g96292a5b4 (" SDL_VENDOR_INFO ")" +#define SDL_REVISION "SDL-release-3.4.2-0-g683181b47 (" SDL_VENDOR_INFO ")" #else -#define SDL_REVISION "release-3.2.20-0-g96292a5b4" +#define SDL_REVISION "SDL-release-3.4.2-0-g683181b47" #endif #endif /* SDL_revision_h_ */ diff --git a/libs/SDL3/src/SDL.c b/libs/SDL3/src/SDL.c index 46a74aa..7679215 100644 --- a/libs/SDL3/src/SDL.c +++ b/libs/SDL3/src/SDL.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -30,6 +30,10 @@ // this checks for HAVE_DBUS_DBUS_H internally. #include "core/linux/SDL_dbus.h" +#if defined(SDL_PLATFORM_UNIX) && !defined(SDL_PLATFORM_ANDROID) +#include "core/unix/SDL_gtk.h" +#endif + #ifdef SDL_PLATFORM_EMSCRIPTEN #include #endif @@ -144,7 +148,7 @@ static bool SDL_ValidMetadataProperty(const char *name) bool SDL_SetAppMetadataProperty(const char *name, const char *value) { - if (!SDL_ValidMetadataProperty(name)) { + CHECK_PARAM(!SDL_ValidMetadataProperty(name)) { return SDL_InvalidParamError("name"); } @@ -153,7 +157,7 @@ bool SDL_SetAppMetadataProperty(const char *name, const char *value) const char *SDL_GetAppMetadataProperty(const char *name) { - if (!SDL_ValidMetadataProperty(name)) { + CHECK_PARAM(!SDL_ValidMetadataProperty(name)) { SDL_InvalidParamError("name"); return NULL; } @@ -185,6 +189,8 @@ static bool SDL_MainIsReady = false; static bool SDL_MainIsReady = true; #endif static SDL_ThreadID SDL_MainThreadID = 0; +static SDL_ThreadID SDL_EventsThreadID = 0; +static SDL_ThreadID SDL_VideoThreadID = 0; static bool SDL_bInMainQuit = false; static Uint8 SDL_SubsystemRefCount[32]; @@ -233,6 +239,7 @@ static bool SDL_ShouldQuitSubsystem(Uint32 subsystem) return (((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 1)) || SDL_bInMainQuit); } +#if !defined(SDL_VIDEO_DISABLED) || !defined(SDL_AUDIO_DISABLED) || !defined(SDL_JOYSTICK_DISABLED) /* Private helper to either increment's existing ref counter, * or fully init a new subsystem. */ static bool SDL_InitOrIncrementSubsystem(Uint32 subsystem) @@ -248,6 +255,7 @@ static bool SDL_InitOrIncrementSubsystem(Uint32 subsystem) } return SDL_InitSubSystem(subsystem); } +#endif // !SDL_VIDEO_DISABLED || !SDL_AUDIO_DISABLED || !SDL_JOYSTICK_DISABLED void SDL_SetMainReady(void) { @@ -260,14 +268,21 @@ void SDL_SetMainReady(void) bool SDL_IsMainThread(void) { - if (SDL_MainThreadID == 0) { - // Not initialized yet? - return true; + if (SDL_VideoThreadID) { + return (SDL_GetCurrentThreadID() == SDL_VideoThreadID); } - if (SDL_MainThreadID == SDL_GetCurrentThreadID()) { - return true; + if (SDL_EventsThreadID) { + return (SDL_GetCurrentThreadID() == SDL_EventsThreadID); } - return false; + if (SDL_MainThreadID) { + return (SDL_GetCurrentThreadID() == SDL_MainThreadID); + } + return true; +} + +bool SDL_IsVideoThread(void) +{ + return (SDL_GetCurrentThreadID() == SDL_VideoThreadID); } // Initialize all the subsystems that require initialization before threads start @@ -275,6 +290,11 @@ void SDL_InitMainThread(void) { static bool done_info = false; + // If we haven't done it by now, mark this as the main thread + if (SDL_MainThreadID == 0) { + SDL_MainThreadID = SDL_GetCurrentThreadID(); + } + SDL_InitTLSData(); SDL_InitEnvironment(); SDL_InitTicks(); @@ -311,6 +331,26 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) return SDL_SetError("Application didn't initialize properly, did you include SDL_main.h in the file containing your main() function?"); } +#ifdef SDL_PLATFORM_EMSCRIPTEN + MAIN_THREAD_EM_ASM({ + // make sure this generic table to hang SDL-specific Javascript stuff is available at init time. + if (typeof(Module['SDL3']) === 'undefined') { + Module['SDL3'] = {}; + } + + var SDL3 = Module['SDL3']; + #if defined(__wasm32__) + if (typeof(SDL3.JSVarToCPtr) === 'undefined') { SDL3.JSVarToCPtr = function(v) { return v; }; } + if (typeof(SDL3.CPtrToHeap32Index) === 'undefined') { SDL3.CPtrToHeap32Index = function(ptr) { return ptr >>> 2; }; } + #elif defined(__wasm64__) + if (typeof(SDL3.JSVarToCPtr) === 'undefined') { SDL3.JSVarToCPtr = function(v) { return BigInt(v); }; } + if (typeof(SDL3.CPtrToHeap32Index) === 'undefined') { SDL3.CPtrToHeap32Index = function(ptr) { return Number(ptr / 4n); }; } + #else + #error Please define your platform. + #endif + }); +#endif + SDL_InitMainThread(); #ifdef SDL_USE_LIBDBUS @@ -329,6 +369,11 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) if (flags & SDL_INIT_EVENTS) { if (SDL_ShouldInitSubsystem(SDL_INIT_EVENTS)) { SDL_IncrementSubsystemRefCount(SDL_INIT_EVENTS); + + // Note which thread initialized events + // This is the thread which should be pumping events + SDL_EventsThreadID = SDL_GetCurrentThreadID(); + if (!SDL_InitEvents()) { SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS); goto quit_and_error; @@ -348,12 +393,16 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) goto quit_and_error; } + SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO); + // We initialize video on the main thread // On Apple platforms this is a requirement. // On other platforms, this is the definition. - SDL_MainThreadID = SDL_GetCurrentThreadID(); + SDL_VideoThreadID = SDL_GetCurrentThreadID(); +#ifdef SDL_PLATFORM_APPLE + SDL_assert(SDL_VideoThreadID == SDL_MainThreadID); +#endif - SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO); if (!SDL_VideoInit(NULL)) { SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO); SDL_PushError(); @@ -603,6 +652,7 @@ void SDL_QuitSubSystem(SDL_InitFlags flags) if (SDL_ShouldQuitSubsystem(SDL_INIT_VIDEO)) { SDL_QuitRender(); SDL_VideoQuit(); + SDL_VideoThreadID = 0; // video implies events SDL_QuitSubSystem(SDL_INIT_EVENTS); } @@ -613,6 +663,7 @@ void SDL_QuitSubSystem(SDL_InitFlags flags) if (flags & SDL_INIT_EVENTS) { if (SDL_ShouldQuitSubsystem(SDL_INIT_EVENTS)) { SDL_QuitEvents(); + SDL_EventsThreadID = 0; } SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS); } @@ -663,6 +714,10 @@ void SDL_Quit(void) SDL_DBus_Quit(); #endif +#if defined(SDL_PLATFORM_UNIX) && !defined(SDL_PLATFORM_ANDROID) && !defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(SDL_PLATFORM_PRIVATE) + SDL_Gtk_Quit(); +#endif + SDL_QuitTimers(); SDL_QuitAsyncIO(); @@ -676,7 +731,7 @@ void SDL_Quit(void) /* Now that every subsystem has been quit, we reset the subsystem refcount * and the list of initialized subsystems. */ - SDL_memset(SDL_SubsystemRefCount, 0x0, sizeof(SDL_SubsystemRefCount)); + SDL_zeroa(SDL_SubsystemRefCount); SDL_QuitLog(); SDL_QuitHints(); @@ -728,6 +783,8 @@ const char *SDL_GetPlatform(void) return "macOS"; #elif defined(SDL_PLATFORM_NETBSD) return "NetBSD"; +#elif defined(SDL_PLATFORM_NGAGE) + return "Nokia N-Gage"; #elif defined(SDL_PLATFORM_OPENBSD) return "OpenBSD"; #elif defined(SDL_PLATFORM_OS2) @@ -748,6 +805,8 @@ const char *SDL_GetPlatform(void) return "Xbox One"; #elif defined(SDL_PLATFORM_XBOXSERIES) return "Xbox Series X|S"; +#elif defined(SDL_PLATFORM_VISIONOS) + return "visionOS"; #elif defined(SDL_PLATFORM_IOS) return "iOS"; #elif defined(SDL_PLATFORM_TVOS) @@ -760,6 +819,8 @@ const char *SDL_GetPlatform(void) return "PlayStation Vita"; #elif defined(SDL_PLATFORM_3DS) return "Nintendo 3DS"; +#elif defined(SDL_PLATFORM_HURD) + return "GNU/Hurd"; #elif defined(__managarm__) return "Managarm"; #else @@ -831,9 +892,7 @@ SDL_Sandbox SDL_GetSandbox(void) #ifdef SDL_PLATFORM_WIN32 -#if (!defined(HAVE_LIBC) || defined(__WATCOMC__)) && !defined(SDL_STATIC_LIB) -// FIXME: Still need to include DllMain() on Watcom C ? - +#if !defined(HAVE_LIBC) && !defined(SDL_STATIC_LIB) BOOL APIENTRY MINGW32_FORCEALIGN _DllMainCRTStartup(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { diff --git a/libs/SDL3/src/SDL_assert.c b/libs/SDL3/src/SDL_assert.c index 2440d04..9d42ecf 100644 --- a/libs/SDL3/src/SDL_assert.c +++ b/libs/SDL3/src/SDL_assert.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -120,23 +120,10 @@ static void SDL_GenerateAssertionReport(void) } } -/* 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. */ -#ifdef __WATCOMC__ -extern void SDL_ExitProcess(int exitcode); -#pragma aux SDL_ExitProcess aborts; -#endif -extern SDL_NORETURN void SDL_ExitProcess(int exitcode); - -#ifdef __WATCOMC__ -static void SDL_AbortAssertion(void); -#pragma aux SDL_AbortAssertion aborts; -#endif static SDL_NORETURN void SDL_AbortAssertion(void) { SDL_Quit(); - SDL_ExitProcess(42); + SDL_abort(); } static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata) @@ -361,7 +348,7 @@ SDL_AssertState SDL_ReportAssertion(SDL_AssertData *data, const char *func, cons if (assertion_running == 2) { SDL_AbortAssertion(); } else if (assertion_running == 3) { // Abort asserted! - SDL_ExitProcess(42); + SDL_abort(); } else { while (1) { // do nothing but spin; what else can you do?! } diff --git a/libs/SDL3/src/SDL_assert_c.h b/libs/SDL3/src/SDL_assert_c.h index d892dbe..4d7a106 100644 --- a/libs/SDL3/src/SDL_assert_c.h +++ b/libs/SDL3/src/SDL_assert_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/SDL_error.c b/libs/SDL3/src/SDL_error.c index 3c62c8a..618eb93 100644 --- a/libs/SDL3/src/SDL_error.c +++ b/libs/SDL3/src/SDL_error.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -20,6 +20,8 @@ */ #include "SDL_internal.h" +#include "stdlib/SDL_vacopy.h" + // Simple error handling in SDL #include "SDL_error_c.h" diff --git a/libs/SDL3/src/SDL_error_c.h b/libs/SDL3/src/SDL_error_c.h index ba4550e..6ea7703 100644 --- a/libs/SDL3/src/SDL_error_c.h +++ b/libs/SDL3/src/SDL_error_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/SDL_guid.c b/libs/SDL3/src/SDL_guid.c index 6c355d8..913206a 100644 --- a/libs/SDL3/src/SDL_guid.c +++ b/libs/SDL3/src/SDL_guid.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -77,7 +77,7 @@ SDL_GUID SDL_StringToGUID(const char *pchGUID) // Make sure it's even len = (len) & ~0x1; - SDL_memset(&guid, 0x00, sizeof(guid)); + SDL_zero(guid); p = (Uint8 *)&guid; for (i = 0; (i < len) && ((p - (Uint8 *)&guid) < maxoutputbytes); i += 2, p++) { diff --git a/libs/SDL3/src/SDL_hashtable.c b/libs/SDL3/src/SDL_hashtable.c index 3124b41..8bc79f7 100644 --- a/libs/SDL3/src/SDL_hashtable.c +++ b/libs/SDL3/src/SDL_hashtable.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -292,7 +292,7 @@ static bool maybe_resize(SDL_HashTable *ht) bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value, bool replace) { - if (!table) { + CHECK_PARAM(!table) { return SDL_InvalidParamError("table"); } @@ -338,7 +338,7 @@ bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void * bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **value) { - if (!table) { + CHECK_PARAM(!table) { if (value) { *value = NULL; } @@ -364,7 +364,7 @@ bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key) { - if (!table) { + CHECK_PARAM(!table) { return SDL_InvalidParamError("table"); } @@ -384,9 +384,10 @@ bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key) bool SDL_IterateHashTable(const SDL_HashTable *table, SDL_HashTableIterateCallback callback, void *userdata) { - if (!table) { + CHECK_PARAM(!table) { return SDL_InvalidParamError("table"); - } else if (!callback) { + } + CHECK_PARAM(!callback) { return SDL_InvalidParamError("callback"); } @@ -410,7 +411,7 @@ bool SDL_IterateHashTable(const SDL_HashTable *table, SDL_HashTableIterateCallba bool SDL_HashTableEmpty(SDL_HashTable *table) { - if (!table) { + CHECK_PARAM(!table) { return SDL_InvalidParamError("table"); } diff --git a/libs/SDL3/src/SDL_hashtable.h b/libs/SDL3/src/SDL_hashtable.h index 598a6d6..e947efc 100644 --- a/libs/SDL3/src/SDL_hashtable.h +++ b/libs/SDL3/src/SDL_hashtable.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -219,10 +219,10 @@ typedef void (SDLCALL *SDL_HashDestroyCallback)(void *userdata, const void *key, * \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. + * still access 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. * @@ -248,7 +248,7 @@ typedef bool (SDLCALL *SDL_HashTableIterateCallback)(void *userdata, const SDL_H * * 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 + * will preallocate resources to accommodate 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 @@ -422,7 +422,7 @@ extern bool SDL_HashTableEmpty(SDL_HashTable *table); * \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). + * \returns true if iteration happened, false if not (bogus parameter, etc.). * * \since This function is available since SDL 3.4.0. */ diff --git a/libs/SDL3/src/SDL_hints.c b/libs/SDL3/src/SDL_hints.c index a10598f..36998c6 100644 --- a/libs/SDL3/src/SDL_hints.c +++ b/libs/SDL3/src/SDL_hints.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -87,7 +87,7 @@ static void SDLCALL CleanupHintProperty(void *userdata, void *value) SDL_free(hint); } -static const char* GetHintEnvironmentVariable(const char *name) +static const char *GetHintEnvironmentVariable(const char *name) { const char *result = SDL_getenv(name); if (!result && name && *name) { @@ -104,7 +104,7 @@ static const char* GetHintEnvironmentVariable(const char *name) bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriority priority) { - if (!name || !*name) { + CHECK_PARAM(!name || !*name) { return SDL_InvalidParamError("name"); } @@ -165,7 +165,7 @@ bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriori bool SDL_ResetHint(const char *name) { - if (!name || !*name) { + CHECK_PARAM(!name || !*name) { return SDL_InvalidParamError("name"); } @@ -316,9 +316,10 @@ bool SDL_GetHintBoolean(const char *name, bool default_value) bool SDL_AddHintCallback(const char *name, SDL_HintCallback callback, void *userdata) { - if (!name || !*name) { + CHECK_PARAM(!name || !*name) { return SDL_InvalidParamError("name"); - } else if (!callback) { + } + CHECK_PARAM(!callback) { return SDL_InvalidParamError("callback"); } diff --git a/libs/SDL3/src/SDL_hints_c.h b/libs/SDL3/src/SDL_hints_c.h index dd54cba..d740eb7 100644 --- a/libs/SDL3/src/SDL_hints_c.h +++ b/libs/SDL3/src/SDL_hints_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/SDL_internal.h b/libs/SDL3/src/SDL_internal.h index 8fcd96a..5d1fd3e 100644 --- a/libs/SDL3/src/SDL_internal.h +++ b/libs/SDL3/src/SDL_internal.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -265,15 +265,50 @@ extern "C" { #include "SDL_utils_c.h" #include "SDL_hashtable.h" + +/* SDL_ExitProcess is not declared in any public 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); + +#ifdef HAVE_LIBC +#define SDL_abort() abort() +#else +#define SDL_abort() do { \ + SDL_TriggerBreakpoint(); \ + SDL_ExitProcess(42); \ + } while (0) +#endif + #define PUSH_SDL_ERROR() \ { char *_error = SDL_strdup(SDL_GetError()); #define POP_SDL_ERROR() \ SDL_SetError("%s", _error); SDL_free(_error); } +#if defined(SDL_DISABLE_INVALID_PARAMS) +#ifdef DEBUG +// If you define SDL_DISABLE_INVALID_PARAMS, you're promising that you'll +// never pass an invalid parameter to SDL, since it may crash or lead to +// hard to diagnose bugs. Let's assert that this is true in debug builds. +#define OBJECT_VALIDATION_REQUIRED +#define CHECK_PARAM(invalid) SDL_assert_always(!(invalid)); if (false) +#else +#define CHECK_PARAM(invalid) if (false) +#endif +#elif defined(SDL_ASSERT_INVALID_PARAMS) +#define OBJECT_VALIDATION_REQUIRED +#define CHECK_PARAM(invalid) SDL_assert_always(!(invalid)); if (invalid) +#else +#define CHECK_PARAM(invalid) if (invalid) +#endif + // Do any initialization that needs to happen before threads are started extern void SDL_InitMainThread(void); +// Return true if this thread has initialized video +extern bool SDL_IsVideoThread(void); + /* The internal implementations of these functions have up to nanosecond precision. We can expose these functions as part of the API if we want to later. */ diff --git a/libs/SDL3/src/SDL_list.c b/libs/SDL3/src/SDL_list.c index 04cc082..a17c407 100644 --- a/libs/SDL3/src/SDL_list.c +++ b/libs/SDL3/src/SDL_list.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/SDL_list.h b/libs/SDL3/src/SDL_list.h index 83ec844..bdfbc35 100644 --- a/libs/SDL3/src/SDL_list.h +++ b/libs/SDL3/src/SDL_list.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/SDL_log.c b/libs/SDL3/src/SDL_log.c index 10a814f..0d1ee3f 100644 --- a/libs/SDL3/src/SDL_log.c +++ b/libs/SDL3/src/SDL_log.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -24,6 +24,10 @@ #include "core/windows/SDL_windows.h" #endif +#if defined(SDL_PLATFORM_NGAGE) +#include "core/ngage/SDL_ngage.h" +#endif + // Simple log messages in SDL #include "SDL_log_c.h" @@ -375,6 +379,9 @@ void SDL_ResetLogPriorities(void) SDL_LockMutex(SDL_log_lock); { + const char *env = SDL_getenv("DEBUG_INVOCATION"); + bool debug = (env && *env && *env != '0'); + CleanupLogPriorities(); SDL_log_default_priority = SDL_LOG_PRIORITY_INVALID; @@ -397,7 +404,11 @@ void SDL_ResetLogPriorities(void) switch (i) { case SDL_LOG_CATEGORY_APPLICATION: - SDL_log_priorities[i] = SDL_LOG_PRIORITY_INFO; + if (debug) { + SDL_log_priorities[i] = SDL_LOG_PRIORITY_DEBUG; + } else { + SDL_log_priorities[i] = SDL_LOG_PRIORITY_INFO; + } break; case SDL_LOG_CATEGORY_ASSERT: SDL_log_priorities[i] = SDL_LOG_PRIORITY_WARN; @@ -406,7 +417,11 @@ void SDL_ResetLogPriorities(void) SDL_log_priorities[i] = SDL_LOG_PRIORITY_VERBOSE; break; default: - SDL_log_priorities[i] = SDL_LOG_PRIORITY_ERROR; + if (debug) { + SDL_log_priorities[i] = SDL_LOG_PRIORITY_DEBUG; + } else { + SDL_log_priorities[i] = SDL_LOG_PRIORITY_ERROR; + } break; } } @@ -450,7 +465,7 @@ bool SDL_SetLogPriorityPrefix(SDL_LogPriority priority, const char *prefix) { char *prefix_copy; - if (priority <= SDL_LOG_PRIORITY_INVALID || priority >= SDL_LOG_PRIORITY_COUNT) { + CHECK_PARAM(priority <= SDL_LOG_PRIORITY_INVALID || priority >= SDL_LOG_PRIORITY_COUNT) { return SDL_InvalidParamError("priority"); } @@ -597,15 +612,21 @@ void SDL_LogMessageV(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_S } // If message truncated, allocate and re-render - if (len >= sizeof(stack_buf) && SDL_size_add_check_overflow(len, 1, &len_plus_term)) { - // Allocate exactly what we need, including the zero-terminator - message = (char *)SDL_malloc(len_plus_term); - if (!message) { - return; + if (len >= sizeof(stack_buf)) { + if (SDL_size_add_check_overflow(len, 1, &len_plus_term)) { + // Allocate exactly what we need, including the zero-terminator + message = (char *)SDL_malloc(len_plus_term); + if (!message) { + return; + } + va_copy(aq, ap); + len = SDL_vsnprintf(message, len_plus_term, fmt, aq); + va_end(aq); + } else { + // Allocation would overflow, use truncated message + message = stack_buf; + len = sizeof(stack_buf); } - va_copy(aq, ap); - len = SDL_vsnprintf(message, len_plus_term, fmt, aq); - va_end(aq); } else { message = stack_buf; } @@ -767,9 +788,22 @@ static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority (void)fclose(pFile); } } +#elif defined(SDL_PLATFORM_NGAGE) + { + NGAGE_DebugPrintf("%s%s", GetLogPriorityPrefix(priority), message); +#ifdef ENABLE_FILE_LOG + FILE *pFile; + pFile = fopen("E:/SDL_Log.txt", "a"); + if (pFile) { + (void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message); + (void)fclose(pFile); + } +#endif + } #endif #if defined(HAVE_STDIO_H) && \ !(defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))) && \ + !(defined(SDL_PLATFORM_NGAGE)) && \ !(defined(SDL_PLATFORM_WIN32)) (void)fprintf(stderr, "%s%s\n", GetLogPriorityPrefix(priority), message); #endif diff --git a/libs/SDL3/src/SDL_log_c.h b/libs/SDL3/src/SDL_log_c.h index 887a325..9349c34 100644 --- a/libs/SDL3/src/SDL_log_c.h +++ b/libs/SDL3/src/SDL_log_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/SDL_properties.c b/libs/SDL3/src/SDL_properties.c index 166ea0f..6dd38ca 100644 --- a/libs/SDL3/src/SDL_properties.c +++ b/libs/SDL3/src/SDL_properties.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -250,10 +250,10 @@ static bool SDLCALL CopyOneProperty(void *userdata, const SDL_HashTable *table, bool SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst) { - if (!src) { + CHECK_PARAM(!src) { return SDL_InvalidParamError("src"); } - if (!dst) { + CHECK_PARAM(!dst) { return SDL_InvalidParamError("dst"); } @@ -261,11 +261,11 @@ bool SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst) SDL_Properties *dst_properties = NULL; SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)src, (const void **)&src_properties); - if (!src_properties) { + CHECK_PARAM(!src_properties) { return SDL_InvalidParamError("src"); } SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)dst, (const void **)&dst_properties); - if (!dst_properties) { + CHECK_PARAM(!dst_properties) { return SDL_InvalidParamError("dst"); } @@ -287,12 +287,12 @@ bool SDL_LockProperties(SDL_PropertiesID props) { SDL_Properties *properties = NULL; - if (!props) { + CHECK_PARAM(!props) { return SDL_InvalidParamError("props"); } SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); - if (!properties) { + CHECK_PARAM(!properties) { return SDL_InvalidParamError("props"); } @@ -321,17 +321,17 @@ static bool SDL_PrivateSetProperty(SDL_PropertiesID props, const char *name, SDL SDL_Properties *properties = NULL; bool result = true; - if (!props) { + CHECK_PARAM(!props) { SDL_FreePropertyWithCleanup(NULL, property, NULL, true); return SDL_InvalidParamError("props"); } - if (!name || !*name) { + CHECK_PARAM(!name || !*name) { SDL_FreePropertyWithCleanup(NULL, property, NULL, true); return SDL_InvalidParamError("name"); } SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); - if (!properties) { + CHECK_PARAM(!properties) { SDL_FreePropertyWithCleanup(NULL, property, NULL, true); return SDL_InvalidParamError("props"); } @@ -755,15 +755,15 @@ bool SDL_EnumerateProperties(SDL_PropertiesID props, SDL_EnumeratePropertiesCall { SDL_Properties *properties = NULL; - if (!props) { + CHECK_PARAM(!props) { return SDL_InvalidParamError("props"); } - if (!callback) { + CHECK_PARAM(!callback) { return SDL_InvalidParamError("callback"); } SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); - if (!properties) { + CHECK_PARAM(!properties) { return SDL_InvalidParamError("props"); } diff --git a/libs/SDL3/src/SDL_properties_c.h b/libs/SDL3/src/SDL_properties_c.h index 8d1d39d..f48b2de 100644 --- a/libs/SDL3/src/SDL_properties_c.h +++ b/libs/SDL3/src/SDL_properties_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/SDL_utils.c b/libs/SDL3/src/SDL_utils.c index f209074..66b0437 100644 --- a/libs/SDL3/src/SDL_utils.c +++ b/libs/SDL3/src/SDL_utils.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -26,6 +26,14 @@ #include "joystick/SDL_joystick_c.h" // For SDL_GetGamepadTypeFromVIDPID() +#ifdef SDL_PLATFORM_EMSCRIPTEN +#include + +EMSCRIPTEN_KEEPALIVE void Emscripten_force_free(void *ptr) +{ + free(ptr); // This should NOT be SDL_free() +} +#endif // Common utility functions that aren't in the public API @@ -137,6 +145,30 @@ Uint32 SDL_GetNextObjectID(void) static SDL_InitState SDL_objects_init; static SDL_HashTable *SDL_objects; +bool SDL_object_validation = true; + +static void SDLCALL SDL_InvalidParamChecksChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + bool validation_enabled = true; + +#ifndef OBJECT_VALIDATION_REQUIRED + if (hint) { + switch (*hint) { + case '0': + case '1': + validation_enabled = false; + break; + case '2': + validation_enabled = true; + break; + default: + break; + } + } +#endif // !OBJECT_VALIDATION_REQUIRED + + SDL_object_validation = validation_enabled; +} static Uint32 SDLCALL SDL_HashObject(void *unused, const void *key) { @@ -159,6 +191,7 @@ void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid) if (!initialized) { return; } + SDL_AddHintCallback(SDL_HINT_INVALID_PARAM_CHECKS, SDL_InvalidParamChecksChanged, NULL); } if (valid) { @@ -168,12 +201,8 @@ void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid) } } -bool SDL_ObjectValid(void *object, SDL_ObjectType type) +bool SDL_FindObject(void *object, SDL_ObjectType type) { - if (!object) { - return false; - } - const void *object_type; if (!SDL_FindInHashTable(SDL_objects, object, &object_type)) { return false; @@ -229,7 +258,7 @@ static bool SDLCALL LogOneLeakedObject(void *userdata, const SDL_HashTable *tabl #undef SDLOBJTYPECASE default: break; } - SDL_Log("Leaked %s (%p)", type, object); + SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Leaked %s (%p)", type, object); return true; // keep iterating. } @@ -238,10 +267,10 @@ void SDL_SetObjectsInvalid(void) if (SDL_ShouldQuit(&SDL_objects_init)) { // Log any leaked objects 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); + SDL_RemoveHintCallback(SDL_HINT_INVALID_PARAM_CHECKS, SDL_InvalidParamChecksChanged, NULL); } } @@ -424,6 +453,7 @@ char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_nam const char *prefix; const char *replacement; } replacements[] = { + { "(Standard system devices) ", "" }, { "8BitDo Tech Ltd", "8BitDo" }, { "ASTRO Gaming", "ASTRO" }, { "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" }, @@ -552,3 +582,10 @@ char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_nam return name; } + + +void SDL_DebugLogBackend(const char *subsystem, const char *backend) +{ + SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "SDL chose %s backend '%s'", subsystem, backend); +} + diff --git a/libs/SDL3/src/SDL_utils_c.h b/libs/SDL3/src/SDL_utils_c.h index 5e0e8f5..266eb09 100644 --- a/libs/SDL3/src/SDL_utils_c.h +++ b/libs/SDL3/src/SDL_utils_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -67,12 +67,37 @@ typedef enum extern Uint32 SDL_GetNextObjectID(void); extern void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid); -extern bool SDL_ObjectValid(void *object, SDL_ObjectType type); +extern bool SDL_FindObject(void *object, SDL_ObjectType type); extern int SDL_GetObjects(SDL_ObjectType type, void **objects, int count); extern void SDL_SetObjectsInvalid(void); +extern bool SDL_object_validation; + +SDL_FORCE_INLINE bool SDL_ObjectValid(void *object, SDL_ObjectType type) +{ + if (!object) { + return false; + } + + if (!SDL_object_validation) { + return true; + } + + return SDL_FindObject(object, type); +} + 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); +// Log what backend a subsystem chose, if a hint was set to do so. Useful for debugging. +extern void SDL_DebugLogBackend(const char *subsystem, const char *backend); + +#ifdef SDL_PLATFORM_EMSCRIPTEN +// even though we reference the C runtime's free() in other places, it appears +// to be inlined more aggressively in Emscripten 4, so we need a reference to +// it here, too, so inlined Javascript doesn't fail to find it. +extern void Emscripten_force_free(void *ptr); +#endif + #endif // SDL_utils_h_ diff --git a/libs/SDL3/src/atomic/SDL_atomic.c b/libs/SDL3/src/atomic/SDL_atomic.c index 5e7774d..40d5792 100644 --- a/libs/SDL3/src/atomic/SDL_atomic.c +++ b/libs/SDL3/src/atomic/SDL_atomic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -48,35 +48,6 @@ #endif #endif -/* *INDENT-OFF* */ // clang-format off -#if defined(__WATCOMC__) && defined(__386__) -SDL_COMPILE_TIME_ASSERT(intsize, 4==sizeof(int)); -#define HAVE_WATCOM_ATOMICS -extern __inline int _SDL_xchg_watcom(volatile int *a, int v); -#pragma aux _SDL_xchg_watcom = \ - "lock xchg [ecx], eax" \ - parm [ecx] [eax] \ - value [eax] \ - modify exact [eax]; - -extern __inline unsigned char _SDL_cmpxchg_watcom(volatile int *a, int newval, int oldval); -#pragma aux _SDL_cmpxchg_watcom = \ - "lock cmpxchg [edx], ecx" \ - "setz al" \ - parm [edx] [ecx] [eax] \ - value [al] \ - modify exact [eax]; - -extern __inline int _SDL_xadd_watcom(volatile int *a, int v); -#pragma aux _SDL_xadd_watcom = \ - "lock xadd [ecx], eax" \ - parm [ecx] [eax] \ - value [eax] \ - modify exact [eax]; - -#endif // __WATCOMC__ && __386__ -/* *INDENT-ON* */ // clang-format on - /* If any of the operations are not provided then we must emulate some of them. That means we need a nice implementation of spin locks @@ -100,7 +71,7 @@ extern __inline int _SDL_xadd_watcom(volatile int *a, int v); Contributed by Bob Pendleton, bob@pendleton.com */ -#if !defined(HAVE_MSC_ATOMICS) && !defined(HAVE_GCC_ATOMICS) && !defined(SDL_PLATFORM_MACOS) && !defined(SDL_PLATFORM_SOLARIS) && !defined(HAVE_WATCOM_ATOMICS) +#if !defined(HAVE_MSC_ATOMICS) && !defined(HAVE_GCC_ATOMICS) && !defined(SDL_PLATFORM_MACOS) && !defined(SDL_PLATFORM_SOLARIS) #define EMULATE_CAS 1 #endif @@ -127,8 +98,6 @@ bool SDL_CompareAndSwapAtomicInt(SDL_AtomicInt *a, int oldval, int newval) #ifdef HAVE_MSC_ATOMICS SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(long) == sizeof(a->value)); return _InterlockedCompareExchange((long *)&a->value, (long)newval, (long)oldval) == (long)oldval; -#elif defined(HAVE_WATCOM_ATOMICS) - return _SDL_cmpxchg_watcom((volatile int *)&a->value, newval, oldval); #elif defined(HAVE_GCC_ATOMICS) return __sync_bool_compare_and_swap(&a->value, oldval, newval); #elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics. @@ -157,13 +126,10 @@ bool SDL_CompareAndSwapAtomicU32(SDL_AtomicU32 *a, Uint32 oldval, Uint32 newval) #ifdef HAVE_MSC_ATOMICS SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(long) == sizeof(a->value)); return _InterlockedCompareExchange((long *)&a->value, (long)newval, (long)oldval) == (long)oldval; -#elif defined(HAVE_WATCOM_ATOMICS) - SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(int) == sizeof(a->value)); - return _SDL_cmpxchg_watcom((volatile int *)&a->value, (int)newval, (int)oldval); #elif defined(HAVE_GCC_ATOMICS) return __sync_bool_compare_and_swap(&a->value, oldval, newval); #elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics. - return OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t*)&a->value); + return OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t *)&a->value); #elif defined(SDL_PLATFORM_SOLARIS) SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(uint_t) == sizeof(a->value)); return ((Uint32)atomic_cas_uint((volatile uint_t *)&a->value, (uint_t)oldval, (uint_t)newval) == oldval); @@ -187,8 +153,6 @@ bool SDL_CompareAndSwapAtomicPointer(void **a, void *oldval, void *newval) { #ifdef HAVE_MSC_ATOMICS return _InterlockedCompareExchangePointer(a, newval, oldval) == oldval; -#elif defined(HAVE_WATCOM_ATOMICS) - return _SDL_cmpxchg_watcom((int *)a, (long)newval, (long)oldval); #elif defined(HAVE_GCC_ATOMICS) return __sync_bool_compare_and_swap(a, oldval, newval); #elif defined(SDL_PLATFORM_MACOS) && defined(__LP64__) // this is deprecated in 10.12 sdk; favor gcc atomics. @@ -218,8 +182,6 @@ int SDL_SetAtomicInt(SDL_AtomicInt *a, int v) #ifdef HAVE_MSC_ATOMICS SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(long) == sizeof(a->value)); return _InterlockedExchange((long *)&a->value, v); -#elif defined(HAVE_WATCOM_ATOMICS) - return _SDL_xchg_watcom(&a->value, v); #elif defined(HAVE_GCC_ATOMICS) return __sync_lock_test_and_set(&a->value, v); #elif defined(SDL_PLATFORM_SOLARIS) @@ -239,8 +201,6 @@ Uint32 SDL_SetAtomicU32(SDL_AtomicU32 *a, Uint32 v) #ifdef HAVE_MSC_ATOMICS SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(long) == sizeof(a->value)); return _InterlockedExchange((long *)&a->value, v); -#elif defined(HAVE_WATCOM_ATOMICS) - return _SDL_xchg_watcom(&a->value, v); #elif defined(HAVE_GCC_ATOMICS) return __sync_lock_test_and_set(&a->value, v); #elif defined(SDL_PLATFORM_SOLARIS) @@ -259,8 +219,6 @@ void *SDL_SetAtomicPointer(void **a, void *v) { #ifdef HAVE_MSC_ATOMICS return _InterlockedExchangePointer(a, v); -#elif defined(HAVE_WATCOM_ATOMICS) - return (void *)_SDL_xchg_watcom((int *)a, (long)v); #elif defined(HAVE_GCC_ATOMICS) return __sync_lock_test_and_set(a, v); #elif defined(SDL_PLATFORM_SOLARIS) @@ -279,9 +237,6 @@ int SDL_AddAtomicInt(SDL_AtomicInt *a, int v) #ifdef HAVE_MSC_ATOMICS SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(long) == sizeof(a->value)); return _InterlockedExchangeAdd((long *)&a->value, v); -#elif defined(HAVE_WATCOM_ATOMICS) - SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(int) == sizeof(a->value)); - return _SDL_xadd_watcom((volatile int *)&a->value, v); #elif defined(HAVE_GCC_ATOMICS) return __sync_fetch_and_add(&a->value, v); #elif defined(SDL_PLATFORM_SOLARIS) @@ -298,6 +253,27 @@ int SDL_AddAtomicInt(SDL_AtomicInt *a, int v) #endif } +Uint32 SDL_AddAtomicU32(SDL_AtomicU32 *a, int v) +{ +#ifdef HAVE_MSC_ATOMICS + SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(long) == sizeof(a->value)); + return (Uint32)_InterlockedExchangeAdd((long *)&a->value, v); +#elif defined(HAVE_GCC_ATOMICS) + return __sync_fetch_and_add(&a->value, v); +#elif defined(SDL_PLATFORM_SOLARIS) + Uint32 pv = a->value; + membar_consumer(); + atomic_add_int((volatile uint_t *)&a->value, v); + return pv; +#else + Uint32 value; + do { + value = a->value; + } while (!SDL_CompareAndSwapAtomicU32(a, value, (value + v))); + return value; +#endif +} + int SDL_GetAtomicInt(SDL_AtomicInt *a) { #ifdef HAVE_ATOMIC_LOAD_N @@ -305,8 +281,6 @@ int SDL_GetAtomicInt(SDL_AtomicInt *a) #elif defined(HAVE_MSC_ATOMICS) SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(long) == sizeof(a->value)); return _InterlockedOr((long *)&a->value, 0); -#elif defined(HAVE_WATCOM_ATOMICS) - return _SDL_xadd_watcom(&a->value, 0); #elif defined(HAVE_GCC_ATOMICS) return __sync_or_and_fetch(&a->value, 0); #elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics. @@ -329,9 +303,6 @@ Uint32 SDL_GetAtomicU32(SDL_AtomicU32 *a) #elif defined(HAVE_MSC_ATOMICS) SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(long) == sizeof(a->value)); return (Uint32)_InterlockedOr((long *)&a->value, 0); -#elif defined(HAVE_WATCOM_ATOMICS) - SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(int) == sizeof(a->value)); - return (Uint32)_SDL_xadd_watcom((volatile int *)&a->value, 0); #elif defined(HAVE_GCC_ATOMICS) return __sync_or_and_fetch(&a->value, 0); #elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics. diff --git a/libs/SDL3/src/atomic/SDL_spinlock.c b/libs/SDL3/src/atomic/SDL_spinlock.c index 8e35c8a..1eb8e35 100644 --- a/libs/SDL3/src/atomic/SDL_spinlock.c +++ b/libs/SDL3/src/atomic/SDL_spinlock.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -44,18 +44,6 @@ #include #endif -/* *INDENT-OFF* */ // clang-format off -#if defined(__WATCOMC__) && defined(__386__) -SDL_COMPILE_TIME_ASSERT(locksize, 4==sizeof(SDL_SpinLock)); -extern __inline int _SDL_xchg_watcom(volatile int *a, int v); -#pragma aux _SDL_xchg_watcom = \ - "lock xchg [ecx], eax" \ - parm [ecx] [eax] \ - value [eax] \ - modify exact [eax]; -#endif // __WATCOMC__ && __386__ -/* *INDENT-ON* */ // clang-format on - // This function is where all the magic happens... bool SDL_TryLockSpinlock(SDL_SpinLock *lock) { @@ -69,9 +57,6 @@ bool SDL_TryLockSpinlock(SDL_SpinLock *lock) SDL_COMPILE_TIME_ASSERT(locksize, sizeof(*lock) == sizeof(long)); return InterlockedExchange((long *)lock, 1) == 0; -#elif defined(__WATCOMC__) && defined(__386__) - return _SDL_xchg_watcom(lock, 1) == 0; - #elif defined(__GNUC__) && defined(__arm__) && \ (defined(__ARM_ARCH_3__) || defined(__ARM_ARCH_3M__) || \ defined(__ARM_ARCH_4__) || defined(__ARM_ARCH_4T__) || \ @@ -106,7 +91,7 @@ bool SDL_TryLockSpinlock(SDL_SpinLock *lock) : "cc", "memory"); return result == 0; -#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#elif (defined(__GNUC__) || defined(__TINYC__)) && (defined(__i386__) || defined(__x86_64__)) int result; __asm__ __volatile__( "lock ; xchgl %0, (%1)\n" @@ -188,10 +173,6 @@ void SDL_UnlockSpinlock(SDL_SpinLock *lock) _ReadWriteBarrier(); *lock = 0; -#elif defined(__WATCOMC__) && defined(__386__) - SDL_CompilerBarrier(); - *lock = 0; - #elif defined(SDL_PLATFORM_SOLARIS) // Used for Solaris when not using gcc. *lock = 0; diff --git a/libs/SDL3/src/audio/SDL_audio.c b/libs/SDL3/src/audio/SDL_audio.c index 5e3e1fb..c1e4241 100644 --- a/libs/SDL3/src/audio/SDL_audio.c +++ b/libs/SDL3/src/audio/SDL_audio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -77,6 +77,9 @@ static const AudioBootStrap *const bootstrap[] = { #ifdef SDL_AUDIO_DRIVER_N3DS &N3DSAUDIO_bootstrap, #endif +#ifdef SDL_AUDIO_DRIVER_NGAGE + &NGAGEAUDIO_bootstrap, +#endif #ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN &EMSCRIPTENAUDIO_bootstrap, #endif @@ -133,11 +136,11 @@ int SDL_GetNumAudioDrivers(void) const char *SDL_GetAudioDriver(int index) { - if (index >= 0 && index < SDL_GetNumAudioDrivers()) { - return deduped_bootstrap[index]->name; + CHECK_PARAM(index < 0 || index >= SDL_GetNumAudioDrivers()) { + SDL_InvalidParamError("index"); + return NULL; } - SDL_InvalidParamError("index"); - return NULL; + return deduped_bootstrap[index]->name; } const char *SDL_GetCurrentAudioDriver(void) @@ -168,10 +171,13 @@ int SDL_GetDefaultSampleFramesFromFreq(const int freq) int *SDL_ChannelMapDup(const int *origchmap, int channels) { - const size_t chmaplen = sizeof (*origchmap) * channels; - int *chmap = (int *)SDL_malloc(chmaplen); - if (chmap) { - SDL_memcpy(chmap, origchmap, chmaplen); + int *chmap = NULL; + if ((channels > 0) && origchmap) { + const size_t chmaplen = sizeof (*origchmap) * channels; + chmap = (int *)SDL_malloc(chmaplen); + if (chmap) { + SDL_memcpy(chmap, origchmap, chmaplen); + } } return chmap; } @@ -183,16 +189,15 @@ void OnAudioStreamCreated(SDL_AudioStream *stream) // NOTE that you can create an audio stream without initializing the audio subsystem, // but it will not be automatically destroyed during a later call to SDL_Quit! // You must explicitly destroy it yourself! - if (current_audio.device_hash_lock) { - // this isn't really part of the "device list" but it's a convenient lock to use here. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + if (current_audio.subsystem_rwlock) { + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); if (current_audio.existing_streams) { current_audio.existing_streams->prev = stream; } stream->prev = NULL; stream->next = current_audio.existing_streams; current_audio.existing_streams = stream; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -203,9 +208,8 @@ void OnAudioStreamDestroy(SDL_AudioStream *stream) // NOTE that you can create an audio stream without initializing the audio subsystem, // but it will not be automatically destroyed during a later call to SDL_Quit! // You must explicitly destroy it yourself! - if (current_audio.device_hash_lock) { - // this isn't really part of the "device list" but it's a convenient lock to use here. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + if (current_audio.subsystem_rwlock) { + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); if (stream->prev) { stream->prev->next = stream->next; } @@ -215,7 +219,7 @@ void OnAudioStreamDestroy(SDL_AudioStream *stream) if (stream == current_audio.existing_streams) { current_audio.existing_streams = stream->next; } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -366,14 +370,28 @@ static SDL_AudioDeviceID AssignAudioDeviceInstanceId(bool recording, bool islogi bool SDL_IsAudioDevicePhysical(SDL_AudioDeviceID devid) { + // bit #1 of devid is set for physical devices and unset for logical. return (devid & (1 << 1)) != 0; } +static bool SDL_IsAudioDeviceLogical(SDL_AudioDeviceID devid) +{ + // bit #1 of devid is set for physical devices and unset for logical. + return (devid & (1 << 1)) == 0; +} + bool SDL_IsAudioDevicePlayback(SDL_AudioDeviceID devid) { + // bit #0 of devid is set for playback devices and unset for recording. return (devid & (1 << 0)) != 0; } +static bool SDL_IsAudioDeviceRecording(SDL_AudioDeviceID devid) +{ + // bit #0 of devid is set for playback devices and unset for recording. + return (devid & (1 << 0)) == 0; +} + static void ObtainPhysicalAudioDeviceObj(SDL_AudioDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXMEL SDL_ACQUIRE { if (device) { @@ -404,21 +422,19 @@ static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid, SDL_AudioDevice *device = NULL; SDL_LogicalAudioDevice *logdev = NULL; - // bit #1 of devid is set for physical devices and unset for logical. - const bool islogical = !(devid & (1<<1)); - if (islogical) { // don't bother looking if it's not a logical device id value. - SDL_LockRWLockForReading(current_audio.device_hash_lock); - SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &logdev); + if (SDL_IsAudioDeviceLogical(devid)) { // don't bother looking if it's not a logical device id value. + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + SDL_FindInHashTable(current_audio.device_hash_logical, (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. } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (logdev) { - // we have to release the device_hash_lock before we take the device lock, to avoid deadlocks, so do a loop + // we have to release the subsystem_rwlock before we take the device lock, to avoid deadlocks, so do a loop // to make sure the correct physical device gets locked, in case we're in a race with the default changing. while (true) { SDL_LockMutex(device->lock); @@ -451,17 +467,15 @@ static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) // ! { SDL_AudioDevice *device = NULL; - // bit #1 of devid is set for physical devices and unset for logical. - const bool islogical = !(devid & (1<<1)); - if (islogical) { + if (SDL_IsAudioDeviceLogical(devid)) { ObtainLogicalAudioDevice(devid, &device); } else if (!SDL_GetCurrentAudioDriver()) { // (the `islogical` path, above, checks this in ObtainLogicalAudioDevice.) SDL_SetError("Audio subsystem is not initialized"); } 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); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + SDL_FindInHashTable(current_audio.device_hash_physical, (const void *) (uintptr_t) devid, (const void **) &device); + SDL_assert(!device || (device->instance_id == devid)); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (!device) { SDL_SetError("Invalid audio device instance ID"); @@ -483,13 +497,13 @@ static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceI const SDL_AudioDeviceID orig_devid = devid; while (true) { - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) { devid = current_audio.default_playback_device_id; } else if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING) { devid = current_audio.default_recording_device_id; } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (devid == 0) { SDL_SetError("No default audio device available"); @@ -503,13 +517,13 @@ static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceI // make sure the default didn't change while we were waiting for the lock... bool got_it = false; - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) && (devid == current_audio.default_playback_device_id)) { got_it = true; } else if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING) && (devid == current_audio.default_recording_device_id)) { got_it = true; } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (got_it) { return device; @@ -526,10 +540,10 @@ static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceI static void DestroyLogicalAudioDevice(SDL_LogicalAudioDevice *logdev) { // Remove ourselves from the device_hash hashtable. - if (current_audio.device_hash) { // will be NULL while shutting down. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - SDL_RemoveFromHashTable(current_audio.device_hash, (const void *) (uintptr_t) logdev->instance_id); - SDL_UnlockRWLock(current_audio.device_hash_lock); + if (current_audio.device_hash_logical) { // will be NULL while shutting down. + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); + SDL_RemoveFromHashTable(current_audio.device_hash_logical, (const void *) (uintptr_t) logdev->instance_id); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } // remove ourselves from the physical device's list of logical devices. @@ -590,11 +604,11 @@ void UnrefPhysicalAudioDevice(SDL_AudioDevice *device) { if (SDL_AtomicDecRef(&device->refcount)) { // take it out of the device list. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - if (SDL_RemoveFromHashTable(current_audio.device_hash, (const void *) (uintptr_t) device->instance_id)) { + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); + if (SDL_RemoveFromHashTable(current_audio.device_hash_physical, (const void *) (uintptr_t) device->instance_id)) { SDL_AddAtomicInt(device->recording ? ¤t_audio.recording_device_count : ¤t_audio.playback_device_count, -1); } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); DestroyPhysicalAudioDevice(device); // ...and nuke it. } } @@ -608,9 +622,9 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, bool recordi { SDL_assert(name != NULL); - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); const int shutting_down = SDL_GetAtomicInt(¤t_audio.shutting_down); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (shutting_down) { return NULL; // we're shutting down, don't add any devices that are hotplugged at the last possible moment. } @@ -652,8 +666,8 @@ 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, false)) { + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); + if (SDL_InsertIntoHashTable(current_audio.device_hash_physical, (const void *) (uintptr_t) device->instance_id, device, false)) { SDL_AddAtomicInt(device_count, 1); } else { SDL_DestroyCondition(device->close_cond); @@ -662,7 +676,7 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, bool recordi SDL_free(device); device = NULL; } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); RefPhysicalAudioDevice(device); // unref'd on device disconnect. return device; @@ -710,12 +724,12 @@ SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_ p->type = SDL_EVENT_AUDIO_DEVICE_ADDED; p->devid = device->instance_id; p->next = NULL; - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_assert(current_audio.pending_events_tail != NULL); SDL_assert(current_audio.pending_events_tail->next == NULL); current_audio.pending_events_tail->next = p; current_audio.pending_events_tail = p; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -723,11 +737,10 @@ SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_ } // Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the audio device's thread. -void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) +static void SDLCALL SDL_AudioDeviceDisconnected_OnMainThread(void *userdata) { - if (!device) { - return; - } + SDL_AudioDevice *device = (SDL_AudioDevice *) userdata; + SDL_assert(device != NULL); // Save off removal info in a list so we can send events for each, next // time the event queue pumps, in case something tries to close a device @@ -739,10 +752,10 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) ObtainPhysicalAudioDeviceObj(device); - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); const SDL_AudioDeviceID devid = device->instance_id; const bool is_default_device = ((devid == current_audio.default_playback_device_id) || (devid == current_audio.default_recording_device_id)); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); const bool first_disconnect = SDL_CompareAndSwapAtomicInt(&device->zombie, 0, 1); if (first_disconnect) { // if already disconnected this device, don't do it twice. @@ -787,16 +800,33 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) if (first_disconnect) { if (pending.next) { // NULL if event is disabled or disaster struck. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_assert(current_audio.pending_events_tail != NULL); SDL_assert(current_audio.pending_events_tail->next == NULL); current_audio.pending_events_tail->next = pending.next; current_audio.pending_events_tail = pending_tail; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } UnrefPhysicalAudioDevice(device); } + + // We always ref this in SDL_AudioDeviceDisconnected(), so if multiple attempts + // to disconnect are queued, the pointer stays valid until the last one comes + // through. + UnrefPhysicalAudioDevice(device); +} + +void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) +{ + // lots of risk of various audio backends deadlocking because they're calling + // this while holding a backend-specific lock, which causes problems when we + // want to obtain the device lock while its audio thread is also waiting for + // that lock to be released. So just queue the work on the main thread. + if (device) { + RefPhysicalAudioDevice(device); + SDL_RunOnMainThread(SDL_AudioDeviceDisconnected_OnMainThread, device, false); + } } @@ -878,11 +908,8 @@ static bool SDLCALL FindLowestDeviceID(void *userdata, const SDL_HashTable *tabl { 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)) { + SDL_assert(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical. + if ((SDL_IsAudioDeviceRecording(devid) == data->recording) && (devid < data->highest)) { data->highest = devid; data->result = (SDL_AudioDevice *) value; SDL_assert(data->result->instance_id == devid); @@ -896,9 +923,9 @@ static SDL_AudioDevice *GetFirstAddedAudioDevice(const bool recording) // (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); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + SDL_IterateHashTable(current_audio.device_hash_physical, FindLowestDeviceID, &data); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); return data.result; } @@ -922,14 +949,21 @@ bool SDL_InitAudio(const char *driver_name) SDL_ChooseAudioConverters(); SDL_SetupAudioResampler(); - SDL_RWLock *device_hash_lock = SDL_CreateRWLock(); // create this early, so if it fails we don't have to tear down the whole audio subsystem. - if (!device_hash_lock) { + SDL_RWLock *subsystem_rwlock = SDL_CreateRWLock(); // create this early, so if it fails we don't have to tear down the whole audio subsystem. + if (!subsystem_rwlock) { return false; } - SDL_HashTable *device_hash = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL); - if (!device_hash) { - SDL_DestroyRWLock(device_hash_lock); + SDL_HashTable *device_hash_physical = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL); + if (!device_hash_physical) { + SDL_DestroyRWLock(subsystem_rwlock); + return false; + } + + SDL_HashTable *device_hash_logical = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL); + if (!device_hash_logical) { + SDL_DestroyHashTable(device_hash_physical); + SDL_DestroyRWLock(subsystem_rwlock); return false; } @@ -946,8 +980,9 @@ bool SDL_InitAudio(const char *driver_name) const char *driver_attempt = driver_name_copy; if (!driver_name_copy) { - SDL_DestroyRWLock(device_hash_lock); - SDL_DestroyHashTable(device_hash); + SDL_DestroyRWLock(subsystem_rwlock); + SDL_DestroyHashTable(device_hash_physical); + SDL_DestroyHashTable(device_hash_logical); return false; } @@ -969,8 +1004,9 @@ bool SDL_InitAudio(const char *driver_name) tried_to_init = true; SDL_zero(current_audio); current_audio.pending_events_tail = ¤t_audio.pending_events; - current_audio.device_hash_lock = device_hash_lock; - current_audio.device_hash = device_hash; + current_audio.subsystem_rwlock = subsystem_rwlock; + current_audio.device_hash_physical = device_hash_physical; + current_audio.device_hash_logical = device_hash_logical; if (bootstrap[i]->init(¤t_audio.impl)) { current_audio.name = bootstrap[i]->name; current_audio.desc = bootstrap[i]->desc; @@ -993,8 +1029,9 @@ bool SDL_InitAudio(const char *driver_name) tried_to_init = true; SDL_zero(current_audio); current_audio.pending_events_tail = ¤t_audio.pending_events; - current_audio.device_hash_lock = device_hash_lock; - current_audio.device_hash = device_hash; + current_audio.subsystem_rwlock = subsystem_rwlock; + current_audio.device_hash_physical = device_hash_physical; + current_audio.device_hash_logical = device_hash_logical; if (bootstrap[i]->init(¤t_audio.impl)) { current_audio.name = bootstrap[i]->name; current_audio.desc = bootstrap[i]->desc; @@ -1003,7 +1040,9 @@ bool SDL_InitAudio(const char *driver_name) } } - if (!initialized) { + if (initialized) { + SDL_DebugLogBackend("audio", current_audio.name); + } else { // specific drivers will set the error message if they fail, but otherwise we do it here. if (!tried_to_init) { if (driver_name) { @@ -1013,8 +1052,9 @@ bool SDL_InitAudio(const char *driver_name) } } - SDL_DestroyRWLock(device_hash_lock); - SDL_DestroyHashTable(device_hash); + SDL_DestroyRWLock(subsystem_rwlock); + SDL_DestroyHashTable(device_hash_physical); + SDL_DestroyHashTable(device_hash_logical); SDL_zero(current_audio); return false; // No driver was available, so fail. } @@ -1050,15 +1090,11 @@ bool SDL_InitAudio(const char *driver_name) 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); - } + SDL_assert(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical. + SDL_AudioDevice *dev = (SDL_AudioDevice *) value; + SDL_assert(dev->instance_id == devid); + DestroyPhysicalAudioDevice(dev); return true; // keep iterating. } @@ -1070,20 +1106,28 @@ void SDL_QuitAudio(void) current_audio.impl.DeinitializeStart(); - // Destroy any audio streams that still exist... - while (current_audio.existing_streams) { - SDL_DestroyAudioStream(current_audio.existing_streams); + // Destroy any audio streams that still exist...unless app asked to keep it. + SDL_AudioStream *next = NULL; + for (SDL_AudioStream *i = current_audio.existing_streams; i; i = next) { + next = i->next; + if (i->simplified || SDL_GetBooleanProperty(i->props, SDL_PROP_AUDIOSTREAM_AUTO_CLEANUP_BOOLEAN, true)) { + SDL_DestroyAudioStream(i); + } else { + i->prev = NULL; + i->next = NULL; + } } - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_SetAtomicInt(¤t_audio.shutting_down, 1); - SDL_HashTable *device_hash = current_audio.device_hash; - current_audio.device_hash = NULL; + SDL_HashTable *device_hash_physical = current_audio.device_hash_physical; + SDL_HashTable *device_hash_logical = current_audio.device_hash_logical; + current_audio.device_hash_physical = current_audio.device_hash_logical = NULL; SDL_PendingAudioDeviceEvent *pending_events = current_audio.pending_events.next; current_audio.pending_events.next = NULL; SDL_SetAtomicInt(¤t_audio.playback_device_count, 0); SDL_SetAtomicInt(¤t_audio.recording_device_count, 0); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); SDL_PendingAudioDeviceEvent *pending_next = NULL; for (SDL_PendingAudioDeviceEvent *i = pending_events; i; i = pending_next) { @@ -1091,13 +1135,15 @@ void SDL_QuitAudio(void) SDL_free(i); } - SDL_IterateHashTable(device_hash, DestroyOnePhysicalAudioDevice, NULL); + SDL_IterateHashTable(device_hash_physical, DestroyOnePhysicalAudioDevice, NULL); + // device_hash_* will _not_ be empty because we nulled them out in current_audio, but all their items are now free'd pointers. Just destroy the hashes, below. // Free the driver data current_audio.impl.Deinitialize(); - SDL_DestroyRWLock(current_audio.device_hash_lock); - SDL_DestroyHashTable(device_hash); + SDL_DestroyRWLock(current_audio.subsystem_rwlock); + SDL_DestroyHashTable(device_hash_physical); + SDL_DestroyHashTable(device_hash_logical); SDL_zero(current_audio); } @@ -1152,11 +1198,9 @@ 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. @@ -1254,7 +1298,11 @@ void SDL_PlaybackAudioThreadShutdown(SDL_AudioDevice *device) const int frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec); // Wait for the audio to drain if device didn't die. if (!SDL_GetAtomicInt(&device->zombie)) { - SDL_Delay(((frames * 1000) / device->spec.freq) * 2); + int delay = ((frames * 1000) / device->spec.freq) * 2; + if (delay > 100) { + delay = 100; + } + SDL_Delay(delay); } current_audio.impl.ThreadDeinit(device); SDL_AudioThreadFinalize(device); @@ -1407,13 +1455,10 @@ static bool SDLCALL CountAudioDevices(void *userdata, const SDL_HashTable *table { 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(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical. + if (SDL_IsAudioDeviceRecording(devid) == 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. + SDL_AudioDevice *device = (SDL_AudioDevice *) value; // this is normally risky, but we hold the subsystem_rwlock here. const bool zombie = SDL_GetAtomicInt(&device->zombie) != 0; if (zombie) { data->devs_skipped++; @@ -1430,19 +1475,19 @@ static SDL_AudioDeviceID *GetAudioDevices(int *count, bool recording) int num_devices = 0; if (SDL_GetCurrentAudioDriver()) { - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); { 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) { CountAudioDevicesData data = { 0, 0, num_devices, result, recording }; - SDL_IterateHashTable(current_audio.device_hash, CountAudioDevices, &data); + SDL_IterateHashTable(current_audio.device_hash_physical, 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); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } else { SDL_SetError("Audio subsystem is not initialized"); } @@ -1478,15 +1523,12 @@ static bool SDLCALL FindAudioDeviceByCallback(void *userdata, const SDL_HashTabl { 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. - } + SDL_assert(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical. + 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. } @@ -1500,9 +1542,9 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(bool (*callback)(SDL_Audi } FindAudioDeviceByCallbackData data = { callback, userdata, NULL }; - SDL_LockRWLockForReading(current_audio.device_hash_lock); - SDL_IterateHashTable(current_audio.device_hash, FindAudioDeviceByCallback, &data); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + SDL_IterateHashTable(current_audio.device_hash_physical, FindAudioDeviceByCallback, &data); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (!data.retval) { SDL_SetError("Device not found"); @@ -1524,19 +1566,28 @@ 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; - const void *vdev = NULL; if (!SDL_GetCurrentAudioDriver()) { SDL_SetError("Audio subsystem is not initialized"); } else { + const bool islogical = SDL_IsAudioDeviceLogical(devid); + const void *vdev = NULL; + // 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); + // subsystem_rwlock while we copy the string. + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + + // Allow default device IDs to be used, just return the current default physical device's name. + if (devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) { + devid = current_audio.default_playback_device_id; + } else if (devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING) { + devid = current_audio.default_recording_device_id; + } + + SDL_FindInHashTable(islogical ? current_audio.device_hash_logical : current_audio.device_hash_physical, (const void *) (uintptr_t) devid, &vdev); if (!vdev) { SDL_SetError("Invalid audio device instance ID"); } else if (islogical) { @@ -1548,7 +1599,7 @@ const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) SDL_assert(device->instance_id == devid); result = SDL_GetPersistentString(device->name); } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } return result; @@ -1556,7 +1607,7 @@ const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) bool SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec, int *sample_frames) { - if (!spec) { + CHECK_PARAM(!spec) { return SDL_InvalidParamError("spec"); } @@ -1581,9 +1632,7 @@ int *SDL_GetAudioDeviceChannelMap(SDL_AudioDeviceID devid, int *count) SDL_AudioDevice *device = ObtainPhysicalAudioDeviceDefaultAllowed(devid); if (device) { channels = device->spec.channels; - if (channels > 0 && device->chmap) { - result = SDL_ChannelMapDup(device->chmap, channels); - } + result = SDL_ChannelMapDup(device->chmap, channels); } ReleaseAudioDevice(device); @@ -1823,8 +1872,7 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp // this will let you use a logical device to make a new logical device on the parent physical device. Could be useful? SDL_AudioDevice *device = NULL; - const bool islogical = (!wants_default && !(devid & (1<<1))); - if (!islogical) { + if ((wants_default || SDL_IsAudioDevicePhysical(devid))) { device = ObtainPhysicalAudioDeviceDefaultAllowed(devid); } else { SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); @@ -1861,9 +1909,9 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp ReleaseAudioDevice(device); if (result) { - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - const bool inserted = SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) result, logdev, false); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); + const bool inserted = SDL_InsertIntoHashTable(current_audio.device_hash_logical, (const void *) (uintptr_t) result, logdev, false); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (!inserted) { SDL_CloseAudioDevice(result); result = 0; @@ -1918,7 +1966,7 @@ float SDL_GetAudioDeviceGain(SDL_AudioDeviceID devid) bool SDL_SetAudioDeviceGain(SDL_AudioDeviceID devid, float gain) { - if (gain < 0.0f) { + CHECK_PARAM(gain < 0.0f) { return SDL_InvalidParamError("gain"); } @@ -1938,8 +1986,9 @@ bool SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallba { SDL_AudioDevice *device = NULL; SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); - bool result = true; + bool result = false; if (logdev) { + result = true; if (callback && !device->postmix_buffer) { device->postmix_buffer = (float *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size); if (!device->postmix_buffer) { @@ -1960,18 +2009,21 @@ bool SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallba bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *streams, int num_streams) { - const bool islogical = !(devid & (1<<1)); SDL_AudioDevice *device = NULL; SDL_LogicalAudioDevice *logdev = NULL; bool result = true; if (num_streams == 0) { return true; // nothing to do - } else if (num_streams < 0) { + } + + CHECK_PARAM(num_streams < 0) { return SDL_InvalidParamError("num_streams"); - } else if (!streams) { + } + CHECK_PARAM(!streams) { return SDL_InvalidParamError("streams"); - } else if (!islogical) { + } + CHECK_PARAM(SDL_IsAudioDevicePhysical(devid)) { return SDL_SetError("Audio streams are bound to device ids from SDL_OpenAudioDevice, not raw physical devices"); } @@ -2131,7 +2183,7 @@ SDL_AudioDeviceID SDL_GetAudioStreamDevice(SDL_AudioStream *stream) { SDL_AudioDeviceID result = 0; - if (!stream) { + CHECK_PARAM(!stream) { SDL_InvalidParamError("stream"); return 0; } @@ -2312,7 +2364,7 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) const bool recording = new_default_device->recording; // change the official default over right away, so new opens will go to the new device. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); const SDL_AudioDeviceID current_devid = recording ? current_audio.default_recording_device_id : current_audio.default_playback_device_id; const bool is_already_default = (new_default_device->instance_id == current_devid); if (!is_already_default) { @@ -2322,7 +2374,7 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) current_audio.default_playback_device_id = new_default_device->instance_id; } } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (is_already_default) { return; // this is already the default. @@ -2388,8 +2440,8 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) continue; // not opened as a default, leave it on the current physical device. } - // now migrate the logical device. Hold device_hash_lock so ObtainLogicalAudioDevice doesn't get a device in the middle of transition. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + // now migrate the logical device. Hold subsystem_rwlock so ObtainLogicalAudioDevice doesn't get a device in the middle of transition. + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); if (logdev->next) { logdev->next->prev = logdev->prev; } @@ -2404,7 +2456,7 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) logdev->prev = NULL; logdev->next = new_default_device->logical_devices; new_default_device->logical_devices = logdev; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); SDL_assert(SDL_GetAtomicInt(¤t_default_device->refcount) > 1); // we should hold at least one extra reference to this device, beyond logical devices, during this phase... RefPhysicalAudioDevice(new_default_device); @@ -2446,12 +2498,12 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) } if (pending.next) { - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_assert(current_audio.pending_events_tail != NULL); SDL_assert(current_audio.pending_events_tail->next == NULL); current_audio.pending_events_tail->next = pending.next; current_audio.pending_events_tail = pending_tail; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -2528,12 +2580,12 @@ bool SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SD } if (pending.next) { - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_assert(current_audio.pending_events_tail != NULL); SDL_assert(current_audio.pending_events_tail->next == NULL); current_audio.pending_events_tail->next = pending.next; current_audio.pending_events_tail = pending_tail; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -2555,20 +2607,20 @@ bool SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec * // ("UpdateSubsystem" is the same naming that the other things that hook into PumpEvents use.) void SDL_UpdateAudio(void) { - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); SDL_PendingAudioDeviceEvent *pending_events = current_audio.pending_events.next; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (!pending_events) { return; // nothing to do, check next time. } // okay, let's take this whole list of events so we can dump the lock, and new ones can queue up for a later update. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); pending_events = current_audio.pending_events.next; // in case this changed... current_audio.pending_events.next = NULL; current_audio.pending_events_tail = ¤t_audio.pending_events; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); SDL_PendingAudioDeviceEvent *pending_next = NULL; for (SDL_PendingAudioDeviceEvent *i = pending_events; i; i = pending_next) { @@ -2578,7 +2630,7 @@ void SDL_UpdateAudio(void) SDL_zero(event); event.type = i->type; event.adevice.which = (Uint32) i->devid; - event.adevice.recording = ((i->devid & (1<<0)) == 0); // bit #0 of devid is set for playback devices and unset for recording. + event.adevice.recording = SDL_IsAudioDeviceRecording(i->devid); // bit #0 of devid is set for playback devices and unset for recording. SDL_PushEvent(&event); } SDL_free(i); diff --git a/libs/SDL3/src/audio/SDL_audio_c.h b/libs/SDL3/src/audio/SDL_audio_c.h index 0e673f1..1068b94 100644 --- a/libs/SDL3/src/audio/SDL_audio_c.h +++ b/libs/SDL3/src/audio/SDL_audio_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/SDL_audio_channel_converters.h b/libs/SDL3/src/audio/SDL_audio_channel_converters.h index ab682e6..6d9cc64 100644 --- a/libs/SDL3/src/audio/SDL_audio_channel_converters.h +++ b/libs/SDL3/src/audio/SDL_audio_channel_converters.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/SDL_audiocvt.c b/libs/SDL3/src/audio/SDL_audiocvt.c index f751b0e..1fc517e 100644 --- a/libs/SDL3/src/audio/SDL_audiocvt.c +++ b/libs/SDL3/src/audio/SDL_audiocvt.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -280,7 +280,7 @@ void ConvertAudio(int num_frames, // swizzle input to "standard" format if necessary. if (src_map) { - void* buf = scratch ? scratch : dst; // use scratch if available, since it has to be big enough to hold src, unless it's NULL, then dst has to be. + void *buf = scratch ? scratch : dst; // use scratch if available, since it has to be big enough to hold src, unless it's NULL, then dst has to be. SwizzleAudio(num_frames, buf, src, src_channels, src_map, src_format); src = buf; } @@ -318,7 +318,7 @@ void ConvertAudio(int num_frames, // get us to float format. if (srcconvert) { - void* buf = (channelconvert || dstconvert) ? scratch : dst; + void *buf = (channelconvert || dstconvert) ? scratch : dst; ConvertAudioToFloat((float *) buf, src, num_frames * src_channels, src_format); src = buf; } @@ -332,7 +332,7 @@ void ConvertAudio(int num_frames, buf[i] *= gain; } } else { - float *fsrc = (float *)src; + const float *fsrc = (const float *)src; for (int i = 0; i < total_samples; i++) { buf[i] = fsrc[i] * gain; } @@ -368,7 +368,7 @@ void ConvertAudio(int num_frames, channel_converter = override; } - void* buf = dstconvert ? scratch : dst; + void *buf = dstconvert ? scratch : dst; channel_converter((float *) buf, (const float *) src, num_frames); src = buf; } @@ -399,7 +399,7 @@ static int CalculateMaxFrameSize(SDL_AudioFormat src_format, int src_channels, S return max_format_size * max_channels; } -static Sint64 GetAudioStreamResampleRate(SDL_AudioStream* stream, int src_freq, Sint64 resample_offset) +static Sint64 GetAudioStreamResampleRate(SDL_AudioStream *stream, int src_freq, Sint64 resample_offset) { src_freq = (int)((float)src_freq * stream->freq_ratio); @@ -474,10 +474,11 @@ SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_ SDL_PropertiesID SDL_GetAudioStreamProperties(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { SDL_InvalidParamError("stream"); return 0; } + SDL_LockMutex(stream->lock); if (stream->props == 0) { stream->props = SDL_CreateProperties(); @@ -488,9 +489,10 @@ SDL_PropertiesID SDL_GetAudioStreamProperties(SDL_AudioStream *stream) bool SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } + SDL_LockMutex(stream->lock); stream->get_callback = callback; stream->get_callback_userdata = userdata; @@ -500,9 +502,10 @@ bool SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamCallb bool SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } + SDL_LockMutex(stream->lock); stream->put_callback = callback; stream->put_callback_userdata = userdata; @@ -512,25 +515,33 @@ bool SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamCallb bool SDL_LockAudioStream(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } + SDL_LockMutex(stream->lock); return true; } bool SDL_UnlockAudioStream(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } + SDL_UnlockMutex(stream->lock); return true; } bool SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioSpec *src_spec, SDL_AudioSpec *dst_spec) { - if (!stream) { + CHECK_PARAM(!stream) { + if (src_spec) { + SDL_zerop(src_spec); + } + if (dst_spec) { + SDL_zerop(dst_spec); + } return SDL_InvalidParamError("stream"); } @@ -554,7 +565,7 @@ bool SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioSpec *src_spec, bool SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } @@ -563,21 +574,25 @@ bool SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_ // like 196608000Hz. File a bug. :P if (src_spec) { - if (!SDL_IsSupportedAudioFormat(src_spec->format)) { + CHECK_PARAM(!SDL_IsSupportedAudioFormat(src_spec->format)) { return SDL_InvalidParamError("src_spec->format"); - } else if (!SDL_IsSupportedChannelCount(src_spec->channels)) { + } + CHECK_PARAM(!SDL_IsSupportedChannelCount(src_spec->channels)) { return SDL_InvalidParamError("src_spec->channels"); - } else if (src_spec->freq <= 0) { + } + CHECK_PARAM(src_spec->freq <= 0) { return SDL_InvalidParamError("src_spec->freq"); } } if (dst_spec) { - if (!SDL_IsSupportedAudioFormat(dst_spec->format)) { + CHECK_PARAM(!SDL_IsSupportedAudioFormat(dst_spec->format)) { return SDL_InvalidParamError("dst_spec->format"); - } else if (!SDL_IsSupportedChannelCount(dst_spec->channels)) { + } + CHECK_PARAM(!SDL_IsSupportedChannelCount(dst_spec->channels)) { return SDL_InvalidParamError("dst_spec->channels"); - } else if (dst_spec->freq <= 0) { + } + CHECK_PARAM(dst_spec->freq <= 0) { return SDL_InvalidParamError("dst_spec->freq"); } } @@ -616,7 +631,7 @@ bool SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_ bool SetAudioStreamChannelMap(SDL_AudioStream *stream, const SDL_AudioSpec *spec, int **stream_chmap, const int *chmap, int channels, int isinput) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } @@ -702,7 +717,7 @@ int *SDL_GetAudioStreamOutputChannelMap(SDL_AudioStream *stream, int *count) float SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { SDL_InvalidParamError("stream"); return 0.0f; } @@ -716,7 +731,7 @@ float SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream *stream) bool SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float freq_ratio) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } @@ -739,7 +754,7 @@ bool SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float freq_ratio) float SDL_GetAudioStreamGain(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { SDL_InvalidParamError("stream"); return -1.0f; } @@ -753,9 +768,10 @@ float SDL_GetAudioStreamGain(SDL_AudioStream *stream) bool SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); - } else if (gain < 0.0f) { + } + CHECK_PARAM(gain < 0.0f) { return SDL_InvalidParamError("gain"); } @@ -768,16 +784,48 @@ bool SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain) static bool CheckAudioStreamIsFullySetup(SDL_AudioStream *stream) { - if (stream->src_spec.format == 0) { + if (stream->src_spec.format == SDL_AUDIO_UNKNOWN) { return SDL_SetError("Stream has no source format"); - } else if (stream->dst_spec.format == 0) { + } else if (stream->dst_spec.format == SDL_AUDIO_UNKNOWN) { return SDL_SetError("Stream has no destination format"); } return true; } -static bool PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void* userdata) +// you MUST hold `stream->lock` when calling this, and validate your parameters! +static bool PutAudioStreamBufferInternal(SDL_AudioStream *stream, const SDL_AudioSpec *spec, const int *chmap, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void *userdata) +{ + SDL_AudioTrack *track = NULL; + + if (callback) { + track = SDL_CreateAudioTrack(stream->queue, spec, chmap, (Uint8 *)buf, len, len, callback, userdata); + if (!track) { + return false; + } + } + + const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0; + + bool retval = true; + + if (track) { + SDL_AddTrackToAudioQueue(stream->queue, track); + } else { + retval = SDL_WriteToAudioQueue(stream->queue, spec, chmap, (const Uint8 *)buf, len); + } + + if (retval) { + if (stream->put_callback) { + const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available; + stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail); + } + } + + return retval; +} + +static bool PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void *userdata) { #if DEBUG_AUDIOSTREAM SDL_Log("AUDIOSTREAM: wants to put %d bytes", len); @@ -795,53 +843,31 @@ static bool PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int l return SDL_SetError("Can't add partial sample frames"); } - SDL_AudioTrack* track = NULL; - - if (callback) { - track = SDL_CreateAudioTrack(stream->queue, &stream->src_spec, stream->src_chmap, (Uint8 *)buf, len, len, callback, userdata); - - if (!track) { - SDL_UnlockMutex(stream->lock); - return false; - } - } - - const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0; - - bool result = true; - - if (track) { - SDL_AddTrackToAudioQueue(stream->queue, track); - } else { - result = SDL_WriteToAudioQueue(stream->queue, &stream->src_spec, stream->src_chmap, (const Uint8 *)buf, len); - } - - if (result) { - if (stream->put_callback) { - const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available; - stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail); - } - } + const bool retval = PutAudioStreamBufferInternal(stream, &stream->src_spec, stream->src_chmap, buf, len, callback, userdata); SDL_UnlockMutex(stream->lock); - return result; + return retval; } static void SDLCALL FreeAllocatedAudioBuffer(void *userdata, const void *buf, int len) { - SDL_free((void*) buf); + SDL_free((void *)buf); } bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); - } else if (!buf) { + } + CHECK_PARAM(!buf) { return SDL_InvalidParamError("buf"); - } else if (len < 0) { + } + CHECK_PARAM(len < 0) { return SDL_InvalidParamError("len"); - } else if (len == 0) { + } + + if (len == 0) { return true; // nothing to do. } @@ -857,9 +883,8 @@ bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) } SDL_memcpy(data, buf, len); - buf = data; - bool ret = PutAudioStreamBuffer(stream, buf, len, FreeAllocatedAudioBuffer, NULL); + bool ret = PutAudioStreamBuffer(stream, data, len, FreeAllocatedAudioBuffer, NULL); if (!ret) { SDL_free(data); } @@ -869,9 +894,192 @@ bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) return PutAudioStreamBuffer(stream, buf, len, NULL, NULL); } + +#define GENERIC_INTERLEAVE_FUNCTION(bits) \ + static void InterleaveAudioChannelsGeneric##bits(void *output, const void * const *channel_buffers, const int channels, int num_samples) { \ + Uint##bits *dst = (Uint##bits *) output; \ + const Uint##bits * const *srcs = (const Uint##bits * const *) channel_buffers; \ + for (int frame = 0; frame < num_samples; frame++) { \ + for (int channel = 0; channel < channels; channel++) { \ + *(dst++) = srcs[channel][frame]; \ + } \ + } \ + } + +GENERIC_INTERLEAVE_FUNCTION(8) +GENERIC_INTERLEAVE_FUNCTION(16) +GENERIC_INTERLEAVE_FUNCTION(32) +//GENERIC_INTERLEAVE_FUNCTION(64) (we don't have any 64-bit audio data types at the moment.) +#undef GENERIC_INTERLEAVE_FUNCTION + +#define GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(bits) \ + static void InterleaveAudioChannelsWithNullsGeneric##bits(void *output, const void * const *channel_buffers, const int channels, int num_samples, const int isilence) { \ + const Uint##bits silence = (Uint##bits) isilence; \ + Uint##bits *dst = (Uint##bits *) output; \ + const Uint##bits * const *srcs = (const Uint##bits * const *) channel_buffers; \ + for (int frame = 0; frame < num_samples; frame++) { \ + for (int channel = 0; channel < channels; channel++) { \ + *(dst++) = srcs[channel] ? srcs[channel][frame] : silence; \ + } \ + } \ + } + +GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(8) +GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(16) +GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(32) +//GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(64) (we don't have any 64-bit audio data types at the moment.) +#undef GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION + +static void InterleaveAudioChannels(void *output, const void * const *channel_buffers, int channels, int num_samples, const SDL_AudioSpec *spec) +{ + bool have_null_channel = false; + void *channels_full[16]; + + // if didn't specify enough channels, pad out a channel array with NULLs. + if ((channels >= 0) && (channels < spec->channels)) { + have_null_channel = true; + SDL_assert(SDL_IsSupportedChannelCount(spec->channels)); + SDL_assert(spec->channels <= SDL_arraysize(channels_full)); + SDL_memcpy(channels_full, channel_buffers, channels * sizeof (*channel_buffers)); + SDL_memset(channels_full + channels, 0, (spec->channels - channels) * sizeof (*channel_buffers)); + channel_buffers = (const void * const *) channels_full; + } + + channels = spec->channels; // it's either < 0, needs to be clamped to spec->channels, or we just padded it out to spec->channels with channels_full. + + if (!have_null_channel) { + for (int i = 0; i < channels; i++) { + if (channel_buffers[i] == NULL) { + have_null_channel = true; + break; + } + } + } + + if (have_null_channel) { + const int silence = SDL_GetSilenceValueForFormat(spec->format); + switch (SDL_AUDIO_BITSIZE(spec->format)) { + case 8: InterleaveAudioChannelsWithNullsGeneric8(output, channel_buffers, channels, num_samples, silence); break; + case 16: InterleaveAudioChannelsWithNullsGeneric16(output, channel_buffers, channels, num_samples, silence); break; + case 32: InterleaveAudioChannelsWithNullsGeneric32(output, channel_buffers, channels, num_samples, silence); break; + //case 64: InterleaveAudioChannelsGeneric64(output, channel_buffers, channels, num_samples); break; (we don't have any 64-bit audio data types at the moment.) + default: SDL_assert(!"Missing needed generic audio interleave function!"); SDL_memset(output, 0, SDL_AUDIO_FRAMESIZE(*spec) * num_samples); break; + } + } else { + // !!! FIXME: it would be possible to do this really well in SIMD for stereo data, using unpack (intel) or zip (arm) instructions, etc. + switch (SDL_AUDIO_BITSIZE(spec->format)) { + case 8: InterleaveAudioChannelsGeneric8(output, channel_buffers, channels, num_samples); break; + case 16: InterleaveAudioChannelsGeneric16(output, channel_buffers, channels, num_samples); break; + case 32: InterleaveAudioChannelsGeneric32(output, channel_buffers, channels, num_samples); break; + //case 64: InterleaveAudioChannelsGeneric64(output, channel_buffers, channels, num_samples); break; (we don't have any 64-bit audio data types at the moment.) + default: SDL_assert(!"Missing needed generic audio interleave function!"); SDL_memset(output, 0, SDL_AUDIO_FRAMESIZE(*spec) * num_samples); break; + } + } +} + +bool SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *channel_buffers, int num_channels, int num_samples) +{ + CHECK_PARAM(!stream) { + return SDL_InvalidParamError("stream"); + } + CHECK_PARAM(!channel_buffers) { + return SDL_InvalidParamError("channel_buffers"); + } + CHECK_PARAM(num_samples < 0) { + return SDL_InvalidParamError("num_samples"); + } + + if (num_samples == 0) { + return true; // nothing to do. + } + + // we do the interleaving up front without the lock held, so the audio device doesn't starve while we work. + // but we _do_ need to know the current input spec. + SDL_AudioSpec spec; + int chmap_copy[SDL_MAX_CHANNELMAP_CHANNELS]; + int *chmap = NULL; + SDL_LockMutex(stream->lock); + if (!CheckAudioStreamIsFullySetup(stream)) { + SDL_UnlockMutex(stream->lock); + return false; + } + SDL_copyp(&spec, &stream->src_spec); + if (stream->src_chmap) { + chmap = chmap_copy; + SDL_memcpy(chmap, stream->src_chmap, sizeof (*chmap) * spec.channels); + } + SDL_UnlockMutex(stream->lock); + + if (spec.channels == 1) { // nothing to interleave, just use the usual function. + return SDL_PutAudioStreamData(stream, channel_buffers[0], SDL_AUDIO_FRAMESIZE(spec) * num_samples); + } + + bool retval = false; + + const int len = SDL_AUDIO_FRAMESIZE(spec) * num_samples; + #if DEBUG_AUDIOSTREAM + SDL_Log("AUDIOSTREAM: wants to put %d bytes of planar data", len); + #endif + + // Is the data small enough to just interleave it on the stack and put it through the normal interface? + #define INTERLEAVE_STACK_SIZE 1024 + Uint8 stackbuf[INTERLEAVE_STACK_SIZE]; + void *data = stackbuf; + SDL_ReleaseAudioBufferCallback callback = NULL; + + if (len > INTERLEAVE_STACK_SIZE) { + // too big for the stack? Just SDL_malloc a block and interleave into that. To avoid the extra copy, we'll just set it as a + // new track in the queue (the distinction is specifying a callback to PutAudioStreamBufferInternal, to release the buffer). + data = SDL_malloc(len); + if (!data) { + return false; + } + callback = FreeAllocatedAudioBuffer; + } + + InterleaveAudioChannels(data, channel_buffers, num_channels, num_samples, &spec); + + // it's okay if the stream format changed on another thread while we didn't hold the lock; PutAudioStreamBufferInternal will notice + // and set up a new track with the right format, and the next SDL_PutAudioStreamData will notice that stream->src_spec doesn't + // match the new track and set up a new one again. It's a bad idea to change the format on another thread while putting here, + // but everything _will_ work out with the format that was (presumably) expected. + SDL_LockMutex(stream->lock); + retval = PutAudioStreamBufferInternal(stream, &spec, chmap, data, len, callback, NULL); + SDL_UnlockMutex(stream->lock); + + return retval; +} + +static void SDLCALL DontFreeThisAudioBuffer(void *userdata, const void *buf, int len) +{ + // We don't own the buffer, but know it will outlive the stream +} + +bool SDL_PutAudioStreamDataNoCopy(SDL_AudioStream *stream, const void *buf, int len, SDL_AudioStreamDataCompleteCallback callback, void *userdata) +{ + CHECK_PARAM(!stream) { + return SDL_InvalidParamError("stream"); + } + CHECK_PARAM(!buf) { + return SDL_InvalidParamError("buf"); + } + CHECK_PARAM(len < 0) { + return SDL_InvalidParamError("len"); + } + + if (len == 0) { + if (callback) { + callback(userdata, buf, len); + } + return true; // nothing to do. + } + + return PutAudioStreamBuffer(stream, buf, len, callback ? callback : DontFreeThisAudioBuffer, userdata); +} + bool SDL_FlushAudioStream(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } @@ -901,8 +1109,8 @@ static Uint8 *EnsureAudioStreamWorkBufferSize(SDL_AudioStream *stream, size_t ne return ptr; } -static Sint64 NextAudioStreamIter(SDL_AudioStream* stream, void** inout_iter, - Sint64* inout_resample_offset, SDL_AudioSpec* out_spec, int **out_chmap, bool* out_flushed) +static Sint64 NextAudioStreamIter(SDL_AudioStream *stream, void **inout_iter, + Sint64 *inout_resample_offset, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed) { SDL_AudioSpec spec; bool flushed; @@ -956,9 +1164,9 @@ static Sint64 NextAudioStreamIter(SDL_AudioStream* stream, void** inout_iter, return output_frames; } -static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream* stream, Sint64* out_resample_offset) +static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream *stream, Sint64 *out_resample_offset) { - void* iter = SDL_BeginAudioQueueIter(stream->queue); + void *iter = SDL_BeginAudioQueueIter(stream->queue); Sint64 resample_offset = stream->resample_offset; Sint64 output_frames = 0; @@ -980,9 +1188,9 @@ static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream* stream, Sint64* out return output_frames; } -static Sint64 GetAudioStreamHead(SDL_AudioStream* stream, SDL_AudioSpec* out_spec, int **out_chmap, bool* out_flushed) +static Sint64 GetAudioStreamHead(SDL_AudioStream *stream, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed) { - void* iter = SDL_BeginAudioQueueIter(stream->queue); + void *iter = SDL_BeginAudioQueueIter(stream->queue); if (!iter) { SDL_zerop(out_spec); @@ -998,8 +1206,8 @@ static Sint64 GetAudioStreamHead(SDL_AudioStream* stream, SDL_AudioSpec* out_spe // Enough input data MUST be available! static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int output_frames, float gain) { - const SDL_AudioSpec* src_spec = &stream->input_spec; - const SDL_AudioSpec* dst_spec = &stream->dst_spec; + const SDL_AudioSpec *src_spec = &stream->input_spec; + const SDL_AudioSpec *dst_spec = &stream->dst_spec; const SDL_AudioFormat src_format = src_spec->format; const int src_channels = src_spec->channels; @@ -1019,7 +1227,7 @@ static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int o // Not resampling? It's an easy conversion (and maybe not even that!) if (resample_rate == 0) { - Uint8* work_buffer = NULL; + Uint8 *work_buffer = NULL; // Ensure we have enough scratch space for any conversions if ((src_format != dst_format) || (src_channels != dst_channels) || (gain != 1.0f)) { @@ -1089,7 +1297,7 @@ static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int o work_buffer_capacity += resample_bytes; } - Uint8* work_buffer = EnsureAudioStreamWorkBufferSize(stream, work_buffer_capacity); + Uint8 *work_buffer = EnsureAudioStreamWorkBufferSize(stream, work_buffer_capacity); if (!work_buffer) { return false; @@ -1101,7 +1309,7 @@ static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int o const float postresample_gain = (input_frames > output_frames) ? gain : 1.0f; // (dst channel map is NULL because we'll do the final swizzle on ConvertAudio after resample.) - const Uint8* input_buffer = SDL_ReadFromAudioQueue(stream->queue, + const Uint8 *input_buffer = SDL_ReadFromAudioQueue(stream->queue, NULL, resample_format, resample_channels, NULL, padding_frames, input_frames, padding_frames, work_buffer, preresample_gain); @@ -1112,11 +1320,11 @@ static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int o input_buffer += padding_frames * resample_frame_size; // Decide where the resampled output goes - void* resample_buffer = (resample_buffer_offset != -1) ? (work_buffer + resample_buffer_offset) : buf; + void *resample_buffer = (resample_buffer_offset != -1) ? (work_buffer + resample_buffer_offset) : buf; SDL_ResampleAudio(resample_channels, - (const float *) input_buffer, input_frames, - (float*) resample_buffer, output_frames, + (const float *)input_buffer, input_frames, + (float *)resample_buffer, output_frames, resample_rate, &stream->resample_offset); // Convert to the final format, if necessary (src channel map is NULL because SDL_ReadFromAudioQueue already handled this). @@ -1134,16 +1342,20 @@ int SDL_GetAudioStreamDataAdjustGain(SDL_AudioStream *stream, void *voidbuf, int SDL_Log("AUDIOSTREAM: want to get %d converted bytes", len); #endif - if (!stream) { + CHECK_PARAM(!stream) { SDL_InvalidParamError("stream"); return -1; - } else if (!buf) { + } + CHECK_PARAM(!buf) { SDL_InvalidParamError("buf"); return -1; - } else if (len < 0) { + } + CHECK_PARAM(len < 0) { SDL_InvalidParamError("len"); return -1; - } else if (len == 0) { + } + + if (len == 0) { return 0; // nothing to do. } @@ -1241,7 +1453,7 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) // number of converted/resampled bytes available for output int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { SDL_InvalidParamError("stream"); return -1; } @@ -1267,7 +1479,7 @@ int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream) // number of sample frames that are currently queued as input. int SDL_GetAudioStreamQueued(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { SDL_InvalidParamError("stream"); return -1; } @@ -1284,7 +1496,7 @@ int SDL_GetAudioStreamQueued(SDL_AudioStream *stream) bool SDL_ClearAudioStream(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } @@ -1326,11 +1538,6 @@ void SDL_DestroyAudioStream(SDL_AudioStream *stream) SDL_free(stream); } -static void SDLCALL DontFreeThisAudioBuffer(void *userdata, const void *buf, int len) -{ - // We don't own the buffer, but know it will outlive the stream -} - bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len) { if (dst_data) { @@ -1341,13 +1548,16 @@ bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_dat *dst_len = 0; } - if (!src_data) { + CHECK_PARAM(!src_data) { return SDL_InvalidParamError("src_data"); - } else if (src_len < 0) { + } + CHECK_PARAM(src_len < 0) { return SDL_InvalidParamError("src_len"); - } else if (!dst_data) { + } + CHECK_PARAM(!dst_data) { return SDL_InvalidParamError("dst_data"); - } else if (!dst_len) { + } + CHECK_PARAM(!dst_len) { return SDL_InvalidParamError("dst_len"); } @@ -1357,8 +1567,7 @@ bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_dat SDL_AudioStream *stream = SDL_CreateAudioStream(src_spec, dst_spec); if (stream) { - if (PutAudioStreamBuffer(stream, src_data, src_len, DontFreeThisAudioBuffer, NULL) && - SDL_FlushAudioStream(stream)) { + if (SDL_PutAudioStreamDataNoCopy(stream, src_data, src_len, NULL, NULL) && SDL_FlushAudioStream(stream)) { dstlen = SDL_GetAudioStreamAvailable(stream); if (dstlen >= 0) { dst = (Uint8 *)SDL_malloc(dstlen); diff --git a/libs/SDL3/src/audio/SDL_audiodev.c b/libs/SDL3/src/audio/SDL_audiodev.c index 623a380..57bb682 100644 --- a/libs/SDL3/src/audio/SDL_audiodev.c +++ b/libs/SDL3/src/audio/SDL_audiodev.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/SDL_audiodev_c.h b/libs/SDL3/src/audio/SDL_audiodev_c.h index ded13fc..5c38847 100644 --- a/libs/SDL3/src/audio/SDL_audiodev_c.h +++ b/libs/SDL3/src/audio/SDL_audiodev_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/SDL_audioqueue.c b/libs/SDL3/src/audio/SDL_audioqueue.c index df7cac8..b59e847 100644 --- a/libs/SDL3/src/audio/SDL_audioqueue.c +++ b/libs/SDL3/src/audio/SDL_audioqueue.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/SDL_audioqueue.h b/libs/SDL3/src/audio/SDL_audioqueue.h index 4662946..2654296 100644 --- a/libs/SDL3/src/audio/SDL_audioqueue.h +++ b/libs/SDL3/src/audio/SDL_audioqueue.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -25,7 +25,7 @@ // Internal functions used by SDL_AudioStream for queueing audio. -typedef void (SDLCALL *SDL_ReleaseAudioBufferCallback)(void *userdata, const void *buffer, int buflen); +typedef SDL_AudioStreamDataCompleteCallback SDL_ReleaseAudioBufferCallback; typedef struct SDL_AudioQueue SDL_AudioQueue; typedef struct SDL_AudioTrack SDL_AudioTrack; diff --git a/libs/SDL3/src/audio/SDL_audioresample.c b/libs/SDL3/src/audio/SDL_audioresample.c index 371002e..8ec29b0 100644 --- a/libs/SDL3/src/audio/SDL_audioresample.c +++ b/libs/SDL3/src/audio/SDL_audioresample.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/SDL_audioresample.h b/libs/SDL3/src/audio/SDL_audioresample.h index 6620073..0a3f23f 100644 --- a/libs/SDL3/src/audio/SDL_audioresample.h +++ b/libs/SDL3/src/audio/SDL_audioresample.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/SDL_audiotypecvt.c b/libs/SDL3/src/audio/SDL_audiotypecvt.c index b3a7bbc..faa223b 100644 --- a/libs/SDL3/src/audio/SDL_audiotypecvt.c +++ b/libs/SDL3/src/audio/SDL_audiotypecvt.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -185,7 +185,7 @@ static void SDL_Convert_F32_to_S32_Scalar(Sint32 *dst, const float *src, int num #undef SIGNMASK -static void SDL_Convert_Swap16_Scalar(Uint16* dst, const Uint16* src, int num_samples) +static void SDL_Convert_Swap16_Scalar(Uint16 *dst, const Uint16 *src, int num_samples) { int i; @@ -194,7 +194,7 @@ static void SDL_Convert_Swap16_Scalar(Uint16* dst, const Uint16* src, int num_sa } } -static void SDL_Convert_Swap32_Scalar(Uint32* dst, const Uint32* src, int num_samples) +static void SDL_Convert_Swap32_Scalar(Uint32 *dst, const Uint32 *src, int num_samples) { int i; @@ -375,7 +375,7 @@ static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S8_SSE2(Sint8 *dst, const f const __m128i bytes = _mm_packus_epi16(shorts0, shorts1); - _mm_store_si128((__m128i*)&dst[i], bytes); + _mm_store_si128((__m128i *)&dst[i], bytes); }) } @@ -409,7 +409,7 @@ static void SDL_TARGETING("sse2") SDL_Convert_F32_to_U8_SSE2(Uint8 *dst, const f const __m128i bytes = _mm_packus_epi16(shorts0, shorts1); - _mm_store_si128((__m128i*)&dst[i], bytes); + _mm_store_si128((__m128i *)&dst[i], bytes); }) } @@ -441,8 +441,8 @@ static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S16_SSE2(Sint16 *dst, const const __m128i shorts0 = _mm_packs_epi32(ints0, ints1); const __m128i shorts1 = _mm_packs_epi32(ints2, ints3); - _mm_store_si128((__m128i*)&dst[i], shorts0); - _mm_store_si128((__m128i*)&dst[i + 8], shorts1); + _mm_store_si128((__m128i *)&dst[i], shorts0); + _mm_store_si128((__m128i *)&dst[i + 8], shorts1); }) } @@ -477,55 +477,55 @@ static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S32_SSE2(Sint32 *dst, const const __m128i ints2 = _mm_xor_si128(_mm_cvttps_epi32(values3), _mm_castps_si128(_mm_cmpge_ps(values3, limit))); const __m128i ints3 = _mm_xor_si128(_mm_cvttps_epi32(values4), _mm_castps_si128(_mm_cmpge_ps(values4, limit))); - _mm_store_si128((__m128i*)&dst[i], ints0); - _mm_store_si128((__m128i*)&dst[i + 4], ints1); - _mm_store_si128((__m128i*)&dst[i + 8], ints2); - _mm_store_si128((__m128i*)&dst[i + 12], ints3); + _mm_store_si128((__m128i *)&dst[i], ints0); + _mm_store_si128((__m128i *)&dst[i + 4], ints1); + _mm_store_si128((__m128i *)&dst[i + 8], ints2); + _mm_store_si128((__m128i *)&dst[i + 12], ints3); }) } #endif // FIXME: SDL doesn't have SSSE3 detection, so use the next one up #ifdef SDL_SSE4_1_INTRINSICS -static void SDL_TARGETING("ssse3") SDL_Convert_Swap16_SSSE3(Uint16* dst, const Uint16* src, int num_samples) +static void SDL_TARGETING("ssse3") SDL_Convert_Swap16_SSSE3(Uint16 *dst, const Uint16 *src, int num_samples) { const __m128i shuffle = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1); CONVERT_16_FWD({ dst[i] = SDL_Swap16(src[i]); }, { - __m128i ints0 = _mm_loadu_si128((const __m128i*)&src[i]); - __m128i ints1 = _mm_loadu_si128((const __m128i*)&src[i + 8]); + __m128i ints0 = _mm_loadu_si128((const __m128i *)&src[i]); + __m128i ints1 = _mm_loadu_si128((const __m128i *)&src[i + 8]); ints0 = _mm_shuffle_epi8(ints0, shuffle); ints1 = _mm_shuffle_epi8(ints1, shuffle); - _mm_store_si128((__m128i*)&dst[i], ints0); - _mm_store_si128((__m128i*)&dst[i + 8], ints1); + _mm_store_si128((__m128i *)&dst[i], ints0); + _mm_store_si128((__m128i *)&dst[i + 8], ints1); }) } -static void SDL_TARGETING("ssse3") SDL_Convert_Swap32_SSSE3(Uint32* dst, const Uint32* src, int num_samples) +static void SDL_TARGETING("ssse3") SDL_Convert_Swap32_SSSE3(Uint32 *dst, const Uint32 *src, int num_samples) { const __m128i shuffle = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3); CONVERT_16_FWD({ dst[i] = SDL_Swap32(src[i]); }, { - __m128i ints0 = _mm_loadu_si128((const __m128i*)&src[i]); - __m128i ints1 = _mm_loadu_si128((const __m128i*)&src[i + 4]); - __m128i ints2 = _mm_loadu_si128((const __m128i*)&src[i + 8]); - __m128i ints3 = _mm_loadu_si128((const __m128i*)&src[i + 12]); + __m128i ints0 = _mm_loadu_si128((const __m128i *)&src[i]); + __m128i ints1 = _mm_loadu_si128((const __m128i *)&src[i + 4]); + __m128i ints2 = _mm_loadu_si128((const __m128i *)&src[i + 8]); + __m128i ints3 = _mm_loadu_si128((const __m128i *)&src[i + 12]); ints0 = _mm_shuffle_epi8(ints0, shuffle); ints1 = _mm_shuffle_epi8(ints1, shuffle); ints2 = _mm_shuffle_epi8(ints2, shuffle); ints3 = _mm_shuffle_epi8(ints3, shuffle); - _mm_store_si128((__m128i*)&dst[i], ints0); - _mm_store_si128((__m128i*)&dst[i + 4], ints1); - _mm_store_si128((__m128i*)&dst[i + 8], ints2); - _mm_store_si128((__m128i*)&dst[i + 12], ints3); + _mm_store_si128((__m128i *)&dst[i], ints0); + _mm_store_si128((__m128i *)&dst[i + 4], ints1); + _mm_store_si128((__m128i *)&dst[i + 8], ints2); + _mm_store_si128((__m128i *)&dst[i + 12], ints3); }) } #endif @@ -776,41 +776,41 @@ static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_s fesetenv(&fenv); } -static void SDL_Convert_Swap16_NEON(Uint16* dst, const Uint16* src, int num_samples) +static void SDL_Convert_Swap16_NEON(Uint16 *dst, const Uint16 *src, int num_samples) { CONVERT_16_FWD({ dst[i] = SDL_Swap16(src[i]); }, { - uint8x16_t ints0 = vld1q_u8((const Uint8*)&src[i]); - uint8x16_t ints1 = vld1q_u8((const Uint8*)&src[i + 8]); + uint8x16_t ints0 = vld1q_u8((const Uint8 *)&src[i]); + uint8x16_t ints1 = vld1q_u8((const Uint8 *)&src[i + 8]); ints0 = vrev16q_u8(ints0); ints1 = vrev16q_u8(ints1); - vst1q_u8((Uint8*)&dst[i], ints0); - vst1q_u8((Uint8*)&dst[i + 8], ints1); + vst1q_u8((Uint8 *)&dst[i], ints0); + vst1q_u8((Uint8 *)&dst[i + 8], ints1); }) } -static void SDL_Convert_Swap32_NEON(Uint32* dst, const Uint32* src, int num_samples) +static void SDL_Convert_Swap32_NEON(Uint32 *dst, const Uint32 *src, int num_samples) { CONVERT_16_FWD({ dst[i] = SDL_Swap32(src[i]); }, { - uint8x16_t ints0 = vld1q_u8((const Uint8*)&src[i]); - uint8x16_t ints1 = vld1q_u8((const Uint8*)&src[i + 4]); - uint8x16_t ints2 = vld1q_u8((const Uint8*)&src[i + 8]); - uint8x16_t ints3 = vld1q_u8((const Uint8*)&src[i + 12]); + uint8x16_t ints0 = vld1q_u8((const Uint8 *)&src[i]); + uint8x16_t ints1 = vld1q_u8((const Uint8 *)&src[i + 4]); + uint8x16_t ints2 = vld1q_u8((const Uint8 *)&src[i + 8]); + uint8x16_t ints3 = vld1q_u8((const Uint8 *)&src[i + 12]); ints0 = vrev32q_u8(ints0); ints1 = vrev32q_u8(ints1); ints2 = vrev32q_u8(ints2); ints3 = vrev32q_u8(ints3); - vst1q_u8((Uint8*)&dst[i], ints0); - vst1q_u8((Uint8*)&dst[i + 4], ints1); - vst1q_u8((Uint8*)&dst[i + 8], ints2); - vst1q_u8((Uint8*)&dst[i + 12], ints3); + vst1q_u8((Uint8 *)&dst[i], ints0); + vst1q_u8((Uint8 *)&dst[i + 4], ints1); + vst1q_u8((Uint8 *)&dst[i + 8], ints2); + vst1q_u8((Uint8 *)&dst[i + 12], ints3); }) } @@ -843,8 +843,8 @@ static void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_sampl static void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples) = NULL; static void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples) = NULL; -static void (*SDL_Convert_Swap16)(Uint16* dst, const Uint16* src, int num_samples) = NULL; -static void (*SDL_Convert_Swap32)(Uint32* dst, const Uint32* src, int num_samples) = NULL; +static void (*SDL_Convert_Swap16)(Uint16 *dst, const Uint16 *src, int num_samples) = NULL; +static void (*SDL_Convert_Swap32)(Uint32 *dst, const Uint32 *src, int num_samples) = NULL; void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt) { @@ -862,7 +862,7 @@ void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_Audio break; case SDL_AUDIO_S16 ^ SDL_AUDIO_MASK_BIG_ENDIAN: - SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) src, num_samples); + SDL_Convert_Swap16((Uint16 *)dst, (const Uint16 *)src, num_samples); SDL_Convert_S16_to_F32(dst, (const Sint16 *) dst, num_samples); break; @@ -871,12 +871,12 @@ void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_Audio break; case SDL_AUDIO_S32 ^ SDL_AUDIO_MASK_BIG_ENDIAN: - SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples); + SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples); SDL_Convert_S32_to_F32(dst, (const Sint32 *) dst, num_samples); break; case SDL_AUDIO_F32 ^ SDL_AUDIO_MASK_BIG_ENDIAN: - SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples); + SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples); break; default: SDL_assert(!"Unexpected audio format!"); break; @@ -900,7 +900,7 @@ void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_Aud case SDL_AUDIO_S16 ^ SDL_AUDIO_MASK_BIG_ENDIAN: SDL_Convert_F32_to_S16((Sint16 *) dst, src, num_samples); - SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) dst, num_samples); + SDL_Convert_Swap16((Uint16 *)dst, (const Uint16 *)dst, num_samples); break; case SDL_AUDIO_S32: @@ -909,22 +909,22 @@ void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_Aud case SDL_AUDIO_S32 ^ SDL_AUDIO_MASK_BIG_ENDIAN: SDL_Convert_F32_to_S32((Sint32 *) dst, src, num_samples); - SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) dst, num_samples); + SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)dst, num_samples); break; case SDL_AUDIO_F32 ^ SDL_AUDIO_MASK_BIG_ENDIAN: - SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples); + SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples); break; default: SDL_assert(!"Unexpected audio format!"); break; } } -void ConvertAudioSwapEndian(void* dst, const void* src, int num_samples, int bitsize) +void ConvertAudioSwapEndian(void *dst, const void *src, int num_samples, int bitsize) { switch (bitsize) { - case 16: SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) src, num_samples); break; - case 32: SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples); break; + case 16: SDL_Convert_Swap16((Uint16 *)dst, (const Uint16 *)src, num_samples); break; + case 32: SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples); break; default: SDL_assert(!"Unexpected audio format!"); break; } } diff --git a/libs/SDL3/src/audio/SDL_mixer.c b/libs/SDL3/src/audio/SDL_mixer.c index 047c6fd..bc8fb77 100644 --- a/libs/SDL3/src/audio/SDL_mixer.c +++ b/libs/SDL3/src/audio/SDL_mixer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/SDL_sysaudio.h b/libs/SDL3/src/audio/SDL_sysaudio.h index 4a88bd2..9104be8 100644 --- a/libs/SDL3/src/audio/SDL_sysaudio.h +++ b/libs/SDL3/src/audio/SDL_sysaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -112,7 +112,7 @@ extern void SDL_AudioThreadFinalize(SDL_AudioDevice *device); extern void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt); extern void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_AudioFormat dst_fmt); -extern void ConvertAudioSwapEndian(void* dst, const void* src, int num_samples, int bitsize); +extern void ConvertAudioSwapEndian(void *dst, const void *src, int num_samples, int bitsize); extern bool SDL_ChannelMapIsDefault(const int *map, int channels); extern bool SDL_ChannelMapIsBogus(const int *map, int channels); @@ -121,7 +121,7 @@ extern bool SDL_ChannelMapIsBogus(const int *map, int channels); extern void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_format, int src_channels, const int *src_map, void *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map, - void* scratch, float gain); + void *scratch, float gain); // Compare two SDL_AudioSpecs, return true if they match exactly. // Using SDL_memcmp directly isn't safe, since potential padding might not be initialized. @@ -183,8 +183,9 @@ typedef struct SDL_AudioDriver const char *name; // The name of this audio driver const char *desc; // The description of this audio driver SDL_AudioDriverImpl impl; // the backend's interface - SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash` - SDL_HashTable *device_hash; // the collection of currently-available audio devices (recording, playback, logical and physical!) + SDL_RWLock *subsystem_rwlock; // A rwlock that protects several things in the audio subsystem (device hashtables, etc). + SDL_HashTable *device_hash_physical; // the collection of currently-available audio devices (recording and playback), for mapping SDL_AudioDeviceID to an SDL_AudioDevice*. + SDL_HashTable *device_hash_logical; // the collection of currently-available audio devices (recording and playback), for mapping SDL_AudioDeviceID to an SDL_LogicalAudioDevice*. SDL_AudioStream *existing_streams; // a list of all existing SDL_AudioStreams. SDL_AudioDeviceID default_playback_device_id; SDL_AudioDeviceID default_recording_device_id; @@ -201,7 +202,7 @@ struct SDL_AudioQueue; // forward decl. struct SDL_AudioStream { - SDL_Mutex* lock; + SDL_Mutex *lock; SDL_PropertiesID props; @@ -217,7 +218,7 @@ struct SDL_AudioStream float freq_ratio; float gain; - struct SDL_AudioQueue* queue; + struct SDL_AudioQueue *queue; SDL_AudioSpec input_spec; // The spec of input data currently being processed int *input_chmap; @@ -386,6 +387,7 @@ extern AudioBootStrap PS2AUDIO_bootstrap; extern AudioBootStrap PSPAUDIO_bootstrap; extern AudioBootStrap VITAAUD_bootstrap; extern AudioBootStrap N3DSAUDIO_bootstrap; +extern AudioBootStrap NGAGEAUDIO_bootstrap; extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap; extern AudioBootStrap QSAAUDIO_bootstrap; diff --git a/libs/SDL3/src/audio/SDL_wave.c b/libs/SDL3/src/audio/SDL_wave.c index 826b652..c819bc2 100644 --- a/libs/SDL3/src/audio/SDL_wave.c +++ b/libs/SDL3/src/audio/SDL_wave.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -813,7 +813,7 @@ static bool IMA_ADPCM_Init(WaveFile *file, size_t datalength) if (format->formattag == EXTENSIBLE_CODE) { /* There's no specification for this, but it's basically the same - * format because the extensible header has wSampePerBlocks too. + * format because the extensible header has wSamplePerBlocks too. */ } else { // The Standards Update says there 'should' be 2 bytes for wSamplesPerBlock. @@ -1775,6 +1775,7 @@ static bool WaveLoad(SDL_IOStream *src, WaveFile *file, SDL_AudioSpec *spec, Uin int result; Uint32 chunkcount = 0; Uint32 chunkcountlimit = 10000; + const Sint64 flen = SDL_GetIOSize(src); // this might be -1 if the IOStream can't determine the total size. const char *hint; Sint64 RIFFstart, RIFFend, lastchunkpos; bool RIFFlengthknown = false; @@ -1852,7 +1853,7 @@ static bool WaveLoad(SDL_IOStream *src, WaveFile *file, SDL_AudioSpec *spec, Uin /* Step through all chunks and save information on the fmt, data, and fact * chunks. Ignore the chunks we don't know as per specification. This - * currently also ignores cue, list, and slnt chunks. + * currently also ignores cue, list, and inst chunks. */ while ((Uint64)RIFFend > (Uint64)chunk->position + chunk->length + (chunk->length & 1)) { // Abort after too many chunks or else corrupt files may waste time. @@ -1883,6 +1884,14 @@ static bool WaveLoad(SDL_IOStream *src, WaveFile *file, SDL_AudioSpec *spec, Uin fmtchunk = *chunk; } } else if (chunk->fourcc == DATA) { + /* If the data chunk is bigger than the file, it might be corrupt + or the file is truncated. Try to recover by clamping the file + size. This also means a malicious file can't allocate 4 gigabytes + for the chunks without actually supplying a 4 gigabyte file. */ + if ((flen > 0) && ((chunk->position + chunk->length) > flen)) { + chunk->length = (Uint32) (flen - chunk->position); + } + /* Only use the first data chunk. Handling the wavl list madness * may require a different approach. */ @@ -2092,16 +2101,19 @@ bool SDL_LoadWAV_IO(SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 } // Make sure we are passed a valid data source - if (!src) { + CHECK_PARAM(!src) { SDL_InvalidParamError("src"); goto done; - } else if (!spec) { + } + CHECK_PARAM(!spec) { SDL_InvalidParamError("spec"); goto done; - } else if (!audio_buf) { + } + CHECK_PARAM(!audio_buf) { SDL_InvalidParamError("audio_buf"); goto done; - } else if (!audio_len) { + } + CHECK_PARAM(!audio_len) { SDL_InvalidParamError("audio_len"); goto done; } diff --git a/libs/SDL3/src/audio/SDL_wave.h b/libs/SDL3/src/audio/SDL_wave.h index 0ec8965..77fb9a4 100644 --- a/libs/SDL3/src/audio/SDL_wave.h +++ b/libs/SDL3/src/audio/SDL_wave.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/aaudio/SDL_aaudio.c b/libs/SDL3/src/audio/aaudio/SDL_aaudio.c index 5436be0..bc0e7a2 100644 --- a/libs/SDL3/src/audio/aaudio/SDL_aaudio.c +++ b/libs/SDL3/src/audio/aaudio/SDL_aaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -53,6 +53,13 @@ struct SDL_PrivateAudioData #define LIB_AAUDIO_SO "libaaudio.so" +SDL_ELF_NOTE_DLOPEN( + "audio-aaudio", + "Support for audio through AAudio", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + LIB_AAUDIO_SO +) + typedef struct AAUDIO_Data { SDL_SharedObject *handle; @@ -65,11 +72,16 @@ static bool AAUDIO_LoadFunctions(AAUDIO_Data *data) { #define SDL_PROC(ret, func, params) \ do { \ - data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \ + data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \ if (!data->func) { \ return SDL_SetError("Couldn't load AAUDIO function %s: %s", #func, SDL_GetError()); \ } \ } while (0); + +#define SDL_PROC_OPTIONAL(ret, func, params) \ + do { \ + data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); /* if it fails, okay. */ \ + } while (0); #include "SDL_aaudiofuncs.h" return true; } @@ -253,7 +265,7 @@ static int AAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen static void AAUDIO_CloseDevice(SDL_AudioDevice *device) { struct SDL_PrivateAudioData *hidden = device->hidden; - LOGI(__func__); + LOGI(SDL_FUNCTION); if (hidden) { if (hidden->stream) { @@ -308,12 +320,16 @@ 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 - } + + int32_t sample_frames; + if (SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES)) { + sample_frames = device->sample_frames; + } else { + // Use 20 ms for the default audio buffer size + sample_frames = (device->spec.freq / 50); + } + ctx.AAudioStreamBuilder_setBufferCapacityInFrames(builder, 2 * sample_frames); // AAudio requires that the buffer capacity is at least + ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, 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); @@ -327,6 +343,12 @@ static bool BuildAAudioStream(SDL_AudioDevice *device) SDL_Log("Low latency audio disabled"); } + if (recording && ctx.AAudioStreamBuilder_setInputPreset) { // optional API: requires Android 28 + // try to use a microphone that is for recording external audio. Otherwise Android might choose the mic used for talking + // on the telephone when held to the user's ear, which is often not useful at any distance from the device. + ctx.AAudioStreamBuilder_setInputPreset(builder, AAUDIO_INPUT_PRESET_CAMCORDER); + } + LOGI("AAudio Try to open %u hz %s %u channels samples %u", device->spec.freq, SDL_GetAudioFormatName(device->spec.format), device->spec.channels, device->sample_frames); @@ -335,7 +357,7 @@ static bool BuildAAudioStream(SDL_AudioDevice *device) if (res != AAUDIO_OK) { LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res); ctx.AAudioStreamBuilder_delete(builder); - return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); + return SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res)); } ctx.AAudioStreamBuilder_delete(builder); @@ -385,7 +407,7 @@ static bool BuildAAudioStream(SDL_AudioDevice *device) res = ctx.AAudioStream_requestStart(hidden->stream); if (res != AAUDIO_OK) { LOGI("SDL Failed AAudioStream_requestStart %d recording:%d", res, recording); - return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); + return SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res)); } LOGI("SDL AAudioStream_requestStart OK"); @@ -405,7 +427,7 @@ static bool AAUDIO_OpenDevice(SDL_AudioDevice *device) SDL_assert(device->handle); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero. #endif - LOGI(__func__); + LOGI(SDL_FUNCTION); if (device->recording) { // !!! FIXME: make this non-blocking! @@ -449,7 +471,7 @@ static bool PauseOneDevice(SDL_AudioDevice *device, void *userdata) if (res != AAUDIO_OK) { LOGI("SDL Failed AAudioStream_requestPause %d", res); - SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); + SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res)); } } } @@ -473,7 +495,7 @@ static bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata) aaudio_result_t res = ctx.AAudioStream_requestStart(hidden->stream); if (res != AAUDIO_OK) { LOGI("SDL Failed AAudioStream_requestStart %d", res); - SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); + SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res)); } } } @@ -491,7 +513,7 @@ static void AAUDIO_Deinitialize(void) { Android_StopAudioHotplug(); - LOGI(__func__); + LOGI(SDL_FUNCTION); if (ctx.handle) { SDL_UnloadObject(ctx.handle); } @@ -502,7 +524,7 @@ static void AAUDIO_Deinitialize(void) static bool AAUDIO_Init(SDL_AudioDriverImpl *impl) { - LOGI(__func__); + LOGI(SDL_FUNCTION); /* AAudio was introduced in Android 8.0, but has reference counting crash issues in that release, * so don't use it until 8.1. diff --git a/libs/SDL3/src/audio/aaudio/SDL_aaudio.h b/libs/SDL3/src/audio/aaudio/SDL_aaudio.h index 5326bad..8005f77 100644 --- a/libs/SDL3/src/audio/aaudio/SDL_aaudio.h +++ b/libs/SDL3/src/audio/aaudio/SDL_aaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h b/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h index 1d9f710..2cea7f6 100644 --- a/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h +++ b/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright , (C) 1997-2025 Sam Lantinga + Copyright , (C) 1997-2026 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 @@ -19,6 +19,10 @@ 3. This notice may not be removed or altered from any source distribution. */ +#ifndef SDL_PROC_OPTIONAL +#define SDL_PROC_OPTIONAL(ret, func, params) SDL_PROC(ret, func, params) +#endif + #define SDL_PROC_UNUSED(ret, func, params) SDL_PROC(const char *, AAudio_convertResultToText, (aaudio_result_t returnCode)) @@ -35,7 +39,7 @@ SDL_PROC(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuild 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 -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) // API 28 +SDL_PROC_OPTIONAL(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) // API 28 SDL_PROC_UNUSED(void, AAudioStreamBuilder_setAllowedCapturePolicy, (AAudioStreamBuilder * builder, aaudio_allowed_capture_policy_t capturePolicy)) // API 29 SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSessionId, (AAudioStreamBuilder * builder, aaudio_session_id_t sessionId)) // API 28 SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPrivacySensitive, (AAudioStreamBuilder * builder, bool privacySensitive)) // API 30 @@ -80,3 +84,4 @@ SDL_PROC_UNUSED(bool, AAudioStream_isPrivacySensitive, (AAudioStream * stream)) #undef SDL_PROC #undef SDL_PROC_UNUSED +#undef SDL_PROC_OPTIONAL diff --git a/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c b/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c index cb8c907..ba9e103 100644 --- a/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c +++ b/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -38,6 +38,7 @@ #include "../SDL_sysaudio.h" #include "SDL_alsa_audio.h" +#include "../../core/linux/SDL_udev.h" #if SDL_ALSA_DEBUG #define LOGDEBUG(...) SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, "ALSA: " __VA_ARGS__) @@ -81,13 +82,14 @@ static int (*ALSA_snd_pcm_nonblock)(snd_pcm_t *, int); static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int); static int (*ALSA_snd_pcm_sw_params_set_avail_min)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t); static int (*ALSA_snd_pcm_reset)(snd_pcm_t *); +static snd_pcm_state_t (*ALSA_snd_pcm_state)(snd_pcm_t *); static int (*ALSA_snd_device_name_hint)(int, const char *, void ***); static char *(*ALSA_snd_device_name_get_hint)(const void *, const char *); static int (*ALSA_snd_device_name_free_hint)(void **); static snd_pcm_sframes_t (*ALSA_snd_pcm_avail)(snd_pcm_t *); static size_t (*ALSA_snd_ctl_card_info_sizeof)(void); static size_t (*ALSA_snd_pcm_info_sizeof)(void); -static int (*ALSA_snd_card_next)(int*); +static int (*ALSA_snd_card_next)(int *); static int (*ALSA_snd_ctl_open)(snd_ctl_t **,const char *,int); static int (*ALSA_snd_ctl_close)(snd_ctl_t *); static int (*ALSA_snd_ctl_card_info)(snd_ctl_t *, snd_ctl_card_info_t *); @@ -97,7 +99,6 @@ static void (*ALSA_snd_pcm_info_set_device)(snd_pcm_info_t *, unsigned int); static void (*ALSA_snd_pcm_info_set_subdevice)(snd_pcm_info_t *, unsigned int); static void (*ALSA_snd_pcm_info_set_stream)(snd_pcm_info_t *, snd_pcm_stream_t); static int (*ALSA_snd_ctl_pcm_info)(snd_ctl_t *, snd_pcm_info_t *); -static unsigned int (*ALSA_snd_pcm_info_get_subdevices_count)(const snd_pcm_info_t *); static const char *(*ALSA_snd_ctl_card_info_get_id)(const snd_ctl_card_info_t *); static const char *(*ALSA_snd_pcm_info_get_name)(const snd_pcm_info_t *); static const char *(*ALSA_snd_pcm_info_get_subdevice_name)(const snd_pcm_info_t *); @@ -171,6 +172,7 @@ static bool load_alsa_syms(void) SDL_ALSA_SYM(snd_pcm_wait); SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min); SDL_ALSA_SYM(snd_pcm_reset); + SDL_ALSA_SYM(snd_pcm_state); SDL_ALSA_SYM(snd_device_name_hint); SDL_ALSA_SYM(snd_device_name_get_hint); SDL_ALSA_SYM(snd_device_name_free_hint); @@ -207,6 +209,13 @@ static bool load_alsa_syms(void) #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libalsa", + "Support for audio through libalsa", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_ALSA_DYNAMIC +) + static void UnloadALSALibrary(void) { if (alsa_handle) { @@ -345,31 +354,42 @@ static char *get_pcm_str(void *handle) return pcm_str; } +static int RecoverALSADevice(snd_pcm_t *pcm, int errnum) +{ + const snd_pcm_state_t prerecovery = ALSA_snd_pcm_state(pcm); + const int status = ALSA_snd_pcm_recover(pcm, errnum, 0); // !!! FIXME: third parameter is non-zero to prevent libasound from printing error messages. Should we do that? + if (status == 0) { + const snd_pcm_state_t postrecovery = ALSA_snd_pcm_state(pcm); + if ((prerecovery == SND_PCM_STATE_XRUN) && (postrecovery == SND_PCM_STATE_PREPARED)) { + ALSA_snd_pcm_start(pcm); // restart the device if it stopped due to an overrun or underrun. + } + } + return status; +} + + // This function waits until it is possible to write a full sound buffer static bool ALSA_WaitDevice(SDL_AudioDevice *device) { - const int fulldelay = (int) ((((Uint64) device->sample_frames) * 1000) / device->spec.freq); - const int delay = SDL_max(fulldelay, 10); + const int sample_frames = device->sample_frames; + const int fulldelay = (int) ((((Uint64) sample_frames) * 1000) / device->spec.freq); + const int delay = SDL_clamp(fulldelay, 1, 5); while (!SDL_GetAtomicInt(&device->shutdown)) { - const int rc = ALSA_snd_pcm_wait(device->hidden->pcm, delay); - if (rc < 0 && (rc != -EAGAIN)) { - const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0); + const int rc = ALSA_snd_pcm_avail(device->hidden->pcm); + if (rc < 0) { + const int status = RecoverALSADevice(device->hidden->pcm, rc); if (status < 0) { // Hmm, not much we can do - abort - SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA: snd_pcm_wait failed (unrecoverable): %s", ALSA_snd_strerror(rc)); + SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA wait failed (unrecoverable): %s", ALSA_snd_strerror(rc)); return false; } - continue; } - - if (rc > 0) { - break; // ready to go! + if (rc >= sample_frames) { + break; } - - // Timed out! Make sure we aren't shutting down and then wait again. + SDL_Delay(delay); } - return true; } @@ -386,7 +406,7 @@ static bool ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int bu SDL_assert(rc != 0); // assuming this can't happen if we used snd_pcm_wait and queried for available space. if (rc < 0) { SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it! - const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0); + const int status = RecoverALSADevice(device->hidden->pcm, rc); if (status < 0) { // Hmm, not much we can do - abort SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA write failed (unrecoverable): %s", ALSA_snd_strerror(rc)); @@ -431,14 +451,17 @@ static int ALSA_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen) SDL_assert((buflen % frame_size) == 0); const snd_pcm_sframes_t total_available = ALSA_snd_pcm_avail(device->hidden->pcm); - const int total_frames = SDL_min(buflen / frame_size, total_available); + if (total_available == 0) { + return 0; // go back to WaitDevice and try again. + } + const int total_frames = SDL_min(buflen / frame_size, total_available); const int rc = ALSA_snd_pcm_readi(device->hidden->pcm, buffer, total_frames); SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it! if (rc < 0) { - const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0); + const int status = RecoverALSADevice(device->hidden->pcm, rc); if (status < 0) { // Hmm, not much we can do - abort SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA read failed (unrecoverable): %s", ALSA_snd_strerror(rc)); @@ -461,8 +484,6 @@ static void ALSA_CloseDevice(SDL_AudioDevice *device) { if (device->hidden) { if (device->hidden->pcm) { - // Wait for the submitted audio to drain. ALSA_snd_pcm_drop() can hang, so don't use that. - SDL_Delay(((device->sample_frames * 1000) / device->spec.freq) * 2); ALSA_snd_pcm_close(device->hidden->pcm); } SDL_free(device->hidden->mixbuf); @@ -651,7 +672,7 @@ static void swizzle_map_compute_alsa_subscan(const struct ALSA_pcm_cfg_ctx *ctx, } } -// XXX: this must stay playback/recording symetric. +// XXX: this must stay playback/recording symmetric. static void swizzle_map_compute(const struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, bool *needs_swizzle) { *needs_swizzle = false; @@ -671,7 +692,7 @@ static void swizzle_map_compute(const struct ALSA_pcm_cfg_ctx *ctx, int *swizzle 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); + snd_pcm_chmap_t *chmap_to_install = (snd_pcm_chmap_t *)SDL_small_alloc(unsigned int, 1 + ctx->chans_n, &isstack); if (!chmap_to_install) { return -1; } @@ -1000,7 +1021,7 @@ static int ALSA_pcm_cfg_hw_chans_n_scan(struct ALSA_pcm_cfg_ctx *ctx, unsigned i SDL_SetError("ALSA: Couldn't set the period size: %s", ALSA_snd_strerror(status)); return -1; } - // let approximate the minimun number of periods per buffer (we target a double buffer) + // let approximate the minimum number of periods per buffer (we target a double buffer) ctx->periods = 2; status = ALSA_snd_pcm_hw_params_set_periods_min(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->periods), NULL); if (status < 0) { @@ -1068,7 +1089,7 @@ static bool ALSA_pcm_cfg_hw(struct ALSA_pcm_cfg_ctx *ctx) } // Here, status == CHANS_N_NOT_CONFIGURED - return SDL_SetError("ALSA: Coudn't configure targetting any SDL supported channel number"); + return SDL_SetError("ALSA: Couldn't configure targeting any SDL supported channel number"); } #undef CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N #undef CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N @@ -1149,7 +1170,7 @@ static bool ALSA_OpenDevice(SDL_AudioDevice *device) goto err_close_pcm; } - // from here, we get only the alsa chmap queries in cfg_ctx to explicitely clean, hwparams is + // from here, we get only the alsa chmap queries in cfg_ctx to explicitly clean, hwparams is // uninstalled upon pcm closing // This is useful for debugging @@ -1215,7 +1236,7 @@ static int hotplug_device_process(snd_ctl_t *ctl, snd_ctl_card_info_t *ctl_card_ unsigned int subdev_idx = 0; const bool recording = direction == SND_PCM_STREAM_CAPTURE ? true : false; // used for the unicity of the device bool isstack; - snd_pcm_info_t *pcm_info = (snd_pcm_info_t*)SDL_small_alloc(Uint8, ALSA_snd_pcm_info_sizeof(), &isstack); + snd_pcm_info_t *pcm_info = (snd_pcm_info_t *)SDL_small_alloc(Uint8, ALSA_snd_pcm_info_sizeof(), &isstack); SDL_memset(pcm_info, 0, ALSA_snd_pcm_info_sizeof()); while (true) { @@ -1445,6 +1466,65 @@ static int SDLCALL ALSA_HotplugThread(void *arg) } #endif +#ifdef SDL_USE_LIBUDEV + +static bool udev_initialized; + +static void ALSA_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath) +{ + if (!devpath) { + return; + } + + switch (udev_type) { + case SDL_UDEV_DEVICEADDED: + ALSA_HotplugIteration(NULL, NULL); + break; + + case SDL_UDEV_DEVICEREMOVED: + ALSA_HotplugIteration(NULL, NULL); + break; + + default: + break; + } +} + +static bool ALSA_start_udev(void) +{ + udev_initialized = SDL_UDEV_Init(); + if (udev_initialized) { + // Set up the udev callback + if (!SDL_UDEV_AddCallback(ALSA_udev_callback)) { + SDL_UDEV_Quit(); + udev_initialized = false; + } + } + return udev_initialized; +} + +static void ALSA_stop_udev(void) +{ + if (udev_initialized) { + SDL_UDEV_DelCallback(ALSA_udev_callback); + SDL_UDEV_Quit(); + udev_initialized = false; + } +} + +#else + +static bool ALSA_start_udev(void) +{ + return false; +} + +static void ALSA_stop_udev(void) +{ +} + +#endif // SDL_USE_LIBUDEV + static void ALSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) { ALSA_guess_device_prefix(); @@ -1454,17 +1534,19 @@ static void ALSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevi bool has_default_playback = false, has_default_recording = false; ALSA_HotplugIteration(&has_default_playback, &has_default_recording); // run once now before a thread continues to check. if (has_default_playback) { - *default_playback = SDL_AddAudioDevice(/*recording=*/false, "ALSA default playback device", NULL, (void*)&default_playback_handle); + *default_playback = SDL_AddAudioDevice(/*recording=*/false, "ALSA default playback device", NULL, (void *)&default_playback_handle); } if (has_default_recording) { - *default_recording = SDL_AddAudioDevice(/*recording=*/true, "ALSA default recording device", NULL, (void*)&default_recording_handle); + *default_recording = SDL_AddAudioDevice(/*recording=*/true, "ALSA default recording device", NULL, (void *)&default_recording_handle); } + if (!ALSA_start_udev()) { #if SDL_ALSA_HOTPLUG_THREAD - SDL_SetAtomicInt(&ALSA_hotplug_shutdown, 0); - ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", NULL); - // if the thread doesn't spin, oh well, you just don't get further hotplug events. + SDL_SetAtomicInt(&ALSA_hotplug_shutdown, 0); + ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", NULL); + // if the thread doesn't spin, oh well, you just don't get further hotplug events. #endif + } } static void ALSA_DeinitializeStart(void) @@ -1479,6 +1561,7 @@ static void ALSA_DeinitializeStart(void) ALSA_hotplug_thread = NULL; } #endif + ALSA_stop_udev(); // Shutting down! Clean up any data we've gathered. for (dev = hotplug_devices; dev; dev = next) { diff --git a/libs/SDL3/src/audio/alsa/SDL_alsa_audio.h b/libs/SDL3/src/audio/alsa/SDL_alsa_audio.h index c68dda8..b90299d 100644 --- a/libs/SDL3/src/audio/alsa/SDL_alsa_audio.h +++ b/libs/SDL3/src/audio/alsa/SDL_alsa_audio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.h b/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.h index f117c09..0599146 100644 --- a/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.h +++ b/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m b/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m index 57b19c7..dda5f57 100644 --- a/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m +++ b/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -341,7 +341,7 @@ static void ResumeAudioDevices(void) static void InterruptionBegin(SDL_AudioDevice *device) { - if (device != NULL && device->hidden->audioQueue != NULL) { + if (device != NULL && device->hidden != NULL && device->hidden->audioQueue != NULL) { device->hidden->interrupted = true; AudioQueuePause(device->hidden->audioQueue); } @@ -366,7 +366,7 @@ static void InterruptionEnd(SDL_AudioDevice *device) { @synchronized(self) { NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey]; - if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) { + if (type && (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan)) { InterruptionBegin(self.device); } else { InterruptionEnd(self.device); @@ -420,7 +420,8 @@ static bool UpdateAudioSession(SDL_AudioDevice *device, bool open, bool allow_pl hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY); if (hint) { - if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) { + if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0 || + SDL_strcasecmp(hint, "ambient") == 0) { category = AVAudioSessionCategoryAmbient; } else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) { category = AVAudioSessionCategorySoloAmbient; diff --git a/libs/SDL3/src/audio/directsound/SDL_directsound.c b/libs/SDL3/src/audio/directsound/SDL_directsound.c index ab4dd0c..3a097a3 100644 --- a/libs/SDL3/src/audio/directsound/SDL_directsound.c +++ b/libs/SDL3/src/audio/directsound/SDL_directsound.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -40,16 +40,16 @@ static bool SupportsIMMDevice = false; // DirectX function pointers for audio static SDL_SharedObject *DSoundDLL = NULL; -typedef HRESULT(WINAPI *fnDirectSoundCreate8)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN); -typedef HRESULT(WINAPI *fnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID); -typedef HRESULT(WINAPI *fnDirectSoundCaptureCreate8)(LPCGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN); -typedef HRESULT(WINAPI *fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID); -typedef HRESULT(WINAPI *fnGetDeviceID)(LPCGUID, LPGUID); -static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL; -static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL; -static fnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL; -static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL; -static fnGetDeviceID pGetDeviceID = NULL; +typedef HRESULT (WINAPI *pfnDirectSoundCreate8)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN); +typedef HRESULT (WINAPI *pfnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID); +typedef HRESULT (WINAPI *pfnDirectSoundCaptureCreate8)(LPCGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN); +typedef HRESULT (WINAPI *pfnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID); +typedef HRESULT (WINAPI *pfnGetDeviceID)(LPCGUID, LPGUID); +static pfnDirectSoundCreate8 pDirectSoundCreate8 = NULL; +static pfnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL; +static pfnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL; +static pfnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL; +static pfnGetDeviceID pGetDeviceID = NULL; #include DEFINE_GUID(SDL_DSDEVID_DefaultPlayback, 0xdef00000, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03); @@ -85,7 +85,7 @@ static bool DSOUND_Load(void) // Now make sure we have DirectX 8 or better... #define DSOUNDLOAD(f) \ { \ - p##f = (fn##f)SDL_LoadFunction(DSoundDLL, #f); \ + p##f = (pfn##f)SDL_LoadFunction(DSoundDLL, #f); \ if (!p##f) \ loaded = false; \ } @@ -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_AUDIO_UNKNOWN); + SDL_IMMDevice_EnumerateEndpoints(default_playback, default_recording, SDL_AUDIO_UNKNOWN, false); } else #endif { diff --git a/libs/SDL3/src/audio/directsound/SDL_directsound.h b/libs/SDL3/src/audio/directsound/SDL_directsound.h index a4fa2fa..754d5a0 100644 --- a/libs/SDL3/src/audio/directsound/SDL_directsound.h +++ b/libs/SDL3/src/audio/directsound/SDL_directsound.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/disk/SDL_diskaudio.c b/libs/SDL3/src/audio/disk/SDL_diskaudio.c index 9e05478..d6c214e 100644 --- a/libs/SDL3/src/audio/disk/SDL_diskaudio.c +++ b/libs/SDL3/src/audio/disk/SDL_diskaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -100,6 +100,16 @@ static const char *get_filename(const bool recording) return devname; } +static const char *AudioFormatString(SDL_AudioFormat fmt) +{ + const char *str = SDL_GetAudioFormatName(fmt); + SDL_assert(str); + if (SDL_strncmp(str, "SDL_AUDIO_", 10) == 0) { + str += 10; // so we return "S8" instead of "SDL_AUDIO_S8", etc. + } + return str; +} + static bool DISKAUDIO_OpenDevice(SDL_AudioDevice *device) { bool recording = device->recording; @@ -136,7 +146,9 @@ static bool DISKAUDIO_OpenDevice(SDL_AudioDevice *device) } SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, "You are using the SDL disk i/o audio driver!"); - SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, " %s file [%s].", recording ? "Reading from" : "Writing to", fname); + SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, " %s file [%s], format=%s channels=%d freq=%d.", + recording ? "Reading from" : "Writing to", fname, + AudioFormatString(device->spec.format), device->spec.channels, device->spec.freq); return true; // We're ready to rock and roll. :-) } diff --git a/libs/SDL3/src/audio/disk/SDL_diskaudio.h b/libs/SDL3/src/audio/disk/SDL_diskaudio.h index d8d9980..a5be571 100644 --- a/libs/SDL3/src/audio/disk/SDL_diskaudio.h +++ b/libs/SDL3/src/audio/disk/SDL_diskaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/dsp/SDL_dspaudio.c b/libs/SDL3/src/audio/dsp/SDL_dspaudio.c index 62b8990..ef274c1 100644 --- a/libs/SDL3/src/audio/dsp/SDL_dspaudio.c +++ b/libs/SDL3/src/audio/dsp/SDL_dspaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/dsp/SDL_dspaudio.h b/libs/SDL3/src/audio/dsp/SDL_dspaudio.h index 65dea60..5641284 100644 --- a/libs/SDL3/src/audio/dsp/SDL_dspaudio.h +++ b/libs/SDL3/src/audio/dsp/SDL_dspaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/dummy/SDL_dummyaudio.c b/libs/SDL3/src/audio/dummy/SDL_dummyaudio.c index d0f1a1a..74a628b 100644 --- a/libs/SDL3/src/audio/dummy/SDL_dummyaudio.c +++ b/libs/SDL3/src/audio/dummy/SDL_dummyaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -25,7 +25,7 @@ #include "../SDL_sysaudio.h" #include "SDL_dummyaudio.h" -#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__) +#if defined(SDL_PLATFORM_EMSCRIPTEN) #include #endif @@ -59,8 +59,8 @@ static bool DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device) } } - // on Emscripten without threads, we just fire a repeating timer to consume audio. - #if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__) + // on Emscripten, we just fire a repeating timer to consume audio. + #if defined(SDL_PLATFORM_EMSCRIPTEN) MAIN_THREAD_EM_ASM({ var a = Module['SDL3'].dummy_audio; if (a.timers[$0] !== undefined) { clearInterval(a.timers[$0]); } @@ -74,8 +74,8 @@ static bool DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device) static void DUMMYAUDIO_CloseDevice(SDL_AudioDevice *device) { if (device->hidden) { - // on Emscripten without threads, we just fire a repeating timer to consume audio. - #if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__) + // on Emscripten, we just fire a repeating timer to consume audio. + #if defined(SDL_PLATFORM_EMSCRIPTEN) MAIN_THREAD_EM_ASM({ var a = Module['SDL3'].dummy_audio; if (a.timers[$0] !== undefined) { clearInterval(a.timers[$0]); } @@ -113,12 +113,9 @@ static bool DUMMYAUDIO_Init(SDL_AudioDriverImpl *impl) impl->OnlyHasDefaultRecordingDevice = true; impl->HasRecordingSupport = true; - // on Emscripten without threads, we just fire a repeating timer to consume audio. - #if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__) + // on Emscripten, we just fire a repeating timer to consume audio. + #if defined(SDL_PLATFORM_EMSCRIPTEN) MAIN_THREAD_EM_ASM({ - if (typeof(Module['SDL3']) === 'undefined') { - Module['SDL3'] = {}; - } Module['SDL3'].dummy_audio = {}; Module['SDL3'].dummy_audio.timers = []; Module['SDL3'].dummy_audio.timers[0] = undefined; diff --git a/libs/SDL3/src/audio/dummy/SDL_dummyaudio.h b/libs/SDL3/src/audio/dummy/SDL_dummyaudio.h index d5e75c7..a122b81 100644 --- a/libs/SDL3/src/audio/dummy/SDL_dummyaudio.h +++ b/libs/SDL3/src/audio/dummy/SDL_dummyaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c b/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c index 46b8b76..4087a5e 100644 --- a/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c +++ b/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -41,13 +41,8 @@ static bool EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buf const int framelen = SDL_AUDIO_FRAMESIZE(device->spec); MAIN_THREAD_EM_ASM({ /* Convert incoming buf pointer to a HEAPF32 offset. */ - #ifdef __wasm64__ - var buf = $0 / 4; - #else - var buf = $0 >>> 2; - #endif - var SDL3 = Module['SDL3']; + var buf = SDL3.CPtrToHeap32Index($0); var numChannels = SDL3.audio_playback.currentPlaybackBuffer['numberOfChannels']; for (var c = 0; c < numChannels; ++c) { var channelData = SDL3.audio_playback.currentPlaybackBuffer['getChannelData'](c); @@ -56,7 +51,7 @@ static bool EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buf } for (var j = 0; j < $1; ++j) { - channelData[j] = HEAPF32[buf + (j*numChannels + c)]; + channelData[j] = HEAPF32[buf + (j * numChannels + c)]; } } }, buffer, buffer_size / framelen); @@ -151,13 +146,11 @@ static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device) // create context const bool result = MAIN_THREAD_EM_ASM_INT({ - if (typeof(Module['SDL3']) === 'undefined') { - Module['SDL3'] = {}; - } var SDL3 = Module['SDL3']; - if (!$0) { + if (typeof(SDL3.audio_playback) === 'undefined') { SDL3.audio_playback = {}; - } else { + } + if (typeof(SDL3.audio_recording) === 'undefined') { SDL3.audio_recording = {}; } @@ -174,7 +167,7 @@ static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device) } } return (SDL3.audioContext !== undefined); - }, device->recording); + }); if (!result) { return SDL_SetError("Web Audio API is not available!"); diff --git a/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.h b/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.h index aaf761b..a178d92 100644 --- a/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.h +++ b/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/haiku/SDL_haikuaudio.cc b/libs/SDL3/src/audio/haiku/SDL_haikuaudio.cc index 730b107..55b6600 100644 --- a/libs/SDL3/src/audio/haiku/SDL_haikuaudio.cc +++ b/libs/SDL3/src/audio/haiku/SDL_haikuaudio.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/haiku/SDL_haikuaudio.h b/libs/SDL3/src/audio/haiku/SDL_haikuaudio.h index 9547546..45ab42e 100644 --- a/libs/SDL3/src/audio/haiku/SDL_haikuaudio.h +++ b/libs/SDL3/src/audio/haiku/SDL_haikuaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/jack/SDL_jackaudio.c b/libs/SDL3/src/audio/jack/SDL_jackaudio.c index 3ae5137..a5237da 100644 --- a/libs/SDL3/src/audio/jack/SDL_jackaudio.c +++ b/libs/SDL3/src/audio/jack/SDL_jackaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -50,6 +50,13 @@ static bool load_jack_syms(void); #ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libjack", + "Support for audio through libjack", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_JACK_DYNAMIC +) + static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC; static SDL_SharedObject *jack_handle = NULL; diff --git a/libs/SDL3/src/audio/jack/SDL_jackaudio.h b/libs/SDL3/src/audio/jack/SDL_jackaudio.h index 4f972d5..1ef7099 100644 --- a/libs/SDL3/src/audio/jack/SDL_jackaudio.h +++ b/libs/SDL3/src/audio/jack/SDL_jackaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.c b/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.c index 780b06c..ded98ea 100644 --- a/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.c +++ b/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.h b/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.h index c9ae4f8..dc93e74 100644 --- a/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.h +++ b/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.c b/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.c index 26060d3..b18ab2f 100644 --- a/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.c +++ b/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.h b/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.h index dcdc6f4..bcb7bc7 100644 --- a/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.h +++ b/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/ngage/SDL_ngageaudio.c b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.c new file mode 100644 index 0000000..5b964d8 --- /dev/null +++ b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.c @@ -0,0 +1,103 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_AUDIO_DRIVER_NGAGE + +#include "../SDL_sysaudio.h" +#include "SDL_ngageaudio.h" + +static SDL_AudioDevice *devptr = NULL; + +SDL_AudioDevice *NGAGE_GetAudioDeviceAddr() +{ + return devptr; +} + +static bool NGAGEAUDIO_OpenDevice(SDL_AudioDevice *device) +{ + SDL_PrivateAudioData *phdata = SDL_calloc(1, sizeof(SDL_PrivateAudioData)); + if (!phdata) { + SDL_OutOfMemory(); + return false; + } + device->hidden = phdata; + + phdata->buffer = SDL_calloc(1, device->buffer_size); + if (!phdata->buffer) { + SDL_OutOfMemory(); + SDL_free(phdata); + return false; + } + devptr = device; + + // Since the phone can change the sample rate during a phone call, + // we set the sample rate to 8KHz to be safe. Even though it + // might be possible to adjust the sample rate dynamically, it's + // not supported by the current implementation. + + device->spec.format = SDL_AUDIO_S16LE; + device->spec.channels = 1; + device->spec.freq = 8000; + + SDL_UpdatedAudioDeviceFormat(device); + + return true; +} + +static Uint8 *NGAGEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) +{ + SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden; + if (!phdata) { + *buffer_size = 0; + return 0; + } + + *buffer_size = device->buffer_size; + return phdata->buffer; +} + +static void NGAGEAUDIO_CloseDevice(SDL_AudioDevice *device) +{ + if (device->hidden) { + SDL_free(device->hidden->buffer); + SDL_free(device->hidden); + } + + return; +} + +static bool NGAGEAUDIO_Init(SDL_AudioDriverImpl *impl) +{ + impl->OpenDevice = NGAGEAUDIO_OpenDevice; + impl->GetDeviceBuf = NGAGEAUDIO_GetDeviceBuf; + impl->CloseDevice = NGAGEAUDIO_CloseDevice; + + impl->ProvidesOwnCallbackThread = true; + impl->OnlyHasDefaultPlaybackDevice = true; + + return true; +} + +AudioBootStrap NGAGEAUDIO_bootstrap = { "N-Gage", "N-Gage audio driver", NGAGEAUDIO_Init, false }; + +#endif // SDL_AUDIO_DRIVER_NGAGE diff --git a/libs/SDL3/src/audio/ngage/SDL_ngageaudio.cpp b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.cpp new file mode 100644 index 0000000..aff7ff0 --- /dev/null +++ b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.cpp @@ -0,0 +1,368 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "SDL_ngageaudio.h" +#include "../SDL_sysaudio.h" +#include "SDL_internal.h" + +#ifdef __cplusplus +} +#endif + +#ifdef SDL_AUDIO_DRIVER_NGAGE + +#include "SDL_ngageaudio.hpp" + +CAudio::CAudio() : CActive(EPriorityStandard), iBufDes(NULL, 0) {} + +CAudio *CAudio::NewL(TInt aLatency) +{ + CAudio *self = new (ELeave) CAudio(); + CleanupStack::PushL(self); + self->ConstructL(aLatency); + CleanupStack::Pop(self); + return self; +} + +void CAudio::ConstructL(TInt aLatency) +{ + CActiveScheduler::Add(this); + User::LeaveIfError(iTimer.CreateLocal()); + iTimerCreated = ETrue; + + iStream = CMdaAudioOutputStream::NewL(*this); + if (!iStream) { + SDL_Log("Error: Failed to create audio stream"); + User::Leave(KErrNoMemory); + } + + iLatency = aLatency; + iLatencySamples = aLatency * 8; // 8kHz. + + // Determine minimum and maximum number of samples to write with one + // WriteL request. + iMinWrite = iLatencySamples / 8; + iMaxWrite = iLatencySamples / 2; + + // Set defaults. + iState = EStateNone; + iTimerCreated = EFalse; + iTimerActive = EFalse; +} + +CAudio::~CAudio() +{ + if (iStream) { + iStream->Stop(); + + while (iState != EStateDone) { + User::After(100000); // 100ms. + } + + delete iStream; + } +} + +void CAudio::Start() +{ + if (iStream) { + // Set to 8kHz mono audio. + iStreamSettings.iChannels = TMdaAudioDataSettings::EChannelsMono; + iStreamSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate8000Hz; + iStream->Open(&iStreamSettings); + iState = EStateOpening; + } else { + SDL_Log("Error: Failed to open audio stream"); + } +} + +// Feeds more processed data to the audio stream. +void CAudio::Feed() +{ + // If a WriteL is already in progress, or we aren't even playing; + // do nothing! + if ((iState != EStateWriting) && (iState != EStatePlaying)) { + return; + } + + // Figure out the number of samples that really have been played + // through the output. + TTimeIntervalMicroSeconds pos = iStream->Position(); + + TInt played = 8 * (pos.Int64() / TInt64(1000)).GetTInt(); // 8kHz. + + played += iBaseSamplesPlayed; + + // Determine the difference between the number of samples written to + // CMdaAudioOutputStream and the number of samples it has played. + // The difference is the amount of data in the buffers. + if (played < 0) { + played = 0; + } + + TInt buffered = iSamplesWritten - played; + if (buffered < 0) { + buffered = 0; + } + + if (iState == EStateWriting) { + return; + } + + // The trick for low latency: Do not let the buffers fill up beyond the + // latency desired! We write as many samples as the difference between + // the latency target (in samples) and the amount of data buffered. + TInt samplesToWrite = iLatencySamples - buffered; + + // Do not write very small blocks. This should improve efficiency, since + // writes to the streaming API are likely to be expensive. + if (samplesToWrite < iMinWrite) { + // Not enough data to write, set up a timer to fire after a while. + // Try againwhen it expired. + if (iTimerActive) { + return; + } + iTimerActive = ETrue; + SetActive(); + iTimer.After(iStatus, (1000 * iLatency) / 8); + return; + } + + // Do not write more than the set number of samples at once. + int numSamples = samplesToWrite; + if (numSamples > iMaxWrite) { + numSamples = iMaxWrite; + } + + SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr(); + if (device) { + SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden; + + iBufDes.Set(phdata->buffer, 2 * numSamples, 2 * numSamples); + iStream->WriteL(iBufDes); + iState = EStateWriting; + + // Keep track of the number of samples written (for latency calculations). + iSamplesWritten += numSamples; + } else { + // Output device not ready yet. Let's go for another round. + if (iTimerActive) { + return; + } + iTimerActive = ETrue; + SetActive(); + iTimer.After(iStatus, (1000 * iLatency) / 8); + } +} + +void CAudio::RunL() +{ + iTimerActive = EFalse; + Feed(); +} + +void CAudio::DoCancel() +{ + iTimerActive = EFalse; + iTimer.Cancel(); +} + +void CAudio::StartThread() +{ + TInt heapMinSize = 8192; // 8 KB initial heap size. + TInt heapMaxSize = 1024 * 1024; // 1 MB maximum heap size. + + TInt err = iProcess.Create(_L("ProcessThread"), ProcessThreadCB, KDefaultStackSize * 2, heapMinSize, heapMaxSize, this); + if (err == KErrNone) { + iProcess.SetPriority(EPriorityLess); + iProcess.Resume(); + } else { + SDL_Log("Error: Failed to create audio processing thread: %d", err); + } +} + +void CAudio::StopThread() +{ + if (iStreamStarted) { + iProcess.Kill(KErrNone); + iProcess.Close(); + iStreamStarted = EFalse; + } +} + +TInt CAudio::ProcessThreadCB(TAny *aPtr) +{ + CAudio *self = static_cast(aPtr); + SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr(); + + while (self->iStreamStarted) { + if (device) { + SDL_PlaybackAudioThreadIterate(device); + } else { + device = NGAGE_GetAudioDeviceAddr(); + } + User::After(100000); // 100ms. + } + return KErrNone; +} + +void CAudio::MaoscOpenComplete(TInt aError) +{ + if (aError == KErrNone) { + iStream->SetVolume(1); + iStreamStarted = ETrue; + StartThread(); + + } else { + SDL_Log("Error: Failed to open audio stream: %d", aError); + } +} + +void CAudio::MaoscBufferCopied(TInt aError, const TDesC8 & /*aBuffer*/) +{ + if (aError == KErrNone) { + iState = EStatePlaying; + Feed(); + } else if (aError == KErrAbort) { + // The stream has been stopped. + iState = EStateDone; + } else { + SDL_Log("Error: Failed to copy audio buffer: %d", aError); + } +} + +void CAudio::MaoscPlayComplete(TInt aError) +{ + // If we finish due to an underflow, we'll need to restart playback. + // Normally KErrUnderlow is raised at stream end, but in our case the API + // should never see the stream end -- we are continuously feeding it more + // data! Many underflow errors mean that the latency target is too low. + if (aError == KErrUnderflow) { + // The number of samples played gets reset to zero when we restart + // playback after underflow. + iBaseSamplesPlayed = iSamplesWritten; + + iStream->Stop(); + Cancel(); + + iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, TMdaAudioDataSettings::EChannelsMono); + + iState = EStatePlaying; + Feed(); + return; + + } else if (aError != KErrNone) { + // Handle error. + } + + // We shouldn't get here. + SDL_Log("%s: %d", SDL_FUNCTION, aError); +} + +static TBool gAudioRunning; + +TBool AudioIsReady() +{ + return gAudioRunning; +} + +TInt AudioThreadCB(TAny *aParams) +{ + CTrapCleanup *cleanup = CTrapCleanup::New(); + if (!cleanup) { + return KErrNoMemory; + } + + CActiveScheduler *scheduler = new CActiveScheduler(); + if (!scheduler) { + delete cleanup; + return KErrNoMemory; + } + + CActiveScheduler::Install(scheduler); + + TRAPD(err, + { + TInt latency = *(TInt *)aParams; + CAudio *audio = CAudio::NewL(latency); + CleanupStack::PushL(audio); + + gAudioRunning = ETrue; + audio->Start(); + TBool once = EFalse; + + while (gAudioRunning) { + // Allow active scheduler to process any events. + TInt error; + CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle); + + if (!once) { + SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr(); + if (device) { + // Stream ready; start feeding audio data. + // After feeding it once, the callbacks will take over. + audio->iState = CAudio::EStatePlaying; + audio->Feed(); + once = ETrue; + } + } + + User::After(100000); // 100ms. + } + + CleanupStack::PopAndDestroy(audio); + }); + + delete scheduler; + delete cleanup; + return err; +} + +RThread audioThread; + +void InitAudio(TInt *aLatency) +{ + _LIT(KAudioThreadName, "AudioThread"); + + TInt err = audioThread.Create(KAudioThreadName, AudioThreadCB, KDefaultStackSize, 0, aLatency); + if (err != KErrNone) { + User::Leave(err); + } + + audioThread.Resume(); +} + +void DeinitAudio() +{ + gAudioRunning = EFalse; + + TRequestStatus status; + audioThread.Logon(status); + User::WaitForRequest(status); + + audioThread.Close(); +} + +#endif // SDL_AUDIO_DRIVER_NGAGE diff --git a/libs/SDL3/src/video/vita/SDL_vitagl_pvr_c.h b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.h similarity index 67% rename from libs/SDL3/src/video/vita/SDL_vitagl_pvr_c.h rename to libs/SDL3/src/audio/ngage/SDL_ngageaudio.h index 118602d..ee3afed 100644 --- a/libs/SDL3/src/video/vita/SDL_vitagl_pvr_c.h +++ b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -18,14 +18,27 @@ 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_vitagl_pvr_c_h_ -#define SDL_vitagl_pvr_c_h_ +#ifndef SDL_ngageaudio_h +#define SDL_ngageaudio_h -#include "SDL_vitavideo.h" +typedef struct SDL_PrivateAudioData +{ + Uint8 *buffer; -extern SDL_GLContext VITA_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window); -extern int VITA_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path); -extern SDL_FunctionPointer VITA_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc); +} SDL_PrivateAudioData; -#endif // SDL_vitagl_pvr_c_h_ +#ifdef __cplusplus +extern "C" { +#endif + +#include "../SDL_sysaudio.h" + +SDL_AudioDevice *NGAGE_GetAudioDeviceAddr(); + +#ifdef __cplusplus +} +#endif + +#endif // SDL_ngageaudio_h diff --git a/libs/SDL3/src/audio/ngage/SDL_ngageaudio.hpp b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.hpp new file mode 100644 index 0000000..68e714a --- /dev/null +++ b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.hpp @@ -0,0 +1,98 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_ngageaudio_hpp +#define SDL_ngageaudio_hpp + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../SDL_sysaudio.h" +#include "SDL_ngageaudio.h" + +#ifdef __cplusplus +} +#endif + +TBool AudioIsReady(); +void InitAudio(TInt *aLatency); +void DeinitAudio(); + +class CAudio : public CActive, public MMdaAudioOutputStreamCallback +{ + public: + static CAudio *NewL(TInt aLatency); + ~CAudio(); + + void ConstructL(TInt aLatency); + void Start(); + void Feed(); + + void RunL(); + void DoCancel(); + + static TInt ProcessThreadCB(TAny * /*aPtr*/); + + // From MMdaAudioOutputStreamCallback + void MaoscOpenComplete(TInt aError); + void MaoscBufferCopied(TInt aError, const TDesC8 &aBuffer); + void MaoscPlayComplete(TInt aError); + + enum + { + EStateNone = 0, + EStateOpening, + EStatePlaying, + EStateWriting, + EStateDone + } iState; + + private: + CAudio(); + void StartThread(); + void StopThread(); + + CMdaAudioOutputStream *iStream; + TMdaAudioDataSettings iStreamSettings; + TBool iStreamStarted; + + TPtr8 iBufDes; // Descriptor for the buffer. + TInt iLatency; // Latency target in ms + TInt iLatencySamples; // Latency target in samples. + TInt iMinWrite; // Min number of samples to write per turn. + TInt iMaxWrite; // Max number of samples to write per turn. + TInt iBaseSamplesPlayed; // amples played before last restart. + TInt iSamplesWritten; // Number of samples written so far. + + RTimer iTimer; + TBool iTimerCreated; + TBool iTimerActive; + + RThread iProcess; +}; + +#endif // SDL_ngageaudio_hpp \ No newline at end of file diff --git a/libs/SDL3/src/audio/openslES/SDL_openslES.c b/libs/SDL3/src/audio/openslES/SDL_openslES.c index 4d5b3bd..0f49585 100644 --- a/libs/SDL3/src/audio/openslES/SDL_openslES.c +++ b/libs/SDL3/src/audio/openslES/SDL_openslES.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -223,9 +223,7 @@ static void OPENSLES_DestroyPCMRecorder(SDL_AudioDevice *device) audiodata->playsem = NULL; } - if (audiodata->mixbuff) { - SDL_free(audiodata->mixbuff); - } + SDL_free(audiodata->mixbuff); } // !!! FIXME: make this non-blocking! @@ -419,35 +417,38 @@ static void OPENSLES_DestroyPCMPlayer(SDL_AudioDevice *device) audiodata->playsem = NULL; } - if (audiodata->mixbuff) { - SDL_free(audiodata->mixbuff); - } + SDL_free(audiodata->mixbuff); } static bool OPENSLES_CreatePCMPlayer(SDL_AudioDevice *device) { - /* If we want to add floating point audio support (requires API level 21) - it can be done as described here: - https://developer.android.com/ndk/guides/audio/opensl/android-extensions.html#floating-point - */ + /* according to https://developer.android.com/ndk/guides/audio/opensl/opensl-for-android, + Android's OpenSL ES only supports Uint8 and _littleendian_ Sint16. + (and float32, with an extension we use, below.) */ if (SDL_GetAndroidSDKVersion() >= 21) { const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); SDL_AudioFormat test_format; while ((test_format = *(closefmts++)) != 0) { - if (SDL_AUDIO_ISSIGNED(test_format)) { + switch (test_format) { + case SDL_AUDIO_U8: + case SDL_AUDIO_S16LE: + case SDL_AUDIO_F32: break; + default: + continue; } + break; } if (!test_format) { // Didn't find a compatible format : - LOGI("No compatible audio format, using signed 16-bit audio"); - test_format = SDL_AUDIO_S16; + LOGI("No compatible audio format, using signed 16-bit LE audio"); + test_format = SDL_AUDIO_S16LE; } device->spec.format = test_format; } else { // Just go with signed 16-bit audio as it's the most compatible - device->spec.format = SDL_AUDIO_S16; + device->spec.format = SDL_AUDIO_S16LE; } // Update the fragment size as size in bytes diff --git a/libs/SDL3/src/audio/openslES/SDL_openslES.h b/libs/SDL3/src/audio/openslES/SDL_openslES.h index 0ae2664..ca17c4b 100644 --- a/libs/SDL3/src/audio/openslES/SDL_openslES.h +++ b/libs/SDL3/src/audio/openslES/SDL_openslES.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/pipewire/SDL_pipewire.c b/libs/SDL3/src/audio/pipewire/SDL_pipewire.c index 6004feb..ee59ca3 100644 --- a/libs/SDL3/src/audio/pipewire/SDL_pipewire.c +++ b/libs/SDL3/src/audio/pipewire/SDL_pipewire.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -93,6 +93,13 @@ static int (*PIPEWIRE_pw_properties_setf)(struct pw_properties *, const char *, #ifdef SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libpipewire", + "Support for audio through libpipewire", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC +) + static const char *pipewire_library = SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC; static SDL_SharedObject *pipewire_handle = NULL; @@ -266,13 +273,11 @@ static bool pipewire_core_version_at_least(int major, int minor, int patch) static bool io_list_check_add(struct io_node *node) { struct io_node *n; - bool ret = true; // See if the node is already in the list spa_list_for_each (n, &hotplug_io_list, link) { if (n->id == node->id) { - ret = false; - goto dup_found; + return false; } } @@ -283,9 +288,7 @@ static bool io_list_check_add(struct io_node *node) SDL_AddAudioDevice(node->recording, node->name, &node->spec, PW_ID_TO_HANDLE(node->id)); } -dup_found: - - return ret; + return true; } static void io_list_remove(Uint32 id) @@ -550,7 +553,7 @@ static void node_event_info(void *object, const struct pw_node_info *info) // Need to parse the parameters to get the sample rate for (i = 0; i < info->n_params; ++i) { - pw_node_enum_params((struct pw_node*)node->proxy, 0, info->params[i].id, 0, 0, NULL); + pw_node_enum_params((struct pw_node *)node->proxy, 0, info->params[i].id, 0, 0, NULL); } hotplug_core_sync(node); @@ -632,16 +635,12 @@ static int metadata_property(void *object, Uint32 subject, const char *key, cons if (subject == PW_ID_CORE && key && value) { if (!SDL_strcmp(key, "default.audio.sink")) { - if (pipewire_default_sink_id) { - SDL_free(pipewire_default_sink_id); - } + SDL_free(pipewire_default_sink_id); pipewire_default_sink_id = get_name_from_json(value); node->persist = true; change_default_device(pipewire_default_sink_id); } else if (!SDL_strcmp(key, "default.audio.source")) { - if (pipewire_default_source_id) { - SDL_free(pipewire_default_source_id); - } + SDL_free(pipewire_default_source_id); pipewire_default_source_id = get_name_from_json(value); node->persist = true; change_default_device(pipewire_default_source_id); diff --git a/libs/SDL3/src/audio/pipewire/SDL_pipewire.h b/libs/SDL3/src/audio/pipewire/SDL_pipewire.h index e609e25..0e98ec4 100644 --- a/libs/SDL3/src/audio/pipewire/SDL_pipewire.h +++ b/libs/SDL3/src/audio/pipewire/SDL_pipewire.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/ps2/SDL_ps2audio.c b/libs/SDL3/src/audio/ps2/SDL_ps2audio.c index 6579f1b..2100bf0 100644 --- a/libs/SDL3/src/audio/ps2/SDL_ps2audio.c +++ b/libs/SDL3/src/audio/ps2/SDL_ps2audio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/ps2/SDL_ps2audio.h b/libs/SDL3/src/audio/ps2/SDL_ps2audio.h index 5a0ecea..b79e396 100644 --- a/libs/SDL3/src/audio/ps2/SDL_ps2audio.h +++ b/libs/SDL3/src/audio/ps2/SDL_ps2audio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/psp/SDL_pspaudio.c b/libs/SDL3/src/audio/psp/SDL_pspaudio.c index 1b095e9..6ead4ad 100644 --- a/libs/SDL3/src/audio/psp/SDL_pspaudio.c +++ b/libs/SDL3/src/audio/psp/SDL_pspaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/psp/SDL_pspaudio.h b/libs/SDL3/src/audio/psp/SDL_pspaudio.h index d7b2056..3d24cfa 100644 --- a/libs/SDL3/src/audio/psp/SDL_pspaudio.h +++ b/libs/SDL3/src/audio/psp/SDL_pspaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c b/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c index 4d618a6..2fcf078 100644 --- a/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -133,6 +133,13 @@ static bool load_pulseaudio_syms(void); #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libpulseaudio", + "Support for audio through libpulseaudio", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC +) + static const char *pulseaudio_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC; static SDL_SharedObject *pulseaudio_handle = NULL; @@ -595,6 +602,71 @@ static void PulseStreamStateChangeCallback(pa_stream *stream, void *userdata) PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details. } +// Channel maps that match the order in SDL_Audio.h +static const pa_channel_position_t Pulse_map_1[] = { PA_CHANNEL_POSITION_MONO }; +static const pa_channel_position_t Pulse_map_2[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT }; + +static const pa_channel_position_t Pulse_map_3[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_LFE }; + +static const pa_channel_position_t Pulse_map_4[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }; + +static const pa_channel_position_t Pulse_map_5[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }; + +static const pa_channel_position_t Pulse_map_6[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }; + +static const pa_channel_position_t Pulse_map_7[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_REAR_CENTER, + PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT }; + +static const pa_channel_position_t Pulse_map_8[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT }; + +#define COPY_CHANNEL_MAP(c) SDL_memcpy(pacmap->map, Pulse_map_##c, sizeof(Pulse_map_##c)) + +static void PulseCreateChannelMap(pa_channel_map *pacmap, uint8_t channels) +{ + SDL_assert(channels <= PA_CHANNELS_MAX); + + pacmap->channels = channels; + + switch (channels) { + case 1: + COPY_CHANNEL_MAP(1); + break; + case 2: + COPY_CHANNEL_MAP(2); + break; + case 3: + COPY_CHANNEL_MAP(3); + break; + case 4: + COPY_CHANNEL_MAP(4); + break; + case 5: + COPY_CHANNEL_MAP(5); + break; + case 6: + COPY_CHANNEL_MAP(6); + break; + case 7: + COPY_CHANNEL_MAP(7); + break; + case 8: + COPY_CHANNEL_MAP(8); + break; + } + +} + static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) { const bool recording = device->recording; @@ -683,9 +755,8 @@ static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); const char *name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME); - // The SDL ALSA output hints us that we use Windows' channel mapping - // https://bugzilla.libsdl.org/show_bug.cgi?id=110 - PULSEAUDIO_pa_channel_map_init_auto(&pacmap, device->spec.channels, PA_CHANNEL_MAP_WAVEEX); + + PulseCreateChannelMap(&pacmap, device->spec.channels); h->stream = PULSEAUDIO_pa_stream_new( pulseaudio_context, @@ -732,7 +803,7 @@ static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) if (!actual_bufattr) { result = SDL_SetError("Could not determine connected PulseAudio stream's buffer attributes"); } else { - device->buffer_size = (int) recording ? actual_bufattr->tlength : actual_bufattr->fragsize; + device->buffer_size = (int) recording ? actual_bufattr->fragsize : actual_bufattr->tlength; device->sample_frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec); } } diff --git a/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.h b/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.h index 9ea44c0..2c7e6b2 100644 --- a/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.h +++ b/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/qnx/SDL_qsa_audio.c b/libs/SDL3/src/audio/qnx/SDL_qsa_audio.c index a31bea4..be1cfa3 100644 --- a/libs/SDL3/src/audio/qnx/SDL_qsa_audio.c +++ b/libs/SDL3/src/audio/qnx/SDL_qsa_audio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -89,8 +89,8 @@ static void QSA_InitAudioParams(snd_pcm_channel_params_t * cpars) static bool QSA_WaitDevice(SDL_AudioDevice *device) { // Setup timeout for playing one fragment equal to 2 seconds - // If timeout occurred than something wrong with hardware or driver - // For example, Vortex 8820 audio driver stucks on second DAC because + // If timeout occurred then something wrong with hardware or driver + // For example, Vortex 8820 audio driver hangs on second DAC because // it doesn't exist ! const int result = SDL_IOReady(device->hidden->audio_fd, device->recording ? SDL_IOR_READ : SDL_IOR_WRITE, diff --git a/libs/SDL3/src/audio/qnx/SDL_qsa_audio.h b/libs/SDL3/src/audio/qnx/SDL_qsa_audio.h index 902752c..ead46f7 100644 --- a/libs/SDL3/src/audio/qnx/SDL_qsa_audio.h +++ b/libs/SDL3/src/audio/qnx/SDL_qsa_audio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/sndio/SDL_sndioaudio.c b/libs/SDL3/src/audio/sndio/SDL_sndioaudio.c index a0d2020..4daab72 100644 --- a/libs/SDL3/src/audio/sndio/SDL_sndioaudio.c +++ b/libs/SDL3/src/audio/sndio/SDL_sndioaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -108,6 +108,13 @@ static bool load_sndio_syms(void) #ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libsndio", + "Support for audio through libsndio", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_SNDIO_DYNAMIC +) + static void UnloadSNDIOLibrary(void) { if (sndio_handle) { diff --git a/libs/SDL3/src/audio/sndio/SDL_sndioaudio.h b/libs/SDL3/src/audio/sndio/SDL_sndioaudio.h index d4ff725..dfae963 100644 --- a/libs/SDL3/src/audio/sndio/SDL_sndioaudio.h +++ b/libs/SDL3/src/audio/sndio/SDL_sndioaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/vita/SDL_vitaaudio.c b/libs/SDL3/src/audio/vita/SDL_vitaaudio.c index 86e8a69..5a963b5 100644 --- a/libs/SDL3/src/audio/vita/SDL_vitaaudio.c +++ b/libs/SDL3/src/audio/vita/SDL_vitaaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/vita/SDL_vitaaudio.h b/libs/SDL3/src/audio/vita/SDL_vitaaudio.h index 1e97499..4bd64bf 100644 --- a/libs/SDL3/src/audio/vita/SDL_vitaaudio.h +++ b/libs/SDL3/src/audio/vita/SDL_vitaaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/audio/wasapi/SDL_wasapi.c b/libs/SDL3/src/audio/wasapi/SDL_wasapi.c index 2ab2dc3..c01f007 100644 --- a/libs/SDL3/src/audio/wasapi/SDL_wasapi.c +++ b/libs/SDL3/src/audio/wasapi/SDL_wasapi.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -45,8 +45,8 @@ // handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency). static HMODULE libavrt = NULL; -typedef HANDLE(WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD); -typedef BOOL(WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE); +typedef HANDLE (WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD); +typedef BOOL (WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE); static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL; static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL; @@ -54,11 +54,15 @@ static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NUL static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } }; static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } }; static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } }; +#ifdef __IAudioClient2_INTERFACE_DEFINED__ +static const IID SDL_IID_IAudioClient2 = { 0x726778cd, 0xf60a, 0x4EDA, { 0x82, 0xde, 0xe4, 0x76, 0x10, 0xcd, 0x78, 0xaa } }; +#endif // #ifdef __IAudioClient3_INTERFACE_DEFINED__ static const IID SDL_IID_IAudioClient3 = { 0x7ed4ee07, 0x8e67, 0x4cd4, { 0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42 } }; #endif // static bool immdevice_initialized = false; +static bool supports_recording_on_playback_devices = false; // WASAPI is _really_ particular about various things happening on the same thread, for COM and such, // so we proxy various stuff to a single background thread to manage. @@ -164,21 +168,10 @@ bool WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, b return true; // successfully added (and possibly executed)! } -static bool mgmtthrtask_AudioDeviceDisconnected(void *userdata) -{ - SDL_AudioDevice *device = (SDL_AudioDevice *) userdata; - SDL_AudioDeviceDisconnected(device); - UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes. - return true; -} static void AudioDeviceDisconnected(SDL_AudioDevice *device) { - // don't wait on this, IMMDevice's own thread needs to return or everything will deadlock. - if (device) { - RefPhysicalAudioDevice(device); // make sure this lives until the task completes. - WASAPI_ProxyToManagementThread(mgmtthrtask_AudioDeviceDisconnected, device, NULL); - } + WASAPI_DisconnectDevice(device); } static bool mgmtthrtask_DefaultAudioDeviceChanged(void *userdata) @@ -337,7 +330,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_AUDIO_F32); + SDL_IMMDevice_EnumerateEndpoints(data->default_playback, data->default_recording, SDL_AUDIO_F32, supports_recording_on_playback_devices); return true; } @@ -351,19 +344,11 @@ static void WASAPI_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDe WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc); } -static bool mgmtthrtask_DisconnectDevice(void *userdata) -{ - SDL_AudioDevice *device = (SDL_AudioDevice *) userdata; - SDL_AudioDeviceDisconnected(device); - UnrefPhysicalAudioDevice(device); - return true; -} - void WASAPI_DisconnectDevice(SDL_AudioDevice *device) { - if (SDL_CompareAndSwapAtomicInt(&device->hidden->device_disconnecting, 0, 1)) { - RefPhysicalAudioDevice(device); // will unref when the task ends. - WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, NULL); + // don't block in here; IMMDevice's own thread needs to return or everything will deadlock. + if (device && (!device->hidden || SDL_CompareAndSwapAtomicInt(&device->hidden->device_disconnecting, 0, 1))) { + SDL_AudioDeviceDisconnected(device); // this proxies the work to the main thread now, so no point in proxying to the management thread. } } @@ -462,6 +447,8 @@ static bool mgmtthrtask_ActivateDevice(void *userdata) return false; // This is already set by SDL_IMMDevice_Get } + device->hidden->isplayback = !SDL_IMMDevice_GetIsCapture(immdevice); + // this is _not_ async in standard win32, yay! HRESULT ret = IMMDevice_Activate(immdevice, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&device->hidden->client); IMMDevice_Release(immdevice); @@ -605,7 +592,7 @@ static int WASAPI_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen UINT32 frames = 0; DWORD flags = 0; - while (device->hidden->capture) { + while (device->hidden->capture && !SDL_GetAtomicInt(&device->hidden->device_disconnecting)) { const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL); if (ret == AUDCLNT_S_BUFFER_EMPTY) { return 0; // in theory we should have waited until there was data, but oh well, we'll go back to waiting. Returning 0 is safe in SDL3. @@ -741,16 +728,62 @@ static bool mgmtthrtask_PrepDevice(void *userdata) newspec.freq = waveformat->nSamplesPerSec; + if (device->recording && device->hidden->isplayback) { + streamflags |= AUDCLNT_STREAMFLAGS_LOOPBACK; + } + streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK; int new_sample_frames = 0; bool iaudioclient3_initialized = false; +#ifdef __IAudioClient2_INTERFACE_DEFINED__ + IAudioClient2 *client2 = NULL; + ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient2, (void **)&client2); + if (SUCCEEDED(ret)) { + AudioClientProperties audioProps; + + SDL_zero(audioProps); + audioProps.cbSize = sizeof(audioProps); + +// Setting AudioCategory_GameChat breaks audio on several devices, including Behringer U-PHORIA UM2 and RODE NT-USB Mini. +// We'll disable this for now until we understand more about what's happening. +#if 0 + const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE); + if (hint && *hint) { + if (SDL_strcasecmp(hint, "Communications") == 0) { + audioProps.eCategory = AudioCategory_Communications; + } else if (SDL_strcasecmp(hint, "Game") == 0) { + // We'll add support for GameEffects as distinct from GameMedia later when we add stream roles + audioProps.eCategory = AudioCategory_GameEffects; + } else if (SDL_strcasecmp(hint, "GameChat") == 0) { + audioProps.eCategory = AudioCategory_GameChat; + } else if (SDL_strcasecmp(hint, "Movie") == 0) { + audioProps.eCategory = AudioCategory_Movie; + } else if (SDL_strcasecmp(hint, "Media") == 0) { + audioProps.eCategory = AudioCategory_Media; + } + } +#endif + + if (SDL_GetHintBoolean(SDL_HINT_AUDIO_DEVICE_RAW_STREAM, false)) { + audioProps.Options = AUDCLNT_STREAMOPTIONS_RAW; + } + + ret = IAudioClient2_SetClientProperties(client2, &audioProps); + if (FAILED(ret)) { + // This isn't fatal, let's log it instead of failing + SDL_LogWarn(SDL_LOG_CATEGORY_AUDIO, "IAudioClient2_SetClientProperties failed: 0x%lx", ret); + } + IAudioClient2_Release(client2); + } +#endif + #ifdef __IAudioClient3_INTERFACE_DEFINED__ // Try querying IAudioClient3 if sharemode is AUDCLNT_SHAREMODE_SHARED if (sharemode == AUDCLNT_SHAREMODE_SHARED) { IAudioClient3 *client3 = NULL; - ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient3, (void**)&client3); + ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient3, (void **)&client3); if (SUCCEEDED(ret)) { UINT32 default_period_in_frames = 0; UINT32 fundamental_period_in_frames = 0; @@ -952,6 +985,7 @@ static bool WASAPI_Init(SDL_AudioDriverImpl *impl) impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle; impl->HasRecordingSupport = true; + supports_recording_on_playback_devices = SDL_GetHintBoolean(SDL_HINT_AUDIO_INCLUDE_MONITORS, false); return true; } diff --git a/libs/SDL3/src/audio/wasapi/SDL_wasapi.h b/libs/SDL3/src/audio/wasapi/SDL_wasapi.h index 5e528dc..202a0cb 100644 --- a/libs/SDL3/src/audio/wasapi/SDL_wasapi.h +++ b/libs/SDL3/src/audio/wasapi/SDL_wasapi.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -43,6 +43,7 @@ struct SDL_PrivateAudioData SDL_AtomicInt device_disconnecting; bool device_lost; bool device_dead; + bool isplayback; }; // win32 implementation calls into these. diff --git a/libs/SDL3/src/camera/SDL_camera.c b/libs/SDL3/src/camera/SDL_camera.c index 9f71cea..323be59 100644 --- a/libs/SDL3/src/camera/SDL_camera.c +++ b/libs/SDL3/src/camera/SDL_camera.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -69,11 +69,11 @@ int SDL_GetNumCameraDrivers(void) const char *SDL_GetCameraDriver(int index) { - if (index >= 0 && index < SDL_GetNumCameraDrivers()) { - return bootstrap[index]->name; + CHECK_PARAM(index < 0 || index >= SDL_GetNumCameraDrivers()) { + SDL_InvalidParamError("index"); + return NULL; } - SDL_InvalidParamError("index"); - return NULL; + return bootstrap[index]->name; } const char *SDL_GetCurrentCameraDriver(void) @@ -150,7 +150,7 @@ static size_t GetFrameBufLen(const SDL_CameraSpec *spec) return wxh * SDL_BYTESPERPIXEL(fmt); } -static SDL_CameraFrameResult ZombieAcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult ZombieAcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { const SDL_CameraSpec *spec = &device->actual_spec; @@ -229,10 +229,15 @@ static void ZombieReleaseFrame(SDL_Camera *device, SDL_Surface *frame) // Reclai // we just leave zombie_pixels alone, as we'll reuse it for every new frame until the camera is closed. } + +static void ObtainPhysicalCameraObj(SDL_Camera *device); +static void ReleaseCamera(SDL_Camera *device); + + static void ClosePhysicalCamera(SDL_Camera *device) { - if (!device) { - return; + if (!device || (device->hidden == NULL)) { + return; // device is not open. } SDL_SetAtomicInt(&device->shutdown, 1); @@ -244,6 +249,8 @@ static void ClosePhysicalCamera(SDL_Camera *device) device->thread = NULL; } + ObtainPhysicalCameraObj(device); + // release frames that are queued up somewhere... if (!device->needs_conversion && !device->needs_scaling) { for (SurfaceList *i = device->filled_output_surfaces.next; i != NULL; i = i->next) { @@ -255,6 +262,7 @@ static void ClosePhysicalCamera(SDL_Camera *device) } camera_driver.impl.CloseDevice(device); + device->hidden = NULL; // just in case backend didn't reset this. SDL_DestroyProperties(device->props); @@ -270,7 +278,7 @@ static void ClosePhysicalCamera(SDL_Camera *device) SDL_aligned_free(device->zombie_pixels); - device->permission = 0; + device->permission = SDL_CAMERA_PERMISSION_STATE_PENDING; device->zombie_pixels = NULL; device->filled_output_surfaces.next = NULL; device->empty_output_surfaces.next = NULL; @@ -280,13 +288,16 @@ static void ClosePhysicalCamera(SDL_Camera *device) device->adjust_timestamp = 0; SDL_zero(device->spec); + UnrefPhysicalCamera(device); // we're closed, release a reference. + + ReleaseCamera(device); } // Don't hold the device lock when calling this, as we may destroy the device! void UnrefPhysicalCamera(SDL_Camera *device) { if (SDL_AtomicDecRef(&device->refcount)) { - // take it out of the device list. + // take it out of the device list. This will call DestroyCameraHashItem to clean up the object. SDL_LockRWLockForWriting(camera_driver.device_hash_lock); if (SDL_RemoveFromHashTable(camera_driver.device_hash, (const void *) (uintptr_t) device->instance_id)) { SDL_AddAtomicInt(&camera_driver.device_count, -1); @@ -555,6 +566,8 @@ void SDL_CameraDisconnected(SDL_Camera *device) pending_tail->next = p; pending_tail = p; } + + UnrefPhysicalCamera(device); // camera is disconnected, drop its reference } ReleaseCamera(device); @@ -581,7 +594,7 @@ void SDL_CameraPermissionOutcome(SDL_Camera *device, bool approved) pending.next = NULL; SDL_PendingCameraEvent *pending_tail = &pending; - const int permission = approved ? 1 : -1; + const SDL_CameraPermissionState permission = approved ? SDL_CAMERA_PERMISSION_STATE_APPROVED : SDL_CAMERA_PERMISSION_STATE_DENIED; ObtainPhysicalCameraObj(device); if (device->permission != permission) { @@ -657,15 +670,16 @@ bool SDL_GetCameraFormat(SDL_Camera *camera, SDL_CameraSpec *spec) { bool result; - if (!camera) { + CHECK_PARAM(!camera) { return SDL_InvalidParamError("camera"); - } else if (!spec) { + } + CHECK_PARAM(!spec) { return SDL_InvalidParamError("spec"); } SDL_Camera *device = camera; // currently there's no separation between physical and logical device. ObtainPhysicalCameraObj(device); - if (device->permission > 0) { + if (device->permission > SDL_CAMERA_PERMISSION_STATE_PENDING) { SDL_copyp(spec, &device->spec); result = true; } else { @@ -808,9 +822,9 @@ bool SDL_CameraThreadIterate(SDL_Camera *device) } const int permission = device->permission; - if (permission <= 0) { + if (permission <= SDL_CAMERA_PERMISSION_STATE_PENDING) { SDL_UnlockMutex(device->lock); - return (permission < 0) ? false : true; // if permission was denied, shut it down. if undecided, we're done for now. + return (permission < SDL_CAMERA_PERMISSION_STATE_PENDING) ? false : true; // if permission was denied, shut it down. if undecided, we're done for now. } bool failed = false; // set to true if disaster worthy of treating the device as lost has happened. @@ -818,9 +832,10 @@ bool SDL_CameraThreadIterate(SDL_Camera *device) SDL_Surface *output_surface = NULL; SurfaceList *slist = NULL; Uint64 timestampNS = 0; + float rotation = 0.0f; // AcquireFrame SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice instead! - const SDL_CameraFrameResult rc = device->AcquireFrame(device, device->acquire_surface, ×tampNS); + const SDL_CameraFrameResult rc = device->AcquireFrame(device, device->acquire_surface, ×tampNS, &rotation); if (rc == SDL_CAMERA_FRAME_READY) { // new frame acquired! #if DEBUG_CAMERA @@ -914,6 +929,8 @@ bool SDL_CameraThreadIterate(SDL_Camera *device) acquired->pixels = NULL; acquired->pitch = 0; + SDL_SetFloatProperty(SDL_GetSurfaceProperties(output_surface), SDL_PROP_SURFACE_ROTATION_FLOAT, rotation); + // make the filled output surface available to the app. SDL_LockMutex(device->lock); slist->next = device->filled_output_surfaces.next; @@ -940,6 +957,8 @@ static int SDLCALL CameraThread(void *devicep) SDL_Log("CAMERA: dev[%p] Start thread 'CameraThread'", devicep); #endif + RefPhysicalCamera(device); // this thread holds a reference. + SDL_assert(device != NULL); SDL_CameraThreadSetup(device); @@ -951,6 +970,8 @@ static int SDLCALL CameraThread(void *devicep) SDL_CameraThreadShutdown(device); + UnrefPhysicalCamera(device); // this thread no longer holds a reference. + #if DEBUG_CAMERA SDL_Log("CAMERA: dev[%p] End thread 'CameraThread'", devicep); #endif @@ -1075,7 +1096,6 @@ static void ChooseBestCameraSpec(SDL_Camera *device, const SDL_CameraSpec *spec, // that. SDL_zerop(closest); - SDL_assert(((Uint32) SDL_PIXELFORMAT_UNKNOWN) == 0); // since we SDL_zerop'd to this value. if (device->num_specs == 0) { // device listed no specs! You get whatever you want! if (spec) { @@ -1244,6 +1264,8 @@ SDL_Camera *SDL_OpenCamera(SDL_CameraID instance_id, const SDL_CameraSpec *spec) } } + RefPhysicalCamera(device); // we're open, hold a reference. + ReleaseCamera(device); // unlock, we're good to go! return device; // currently there's no separation between physical and logical device. @@ -1255,7 +1277,7 @@ SDL_Surface *SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS) *timestampNS = 0; } - if (!camera) { + CHECK_PARAM(!camera) { SDL_InvalidParamError("camera"); return NULL; } @@ -1264,7 +1286,7 @@ SDL_Surface *SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS) ObtainPhysicalCameraObj(device); - if (device->permission <= 0) { + if (device->permission <= SDL_CAMERA_PERMISSION_STATE_PENDING) { ReleaseCamera(device); SDL_SetError("Camera permission has not been granted"); return NULL; @@ -1340,49 +1362,55 @@ void SDL_ReleaseCameraFrame(SDL_Camera *camera, SDL_Surface *frame) SDL_CameraID SDL_GetCameraID(SDL_Camera *camera) { - SDL_CameraID result = 0; - if (!camera) { + SDL_CameraID result; + + CHECK_PARAM(!camera) { SDL_InvalidParamError("camera"); - } else { - SDL_Camera *device = camera; // currently there's no separation between physical and logical device. - ObtainPhysicalCameraObj(device); - result = device->instance_id; - ReleaseCamera(device); + return 0; } + SDL_Camera *device = camera; // currently there's no separation between physical and logical device. + ObtainPhysicalCameraObj(device); + result = device->instance_id; + ReleaseCamera(device); + return result; } SDL_PropertiesID SDL_GetCameraProperties(SDL_Camera *camera) { - SDL_PropertiesID result = 0; - if (!camera) { + SDL_PropertiesID result; + + CHECK_PARAM(!camera) { SDL_InvalidParamError("camera"); - } else { - SDL_Camera *device = camera; // currently there's no separation between physical and logical device. - ObtainPhysicalCameraObj(device); - if (device->props == 0) { - device->props = SDL_CreateProperties(); - } - result = device->props; - ReleaseCamera(device); + return 0; } + SDL_Camera *device = camera; // currently there's no separation between physical and logical device. + ObtainPhysicalCameraObj(device); + if (device->props == 0) { + device->props = SDL_CreateProperties(); + } + result = device->props; + ReleaseCamera(device); + return result; } -int SDL_GetCameraPermissionState(SDL_Camera *camera) +SDL_CameraPermissionState SDL_GetCameraPermissionState(SDL_Camera *camera) { - int result; - if (!camera) { + SDL_CameraPermissionState result; + + CHECK_PARAM(!camera) { SDL_InvalidParamError("camera"); - result = -1; - } else { - SDL_Camera *device = camera; // currently there's no separation between physical and logical device. - ObtainPhysicalCameraObj(device); - result = device->permission; - ReleaseCamera(device); + return SDL_CAMERA_PERMISSION_STATE_DENIED; } + + SDL_Camera *device = camera; // currently there's no separation between physical and logical device. + ObtainPhysicalCameraObj(device); + result = device->permission; + ReleaseCamera(device); + return result; } @@ -1436,6 +1464,11 @@ void SDL_QuitCamera(void) static void SDLCALL DestroyCameraHashItem(void *userdata, const void *key, const void *value) { SDL_Camera *device = (SDL_Camera *) value; + + #if DEBUG_CAMERA + SDL_Log("DESTROYING CAMERA '%s'", device->name); + #endif + ClosePhysicalCamera(device); camera_driver.impl.FreeDeviceHandle(device); SDL_DestroyMutex(device->lock); @@ -1524,7 +1557,9 @@ bool SDL_CameraInit(const char *driver_name) } } - if (!initialized) { + if (initialized) { + SDL_DebugLogBackend("camera", camera_driver.name); + } else { // specific drivers will set the error message if they fail, but otherwise we do it here. if (!tried_to_init) { if (driver_name) { diff --git a/libs/SDL3/src/camera/SDL_camera_c.h b/libs/SDL3/src/camera/SDL_camera_c.h index 316ae7d..3d1f21a 100644 --- a/libs/SDL3/src/camera/SDL_camera_c.h +++ b/libs/SDL3/src/camera/SDL_camera_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/camera/SDL_syscamera.h b/libs/SDL3/src/camera/SDL_syscamera.h index 30a02f3..40c4ae9 100644 --- a/libs/SDL3/src/camera/SDL_syscamera.h +++ b/libs/SDL3/src/camera/SDL_syscamera.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -99,7 +99,7 @@ struct SDL_Camera // These are, initially, set from camera_driver, but we might swap them out with Zombie versions on disconnect/failure. bool (*WaitDevice)(SDL_Camera *device); - SDL_CameraFrameResult (*AcquireFrame)(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS); + SDL_CameraFrameResult (*AcquireFrame)(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation); void (*ReleaseFrame)(SDL_Camera *device, SDL_Surface *frame); // All supported formats/dimensions for this device. @@ -160,20 +160,25 @@ struct SDL_Camera // Optional properties. SDL_PropertiesID props; - // -1: user denied permission, 0: waiting for user response, 1: user approved permission. - int permission; + // Current state of user permission check. + SDL_CameraPermissionState permission; // Data private to this driver, used when device is opened and running. struct SDL_PrivateCameraData *hidden; }; + +// Note that for AcquireFrame, `rotation` is degrees, with positive values rotating clockwise. This is the amount to rotate an image so it would be right-side up. +// Rotations should be in 90 degree increments at this time (landscape to portrait, or upside down to right side up, etc). +// Most platforms won't care about this, but mobile devices might need to deal with the device itself being physically rotated, causing the fixed-orientation camera to be presenting sideways images. + typedef struct SDL_CameraDriverImpl { void (*DetectDevices)(void); bool (*OpenDevice)(SDL_Camera *device, const SDL_CameraSpec *spec); void (*CloseDevice)(SDL_Camera *device); bool (*WaitDevice)(SDL_Camera *device); - SDL_CameraFrameResult (*AcquireFrame)(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS); // set frame->pixels, frame->pitch, and *timestampNS! + SDL_CameraFrameResult (*AcquireFrame)(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation); // set frame->pixels, frame->pitch, *timestampNS, and *rotation! void (*ReleaseFrame)(SDL_Camera *device, SDL_Surface *frame); // Reclaim frame->pixels and frame->pitch! void (*FreeDeviceHandle)(SDL_Camera *device); // SDL is done with this device; free the handle from SDL_AddCamera() void (*Deinitialize)(void); diff --git a/libs/SDL3/src/camera/android/SDL_camera_android.c b/libs/SDL3/src/camera/android/SDL_camera_android.c index 97c7c4d..ab6fb9d 100644 --- a/libs/SDL3/src/camera/android/SDL_camera_android.c +++ b/libs/SDL3/src/camera/android/SDL_camera_android.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -149,6 +149,7 @@ struct SDL_PrivateCameraData ACaptureRequest *request; ACameraCaptureSession *session; SDL_CameraSpec requested_spec; + int rotation; // degrees to rotate clockwise to get from camera's static orientation to device's native orientation. Apply this plus current phone rotation to get upright image! }; static bool SetErrorStr(const char *what, const char *errstr, const int rc) @@ -295,7 +296,7 @@ static bool ANDROIDCAMERA_WaitDevice(SDL_Camera *device) return true; // this isn't used atm, since we run our own thread via onImageAvailable callbacks. } -static SDL_CameraFrameResult ANDROIDCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult ANDROIDCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY; media_status_t res; @@ -366,6 +367,21 @@ static SDL_CameraFrameResult ANDROIDCAMERA_AcquireFrame(SDL_Camera *device, SDL_ pAImage_delete(image); + int dev_rotation = 0; + switch (Android_JNI_GetDisplayCurrentOrientation()) { + case SDL_ORIENTATION_PORTRAIT: dev_rotation = 0; break; + case SDL_ORIENTATION_LANDSCAPE: dev_rotation = 90; break; + case SDL_ORIENTATION_PORTRAIT_FLIPPED: dev_rotation = 180; break; + case SDL_ORIENTATION_LANDSCAPE_FLIPPED: dev_rotation = 270; break; + default: SDL_assert(!"Unexpected device rotation!"); dev_rotation = 0; break; + } + + if (device->position == SDL_CAMERA_POSITION_BACK_FACING) { + dev_rotation = -dev_rotation; // we want to subtract this value, instead of add, if back-facing. + } + + *rotation = (float) (dev_rotation + device->hidden->rotation); // current phone orientation, static camera orientation in relation to phone. + return result; } @@ -494,10 +510,23 @@ static bool PrepareCamera(SDL_Camera *device) imglistener.context = device; imglistener.onImageAvailable = onImageAvailable; + + const char *devid = (const char *) device->handle; + + device->hidden->rotation = 0; + ACameraMetadata *metadata = NULL; + ACameraMetadata_const_entry orientationentry; + if (pACameraManager_getCameraCharacteristics(cameraMgr, devid, &metadata) == ACAMERA_OK) { + if (pACameraMetadata_getConstEntry(metadata, ACAMERA_SENSOR_ORIENTATION, &orientationentry) == ACAMERA_OK) { + device->hidden->rotation = (int) (*orientationentry.data.i32 % 360); + } + pACameraMetadata_free(metadata); + } + // just in case SDL_OpenCamera is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy. const SDL_CameraSpec *spec = &device->hidden->requested_spec; - if ((res = pACameraManager_openCamera(cameraMgr, (const char *) device->handle, &dev_callbacks, &device->hidden->device)) != ACAMERA_OK) { + if ((res = pACameraManager_openCamera(cameraMgr, devid, &dev_callbacks, &device->hidden->device)) != ACAMERA_OK) { return SetCameraError("Failed to open camera", res); } else if ((res2 = pAImageReader_new(spec->width, spec->height, format_sdl_to_android(spec->format), 10 /* nb buffers */, &device->hidden->reader)) != AMEDIA_OK) { return SetMediaError("Error AImageReader_new", res2); @@ -729,7 +758,7 @@ static void onCameraAvailable(void *context, const char *cameraId) static void onCameraUnavailable(void *context, const char *cameraId) { #if DEBUG_CAMERA - SDL_Log("CAMERA: CB onCameraUnvailable('%s')", cameraId); + SDL_Log("CAMERA: CB onCameraUnavailable('%s')", cameraId); #endif SDL_assert(cameraId != NULL); diff --git a/libs/SDL3/src/camera/coremedia/SDL_camera_coremedia.m b/libs/SDL3/src/camera/coremedia/SDL_camera_coremedia.m index 2ecfd13..2f3fb59 100644 --- a/libs/SDL3/src/camera/coremedia/SDL_camera_coremedia.m +++ b/libs/SDL3/src/camera/coremedia/SDL_camera_coremedia.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -29,6 +29,14 @@ #import #import +#if defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_TVOS) +#define USE_UIKIT_DEVICE_ROTATION +#endif + +#ifdef USE_UIKIT_DEVICE_ROTATION +#import +#endif + /* * Need to link with:: CoreMedia CoreVideo * @@ -77,6 +85,9 @@ static void CoreMediaFormatToSDL(FourCharCode fmt, SDL_PixelFormat *pixel_format @property(nonatomic, retain) AVCaptureSession *session; @property(nonatomic, retain) SDLCaptureVideoDataOutputSampleBufferDelegate *delegate; @property(nonatomic, assign) CMSampleBufferRef current_sample; +#ifdef USE_UIKIT_DEVICE_ROTATION +@property(nonatomic, assign) UIDeviceOrientation last_device_orientation; +#endif @end @implementation SDLPrivateCameraData @@ -85,7 +96,7 @@ static void CoreMediaFormatToSDL(FourCharCode fmt, SDL_PixelFormat *pixel_format static bool CheckCameraPermissions(SDL_Camera *device) { - if (device->permission == 0) { // still expecting a permission result. + if (device->permission == SDL_CAMERA_PERMISSION_STATE_PENDING) { // still expecting a permission result. if (@available(macOS 14, *)) { const AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; if (status != AVAuthorizationStatusNotDetermined) { // NotDetermined == still waiting for an answer from the user. @@ -96,7 +107,7 @@ static bool CheckCameraPermissions(SDL_Camera *device) } } - return (device->permission > 0); + return (device->permission > SDL_CAMERA_PERMISSION_STATE_PENDING); } // this delegate just receives new video frames on a Grand Central Dispatch queue, and fires off the @@ -146,7 +157,7 @@ static bool COREMEDIA_WaitDevice(SDL_Camera *device) return true; // this isn't used atm, since we run our own thread out of Grand Central Dispatch. } -static SDL_CameraFrameResult COREMEDIA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult COREMEDIA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY; SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden; @@ -219,6 +230,37 @@ static SDL_CameraFrameResult COREMEDIA_AcquireFrame(SDL_Camera *device, SDL_Surf CVPixelBufferUnlockBaseAddress(image, 0); + #ifdef USE_UIKIT_DEVICE_ROTATION + UIDeviceOrientation device_orientation = [[UIDevice currentDevice] orientation]; + if (!UIDeviceOrientationIsValidInterfaceOrientation(device_orientation)) { + device_orientation = hidden.last_device_orientation; // possible the phone is laying flat or something went wrong, just stay with the last known-good orientation. + } else { + hidden.last_device_orientation = device_orientation; // update the last known-good orientation for later. + } + + const UIInterfaceOrientation ui_orientation = [UIApplication sharedApplication].statusBarOrientation; + + // there is probably math for this, but this is easy to slap into a table. + // rotation = rotations[uiorientation-1][devorientation-1]; + if (device->position == SDL_CAMERA_POSITION_BACK_FACING) { + static const Uint16 back_rotations[4][4] = { + { 90, 90, 90, 90 }, // ui portrait + { 270, 270, 270, 270 }, // ui portrait upside down + { 0, 0, 0, 0 }, // ui landscape left + { 180, 180, 180, 180 } // ui landscape right + }; + *rotation = (float) back_rotations[ui_orientation - 1][device_orientation - 1]; + } else { + static const Uint16 front_rotations[4][4] = { + { 90, 90, 270, 270 }, // ui portrait + { 270, 270, 90, 90 }, // ui portrait upside down + { 0, 0, 180, 180 }, // ui landscape left + { 180, 180, 0, 0 } // ui landscape right + }; + *rotation = (float) front_rotations[ui_orientation - 1][device_orientation - 1]; + } + #endif + return result; } @@ -231,6 +273,10 @@ static void COREMEDIA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame) static void COREMEDIA_CloseDevice(SDL_Camera *device) { if (device && device->hidden) { + #ifdef USE_UIKIT_DEVICE_ROTATION + [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; + #endif + SDLPrivateCameraData *hidden = (SDLPrivateCameraData *) CFBridgingRelease(device->hidden); device->hidden = NULL; @@ -239,7 +285,7 @@ static void COREMEDIA_CloseDevice(SDL_Camera *device) hidden.session = nil; [session stopRunning]; [session removeInput:[session.inputs objectAtIndex:0]]; - [session removeOutput:(AVCaptureVideoDataOutput*)[session.outputs objectAtIndex:0]]; + [session removeOutput:(AVCaptureVideoDataOutput *)[session.outputs objectAtIndex:0]]; session = nil; } @@ -274,10 +320,37 @@ static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) const float FRAMERATE_EPSILON = 0.01f; for (AVFrameRateRange *framerate in format.videoSupportedFrameRateRanges) { - if (rate > (framerate.minFrameRate - FRAMERATE_EPSILON) && - rate < (framerate.maxFrameRate + FRAMERATE_EPSILON)) { - spec_format = format; - break; + // Check if the requested rate is within the supported range + if (rate >= (framerate.minFrameRate - FRAMERATE_EPSILON) && + rate <= (framerate.maxFrameRate + FRAMERATE_EPSILON)) { + + // Prefer formats with narrower frame rate ranges that are closer to our target + // This helps avoid formats that support a wide range (like 10-60 FPS) + // when we want a specific rate (like 30 FPS) + bool should_select = false; + if (spec_format == nil) { + should_select = true; + } else { + AVFrameRateRange *current_range = spec_format.videoSupportedFrameRateRanges.firstObject; + float current_range_width = current_range.maxFrameRate - current_range.minFrameRate; + float new_range_width = framerate.maxFrameRate - framerate.minFrameRate; + + // Prefer formats with narrower ranges, or if ranges are similar, prefer closer to target + if (new_range_width < current_range_width) { + should_select = true; + } else if (SDL_fabsf(new_range_width - current_range_width) < 0.1f) { + // Similar range width, prefer the one closer to our target rate + float current_distance = SDL_fabsf(rate - current_range.minFrameRate); + float new_distance = SDL_fabsf(rate - framerate.minFrameRate); + if (new_distance < current_distance) { + should_select = true; + } + } + } + + if (should_select) { + spec_format = format; + } } } @@ -293,6 +366,22 @@ static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) } avdevice.activeFormat = spec_format; + + // Try to set the frame duration to enforce the requested frame rate + const float frameRate = (float)spec->framerate_numerator / spec->framerate_denominator; + const CMTime frameDuration = CMTimeMake(1, (int32_t)frameRate); + + // Check if the device supports setting frame duration + if ([avdevice respondsToSelector:@selector(setActiveVideoMinFrameDuration:)] && + [avdevice respondsToSelector:@selector(setActiveVideoMaxFrameDuration:)]) { + @try { + avdevice.activeVideoMinFrameDuration = frameDuration; + avdevice.activeVideoMaxFrameDuration = frameDuration; + } @catch (NSException *exception) { + // Some devices don't support setting frame duration, that's okay + } + } + [avdevice unlockForConfiguration]; AVCaptureSession *session = [[AVCaptureSession alloc] init]; @@ -348,6 +437,15 @@ static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) } [session addOutput:output]; + // Try to set the frame rate on the connection + AVCaptureConnection *connection = [output connectionWithMediaType:AVMediaTypeVideo]; + if (connection && connection.isVideoMinFrameDurationSupported) { + connection.videoMinFrameDuration = frameDuration; + if (connection.isVideoMaxFrameDurationSupported) { + connection.videoMaxFrameDuration = frameDuration; + } + } + [session commitConfiguration]; SDLPrivateCameraData *hidden = [[SDLPrivateCameraData alloc] init]; @@ -358,6 +456,28 @@ static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) hidden.session = session; hidden.delegate = delegate; hidden.current_sample = NULL; + + #ifdef USE_UIKIT_DEVICE_ROTATION + // When using a camera, we turn on device orientation tracking. The docs note that this turns on + // the device's accelerometer, so I assume this burns power, so we don't leave this running all + // the time. These calls nest, so we just need to call the matching `end` message when we close. + // You _can_ get an actual events through this mechanism, but we just want to be able to call + // -[UIDevice orientation], which will update with real info while notifications are enabled. + UIDevice *uidevice = [UIDevice currentDevice]; + [uidevice beginGeneratingDeviceOrientationNotifications]; + hidden.last_device_orientation = uidevice.orientation; + if (!UIDeviceOrientationIsValidInterfaceOrientation(hidden.last_device_orientation)) { + // accelerometer isn't ready yet or the phone is laying flat or something. Just try to guess from how the UI is oriented at the moment. + switch ([UIApplication sharedApplication].statusBarOrientation) { + case UIInterfaceOrientationPortrait: hidden.last_device_orientation = UIDeviceOrientationPortrait; break; + case UIInterfaceOrientationPortraitUpsideDown: hidden.last_device_orientation = UIDeviceOrientationPortraitUpsideDown; break; + case UIInterfaceOrientationLandscapeLeft: hidden.last_device_orientation = UIDeviceOrientationLandscapeRight; break; // Apple docs say UI and device orientations are reversed in landscape. + case UIInterfaceOrientationLandscapeRight: hidden.last_device_orientation = UIDeviceOrientationLandscapeLeft; break; + default: hidden.last_device_orientation = UIDeviceOrientationPortrait; break; // oh well. + } + } + #endif + device->hidden = (struct SDL_PrivateCameraData *)CFBridgingRetain(hidden); [session startRunning]; // !!! FIXME: docs say this can block while camera warms up and shouldn't be done on main thread. Maybe push through `queue`? diff --git a/libs/SDL3/src/camera/dummy/SDL_camera_dummy.c b/libs/SDL3/src/camera/dummy/SDL_camera_dummy.c index b2a4dc1..30ccffa 100644 --- a/libs/SDL3/src/camera/dummy/SDL_camera_dummy.c +++ b/libs/SDL3/src/camera/dummy/SDL_camera_dummy.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -38,7 +38,7 @@ static bool DUMMYCAMERA_WaitDevice(SDL_Camera *device) return SDL_Unsupported(); } -static SDL_CameraFrameResult DUMMYCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult DUMMYCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { SDL_Unsupported(); return SDL_CAMERA_FRAME_ERROR; diff --git a/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c b/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c index 36418c5..85ff587 100644 --- a/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c +++ b/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -33,15 +33,13 @@ // each EM_ASM section is ugly. /* *INDENT-OFF* */ // clang-format off -EM_JS_DEPS(sdlcamera, "$dynCall"); - static bool EMSCRIPTENCAMERA_WaitDevice(SDL_Camera *device) { SDL_assert(!"This shouldn't be called"); // we aren't using SDL's internal thread. return false; } -static SDL_CameraFrameResult EMSCRIPTENCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult EMSCRIPTENCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { void *rgba = SDL_malloc(device->actual_spec.width * device->actual_spec.height * 4); if (!rgba) { @@ -98,7 +96,7 @@ static void EMSCRIPTENCAMERA_CloseDevice(SDL_Camera *device) } } -static int SDLEmscriptenCameraPermissionOutcome(SDL_Camera *device, int approved, int w, int h, int fps) +EMSCRIPTEN_KEEPALIVE int SDLEmscriptenCameraPermissionOutcome(SDL_Camera *device, int approved, int w, int h, int fps) { if (approved) { device->actual_spec.format = SDL_PIXELFORMAT_RGBA32; @@ -118,6 +116,10 @@ static int SDLEmscriptenCameraPermissionOutcome(SDL_Camera *device, int approved return approved; } +EMSCRIPTEN_KEEPALIVE bool SDLEmscriptenThreadIterate(SDL_Camera *device) { + return SDL_CameraThreadIterate(device); +} + static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) { MAIN_THREAD_EM_ASM({ @@ -128,8 +130,8 @@ static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec const h = $2; const framerate_numerator = $3; const framerate_denominator = $4; - const outcome = $5; - const iterate = $6; + const outcome = Module._SDLEmscriptenCameraPermissionOutcome; + const iterate = Module._SDLEmscriptenThreadIterate; const constraints = {}; if ((w <= 0) || (h <= 0)) { @@ -155,7 +157,7 @@ static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec const nextframems = SDL3.camera.next_frame_time; const now = performance.now(); if (now >= nextframems) { - dynCall('vi', iterate, [device]); // calls SDL_CameraThreadIterate, which will call our AcquireFrame implementation. + iterate(device); // calls SDL_CameraThreadIterate, which will call our AcquireFrame implementation. // bump ahead but try to stay consistent on timing, in case we dropped frames. while (SDL3.camera.next_frame_time < now) { @@ -174,7 +176,7 @@ 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); - if (dynCall('iiiiii', outcome, [device, 1, actualw, actualh, actualfps])) { + if (outcome(device, 1, actualw, actualh, actualfps)) { const video = document.createElement("video"); video.width = actualw; video.height = actualh; @@ -207,9 +209,9 @@ static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec }) .catch((err) => { console.error("Tried to open camera but it threw an error! " + err.name + ": " + err.message); - dynCall('iiiiii', outcome, [device, 0, 0, 0, 0]); // we call this a permission error, because it probably is. + 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); + }, device, spec->width, spec->height, spec->framerate_numerator, spec->framerate_denominator); return true; // the real work waits until the user approves a camera. } @@ -245,9 +247,6 @@ static void EMSCRIPTENCAMERA_DetectDevices(void) static bool EMSCRIPTENCAMERA_Init(SDL_CameraDriverImpl *impl) { MAIN_THREAD_EM_ASM({ - if (typeof(Module['SDL3']) === 'undefined') { - Module['SDL3'] = {}; - } Module['SDL3'].camera = {}; }); diff --git a/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c b/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c index d9d627d..f04b77b 100644 --- a/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c +++ b/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -324,18 +324,18 @@ static const GUID *SDLFmtToMFVidFmtGuid(SDL_PixelFormat format) // mf.dll ... static HMODULE libmf = NULL; -typedef HRESULT(WINAPI *pfnMFEnumDeviceSources)(IMFAttributes *,IMFActivate ***,UINT32 *); -typedef HRESULT(WINAPI *pfnMFCreateDeviceSource)(IMFAttributes *, IMFMediaSource **); +typedef HRESULT (WINAPI *pfnMFEnumDeviceSources)(IMFAttributes *,IMFActivate ***,UINT32 *); +typedef HRESULT (WINAPI *pfnMFCreateDeviceSource)(IMFAttributes *, IMFMediaSource **); static pfnMFEnumDeviceSources pMFEnumDeviceSources = NULL; static pfnMFCreateDeviceSource pMFCreateDeviceSource = NULL; // mfplat.dll ... static HMODULE libmfplat = NULL; -typedef HRESULT(WINAPI *pfnMFStartup)(ULONG, DWORD); -typedef HRESULT(WINAPI *pfnMFShutdown)(void); -typedef HRESULT(WINAPI *pfnMFCreateAttributes)(IMFAttributes **, UINT32); -typedef HRESULT(WINAPI *pfnMFCreateMediaType)(IMFMediaType **); -typedef HRESULT(WINAPI *pfnMFGetStrideForBitmapInfoHeader)(DWORD, DWORD, LONG *); +typedef HRESULT (WINAPI *pfnMFStartup)(ULONG, DWORD); +typedef HRESULT (WINAPI *pfnMFShutdown)(void); +typedef HRESULT (WINAPI *pfnMFCreateAttributes)(IMFAttributes **, UINT32); +typedef HRESULT (WINAPI *pfnMFCreateMediaType)(IMFMediaType **); +typedef HRESULT (WINAPI *pfnMFGetStrideForBitmapInfoHeader)(DWORD, DWORD, LONG *); static pfnMFStartup pMFStartup = NULL; static pfnMFShutdown pMFShutdown = NULL; @@ -345,7 +345,7 @@ static pfnMFGetStrideForBitmapInfoHeader pMFGetStrideForBitmapInfoHeader = NULL; // mfreadwrite.dll ... static HMODULE libmfreadwrite = NULL; -typedef HRESULT(WINAPI *pfnMFCreateSourceReaderFromMediaSource)(IMFMediaSource *, IMFAttributes *, IMFSourceReader **); +typedef HRESULT (WINAPI *pfnMFCreateSourceReaderFromMediaSource)(IMFMediaSource *, IMFAttributes *, IMFSourceReader **); static pfnMFCreateSourceReaderFromMediaSource pMFCreateSourceReaderFromMediaSource = NULL; @@ -430,7 +430,7 @@ static void SDLCALL CleanupIMFMediaBuffer(void *userdata, void *value) SDL_free(objs); } -static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { SDL_assert(device->hidden->current_sample != NULL); @@ -562,7 +562,7 @@ static SDL_CameraFrameResult MEDIAFOUNDATION_CopyFrame(SDL_Surface *frame, const return SDL_CAMERA_FRAME_READY; } -static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { SDL_assert(device->hidden->current_sample != NULL); @@ -675,7 +675,7 @@ static HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride) LONG lStride = 0; // Try to get the default stride from the media type. - HRESULT ret = IMFMediaType_GetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32*)&lStride); + HRESULT ret = IMFMediaType_GetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32 *)&lStride); if (FAILED(ret)) { // Attribute not set. Try to calculate the default stride. @@ -741,7 +741,7 @@ static bool MEDIAFOUNDATION_OpenDevice(SDL_Camera *device, const SDL_CameraSpec SDL_Log("CAMERA: opening device with symlink of '%s'", utf8symlink); #endif - wstrsymlink = WIN_UTF8ToString(utf8symlink); + wstrsymlink = WIN_UTF8ToStringW(utf8symlink); if (!wstrsymlink) { goto failed; } @@ -901,7 +901,7 @@ static char *QueryActivationObjectString(IMFActivate *activation, const GUID *pg return NULL; } - char *utf8str = WIN_StringToUTF8(wstr); + char *utf8str = WIN_StringToUTF8W(wstr); CoTaskMemFree(wstr); return utf8str; } @@ -1001,7 +1001,7 @@ static void MaybeAddDevice(IMFActivate *activation) if (name && symlink) { IMFMediaSource *source = NULL; // "activating" here only creates an object, it doesn't open the actual camera hardware or start recording. - HRESULT ret = IMFActivate_ActivateObject(activation, &SDL_IID_IMFMediaSource, (void**)&source); + HRESULT ret = IMFActivate_ActivateObject(activation, &SDL_IID_IMFMediaSource, (void **)&source); if (SUCCEEDED(ret) && source) { CameraFormatAddData add_data; GatherCameraSpecs(source, &add_data); diff --git a/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c b/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c index 5d868bf..909aefa 100644 --- a/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c +++ b/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga Copyright (C) 2024 Wim Taymans This software is provided 'as-is', without any express or implied @@ -25,6 +25,10 @@ #include "../SDL_syscamera.h" +#ifdef HAVE_DBUS_DBUS_H +#include "../../core/linux/SDL_dbus.h" +#endif + #include #include #include @@ -80,6 +84,9 @@ static int (*PIPEWIRE_pw_thread_loop_start)(struct pw_thread_loop *); static struct pw_context *(*PIPEWIRE_pw_context_new)(struct pw_loop *, struct pw_properties *, size_t); static void (*PIPEWIRE_pw_context_destroy)(struct pw_context *); static struct pw_core *(*PIPEWIRE_pw_context_connect)(struct pw_context *, struct pw_properties *, size_t); +#ifdef SDL_USE_LIBDBUS +static struct pw_core *(*PIPEWIRE_pw_context_connect_fd)(struct pw_context *, int, struct pw_properties *, size_t); +#endif static void (*PIPEWIRE_pw_proxy_add_object_listener)(struct pw_proxy *, struct spa_hook *, const void *, void *); static void (*PIPEWIRE_pw_proxy_add_listener)(struct pw_proxy *, struct spa_hook *, const struct pw_proxy_events *, void *); static void *(*PIPEWIRE_pw_proxy_get_user_data)(struct pw_proxy *); @@ -102,6 +109,13 @@ static int (*PIPEWIRE_pw_properties_setf)(struct pw_properties *, const char *, #ifdef SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "camera-libpipewire", + "Support for camera through libpipewire", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC +) + static const char *pipewire_library = SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC; static SDL_SharedObject *pipewire_handle = NULL; @@ -175,6 +189,9 @@ static bool load_pipewire_syms(void) SDL_PIPEWIRE_SYM(pw_context_new); SDL_PIPEWIRE_SYM(pw_context_destroy); SDL_PIPEWIRE_SYM(pw_context_connect); +#ifdef SDL_USE_LIBDBUS + SDL_PIPEWIRE_SYM(pw_context_connect_fd); +#endif SDL_PIPEWIRE_SYM(pw_proxy_add_listener); SDL_PIPEWIRE_SYM(pw_proxy_add_object_listener); SDL_PIPEWIRE_SYM(pw_proxy_get_user_data); @@ -313,7 +330,7 @@ static struct param *param_add(struct spa_list *params, id = SPA_POD_OBJECT_ID(param); } - p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0)); + p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0)); // This should NOT be SDL_malloc() if (p == NULL) return NULL; @@ -560,7 +577,7 @@ static bool PIPEWIRECAMERA_WaitDevice(SDL_Camera *device) return true; } -static SDL_CameraFrameResult PIPEWIRECAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult PIPEWIRECAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { struct pw_buffer *b; @@ -828,7 +845,7 @@ static void node_event_info(void *object, const struct pw_node_info *info) if (!(info->params[i].flags & SPA_PARAM_INFO_READ)) continue; - res = pw_node_enum_params((struct pw_node*)g->proxy, + res = pw_node_enum_params((struct pw_node *)g->proxy, ++SPA_PARAMS_INFO_SEQ(info->params[i]), id, 0, -1, NULL); if (SPA_RESULT_IS_ASYNC(res)) SPA_PARAMS_INFO_SEQ(info->params[i]) = res; @@ -933,7 +950,7 @@ static void hotplug_registry_global_callback(void *object, uint32_t id, g->permissions = permissions; g->props = props ? PIPEWIRE_pw_properties_new_dict(props) : NULL; g->proxy = proxy; - g->name = strdup(name); + g->name = strdup(name); // This should NOT be SDL_strdup() spa_list_init(&g->pending_list); spa_list_init(&g->param_list); spa_list_append(&hotplug.global_list, &g->link); @@ -1025,6 +1042,13 @@ static bool pipewire_server_version_at_least(int major, int minor, int patch) static bool hotplug_loop_init(void) { int res; +#ifdef SDL_USE_LIBDBUS + int fd; + + fd = SDL_DBus_CameraPortalRequestAccess(); + if (fd == -1) + return false; +#endif spa_list_init(&hotplug.global_list); @@ -1043,8 +1067,15 @@ static bool hotplug_loop_init(void) if (!hotplug.context) { return SDL_SetError("Pipewire: Failed to create hotplug detection context (%i)", errno); } - +#ifdef SDL_USE_LIBDBUS + if (fd >= 0) { + hotplug.core = PIPEWIRE_pw_context_connect_fd(hotplug.context, fd, NULL, 0); + } else { + hotplug.core = PIPEWIRE_pw_context_connect(hotplug.context, NULL, 0); + } +#else hotplug.core = PIPEWIRE_pw_context_connect(hotplug.context, NULL, 0); +#endif if (!hotplug.core) { return SDL_SetError("Pipewire: Failed to connect hotplug detection context (%i)", errno); } diff --git a/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c b/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c index 9cdb54b..e1ee5ea 100644 --- a/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c +++ b/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -32,6 +32,9 @@ #include #include +#ifndef V4L2_PIX_FMT_RGBX32 +#define V4L2_PIX_FMT_RGBX32 v4l2_fourcc('X','B','2','4') +#endif #ifndef V4L2_CAP_DEVICE_CAPS // device_caps was added to struct v4l2_capability as of kernel 3.4. #define device_caps reserved[0] @@ -122,7 +125,7 @@ static bool V4L2_WaitDevice(SDL_Camera *device) return false; } -static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { const int fd = device->hidden->fd; const io_method io = device->hidden->io; @@ -193,7 +196,7 @@ static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface * *timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec); #if DEBUG_CAMERA - SDL_Log("CAMERA: debug mmap: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void*)frame->pixels); + SDL_Log("CAMERA: debug mmap: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void *)frame->pixels); #endif break; @@ -230,7 +233,7 @@ static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface * return SDL_CAMERA_FRAME_ERROR; } - frame->pixels = (void*)buf.m.userptr; + frame->pixels = (void *)buf.m.userptr; if (device->hidden->driver_pitch) { frame->pitch = device->hidden->driver_pitch; } else { @@ -241,7 +244,7 @@ static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface * *timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec); #if DEBUG_CAMERA - SDL_Log("CAMERA: debug userptr: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void*)frame->pixels); + SDL_Log("CAMERA: debug userptr: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void *)frame->pixels); #endif break; @@ -418,6 +421,7 @@ static void format_v4l2_to_sdl(Uint32 fmt, SDL_PixelFormat *format, SDL_Colorspa #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); + CASE(V4L2_PIX_FMT_RGBX32, SDL_PIXELFORMAT_RGBX32, SDL_COLORSPACE_SRGB); #undef CASE default: #if DEBUG_CAMERA @@ -439,6 +443,7 @@ static Uint32 format_sdl_to_v4l2(SDL_PixelFormat fmt) #define CASE(y, x) case x: return y CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2); CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_MJPG); + CASE(V4L2_PIX_FMT_RGBX32, SDL_PIXELFORMAT_RGBX32); #undef CASE default: return 0; @@ -570,7 +575,7 @@ static bool V4L2_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (xioctl(fd, VIDIOC_G_PARM, &setfps) == 0) { if ( (setfps.parm.capture.timeperframe.denominator != spec->framerate_numerator) || - (setfps.parm.capture.timeperframe.numerator = spec->framerate_denominator) ) { + (setfps.parm.capture.timeperframe.numerator != spec->framerate_denominator) ) { setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; setfps.parm.capture.timeperframe.numerator = spec->framerate_denominator; setfps.parm.capture.timeperframe.denominator = spec->framerate_numerator; @@ -796,7 +801,7 @@ static void MaybeAddDevice(const char *path) const int stepw = (int) frmsizeenum.stepwise.step_width; const int steph = (int) frmsizeenum.stepwise.step_height; for (int w = minw; w <= maxw; w += stepw) { - for (int h = minh; w <= maxh; w += steph) { + for (int h = minh; h <= maxh; h += steph) { #if DEBUG_CAMERA SDL_Log("CAMERA: * Has %s size %dx%d", (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) ? "stepwise" : "continuous", w, h); #endif diff --git a/libs/SDL3/src/camera/vita/SDL_camera_vita.c b/libs/SDL3/src/camera/vita/SDL_camera_vita.c index 42a5a89..36b75b5 100644 --- a/libs/SDL3/src/camera/vita/SDL_camera_vita.c +++ b/libs/SDL3/src/camera/vita/SDL_camera_vita.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -93,7 +93,7 @@ static void MaybeAddDevice(Sint32 devid) GatherCameraSpecs(devid, &add_data, &fullname, &position); if (add_data.num_specs > 0) { - SDL_AddCamera(fullname, position, add_data.num_specs, add_data.specs, (void*)devid); + SDL_AddCamera(fullname, position, add_data.num_specs, add_data.specs, (void *)devid); } SDL_free(fullname); @@ -102,7 +102,7 @@ static void MaybeAddDevice(Sint32 devid) static SceUID imbUid = -1; -static void freeBuffers(SceCameraInfo* info) +static void freeBuffers(SceCameraInfo *info) { if (imbUid != -1) { sceKernelFreeMemBlock(imbUid); @@ -118,7 +118,7 @@ static bool VITACAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec return SDL_SetError("Only one camera can be active"); } - SceCameraInfo* info = (SceCameraInfo*)SDL_calloc(1, sizeof(SceCameraInfo)); + SceCameraInfo *info = (SceCameraInfo *)SDL_calloc(1, sizeof(SceCameraInfo)); info->size = sizeof(SceCameraInfo); info->priority = SCE_CAMERA_PRIORITY_SHARE; @@ -139,12 +139,12 @@ static bool VITACAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec info->format = SCE_CAMERA_FORMAT_YUV420_PLANE; info->pitch = 0; // same size surface - info->sizeIBase = spec->width*spec->height;; + info->sizeIBase = spec->width * spec->height; info->sizeUBase = ((spec->width+1)/2) * ((spec->height+1) / 2); info->sizeVBase = ((spec->width+1)/2) * ((spec->height+1) / 2); // PHYCONT memory size *must* be a multiple of 1MB, we can just always spend 2MB, since we don't use PHYCONT anywhere else - imbUid = sceKernelAllocMemBlock("CameraI", SCE_KERNEL_MEMBLOCK_TYPE_USER_MAIN_PHYCONT_NC_RW, 2*1024*1024 , NULL); + imbUid = sceKernelAllocMemBlock("CameraI", SCE_KERNEL_MEMBLOCK_TYPE_USER_MAIN_PHYCONT_NC_RW, 2 * 1024 * 1024 , NULL); if (imbUid < 0) { return SDL_SetError("sceKernelAllocMemBlock error: 0x%08X", imbUid); @@ -179,7 +179,7 @@ static void VITACAMERA_CloseDevice(SDL_Camera *device) if (device->hidden) { sceCameraStop((int)device->handle); sceCameraClose((int)device->handle); - freeBuffers((SceCameraInfo*)device->hidden); + freeBuffers((SceCameraInfo *)device->hidden); SDL_free(device->hidden); } } @@ -190,7 +190,7 @@ static bool VITACAMERA_WaitDevice(SDL_Camera *device) return true; } -static SDL_CameraFrameResult VITACAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult VITACAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { SceCameraRead read = {0}; read.size = sizeof(SceCameraRead); @@ -205,7 +205,7 @@ static SDL_CameraFrameResult VITACAMERA_AcquireFrame(SDL_Camera *device, SDL_Sur *timestampNS = read.timestamp; - SceCameraInfo* info = (SceCameraInfo*)(device->hidden); + SceCameraInfo *info = (SceCameraInfo *)(device->hidden); frame->pitch = info->width; frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), info->sizeIBase + info->sizeUBase + info->sizeVBase); diff --git a/libs/SDL3/src/core/SDL_core_unsupported.c b/libs/SDL3/src/core/SDL_core_unsupported.c index af963ed..5a70b5b 100644 --- a/libs/SDL3/src/core/SDL_core_unsupported.c +++ b/libs/SDL3/src/core/SDL_core_unsupported.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -20,18 +20,18 @@ */ #include "SDL_internal.h" +#include "SDL_core_unsupported.h" + #ifndef SDL_VIDEO_DRIVER_X11 -SDL_DECLSPEC void SDLCALL SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata); void SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata) { } -#endif +#endif /* !SDL_VIDEO_DRIVER_X11 */ #ifndef SDL_PLATFORM_LINUX -SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriority(Sint64 threadID, int priority); bool SDL_SetLinuxThreadPriority(Sint64 threadID, int priority) { (void)threadID; @@ -39,7 +39,6 @@ bool SDL_SetLinuxThreadPriority(Sint64 threadID, int priority) return SDL_Unsupported(); } -SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy); bool SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy) { (void)threadID; @@ -48,37 +47,32 @@ bool SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int s return SDL_Unsupported(); } -#endif +#endif /* !SDL_PLATFORM_LINUX */ #ifndef SDL_PLATFORM_GDK -SDL_DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void); void SDL_GDKSuspendComplete(void) { SDL_Unsupported(); } -SDL_DECLSPEC bool SDLCALL SDL_GetGDKDefaultUser(void *outUserHandle); /* XUserHandle *outUserHandle */ -bool SDL_GetGDKDefaultUser(void *outUserHandle) +bool SDL_GetGDKDefaultUser(XUserHandle *outUserHandle) { return SDL_Unsupported(); } -SDL_DECLSPEC void SDLCALL SDL_GDKSuspendGPU(SDL_GPUDevice *device); void SDL_GDKSuspendGPU(SDL_GPUDevice *device) { } -SDL_DECLSPEC void SDLCALL SDL_GDKResumeGPU(SDL_GPUDevice *device); void SDL_GDKResumeGPU(SDL_GPUDevice *device) { } -#endif +#endif /* !SDL_PLATFORM_GDK */ #if !defined(SDL_PLATFORM_WINDOWS) -SDL_DECLSPEC bool SDLCALL SDL_RegisterApp(const char *name, Uint32 style, void *hInst); bool SDL_RegisterApp(const char *name, Uint32 style, void *hInst) { (void)name; @@ -87,7 +81,6 @@ bool SDL_RegisterApp(const char *name, Uint32 style, void *hInst) return SDL_Unsupported(); } -SDL_DECLSPEC void SDLCALL SDL_SetWindowsMessageHook(void *callback, void *userdata); // SDL_WindowsMessageHook callback void SDL_SetWindowsMessageHook(void *callback, void *userdata) { (void)callback; @@ -95,66 +88,56 @@ void SDL_SetWindowsMessageHook(void *callback, void *userdata) SDL_Unsupported(); } -SDL_DECLSPEC void SDLCALL SDL_UnregisterApp(void); void SDL_UnregisterApp(void) { SDL_Unsupported(); } -#endif +#endif /* !SDL_PLATFORM_WINDOWS */ #ifndef SDL_PLATFORM_ANDROID -SDL_DECLSPEC void SDLCALL SDL_SendAndroidBackButton(void); void SDL_SendAndroidBackButton(void) { SDL_Unsupported(); } -SDL_DECLSPEC void * SDLCALL SDL_GetAndroidActivity(void); void *SDL_GetAndroidActivity(void) { SDL_Unsupported(); return NULL; } -SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidCachePath(void); -const char* SDL_GetAndroidCachePath(void) +const char *SDL_GetAndroidCachePath(void) { SDL_Unsupported(); return NULL; } -SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidExternalStoragePath(void); -const char* SDL_GetAndroidExternalStoragePath(void) +const char *SDL_GetAndroidExternalStoragePath(void) { SDL_Unsupported(); return NULL; } -SDL_DECLSPEC Uint32 SDLCALL SDL_GetAndroidExternalStorageState(void); Uint32 SDL_GetAndroidExternalStorageState(void) { SDL_Unsupported(); return 0; } -SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidInternalStoragePath(void); const char *SDL_GetAndroidInternalStoragePath(void) { SDL_Unsupported(); return NULL; } -SDL_DECLSPEC void * SDLCALL SDL_GetAndroidJNIEnv(void); void *SDL_GetAndroidJNIEnv(void) { SDL_Unsupported(); return NULL; } -typedef void (SDLCALL *SDL_RequestAndroidPermissionCallback)(void *userdata, const char *permission, bool granted); -SDL_DECLSPEC bool SDLCALL SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPermissionCallback cb, void *userdata); bool SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPermissionCallback cb, void *userdata) { (void)permission; @@ -163,7 +146,6 @@ bool SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPerm return SDL_Unsupported(); } -SDL_DECLSPEC bool SDLCALL SDL_SendAndroidMessage(Uint32 command, int param); bool SDL_SendAndroidMessage(Uint32 command, int param) { (void)command; @@ -171,8 +153,7 @@ bool SDL_SendAndroidMessage(Uint32 command, int param) return SDL_Unsupported(); } -SDL_DECLSPEC bool SDLCALL SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xoffset, int yoffset); -bool SDL_ShowAndroidToast(const char* message, int duration, int gravity, int xoffset, int yoffset) +bool SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xoffset, int yoffset) { (void)message; (void)duration; @@ -182,32 +163,27 @@ bool SDL_ShowAndroidToast(const char* message, int duration, int gravity, int xo return SDL_Unsupported(); } -SDL_DECLSPEC int SDLCALL SDL_GetAndroidSDKVersion(void); int SDL_GetAndroidSDKVersion(void) { return SDL_Unsupported(); } -SDL_DECLSPEC bool SDLCALL SDL_IsChromebook(void); bool SDL_IsChromebook(void) { SDL_Unsupported(); return false; } -SDL_DECLSPEC bool SDLCALL SDL_IsDeXMode(void); bool SDL_IsDeXMode(void) { SDL_Unsupported(); return false; } -SDL_DECLSPEC Sint32 SDLCALL JNI_OnLoad(void *vm, void *reserved); -Sint32 JNI_OnLoad(void *vm, void *reserved) +Sint32 JNI_OnLoad(JavaVM *vm, void *reserved) { (void)vm; (void)reserved; - SDL_Unsupported(); - return -1; // JNI_ERR + return 0x00010004; // JNI_VERSION_1_4 } #endif diff --git a/libs/SDL3/src/core/SDL_core_unsupported.h b/libs/SDL3/src/core/SDL_core_unsupported.h new file mode 100644 index 0000000..3a94287 --- /dev/null +++ b/libs/SDL3/src/core/SDL_core_unsupported.h @@ -0,0 +1,65 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_VIDEO_DRIVER_X11 +extern SDL_DECLSPEC void SDLCALL SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata); +#endif + +#ifndef SDL_PLATFORM_LINUX +extern SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriority(Sint64 threadID, int priority); +extern SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy); +#endif + +#if !defined(SDL_PLATFORM_GDK) +typedef struct XUserHandle XUserHandle; + +extern SDL_DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void); +extern SDL_DECLSPEC bool SDLCALL SDL_GetGDKDefaultUser(XUserHandle *outUserHandle); +extern SDL_DECLSPEC void SDLCALL SDL_GDKSuspendGPU(SDL_GPUDevice *device); +extern SDL_DECLSPEC void SDLCALL SDL_GDKResumeGPU(SDL_GPUDevice *device); +#endif /* !SDL_PLATFORM_GDK */ + +#if !defined(SDL_PLATFORM_WINDOWS) +extern SDL_DECLSPEC bool SDLCALL SDL_RegisterApp(const char *name, Uint32 style, void *hInst); +extern SDL_DECLSPEC void SDLCALL SDL_SetWindowsMessageHook(void *callback, void *userdata); +extern SDL_DECLSPEC void SDLCALL SDL_UnregisterApp(void); +#endif /* !SDL_PLATFORM_WINDOWS */ + +#if !defined(SDL_PLATFORM_ANDROID) + +typedef void *SDL_RequestAndroidPermissionCallback; +typedef void *JavaVM; + +extern SDL_DECLSPEC void SDLCALL SDL_SendAndroidBackButton(void); +extern SDL_DECLSPEC void * SDLCALL SDL_GetAndroidActivity(void); +extern SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidCachePath(void); +extern SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidExternalStoragePath(void); +extern SDL_DECLSPEC Uint32 SDLCALL SDL_GetAndroidExternalStorageState(void); +extern SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidInternalStoragePath(void); +extern SDL_DECLSPEC void * SDLCALL SDL_GetAndroidJNIEnv(void); +extern SDL_DECLSPEC bool SDLCALL SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPermissionCallback cb, void *userdata); +extern SDL_DECLSPEC bool SDLCALL SDL_SendAndroidMessage(Uint32 command, int param); +extern SDL_DECLSPEC bool SDLCALL SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xoffset, int yoffset); +extern SDL_DECLSPEC int SDLCALL SDL_GetAndroidSDKVersion(void); +extern SDL_DECLSPEC bool SDLCALL SDL_IsChromebook(void); +extern SDL_DECLSPEC bool SDLCALL SDL_IsDeXMode(void); +extern SDL_DECLSPEC Sint32 SDLCALL JNI_OnLoad(JavaVM *vm, void *reserved); +#endif /* !SDL_PLATFORM_ANDROID */ diff --git a/libs/SDL3/src/core/android/SDL_android.c b/libs/SDL3/src/core/android/SDL_android.c index 1cc9ccd..fa55948 100644 --- a/libs/SDL3/src/core/android/SDL_android.c +++ b/libs/SDL3/src/core/android/SDL_android.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -96,6 +96,12 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)( JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)( JNIEnv *env, jclass jcls); +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeScreenKeyboardShown)( + JNIEnv *env, jclass jcls); + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeScreenKeyboardHidden)( + JNIEnv *env, jclass jcls); + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)( JNIEnv *env, jclass jcls, jint keycode); @@ -115,13 +121,23 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)( jint touch_device_id_in, jint pointer_finger_id_in, jint action, jfloat x, jfloat y, jfloat p); +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchStart)( + JNIEnv *env, jclass jcls); + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchUpdate)( + JNIEnv *env, jclass jcls, + jfloat scale); + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchEnd)( + JNIEnv *env, jclass jcls); + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)( JNIEnv *env, jclass jcls, jint button, jint action, jfloat x, jfloat y, jboolean relative); JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePen)( JNIEnv *env, jclass jcls, - jint pen_id_in, jint button, jint action, jfloat x, jfloat y, jfloat p); + jint pen_id_in, jint device_type, jint button, jint action, jfloat x, jfloat y, jfloat p); JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)( JNIEnv *env, jclass jcls, @@ -198,7 +214,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)( static JNINativeMethod SDLActivity_tab[] = { { "nativeGetVersion", "()Ljava/lang/String;", SDL_JAVA_INTERFACE(nativeGetVersion) }, - { "nativeSetupJNI", "()I", SDL_JAVA_INTERFACE(nativeSetupJNI) }, + { "nativeSetupJNI", "()V", SDL_JAVA_INTERFACE(nativeSetupJNI) }, { "nativeInitMainThread", "()V", SDL_JAVA_INTERFACE(nativeInitMainThread) }, { "nativeCleanupMainThread", "()V", SDL_JAVA_INTERFACE(nativeCleanupMainThread) }, { "nativeRunMain", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)I", SDL_JAVA_INTERFACE(nativeRunMain) }, @@ -208,13 +224,18 @@ static JNINativeMethod SDLActivity_tab[] = { { "onNativeSurfaceCreated", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceCreated) }, { "onNativeSurfaceChanged", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceChanged) }, { "onNativeSurfaceDestroyed", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed) }, + { "onNativeScreenKeyboardShown", "()V", SDL_JAVA_INTERFACE(onNativeScreenKeyboardShown) }, + { "onNativeScreenKeyboardHidden", "()V", SDL_JAVA_INTERFACE(onNativeScreenKeyboardHidden) }, { "onNativeKeyDown", "(I)V", SDL_JAVA_INTERFACE(onNativeKeyDown) }, { "onNativeKeyUp", "(I)V", SDL_JAVA_INTERFACE(onNativeKeyUp) }, { "onNativeSoftReturnKey", "()Z", SDL_JAVA_INTERFACE(onNativeSoftReturnKey) }, { "onNativeKeyboardFocusLost", "()V", SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost) }, { "onNativeTouch", "(IIIFFF)V", SDL_JAVA_INTERFACE(onNativeTouch) }, + { "onNativePinchStart", "()V", SDL_JAVA_INTERFACE(onNativePinchStart) }, + { "onNativePinchUpdate", "(F)V", SDL_JAVA_INTERFACE(onNativePinchUpdate) }, + { "onNativePinchEnd", "()V", SDL_JAVA_INTERFACE(onNativePinchEnd) }, { "onNativeMouse", "(IIFFZ)V", SDL_JAVA_INTERFACE(onNativeMouse) }, - { "onNativePen", "(IIIFFF)V", SDL_JAVA_INTERFACE(onNativePen) }, + { "onNativePen", "(IIIIFFF)V", SDL_JAVA_INTERFACE(onNativePen) }, { "onNativeAccel", "(FFF)V", SDL_JAVA_INTERFACE(onNativeAccel) }, { "onNativeClipboardChanged", "()V", SDL_JAVA_INTERFACE(onNativeClipboardChanged) }, { "nativeLowMemory", "()V", SDL_JAVA_INTERFACE(nativeLowMemory) }, @@ -257,17 +278,17 @@ JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)( JNIEnv *env, jclass jcls); JNIEXPORT void JNICALL - SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jstring name, + SDL_JAVA_AUDIO_INTERFACE(nativeAddAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jstring name, jint device_id); JNIEXPORT void JNICALL - SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, + SDL_JAVA_AUDIO_INTERFACE(nativeRemoveAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jint device_id); static JNINativeMethod SDLAudioManager_tab[] = { - { "nativeSetupJNI", "()I", SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI) }, - { "addAudioDevice", "(ZLjava/lang/String;I)V", SDL_JAVA_AUDIO_INTERFACE(addAudioDevice) }, - { "removeAudioDevice", "(ZI)V", SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice) } + { "nativeSetupJNI", "()V", SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI) }, + { "nativeAddAudioDevice", "(ZLjava/lang/String;I)V", SDL_JAVA_AUDIO_INTERFACE(nativeAddAudioDevice) }, + { "nativeRemoveAudioDevice", "(ZI)V", SDL_JAVA_AUDIO_INTERFACE(nativeRemoveAudioDevice) } }; // Java class SDLControllerManager @@ -293,7 +314,7 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)( JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( JNIEnv *env, jclass jcls, jint device_id, jstring device_name, jstring device_desc, jint vendor_id, jint product_id, - jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble); + jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble, jboolean has_rgb_led); JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)( JNIEnv *env, jclass jcls, @@ -308,12 +329,12 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)( jint device_id); static JNINativeMethod SDLControllerManager_tab[] = { - { "nativeSetupJNI", "()I", SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI) }, + { "nativeSetupJNI", "()V", SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI) }, { "onNativePadDown", "(II)Z", SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown) }, { "onNativePadUp", "(II)Z", SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp) }, { "onNativeJoy", "(IIF)V", SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy) }, { "onNativeHat", "(IIII)V", SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat) }, - { "nativeAddJoystick", "(ILjava/lang/String;Ljava/lang/String;IIIIIIZ)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick) }, + { "nativeAddJoystick", "(ILjava/lang/String;Ljava/lang/String;IIIIIIZZ)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick) }, { "nativeRemoveJoystick", "(I)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick) }, { "nativeAddHaptic", "(ILjava/lang/String;)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic) }, { "nativeRemoveHaptic", "(I)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic) } @@ -352,7 +373,6 @@ static jmethodID midInitTouch; static jmethodID midIsAndroidTV; static jmethodID midIsChromebook; static jmethodID midIsDeXMode; -static jmethodID midIsScreenKeyboardShown; static jmethodID midIsTablet; static jmethodID midManualBackButton; static jmethodID midMinimizeWindow; @@ -386,6 +406,7 @@ static jclass mControllerManagerClass; // method signatures static jmethodID midPollInputDevices; +static jmethodID midJoystickSetLED; static jmethodID midPollHapticDevices; static jmethodID midHapticRun; static jmethodID midHapticRumble; @@ -635,14 +656,13 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl midClipboardSetText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardSetText", "(Ljava/lang/String;)V"); midCreateCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "createCustomCursor", "([IIIII)I"); midDestroyCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "destroyCustomCursor", "(I)V"); - midGetContext = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;"); + midGetContext = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/app/Activity;"); midGetManifestEnvironmentVariables = (*env)->GetStaticMethodID(env, mActivityClass, "getManifestEnvironmentVariables", "()Z"); midGetNativeSurface = (*env)->GetStaticMethodID(env, mActivityClass, "getNativeSurface", "()Landroid/view/Surface;"); midInitTouch = (*env)->GetStaticMethodID(env, mActivityClass, "initTouch", "()V"); midIsAndroidTV = (*env)->GetStaticMethodID(env, mActivityClass, "isAndroidTV", "()Z"); midIsChromebook = (*env)->GetStaticMethodID(env, mActivityClass, "isChromebook", "()Z"); midIsDeXMode = (*env)->GetStaticMethodID(env, mActivityClass, "isDeXMode", "()Z"); - midIsScreenKeyboardShown = (*env)->GetStaticMethodID(env, mActivityClass, "isScreenKeyboardShown", "()Z"); midIsTablet = (*env)->GetStaticMethodID(env, mActivityClass, "isTablet", "()Z"); midManualBackButton = (*env)->GetStaticMethodID(env, mActivityClass, "manualBackButton", "()V"); midMinimizeWindow = (*env)->GetStaticMethodID(env, mActivityClass, "minimizeWindow", "()V"); @@ -675,7 +695,6 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl !midIsAndroidTV || !midIsChromebook || !midIsDeXMode || - !midIsScreenKeyboardShown || !midIsTablet || !midManualBackButton || !midMinimizeWindow || @@ -734,6 +753,8 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env midPollInputDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass, "pollInputDevices", "()V"); + midJoystickSetLED = (*env)->GetStaticMethodID(env, mControllerManagerClass, + "joystickSetLED", "(IIII)V"); midPollHapticDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass, "pollHapticDevices", "()V"); midHapticRun = (*env)->GetStaticMethodID(env, mControllerManagerClass, @@ -743,16 +764,13 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env midHapticStop = (*env)->GetStaticMethodID(env, mControllerManagerClass, "hapticStop", "(I)V"); - if (!midPollInputDevices || !midPollHapticDevices || !midHapticRun || !midHapticRumble || !midHapticStop) { + if (!midPollInputDevices || !midJoystickSetLED || !midPollHapticDevices || !midHapticRun || !midHapticRumble || !midHapticStop) { __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLControllerManager.java?"); } checkJNIReady(); } -// SDL main function prototype -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; @@ -822,47 +840,61 @@ JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv *env, jclass cls, function_name = (*env)->GetStringUTFChars(env, function, NULL); SDL_main = (SDL_main_func)dlsym(library_handle, function_name); if (SDL_main) { - int i; - int argc; - int len; - char **argv; - bool isstack; + // Use the name "app_process" for argv[0] so PHYSFS_platformCalcBaseDir() works. + // https://github.com/love2d/love-android/issues/24 + // (note that PhysicsFS hasn't used argv on Android in a long time, but we'll keep this for compat at least for SDL3's lifetime. --ryan.) + const char *argv0 = "app_process"; + const int len = (*env)->GetArrayLength(env, array); // argv elements, not counting argv[0]. - // Prepare the arguments. - len = (*env)->GetArrayLength(env, array); - argv = SDL_small_alloc(char *, 1 + len + 1, &isstack); // !!! FIXME: check for NULL - argc = 0; - /* Use the name "app_process" so PHYSFS_platformCalcBaseDir() works. - https://github.com/love2d/love-android/issues/24 - */ - argv[argc++] = SDL_strdup("app_process"); - for (i = 0; i < len; ++i) { - char *arg = NULL; + size_t total_alloc_len = (SDL_strlen(argv0) + 1) + ((len + 2) * sizeof (char *)); // len+2 to allocate an array that also holds argv0 and a NULL terminator. + for (int i = 0; i < len; ++i) { + total_alloc_len++; // null terminator. jstring string = (*env)->GetObjectArrayElement(env, array, i); if (string) { const char *utf = (*env)->GetStringUTFChars(env, string, 0); if (utf) { - arg = SDL_strdup(utf); + total_alloc_len += SDL_strlen(utf) + 1; (*env)->ReleaseStringUTFChars(env, string, utf); } (*env)->DeleteLocalRef(env, string); } - if (arg == NULL) { - arg = SDL_strdup(""); + } + + void *args = malloc(total_alloc_len); // This should NOT be SDL_malloc() + if (!args) { // uhoh. + __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Out of memory parsing command line!"); + } else { + size_t remain = total_alloc_len - (sizeof (char *) * (len + 2)); + int argc = 0; + char **argv = (char **) args; + char *ptr = (char *) &argv[len + 2]; + size_t cpy = SDL_strlcpy(ptr, argv0, remain) + 1; + argv[argc++] = ptr; + SDL_assert(cpy <= remain); remain -= cpy; ptr += cpy; + for (int i = 0; i < len; ++i) { + jstring string = (*env)->GetObjectArrayElement(env, array, i); + const char *utf = string ? (*env)->GetStringUTFChars(env, string, 0) : NULL; + cpy = SDL_strlcpy(ptr, utf ? utf : "", remain) + 1; + if (cpy < remain) { + argv[argc++] = ptr; + remain -= cpy; + ptr += cpy; + } + if (utf) { + (*env)->ReleaseStringUTFChars(env, string, utf); + } + if (string) { + (*env)->DeleteLocalRef(env, string); + } } - argv[argc++] = arg; + argv[argc] = NULL; + + // Run the application. + status = SDL_RunApp(argc, argv, SDL_main, NULL); + + // Release the arguments. + free(args); // This should NOT be SDL_free() } - argv[argc] = NULL; - - // Run the application. - status = SDL_main(argc, argv); - - // Release the arguments. - for (i = 0; i < argc; ++i) { - SDL_free(argv[i]); - } - SDL_small_free(argv, isstack); - } else { __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't find function %s in library %s", function_name, library_file); } @@ -1080,13 +1112,14 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)( { const char *utfname = (*env)->GetStringUTFChars(env, name, NULL); - SDL_AddTouch((SDL_TouchID)touchId, SDL_TOUCH_DEVICE_DIRECT, utfname); + SDL_AddTouch(Android_ConvertJavaTouchID(touchId), + SDL_TOUCH_DEVICE_DIRECT, utfname); (*env)->ReleaseStringUTFChars(env, name, utfname); } JNIEXPORT void JNICALL -SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, +SDL_JAVA_AUDIO_INTERFACE(nativeAddAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jstring name, jint device_id) { #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES @@ -1102,7 +1135,7 @@ SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean reco } JNIEXPORT void JNICALL -SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, +SDL_JAVA_AUDIO_INTERFACE(nativeRemoveAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jint device_id) { #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES @@ -1161,13 +1194,13 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( JNIEnv *env, jclass jcls, jint device_id, jstring device_name, jstring device_desc, jint vendor_id, jint product_id, - jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble) + jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble, jboolean has_rgb_led) { #ifdef SDL_JOYSTICK_ANDROID const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL); - Android_AddJoystick(device_id, name, desc, vendor_id, product_id, button_mask, naxes, axis_mask, nhats, can_rumble); + Android_AddJoystick(device_id, name, desc, vendor_id, product_id, button_mask, naxes, axis_mask, nhats, can_rumble, has_rgb_led); (*env)->ReleaseStringUTFChars(env, device_name, name); (*env)->ReleaseStringUTFChars(env, device_desc, desc); @@ -1241,6 +1274,10 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(JNIEnv *env, j } #endif + if (Android_Window) { + Android_RestoreScreenKeyboard(SDL_GetVideoDevice(), Android_Window); + } + SDL_UnlockMutex(Android_ActivityMutex); } @@ -1286,6 +1323,16 @@ retry: SDL_UnlockMutex(Android_ActivityMutex); } +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeScreenKeyboardShown)(JNIEnv *env, jclass jcls) +{ + SDL_SendScreenKeyboardShown(); +} + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeScreenKeyboardHidden)(JNIEnv *env, jclass jcls) +{ + SDL_SendScreenKeyboardHidden(); +} + // Keydown JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)( JNIEnv *env, jclass jcls, @@ -1346,6 +1393,43 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)( SDL_UnlockMutex(Android_ActivityMutex); } +// Pinch +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchStart)( + JNIEnv *env, jclass jcls) +{ + SDL_LockMutex(Android_ActivityMutex); + + if (Android_Window) { + SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, Android_Window, 0); + } + + SDL_UnlockMutex(Android_ActivityMutex); +} + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchUpdate)( + JNIEnv *env, jclass jcls, jfloat scale) +{ + SDL_LockMutex(Android_ActivityMutex); + + if (Android_Window) { + SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, Android_Window, scale); + } + + SDL_UnlockMutex(Android_ActivityMutex); +} + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchEnd)( + JNIEnv *env, jclass jcls) +{ + SDL_LockMutex(Android_ActivityMutex); + + if (Android_Window) { + SDL_SendPinch(SDL_EVENT_PINCH_END, 0, Android_Window, 0); + } + + SDL_UnlockMutex(Android_ActivityMutex); +} + // Mouse JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)( JNIEnv *env, jclass jcls, @@ -1361,11 +1445,11 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)( // Pen JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePen)( JNIEnv *env, jclass jcls, - jint pen_id_in, jint button, jint action, jfloat x, jfloat y, jfloat p) + jint pen_id_in, jint device_type, jint button, jint action, jfloat x, jfloat y, jfloat p) { SDL_LockMutex(Android_ActivityMutex); - Android_OnPen(Android_Window, pen_id_in, button, action, x, y, p); + Android_OnPen(Android_Window, pen_id_in, device_type, button, action, x, y, p); SDL_UnlockMutex(Android_ActivityMutex); } @@ -1644,7 +1728,7 @@ SDL_DisplayOrientation Android_JNI_GetDisplayCurrentOrientation(void) return displayCurrentOrientation; } -void Android_JNI_MinizeWindow(void) +void Android_JNI_MinimizeWindow(void) { JNIEnv *env = Android_JNI_GetEnv(); (*env)->CallStaticVoidMethod(env, mActivityClass, midMinimizeWindow); @@ -1728,18 +1812,24 @@ static bool Android_JNI_ExceptionOccurred(bool silent) exceptionName = (jstring)(*env)->CallObjectMethod(env, exceptionClass, mid); exceptionNameUTF8 = (*env)->GetStringUTFChars(env, exceptionName, 0); + (*env)->DeleteLocalRef(env, classClass); + mid = (*env)->GetMethodID(env, exceptionClass, "getMessage", "()Ljava/lang/String;"); exceptionMessage = (jstring)(*env)->CallObjectMethod(env, exception, mid); + (*env)->DeleteLocalRef(env, exceptionClass); + if (exceptionMessage != NULL) { const char *exceptionMessageUTF8 = (*env)->GetStringUTFChars(env, exceptionMessage, 0); SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8); (*env)->ReleaseStringUTFChars(env, exceptionMessage, exceptionMessageUTF8); + (*env)->DeleteLocalRef(env, exceptionMessage); } else { SDL_SetError("%s", exceptionNameUTF8); } (*env)->ReleaseStringUTFChars(env, exceptionName, exceptionNameUTF8); + (*env)->DeleteLocalRef(env, exceptionName); } return true; @@ -1751,7 +1841,7 @@ static bool Android_JNI_ExceptionOccurred(bool silent) static void Internal_Android_Create_AssetManager(void) { - struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); JNIEnv *env = Android_JNI_GetEnv(); jmethodID mid; jobject context; @@ -1797,6 +1887,17 @@ static void Internal_Android_Destroy_AssetManager(void) } } +static const char *GetAssetPath(const char *path) +{ + if (path && path[0] == '.' && path[1] == '/') { + path += 2; + while (*path == '/') { + ++path; + } + } + return path; +} + bool Android_JNI_FileOpen(void **puserdata, const char *fileName, const char *mode) { SDL_assert(puserdata != NULL); @@ -1806,11 +1907,12 @@ bool Android_JNI_FileOpen(void **puserdata, const char *fileName, const char *mo if (!asset_manager) { Internal_Android_Create_AssetManager(); + if (!asset_manager) { + return SDL_SetError("Couldn't create asset manager"); + } } - if (!asset_manager) { - return SDL_SetError("Couldn't create asset manager"); - } + fileName = GetAssetPath(fileName); asset = AAssetManager_open(asset_manager, fileName, AASSET_MODE_UNKNOWN); if (!asset) { @@ -1826,7 +1928,10 @@ size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOSta const int bytes = AAsset_read((AAsset *)userdata, buffer, size); if (bytes < 0) { SDL_SetError("AAsset_read() failed"); + *status = SDL_IO_STATUS_ERROR; return 0; + } else if (bytes < size) { + *status = SDL_IO_STATUS_EOF; } return (size_t)bytes; } @@ -1834,6 +1939,7 @@ size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOSta size_t Android_JNI_FileWrite(void *userdata, const void *buffer, size_t size, SDL_IOStatus *status) { SDL_SetError("Cannot write to Android package filesystem"); + *status = SDL_IO_STATUS_ERROR; return 0; } @@ -1853,6 +1959,69 @@ bool Android_JNI_FileClose(void *userdata) return true; } +bool Android_JNI_EnumerateAssetDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata) +{ + SDL_assert(path != NULL); + + if (!asset_manager) { + Internal_Android_Create_AssetManager(); + if (!asset_manager) { + return SDL_SetError("Couldn't create asset manager"); + } + } + + path = GetAssetPath(path); + + AAssetDir *adir = AAssetManager_openDir(asset_manager, path); + if (!adir) { + return SDL_SetError("AAssetManager_openDir failed"); + } + + SDL_EnumerationResult result = SDL_ENUM_CONTINUE; + const char *ent; + while ((result == SDL_ENUM_CONTINUE) && ((ent = AAssetDir_getNextFileName(adir)) != NULL)) { + result = cb(userdata, path, ent); + } + + AAssetDir_close(adir); + + return (result != SDL_ENUM_FAILURE); +} + +bool Android_JNI_GetAssetPathInfo(const char *path, SDL_PathInfo *info) +{ + if (!asset_manager) { + Internal_Android_Create_AssetManager(); + if (!asset_manager) { + return SDL_SetError("Couldn't create asset manager"); + } + } + + path = GetAssetPath(path); + + // this is sort of messy, but there isn't a stat()-like interface to the Assets. + AAsset *aasset = AAssetManager_open(asset_manager, path, AASSET_MODE_UNKNOWN); + if (aasset) { // it's a file! + info->type = SDL_PATHTYPE_FILE; + info->size = (Uint64) AAsset_getLength64(aasset); + AAsset_close(aasset); + return true; + } + + AAssetDir *adir = AAssetManager_openDir(asset_manager, path); + if (adir) { // This does _not_ return NULL for a missing directory! Treat empty directories as missing. Better than nothing. :/ + const bool contains_something = (AAssetDir_getNextFileName(adir) != NULL); // if not NULL, there are files in this directory, so it's _definitely_ a directory. + AAssetDir_close(adir); + if (contains_something) { + info->type = SDL_PATHTYPE_DIRECTORY; + info->size = 0; + return true; + } + } + + return SDL_SetError("Couldn't open asset '%s'", path); +} + bool Android_JNI_SetClipboardText(const char *text) { JNIEnv *env = Android_JNI_GetEnv(); @@ -1893,7 +2062,7 @@ bool Android_JNI_HasClipboardText(void) */ int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seconds, int *percent) { - struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); JNIEnv *env = Android_JNI_GetEnv(); jmethodID mid; jobject context; @@ -2020,6 +2189,12 @@ void Android_JNI_PollInputDevices(void) (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollInputDevices); } +void Android_JNI_JoystickSetLED(int device_id, int red, int green, int blue) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midJoystickSetLED, device_id, red, green, blue); +} + void Android_JNI_PollHapticDevices(void) { JNIEnv *env = Android_JNI_GetEnv(); @@ -2049,7 +2224,7 @@ void Android_JNI_HapticStop(int device_id) bool SDL_SendAndroidMessage(Uint32 command, int param) { - if (command < 0x8000) { + CHECK_PARAM(command < 0x8000) { return SDL_InvalidParamError("command"); } return Android_JNI_SendMessage(command, param); @@ -2085,14 +2260,6 @@ void Android_JNI_HideScreenKeyboard(void) Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0); } -bool Android_JNI_IsScreenKeyboardShown(void) -{ - JNIEnv *env = Android_JNI_GetEnv(); - jboolean is_shown = 0; - is_shown = (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsScreenKeyboardShown); - return is_shown; -} - bool Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) { JNIEnv *env; @@ -2163,7 +2330,7 @@ bool Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *b mid = (*env)->GetMethodID(env, clazz, "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I"); *buttonID = (*env)->CallIntMethod(env, context, mid, - messageboxdata->flags, + (jint)messageboxdata->flags, title, message, button_flags, @@ -2258,7 +2425,7 @@ const char *SDL_GetAndroidInternalStoragePath(void) static char *s_AndroidInternalFilesPath = NULL; if (!s_AndroidInternalFilesPath) { - struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); jmethodID mid; jobject context; jobject fileObject; @@ -2309,7 +2476,7 @@ const char *SDL_GetAndroidInternalStoragePath(void) Uint32 SDL_GetAndroidExternalStorageState(void) { - struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); jmethodID mid; jclass cls; jstring stateString; @@ -2352,7 +2519,7 @@ const char *SDL_GetAndroidExternalStoragePath(void) static char *s_AndroidExternalFilesPath = NULL; if (!s_AndroidExternalFilesPath) { - struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); jmethodID mid; jobject context; jobject fileObject; @@ -2398,7 +2565,7 @@ const char *SDL_GetAndroidCachePath(void) static char *s_AndroidCachePath = NULL; if (!s_AndroidCachePath) { - struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); jmethodID mid; jobject context; jobject fileObject; @@ -2417,7 +2584,7 @@ const char *SDL_GetAndroidCachePath(void) // fileObj = context.getExternalFilesDir(); mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), "getCacheDir", "()Ljava/io/File;"); - fileObject = (*env)->CallObjectMethod(env, context, mid, NULL); + fileObject = (*env)->CallObjectMethod(env, context, mid); if (!fileObject) { SDL_SetError("Couldn't get cache directory"); LocalReferenceHolder_Cleanup(&refs); @@ -2691,7 +2858,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)( // Convert fileList to string size_t count = (*env)->GetArrayLength(env, fileList); - char **charFileList = SDL_calloc(count + 1, sizeof(char*)); + char **charFileList = SDL_calloc(count + 1, sizeof(char *)); if (charFileList == NULL) { mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1); @@ -2747,7 +2914,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)( } bool Android_JNI_OpenFileDialog( - SDL_DialogFileCallback callback, void* userdata, + SDL_DialogFileCallback callback, void *userdata, const SDL_DialogFileFilter *filters, int nfilters, bool forwrite, bool multiple) { @@ -2767,6 +2934,7 @@ bool Android_JNI_OpenFileDialog( if (filters) { jclass stringClass = (*env)->FindClass(env, "java/lang/String"); filtersArray = (*env)->NewObjectArray(env, nfilters, stringClass, NULL); + (*env)->DeleteLocalRef(env, stringClass); // Convert to string for (int i = 0; i < nfilters; i++) { diff --git a/libs/SDL3/src/core/android/SDL_android.h b/libs/SDL3/src/core/android/SDL_android.h index 620639c..9bb44eb 100644 --- a/libs/SDL3/src/core/android/SDL_android.h +++ b/libs/SDL3/src/core/android/SDL_android.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -61,13 +61,12 @@ void Android_SetAllowRecreateActivity(bool enabled); extern void Android_JNI_SetActivityTitle(const char *title); extern void Android_JNI_SetWindowStyle(bool fullscreen); extern void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint); -extern void Android_JNI_MinizeWindow(void); +extern void Android_JNI_MinimizeWindow(void); extern bool Android_JNI_ShouldMinimizeOnFocusLoss(void); extern bool Android_JNI_GetAccelerometerValues(float values[3]); extern void Android_JNI_ShowScreenKeyboard(int input_type, SDL_Rect *inputRect); extern void Android_JNI_HideScreenKeyboard(void); -extern bool Android_JNI_IsScreenKeyboardShown(void); extern ANativeWindow *Android_JNI_GetNativeWindow(void); extern SDL_DisplayOrientation Android_JNI_GetDisplayNaturalOrientation(void); @@ -88,6 +87,8 @@ Sint64 Android_JNI_FileSeek(void *userdata, Sint64 offset, SDL_IOWhence whence); size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOStatus *status); size_t Android_JNI_FileWrite(void *userdata, const void *buffer, size_t size, SDL_IOStatus *status); bool Android_JNI_FileClose(void *userdata); +bool Android_JNI_EnumerateAssetDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata); +bool Android_JNI_GetAssetPathInfo(const char *path, SDL_PathInfo *info); // Environment support void Android_JNI_GetManifestEnvironmentVariables(void); @@ -103,6 +104,7 @@ int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seco // Joystick support void Android_JNI_PollInputDevices(void); +void Android_JNI_JoystickSetLED(int device_id, int red, int green, int blue); // Haptic support void Android_JNI_PollHapticDevices(void); @@ -151,7 +153,7 @@ bool SDL_IsAndroidTablet(void); bool SDL_IsAndroidTV(void); // File Dialogs -bool Android_JNI_OpenFileDialog(SDL_DialogFileCallback callback, void* userdata, +bool Android_JNI_OpenFileDialog(SDL_DialogFileCallback callback, void *userdata, const SDL_DialogFileFilter *filters, int nfilters, bool forwrite, bool multiple); diff --git a/libs/SDL3/src/core/freebsd/SDL_evdev_kbd_freebsd.c b/libs/SDL3/src/core/freebsd/SDL_evdev_kbd_freebsd.c index 16a2171..17c8ddf 100644 --- a/libs/SDL3/src/core/freebsd/SDL_evdev_kbd_freebsd.c +++ b/libs/SDL3/src/core/freebsd/SDL_evdev_kbd_freebsd.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -27,19 +27,26 @@ // This logic is adapted from drivers/tty/vt/keyboard.c in the Linux kernel source, slightly modified to work with FreeBSD +#include #include #include #include #include #include +#include #include #include "../../events/SDL_events_c.h" #include "SDL_evdev_kbd_default_keyaccmap.h" +#ifndef K_OFF +#define K_OFF 0x04 +#endif + typedef void(fn_handler_fn)(SDL_EVDEV_keyboard_state *kbd); + /* * Keyboard State */ @@ -48,6 +55,7 @@ struct SDL_EVDEV_keyboard_state { int console_fd; int keyboard_fd; + bool muted; unsigned long old_kbd_mode; unsigned short **key_maps; keymap_t *key_map; @@ -63,6 +71,10 @@ struct SDL_EVDEV_keyboard_state char shift_state; char text[128]; unsigned int text_len; + void (*vt_release_callback)(void *); + void *vt_release_callback_data; + void (*vt_acquire_callback)(void *); + void *vt_acquire_callback_data; }; static bool SDL_EVDEV_kbd_load_keymaps(SDL_EVDEV_keyboard_state *kbd) @@ -83,22 +95,28 @@ static int fatal_signals[] = { SIGSYS }; -static void kbd_cleanup(void) +static void vt_update_mouse(SDL_EVDEV_keyboard_state *kbd, int operation) { struct mouse_info mData; + + SDL_zero(mData); + mData.operation = operation; + ioctl(kbd->console_fd, CONS_MOUSECTL, &mData); +} + +static void kbd_cleanup(void) +{ SDL_EVDEV_keyboard_state *kbd = kbd_cleanup_state; if (!kbd) { return; } kbd_cleanup_state = NULL; - SDL_zero(mData); - mData.operation = MOUSE_SHOW; ioctl(kbd->keyboard_fd, KDSKBMODE, kbd->old_kbd_mode); if (kbd->keyboard_fd != kbd->console_fd) { close(kbd->keyboard_fd); } ioctl(kbd->console_fd, CONS_SETKBD, (unsigned long)(kbd->kbInfo->kb_index)); - ioctl(kbd->console_fd, CONS_MOUSECTL, &mData); + vt_update_mouse(kbd, true); } void SDL_EVDEV_kbd_reraise_signal(int sig) @@ -144,7 +162,7 @@ static void kbd_unregister_emerg_cleanup(void) } kbd_cleanup_sigactions_installed = 0; - for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) { + for (tabidx = 0; tabidx < SDL_arraysize(fatal_signals); ++tabidx) { struct sigaction *old_action_p; struct sigaction cur_action; int signum = fatal_signals[tabidx]; @@ -197,7 +215,7 @@ static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state *kbd) } kbd_cleanup_sigactions_installed = 1; - for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) { + for (tabidx = 0; tabidx < SDL_arraysize(fatal_signals); ++tabidx) { struct sigaction *old_action_p; struct sigaction new_action; int signum = fatal_signals[tabidx]; @@ -220,15 +238,108 @@ static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state *kbd) } } +enum { + VT_SIGNAL_NONE, + VT_SIGNAL_RELEASE, + VT_SIGNAL_ACQUIRE, +}; +static int vt_release_signal; +static int vt_acquire_signal; +static SDL_AtomicInt vt_signal_pending; +SDL_AtomicInt vt_current; + +typedef void (*signal_handler)(int signum); + + +static void kbd_vt_release_signal_action(int signum) +{ + SDL_SetAtomicInt(&vt_signal_pending, VT_SIGNAL_RELEASE); + SDL_SetAtomicInt(&vt_current, VT_THEIRS); +} + +static void kbd_vt_acquire_signal_action(int signum) +{ + SDL_SetAtomicInt(&vt_signal_pending, VT_SIGNAL_ACQUIRE); + SDL_SetAtomicInt(&vt_current, VT_OURS); +} + +static bool setup_vt_signal(int signum, signal_handler handler) +{ + struct sigaction *old_action_p; + struct sigaction new_action; + old_action_p = &(old_sigaction[signum]); + SDL_zero(new_action); + new_action.sa_handler = handler; + new_action.sa_flags = SA_RESTART; + if (sigaction(signum, &new_action, old_action_p) < 0) { + return false; + } + if (old_action_p->sa_handler != SIG_DFL) { + // This signal is already in use + if (signum == SIGUSR1 || signum == SIGUSR2) { + /* + * For the moment we have no other options in FreeBSD because + * the vt(4) will not accept signal numbers over 32. + */ + return true; + } + sigaction(signum, old_action_p, NULL); + return false; + } + return true; +} + +static void kbd_vt_quit(int console_fd) +{ + struct vt_mode mode; + + if (vt_release_signal) { + sigaction(vt_release_signal, &old_sigaction[vt_release_signal], NULL); + vt_release_signal = 0; + } + if (vt_acquire_signal) { + sigaction(vt_acquire_signal, &old_sigaction[vt_acquire_signal], NULL); + vt_acquire_signal = 0; + } + + SDL_zero(mode); + mode.mode = VT_AUTO; + ioctl(console_fd, VT_SETMODE, &mode); +} + +static bool kbd_vt_init(int console_fd) +{ + struct vt_mode mode; + + if (setup_vt_signal(SIGUSR1, kbd_vt_release_signal_action)) { + vt_release_signal = SIGUSR1; + } + if (setup_vt_signal(SIGUSR2, kbd_vt_acquire_signal_action)) { + vt_acquire_signal = SIGUSR2; + } + if (!vt_release_signal || !vt_acquire_signal ) { + kbd_vt_quit(console_fd); + return false; + } + + SDL_zero(mode); + mode.mode = VT_PROCESS; + mode.relsig = vt_release_signal; + mode.acqsig = vt_acquire_signal; + mode.frsig = SIGIO; + if (ioctl(console_fd, VT_SETMODE, &mode) < 0) { + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed VT_SETMODE ioctl: %s", strerror(errno)); + kbd_vt_quit(console_fd); + return false; + } + return true; +} + SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void) { SDL_EVDEV_keyboard_state *kbd; - struct mouse_info mData; char flag_state; - char *devicePath; - SDL_zero(mData); - mData.operation = MOUSE_HIDE; kbd = (SDL_EVDEV_keyboard_state *)SDL_calloc(1, sizeof(SDL_EVDEV_keyboard_state)); if (!kbd) { return NULL; @@ -246,7 +357,6 @@ SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void) kbd->kbInfo = SDL_calloc(1, sizeof(keyboard_info_t)); ioctl(kbd->console_fd, KDGKBINFO, kbd->kbInfo); - ioctl(kbd->console_fd, CONS_MOUSECTL, &mData); if (ioctl(kbd->console_fd, KDGKBSTATE, &flag_state) == 0) { kbd->ledflagstate = flag_state; @@ -265,43 +375,31 @@ SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void) kbd->key_map = &keymap_default_us_acc; } - if (SDL_GetHintBoolean(SDL_HINT_MUTE_CONSOLE_KEYBOARD, true)) { - /* Take keyboard from console and open the actual keyboard device. - * Ensures that the keystrokes do not leak through to the console. - */ - ioctl(kbd->console_fd, CONS_RELKBD, 1ul); - SDL_asprintf(&devicePath, "/dev/kbd%d", kbd->kbInfo->kb_index); - kbd->keyboard_fd = open(devicePath, O_WRONLY | O_CLOEXEC); - if (kbd->keyboard_fd == -1) { - // Give keyboard back. - ioctl(kbd->console_fd, CONS_SETKBD, (unsigned long)(kbd->kbInfo->kb_index)); - kbd->keyboard_fd = kbd->console_fd; - } + if (!kbd_vt_init(kbd->console_fd)) { + SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "kbd_vt_init failed"); + } - /* Make sure to restore keyboard if application fails to call - * SDL_Quit before exit or fatal signal is raised. - */ - if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, false)) { - kbd_register_emerg_cleanup(kbd); - } - SDL_free(devicePath); - } else - kbd->keyboard_fd = kbd->console_fd; + kbd->keyboard_fd = kbd->console_fd; + + if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, false)) { + kbd_register_emerg_cleanup(kbd); + } } + vt_update_mouse(kbd, MOUSE_HIDE); + return kbd; } void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *kbd) { - struct mouse_info mData; - if (!kbd) { return; } - SDL_zero(mData); - mData.operation = MOUSE_SHOW; - ioctl(kbd->console_fd, CONS_MOUSECTL, &mData); + + kbd_vt_quit(kbd->console_fd); + + vt_update_mouse(kbd, MOUSE_SHOW); kbd_unregister_emerg_cleanup(); @@ -322,14 +420,83 @@ void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *kbd) void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted) { + struct termios tios; + + SDL_zero(tios); + + if (!state) { + return; + } + + if (muted == state->muted) { + return; + } + + if (tcgetattr(state->console_fd, &tios) == -1) { + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Could not get terminal mode: %s", strerror(errno)); + return; + } + + if (muted) { + if (SDL_GetHintBoolean(SDL_HINT_MUTE_CONSOLE_KEYBOARD, true)) { + ioctl(state->console_fd, KDSKBMODE, K_OFF); + cfmakeraw(&tios); + + if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, false)) { + kbd_register_emerg_cleanup(state); + } + } + } else { + kbd_unregister_emerg_cleanup(); + + cfmakesane(&tios); + ioctl(state->console_fd, KDSKBMODE, state->old_kbd_mode); + } + + if (tcsetattr(state->console_fd, TCSAFLUSH, &tios) == -1) { + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Could not set terminal mode to %s: %s", muted ? "raw" : "sane", strerror(errno)); + return; + } + + state->muted = muted; } -void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data) +void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void *), void *release_callback_data, void (*acquire_callback)(void *), void *acquire_callback_data) { + if (state == NULL) { + return; + } + + state->vt_release_callback = release_callback; + state->vt_release_callback_data = release_callback_data; + state->vt_acquire_callback = acquire_callback; + state->vt_acquire_callback_data = acquire_callback_data; } void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state) { + if (!state) { + return; + } + + int signal_pending = SDL_GetAtomicInt(&vt_signal_pending); + + if (signal_pending != VT_SIGNAL_NONE) { + if (signal_pending == VT_SIGNAL_RELEASE) { + if (state->vt_release_callback) { + vt_update_mouse(state, MOUSE_SHOW); + state->vt_release_callback(state->vt_release_callback_data); + } + ioctl(state->console_fd, VT_RELDISP, 1); + } else { + if (state->vt_acquire_callback) { + state->vt_acquire_callback(state->vt_acquire_callback_data); + vt_update_mouse(state, MOUSE_HIDE); + } + ioctl(state->console_fd, VT_RELDISP, VT_ACKACQ); + } + SDL_CompareAndSwapAtomicInt(&vt_signal_pending, signal_pending, VT_SIGNAL_NONE); + } } /* diff --git a/libs/SDL3/src/core/gdk/SDL_gdk.cpp b/libs/SDL3/src/core/gdk/SDL_gdk.cpp index 4e792ef..c449527 100644 --- a/libs/SDL3/src/core/gdk/SDL_gdk.cpp +++ b/libs/SDL3/src/core/gdk/SDL_gdk.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/gdk/SDL_gdk.h b/libs/SDL3/src/core/gdk/SDL_gdk.h index 14f4e6f..37d470c 100644 --- a/libs/SDL3/src/core/gdk/SDL_gdk.h +++ b/libs/SDL3/src/core/gdk/SDL_gdk.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/haiku/SDL_BApp.h b/libs/SDL3/src/core/haiku/SDL_BApp.h index cbd3e3a..4e6e9c9 100644 --- a/libs/SDL3/src/core/haiku/SDL_BApp.h +++ b/libs/SDL3/src/core/haiku/SDL_BApp.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -82,7 +82,7 @@ extern "C" SDL_BLooper *SDL_Looper; class SDL_BLooper : public BLooper { public: - SDL_BLooper(const char* name) : BLooper(name) + SDL_BLooper(const char *name) : BLooper(name) { #ifdef SDL_VIDEO_OPENGL _current_context = NULL; diff --git a/libs/SDL3/src/core/haiku/SDL_BeApp.cc b/libs/SDL3/src/core/haiku/SDL_BeApp.cc index 350f7f3..e1f7d5d 100644 --- a/libs/SDL3/src/core/haiku/SDL_BeApp.cc +++ b/libs/SDL3/src/core/haiku/SDL_BeApp.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -56,7 +56,7 @@ const char *SDL_signature = "application/x-SDL-executable"; // Create a descendant of BApplication class SDL_BApp : public BApplication { public: - SDL_BApp(const char* signature) : + SDL_BApp(const char *signature) : BApplication(signature) { } @@ -65,7 +65,7 @@ public: } - virtual void RefsReceived(BMessage* message) { + virtual void RefsReceived(BMessage *message) { entry_ref entryRef; for (int32 i = 0; message->FindRef("refs", i, &entryRef) == B_OK; i++) { BPath referencePath = BPath(&entryRef); diff --git a/libs/SDL3/src/core/haiku/SDL_BeApp.h b/libs/SDL3/src/core/haiku/SDL_BeApp.h index de5bb6b..178adab 100644 --- a/libs/SDL3/src/core/haiku/SDL_BeApp.h +++ b/libs/SDL3/src/core/haiku/SDL_BeApp.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/linux/SDL_dbus.c b/libs/SDL3/src/core/linux/SDL_dbus.c index 9a2fc1e..2d083e8 100644 --- a/libs/SDL3/src/core/linux/SDL_dbus.c +++ b/libs/SDL3/src/core/linux/SDL_dbus.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -24,12 +24,20 @@ #ifdef SDL_USE_LIBDBUS // we never link directly to libdbus. -static const char *dbus_library = "libdbus-1.so.3"; +#define SDL_DRIVER_DBUS_DYNAMIC "libdbus-1.so.3" +static const char *dbus_library = SDL_DRIVER_DBUS_DYNAMIC; static SDL_SharedObject *dbus_handle = NULL; static char *inhibit_handle = NULL; static unsigned int screensaver_cookie = 0; static SDL_DBusContext dbus; +SDL_ELF_NOTE_DLOPEN( + "core-libdbus", + "Support for D-Bus IPC", + SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SDL_DRIVER_DBUS_DYNAMIC +) + static bool LoadDBUSSyms(void) { #define SDL_DBUS_SYM2_OPTIONAL(TYPE, x, y) \ @@ -48,6 +56,8 @@ static bool LoadDBUSSyms(void) SDL_DBUS_SYM(DBusConnection *(*)(DBusBusType, DBusError *), bus_get_private); SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusError *), bus_register); SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_add_match); + SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_remove_match); + SDL_DBUS_SYM(const char *(*)(DBusConnection *), bus_get_unique_name); SDL_DBUS_SYM(DBusConnection *(*)(const char *, DBusError *), connection_open_private); SDL_DBUS_SYM(void (*)(DBusConnection *, dbus_bool_t), connection_set_exit_on_disconnect); SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *), connection_get_is_connected); @@ -61,10 +71,14 @@ static bool LoadDBUSSyms(void) SDL_DBUS_SYM(void (*)(DBusConnection *), connection_unref); SDL_DBUS_SYM(void (*)(DBusConnection *), connection_flush); SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write); + SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write_dispatch); SDL_DBUS_SYM(DBusDispatchStatus (*)(DBusConnection *), connection_dispatch); + SDL_DBUS_SYM(dbus_bool_t (*)(int), type_is_fixed); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_signal); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *), message_has_path); SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *, const char *), message_new_method_call); + SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *), message_new_signal); + SDL_DBUS_SYM(void (*)(DBusMessage *, dbus_bool_t), message_set_no_reply); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, ...), message_append_args); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, va_list), message_append_args_valist); SDL_DBUS_SYM(void (*)(DBusMessage *, DBusMessageIter *), message_iter_init_append); @@ -82,6 +96,7 @@ static bool LoadDBUSSyms(void) SDL_DBUS_SYM(dbus_bool_t (*)(void), threads_init_default); SDL_DBUS_SYM(void (*)(DBusError *), error_init); SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *), error_is_set); + SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *, const char *), error_has_name); SDL_DBUS_SYM(void (*)(DBusError *), error_free); SDL_DBUS_SYM(char *(*)(void), get_local_machine_id); SDL_DBUS_SYM_OPTIONAL(char *(*)(DBusError *), try_get_local_machine_id); @@ -216,7 +231,7 @@ SDL_DBusContext *SDL_DBus_GetContext(void) return (dbus_handle && dbus.session_conn) ? &dbus : NULL; } -static bool SDL_DBus_CallMethodInternal(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, va_list ap) +static bool SDL_DBus_CallMethodInternal(DBusConnection *conn, DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *method, va_list ap) { bool result = false; @@ -248,7 +263,11 @@ static bool SDL_DBus_CallMethodInternal(DBusConnection *conn, const char *node, if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_get_args_valist(reply, NULL, firstarg, ap_reply)) { result = true; } - dbus.message_unref(reply); + if (save_reply) { + *save_reply = reply; + } else { + dbus.message_unref(reply); + } } } va_end(ap_reply); @@ -259,22 +278,22 @@ static bool SDL_DBus_CallMethodInternal(DBusConnection *conn, const char *node, return result; } -bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...) +bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *method, ...) { bool result; va_list ap; va_start(ap, method); - result = SDL_DBus_CallMethodInternal(conn, node, path, interface, method, ap); + result = SDL_DBus_CallMethodInternal(conn, save_reply, node, path, interface, method, ap); va_end(ap); return result; } -bool SDL_DBus_CallMethod(const char *node, const char *path, const char *interface, const char *method, ...) +bool SDL_DBus_CallMethod(DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *method, ...) { bool result; va_list ap; va_start(ap, method); - result = SDL_DBus_CallMethodInternal(dbus.session_conn, node, path, interface, method, ap); + result = SDL_DBus_CallMethodInternal(dbus.session_conn, save_reply, node, path, interface, method, ap); va_end(ap); return result; } @@ -288,6 +307,7 @@ static bool SDL_DBus_CallVoidMethodInternal(DBusConnection *conn, const char *no if (msg) { int firstarg = va_arg(ap, int); if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_append_args_valist(msg, firstarg, ap)) { + dbus.message_set_no_reply(msg, true); if (dbus.connection_send(conn, msg, NULL)) { dbus.connection_flush(conn); result = true; @@ -301,31 +321,6 @@ static bool SDL_DBus_CallVoidMethodInternal(DBusConnection *conn, const char *no return result; } -static bool SDL_DBus_CallWithBasicReply(DBusConnection *conn, DBusMessage *msg, const int expectedtype, void *result) -{ - bool retval = false; - - DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL); - if (reply) { - DBusMessageIter iter, actual_iter; - dbus.message_iter_init(reply, &iter); - if (dbus.message_iter_get_arg_type(&iter) == DBUS_TYPE_VARIANT) { - dbus.message_iter_recurse(&iter, &actual_iter); - } else { - actual_iter = iter; - } - - if (dbus.message_iter_get_arg_type(&actual_iter) == expectedtype) { - dbus.message_iter_get_basic(&actual_iter, result); - retval = true; - } - - dbus.message_unref(reply); - } - - return retval; -} - bool SDL_DBus_CallVoidMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...) { bool result; @@ -346,7 +341,40 @@ bool SDL_DBus_CallVoidMethod(const char *node, const char *path, const char *int return result; } -bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result) +static bool SDL_DBus_CallWithBasicReply(DBusConnection *conn, DBusMessage **save_reply, DBusMessage *msg, const int expectedtype, void *result) +{ + bool retval = false; + + // Make sure we're not looking up strings here, otherwise we'd have to save and return the reply + SDL_assert(save_reply == NULL || *save_reply == NULL); + SDL_assert(save_reply != NULL || dbus.type_is_fixed(expectedtype)); + + DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL); + if (reply) { + DBusMessageIter iter, actual_iter; + dbus.message_iter_init(reply, &iter); + if (dbus.message_iter_get_arg_type(&iter) == DBUS_TYPE_VARIANT) { + dbus.message_iter_recurse(&iter, &actual_iter); + } else { + actual_iter = iter; + } + + if (dbus.message_iter_get_arg_type(&actual_iter) == expectedtype) { + dbus.message_iter_get_basic(&actual_iter, result); + retval = true; + } + + if (save_reply) { + *save_reply = reply; + } else { + dbus.message_unref(reply); + } + } + + return retval; +} + +bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result) { bool retval = false; @@ -354,7 +382,7 @@ bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, DBusMessage *msg = dbus.message_new_method_call(node, path, "org.freedesktop.DBus.Properties", "Get"); if (msg) { if (dbus.message_append_args(msg, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) { - retval = SDL_DBus_CallWithBasicReply(conn, msg, expectedtype, result); + retval = SDL_DBus_CallWithBasicReply(conn, save_reply, msg, expectedtype, result); } dbus.message_unref(msg); } @@ -363,9 +391,18 @@ bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, return retval; } -bool SDL_DBus_QueryProperty(const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result) +bool SDL_DBus_QueryProperty(DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result) { - return SDL_DBus_QueryPropertyOnConnection(dbus.session_conn, node, path, interface, property, expectedtype, result); + return SDL_DBus_QueryPropertyOnConnection(dbus.session_conn, save_reply, node, path, interface, property, expectedtype, result); +} + +void SDL_DBus_FreeReply(DBusMessage **saved_reply) +{ + DBusMessage *reply = *saved_reply; + if (reply) { + dbus.message_unref(reply); + *saved_reply = NULL; + } } void SDL_DBus_ScreensaverTickle(void) @@ -460,7 +497,7 @@ bool SDL_DBus_ScreensaverInhibit(bool inhibit) DBusMessage *msg; bool result = false; const char *key = "reason"; - const char *reply = NULL; + const char *reply_path = NULL; const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME); if (!reason || !reason[0]) { reason = default_inhibit_reason; @@ -484,11 +521,12 @@ bool SDL_DBus_ScreensaverInhibit(bool inhibit) return false; } - if (SDL_DBus_CallWithBasicReply(dbus.session_conn, msg, DBUS_TYPE_OBJECT_PATH, &reply)) { - inhibit_handle = SDL_strdup(reply); + DBusMessage *reply = NULL; + if (SDL_DBus_CallWithBasicReply(dbus.session_conn, &reply, msg, DBUS_TYPE_OBJECT_PATH, &reply_path)) { + inhibit_handle = SDL_strdup(reply_path); result = true; } - + SDL_DBus_FreeReply(&reply); dbus.message_unref(msg); return result; } else { @@ -510,7 +548,7 @@ bool SDL_DBus_ScreensaverInhibit(bool inhibit) reason = default_inhibit_reason; } - if (!SDL_DBus_CallMethod(bus_name, path, interface, "Inhibit", + if (!SDL_DBus_CallMethod(NULL, bus_name, path, interface, "Inhibit", DBUS_TYPE_STRING, &app, DBUS_TYPE_STRING, &reason, DBUS_TYPE_INVALID, DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) { return false; @@ -638,4 +676,184 @@ failed: return NULL; } +typedef struct SDL_DBus_CameraPortalMessageHandlerData +{ + uint32_t response; + char *path; + DBusError *err; + bool done; +} SDL_DBus_CameraPortalMessageHandlerData; + +static DBusHandlerResult SDL_DBus_CameraPortalMessageHandler(DBusConnection *conn, DBusMessage *msg, void *v) +{ + SDL_DBus_CameraPortalMessageHandlerData *data = v; + const char *name, *old, *new; + + if (dbus.message_is_signal(msg, "org.freedesktop.DBus", "NameOwnerChanged")) { + if (!dbus.message_get_args(msg, data->err, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old, + DBUS_TYPE_STRING, &new, + DBUS_TYPE_INVALID)) { + data->done = true; + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + if (SDL_strcmp(name, "org.freedesktop.portal.Desktop") != 0 || + SDL_strcmp(new, "") != 0) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + data->done = true; + data->response = -1; + return DBUS_HANDLER_RESULT_HANDLED; + } + if (!dbus.message_has_path(msg, data->path) || !dbus.message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + dbus.message_get_args(msg, data->err, DBUS_TYPE_UINT32, &data->response, DBUS_TYPE_INVALID); + data->done = true; + return DBUS_HANDLER_RESULT_HANDLED; +} + +#define SIGNAL_NAMEOWNERCHANGED "type='signal',\ + sender='org.freedesktop.DBus',\ + interface='org.freedesktop.DBus',\ + member='NameOwnerChanged',\ + arg0='org.freedesktop.portal.Desktop',\ + arg2=''" + +/* + * Requests access for the camera. Returns -1 on error, -2 on denied access or + * missing portal, otherwise returns a file descriptor to be used by the Pipewire driver. + * https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Camera.html + */ +int SDL_DBus_CameraPortalRequestAccess(void) +{ + SDL_DBus_CameraPortalMessageHandlerData data; + DBusError err; + DBusMessageIter iter, iterDict; + DBusMessage *reply, *msg; + int fd; + + if (SDL_GetSandbox() == SDL_SANDBOX_NONE) { + return -2; + } + + if (!SDL_DBus_GetContext()) { + return -2; + } + + dbus.error_init(&err); + + msg = dbus.message_new_method_call("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Camera", + "AccessCamera"); + + dbus.message_iter_init_append(msg, &iter); + if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) || + !dbus.message_iter_close_container(&iter, &iterDict)) { + SDL_OutOfMemory(); + dbus.message_unref(msg); + goto failed; + } + + reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err); + dbus.message_unref(msg); + + if (reply) { + dbus.message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, &data.path, DBUS_TYPE_INVALID); + if (dbus.error_is_set(&err)) { + dbus.message_unref(reply); + goto failed; + } + if ((data.path = SDL_strdup(data.path)) == NULL) { + dbus.message_unref(reply); + SDL_OutOfMemory(); + goto failed; + } + dbus.message_unref(reply); + } else { + if (dbus.error_has_name(&err, DBUS_ERROR_NAME_HAS_NO_OWNER)) { + return -2; + } + goto failed; + } + + dbus.bus_add_match(dbus.session_conn, SIGNAL_NAMEOWNERCHANGED, &err); + if (dbus.error_is_set(&err)) { + SDL_free(data.path); + goto failed; + } + data.err = &err; + data.done = false; + if (!dbus.connection_add_filter(dbus.session_conn, SDL_DBus_CameraPortalMessageHandler, &data, NULL)) { + SDL_free(data.path); + SDL_OutOfMemory(); + goto failed; + } + while (!data.done && dbus.connection_read_write_dispatch(dbus.session_conn, -1)) { + ; + } + + dbus.bus_remove_match(dbus.session_conn, SIGNAL_NAMEOWNERCHANGED, &err); + if (dbus.error_is_set(&err)) { + SDL_free(data.path); + goto failed; + } + dbus.connection_remove_filter(dbus.session_conn, SDL_DBus_CameraPortalMessageHandler, &data); + SDL_free(data.path); + if (!data.done) { + goto failed; + } + if (dbus.error_is_set(&err)) { // from the message handler + goto failed; + } + if (data.response == 1 || data.response == 2) { + return -2; + } else if (data.response != 0) { + goto failed; + } + + msg = dbus.message_new_method_call("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Camera", + "OpenPipeWireRemote"); + + dbus.message_iter_init_append(msg, &iter); + if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) || + !dbus.message_iter_close_container(&iter, &iterDict)) { + SDL_OutOfMemory(); + dbus.message_unref(msg); + goto failed; + } + + reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err); + dbus.message_unref(msg); + + if (reply) { + dbus.message_get_args(reply, &err, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_INVALID); + dbus.message_unref(reply); + if (dbus.error_is_set(&err)) { + goto failed; + } + } else { + goto failed; + } + + return fd; + +failed: + if (dbus.error_is_set(&err)) { + if (dbus.error_has_name(&err, DBUS_ERROR_NO_MEMORY)) { + SDL_OutOfMemory(); + } + SDL_SetError("%s: %s", err.name, err.message); + dbus.error_free(&err); + } else { + SDL_SetError("Error requesting access for the camera"); + } + + return -1; +} + #endif diff --git a/libs/SDL3/src/core/linux/SDL_dbus.h b/libs/SDL3/src/core/linux/SDL_dbus.h index 2073d6c..e6f81b4 100644 --- a/libs/SDL3/src/core/linux/SDL_dbus.h +++ b/libs/SDL3/src/core/linux/SDL_dbus.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -34,6 +34,9 @@ #ifndef DBUS_TIMEOUT_INFINITE #define DBUS_TIMEOUT_INFINITE ((int) 0x7fffffff) #endif +#ifndef DBUS_TYPE_UNIX_FD +#define DBUS_TYPE_UNIX_FD ((int) 'h') +#endif typedef struct SDL_DBusContext { @@ -43,6 +46,8 @@ typedef struct SDL_DBusContext DBusConnection *(*bus_get_private)(DBusBusType, DBusError *); dbus_bool_t (*bus_register)(DBusConnection *, DBusError *); void (*bus_add_match)(DBusConnection *, const char *, DBusError *); + void (*bus_remove_match)(DBusConnection *, const char *, DBusError *); + const char *(*bus_get_unique_name)(DBusConnection *); DBusConnection *(*connection_open_private)(const char *, DBusError *); void (*connection_set_exit_on_disconnect)(DBusConnection *, dbus_bool_t); dbus_bool_t (*connection_get_is_connected)(DBusConnection *); @@ -57,10 +62,14 @@ typedef struct SDL_DBusContext void (*connection_unref)(DBusConnection *); void (*connection_flush)(DBusConnection *); dbus_bool_t (*connection_read_write)(DBusConnection *, int); + dbus_bool_t (*connection_read_write_dispatch)(DBusConnection *, int); DBusDispatchStatus (*connection_dispatch)(DBusConnection *); + dbus_bool_t (*type_is_fixed)(int); dbus_bool_t (*message_is_signal)(DBusMessage *, const char *, const char *); dbus_bool_t (*message_has_path)(DBusMessage *, const char *); DBusMessage *(*message_new_method_call)(const char *, const char *, const char *, const char *); + DBusMessage *(*message_new_signal)(const char *, const char *, const char *); + void (*message_set_no_reply)(DBusMessage *, dbus_bool_t); dbus_bool_t (*message_append_args)(DBusMessage *, int, ...); dbus_bool_t (*message_append_args_valist)(DBusMessage *, int, va_list); void (*message_iter_init_append)(DBusMessage *, DBusMessageIter *); @@ -78,6 +87,7 @@ typedef struct SDL_DBusContext dbus_bool_t (*threads_init_default)(void); void (*error_init)(DBusError *); dbus_bool_t (*error_is_set)(const DBusError *); + dbus_bool_t (*error_has_name)(const DBusError *, const char *); void (*error_free)(DBusError *); char *(*get_local_machine_id)(void); char *(*try_get_local_machine_id)(DBusError *); @@ -92,14 +102,19 @@ extern void SDL_DBus_Quit(void); extern SDL_DBusContext *SDL_DBus_GetContext(void); // These use the built-in Session connection. -extern bool SDL_DBus_CallMethod(const char *node, const char *path, const char *interface, const char *method, ...); +extern bool SDL_DBus_CallMethod(DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *method, ...); extern bool SDL_DBus_CallVoidMethod(const char *node, const char *path, const char *interface, const char *method, ...); -extern bool SDL_DBus_QueryProperty(const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result); +// save_reply must be non-NULL if it's a string property +extern bool SDL_DBus_QueryProperty(DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result); // These use whatever connection you like. -extern bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...); +extern bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *method, ...); extern bool SDL_DBus_CallVoidMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...); -extern bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result); +// save_reply must be non-NULL if it's a string property +extern bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result); + +// Used to free any reply returned from SDL_DBus_CallMethod() and SDL_DBus_QueryProperty() +extern void SDL_DBus_FreeReply(DBusMessage **saved_reply); extern void SDL_DBus_ScreensaverTickle(void); extern bool SDL_DBus_ScreensaverInhibit(bool inhibit); @@ -109,6 +124,8 @@ extern char *SDL_DBus_GetLocalMachineId(void); extern char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *files_count); +extern int SDL_DBus_CameraPortalRequestAccess(void); + #endif // HAVE_DBUS_DBUS_H #endif // SDL_dbus_h_ diff --git a/libs/SDL3/src/core/linux/SDL_evdev.c b/libs/SDL3/src/core/linux/SDL_evdev.c index 5746e2e..a6ef26e 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev.c +++ b/libs/SDL3/src/core/linux/SDL_evdev.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -32,10 +32,11 @@ #include "SDL_evdev.h" #include "SDL_evdev_kbd.h" -#include -#include +#include #include +#include #include +#include #include #include "../../events/SDL_events_c.h" @@ -287,8 +288,8 @@ static void SDL_EVDEV_udev_callback(SDL_UDEV_deviceevent udev_event, int udev_cl } #endif // SDL_USE_LIBUDEV -void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void*), void *release_callback_data, - void (*acquire_callback)(void*), void *acquire_callback_data) +void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void *), void *release_callback_data, + void (*acquire_callback)(void *), void *acquire_callback_data) { SDL_EVDEV_kbd_set_vt_switch_callbacks(_this->kbd, release_callback, release_callback_data, @@ -322,16 +323,17 @@ void SDL_EVDEV_Poll(void) return; } -#ifdef SDL_USE_LIBUDEV - SDL_UDEV_Poll(); -#endif - SDL_EVDEV_kbd_update(_this->kbd); mouse = SDL_GetMouse(); for (item = _this->first; item; item = item->next) { while ((len = read(item->fd, events, sizeof(events))) > 0) { +#ifdef SDL_INPUT_FBSDKBIO + if (SDL_GetAtomicInt(&vt_current) == VT_THEIRS) { + continue; + } +#endif len /= sizeof(events[0]); for (i = 0; i < len; ++i) { struct input_event *event = &events[i]; @@ -546,16 +548,16 @@ void SDL_EVDEV_Poll(void) * be window-relative in that case. */ switch (item->touchscreen_data->slots[j].delta) { case EVDEV_TOUCH_SLOTDELTA_DOWN: - SDL_SendTouch(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, NULL, SDL_EVENT_FINGER_DOWN, norm_x, norm_y, norm_pressure); + SDL_SendTouch(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, mouse->focus, SDL_EVENT_FINGER_DOWN, norm_x, norm_y, norm_pressure); item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE; break; case EVDEV_TOUCH_SLOTDELTA_UP: - SDL_SendTouch(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, NULL, SDL_EVENT_FINGER_UP, norm_x, norm_y, norm_pressure); + SDL_SendTouch(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, mouse->focus, SDL_EVENT_FINGER_UP, norm_x, norm_y, norm_pressure); item->touchscreen_data->slots[j].tracking_id = 0; item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE; break; case EVDEV_TOUCH_SLOTDELTA_MOVE: - SDL_SendTouchMotion(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, NULL, norm_x, norm_y, norm_pressure); + SDL_SendTouchMotion(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, mouse->focus, norm_x, norm_y, norm_pressure); item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE; break; default: @@ -612,14 +614,14 @@ static bool SDL_EVDEV_init_keyboard(SDL_evdevlist_item *item, int udev_class) name[0] = '\0'; ioctl(item->fd, EVIOCGNAME(sizeof(name)), name); - SDL_AddKeyboard((SDL_KeyboardID)item->fd, name, true); + SDL_AddKeyboard((SDL_KeyboardID)item->fd, name); return true; } static void SDL_EVDEV_destroy_keyboard(SDL_evdevlist_item *item) { - SDL_RemoveKeyboard((SDL_KeyboardID)item->fd, true); + SDL_RemoveKeyboard((SDL_KeyboardID)item->fd); } static bool SDL_EVDEV_init_mouse(SDL_evdevlist_item *item, int udev_class) @@ -631,7 +633,7 @@ static bool SDL_EVDEV_init_mouse(SDL_evdevlist_item *item, int udev_class) name[0] = '\0'; ioctl(item->fd, EVIOCGNAME(sizeof(name)), name); - SDL_AddMouse((SDL_MouseID)item->fd, name, true); + SDL_AddMouse((SDL_MouseID)item->fd, name); ret = ioctl(item->fd, EVIOCGABS(ABS_X), &abs_info); if (ret < 0) { @@ -656,7 +658,7 @@ static bool SDL_EVDEV_init_mouse(SDL_evdevlist_item *item, int udev_class) static void SDL_EVDEV_destroy_mouse(SDL_evdevlist_item *item) { - SDL_RemoveMouse((SDL_MouseID)item->fd, true); + SDL_RemoveMouse((SDL_MouseID)item->fd); } static bool SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) @@ -678,7 +680,8 @@ static bool SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) ret = ioctl(item->fd, EVIOCGNAME(sizeof(name)), name); if (ret < 0) { SDL_free(item->touchscreen_data); - return SDL_SetError("Failed to get evdev touchscreen name"); + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed to get evdev touchscreen name"); + return false; } item->touchscreen_data->name = SDL_strdup(name); @@ -691,7 +694,8 @@ static bool SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) if (ret < 0) { SDL_free(item->touchscreen_data->name); SDL_free(item->touchscreen_data); - return SDL_SetError("Failed to get evdev touchscreen limits"); + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed to get evdev touchscreen limits"); + return false; } if (abs_info.maximum == 0) { @@ -708,7 +712,8 @@ static bool SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) if (ret < 0) { SDL_free(item->touchscreen_data->name); SDL_free(item->touchscreen_data); - return SDL_SetError("Failed to get evdev touchscreen limits"); + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed to get evdev touchscreen limits"); + return false; } item->touchscreen_data->min_x = abs_info.minimum; item->touchscreen_data->max_x = abs_info.maximum; @@ -718,7 +723,8 @@ static bool SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) if (ret < 0) { SDL_free(item->touchscreen_data->name); SDL_free(item->touchscreen_data); - return SDL_SetError("Failed to get evdev touchscreen limits"); + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed to get evdev touchscreen limits"); + return false; } item->touchscreen_data->min_y = abs_info.minimum; item->touchscreen_data->max_y = abs_info.maximum; @@ -728,7 +734,8 @@ static bool SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) if (ret < 0) { SDL_free(item->touchscreen_data->name); SDL_free(item->touchscreen_data); - return SDL_SetError("Failed to get evdev touchscreen limits"); + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed to get evdev touchscreen limits"); + return false; } item->touchscreen_data->min_pressure = abs_info.minimum; item->touchscreen_data->max_pressure = abs_info.maximum; @@ -912,8 +919,9 @@ static bool SDL_EVDEV_device_added(const char *dev_path, int udev_class) item->fd = open(dev_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (item->fd < 0) { + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Couldn't open %s: %s", dev_path, strerror(errno)); SDL_free(item); - return SDL_SetError("Unable to open %s", dev_path); + return false; } item->path = SDL_strdup(dev_path); diff --git a/libs/SDL3/src/core/linux/SDL_evdev.h b/libs/SDL3/src/core/linux/SDL_evdev.h index d3e2fe1..8ddf476 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev.h +++ b/libs/SDL3/src/core/linux/SDL_evdev.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -30,8 +30,8 @@ struct input_event; extern bool SDL_EVDEV_Init(void); extern void SDL_EVDEV_Quit(void); -extern void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void*), void *release_callback_data, - void (*acquire_callback)(void*), void *acquire_callback_data); +extern void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void *), void *release_callback_data, + void (*acquire_callback)(void *), void *acquire_callback_data); extern int SDL_EVDEV_GetDeviceCount(int device_class); extern void SDL_EVDEV_Poll(void); extern Uint64 SDL_EVDEV_GetEventTimestamp(struct input_event *event); diff --git a/libs/SDL3/src/core/linux/SDL_evdev_capabilities.c b/libs/SDL3/src/core/linux/SDL_evdev_capabilities.c index 61e240e..38c891c 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev_capabilities.c +++ b/libs/SDL3/src/core/linux/SDL_evdev_capabilities.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga Copyright (C) 2020 Collabora Ltd. This software is provided 'as-is', without any express or implied diff --git a/libs/SDL3/src/core/linux/SDL_evdev_capabilities.h b/libs/SDL3/src/core/linux/SDL_evdev_capabilities.h index 5a70bdd..e7f081e 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev_capabilities.h +++ b/libs/SDL3/src/core/linux/SDL_evdev_capabilities.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga Copyright (C) 2020 Collabora Ltd. This software is provided 'as-is', without any express or implied diff --git a/libs/SDL3/src/core/linux/SDL_evdev_kbd.c b/libs/SDL3/src/core/linux/SDL_evdev_kbd.c index b1a5664..679f901 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev_kbd.c +++ b/libs/SDL3/src/core/linux/SDL_evdev_kbd.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -224,7 +224,7 @@ static void kbd_unregister_emerg_cleanup(void) } kbd_cleanup_sigactions_installed = 0; - for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) { + for (tabidx = 0; tabidx < SDL_arraysize(fatal_signals); ++tabidx) { struct sigaction *old_action_p; struct sigaction cur_action; int signum = fatal_signals[tabidx]; @@ -277,7 +277,7 @@ static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state *kbd) } kbd_cleanup_sigactions_installed = 1; - for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) { + for (tabidx = 0; tabidx < SDL_arraysize(fatal_signals); ++tabidx) { struct sigaction *old_action_p; struct sigaction new_action; int signum = fatal_signals[tabidx]; @@ -495,7 +495,7 @@ void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted) state->muted = muted; } -void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data) +void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void *), void *release_callback_data, void (*acquire_callback)(void *), void *acquire_callback_data) { if (state == NULL) { return; @@ -534,9 +534,7 @@ void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state) if (state->key_maps && state->key_maps != default_key_maps) { int i; for (i = 0; i < MAX_NR_KEYMAPS; ++i) { - if (state->key_maps[i]) { - SDL_free(state->key_maps[i]); - } + SDL_free(state->key_maps[i]); } SDL_free(state->key_maps); } @@ -978,7 +976,7 @@ void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted) { } -void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data) +void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void *), void *release_callback_data, void (*acquire_callback)(void *), void *acquire_callback_data) { } diff --git a/libs/SDL3/src/core/linux/SDL_evdev_kbd.h b/libs/SDL3/src/core/linux/SDL_evdev_kbd.h index 6ea19fb..488548a 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev_kbd.h +++ b/libs/SDL3/src/core/linux/SDL_evdev_kbd.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -22,12 +22,20 @@ #ifndef SDL_evdev_kbd_h_ #define SDL_evdev_kbd_h_ +#ifdef SDL_INPUT_FBSDKBIO +enum { + VT_OURS, + VT_THEIRS, +}; +extern SDL_AtomicInt vt_current; +#endif + struct SDL_EVDEV_keyboard_state; typedef struct SDL_EVDEV_keyboard_state SDL_EVDEV_keyboard_state; extern SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void); extern void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted); -extern void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data); +extern void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void *), void *release_callback_data, void (*acquire_callback)(void *), void *acquire_callback_data); extern void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state); extern void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down); extern void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state); diff --git a/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_accents.h b/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_accents.h index b6d034c..7e3bb29 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_accents.h +++ b/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_accents.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_keymap.h b/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_keymap.h index 0529454..9a23e52 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_keymap.h +++ b/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_keymap.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/linux/SDL_fcitx.c b/libs/SDL3/src/core/linux/SDL_fcitx.c index d7a9ed6..d47b44e 100644 --- a/libs/SDL3/src/core/linux/SDL_fcitx.c +++ b/libs/SDL3/src/core/linux/SDL_fcitx.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -25,6 +25,7 @@ #include "SDL_fcitx.h" #include "../../video/SDL_sysvideo.h" #include "../../events/SDL_keyboard_c.h" +#include "../../core/unix/SDL_appid.h" #include "SDL_dbus.h" #ifdef SDL_VIDEO_DRIVER_X11 @@ -53,32 +54,13 @@ typedef struct FcitxClient static FcitxClient fcitx_client; -static char *GetAppName(void) +static const char *GetAppName(void) { -#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_FREEBSD) - char *spot; - char procfile[1024]; - char linkfile[1024]; - int linksize; - -#ifdef SDL_PLATFORM_LINUX - (void)SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/exe", getpid()); -#elif defined(SDL_PLATFORM_FREEBSD) - (void)SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/file", getpid()); -#endif - linksize = readlink(procfile, linkfile, sizeof(linkfile) - 1); - if (linksize > 0) { - linkfile[linksize] = '\0'; - spot = SDL_strrchr(linkfile, '/'); - if (spot) { - return SDL_strdup(spot + 1); - } else { - return SDL_strdup(linkfile); - } + const char *exe_name = SDL_GetExeName(); + if (exe_name) { + return exe_name; } -#endif // SDL_PLATFORM_LINUX || SDL_PLATFORM_FREEBSD - - return SDL_strdup("SDL_App"); + return "SDL_App"; } static size_t Fcitx_GetPreeditString(SDL_DBusContext *dbus, @@ -261,7 +243,7 @@ static bool FcitxCreateInputContext(SDL_DBusContext *dbus, const char *appname, DBusMessageIter args, array, sub; dbus->message_iter_init_append(msg, &args); dbus->message_iter_open_container(&args, DBUS_TYPE_ARRAY, "(ss)", &array); - dbus->message_iter_open_container(&array, DBUS_TYPE_STRUCT, 0, &sub); + dbus->message_iter_open_container(&array, DBUS_TYPE_STRUCT, NULL, &sub); dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &program); dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &appname); dbus->message_iter_close_container(&array, &sub); @@ -281,7 +263,7 @@ static bool FcitxCreateInputContext(SDL_DBusContext *dbus, const char *appname, static bool FcitxClientCreateIC(FcitxClient *client) { - char *appname = GetAppName(); + const char *appname = GetAppName(); char *ic_path = NULL; SDL_DBusContext *dbus = client->dbus; @@ -290,8 +272,6 @@ static bool FcitxClientCreateIC(FcitxClient *client) ic_path = NULL; // just in case. } - SDL_free(appname); - if (ic_path) { SDL_free(client->ic_path); client->ic_path = SDL_strdup(ic_path); @@ -390,7 +370,7 @@ bool SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down) return false; } - if (SDL_DBus_CallMethod(FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "ProcessKeyEvent", + if (SDL_DBus_CallMethod(NULL, FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "ProcessKeyEvent", DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &mod_state, DBUS_TYPE_BOOLEAN, &is_release, DBUS_TYPE_UINT32, &event_time, DBUS_TYPE_INVALID, DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID)) { if (handled) { diff --git a/libs/SDL3/src/core/linux/SDL_fcitx.h b/libs/SDL3/src/core/linux/SDL_fcitx.h index 82c544e..9ea1141 100644 --- a/libs/SDL3/src/core/linux/SDL_fcitx.h +++ b/libs/SDL3/src/core/linux/SDL_fcitx.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/linux/SDL_ibus.c b/libs/SDL3/src/core/linux/SDL_ibus.c index ea58ed5..b0d96ea 100644 --- a/libs/SDL3/src/core/linux/SDL_ibus.c +++ b/libs/SDL3/src/core/linux/SDL_ibus.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -371,7 +371,7 @@ static char *IBus_GetDBusAddressFilename(void) } } - SDL_memset(config_dir, 0, sizeof(config_dir)); + SDL_zeroa(config_dir); conf_env = SDL_getenv("XDG_CONFIG_HOME"); if (conf_env && *conf_env) { @@ -392,7 +392,7 @@ static char *IBus_GetDBusAddressFilename(void) return NULL; } - SDL_memset(file_path, 0, sizeof(file_path)); + SDL_zeroa(file_path); (void)SDL_snprintf(file_path, sizeof(file_path), "%s/ibus/bus/%s-%s-%s", config_dir, key, host, disp_num); dbus->free(key); @@ -426,6 +426,7 @@ static void SDLCALL IBus_SetCapabilities(void *data, const char *name, const cha static bool IBus_SetupConnection(SDL_DBusContext *dbus, const char *addr) { const char *client_name = "SDL3_Application"; + DBusMessage *reply = NULL; const char *path = NULL; bool result = false; DBusObjectPathVTable ibus_vtable; @@ -442,7 +443,7 @@ static bool IBus_SetupConnection(SDL_DBusContext *dbus, const char *addr) ibus_input_interface = IBUS_PORTAL_INPUT_INTERFACE; ibus_conn = dbus->session_conn; - result = SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, IBUS_PATH, ibus_interface, "CreateInputContext", + result = SDL_DBus_CallMethodOnConnection(ibus_conn, &reply, ibus_service, IBUS_PATH, ibus_interface, "CreateInputContext", DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); if (!result) { @@ -465,7 +466,7 @@ static bool IBus_SetupConnection(SDL_DBusContext *dbus, const char *addr) dbus->connection_flush(ibus_conn); - result = SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, IBUS_PATH, ibus_interface, "CreateInputContext", + result = SDL_DBus_CallMethodOnConnection(ibus_conn, &reply, ibus_service, IBUS_PATH, ibus_interface, "CreateInputContext", DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); } else { @@ -483,6 +484,7 @@ static bool IBus_SetupConnection(SDL_DBusContext *dbus, const char *addr) dbus->connection_try_register_object_path(ibus_conn, input_ctx_path, &ibus_vtable, dbus, NULL); dbus->connection_flush(ibus_conn); } + SDL_DBus_FreeReply(&reply); SDL_Window *window = SDL_GetKeyboardFocus(); if (SDL_TextInputActive(window)) { @@ -563,9 +565,7 @@ bool SDL_IBus_Init(void) return false; } - if (ibus_addr_file) { - SDL_free(ibus_addr_file); - } + SDL_free(ibus_addr_file); ibus_addr_file = SDL_strdup(addr_file); if (inotify_fd < 0) { @@ -637,7 +637,7 @@ void SDL_IBus_Quit(void) SDL_RemoveHintCallback(SDL_HINT_IME_IMPLEMENTED_UI, IBus_SetCapabilities, NULL); - SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect)); + SDL_zero(ibus_cursor_rect); } static void IBus_SimpleMessage(const char *method) @@ -671,7 +671,7 @@ bool SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down) if (!down) { mods |= (1 << 30); // IBUS_RELEASE_MASK } - if (!SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, input_ctx_path, ibus_input_interface, "ProcessKeyEvent", + if (!SDL_DBus_CallMethodOnConnection(ibus_conn, NULL, ibus_service, input_ctx_path, ibus_input_interface, "ProcessKeyEvent", DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &ibus_keycode, DBUS_TYPE_UINT32, &mods, DBUS_TYPE_INVALID, DBUS_TYPE_BOOLEAN, &result, DBUS_TYPE_INVALID)) { result = 0; diff --git a/libs/SDL3/src/core/linux/SDL_ibus.h b/libs/SDL3/src/core/linux/SDL_ibus.h index ba08be6..ceeea6f 100644 --- a/libs/SDL3/src/core/linux/SDL_ibus.h +++ b/libs/SDL3/src/core/linux/SDL_ibus.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/linux/SDL_ime.c b/libs/SDL3/src/core/linux/SDL_ime.c index c15defb..9c971b5 100644 --- a/libs/SDL3/src/core/linux/SDL_ime.c +++ b/libs/SDL3/src/core/linux/SDL_ime.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/linux/SDL_ime.h b/libs/SDL3/src/core/linux/SDL_ime.h index ac192fa..386c784 100644 --- a/libs/SDL3/src/core/linux/SDL_ime.h +++ b/libs/SDL3/src/core/linux/SDL_ime.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/linux/SDL_progressbar.c b/libs/SDL3/src/core/linux/SDL_progressbar.c new file mode 100644 index 0000000..ca66d96 --- /dev/null +++ b/libs/SDL3/src/core/linux/SDL_progressbar.c @@ -0,0 +1,163 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_progressbar.h" +#include "SDL_internal.h" + +#include "SDL_dbus.h" + +#ifdef SDL_USE_LIBDBUS + +#include + +#include "../unix/SDL_appid.h" + +#define UnityLauncherAPI_DBUS_INTERFACE "com.canonical.Unity.LauncherEntry" +#define UnityLauncherAPI_DBUS_SIGNAL "Update" + +static char *GetDBUSObjectPath(void) +{ + char *app_id = SDL_strdup(SDL_GetAppID()); + + if (!app_id) { + return NULL; + } + + // Sanitize exe_name to make it a legal D-Bus path element + for (char *p = app_id; *p; ++p) { + if (!SDL_isalnum(*p)) { + *p = '_'; + } + } + + // Ensure it starts with a letter or underscore + if (!SDL_isalpha(app_id[0]) && app_id[0] != '_') { + app_id = SDL_realloc(app_id, SDL_strlen(app_id) + 2); + if (!app_id) { + return NULL; + } + SDL_memmove(app_id + 1, app_id, SDL_strlen(app_id) + 1); + app_id[0] = '_'; + } + + // Create full path + char *path; + if (SDL_asprintf(&path, "/org/libsdl/%s_%d", app_id, getpid()) < 0) { + path = NULL; + } + + SDL_free(app_id); + + return path; +} + +static char *GetAppDesktopPath(void) +{ + const char *desktop_suffix = ".desktop"; + const char *app_id = SDL_GetAppID(); + const size_t desktop_path_total_length = SDL_strlen(app_id) + SDL_strlen(desktop_suffix) + 1; + char *desktop_path = (char *)SDL_malloc(desktop_path_total_length); + if (!desktop_path) { + return NULL; + } + *desktop_path = '\0'; + SDL_strlcat(desktop_path, app_id, desktop_path_total_length); + SDL_strlcat(desktop_path, desktop_suffix, desktop_path_total_length); + + return desktop_path; +} + +static int ShouldShowProgress(SDL_ProgressState progressState) +{ + if (progressState == SDL_PROGRESS_STATE_INVALID || + progressState == SDL_PROGRESS_STATE_NONE) { + return 0; + } + + // Unity LauncherAPI only supports "normal" display of progress + return 1; +} + +bool DBUS_ApplyWindowProgress(SDL_VideoDevice *_this, SDL_Window *window) +{ + // Signal signature: + // signal com.canonical.Unity.LauncherEntry.Update (in s app_uri, in a{sv} properties) + + SDL_DBusContext *dbus = SDL_DBus_GetContext(); + + if (!dbus || !dbus->session_conn) { + return false; + } + + char *objectPath = GetDBUSObjectPath(); + if (!objectPath) { + return false; + } + + DBusMessage *msg = dbus->message_new_signal(objectPath, UnityLauncherAPI_DBUS_INTERFACE, UnityLauncherAPI_DBUS_SIGNAL); + if (!msg) { + SDL_free(objectPath); + return false; + } + + char *desktop_path = GetAppDesktopPath(); + if (!desktop_path) { + dbus->message_unref(msg); + SDL_free(objectPath); + return false; + } + + const char *progress_visible_str = "progress-visible"; + const char *progress_str = "progress"; + + const int progress_visible = ShouldShowProgress(window->progress_state); + double progress = (double)window->progress_value; + + DBusMessageIter args, props; + dbus->message_iter_init_append(msg, &args); + dbus->message_iter_append_basic(&args, DBUS_TYPE_STRING, &desktop_path); // Setup app_uri parameter + dbus->message_iter_open_container(&args, DBUS_TYPE_ARRAY, "{sv}", &props); // Setup properties parameter + DBusMessageIter key_it, value_it; + // Set progress visible property + dbus->message_iter_open_container(&props, DBUS_TYPE_DICT_ENTRY, NULL, &key_it); + dbus->message_iter_append_basic(&key_it, DBUS_TYPE_STRING, &progress_visible_str); // Append progress-visible key data + dbus->message_iter_open_container(&key_it, DBUS_TYPE_VARIANT, "b", &value_it); + dbus->message_iter_append_basic(&value_it, DBUS_TYPE_BOOLEAN, &progress_visible); // Append progress-visible value data + dbus->message_iter_close_container(&key_it, &value_it); + dbus->message_iter_close_container(&props, &key_it); + // Set progress value property + dbus->message_iter_open_container(&props, DBUS_TYPE_DICT_ENTRY, NULL, &key_it); + dbus->message_iter_append_basic(&key_it, DBUS_TYPE_STRING, &progress_str); // Append progress key data + dbus->message_iter_open_container(&key_it, DBUS_TYPE_VARIANT, "d", &value_it); + dbus->message_iter_append_basic(&value_it, DBUS_TYPE_DOUBLE, &progress); // Append progress value data + dbus->message_iter_close_container(&key_it, &value_it); + dbus->message_iter_close_container(&props, &key_it); + dbus->message_iter_close_container(&args, &props); + + dbus->connection_send(dbus->session_conn, msg, NULL); + + SDL_free(desktop_path); + dbus->message_unref(msg); + SDL_free(objectPath); + + return true; +} + +#endif // SDL_USE_LIBDBUS diff --git a/libs/SDL3/src/video/psp/SDL_pspmouse.c b/libs/SDL3/src/core/linux/SDL_progressbar.h similarity index 74% rename from libs/SDL3/src/video/psp/SDL_pspmouse.c rename to libs/SDL3/src/core/linux/SDL_progressbar.h index e63be96..186e46a 100644 --- a/libs/SDL3/src/video/psp/SDL_pspmouse.c +++ b/libs/SDL3/src/core/linux/SDL_progressbar.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -18,20 +18,13 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ + +#ifndef SDL_prograssbar_h_ +#define SDL_prograssbar_h_ + +#include "../../video/SDL_sysvideo.h" #include "SDL_internal.h" -#ifdef SDL_VIDEO_DRIVER_PSP +extern bool DBUS_ApplyWindowProgress(SDL_VideoDevice *_this, SDL_Window *window); -#include - -#include "../../events/SDL_events_c.h" - -#include "SDL_pspmouse_c.h" - -// The implementation dependent data for the window manager cursor -struct WMcursor -{ - int unused; -}; - -#endif // SDL_VIDEO_DRIVER_PSP +#endif // SDL_prograssbar_h_ diff --git a/libs/SDL3/src/core/linux/SDL_system_theme.c b/libs/SDL3/src/core/linux/SDL_system_theme.c index 6d6087d..100661d 100644 --- a/libs/SDL3/src/core/linux/SDL_system_theme.c +++ b/libs/SDL3/src/core/linux/SDL_system_theme.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/linux/SDL_system_theme.h b/libs/SDL3/src/core/linux/SDL_system_theme.h index 2cd55cd..683c21c 100644 --- a/libs/SDL3/src/core/linux/SDL_system_theme.h +++ b/libs/SDL3/src/core/linux/SDL_system_theme.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/linux/SDL_threadprio.c b/libs/SDL3/src/core/linux/SDL_threadprio.c index 1a3e22f..68b1191 100644 --- a/libs/SDL3/src/core/linux/SDL_threadprio.c +++ b/libs/SDL3/src/core/linux/SDL_threadprio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -70,7 +70,7 @@ static Sint64 rtkit_max_rttime_usec = 200000; static bool realtime_portal_supported(DBusConnection *conn) { Sint64 res; - return SDL_DBus_QueryPropertyOnConnection(conn, XDG_PORTAL_DBUS_NODE, XDG_PORTAL_DBUS_PATH, XDG_PORTAL_DBUS_INTERFACE, + return SDL_DBus_QueryPropertyOnConnection(conn, NULL, XDG_PORTAL_DBUS_NODE, XDG_PORTAL_DBUS_PATH, XDG_PORTAL_DBUS_INTERFACE, "RTTimeUSecMax", DBUS_TYPE_INT64, &res); } @@ -111,19 +111,19 @@ static void rtkit_initialize(void) dbus_conn = get_rtkit_dbus_connection(); // Try getting minimum nice level: this is often greater than PRIO_MIN (-20). - if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MinNiceLevel", + if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, NULL, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MinNiceLevel", DBUS_TYPE_INT32, &rtkit_min_nice_level)) { rtkit_min_nice_level = -20; } // Try getting maximum realtime priority: this can be less than the POSIX default (99). - if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MaxRealtimePriority", + if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, NULL, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MaxRealtimePriority", DBUS_TYPE_INT32, &rtkit_max_realtime_priority)) { rtkit_max_realtime_priority = 99; } // Try getting maximum rttime allowed by rtkit: exceeding this value will result in SIGKILL - if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "RTTimeUSecMax", + if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, NULL, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "RTTimeUSecMax", DBUS_TYPE_INT64, &rtkit_max_rttime_usec)) { rtkit_max_rttime_usec = 200000; } @@ -202,10 +202,10 @@ static bool rtkit_setpriority_nice(pid_t thread, int nice_level) nice = rtkit_min_nice_level; } - if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(dbus_conn, - rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MakeThreadHighPriorityWithPID", - DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &tid, DBUS_TYPE_INT32, &nice, DBUS_TYPE_INVALID, - DBUS_TYPE_INVALID)) { + if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(dbus_conn, NULL, + rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MakeThreadHighPriorityWithPID", + DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &tid, DBUS_TYPE_INT32, &nice, DBUS_TYPE_INVALID, + DBUS_TYPE_INVALID)) { return false; } return true; @@ -233,10 +233,10 @@ static bool rtkit_setpriority_realtime(pid_t thread, int rt_priority) // go through to determine whether it really needs to fail or not. rtkit_initialize_realtime_thread(); - if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(dbus_conn, - rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MakeThreadRealtimeWithPID", - DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &tid, DBUS_TYPE_UINT32, &priority, DBUS_TYPE_INVALID, - DBUS_TYPE_INVALID)) { + if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(dbus_conn, NULL, + rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MakeThreadRealtimeWithPID", + DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &tid, DBUS_TYPE_UINT32, &priority, DBUS_TYPE_INVALID, + DBUS_TYPE_INVALID)) { return false; } return true; diff --git a/libs/SDL3/src/core/linux/SDL_udev.c b/libs/SDL3/src/core/linux/SDL_udev.c index 907c34c..3aaa0fb 100644 --- a/libs/SDL3/src/core/linux/SDL_udev.c +++ b/libs/SDL3/src/core/linux/SDL_udev.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -36,7 +36,22 @@ #include "SDL_evdev_capabilities.h" #include "../unix/SDL_poll.h" -static const char *SDL_UDEV_LIBS[] = { "libudev.so.1", "libudev.so.0" }; +#define SDL_UDEV_FALLBACK_LIBS "libudev.so.1", "libudev.so.0" + +static const char *SDL_UDEV_LIBS[] = { SDL_UDEV_FALLBACK_LIBS }; + +#ifdef SDL_UDEV_DYNAMIC +#define SDL_UDEV_DLNOTE_LIBS SDL_UDEV_DYNAMIC, SDL_UDEV_FALLBACK_LIBS +#else +#define SDL_UDEV_DLNOTE_LIBS SDL_UDEV_FALLBACK_LIBS +#endif + +SDL_ELF_NOTE_DLOPEN( + "events-udev", + "Support for events through libudev", + SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SDL_UDEV_DLNOTE_LIBS +) static SDL_UDEV_PrivateData *_this = NULL; @@ -68,6 +83,7 @@ static bool SDL_UDEV_load_syms(void) SDL_UDEV_SYM(udev_device_get_action); SDL_UDEV_SYM(udev_device_get_devnode); + SDL_UDEV_SYM(udev_device_get_driver); SDL_UDEV_SYM(udev_device_get_syspath); SDL_UDEV_SYM(udev_device_get_subsystem); SDL_UDEV_SYM(udev_device_get_parent_with_subsystem_devtype); @@ -219,51 +235,58 @@ bool SDL_UDEV_Scan(void) return true; } -bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class) +bool SDL_UDEV_GetProductInfo(const char *device_path, struct input_id *inpid, int *class, char **driver) { struct stat statbuf; char type; struct udev_device *dev; - const char* val; + const char *val; int class_temp; if (!_this) { return false; } - if (stat(device_path, &statbuf) == -1) { + if (stat(device_path, &statbuf) < 0) { return false; } if (S_ISBLK(statbuf.st_mode)) { type = 'b'; - } - else if (S_ISCHR(statbuf.st_mode)) { + } else if (S_ISCHR(statbuf.st_mode)) { type = 'c'; - } - else { + } else { return false; } dev = _this->syms.udev_device_new_from_devnum(_this->udev, type, statbuf.st_rdev); - if (!dev) { return false; } val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID"); if (val) { - *vendor = (Uint16)SDL_strtol(val, NULL, 16); + inpid->vendor = (Uint16)SDL_strtol(val, NULL, 16); } val = _this->syms.udev_device_get_property_value(dev, "ID_MODEL_ID"); if (val) { - *product = (Uint16)SDL_strtol(val, NULL, 16); + inpid->product = (Uint16)SDL_strtol(val, NULL, 16); } val = _this->syms.udev_device_get_property_value(dev, "ID_REVISION"); if (val) { - *version = (Uint16)SDL_strtol(val, NULL, 16); + inpid->version = (Uint16)SDL_strtol(val, NULL, 16); + } + + if (driver) { + val = _this->syms.udev_device_get_driver(dev); + if (!val) { + val = _this->syms.udev_device_get_property_value(dev, "ID_USB_DRIVER"); + } + if (val) { + *driver = SDL_strdup(val); + } } class_temp = device_class(dev); @@ -276,6 +299,45 @@ bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *pr return true; } +char *SDL_UDEV_GetProductSerial(const char *device_path) +{ + struct stat statbuf; + char type; + struct udev_device *dev; + const char *val; + char *result = NULL; + + if (!_this) { + return NULL; + } + + if (stat(device_path, &statbuf) < 0) { + return NULL; + } + + if (S_ISBLK(statbuf.st_mode)) { + type = 'b'; + } else if (S_ISCHR(statbuf.st_mode)) { + type = 'c'; + } else { + return NULL; + } + + dev = _this->syms.udev_device_new_from_devnum(_this->udev, type, statbuf.st_rdev); + if (!dev) { + return NULL; + } + + val = _this->syms.udev_device_get_property_value(dev, "ID_SERIAL_SHORT"); + if (val && *val) { + result = SDL_strdup(val); + } + + _this->syms.udev_device_unref(dev); + + return result; +} + void SDL_UDEV_UnloadLibrary(void) { if (!_this) { diff --git a/libs/SDL3/src/core/linux/SDL_udev.h b/libs/SDL3/src/core/linux/SDL_udev.h index 738f4ba..f2b1ce5 100644 --- a/libs/SDL3/src/core/linux/SDL_udev.h +++ b/libs/SDL3/src/core/linux/SDL_udev.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -31,6 +31,7 @@ #endif #include +#include #include #include @@ -56,6 +57,7 @@ typedef struct SDL_UDEV_Symbols { const char *(*udev_device_get_action)(struct udev_device *); const char *(*udev_device_get_devnode)(struct udev_device *); + const char *(*udev_device_get_driver)(struct udev_device *); const char *(*udev_device_get_syspath)(struct udev_device *); const char *(*udev_device_get_subsystem)(struct udev_device *); struct udev_device *(*udev_device_get_parent_with_subsystem_devtype)(struct udev_device *udev_device, const char *subsystem, const char *devtype); @@ -102,7 +104,8 @@ extern void SDL_UDEV_UnloadLibrary(void); extern bool SDL_UDEV_LoadLibrary(void); extern void SDL_UDEV_Poll(void); extern bool SDL_UDEV_Scan(void); -extern bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class); +extern bool SDL_UDEV_GetProductInfo(const char *device_path, struct input_id *inpid, int *class, char **driver); +extern char *SDL_UDEV_GetProductSerial(const char *device_path); extern bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb); extern void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb); extern const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void); diff --git a/libs/SDL3/src/core/ngage/SDL_ngage.cpp b/libs/SDL3/src/core/ngage/SDL_ngage.cpp new file mode 100644 index 0000000..518d43f --- /dev/null +++ b/libs/SDL3/src/core/ngage/SDL_ngage.cpp @@ -0,0 +1,62 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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 +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool NGAGE_IsClassicModel() +{ + int phone_id; + HAL::Get(HALData::EMachineUid, phone_id); + + return (0x101f8c19 == phone_id); +} + +void NGAGE_DebugPrintf(const char *fmt, ...) +{ + char buffer[512] = { 0 }; + + va_list ap; + va_start(ap, fmt); + (void)SDL_vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + TBuf<512> buf; + buf.Copy(TPtrC8((TText8 *)buffer)); + + RDebug::Print(_L("%S"), &buf); +} + +TInt NGAGE_GetFreeHeapMemory() +{ + TInt free = 0; + return User::Available(free); +} + +#ifdef __cplusplus +} +#endif diff --git a/libs/SDL3/src/video/windows/SDL_surface_utils.h b/libs/SDL3/src/core/ngage/SDL_ngage.h similarity index 79% rename from libs/SDL3/src/video/windows/SDL_surface_utils.h rename to libs/SDL3/src/core/ngage/SDL_ngage.h index a793cdc..52f3902 100644 --- a/libs/SDL3/src/video/windows/SDL_surface_utils.h +++ b/libs/SDL3/src/core/ngage/SDL_ngage.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -20,19 +20,18 @@ */ #include "SDL_internal.h" -#ifndef SDL_surface_utils_h_ -#define SDL_surface_utils_h_ - -#include "../../core/windows/SDL_windows.h" +#ifndef SDL_ngage_h +#define SDL_ngage_h #ifdef __cplusplus extern "C" { #endif -extern HICON CreateIconFromSurface(SDL_Surface *surface); +bool NGAGE_IsClassicModel(); +void NGAGE_DebugPrintf(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(1); #ifdef __cplusplus } #endif -#endif +#endif /* SDL_ngage_h */ diff --git a/libs/SDL3/src/core/openbsd/SDL_wscons.h b/libs/SDL3/src/core/openbsd/SDL_wscons.h index f9b7bf8..a20220b 100644 --- a/libs/SDL3/src/core/openbsd/SDL_wscons.h +++ b/libs/SDL3/src/core/openbsd/SDL_wscons.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/openbsd/SDL_wscons_kbd.c b/libs/SDL3/src/core/openbsd/SDL_wscons_kbd.c index 5a71044..3e3a104 100644 --- a/libs/SDL3/src/core/openbsd/SDL_wscons_kbd.c +++ b/libs/SDL3/src/core/openbsd/SDL_wscons_kbd.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -433,7 +433,7 @@ static SDL_WSCONS_input_data *SDL_WSCONS_Init_Keyboard(const char *dev) } input->keyboardID = SDL_GetNextObjectID(); - SDL_AddKeyboard(input->keyboardID, NULL, false); + SDL_AddKeyboard(input->keyboardID, NULL); input->keymap.map = SDL_calloc(KS_NUMKEYCODES, sizeof(struct wscons_keymap)); if (!input->keymap.map) { diff --git a/libs/SDL3/src/core/openbsd/SDL_wscons_mouse.c b/libs/SDL3/src/core/openbsd/SDL_wscons_mouse.c index 51195f3..1b3dc3e 100644 --- a/libs/SDL3/src/core/openbsd/SDL_wscons_mouse.c +++ b/libs/SDL3/src/core/openbsd/SDL_wscons_mouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -52,7 +52,7 @@ SDL_WSCONS_mouse_input_data *SDL_WSCONS_Init_Mouse(void) } input->mouseID = SDL_GetNextObjectID(); - SDL_AddMouse(input->mouseID, NULL, false); + SDL_AddMouse(input->mouseID, NULL); #ifdef WSMOUSEIO_SETMODE ioctl(input->fd, WSMOUSEIO_SETMODE, WSMOUSE_COMPAT); diff --git a/libs/SDL3/src/core/unix/SDL_appid.c b/libs/SDL3/src/core/unix/SDL_appid.c index 996e216..3b85cbc 100644 --- a/libs/SDL3/src/core/unix/SDL_appid.c +++ b/libs/SDL3/src/core/unix/SDL_appid.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -30,11 +30,11 @@ const char *SDL_GetExeName(void) // TODO: Use a fallback if BSD has no mounted procfs (OpenBSD has no procfs at all) if (!proc_name) { -#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_FREEBSD) || defined (SDL_PLATFORM_NETBSD) +#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_FREEBSD) || defined (SDL_PLATFORM_NETBSD) || defined(SDL_PLATFORM_HURD) static char linkfile[1024]; int linksize; -#if defined(SDL_PLATFORM_LINUX) +#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_HURD) const char *proc_path = "/proc/self/exe"; #elif defined(SDL_PLATFORM_FREEBSD) const char *proc_path = "/proc/curproc/file"; diff --git a/libs/SDL3/src/core/unix/SDL_appid.h b/libs/SDL3/src/core/unix/SDL_appid.h index 3482521..9ed45c3 100644 --- a/libs/SDL3/src/core/unix/SDL_appid.h +++ b/libs/SDL3/src/core/unix/SDL_appid.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer -Copyright (C) 1997-2025 Sam Lantinga +Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/unix/SDL_fribidi.c b/libs/SDL3/src/core/unix/SDL_fribidi.c new file mode 100644 index 0000000..4a55133 --- /dev/null +++ b/libs/SDL3/src/core/unix/SDL_fribidi.c @@ -0,0 +1,171 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_FRIBIDI_H + +#include "SDL_fribidi.h" + +#ifdef SDL_FRIBIDI_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "fribidi", + "Bidirectional text support", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_FRIBIDI_DYNAMIC +) +#endif + +SDL_FriBidi *SDL_FriBidi_Create(void) +{ + SDL_FriBidi *fribidi; + + fribidi = (SDL_FriBidi *)SDL_malloc(sizeof(SDL_FriBidi)); + if (!fribidi) { + return NULL; + } + +#ifdef SDL_FRIBIDI_DYNAMIC + #define SDL_FRIBIDI_LOAD_SYM(x, n, t) x = ((t)SDL_LoadFunction(fribidi->lib, n)); if (!x) { SDL_UnloadObject(fribidi->lib); SDL_free(fribidi); return NULL; } + + fribidi->lib = SDL_LoadObject(SDL_FRIBIDI_DYNAMIC); + if (!fribidi->lib) { + SDL_free(fribidi); + return NULL; + } + + SDL_FRIBIDI_LOAD_SYM(fribidi->unicode_to_charset, "fribidi_unicode_to_charset", SDL_FriBidiUnicodeToCharset); + SDL_FRIBIDI_LOAD_SYM(fribidi->charset_to_unicode, "fribidi_charset_to_unicode", SDL_FriBidiCharsetToUnicode); + SDL_FRIBIDI_LOAD_SYM(fribidi->get_bidi_types, "fribidi_get_bidi_types", SDL_FriBidiGetBidiTypes); + SDL_FRIBIDI_LOAD_SYM(fribidi->get_par_direction, "fribidi_get_par_direction", SDL_FriBidiGetParDirection); + SDL_FRIBIDI_LOAD_SYM(fribidi->get_par_embedding_levels, "fribidi_get_par_embedding_levels", SDL_FriBidiGetParEmbeddingLevels); + SDL_FRIBIDI_LOAD_SYM(fribidi->get_joining_types, "fribidi_get_joining_types", SDL_FriBidiGetJoiningTypes); + SDL_FRIBIDI_LOAD_SYM(fribidi->join_arabic, "fribidi_join_arabic", SDL_FriBidiJoinArabic); + SDL_FRIBIDI_LOAD_SYM(fribidi->shape, "fribidi_shape", SDL_FriBidiShape); + SDL_FRIBIDI_LOAD_SYM(fribidi->reorder_line, "fribidi_reorder_line", SDL_FriBidiReorderLine); +#else + fribidi->unicode_to_charset = fribidi_unicode_to_charset; + fribidi->charset_to_unicode = fribidi_charset_to_unicode; + fribidi->get_bidi_types = fribidi_get_bidi_types; + fribidi->get_par_direction = fribidi_get_par_direction; + fribidi->get_par_embedding_levels = fribidi_get_par_embedding_levels; + fribidi->get_joining_types = fribidi_get_joining_types; + fribidi->join_arabic = fribidi_join_arabic; + fribidi->shape = fribidi_shape; + fribidi->reorder_line = fribidi_reorder_line; +#endif + + return fribidi; +} + +char *SDL_FriBidi_Process(SDL_FriBidi *fribidi, char *utf8, ssize_t utf8_len, bool shaping, FriBidiParType *out_par_type) +{ + FriBidiCharType *types; + FriBidiLevel *levels; + FriBidiArabicProp *props; + FriBidiChar *str; + char *result; + FriBidiStrIndex len; + FriBidiLevel max_level; + FriBidiLevel start; + FriBidiLevel end; + FriBidiParType direction; + FriBidiParType str_direction; + unsigned int i; + unsigned int c; + + if (!fribidi || !utf8) { + return NULL; + } + + /* Convert to UTF32 */ + if (utf8_len < 0) { + utf8_len = SDL_strlen(utf8); + } + str = SDL_calloc(SDL_utf8strnlen(utf8, utf8_len), sizeof(FriBidiChar)); + len = fribidi->charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, utf8, utf8_len, str); + + /* Setup various BIDI structures */ + direction = FRIBIDI_PAR_LTR; + types = NULL; + levels = NULL; + props = SDL_calloc(len + 1, sizeof(FriBidiArabicProp)); + levels = SDL_calloc(len + 1, sizeof(FriBidiLevel)); + types = SDL_calloc(len + 1, sizeof(FriBidiCharType)); + + /* Shape */ + fribidi->get_bidi_types(str, len, types); + str_direction = fribidi->get_par_direction(types, len); + max_level = fribidi->get_par_embedding_levels(types, len, &direction, levels); + if (shaping) { + fribidi->get_joining_types(str, len, props); + fribidi->join_arabic(types, len, levels, props); + fribidi->shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC, levels, len, props, str); + } + + /* BIDI */ + for (end = 0, start = 0; end < len; end++) { + if (str[end] == '\n' || str[end] == '\r' || str[end] == '\f' || str[end] == '\v' || end == len - 1) { + max_level = fribidi->reorder_line(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC, types, end - start + 1, start, direction, levels, str, NULL); + start = end + 1; + } + } + + /* Silence warning */ + (void)max_level; + + /* Remove fillers */ + for (i = 0, c = 0; i < len; i++) { + if (str[i] != FRIBIDI_CHAR_FILL) { + str[c++] = str[i]; + } + } + len = c; + + /* Convert back to UTF8 */ + result = SDL_malloc(len * 4 + 1); + fribidi->unicode_to_charset(FRIBIDI_CHAR_SET_UTF8, str, len, result); + + /* Cleanup */ + SDL_free(levels); + SDL_free(props); + SDL_free(types); + + /* Return */ + if (out_par_type) { + *out_par_type = str_direction; + } + return result; +} + +void SDL_FriBidi_Destroy(SDL_FriBidi *fribidi) +{ + if (!fribidi) { + return; + } + +#ifdef SDL_FRIBIDI_DYNAMIC + SDL_UnloadObject(fribidi->lib); +#endif + + SDL_free(fribidi); +} + +#endif diff --git a/libs/SDL3/src/core/unix/SDL_fribidi.h b/libs/SDL3/src/core/unix/SDL_fribidi.h new file mode 100644 index 0000000..671ec91 --- /dev/null +++ b/libs/SDL3/src/core/unix/SDL_fribidi.h @@ -0,0 +1,60 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_fribidi_h_ +#define SDL_fribidi_h_ + +#ifdef HAVE_FRIBIDI_H +#include // for ssize_t +#include + +typedef FriBidiStrIndex (*SDL_FriBidiUnicodeToCharset)(FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); +typedef FriBidiStrIndex (*SDL_FriBidiCharsetToUnicode)(FriBidiCharSet, const char *, FriBidiStrIndex, FriBidiChar *); +typedef void (*SDL_FriBidiGetBidiTypes)(const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *); +typedef FriBidiParType (*SDL_FriBidiGetParDirection)(const FriBidiCharType *, const FriBidiStrIndex); +typedef FriBidiLevel (*SDL_FriBidiGetParEmbeddingLevels)(const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, FriBidiLevel *); +typedef void (*SDL_FriBidiGetJoiningTypes)(const FriBidiChar *, const FriBidiStrIndex, FriBidiJoiningType *); +typedef void (*SDL_FriBidiJoinArabic)(const FriBidiCharType *, const FriBidiStrIndex, const FriBidiLevel *, FriBidiArabicProp *); +typedef void (*SDL_FriBidiShape)(FriBidiFlags flags, const FriBidiLevel *, const FriBidiStrIndex, FriBidiArabicProp *, FriBidiChar *str); +typedef FriBidiLevel (*SDL_FriBidiReorderLine)(FriBidiFlags flags, const FriBidiCharType *, const FriBidiStrIndex, const FriBidiStrIndex, const FriBidiParType, FriBidiLevel *, FriBidiChar *, FriBidiStrIndex *); + +typedef struct SDL_FriBidi { + SDL_SharedObject *lib; + SDL_FriBidiUnicodeToCharset unicode_to_charset; + SDL_FriBidiCharsetToUnicode charset_to_unicode; + SDL_FriBidiGetBidiTypes get_bidi_types; + SDL_FriBidiGetParDirection get_par_direction; + SDL_FriBidiGetParEmbeddingLevels get_par_embedding_levels; + SDL_FriBidiGetJoiningTypes get_joining_types; + SDL_FriBidiJoinArabic join_arabic; + SDL_FriBidiShape shape; + SDL_FriBidiReorderLine reorder_line; +} SDL_FriBidi; + +extern SDL_FriBidi *SDL_FriBidi_Create(void); +extern char *SDL_FriBidi_Process(SDL_FriBidi *fribidi, char *utf8, ssize_t utf8_len, bool shaping, FriBidiParType *out_par_type); +extern void SDL_FriBidi_Destroy(SDL_FriBidi *fribidi); + +#endif // HAVE_FRIBIDI_H + +#endif // SDL_fribidi_h_ diff --git a/libs/SDL3/src/core/unix/SDL_gtk.c b/libs/SDL3/src/core/unix/SDL_gtk.c new file mode 100644 index 0000000..6cf26fd --- /dev/null +++ b/libs/SDL3/src/core/unix/SDL_gtk.c @@ -0,0 +1,300 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_gtk.h" + +#include +#include +#include + +#define SDL_GTK_SYM2_OPTIONAL(ctx, lib, sub, fn, sym) \ + ctx.sub.fn = (void *)SDL_LoadFunction(lib, #sym) + +#define SDL_GTK_SYM2(ctx, lib, sub, fn, sym) \ + SDL_GTK_SYM2_OPTIONAL(ctx, lib, sub, fn, sym); \ + if (!ctx.sub.fn) { \ + return SDL_SetError("Could not load GTK functions"); \ + } + +#define SDL_GTK_SYM_OPTIONAL(ctx, lib, sub, fn) \ + SDL_GTK_SYM2_OPTIONAL(ctx, lib, sub, fn, sub##_##fn) + +#define SDL_GTK_SYM(ctx, lib, sub, fn) \ + SDL_GTK_SYM2(ctx, lib, sub, fn, sub##_##fn) + +#ifdef SDL_PLATFORM_OPENBSD +#define GDK3_LIB "libgdk-3.so" +#else +#define GDK3_LIB "libgdk-3.so.0" +#endif + +#ifdef SDL_PLATFORM_OPENBSD +#define GTK3_LIB "libgtk-3.so" +#else +#define GTK3_LIB "libgtk-3.so.0" +#endif + +// we never link directly to gtk +static void *libgdk = NULL; +static void *libgtk = NULL; + +static SDL_GtkContext gtk; +static GMainContext *sdl_main_context; + +static gulong signal_connect(gpointer instance, const gchar *detailed_signal, void *c_handler, gpointer data) +{ + return gtk.g.signal_connect_data(instance, detailed_signal, SDL_G_CALLBACK(c_handler), data, NULL, (SDL_GConnectFlags)0); +} + +static void QuitGtk(void) +{ + if (sdl_main_context) { + gtk.g.main_context_unref(sdl_main_context); + sdl_main_context = NULL; + } + + SDL_UnloadObject(libgdk); + SDL_UnloadObject(libgtk); + + libgdk = NULL; + libgtk = NULL; +} + +static bool IsGtkInit() +{ + return libgdk != NULL && libgtk != NULL; +} + +#ifndef HAVE_GETRESUID +// Non-POSIX, but Linux and some BSDs have it. +// To reduce the number of code paths, if getresuid() isn't available at +// compile-time, we behave as though it existed but failed at runtime. +static inline int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) { + errno = ENOSYS; + return -1; +} +#endif + +#ifndef HAVE_GETRESGID +// Same as getresuid() but for the primary group +static inline int getresgid(uid_t *ruid, uid_t *euid, uid_t *suid) { + errno = ENOSYS; + return -1; +} +#endif + +bool SDL_CanUseGtk(void) +{ + // "Real", "effective" and "saved" IDs: see e.g. Linux credentials(7) + uid_t ruid = -1, euid = -1, suid = -1; + gid_t rgid = -1, egid = -1, sgid = -1; + + if (!SDL_GetHintBoolean("SDL_ENABLE_GTK", true)) { + SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Not using GTK due to hint"); + return false; + } + + // This is intended to match the check in gtkmain.c, rather than being + // an exhaustive check for having elevated privileges: as a result + // we don't use Linux getauxval() or prctl PR_GET_DUMPABLE, + // BSD issetugid(), or similar OS-specific detection + + if (getresuid(&ruid, &euid, &suid) != 0) { + ruid = suid = getuid(); + euid = geteuid(); + } + + if (getresgid(&rgid, &egid, &sgid) != 0) { + rgid = sgid = getgid(); + egid = getegid(); + } + + // Real ID != effective ID means we are setuid or setgid: + // GTK will refuse to initialize, and instead will call exit(). + if (ruid != euid || rgid != egid) { + SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Not using GTK due to setuid/setgid"); + return false; + } + + // Real ID != saved ID means we are setuid or setgid, we previously + // dropped privileges, but we can regain them; this protects against + // accidents but does not protect against arbitrary code execution. + // Again, GTK will refuse to initialize if this is the case. + if (ruid != suid || rgid != sgid) { + SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Not using GTK due to saved uid/gid"); + return false; + } + + return true; +} + +static bool InitGtk(void) +{ + if (!SDL_CanUseGtk()) { + return false; + } + + if (IsGtkInit()) { + return true; + } + + // GTK only allows a single version to be loaded into a process at a time, + // so if there is one already loaded ensure it is the version we use. + void *progress_get_type = dlsym(RTLD_DEFAULT, "gtk_progress_get_type"); + void *misc_get_type = dlsym(RTLD_DEFAULT, "gtk_misc_get_type"); + if (progress_get_type || misc_get_type) { + void *libgtk3 = dlopen(GTK3_LIB, RTLD_NOLOAD | RTLD_LAZY); + if (!libgtk3) { + QuitGtk(); + return SDL_SetError("Could not load GTK-3, another GTK version already present"); + } + + dlclose(libgtk3); + } + + libgdk = SDL_LoadObject(GDK3_LIB); + libgtk = SDL_LoadObject(GTK3_LIB); + + if (!libgdk || !libgtk) { + QuitGtk(); + return SDL_SetError("Could not load GTK libraries"); + } + + SDL_GTK_SYM(gtk, libgtk, gtk, init_check); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_new); + SDL_GTK_SYM(gtk, libgtk, gtk, separator_menu_item_new); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_item_new_with_label); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_item_set_submenu); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_item_get_label); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_item_set_label); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_shell_append); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_shell_insert); + SDL_GTK_SYM(gtk, libgtk, gtk, check_menu_item_new_with_label); + SDL_GTK_SYM(gtk, libgtk, gtk, check_menu_item_get_active); + SDL_GTK_SYM(gtk, libgtk, gtk, check_menu_item_set_active); + SDL_GTK_SYM(gtk, libgtk, gtk, widget_show); + SDL_GTK_SYM(gtk, libgtk, gtk, widget_destroy); + SDL_GTK_SYM(gtk, libgtk, gtk, widget_get_sensitive); + SDL_GTK_SYM(gtk, libgtk, gtk, widget_set_sensitive); + SDL_GTK_SYM(gtk, libgtk, gtk, settings_get_default); + + SDL_GTK_SYM(gtk, libgdk, g, signal_connect_data); + SDL_GTK_SYM(gtk, libgdk, g, mkdtemp); + SDL_GTK_SYM(gtk, libgdk, g, get_user_cache_dir); + SDL_GTK_SYM(gtk, libgdk, g, object_ref); + SDL_GTK_SYM(gtk, libgdk, g, object_ref_sink); + SDL_GTK_SYM(gtk, libgdk, g, object_unref); + SDL_GTK_SYM(gtk, libgdk, g, object_get); + SDL_GTK_SYM(gtk, libgdk, g, signal_handler_disconnect); + SDL_GTK_SYM(gtk, libgdk, g, main_context_push_thread_default); + SDL_GTK_SYM(gtk, libgdk, g, main_context_pop_thread_default); + SDL_GTK_SYM(gtk, libgdk, g, main_context_new); + SDL_GTK_SYM(gtk, libgdk, g, main_context_unref); + SDL_GTK_SYM(gtk, libgdk, g, main_context_acquire); + SDL_GTK_SYM(gtk, libgdk, g, main_context_iteration); + + gtk.g.signal_connect = signal_connect; + + if (gtk.gtk.init_check(NULL, NULL) == GTK_FALSE) { + QuitGtk(); + return SDL_SetError("Could not init GTK"); + } + + sdl_main_context = gtk.g.main_context_new(); + if (!sdl_main_context) { + QuitGtk(); + return SDL_SetError("Could not create GTK context"); + } + + if (!gtk.g.main_context_acquire(sdl_main_context)) { + QuitGtk(); + return SDL_SetError("Could not acquire GTK context"); + } + + return true; +} + +static SDL_InitState gtk_init; + +bool SDL_Gtk_Init(void) +{ + static bool is_gtk_available = true; + + if (!is_gtk_available) { + return false; // don't keep trying if this fails. + } + + if (SDL_ShouldInit(>k_init)) { + if (InitGtk()) { + SDL_SetInitialized(>k_init, true); + } else { + is_gtk_available = false; + SDL_SetInitialized(>k_init, true); + SDL_Gtk_Quit(); + } + } + + return IsGtkInit(); +} + +void SDL_Gtk_Quit(void) +{ + if (!SDL_ShouldQuit(>k_init)) { + return; + } + + QuitGtk(); + SDL_zero(gtk); + + SDL_SetInitialized(>k_init, false); +} + +SDL_GtkContext *SDL_Gtk_GetContext(void) +{ + return IsGtkInit() ? >k : NULL; +} + +SDL_GtkContext *SDL_Gtk_EnterContext(void) +{ + SDL_Gtk_Init(); + + if (IsGtkInit()) { + gtk.g.main_context_push_thread_default(sdl_main_context); + return >k; + } + + return NULL; +} + +void SDL_Gtk_ExitContext(SDL_GtkContext *ctx) +{ + if (ctx) { + ctx->g.main_context_pop_thread_default(sdl_main_context); + } +} + +void SDL_UpdateGtk(void) +{ + if (IsGtkInit()) { + gtk.g.main_context_iteration(sdl_main_context, GTK_FALSE); + gtk.g.main_context_iteration(NULL, GTK_FALSE); + } +} diff --git a/libs/SDL3/src/core/unix/SDL_gtk.h b/libs/SDL3/src/core/unix/SDL_gtk.h new file mode 100644 index 0000000..663b285 --- /dev/null +++ b/libs/SDL3/src/core/unix/SDL_gtk.h @@ -0,0 +1,126 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_gtk_h_ +#define SDL_gtk_h_ + +/* Glib 2.0 */ + +typedef unsigned long gulong; +typedef void *gpointer; +typedef char gchar; +typedef int gint; +typedef unsigned int guint; +typedef double gdouble; +typedef gint gboolean; +typedef void (*GCallback)(void); +typedef struct _GClosure GClosure; +typedef void (*GClosureNotify) (gpointer data, GClosure *closure); +typedef gboolean (*GSourceFunc) (gpointer user_data); + +typedef struct _GParamSpec GParamSpec; +typedef struct _GMainContext GMainContext; + +typedef enum SDL_GConnectFlags +{ + SDL_G_CONNECT_DEFAULT = 0, + SDL_G_CONNECT_AFTER = 1 << 0, + SDL_G_CONNECT_SWAPPED = 1 << 1 +} SDL_GConnectFlags; + +#define SDL_G_CALLBACK(f) ((GCallback) (f)) +#define SDL_G_TYPE_CIC(ip, gt, ct) ((ct*) ip) +#define SDL_G_TYPE_CHECK_INSTANCE_CAST(instance, g_type, c_type) (SDL_G_TYPE_CIC ((instance), (g_type), c_type)) + +#define GTK_FALSE 0 +#define GTK_TRUE 1 + + +/* GTK 3.0 */ + +typedef struct _GtkMenu GtkMenu; +typedef struct _GtkMenuItem GtkMenuItem; +typedef struct _GtkMenuShell GtkMenuShell; +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkCheckMenuItem GtkCheckMenuItem; +typedef struct _GtkSettings GtkSettings; + +#define GTK_MENU_ITEM(obj) (SDL_G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU_ITEM, GtkMenuItem)) +#define GTK_WIDGET(widget) (SDL_G_TYPE_CHECK_INSTANCE_CAST ((widget), GTK_TYPE_WIDGET, GtkWidget)) +#define GTK_CHECK_MENU_ITEM(obj) (SDL_G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CHECK_MENU_ITEM, GtkCheckMenuItem)) +#define GTK_MENU(obj) (SDL_G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU, GtkMenu)) + + +typedef struct SDL_GtkContext +{ + /* Glib 2.0 */ + struct + { + gulong (*signal_connect)(gpointer instance, const gchar *detailed_signal, void *c_handler, gpointer data); + gulong (*signal_connect_data)(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data, SDL_GConnectFlags connect_flags); + void (*object_unref)(gpointer object); + gchar *(*mkdtemp)(gchar *template); + gchar *(*get_user_cache_dir)(void); + gpointer (*object_ref_sink)(gpointer object); + gpointer (*object_ref)(gpointer object); + void (*object_get)(gpointer object, const gchar *first_property_name, ...); + void (*signal_handler_disconnect)(gpointer instance, gulong handler_id); + void (*main_context_push_thread_default)(GMainContext *context); + void (*main_context_pop_thread_default)(GMainContext *context); + GMainContext *(*main_context_new)(void); + void (*main_context_unref)(GMainContext *context); + gboolean (*main_context_acquire)(GMainContext *context); + gboolean (*main_context_iteration)(GMainContext *context, gboolean may_block); + } g; + + /* GTK 3.0 */ + struct + { + gboolean (*init_check)(int *argc, char ***argv); + GtkWidget *(*menu_new)(void); + GtkWidget *(*separator_menu_item_new)(void); + GtkWidget *(*menu_item_new_with_label)(const gchar *label); + void (*menu_item_set_submenu)(GtkMenuItem *menu_item, GtkWidget *submenu); + GtkWidget *(*check_menu_item_new_with_label)(const gchar *label); + void (*check_menu_item_set_active)(GtkCheckMenuItem *check_menu_item, gboolean is_active); + void (*widget_set_sensitive)(GtkWidget *widget, gboolean sensitive); + void (*widget_show)(GtkWidget *widget); + void (*menu_shell_append)(GtkMenuShell *menu_shell, GtkWidget *child); + void (*menu_shell_insert)(GtkMenuShell *menu_shell, GtkWidget *child, gint position); + void (*widget_destroy)(GtkWidget *widget); + const gchar *(*menu_item_get_label)(GtkMenuItem *menu_item); + void (*menu_item_set_label)(GtkMenuItem *menu_item, const gchar *label); + gboolean (*check_menu_item_get_active)(GtkCheckMenuItem *check_menu_item); + gboolean (*widget_get_sensitive)(GtkWidget *widget); + GtkSettings *(*settings_get_default)(void); + } gtk; +} SDL_GtkContext; + +extern bool SDL_CanUseGtk(void); +extern bool SDL_Gtk_Init(void); +extern void SDL_Gtk_Quit(void); +extern SDL_GtkContext *SDL_Gtk_EnterContext(void); +extern void SDL_Gtk_ExitContext(SDL_GtkContext *ctx); +extern void SDL_UpdateGtk(void); + +#endif // SDL_gtk_h_ diff --git a/libs/SDL3/src/core/unix/SDL_libthai.c b/libs/SDL3/src/core/unix/SDL_libthai.c new file mode 100644 index 0000000..2edc4f6 --- /dev/null +++ b/libs/SDL3/src/core/unix/SDL_libthai.c @@ -0,0 +1,76 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_LIBTHAI_H + +#include "SDL_libthai.h" + +#ifdef SDL_LIBTHAI_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "Thai", + "Thai language support", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_LIBTHAI_DYNAMIC +) +#endif + + +SDL_LibThai *SDL_LibThai_Create(void) +{ + SDL_LibThai *th; + + th = (SDL_LibThai *)SDL_malloc(sizeof(SDL_LibThai)); + if (!th) { + return NULL; + } + +#ifdef SDL_LIBTHAI_DYNAMIC + #define SDL_LIBTHAI_LOAD_SYM(a, x, n, t) x = ((t)SDL_LoadFunction(a->lib, n)); if (!x) { SDL_UnloadObject(a->lib); SDL_free(a); return NULL; } + + th->lib = SDL_LoadObject(SDL_LIBTHAI_DYNAMIC); + if (!th->lib) { + SDL_free(th); + return NULL; + } + + SDL_LIBTHAI_LOAD_SYM(th, th->make_cells, "th_make_cells", SDL_LibThaiMakeCells); +#else + th->make_cells = th_make_cells; +#endif + + return th; +} + +void SDL_LibThai_Destroy(SDL_LibThai *th) +{ + if (!th) { + return; + } + +#ifdef SDL_LIBTHAI_DYNAMIC + SDL_UnloadObject(th->lib); +#endif + + SDL_free(th); +} + +#endif diff --git a/libs/SDL3/src/video/vita/SDL_vitagles_pvr_c.h b/libs/SDL3/src/core/unix/SDL_libthai.h similarity index 61% rename from libs/SDL3/src/video/vita/SDL_vitagles_pvr_c.h rename to libs/SDL3/src/core/unix/SDL_libthai.h index ad87212..2cf6da7 100644 --- a/libs/SDL3/src/video/vita/SDL_vitagles_pvr_c.h +++ b/libs/SDL3/src/core/unix/SDL_libthai.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -19,14 +19,25 @@ 3. This notice may not be removed or altered from any source distribution. */ -#ifndef SDL_vitagles_pvr_c_h_ -#define SDL_vitagles_pvr_c_h_ +#include "SDL_internal.h" -#include "SDL_vitavideo.h" +#ifndef SDL_libthai_h_ +#define SDL_libthai_h_ -extern bool VITA_GLES_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context); -extern bool VITA_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern SDL_GLContext VITA_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *window); -extern bool VITA_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path); +#ifdef HAVE_LIBTHAI_H +#include -#endif // SDL_vitagles_pvr_c_h_ +typedef size_t (*SDL_LibThaiMakeCells)(const thchar_t *s, size_t, struct thcell_t cells[], size_t *, int); + +typedef struct SDL_LibThai { + SDL_SharedObject *lib; + + SDL_LibThaiMakeCells make_cells; +} SDL_LibThai; + +extern SDL_LibThai *SDL_LibThai_Create(void); +extern void SDL_LibThai_Destroy(SDL_LibThai *th); + +#endif // HAVE_LIBTHAI_H + +#endif // SDL_libthai_h_ diff --git a/libs/SDL3/src/core/unix/SDL_poll.c b/libs/SDL3/src/core/unix/SDL_poll.c index 572ed5a..641fc17 100644 --- a/libs/SDL3/src/core/unix/SDL_poll.c +++ b/libs/SDL3/src/core/unix/SDL_poll.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -23,15 +23,13 @@ #include "SDL_poll.h" -#ifdef HAVE_POLL #include -#else -#include -#include -#include -#endif #include +#ifdef HAVE_PPOLL +#include +#endif + int SDL_IOReady(int fd, int flags, Sint64 timeoutNS) { int result; @@ -40,9 +38,7 @@ int SDL_IOReady(int fd, int flags, Sint64 timeoutNS) // Note: We don't bother to account for elapsed time if we get EINTR do { -#ifdef HAVE_POLL struct pollfd info; - int timeoutMS; info.fd = fd; info.events = 0; @@ -52,7 +48,21 @@ int SDL_IOReady(int fd, int flags, Sint64 timeoutNS) if (flags & SDL_IOR_WRITE) { info.events |= POLLOUT; } - // FIXME: Add support for ppoll() for nanosecond precision + +#ifdef HAVE_PPOLL + struct timespec *timeout = NULL; + struct timespec ts; + + if (timeoutNS >= 0) { + ts.tv_sec = SDL_NS_TO_SECONDS(timeoutNS); + ts.tv_nsec = timeoutNS - SDL_SECONDS_TO_NS(ts.tv_sec); + timeout = &ts; + } + + result = ppoll(&info, 1, timeout, NULL); +#else + int timeoutMS; + if (timeoutNS > 0) { timeoutMS = (int)SDL_NS_TO_MS(timeoutNS + (SDL_NS_PER_MS - 1)); } else if (timeoutNS == 0) { @@ -61,34 +71,7 @@ int SDL_IOReady(int fd, int flags, Sint64 timeoutNS) timeoutMS = -1; } result = poll(&info, 1, timeoutMS); -#else - fd_set rfdset, *rfdp = NULL; - fd_set wfdset, *wfdp = NULL; - struct timeval tv, *tvp = NULL; - - // If this assert triggers we'll corrupt memory here - SDL_assert(fd >= 0 && fd < FD_SETSIZE); - - if (flags & SDL_IOR_READ) { - FD_ZERO(&rfdset); - FD_SET(fd, &rfdset); - rfdp = &rfdset; - } - if (flags & SDL_IOR_WRITE) { - FD_ZERO(&wfdset); - FD_SET(fd, &wfdset); - wfdp = &wfdset; - } - - if (timeoutNS >= 0) { - tv.tv_sec = (timeoutNS / SDL_NS_PER_SECOND); - tv.tv_usec = SDL_NS_TO_US((timeoutNS % SDL_NS_PER_SECOND) + (SDL_NS_PER_US - 1)); - tvp = &tv; - } - - result = select(fd + 1, rfdp, wfdp, NULL, tvp); -#endif // HAVE_POLL - +#endif } while (result < 0 && errno == EINTR && !(flags & SDL_IOR_NO_RETRY)); return result; diff --git a/libs/SDL3/src/core/unix/SDL_poll.h b/libs/SDL3/src/core/unix/SDL_poll.h index 571619d..5001c61 100644 --- a/libs/SDL3/src/core/unix/SDL_poll.h +++ b/libs/SDL3/src/core/unix/SDL_poll.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/windows/SDL_directx.h b/libs/SDL3/src/core/windows/SDL_directx.h index 6101e92..77b13dd 100644 --- a/libs/SDL3/src/core/windows/SDL_directx.h +++ b/libs/SDL3/src/core/windows/SDL_directx.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/windows/SDL_gameinput.c b/libs/SDL3/src/core/windows/SDL_gameinput.cpp similarity index 74% rename from libs/SDL3/src/core/windows/SDL_gameinput.c rename to libs/SDL3/src/core/windows/SDL_gameinput.cpp index 9ac5912..baf7ed2 100644 --- a/libs/SDL3/src/core/windows/SDL_gameinput.c +++ b/libs/SDL3/src/core/windows/SDL_gameinput.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -25,16 +25,11 @@ #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) { @@ -43,30 +38,34 @@ bool SDL_InitGameInput(IGameInput **ppGameInput) return false; } - typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput * *gameInput); - GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate"); - if (!GameInputCreateFunc) { + typedef HRESULT (WINAPI *pfnGameInputCreate)(IGameInput **gameInput); + pfnGameInputCreate pGameInputCreate = (pfnGameInputCreate)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate"); + if (!pGameInputCreate) { SDL_UnloadObject(g_hGameInputDLL); return false; } IGameInput *pGameInput = NULL; - HRESULT hr = GameInputCreateFunc(&pGameInput); + HRESULT hr = pGameInputCreate(&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 GAMEINPUT_API_VERSION >= 1 + hr = pGameInput->QueryInterface(IID_IGameInput, (void **)&g_pGameInput); +#else + // We require GameInput v1.1 or newer + hr = E_NOINTERFACE; +#endif + pGameInput->Release(); 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 } @@ -85,7 +84,7 @@ void SDL_QuitGameInput(void) --g_nGameInputRefCount; if (g_nGameInputRefCount == 0) { if (g_pGameInput) { - IGameInput_Release(g_pGameInput); + g_pGameInput->Release(); g_pGameInput = NULL; } if (g_hGameInputDLL) { diff --git a/libs/SDL3/src/core/windows/SDL_gameinput.h b/libs/SDL3/src/core/windows/SDL_gameinput.h index 0022c0b..a046489 100644 --- a/libs/SDL3/src/core/windows/SDL_gameinput.h +++ b/libs/SDL3/src/core/windows/SDL_gameinput.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -25,9 +25,18 @@ #ifdef HAVE_GAMEINPUT_H -#define COBJMACROS #include +#ifndef GAMEINPUT_API_VERSION +#define GAMEINPUT_API_VERSION 0 +#endif + +#if GAMEINPUT_API_VERSION == 2 +using namespace GameInput::v2; +#elif GAMEINPUT_API_VERSION == 1 +using namespace GameInput::v1; +#endif + extern bool SDL_InitGameInput(IGameInput **ppGameInput); extern void SDL_QuitGameInput(void); diff --git a/libs/SDL3/src/core/windows/SDL_hid.c b/libs/SDL3/src/core/windows/SDL_hid.c index 87e8735..1f629d5 100644 --- a/libs/SDL3/src/core/windows/SDL_hid.c +++ b/libs/SDL3/src/core/windows/SDL_hid.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -175,12 +175,39 @@ static CM_Register_NotificationFunc CM_Register_Notification; static CM_Unregister_NotificationFunc CM_Unregister_Notification; static HCMNOTIFICATION s_DeviceNotificationFuncHandle; static Uint64 s_LastDeviceNotification = 1; +static HANDLE s_HotplugEvent = INVALID_HANDLE_VALUE; +static SDL_AtomicInt s_HotplugRunning; +static SDL_Thread *s_HotplugThread; + +#ifdef SDL_VIDEO_DRIVER_WINDOWS +// Defined in SDL_windowsevents.c +extern void WIN_CheckKeyboardAndMouseHotplug(bool hid_loaded); +#endif + +static int SDLCALL DeviceHotplugThread(void *unused) +{ + bool hid_loaded = WIN_LoadHIDDLL(); + + // Always run the initial device detection + do { +#ifdef SDL_VIDEO_DRIVER_WINDOWS + WIN_CheckKeyboardAndMouseHotplug(hid_loaded); +#endif + WaitForSingleObject(s_HotplugEvent, INFINITE); + } while (SDL_GetAtomicInt(&s_HotplugRunning)); + + if (hid_loaded) { + WIN_UnloadHIDDLL(); + } + return 0; +} static DWORD CALLBACK SDL_DeviceNotificationFunc(HCMNOTIFICATION hNotify, PVOID context, CM_NOTIFY_ACTION action, PCM_NOTIFY_EVENT_DATA eventData, DWORD event_data_size) { if (action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL || action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL) { s_LastDeviceNotification = SDL_GetTicksNS(); + SetEvent(s_HotplugEvent); } return ERROR_SUCCESS; } @@ -192,6 +219,11 @@ void WIN_InitDeviceNotification(void) return; } + // Start the device hotplug thread + SDL_SetAtomicInt(&s_HotplugRunning, true); + s_HotplugEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + s_HotplugThread = SDL_CreateThread(DeviceHotplugThread, "DeviceHotplugThread", NULL); + cfgmgr32_lib_handle = LoadLibraryA("cfgmgr32.dll"); if (cfgmgr32_lib_handle) { CM_Register_Notification = (CM_Register_NotificationFunc)GetProcAddress(cfgmgr32_lib_handle, "CM_Register_Notification"); @@ -225,6 +257,12 @@ void WIN_QuitDeviceNotification(void) // Make sure we have balanced calls to init/quit SDL_assert(s_DeviceNotificationsRequested == 0); + // Stop the device hotplug thread + SDL_SetAtomicInt(&s_HotplugRunning, false); + SetEvent(s_HotplugEvent); + SDL_WaitThread(s_HotplugThread, NULL); + s_HotplugThread = NULL; + if (cfgmgr32_lib_handle) { if (s_DeviceNotificationFuncHandle && CM_Unregister_Notification) { CM_Unregister_Notification(s_DeviceNotificationFuncHandle); diff --git a/libs/SDL3/src/core/windows/SDL_hid.h b/libs/SDL3/src/core/windows/SDL_hid.h index 46c22f2..f085f68 100644 --- a/libs/SDL3/src/core/windows/SDL_hid.h +++ b/libs/SDL3/src/core/windows/SDL_hid.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/windows/SDL_immdevice.c b/libs/SDL3/src/core/windows/SDL_immdevice.c index cc6945b..64cea6d 100644 --- a/libs/SDL3/src/core/windows/SDL_immdevice.c +++ b/libs/SDL3/src/core/windows/SDL_immdevice.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -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, SDL_AudioFormat force_format) +static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid, GUID *dsoundguid, SDL_AudioFormat force_format, bool supports_recording_playback_devices) { /* 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 @@ -147,7 +147,7 @@ static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devn if (!device) { // handle is freed by SDL_IMMDevice_FreeDeviceHandle! - SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *)SDL_malloc(sizeof(SDL_IMMDevice_HandleData)); + SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *)SDL_calloc(1, sizeof(*handle)); if (!handle) { return NULL; } @@ -156,7 +156,7 @@ static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devn SDL_free(handle); return NULL; } - SDL_memcpy(&handle->directsound_guid, dsoundguid, sizeof(GUID)); + SDL_copyp(&handle->directsound_guid, dsoundguid); SDL_AudioSpec spec; SDL_zero(spec); @@ -165,6 +165,28 @@ static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devn spec.format = (force_format != SDL_AUDIO_UNKNOWN) ? force_format : SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt); device = SDL_AddAudioDevice(recording, devname, &spec, handle); + + if (!recording && supports_recording_playback_devices) { + // handle is freed by SDL_IMMDevice_FreeDeviceHandle! + SDL_IMMDevice_HandleData *recording_handle = (SDL_IMMDevice_HandleData *)SDL_malloc(sizeof(*recording_handle)); + if (!recording_handle) { + return NULL; + } + + recording_handle->immdevice_id = SDL_wcsdup(devid); + if (!recording_handle->immdevice_id) { + SDL_free(recording_handle); + return NULL; + } + + SDL_copyp(&recording_handle->directsound_guid, dsoundguid); + + if (!SDL_AddAudioDevice(true, devname, &spec, recording_handle)) { + SDL_free(recording_handle->immdevice_id); + SDL_free(recording_handle); + } + } + if (!device) { SDL_free(handle->immdevice_id); SDL_free(handle); @@ -184,6 +206,7 @@ typedef struct SDLMMNotificationClient const IMMNotificationClientVtbl *lpVtbl; SDL_AtomicInt refcount; SDL_AudioFormat force_format; + bool supports_recording_playback_devices; } SDLMMNotificationClient; static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_QueryInterface(IMMNotificationClient *client, REFIID iid, void **ppv) @@ -257,7 +280,7 @@ static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceStateChanged(IM GUID dsoundguid; GetMMDeviceInfo(device, &utf8dev, &fmt, &dsoundguid); if (utf8dev) { - SDL_IMMDevice_Add(recording, utf8dev, &fmt, pwstrDeviceId, &dsoundguid, client->force_format); + SDL_IMMDevice_Add(recording, utf8dev, &fmt, pwstrDeviceId, &dsoundguid, client->force_format, client->supports_recording_playback_devices); SDL_free(utf8dev); } } else { @@ -288,7 +311,7 @@ static const IMMNotificationClientVtbl notification_client_vtbl = { SDLMMNotificationClient_OnPropertyValueChanged }; -static SDLMMNotificationClient notification_client = { ¬ification_client_vtbl, { 1 }, SDL_AUDIO_UNKNOWN }; +static SDLMMNotificationClient notification_client = { ¬ification_client_vtbl, { 1 }, SDL_AUDIO_UNKNOWN, false }; bool SDL_IMMDevice_Init(const SDL_IMMDevice_callbacks *callbacks) { @@ -365,7 +388,23 @@ bool SDL_IMMDevice_Get(SDL_AudioDevice *device, IMMDevice **immdevice, bool reco return true; } -static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **default_device, SDL_AudioFormat force_format) +bool SDL_IMMDevice_GetIsCapture(IMMDevice *device) +{ + bool iscapture = false; + IMMEndpoint *endpoint = NULL; + if (SUCCEEDED(IMMDevice_QueryInterface(device, &SDL_IID_IMMEndpoint, (void **)&endpoint))) { + EDataFlow flow; + + if (SUCCEEDED(IMMEndpoint_GetDataFlow(endpoint, &flow))) { + iscapture = (flow == eCapture); + } + } + + IMMEndpoint_Release(endpoint); + return iscapture; +} + +static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **default_device, SDL_AudioFormat force_format, bool supports_recording_playback_devices) { /* Note that WASAPI separates "adapter devices" from "audio endpoint devices" ...one adapter device ("SoundBlaster Pro") might have multiple endpoint devices ("Speakers", "Line-Out"). */ @@ -407,7 +446,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, force_format); + SDL_AudioDevice *sdldevice = SDL_IMMDevice_Add(recording, devname, &fmt, devid, &dsoundguid, force_format, supports_recording_playback_devices); if (default_device && default_devid && SDL_wcscmp(default_devid, devid) == 0) { *default_device = sdldevice; } @@ -424,12 +463,13 @@ static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **de IMMDeviceCollection_Release(collection); } -void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording, SDL_AudioFormat force_format) +void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording, SDL_AudioFormat force_format, bool supports_recording_playback_devices) { - EnumerateEndpointsForFlow(false, default_playback, force_format); - EnumerateEndpointsForFlow(true, default_recording, force_format); + EnumerateEndpointsForFlow(false, default_playback, force_format, supports_recording_playback_devices); + EnumerateEndpointsForFlow(true, default_recording, force_format, supports_recording_playback_devices); notification_client.force_format = force_format; + notification_client.supports_recording_playback_devices = supports_recording_playback_devices; // 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 0582bc0..3fc5548 100644 --- a/libs/SDL3/src/core/windows/SDL_immdevice.h +++ b/libs/SDL3/src/core/windows/SDL_immdevice.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -37,7 +37,8 @@ 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, SDL_AudioFormat force_format); +bool SDL_IMMDevice_GetIsCapture(IMMDevice* device); +void SDL_IMMDevice_EnumerateEndpoints(struct SDL_AudioDevice **default_playback, struct SDL_AudioDevice **default_recording, SDL_AudioFormat force_format, bool supports_recording_playback_devices); 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 163635a..562a3f3 100644 --- a/libs/SDL3/src/core/windows/SDL_windows.c +++ b/libs/SDL3/src/core/windows/SDL_windows.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -24,6 +24,10 @@ #include "SDL_windows.h" +#include "../../video/SDL_surface_c.h" + +#include // CommandLineToArgvW() + #include // for CoInitialize/CoUninitialize (Win32 only) #ifdef HAVE_ROAPI_H #include // For RoInitialize/RoUninitialize (Win32 only) @@ -35,16 +39,6 @@ typedef enum RO_INIT_TYPE } RO_INIT_TYPE; #endif -#ifndef _WIN32_WINNT_VISTA -#define _WIN32_WINNT_VISTA 0x0600 -#endif -#ifndef _WIN32_WINNT_WIN7 -#define _WIN32_WINNT_WIN7 0x0601 -#endif -#ifndef _WIN32_WINNT_WIN8 -#define _WIN32_WINNT_WIN8 0x0602 -#endif - #ifndef LOAD_LIBRARY_SEARCH_SYSTEM32 #define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800 #endif @@ -53,6 +47,44 @@ typedef enum RO_INIT_TYPE #define WC_ERR_INVALID_CHARS 0x00000080 #endif +// Dark mode support +typedef enum { + UXTHEME_APPMODE_DEFAULT, + UXTHEME_APPMODE_ALLOW_DARK, + UXTHEME_APPMODE_FORCE_DARK, + UXTHEME_APPMODE_FORCE_LIGHT, + UXTHEME_APPMODE_MAX +} UxthemePreferredAppMode; + +typedef enum { + WCA_UNDEFINED = 0, + WCA_USEDARKMODECOLORS = 26, + WCA_LAST = 27 +} WINDOWCOMPOSITIONATTRIB; + +typedef struct { + WINDOWCOMPOSITIONATTRIB Attrib; + PVOID pvData; + SIZE_T cbData; +} WINDOWCOMPOSITIONATTRIBDATA; + +typedef struct { + ULONG dwOSVersionInfoSize; + ULONG dwMajorVersion; + ULONG dwMinorVersion; + ULONG dwBuildNumber; + ULONG dwPlatformId; + WCHAR szCSDVersion[128]; +} NT_OSVERSIONINFOW; + +typedef bool (WINAPI *ShouldAppsUseDarkMode_t)(void); +typedef void (WINAPI *AllowDarkModeForWindow_t)(HWND, bool); +typedef void (WINAPI *AllowDarkModeForApp_t)(bool); +typedef void (WINAPI *RefreshImmersiveColorPolicyState_t)(void); +typedef UxthemePreferredAppMode (WINAPI *SetPreferredAppMode_t)(UxthemePreferredAppMode); +typedef BOOL (WINAPI *SetWindowCompositionAttribute_t)(HWND, const WINDOWCOMPOSITIONATTRIBDATA *); +typedef void (NTAPI *RtlGetVersion_t)(NT_OSVERSIONINFOW *); + // Fake window to help with DirectInput events. HWND SDL_HelperWindow = NULL; static const TCHAR *SDL_HelperWindowClassName = TEXT("SDLHelperWindowInputCatcher"); @@ -254,6 +286,36 @@ static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WO return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE; } + +static DWORD WIN_BuildNumber = 0; +static BOOL IsWindowsBuildVersionAtLeast(DWORD dwBuildNumber) +{ + if (WIN_BuildNumber != 0) { + return (WIN_BuildNumber >= dwBuildNumber); + } + + HMODULE ntdll = LoadLibrary(TEXT("ntdll.dll")); + if (!ntdll) { + return false; + } + // There is no function to get Windows build number, so let's get it here via 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); + } + FreeLibrary(ntdll); + + WIN_BuildNumber = (os_info.dwBuildNumber & ~0xF0000000); + return (WIN_BuildNumber >= dwBuildNumber); +} +#else +static BOOL IsWindowsBuildVersionAtLeast(DWORD dwBuildNumber) +{ + return TRUE; +} #endif // apply some static variables so we only call into the Win32 API once per process for each check. @@ -270,6 +332,24 @@ static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WO return result; #endif +BOOL WIN_IsWine(void) +{ + static bool checked; + static bool is_wine; + + if (!checked) { + HMODULE ntdll = LoadLibrary(TEXT("ntdll.dll")); + if (ntdll) { + if (GetProcAddress(ntdll, "wine_get_version") != NULL) { + is_wine = true; + } + FreeLibrary(ntdll); + } + checked = true; + } + return is_wine; +} + // this is the oldest thing we run on (and we may lose support for this in SDL3 at any time!), // so there's no "OrGreater" as that would always be TRUE. The other functions are here to // ask "can we support a specific feature?" but this function is here to ask "do we need to do @@ -294,6 +374,11 @@ BOOL WIN_IsWindows8OrGreater(void) CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0)); } +BOOL WIN_IsWindows11OrGreater(void) +{ + return IsWindowsBuildVersionAtLeast(22000); +} + #undef CHECKWINVER @@ -333,7 +418,7 @@ char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid) char *result = NULL; if (WIN_IsEqualGUID(guid, &nullguid)) { - return WIN_StringToUTF8(name); // No GUID, go with what we've got. + return WIN_StringToUTF8W(name); // No GUID, go with what we've got. } ptr = (const unsigned char *)guid; @@ -342,37 +427,37 @@ char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid) ptr[3], ptr[2], ptr[1], ptr[0], ptr[5], ptr[4], ptr[7], ptr[6], ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15]); - strw = WIN_UTF8ToString(keystr); + strw = WIN_UTF8ToStringW(keystr); rc = (RegOpenKeyExW(HKEY_LOCAL_MACHINE, strw, 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS); SDL_free(strw); if (!rc) { - return WIN_StringToUTF8(name); // oh well. + return WIN_StringToUTF8W(name); // oh well. } rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, NULL, &len) == ERROR_SUCCESS); if (!rc) { RegCloseKey(hkey); - return WIN_StringToUTF8(name); // oh well. + return WIN_StringToUTF8W(name); // oh well. } strw = (WCHAR *)SDL_malloc(len + sizeof(WCHAR)); if (!strw) { RegCloseKey(hkey); - return WIN_StringToUTF8(name); // oh well. + return WIN_StringToUTF8W(name); // oh well. } rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, (LPBYTE)strw, &len) == ERROR_SUCCESS); RegCloseKey(hkey); if (!rc) { SDL_free(strw); - return WIN_StringToUTF8(name); // oh well. + return WIN_StringToUTF8W(name); // oh well. } strw[len / 2] = 0; // make sure it's null-terminated. - result = WIN_StringToUTF8(strw); + result = WIN_StringToUTF8W(strw); SDL_free(strw); - return result ? result : WIN_StringToUTF8(name); + return result ? result : WIN_StringToUTF8W(name); #endif } @@ -408,6 +493,134 @@ bool WIN_WindowRectValid(const RECT *rect) return (rect->right > 0); } +void WIN_UpdateDarkModeForHWND(HWND hwnd) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (!IsWindowsBuildVersionAtLeast(17763)) { + // Too old to support dark mode + return; + } + HMODULE uxtheme = LoadLibrary(TEXT("uxtheme.dll")); + if (!uxtheme) { + return; + } + 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 (!IsWindowsBuildVersionAtLeast(18362)) { + AllowDarkModeForApp_t AllowDarkModeForAppFunc = (AllowDarkModeForApp_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(135)); + if (AllowDarkModeForAppFunc) { + AllowDarkModeForAppFunc(true); + } + } else { + SetPreferredAppMode_t SetPreferredAppModeFunc = (SetPreferredAppMode_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(135)); + if (SetPreferredAppModeFunc) { + SetPreferredAppModeFunc(UXTHEME_APPMODE_ALLOW_DARK); + } + } + if (RefreshImmersiveColorPolicyStateFunc) { + RefreshImmersiveColorPolicyStateFunc(); + } + if (AllowDarkModeForWindowFunc) { + AllowDarkModeForWindowFunc(hwnd, true); + } + BOOL value; + // Check dark mode using ShouldAppsUseDarkMode, but use SDL_GetSystemTheme as a fallback + if (ShouldAppsUseDarkModeFunc) { + value = ShouldAppsUseDarkModeFunc() ? TRUE : FALSE; + } else { + value = (SDL_GetSystemTheme() == SDL_SYSTEM_THEME_DARK) ? TRUE : FALSE; + } + FreeLibrary(uxtheme); + if (!IsWindowsBuildVersionAtLeast(18362)) { + SetProp(hwnd, TEXT("UseImmersiveDarkModeColors"), SDL_reinterpret_cast(HANDLE, SDL_static_cast(INT_PTR, value))); + } else { + HMODULE user32 = GetModuleHandle(TEXT("user32.dll")); + if (user32) { + SetWindowCompositionAttribute_t SetWindowCompositionAttributeFunc = (SetWindowCompositionAttribute_t)GetProcAddress(user32, "SetWindowCompositionAttribute"); + if (SetWindowCompositionAttributeFunc) { + WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &value, sizeof(value) }; + SetWindowCompositionAttributeFunc(hwnd, &data); + } + } + } +#endif +} + +HICON WIN_CreateIconFromSurface(SDL_Surface *surface) +{ +#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + SDL_Surface *s = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888); + if (!s) { + return NULL; + } + + /* The dimensions will be needed after s is freed */ + const int width = s->w; + const int height = s->h; + + BITMAPINFO bmpInfo; + SDL_zero(bmpInfo); + bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmpInfo.bmiHeader.biWidth = width; + bmpInfo.bmiHeader.biHeight = -height; /* Top-down bitmap */ + bmpInfo.bmiHeader.biPlanes = 1; + bmpInfo.bmiHeader.biBitCount = 32; + bmpInfo.bmiHeader.biCompression = BI_RGB; + + HDC hdc = GetDC(NULL); + void *pBits = NULL; + HBITMAP hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, &pBits, NULL, 0); + if (!hBitmap) { + ReleaseDC(NULL, hdc); + SDL_DestroySurface(s); + return NULL; + } + + SDL_memcpy(pBits, s->pixels, width * height * 4); + + SDL_DestroySurface(s); + + HBITMAP hMask = CreateBitmap(width, height, 1, 1, NULL); + if (!hMask) { + DeleteObject(hBitmap); + ReleaseDC(NULL, hdc); + return NULL; + } + + HDC hdcMem = CreateCompatibleDC(hdc); + HGDIOBJ oldBitmap = SelectObject(hdcMem, hMask); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + BYTE* pixel = (BYTE*)pBits + (y * width + x) * 4; + BYTE alpha = pixel[3]; + COLORREF maskColor = (alpha == 0) ? RGB(0, 0, 0) : RGB(255, 255, 255); + SetPixel(hdcMem, x, y, maskColor); + } + } + + ICONINFO iconInfo; + iconInfo.fIcon = TRUE; + iconInfo.xHotspot = 0; + iconInfo.yHotspot = 0; + iconInfo.hbmMask = hMask; + iconInfo.hbmColor = hBitmap; + + HICON hIcon = CreateIconIndirect(&iconInfo); + + SelectObject(hdcMem, oldBitmap); + DeleteDC(hdcMem); + DeleteObject(hBitmap); + DeleteObject(hMask); + ReleaseDC(NULL, hdc); + + return hIcon; +#else + return NULL; +#endif +} + // Some GUIDs we need to know without linking to libraries that aren't available before Vista. /* *INDENT-OFF* */ // clang-format off static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; @@ -444,4 +657,72 @@ int WIN_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, return WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cbMultiByte, lpDefaultChar, lpUsedDefaultChar); } +const char *WIN_CheckDefaultArgcArgv(int *pargc, char ***pargv, void **pallocated) +{ + // If the provided argv is valid, we pass it to the main function as-is, since it's probably what the user wants. + // Otherwise, we take a NULL argv as an instruction for SDL to parse the command line into an argv. + // On Windows, when SDL provides the main entry point, argv is always NULL. + + const char *out_of_mem_str = "Out of memory - aborting"; + const char *proc_err_str = "Error processing command line arguments - aborting"; + + *pallocated = NULL; + + if (*pargv) { + return NULL; // just go with what was provided, no error message. + } + + // We need to be careful about how we allocate/free memory here. We can't use SDL_alloc()/SDL_free() + // because the application might have used SDL_SetMemoryFunctions() to change the allocator. + LPWSTR *argvw = NULL; + char **argv = NULL; + + const LPWSTR command_line = GetCommandLineW(); + + // Because of how the Windows command line is structured, we know for sure that the buffer size required to + // store all argument strings converted to UTF-8 (with null terminators) is guaranteed to be less than or equal + // to the size of the original command line string converted to UTF-8. + const int argdata_size = WideCharToMultiByte(CP_UTF8, 0, command_line, -1, NULL, 0, NULL, NULL); // Includes the null terminator + if (!argdata_size) { + return proc_err_str; + } + + int argc = -1; + argvw = CommandLineToArgvW(command_line, &argc); + if (!argvw || argc < 0) { + return out_of_mem_str; + } + + // Allocate argv followed by the argument string buffer as one contiguous allocation. + argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv) + argdata_size); + if (!argv) { + LocalFree(argvw); + return out_of_mem_str; + } + + char *argdata = ((char *)argv) + (argc + 1) * sizeof(*argv); + int argdata_index = 0; + + for (int i = 0; i < argc; ++i) { + const int bytes_written = WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, argdata + argdata_index, argdata_size - argdata_index, NULL, NULL); + if (!bytes_written) { + HeapFree(GetProcessHeap(), 0, argv); + LocalFree(argvw); + return proc_err_str; + } + argv[i] = argdata + argdata_index; + argdata_index += bytes_written; + } + + argv[argc] = NULL; + + LocalFree(argvw); + + *pargc = argc; + *pallocated = argv; + *pargv = argv; + + return NULL; // no error string. +} + #endif // defined(SDL_PLATFORM_WINDOWS) diff --git a/libs/SDL3/src/core/windows/SDL_windows.h b/libs/SDL3/src/core/windows/SDL_windows.h index ef54fe3..5317e30 100644 --- a/libs/SDL3/src/core/windows/SDL_windows.h +++ b/libs/SDL3/src/core/windows/SDL_windows.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -21,10 +21,39 @@ // This is an include file for windows.h with the SDL build settings -#ifndef _INCLUDED_WINDOWS_H -#define _INCLUDED_WINDOWS_H +#ifndef SDL_windows_h_ +#define SDL_windows_h_ #ifdef SDL_PLATFORM_WIN32 + +#ifndef _WIN32_WINNT_NT4 +#define _WIN32_WINNT_NT4 0x0400 +#endif +#ifndef _WIN32_WINNT_WIN2K +#define _WIN32_WINNT_WIN2K 0x0500 +#endif +#ifndef _WIN32_WINNT_WINXP +#define _WIN32_WINNT_WINXP 0x0501 +#endif +#ifndef _WIN32_WINNT_WS03 +#define _WIN32_WINNT_WS03 0x0502 +#endif +#ifndef _WIN32_WINNT_VISTA +#define _WIN32_WINNT_VISTA 0x0600 +#endif +#ifndef _WIN32_WINNT_WIN7 +#define _WIN32_WINNT_WIN7 0x0601 +#endif +#ifndef _WIN32_WINNT_WIN8 +#define _WIN32_WINNT_WIN8 0x0602 +#endif +#ifndef _WIN32_WINNT_WINBLUE +#define _WIN32_WINNT_WINBLUE 0x0603 +#endif +#ifndef _WIN32_WINNT_WIN10 +#define _WIN32_WINNT_WIN10 0x0A00 +#endif + #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN 1 #endif @@ -37,11 +66,17 @@ #undef WINVER #undef _WIN32_WINNT #if defined(SDL_VIDEO_RENDER_D3D12) || defined(HAVE_DXGI1_6_H) -#define _WIN32_WINNT 0xA00 // For D3D12, 0xA00 is required +#define _WIN32_WINNT _WIN32_WINNT_WIN10 // For D3D12, 0xA00 is required #elif defined(HAVE_SHELLSCALINGAPI_H) -#define _WIN32_WINNT 0x603 // For DPI support +#define _WIN32_WINNT _WIN32_WINNT_WINBLUE // For DPI support +#elif defined(HAVE_ROAPI_H) +#define _WIN32_WINNT _WIN32_WINNT_WIN8 +#elif defined(HAVE_SENSORSAPI_H) +#define _WIN32_WINNT _WIN32_WINNT_WIN7 +#elif defined(HAVE_MMDEVICEAPI_H) +#define _WIN32_WINNT _WIN32_WINNT_VISTA #else -#define _WIN32_WINNT 0x501 // Need 0x410 for AlphaBlend() and 0x500 for EnumDisplayDevices(), 0x501 for raw input +#define _WIN32_WINNT _WIN32_WINNT_WINXP // Need 0x410 for AlphaBlend() and 0x500 for EnumDisplayDevices(), 0x501 for raw input #endif #define WINVER _WIN32_WINNT @@ -133,6 +168,9 @@ extern void WIN_CoUninitialize(void); extern HRESULT WIN_RoInitialize(void); extern void WIN_RoUninitialize(void); +// Returns true if we're running on Wine +extern BOOL WIN_IsWine(void); + // Returns true if we're running on Windows XP (any service pack). DOES NOT CHECK XP "OR GREATER"! extern BOOL WIN_IsWindowsXP(void); @@ -145,6 +183,9 @@ extern BOOL WIN_IsWindows7OrGreater(void); // Returns true if we're running on Windows 8 and newer extern BOOL WIN_IsWindows8OrGreater(void); +// Returns true if we're running on Windows 11 and newer +extern BOOL WIN_IsWindows11OrGreater(void); + // You need to SDL_free() the result of this call. extern char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid); @@ -157,16 +198,23 @@ extern void WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect); extern void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect); // Returns false if a window client rect is not valid -bool WIN_WindowRectValid(const RECT *rect); +extern bool WIN_WindowRectValid(const RECT *rect); + +extern void WIN_UpdateDarkModeForHWND(HWND hwnd); + +extern HICON WIN_CreateIconFromSurface(SDL_Surface *surface); extern SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat); // WideCharToMultiByte, but with some WinXP management. extern int WIN_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar); +// parse out command lines from OS if argv is NULL, otherwise pass through unchanged. `*pallocated` must be HeapFree'd by caller if not NULL on successful return. Returns NULL on success, error string on problems. +const char *WIN_CheckDefaultArgcArgv(int *pargc, char ***pargv, void **pallocated); + // Ends C function definitions when using C++ #ifdef __cplusplus } #endif -#endif // _INCLUDED_WINDOWS_H +#endif // SDL_windows_h_ diff --git a/libs/SDL3/src/core/windows/SDL_xinput.c b/libs/SDL3/src/core/windows/SDL_xinput.c index ba5e4c1..93132dd 100644 --- a/libs/SDL3/src/core/windows/SDL_xinput.c +++ b/libs/SDL3/src/core/windows/SDL_xinput.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -32,7 +32,6 @@ XInputSetState_t SDL_XInputSetState = NULL; XInputGetCapabilities_t SDL_XInputGetCapabilities = NULL; XInputGetCapabilitiesEx_t SDL_XInputGetCapabilitiesEx = NULL; XInputGetBatteryInformation_t SDL_XInputGetBatteryInformation = NULL; -DWORD SDL_XInputVersion = 0; static HMODULE s_pXInputDLL = NULL; static int s_XInputDLLRefCount = 0; @@ -54,9 +53,6 @@ bool WIN_LoadXInputDLL(void) SDL_XInputGetCapabilities = (XInputGetCapabilities_t)XInputGetCapabilities; SDL_XInputGetBatteryInformation = (XInputGetBatteryInformation_t)XInputGetBatteryInformation; - // XInput 1.4 ships with Windows 8 and 8.1: - SDL_XInputVersion = (1 << 16) | 4; - return true; } @@ -68,8 +64,6 @@ void WIN_UnloadXInputDLL(void) bool WIN_LoadXInputDLL(void) { - DWORD version = 0; - if (s_pXInputDLL) { SDL_assert(s_XInputDLLRefCount > 0); s_XInputDLLRefCount++; @@ -80,15 +74,10 @@ bool WIN_LoadXInputDLL(void) * This is XInput emulation over Windows.Gaming.Input, and has all the * limitations of that API (no devices at startup, no background input, etc.) */ - version = (1 << 16) | 4; s_pXInputDLL = LoadLibrary(TEXT("XInput1_4.dll")); // 1.4 Ships with Windows 8. if (!s_pXInputDLL) { - version = (1 << 16) | 3; s_pXInputDLL = LoadLibrary(TEXT("XInput1_3.dll")); // 1.3 can be installed as a redistributable component. } - if (!s_pXInputDLL) { - s_pXInputDLL = LoadLibrary(TEXT("bin\\XInput1_3.dll")); - } if (!s_pXInputDLL) { // "9.1.0" Ships with Vista and Win7, and is more limited than 1.3+ (e.g. XInputGetStateEx is not available.) s_pXInputDLL = LoadLibrary(TEXT("XInput9_1_0.dll")); @@ -98,7 +87,6 @@ bool WIN_LoadXInputDLL(void) } SDL_assert(s_XInputDLLRefCount == 0); - SDL_XInputVersion = version; s_XInputDLLRefCount = 1; // 100 is the ordinal for _XInputGetStateEx, which returns the same struct as XinputGetState, but with extra data in wButtons for the guide button, we think... diff --git a/libs/SDL3/src/core/windows/SDL_xinput.h b/libs/SDL3/src/core/windows/SDL_xinput.h index d499cd5..aa2cf99 100644 --- a/libs/SDL3/src/core/windows/SDL_xinput.h +++ b/libs/SDL3/src/core/windows/SDL_xinput.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -260,7 +260,6 @@ extern XInputSetState_t SDL_XInputSetState; extern XInputGetCapabilities_t SDL_XInputGetCapabilities; extern XInputGetCapabilitiesEx_t SDL_XInputGetCapabilitiesEx; extern XInputGetBatteryInformation_t SDL_XInputGetBatteryInformation; -extern DWORD SDL_XInputVersion; // ((major << 16) & 0xFF00) | (minor & 0xFF) // Ends C function definitions when using C++ #ifdef __cplusplus diff --git a/libs/SDL3/src/core/windows/pch.c b/libs/SDL3/src/core/windows/pch.c index 4b0c6f8..b2b446f 100644 --- a/libs/SDL3/src/core/windows/pch.c +++ b/libs/SDL3/src/core/windows/pch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/windows/pch_cpp.cpp b/libs/SDL3/src/core/windows/pch_cpp.cpp index 4b0c6f8..b2b446f 100644 --- a/libs/SDL3/src/core/windows/pch_cpp.cpp +++ b/libs/SDL3/src/core/windows/pch_cpp.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/core/windows/version.rc b/libs/SDL3/src/core/windows/version.rc index 568e832..fd7ee2a 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,20,0 - PRODUCTVERSION 3,2,20,0 + FILEVERSION 3,4,2,0 + PRODUCTVERSION 3,4,2,0 FILEFLAGSMASK 0x3fL FILEFLAGS 0x0L FILEOS 0x40004L @@ -23,12 +23,12 @@ BEGIN BEGIN VALUE "CompanyName", "\0" VALUE "FileDescription", "SDL\0" - VALUE "FileVersion", "3, 2, 20, 0\0" + VALUE "FileVersion", "3, 4, 2, 0\0" VALUE "InternalName", "SDL\0" - VALUE "LegalCopyright", "Copyright (C) 2025 Sam Lantinga\0" + VALUE "LegalCopyright", "Copyright (C) 2026 Sam Lantinga\0" VALUE "OriginalFilename", "SDL3.dll\0" VALUE "ProductName", "Simple DirectMedia Layer\0" - VALUE "ProductVersion", "3, 2, 20, 0\0" + VALUE "ProductVersion", "3, 4, 2, 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 b836532..966a5ae 100644 --- a/libs/SDL3/src/cpuinfo/SDL_cpuinfo.c +++ b/libs/SDL3/src/cpuinfo/SDL_cpuinfo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -29,7 +29,11 @@ // CPU feature detection for SDL -#ifdef HAVE_SYSCONF +#if defined(HAVE_GETPAGESIZE) && !defined(SDL_PLATFORM_WINDOWS) +#define USE_GETPAGESIZE +#endif + +#if defined(HAVE_SYSCONF) || defined(USE_GETPAGESIZE) #include #endif #ifdef HAVE_SYSCTLBYNAME @@ -38,13 +42,12 @@ #endif #if defined(SDL_PLATFORM_MACOS) && (defined(__ppc__) || defined(__ppc64__)) #include // For AltiVec check -#elif defined(SDL_PLATFORM_OPENBSD) && defined(__powerpc__) +#elif defined(SDL_PLATFORM_OPENBSD) && defined(__powerpc__) && !defined(HAVE_ELF_AUX_INFO) #include #include // For AltiVec check #include -#elif defined(SDL_PLATFORM_FREEBSD) && defined(__powerpc__) +#elif defined(SDL_PLATFORM_FREEBSD) && defined(__powerpc__) && defined(HAVE_ELF_AUX_INFO) #include -#include #elif defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) #include #include @@ -73,11 +76,7 @@ #include #endif -#if defined(SDL_PLATFORM_ANDROID) && defined(__arm__) && !defined(HAVE_GETAUXVAL) -#include -#endif - -#if defined(HAVE_GETAUXVAL) || defined(HAVE_ELF_AUX_INFO) +#if defined(HAVE_GETAUXVAL) || defined(HAVE_ELF_AUX_INFO) || defined(SDL_PLATFORM_ANDROID) #include #endif @@ -174,7 +173,7 @@ static int CPU_haveCPUID(void) : : "%rax", "%rcx" ); -#elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__) +#elif defined(_MSC_VER) && defined(_M_IX86) __asm { pushfd ; Get original EFLAGS pop eax @@ -247,7 +246,7 @@ done: " popq %%rbx \n" \ : "=a"(a), "=S"(b), "=c"(c), "=d"(d) \ : "a"(func)) -#elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__) +#elif defined(_MSC_VER) && defined(_M_IX86) #define cpuid(func, a, b, c, d) \ __asm { \ __asm mov eax, func \ @@ -311,7 +310,7 @@ static void CPU_calcCPUIDFeatures(void) : "%edx"); #elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) && (_MSC_FULL_VER >= 160040219) // VS2010 SP1 a = (int)_xgetbv(0); -#elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__) +#elif defined(_MSC_VER) && defined(_M_IX86) __asm { xor ecx, ecx @@ -331,7 +330,12 @@ static int CPU_haveAltiVec(void) { volatile int altivec = 0; #ifndef SDL_CPUINFO_DISABLED -#if (defined(SDL_PLATFORM_MACOS) && (defined(__ppc__) || defined(__ppc64__))) || (defined(SDL_PLATFORM_OPENBSD) && defined(__powerpc__)) +#if (defined(SDL_PLATFORM_FREEBSD) || defined(SDL_PLATFORM_OPENBSD)) && defined(__powerpc__) && defined(HAVE_ELF_AUX_INFO) + unsigned long cpufeatures = 0; + elf_aux_info(AT_HWCAP, &cpufeatures, sizeof(cpufeatures)); + altivec = cpufeatures & PPC_FEATURE_HAS_ALTIVEC; + return altivec; +#elif (defined(SDL_PLATFORM_MACOS) && (defined(__ppc__) || defined(__ppc64__))) || (defined(SDL_PLATFORM_OPENBSD) && defined(__powerpc__)) #ifdef SDL_PLATFORM_OPENBSD int selectors[2] = { CTL_MACHDEP, CPU_ALTIVEC }; #else @@ -343,11 +347,6 @@ static int CPU_haveAltiVec(void) if (0 == error) { altivec = (hasVectorUnit != 0); } -#elif defined(SDL_PLATFORM_FREEBSD) && defined(__powerpc__) - unsigned long cpufeatures = 0; - 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) @@ -421,6 +420,12 @@ static int CPU_haveARMSIMD(void) return regs.r[0]; } +#elif defined(SDL_PLATFORM_NGAGE) +static int CPU_haveARMSIMD(void) +{ + // The RM920T is based on the ARMv4T architecture and doesn't have SIMD. + return 0; +} #else static int CPU_haveARMSIMD(void) { @@ -468,6 +473,8 @@ static int CPU_haveNEON(void) return 1; #elif defined(SDL_PLATFORM_3DS) return 0; +#elif defined(SDL_PLATFORM_NGAGE) + return 0; // The ARM920T is based on the ARMv4T architecture and doesn't have NEON. #elif defined(SDL_PLATFORM_APPLE) && defined(__ARM_ARCH) && (__ARM_ARCH >= 7) // (note that sysctlbyname("hw.optional.neon") doesn't work!) return 1; // all Apple ARMv7 chips and later have NEON. @@ -475,30 +482,16 @@ static int CPU_haveNEON(void) return 0; // assume anything else from Apple doesn't have NEON. #elif !defined(__arm__) return 0; // not an ARM CPU at all. -#elif defined(SDL_PLATFORM_OPENBSD) - return 1; // OpenBSD only supports ARMv7 CPUs that have NEON. #elif defined(HAVE_ELF_AUX_INFO) unsigned long hasneon = 0; if (elf_aux_info(AT_HWCAP, (void *)&hasneon, (int)sizeof(hasneon)) != 0) { return 0; } return (hasneon & HWCAP_NEON) == HWCAP_NEON; -#elif (defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)) && defined(HAVE_GETAUXVAL) +#elif (defined(SDL_PLATFORM_LINUX) && defined(HAVE_GETAUXVAL)) || defined(SDL_PLATFORM_ANDROID) return (getauxval(AT_HWCAP) & HWCAP_NEON) == HWCAP_NEON; #elif defined(SDL_PLATFORM_LINUX) return readProcAuxvForNeon(); -#elif defined(SDL_PLATFORM_ANDROID) - // Use NDK cpufeatures to read either /proc/self/auxv or /proc/cpuinfo - { - AndroidCpuFamily cpu_family = android_getCpuFamily(); - if (cpu_family == ANDROID_CPU_FAMILY_ARM) { - uint64_t cpu_features = android_getCpuFeatures(); - if (cpu_features & ANDROID_CPU_ARM_FEATURE_NEON) { - return 1; - } - } - return 0; - } #elif defined(SDL_PLATFORM_RISCOS) // Use the VFPSupport_Features SWI to access the MVFR registers { @@ -511,6 +504,8 @@ static int CPU_haveNEON(void) } return 0; } +#elif defined(SDL_PLATFORM_OPENBSD) + return 1; // OpenBSD only supports ARMv7 CPUs that have NEON. #elif defined(SDL_PLATFORM_EMSCRIPTEN) return 0; #else @@ -1206,6 +1201,65 @@ int SDL_GetSystemRAM(void) return SDL_SystemRAM; } + +static int SDL_SystemPageSize = -1; + +int SDL_GetSystemPageSize(void) +{ + if (SDL_SystemPageSize == -1) { +#ifdef SDL_PLATFORM_SYSTEM_PAGE_SIZE_PRIVATE // consoles will define this in a platform-specific internal header. + SDL_SystemPageSize = SDL_PLATFORM_SYSTEM_PAGE_SIZE_PRIVATE; +#endif +#ifdef SDL_PLATFORM_3DS + SDL_SystemPageSize = 4096; // It's an ARM11 CPU; I assume this is 4K. +#endif +#ifdef SDL_PLATFORM_VITA + SDL_SystemPageSize = 4096; // It's an ARMv7 CPU; I assume this is 4K. +#endif +#ifdef SDL_PLATFORM_PS2 + SDL_SystemPageSize = 4096; // It's a MIPS R5900 CPU; I assume this is 4K. +#endif +#if defined(HAVE_SYSCONF) && (defined(_SC_PAGESIZE) || defined(_SC_PAGE_SIZE)) + if (SDL_SystemPageSize <= 0) { + #if defined(_SC_PAGE_SIZE) + SDL_SystemPageSize = (int)sysconf(_SC_PAGE_SIZE); + #else + SDL_SystemPageSize = (int)sysconf(_SC_PAGESIZE); + #endif + } +#endif +#if defined(HAVE_SYSCTLBYNAME) && defined(HW_PAGESIZE) + if (SDL_SystemPageSize <= 0) { + // NetBSD, OpenBSD, FreeBSD, Darwin...everything agrees to use HW_PAGESIZE. + int mib[2] = { CTL_HW, HW_PAGESIZE }; + int pagesize = 0; + size_t len = sizeof(pagesize); + + if (sysctl(mib, 2, &pagesize, &len, NULL, 0) == 0) { + SDL_SystemPageSize = pagesize; + } + } +#endif +#ifdef USE_GETPAGESIZE + if (SDL_SystemPageSize <= 0) { + SDL_SystemPageSize = getpagesize(); + } +#endif +#if defined(SDL_PLATFORM_WINDOWS) + if (SDL_SystemPageSize <= 0) { + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + SDL_SystemPageSize = (int) sysinfo.dwPageSize; + } +#endif + if (SDL_SystemPageSize < 0) { // in case we got a weird result somewhere, or no better information, force it to 0. + SDL_SystemPageSize = 0; // unknown page size, sorry. + } + } + return SDL_SystemPageSize; +} + + size_t SDL_GetSIMDAlignment(void) { if (SDL_SIMDAlignment == 0xFFFFFFFF) { diff --git a/libs/SDL3/src/cpuinfo/SDL_cpuinfo_c.h b/libs/SDL3/src/cpuinfo/SDL_cpuinfo_c.h index 9da31a9..c3918a5 100644 --- a/libs/SDL3/src/cpuinfo/SDL_cpuinfo_c.h +++ b/libs/SDL3/src/cpuinfo/SDL_cpuinfo_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/dialog/SDL_dialog.c b/libs/SDL3/src/dialog/SDL_dialog.c index a77e443..817df38 100644 --- a/libs/SDL3/src/dialog/SDL_dialog.c +++ b/libs/SDL3/src/dialog/SDL_dialog.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/dialog/SDL_dialog.h b/libs/SDL3/src/dialog/SDL_dialog.h index beee7dd..12f61f2 100644 --- a/libs/SDL3/src/dialog/SDL_dialog.h +++ b/libs/SDL3/src/dialog/SDL_dialog.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/dialog/SDL_dialog_utils.c b/libs/SDL3/src/dialog/SDL_dialog_utils.c index 8d2b186..826cc60 100644 --- a/libs/SDL3/src/dialog/SDL_dialog_utils.c +++ b/libs/SDL3/src/dialog/SDL_dialog_utils.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/dialog/SDL_dialog_utils.h b/libs/SDL3/src/dialog/SDL_dialog_utils.h index 1343dd7..773fb96 100644 --- a/libs/SDL3/src/dialog/SDL_dialog_utils.h +++ b/libs/SDL3/src/dialog/SDL_dialog_utils.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -28,7 +28,7 @@ /* Transform the name given in argument into something viable for the engine. Useful if there are special characters to avoid on certain platforms (such as "|" with Zenity). */ -typedef char *(NameTransform)(const char * name); +typedef char *(*NameTransform)(const char * name); // Converts all the filters into a single string. // [filter]{[filter]...} diff --git a/libs/SDL3/src/dialog/android/SDL_androiddialog.c b/libs/SDL3/src/dialog/android/SDL_androiddialog.c index 5eaf945..705a0d5 100644 --- a/libs/SDL3/src/dialog/android/SDL_androiddialog.c +++ b/libs/SDL3/src/dialog/android/SDL_androiddialog.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m b/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m index bc806f5..a2337cb 100644 --- a/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m +++ b/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -27,7 +27,11 @@ #import #import +#ifdef SDL_VIDEO_DRIVER_COCOA extern void Cocoa_SetWindowHasModalDialog(SDL_Window *window, bool has_modal); +#else +#define Cocoa_SetWindowHasModalDialog(window, has_modal) +#endif static void AddFileExtensionType(NSMutableArray *types, const char *pattern_ptr) { @@ -60,13 +64,13 @@ static void ReactivateAfterDialog(void) 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); + SDL_Window *window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL); int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0); bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false); - const char* default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); - const char* title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL); - const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); + const char *default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); + const char *title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL); + const char *accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); if (filters) { const char *msg = validate_filters(filters, nfilters); @@ -157,8 +161,15 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil // Keep behavior consistent with other platforms [dialog setAllowsOtherFileTypes:YES]; - if (default_location) { - [dialog setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:default_location]]]; + if (default_location && *default_location) { + char last = default_location[SDL_strlen(default_location) - 1]; + NSURL* url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:default_location]]; + if (last == '/') { + [dialog setDirectoryURL:url]; + } else { + [dialog setDirectoryURL:[url URLByDeletingLastPathComponent]]; + [dialog setNameFieldStringValue:[url lastPathComponent]]; + } } NSWindow *w = NULL; @@ -175,7 +186,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil [dialog beginSheetModalForWindow:w completionHandler:^(NSInteger result) { if (result == NSModalResponseOK) { if (dialog_as_open) { - NSArray* urls = [dialog_as_open URLs]; + NSArray *urls = [dialog_as_open URLs]; const char *files[[urls count] + 1]; for (int i = 0; i < [urls count]; i++) { files[i] = [[[urls objectAtIndex:i] path] UTF8String]; @@ -197,7 +208,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil } else { if ([dialog runModal] == NSModalResponseOK) { if (dialog_as_open) { - NSArray* urls = [dialog_as_open URLs]; + NSArray *urls = [dialog_as_open URLs]; const char *files[[urls count] + 1]; for (int i = 0; i < [urls count]; i++) { files[i] = [[[urls objectAtIndex:i] path] UTF8String]; diff --git a/libs/SDL3/src/dialog/dummy/SDL_dummydialog.c b/libs/SDL3/src/dialog/dummy/SDL_dummydialog.c index 121a090..9f36afd 100644 --- a/libs/SDL3/src/dialog/dummy/SDL_dummydialog.c +++ b/libs/SDL3/src/dialog/dummy/SDL_dummydialog.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/dialog/haiku/SDL_haikudialog.cc b/libs/SDL3/src/dialog/haiku/SDL_haikudialog.cc index d60e343..33bf87c 100644 --- a/libs/SDL3/src/dialog/haiku/SDL_haikudialog.cc +++ b/libs/SDL3/src/dialog/haiku/SDL_haikudialog.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -163,7 +163,7 @@ public: case B_CANCEL: // Whenever the dialog is closed (Cancel but also after Open and Save) { nFiles = m_files.size(); - const char* files[nFiles + 1]; + const char *files[nFiles + 1]; for (int i = 0; i < nFiles; i++) { files[i] = m_files[i].c_str(); } @@ -194,14 +194,14 @@ private: void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) { - SDL_Window* window = (SDL_Window*) SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); - SDL_DialogFileFilter* filters = (SDL_DialogFileFilter*) SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL); + SDL_Window *window = (SDL_Window *)SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); + SDL_DialogFileFilter *filters = (SDL_DialogFileFilter *)SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL); int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0); bool many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false); - const char* location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); - const char* title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL); - const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); - const char* cancel = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, NULL); + const char *location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); + const char *title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL); + const char *accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); + const char *cancel = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, NULL); bool modal = !!window; @@ -222,7 +222,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil }; if (!SDL_InitBeApp()) { - char* err = SDL_strdup(SDL_GetError()); + char *err = SDL_strdup(SDL_GetError()); SDL_SetError("Couldn't init Be app: %s", err); SDL_free(err); callback(userdata, NULL, -1); @@ -251,9 +251,9 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil SDLBRefFilter *filter = new(std::nothrow) SDLBRefFilter(filters, nfilters); if (looper == NULL || messenger == NULL || filter == NULL) { - SDL_free(looper); - SDL_free(messenger); - SDL_free(filter); + delete looper; + delete messenger; + delete filter; SDL_OutOfMemory(); callback(userdata, NULL, -1); return; diff --git a/libs/SDL3/src/dialog/unix/SDL_portaldialog.c b/libs/SDL3/src/dialog/unix/SDL_portaldialog.c index efecd12..7949a33 100644 --- a/libs/SDL3/src/dialog/unix/SDL_portaldialog.c +++ b/libs/SDL3/src/dialog/unix/SDL_portaldialog.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -26,6 +26,8 @@ #ifdef SDL_USE_LIBDBUS #include +#include +#include #include #include #include @@ -288,13 +290,18 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog const char *method; const char *method_title; - SDL_Window* window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); + SDL_Window *window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL); int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0); bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false); - const char* default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); - const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); + const char *default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); + const char *accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); + char *location_name = NULL; + char *location_folder = NULL; + struct stat statbuf; bool open_folders = false; + bool save_file_existing = false; + bool save_file_new_named = false; switch (type) { case SDL_FILEDIALOG_OPENFILE: @@ -305,6 +312,28 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog case SDL_FILEDIALOG_SAVEFILE: method = "SaveFile"; method_title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, "Save File"); + if (default_location) { + if (stat(default_location, &statbuf) == 0) { + save_file_existing = S_ISREG(statbuf.st_mode); + } else if (errno == ENOENT) { + char *dirc = SDL_strdup(default_location); + if (dirc) { + location_folder = SDL_strdup(dirname(dirc)); + SDL_free(dirc); + if (location_folder) { + save_file_new_named = (stat(location_folder, &statbuf) == 0) && S_ISDIR(statbuf.st_mode); + } + } + } + + if (save_file_existing || save_file_new_named) { + char *basec = SDL_strdup(default_location); + if (basec) { + location_name = SDL_strdup(basename(basec)); + SDL_free(basec); + } + } + } break; case SDL_FILEDIALOG_OPENFOLDER: @@ -317,10 +346,11 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog /* This is already checked in ../SDL_dialog.c; this silences compiler warnings */ SDL_SetError("Invalid file dialog type: %d", type); callback(userdata, NULL, -1); - return; + goto cleanup; } SDL_DBusContext *dbus = SDL_DBus_GetContext(); + DBusError error; DBusMessage *msg; DBusMessageIter params, options; const char *signal_id = NULL; @@ -332,23 +362,25 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog const char *err_msg = validate_filters(filters, nfilters); + dbus->error_init(&error); + if (err_msg) { SDL_SetError("%s", err_msg); callback(userdata, NULL, -1); - return; + goto cleanup; } if (dbus == NULL) { SDL_SetError("Failed to connect to DBus"); callback(userdata, NULL, -1); - return; + goto cleanup; } msg = dbus->message_new_method_call(PORTAL_DESTINATION, PORTAL_PATH, PORTAL_INTERFACE, method); if (msg == NULL) { SDL_SetError("Failed to send message to portal"); callback(userdata, NULL, -1); - return; + goto cleanup; } dbus->message_iter_init_append(msg, ¶ms); @@ -362,7 +394,7 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog handle_str = SDL_malloc(len * sizeof(char)); if (!handle_str) { callback(userdata, NULL, -1); - return; + goto cleanup; } SDL_snprintf(handle_str, len, "%s%s", WAYLAND_HANDLE_PREFIX, parent_handle); @@ -373,7 +405,7 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog handle_str = SDL_malloc(len * sizeof(char)); if (!handle_str) { callback(userdata, NULL, -1); - return; + goto cleanup; } // The portal wants X11 window ID numbers in hex. @@ -393,7 +425,7 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog handle_str = SDL_malloc(sizeof(char) * (HANDLE_LEN + 1)); if (!handle_str) { callback(userdata, NULL, -1); - return; + goto cleanup; } SDL_snprintf(handle_str, HANDLE_LEN, "%u", ++handle_id); DBus_AppendStringOption(dbus, &options, "handle_token", handle_str); @@ -410,14 +442,34 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog DBus_AppendFilters(dbus, &options, filters, nfilters); } if (default_location) { - DBus_AppendByteArray(dbus, &options, "current_folder", default_location); + if (save_file_existing && location_name) { + /* Open a save dialog at an existing file */ + DBus_AppendByteArray(dbus, &options, "current_file", default_location); + /* Setting "current_name" should not be necessary however the kde-desktop-portal sets the filename without an extension. + * An alternative would be to match the extension to a filter and set "current_filter". + */ + DBus_AppendStringOption(dbus, &options, "current_name", location_name); + } else if (save_file_new_named && location_folder && location_name) { + /* Open a save dialog at a location with a suggested name */ + DBus_AppendByteArray(dbus, &options, "current_folder", location_folder); + DBus_AppendStringOption(dbus, &options, "current_name", location_name); + } else { + DBus_AppendByteArray(dbus, &options, "current_folder", default_location); + } } if (accept) { DBus_AppendStringOption(dbus, &options, "accept_label", accept); } dbus->message_iter_close_container(¶ms, &options); - DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_INFINITE, NULL); + DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_INFINITE, &error); + if (dbus->error_is_set(&error)) { + SDL_SetError("Failed to open dialog via DBus, %s: %s", error.name, error.message); + dbus->error_free(&error); + callback(userdata, NULL, -1); + goto cleanup; + } + if (reply) { DBusMessageIter reply_iter; dbus->message_iter_init(reply, &reply_iter); @@ -443,9 +495,16 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog } SDL_snprintf(filter, filter_len, SIGNAL_FILTER"%s'", signal_id); - dbus->bus_add_match(dbus->session_conn, filter, NULL); + dbus->bus_add_match(dbus->session_conn, filter, &error); SDL_free(filter); + if (dbus->error_is_set(&error)) { + SDL_SetError("Failed to set up DBus listener for dialog, %s: %s", error.name, error.message); + dbus->error_free(&error); + callback(userdata, NULL, -1); + goto cleanup; + } + SignalCallback *data = SDL_malloc(sizeof(SignalCallback)); if (!data) { callback(userdata, NULL, -1); @@ -469,6 +528,10 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog incorrect_type: dbus->message_unref(reply); + +cleanup: + SDL_free(location_name); + SDL_free(location_folder); } bool SDL_Portal_detect(void) diff --git a/libs/SDL3/src/dialog/unix/SDL_portaldialog.h b/libs/SDL3/src/dialog/unix/SDL_portaldialog.h index 4497287..a31d0bf 100644 --- a/libs/SDL3/src/dialog/unix/SDL_portaldialog.h +++ b/libs/SDL3/src/dialog/unix/SDL_portaldialog.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/dialog/unix/SDL_unixdialog.c b/libs/SDL3/src/dialog/unix/SDL_unixdialog.c index bec2ac9..d9b57c0 100644 --- a/libs/SDL3/src/dialog/unix/SDL_unixdialog.c +++ b/libs/SDL3/src/dialog/unix/SDL_unixdialog.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/dialog/unix/SDL_zenitydialog.c b/libs/SDL3/src/dialog/unix/SDL_zenitydialog.c index 4632c8e..e34434f 100644 --- a/libs/SDL3/src/dialog/unix/SDL_zenitydialog.c +++ b/libs/SDL3/src/dialog/unix/SDL_zenitydialog.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -21,6 +21,8 @@ #include "SDL_internal.h" #include "../SDL_dialog_utils.h" +#include "SDL_zenitydialog.h" +#include "SDL_zenitymessagebox.h" #define X11_HANDLE_MAX_WIDTH 28 typedef struct @@ -88,6 +90,7 @@ static bool get_x11_window_handle(SDL_PropertiesID props, char *out) static zenityArgs *create_zenity_args(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) { zenityArgs *args = SDL_calloc(1, sizeof(*args)); + int zenity_major = 0, zenity_minor = 0; if (!args) { return NULL; } @@ -110,6 +113,8 @@ static zenityArgs *create_zenity_args(SDL_FileDialogType type, SDL_DialogFileCal } args->argv = argv; + SDL_get_zenity_version(&zenity_major, &zenity_minor); + /* Properties can be destroyed as soon as the function returns; copy over what we need. */ #define COPY_STRING_PROPERTY(dst, prop) \ { \ @@ -158,7 +163,8 @@ static zenityArgs *create_zenity_args(SDL_FileDialogType type, SDL_DialogFileCal argv[argc++] = args->filename; } - if (get_x11_window_handle(props, args->x11_window_handle)) { + if (get_x11_window_handle(props, args->x11_window_handle) && + (zenity_major > 3 || (zenity_major == 3 && zenity_minor >= 6))) { argv[argc++] = "--modal"; argv[argc++] = "--attach"; argv[argc++] = args->x11_window_handle; diff --git a/libs/SDL3/src/dialog/unix/SDL_zenitydialog.h b/libs/SDL3/src/dialog/unix/SDL_zenitydialog.h index 4cfe892..4f86498 100644 --- a/libs/SDL3/src/dialog/unix/SDL_zenitydialog.h +++ b/libs/SDL3/src/dialog/unix/SDL_zenitydialog.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/dialog/unix/SDL_zenitymessagebox.c b/libs/SDL3/src/dialog/unix/SDL_zenitymessagebox.c new file mode 100644 index 0000000..3683b2a --- /dev/null +++ b/libs/SDL3/src/dialog/unix/SDL_zenitymessagebox.c @@ -0,0 +1,189 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_zenitymessagebox.h" + +#define ZENITY_VERSION_LEN 32 // Number of bytes to read from zenity --version (including NUL) + +#define MAX_BUTTONS 8 // Maximum number of buttons supported + +static bool parse_zenity_version(const char *version, int *major, int *minor) +{ + /* We expect the version string is in the form of MAJOR.MINOR.MICRO + * as described in meson.build. We'll ignore everything after that. + */ + const char *version_ptr = version; + char *end_ptr = NULL; + int tmp = (int) SDL_strtol(version_ptr, &end_ptr, 10); + if (tmp == 0 && end_ptr == version_ptr) { + return SDL_SetError("failed to get zenity major version number"); + } + *major = tmp; + + if (*end_ptr == '.') { + version_ptr = end_ptr + 1; // skip the dot + tmp = (int) SDL_strtol(version_ptr, &end_ptr, 10); + if (tmp == 0 && end_ptr == version_ptr) { + return SDL_SetError("failed to get zenity minor version number"); + } + *minor = tmp; + } else { + *minor = 0; + } + return true; +} + +bool SDL_get_zenity_version(int *major, int *minor) +{ + const char *argv[] = { "zenity", "--version", NULL }; + bool result = false; + + SDL_Process *process = SDL_CreateProcess(argv, true); + if (!process) { + return false; + } + + char *output = SDL_ReadProcess(process, NULL, NULL); + if (output) { + result = parse_zenity_version(output, major, minor); + SDL_free(output); + } + SDL_DestroyProcess(process); + + return result; +} + +bool SDL_Zenity_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) +{ + int exit_code = -1; + int zenity_major = 0, zenity_minor = 0, output_len = 0; + int argc = 5, i; + const char *argv[5 + 2 /* icon name */ + 2 /* title */ + 2 /* message */ + 2 * MAX_BUTTONS + 1 /* NULL */] = { + "zenity", "--question", "--switch", "--no-wrap", "--no-markup" + }; + SDL_Process *process; + + if (messageboxdata->numbuttons > MAX_BUTTONS) { + return SDL_SetError("Too many buttons (%d max allowed)", MAX_BUTTONS); + } + + // get zenity version so we know which arg to use + if (!SDL_get_zenity_version(&zenity_major, &zenity_minor)) { + return false; // get_zenity_version() calls SDL_SetError(), so message is already set + } + + /* https://gitlab.gnome.org/GNOME/zenity/-/commit/c686bdb1b45e95acf010efd9ca0c75527fbb4dea + * This commit removed --icon-name without adding a deprecation notice. + * We need to handle it gracefully, otherwise no message box will be shown. + */ + argv[argc++] = zenity_major > 3 || (zenity_major == 3 && zenity_minor >= 90) ? "--icon" : "--icon-name"; + switch (messageboxdata->flags & (SDL_MESSAGEBOX_ERROR | SDL_MESSAGEBOX_WARNING | SDL_MESSAGEBOX_INFORMATION)) { + case SDL_MESSAGEBOX_ERROR: + argv[argc++] = "dialog-error"; + break; + case SDL_MESSAGEBOX_WARNING: + argv[argc++] = "dialog-warning"; + break; + case SDL_MESSAGEBOX_INFORMATION: + default: + argv[argc++] = "dialog-information"; + break; + } + + if (messageboxdata->title && messageboxdata->title[0]) { + argv[argc++] = "--title"; + argv[argc++] = messageboxdata->title; + } else { + argv[argc++] = "--title="; + } + + if (messageboxdata->message && messageboxdata->message[0]) { + argv[argc++] = "--text"; + argv[argc++] = messageboxdata->message; + } else { + argv[argc++] = "--text="; + } + + for (i = 0; i < messageboxdata->numbuttons; ++i) { + if (messageboxdata->buttons[i].text && messageboxdata->buttons[i].text[0]) { + int len = SDL_strlen(messageboxdata->buttons[i].text); + if (len > output_len) { + output_len = len; + } + + argv[argc++] = "--extra-button"; + argv[argc++] = messageboxdata->buttons[i].text; + } else { + argv[argc++] = "--extra-button="; + } + } + if (messageboxdata->numbuttons == 0) { + argv[argc++] = "--extra-button=OK"; + } + argv[argc] = NULL; + + SDL_PropertiesID props = SDL_CreateProperties(); + if (!props) { + return false; + } + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, argv); + // If buttonID is set we need to wait and read the results + if (buttonID) { + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + } else { + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_NULL); + } + process = SDL_CreateProcessWithProperties(props); + SDL_DestroyProperties(props); + if (!process) { + return false; + } + if (buttonID) { + char *output = SDL_ReadProcess(process, NULL, &exit_code); + if (exit_code < 0 || exit_code == 255) { + SDL_free(output); + } else if (output) { + // It likes to add a newline... + char *tmp = SDL_strrchr(output, '\n'); + if (tmp) { + *tmp = '\0'; + } + + // Check which button got pressed + for (i = 0; i < messageboxdata->numbuttons; i += 1) { + if (messageboxdata->buttons[i].text) { + if (SDL_strcmp(output, messageboxdata->buttons[i].text) == 0) { + *buttonID = messageboxdata->buttons[i].buttonID; + break; + } + } + } + SDL_free(output); + } + } else { + SDL_WaitProcess(process, true, &exit_code); + } + SDL_DestroyProcess(process); + + return !(exit_code < 0 || exit_code == 255); +} diff --git a/libs/SDL3/src/dialog/unix/SDL_zenitymessagebox.h b/libs/SDL3/src/dialog/unix/SDL_zenitymessagebox.h new file mode 100644 index 0000000..a06c462 --- /dev/null +++ b/libs/SDL3/src/dialog/unix/SDL_zenitymessagebox.h @@ -0,0 +1,28 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_zenitymessagebox_h_ +#define SDL_zenitymessagebox_h_ + +extern bool SDL_Zenity_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID); +extern bool SDL_get_zenity_version(int *major, int *minor); + +#endif // SDL_waylandmessagebox_h_ diff --git a/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c b/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c index 364753c..b3d86c5 100644 --- a/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c +++ b/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -19,15 +19,367 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "SDL_internal.h" +#include "../../core/windows/SDL_windows.h" #include "../SDL_dialog.h" #include "../SDL_dialog_utils.h" -#include +#include #include #include -#include "../../core/windows/SDL_windows.h" +#include #include "../../thread/SDL_systhread.h" +#if WINVER < _WIN32_WINNT_VISTA +typedef struct _COMDLG_FILTERSPEC +{ + LPCWSTR pszName; + LPCWSTR pszSpec; +} COMDLG_FILTERSPEC; + +typedef enum FDE_OVERWRITE_RESPONSE +{ + FDEOR_DEFAULT, + FDEOR_ACCEPT, + FDEOR_REFUSE, +} FDE_OVERWRITE_RESPONSE; + +typedef enum FDE_SHAREVIOLATION_RESPONSE +{ + FDESVR_DEFAULT, + FDESVR_ACCEPT, + FDESVR_REFUSE, +} FDE_SHAREVIOLATION_RESPONSE; + +typedef enum FDAP +{ + FDAP_BOTTOM, + FDAP_TOP, +} FDAP; + +typedef ULONG SFGAOF; +typedef DWORD SHCONTF; + +#endif // WINVER < _WIN32_WINNT_VISTA + +#ifndef __IFileDialog_FWD_DEFINED__ +typedef struct IFileDialog IFileDialog; +#endif +#ifndef __IShellItem_FWD_DEFINED__ +typedef struct IShellItem IShellItem; +#endif +#ifndef __IFileOpenDialog_FWD_DEFINED__ +typedef struct IFileOpenDialog IFileOpenDialog; +#endif +#ifndef __IFileDialogEvents_FWD_DEFINED__ +typedef struct IFileDialogEvents IFileDialogEvents; +#endif +#ifndef __IShellItemArray_FWD_DEFINED__ +typedef struct IShellItemArray IShellItemArray; +#endif +#ifndef __IEnumShellItems_FWD_DEFINED__ +typedef struct IEnumShellItems IEnumShellItems; +#endif +#ifndef __IShellItemFilter_FWD_DEFINED__ +typedef struct IShellItemFilter IShellItemFilter; +#endif +#ifndef __IFileDialog2_FWD_DEFINED__ +typedef struct IFileDialog2 IFileDialog2; +#endif + +#ifndef __IShellItemFilter_INTERFACE_DEFINED__ +typedef struct IShellItemFilterVtbl +{ + HRESULT (__stdcall *QueryInterface)(IShellItemFilter *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IShellItemFilter *This); + ULONG (__stdcall *Release)(IShellItemFilter *This); + HRESULT (__stdcall *IncludeItem)(IShellItemFilter *This, IShellItem *psi); + HRESULT (__stdcall *GetEnumFlagsForItem)(IShellItemFilter *This, IShellItem *psi, SHCONTF *pgrfFlags); +} IShellItemFilterVtbl; + +struct IShellItemFilter +{ + IShellItemFilterVtbl *lpVtbl; +}; + +#endif // #ifndef __IShellItemFilter_INTERFACE_DEFINED__ + +#ifndef __IFileDialogEvents_INTERFACE_DEFINED__ +typedef struct IFileDialogEventsVtbl +{ + HRESULT (__stdcall *QueryInterface)(IFileDialogEvents *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IFileDialogEvents *This); + ULONG (__stdcall *Release)(IFileDialogEvents *This); + HRESULT (__stdcall *OnFileOk)(IFileDialogEvents *This, IFileDialog *pfd); + HRESULT (__stdcall *OnFolderChanging)(IFileDialogEvents *This, IFileDialog *pfd, IShellItem *psiFolder); + HRESULT (__stdcall *OnFolderChange)(IFileDialogEvents *This, IFileDialog *pfd); + HRESULT (__stdcall *OnSelectionChange)(IFileDialogEvents *This, IFileDialog *pfd); + HRESULT (__stdcall *OnShareViolation)(IFileDialogEvents *This, IFileDialog *pfd, IShellItem *psi, FDE_SHAREVIOLATION_RESPONSE *pResponse); + HRESULT (__stdcall *OnTypeChange)(IFileDialogEvents *This, IFileDialog *pfd); + HRESULT (__stdcall *OnOverwrite)(IFileDialogEvents *This, IFileDialog *pfd, IShellItem *psi, FDE_OVERWRITE_RESPONSE *pResponse); +} IFileDialogEventsVtbl; + +struct IFileDialogEvents +{ + IFileDialogEventsVtbl *lpVtbl; +}; + +#endif // #ifndef __IFileDialogEvents_INTERFACE_DEFINED__ + +#ifndef __IShellItem_INTERFACE_DEFINED__ +typedef enum _SIGDN { + SIGDN_NORMALDISPLAY = 0x00000000, + SIGDN_PARENTRELATIVEPARSING = 0x80018001, + SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000, + SIGDN_PARENTRELATIVEEDITING = 0x80031001, + SIGDN_DESKTOPABSOLUTEEDITING = 0x8004C000, + SIGDN_FILESYSPATH = 0x80058000, + SIGDN_URL = 0x80068000, + SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007C001, + SIGDN_PARENTRELATIVE = 0x80080001, + SIGDN_PARENTRELATIVEFORUI = 0x80094001 +} SIGDN; + +enum _SICHINTF { + SICHINT_DISPLAY = 0x00000000, + SICHINT_ALLFIELDS = 0x80000000, + SICHINT_CANONICAL = 0x10000000, + SICHINT_TEST_FILESYSPATH_IF_NOT_EQUAL = 0x20000000 +}; +typedef DWORD SICHINTF; + +extern const IID IID_IShellItem; + +typedef struct IShellItemVtbl +{ + HRESULT (__stdcall *QueryInterface)(IShellItem *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IShellItem *This); + ULONG (__stdcall *Release)(IShellItem *This); + HRESULT (__stdcall *BindToHandler)(IShellItem *This, IBindCtx *pbc, REFGUID bhid, REFIID riid, void **ppv); + HRESULT (__stdcall *GetParent)(IShellItem *This, IShellItem **ppsi); + HRESULT (__stdcall *GetDisplayName)(IShellItem *This, SIGDN sigdnName, LPWSTR *ppszName); + HRESULT (__stdcall *GetAttributes)(IShellItem *This, SFGAOF sfgaoMask, SFGAOF *psfgaoAttribs); + HRESULT (__stdcall *Compare)(IShellItem *This, IShellItem *psi, SICHINTF hint, int *piOrder); +} IShellItemVtbl; + +struct IShellItem +{ + IShellItemVtbl *lpVtbl; +}; + +#endif // #ifndef __IShellItem_INTERFACE_DEFINED__ + +#ifndef __IEnumShellItems_INTERFACE_DEFINED__ +typedef struct IEnumShellItemsVtbl +{ + HRESULT (__stdcall *QueryInterface)(IEnumShellItems *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IEnumShellItems *This); + ULONG (__stdcall *Release)(IEnumShellItems *This); + HRESULT (__stdcall *Next)(IEnumShellItems *This, ULONG celt, IShellItem **rgelt, ULONG *pceltFetched); + HRESULT (__stdcall *Skip)(IEnumShellItems *This, ULONG celt); + HRESULT (__stdcall *Reset)(IEnumShellItems *This); + HRESULT (__stdcall *Clone)(IEnumShellItems *This, IEnumShellItems **ppenum); +} IEnumShellItemsVtbl; + +struct IEnumShellItems +{ + IEnumShellItemsVtbl *lpVtbl; +}; + +#endif // #ifndef __IEnumShellItems_INTERFACE_DEFINED__ + +#ifndef __IShellItemArray_INTERFACE_DEFINED__ +typedef enum SIATTRIBFLAGS +{ + SIATTRIBFLAGS_AND = 0x1, + SIATTRIBFLAGS_OR = 0x2, + SIATTRIBFLAGS_APPCOMPAT = 0x3, + SIATTRIBFLAGS_MASK = 0x3, + SIATTRIBFLAGS_ALLITEMS = 0x4000 +} SIATTRIBFLAGS; + +typedef struct IShellItemArrayVtbl +{ + HRESULT (__stdcall *QueryInterface)(IShellItemArray *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IShellItemArray *This); + ULONG (__stdcall *Release)(IShellItemArray *This); + HRESULT (__stdcall *BindToHandler)(IShellItemArray *This, IBindCtx *pbc, REFGUID bhid, REFIID riid, void **ppvOut); + HRESULT (__stdcall *GetPropertyStore)(IShellItemArray *This, GETPROPERTYSTOREFLAGS flags, REFIID riid, void **ppv); + HRESULT (__stdcall *GetPropertyDescriptionList)(IShellItemArray *This, REFPROPERTYKEY keyType, REFIID riid, void **ppv); + HRESULT (__stdcall *GetAttributes)(IShellItemArray *This, SIATTRIBFLAGS AttribFlags, SFGAOF sfgaoMask, SFGAOF *psfgaoAttribs); + HRESULT (__stdcall *GetCount)(IShellItemArray *This, DWORD *pdwNumItems); + HRESULT (__stdcall *GetItemAt)(IShellItemArray *This, DWORD dwIndex, IShellItem **ppsi); + HRESULT (__stdcall *EnumItems)(IShellItemArray *This, IEnumShellItems **ppenumShellItems); +} IShellItemArrayVtbl; + +struct IShellItemArray +{ + IShellItemArrayVtbl *lpVtbl; +}; + +#endif // #ifndef __IShellItemArray_INTERFACE_DEFINED__ + +// Flags/GUIDs defined for compatibility with pre-Vista headers +#ifndef __IFileDialog_INTERFACE_DEFINED__ +enum _FILEOPENDIALOGOPTIONS +{ + FOS_OVERWRITEPROMPT = 0x2, + FOS_STRICTFILETYPES = 0x4, + FOS_NOCHANGEDIR = 0x8, + FOS_PICKFOLDERS = 0x20, + FOS_FORCEFILESYSTEM = 0x40, + FOS_ALLNONSTORAGEITEMS = 0x80, + FOS_NOVALIDATE = 0x100, + FOS_ALLOWMULTISELECT = 0x200, + FOS_PATHMUSTEXIST = 0x800, + FOS_FILEMUSTEXIST = 0x1000, + FOS_CREATEPROMPT = 0x2000, + FOS_SHAREAWARE = 0x4000, + FOS_NOREADONLYRETURN = 0x8000, + FOS_NOTESTFILECREATE = 0x10000, + FOS_HIDEMRUPLACES = 0x20000, + FOS_HIDEPINNEDPLACES = 0x40000, + FOS_NODEREFERENCELINKS = 0x100000, + FOS_OKBUTTONNEEDSINTERACTION = 0x200000, + FOS_DONTADDTORECENT = 0x2000000, + FOS_FORCESHOWHIDDEN = 0x10000000, + FOS_DEFAULTNOMINIMODE = 0x20000000, + FOS_FORCEPREVIEWPANEON = 0x40000000, + FOS_SUPPORTSTREAMABLEITEMS = 0x80000000 +}; + +typedef DWORD FILEOPENDIALOGOPTIONS; + +extern const IID IID_IFileDialog; + +typedef struct IFileDialogVtbl +{ + HRESULT (__stdcall *QueryInterface)(IFileDialog *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IFileDialog *This); + ULONG (__stdcall *Release)(IFileDialog *This); + HRESULT (__stdcall *Show)(IFileDialog *This, HWND hwndOwner); + HRESULT (__stdcall *SetFileTypes)(IFileDialog *This, UINT cFileTypes, const COMDLG_FILTERSPEC *rgFilterSpec); + HRESULT (__stdcall *SetFileTypeIndex)(IFileDialog *This, UINT iFileType); + HRESULT (__stdcall *GetFileTypeIndex)(IFileDialog *This, UINT *piFileType); + HRESULT (__stdcall *Advise)(IFileDialog *This, IFileDialogEvents *pfde, DWORD *pdwCookie); + HRESULT (__stdcall *Unadvise)(IFileDialog *This, DWORD dwCookie); + HRESULT (__stdcall *SetOptions)(IFileDialog *This, FILEOPENDIALOGOPTIONS fos); + HRESULT (__stdcall *GetOptions)(IFileDialog *This, FILEOPENDIALOGOPTIONS *pfos); + HRESULT (__stdcall *SetDefaultFolder)(IFileDialog *This, IShellItem *psi); + HRESULT (__stdcall *SetFolder)(IFileDialog *This, IShellItem *psi); + HRESULT (__stdcall *GetFolder)(IFileDialog *This, IShellItem **ppsi); + HRESULT (__stdcall *GetCurrentSelection)(IFileDialog *This, IShellItem **ppsi); + HRESULT (__stdcall *SetFileName)(IFileDialog *This, LPCWSTR pszName); + HRESULT (__stdcall *GetFileName)(IFileDialog *This, LPWSTR *pszName); + HRESULT (__stdcall *SetTitle)(IFileDialog *This, LPCWSTR pszTitle); + HRESULT (__stdcall *SetOkButtonLabel)(IFileDialog *This, LPCWSTR pszText); + HRESULT (__stdcall *SetFileNameLabel)(IFileDialog *This, LPCWSTR pszLabel); + HRESULT (__stdcall *GetResult)(IFileDialog *This, IShellItem **ppsi); + HRESULT (__stdcall *AddPlace)(IFileDialog *This, IShellItem *psi, FDAP fdap); + HRESULT (__stdcall *SetDefaultExtension)(IFileDialog *This, LPCWSTR pszDefaultExtension); + HRESULT (__stdcall *Close)(IFileDialog *This, HRESULT hr); + HRESULT (__stdcall *SetClientGuid)(IFileDialog *This, REFGUID guid); + HRESULT (__stdcall *ClearClientData)(IFileDialog *This); + HRESULT (__stdcall *SetFilter)(IFileDialog *This, IShellItemFilter *pFilter); +} IFileDialogVtbl; + +struct IFileDialog +{ + IFileDialogVtbl *lpVtbl; +}; + +#endif // #ifndef __IFileDialog_INTERFACE_DEFINED__ + +#ifndef __IFileOpenDialog_INTERFACE_DEFINED__ +typedef struct IFileOpenDialogVtbl +{ + HRESULT (__stdcall *QueryInterface)(IFileOpenDialog *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IFileOpenDialog *This); + ULONG (__stdcall *Release)(IFileOpenDialog *This); + HRESULT (__stdcall *Show)(IFileOpenDialog *This, HWND hwndOwner); + HRESULT (__stdcall *SetFileTypes)(IFileOpenDialog *This, UINT cFileTypes, const COMDLG_FILTERSPEC *rgFilterSpec); + HRESULT (__stdcall *SetFileTypeIndex)(IFileOpenDialog *This, UINT iFileType); + HRESULT (__stdcall *GetFileTypeIndex)(IFileOpenDialog *This, UINT *piFileType); + HRESULT (__stdcall *Advise)(IFileOpenDialog *This, IFileDialogEvents *pfde, DWORD *pdwCookie); + HRESULT (__stdcall *Unadvise)(IFileOpenDialog *This, DWORD dwCookie); + HRESULT (__stdcall *SetOptions)(IFileOpenDialog *This, FILEOPENDIALOGOPTIONS fos); + HRESULT (__stdcall *GetOptions)(IFileOpenDialog *This, FILEOPENDIALOGOPTIONS *pfos); + HRESULT (__stdcall *SetDefaultFolder)(IFileOpenDialog *This, IShellItem *psi); + HRESULT (__stdcall *SetFolder)(IFileOpenDialog *This, IShellItem *psi); + HRESULT (__stdcall *GetFolder)(IFileOpenDialog *This, IShellItem **ppsi); + HRESULT (__stdcall *GetCurrentSelection)(IFileOpenDialog *This, IShellItem **ppsi); + HRESULT (__stdcall *SetFileName)(IFileOpenDialog *This, LPCWSTR pszName); + HRESULT (__stdcall *GetFileName)(IFileOpenDialog *This, LPWSTR *pszName); + HRESULT (__stdcall *SetTitle)(IFileOpenDialog *This, LPCWSTR pszTitle); + HRESULT (__stdcall *SetOkButtonLabel)(IFileOpenDialog *This, LPCWSTR pszText); + HRESULT (__stdcall *SetFileNameLabel)(IFileOpenDialog *This, LPCWSTR pszLabel); + HRESULT (__stdcall *GetResult)(IFileOpenDialog *This, IShellItem **ppsi); + HRESULT (__stdcall *AddPlace)(IFileOpenDialog *This, IShellItem *psi, FDAP fdap); + HRESULT (__stdcall *SetDefaultExtension)(IFileOpenDialog *This, LPCWSTR pszDefaultExtension); + HRESULT (__stdcall *Close)(IFileOpenDialog *This, HRESULT hr); + HRESULT (__stdcall *SetClientGuid)(IFileOpenDialog *This, REFGUID guid); + HRESULT (__stdcall *ClearClientData)(IFileOpenDialog *This); + HRESULT (__stdcall *SetFilter)(IFileOpenDialog *This, IShellItemFilter *pFilter); + HRESULT (__stdcall *GetResults)(IFileOpenDialog *This, IShellItemArray **ppenum); + HRESULT (__stdcall *GetSelectedItems)(IFileOpenDialog *This, IShellItemArray **ppsai); +} IFileOpenDialogVtbl; + +struct IFileOpenDialog +{ + IFileOpenDialogVtbl *lpVtbl; +}; + +#endif // #ifndef __IFileOpenDialog_INTERFACE_DEFINED__ + +#ifndef __IFileDialog2_INTERFACE_DEFINED__ +typedef struct IFileDialog2Vtbl +{ + HRESULT (__stdcall *QueryInterface)(IFileDialog2 *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IFileDialog2 *This); + ULONG (__stdcall *Release)(IFileDialog2 *This); + HRESULT (__stdcall *Show)(IFileDialog2 *This, HWND hwndOwner); + HRESULT (__stdcall *SetFileTypes)(IFileDialog2 *This, UINT cFileTypes, const COMDLG_FILTERSPEC *rgFilterSpec); + HRESULT (__stdcall *SetFileTypeIndex)(IFileDialog2 *This, UINT iFileType); + HRESULT (__stdcall *GetFileTypeIndex)(IFileDialog2 *This, UINT *piFileType); + HRESULT (__stdcall *Advise)(IFileDialog2 *This, IFileDialogEvents *pfde, DWORD *pdwCookie); + HRESULT (__stdcall *Unadvise)(IFileDialog2 *This, DWORD dwCookie); + HRESULT (__stdcall *SetOptions)(IFileDialog2 *This, FILEOPENDIALOGOPTIONS fos); + HRESULT (__stdcall *GetOptions)(IFileDialog2 *This, FILEOPENDIALOGOPTIONS *pfos); + HRESULT (__stdcall *SetDefaultFolder)(IFileDialog2 *This, IShellItem *psi); + HRESULT (__stdcall *SetFolder)(IFileDialog2 *This, IShellItem *psi); + HRESULT (__stdcall *GetFolder)(IFileDialog2 *This, IShellItem **ppsi); + HRESULT (__stdcall *GetCurrentSelection)(IFileDialog2 *This, IShellItem **ppsi); + HRESULT (__stdcall *SetFileName)(IFileDialog2 *This, LPCWSTR pszName); + HRESULT (__stdcall *GetFileName)(IFileDialog2 *This, LPWSTR *pszName); + HRESULT (__stdcall *SetTitle)(IFileDialog2 *This, LPCWSTR pszTitle); + HRESULT (__stdcall *SetOkButtonLabel)(IFileDialog2 *This, LPCWSTR pszText); + HRESULT (__stdcall *SetFileNameLabel)(IFileDialog2 *This, LPCWSTR pszLabel); + HRESULT (__stdcall *GetResult)(IFileDialog2 *This, IShellItem **ppsi); + HRESULT (__stdcall *AddPlace)(IFileDialog2 *This, IShellItem *psi, FDAP fdap); + HRESULT (__stdcall *SetDefaultExtension)(IFileDialog2 *This, LPCWSTR pszDefaultExtension); + HRESULT (__stdcall *Close)(IFileDialog2 *This, HRESULT hr); + HRESULT (__stdcall *SetClientGuid)(IFileDialog2 *This, REFGUID guid); + HRESULT (__stdcall *ClearClientData)(IFileDialog2 *This); + HRESULT (__stdcall *SetFilter)(IFileDialog2 *This, IShellItemFilter *pFilter); + HRESULT (__stdcall *SetCancelButtonLabel)(IFileDialog2 *This, LPCWSTR pszLabel); + HRESULT (__stdcall *SetNavigationRoot)(IFileDialog2 *This, IShellItem *psi); +} IFileDialog2Vtbl; + +struct IFileDialog2 +{ + IFileDialog2Vtbl *lpVtbl; +}; + +#endif // #ifndef __IFileDialog2_INTERFACE_DEFINED__ + +/* *INDENT-OFF* */ // clang-format off +static const CLSID SDL_CLSID_FileOpenDialog = { 0xdc1c5a9c, 0xe88a, 0x4dde, { 0xa5, 0xa1, 0x60, 0xf8, 0x2a, 0x20, 0xae, 0xf7 } }; +static const CLSID SDL_CLSID_FileSaveDialog = { 0xc0b4e2f3, 0xba21, 0x4773, { 0x8d, 0xba, 0x33, 0x5e, 0xc9, 0x46, 0xeb, 0x8b } }; + +static const IID SDL_IID_IFileDialog = { 0x42f85136, 0xdb7e, 0x439c, { 0x85, 0xf1, 0xe4, 0x07, 0x5d, 0x13, 0x5f, 0xc8 } }; +static const IID SDL_IID_IFileDialog2 = { 0x61744fc7, 0x85b5, 0x4791, { 0xa9, 0xb0, 0x27, 0x22, 0x76, 0x30, 0x9b, 0x13 } }; +static const IID SDL_IID_IFileOpenDialog = { 0xd57c7288, 0xd4ad, 0x4768, { 0xbe, 0x02, 0x9d, 0x96, 0x95, 0x32, 0xd9, 0x60 } }; +/* *INDENT-ON* */ // clang-format on + // If this number is too small, selecting too many files will give an error #define SELECTLIST_SIZE 65536 @@ -35,9 +387,11 @@ typedef struct { bool is_save; wchar_t *filters_str; + int nfilters; char *default_file; SDL_Window *parent; DWORD flags; + bool allow_many; SDL_DialogFileCallback callback; void *userdata; char *title; @@ -48,6 +402,7 @@ typedef struct typedef struct { SDL_Window *parent; + bool allow_many; SDL_DialogFileCallback callback; char *default_folder; void *userdata; @@ -101,18 +456,324 @@ char *clear_filt_names(const char *filt) return cleared; } +bool windows_ShowModernFileFolderDialog(SDL_FileDialogType dialog_type, const char *default_file, SDL_Window *parent, bool allow_many, SDL_DialogFileCallback callback, void *userdata, const char *title, const char *accept, const char *cancel, wchar_t *filter_wchar, int nfilters) +{ + bool is_save = dialog_type == SDL_FILEDIALOG_SAVEFILE; + bool is_folder = dialog_type == SDL_FILEDIALOG_OPENFOLDER; + + if (is_save) { + // Just in case; the code relies on that + allow_many = false; + } + + HMODULE shell32_handle = NULL; + + typedef HRESULT(WINAPI *pfnSHCreateItemFromParsingName)(PCWSTR, IBindCtx *, REFIID, void **); + pfnSHCreateItemFromParsingName pSHCreateItemFromParsingName = NULL; + + IFileDialog *pFileDialog = NULL; + IFileOpenDialog *pFileOpenDialog = NULL; + IFileDialog2 *pFileDialog2 = NULL; + IShellItemArray *pItemArray = NULL; + IShellItem *pItem = NULL; + IShellItem *pFolderItem = NULL; + LPWSTR filePath = NULL; + COMDLG_FILTERSPEC *filter_data = NULL; + char **files = NULL; + wchar_t *title_w = NULL; + wchar_t *accept_w = NULL; + wchar_t *cancel_w = NULL; + FILEOPENDIALOGOPTIONS pfos; + + wchar_t *default_file_w = NULL; + wchar_t *default_folder_w = NULL; + + bool success = false; + bool co_init = false; + + // We can assume shell32 is already loaded here. + shell32_handle = GetModuleHandle(TEXT("shell32.dll")); + if (!shell32_handle) { + goto quit; + } + + pSHCreateItemFromParsingName = (pfnSHCreateItemFromParsingName)GetProcAddress(shell32_handle, "SHCreateItemFromParsingName"); + if (!pSHCreateItemFromParsingName) { + goto quit; + } + + if (filter_wchar && nfilters > 0) { + wchar_t *filter_ptr = filter_wchar; + filter_data = SDL_calloc(sizeof(COMDLG_FILTERSPEC), nfilters); + if (!filter_data) { + goto quit; + } + + for (int i = 0; i < nfilters; i++) { + filter_data[i].pszName = filter_ptr; + filter_ptr += SDL_wcslen(filter_ptr) + 1; + filter_data[i].pszSpec = filter_ptr; + filter_ptr += SDL_wcslen(filter_ptr) + 1; + } + + // assert(*filter_ptr == L'\0'); + } + + if (title && (title_w = WIN_UTF8ToStringW(title)) == NULL) { + goto quit; + } + + if (accept && (accept_w = WIN_UTF8ToStringW(accept)) == NULL) { + goto quit; + } + + if (cancel && (cancel_w = WIN_UTF8ToStringW(cancel)) == NULL) { + goto quit; + } + + if (default_file) { + default_folder_w = WIN_UTF8ToStringW(default_file); + + if (!default_folder_w) { + goto quit; + } + + for (wchar_t *chrptr = default_folder_w; *chrptr; chrptr++) { + if (*chrptr == L'/' || *chrptr == L'\\') { + default_file_w = chrptr; + } + } + + if (!default_file_w) { + default_file_w = default_folder_w; + default_folder_w = NULL; + } else { + *default_file_w = L'\0'; + default_file_w++; + + if (SDL_wcslen(default_file_w) == 0) { + default_file_w = NULL; + } + } + } + +#define CHECK(op) if (!SUCCEEDED(op)) { goto quit; } + + CHECK(WIN_CoInitialize()); + + co_init = true; + + CHECK(CoCreateInstance(is_save ? &SDL_CLSID_FileSaveDialog : &SDL_CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, &SDL_IID_IFileDialog, (void**)&pFileDialog)); + CHECK(pFileDialog->lpVtbl->QueryInterface(pFileDialog, &SDL_IID_IFileDialog2, (void**)&pFileDialog2)); + + if (allow_many) { + CHECK(pFileDialog->lpVtbl->QueryInterface(pFileDialog, &SDL_IID_IFileOpenDialog, (void**)&pFileOpenDialog)); + } + + CHECK(pFileDialog2->lpVtbl->GetOptions(pFileDialog2, &pfos)); + + pfos |= FOS_NOCHANGEDIR; + if (allow_many) pfos |= FOS_ALLOWMULTISELECT; + if (is_save) pfos |= FOS_OVERWRITEPROMPT; + if (is_folder) pfos |= FOS_PICKFOLDERS; + + CHECK(pFileDialog2->lpVtbl->SetOptions(pFileDialog2, pfos)); + + if (cancel_w) { + CHECK(pFileDialog2->lpVtbl->SetCancelButtonLabel(pFileDialog2, cancel_w)); + } + + if (accept_w) { + CHECK(pFileDialog->lpVtbl->SetOkButtonLabel(pFileDialog, accept_w)); + } + + if (title_w) { + CHECK(pFileDialog->lpVtbl->SetTitle(pFileDialog, title_w)); + } + + if (filter_data) { + CHECK(pFileDialog->lpVtbl->SetFileTypes(pFileDialog, nfilters, filter_data)); + } + + if (default_folder_w) { + CHECK(pSHCreateItemFromParsingName(default_folder_w, NULL, &IID_IShellItem, (void**)&pFolderItem)); + CHECK(pFileDialog->lpVtbl->SetFolder(pFileDialog, pFolderItem)); + } + + if (default_file_w) { + CHECK(pFileDialog->lpVtbl->SetFileName(pFileDialog, default_file_w)); + } + + if (parent) { + HWND window = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(parent), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); + + HRESULT hr = pFileDialog->lpVtbl->Show(pFileDialog, window); + + if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { + const char * const results[] = { NULL }; + UINT selected_filter; + + // This is a one-based index, not zero-based. Doc link in similar comment below + CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); + callback(userdata, results, selected_filter - 1); + success = true; + goto quit; + } else if (!SUCCEEDED(hr)) { + goto quit; + } + } else { + HRESULT hr = pFileDialog->lpVtbl->Show(pFileDialog, NULL); + + if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { + const char * const results[] = { NULL }; + UINT selected_filter; + + // This is a one-based index, not zero-based. Doc link in similar comment below + CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); + callback(userdata, results, selected_filter - 1); + success = true; + goto quit; + } else if (!SUCCEEDED(hr)) { + goto quit; + } + } + + if (allow_many) { + DWORD nResults; + UINT selected_filter; + + CHECK(pFileOpenDialog->lpVtbl->GetResults(pFileOpenDialog, &pItemArray)); + CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); + CHECK(pItemArray->lpVtbl->GetCount(pItemArray, &nResults)); + + files = SDL_calloc(nResults + 1, sizeof(char*)); + if (!files) { + goto quit; + } + char** files_ptr = files; + + for (DWORD i = 0; i < nResults; i++) { + CHECK(pItemArray->lpVtbl->GetItemAt(pItemArray, i, &pItem)); + CHECK(pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, &filePath)); + + *(files_ptr++) = WIN_StringToUTF8(filePath); + + CoTaskMemFree(filePath); + filePath = NULL; + pItem->lpVtbl->Release(pItem); + pItem = NULL; + } + + callback(userdata, (const char * const *) files, selected_filter - 1); + success = true; + } else { + // This is a one-based index, not zero-based. + // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-getfiletypeindex#parameters + UINT selected_filter; + + CHECK(pFileDialog->lpVtbl->GetResult(pFileDialog, &pItem)); + CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); + CHECK(pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, &filePath)); + + char *file = WIN_StringToUTF8(filePath); + if (!file) { + goto quit; + } + const char * const results[] = { file, NULL }; + callback(userdata, results, selected_filter - 1); + success = true; + SDL_free(file); + } + + success = true; + +#undef CHECK + +quit: + if (!success) { + WIN_SetError("dialogg"); + callback(userdata, NULL, -1); + } + + if (co_init) { + WIN_CoUninitialize(); + } + + if (pFileOpenDialog) { + pFileOpenDialog->lpVtbl->Release(pFileOpenDialog); + } + + if (pFileDialog2) { + pFileDialog2->lpVtbl->Release(pFileDialog2); + } + + if (pFileDialog) { + pFileDialog->lpVtbl->Release(pFileDialog); + } + + if (pItem) { + pItem->lpVtbl->Release(pItem); + } + + if (pFolderItem) { + pFolderItem->lpVtbl->Release(pFolderItem); + } + + if (pItemArray) { + pItemArray->lpVtbl->Release(pItemArray); + } + + if (filePath) { + CoTaskMemFree(filePath); + } + + // If both default_file_w and default_folder_w are non-NULL, then + // default_file_w is a pointer into default_folder_w. + if (default_folder_w) { + SDL_free(default_folder_w); + } else { + SDL_free(default_file_w); + } + + SDL_free(title_w); + + SDL_free(accept_w); + + SDL_free(cancel_w); + + SDL_free(filter_data); + + if (files) { + for (char** files_ptr = files; *files_ptr; files_ptr++) { + SDL_free(*files_ptr); + } + SDL_free(files); + } + + return success; +} + // TODO: The new version of file dialogs void windows_ShowFileDialog(void *ptr) { + winArgs *args = (winArgs *) ptr; bool is_save = args->is_save; const char *default_file = args->default_file; SDL_Window *parent = args->parent; DWORD flags = args->flags; + bool allow_many = args->allow_many; SDL_DialogFileCallback callback = args->callback; void *userdata = args->userdata; const char *title = args->title; + const char *accept = args->accept; + const char *cancel = args->cancel; wchar_t *filter_wchar = args->filters_str; + int nfilters = args->nfilters; + + if (windows_ShowModernFileFolderDialog(is_save ? SDL_FILEDIALOG_SAVEFILE : SDL_FILEDIALOG_OPENFILE, default_file, parent, allow_many, callback, userdata, title, accept, cancel, filter_wchar, nfilters)) { + return; + } /* GetOpenFileName and GetSaveFileName have the same signature (yes, LPOPENFILENAMEW even for the save dialog) */ @@ -277,7 +938,7 @@ void windows_ShowFileDialog(void *ptr) while (*file_ptr) { nfiles++; - char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char*) * (nfiles + 1)); + char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char *) * (nfiles + 1)); if (!new_cfl) { for (size_t i = 0; i < nfiles - 1; i++) { @@ -327,7 +988,7 @@ void windows_ShowFileDialog(void *ptr) // If the user chose only one file, it's all just one string if (nfiles == 0) { nfiles++; - char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char*) * (nfiles + 1)); + char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char *) * (nfiles + 1)); if (!new_cfl) { SDL_free(chosen_files_list); @@ -348,7 +1009,7 @@ void windows_ShowFileDialog(void *ptr) } } - callback(userdata, (const char * const*) chosen_files_list, getFilterIndex(dialog.nFilterIndex)); + callback(userdata, (const char * const *) chosen_files_list, getFilterIndex(dialog.nFilterIndex)); for (size_t i = 0; i < nfiles; i++) { SDL_free(chosen_files_list[i]); @@ -407,7 +1068,15 @@ void windows_ShowFolderDialog(void *ptr) SDL_DialogFileCallback callback = args->callback; void *userdata = args->userdata; HWND parent = NULL; + int allow_many = args->allow_many; + char *default_folder = args->default_folder; const char *title = args->title; + const char *accept = args->accept; + const char *cancel = args->cancel; + + if (windows_ShowModernFileFolderDialog(SDL_FILEDIALOG_OPENFOLDER, default_folder, window, allow_many, callback, userdata, title, accept, cancel, NULL, 0)) { + return; + } if (window) { parent = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); @@ -443,11 +1112,11 @@ void windows_ShowFolderDialog(void *ptr) SHGetPathFromIDListW(lpItem, buffer); char *chosen_file = WIN_StringToUTF8W(buffer); const char *files[2] = { chosen_file, NULL }; - callback(userdata, (const char * const*) files, -1); + callback(userdata, (const char * const *) files, -1); SDL_free(chosen_file); } else { const char *files[1] = { NULL }; - callback(userdata, (const char * const*) files, -1); + callback(userdata, (const char * const *) files, -1); } } @@ -532,9 +1201,11 @@ static void ShowFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_ args->is_save = is_save; args->filters_str = filters_str; + args->nfilters = nfilters; args->default_file = default_location ? SDL_strdup(default_location) : NULL; args->parent = window; args->flags = flags; + args->allow_many = allow_many; args->callback = callback; args->userdata = userdata; args->title = title ? SDL_strdup(title) : NULL; @@ -571,6 +1242,7 @@ void ShowFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Windo } args->parent = window; + args->allow_many = allow_many; args->callback = callback; args->default_folder = default_location ? SDL_strdup(default_location) : NULL; args->userdata = userdata; diff --git a/libs/SDL3/src/dynapi/SDL_dynapi.c b/libs/SDL3/src/dynapi/SDL_dynapi.c index a7b51af..495a8e5 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi.c +++ b/libs/SDL3/src/dynapi/SDL_dynapi.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -40,6 +40,8 @@ #include #define SDL_MAIN_NOIMPL // don't drag in header-only implementation of SDL_main #include +#include "../core/SDL_core_unsupported.h" +#include "../video/SDL_video_unsupported.h" // These headers have system specific definitions, so aren't included above @@ -86,7 +88,7 @@ static void SDL_InitDynamicAPI(void); } #define SDL_DYNAPI_VARARGS(_static, name, initcall) \ - _static bool SDLCALL SDL_SetError##name(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \ + _static bool SDLCALL SDL_SetError##name(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \ { \ char buf[128], *str = buf; \ int result; \ @@ -98,7 +100,7 @@ static void SDL_InitDynamicAPI(void); if (result >= 0 && (size_t)result >= sizeof(buf)) { \ str = NULL; \ va_start(ap, fmt); \ - result = jump_table.SDL_vasprintf(&str, fmt, ap); \ + result = jump_table.SDL_vasprintf(&str, fmt, ap); \ va_end(ap); \ } \ if (result >= 0) { \ @@ -107,7 +109,7 @@ static void SDL_InitDynamicAPI(void); if (str != buf) { \ jump_table.SDL_free(str); \ } \ - return false; \ + return false; \ } \ _static int SDLCALL SDL_sscanf##name(const char *buf, SDL_SCANF_FORMAT_STRING const char *fmt, ...) \ { \ @@ -149,7 +151,7 @@ static void SDL_InitDynamicAPI(void); va_end(ap); \ return result; \ } \ - _static size_t SDLCALL SDL_IOprintf##name(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \ + _static size_t SDLCALL SDL_IOprintf##name(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \ { \ size_t result; \ va_list ap; \ @@ -199,7 +201,7 @@ static void SDL_InitDynamicAPI(void); jump_table.SDL_LogMessageV(category, priority, fmt, ap); \ va_end(ap); \ } \ - SDL_DYNAPI_VARARGS_LOGFN(_static, name, initcall, Trace, TRACE) \ + SDL_DYNAPI_VARARGS_LOGFN(_static, name, initcall, Trace, TRACE) \ SDL_DYNAPI_VARARGS_LOGFN(_static, name, initcall, Verbose, VERBOSE) \ SDL_DYNAPI_VARARGS_LOGFN(_static, name, initcall, Debug, DEBUG) \ SDL_DYNAPI_VARARGS_LOGFN(_static, name, initcall, Info, INFO) \ @@ -500,9 +502,6 @@ static void dynapi_warn(const char *msg) extern "C" { #endif extern SDL_NORETURN void SDL_ExitProcess(int exitcode); -#ifdef __WATCOMC__ -#pragma aux SDL_ExitProcess aborts; -#endif #ifdef __cplusplus } #endif @@ -516,7 +515,7 @@ static void SDL_InitDynamicAPILocked(void) const DWORD rc = GetEnvironmentVariableA(SDL_DYNAMIC_API_ENVVAR, envbuf, (DWORD) sizeof (envbuf)); char *libname = ((rc != 0) && (rc < sizeof (envbuf))) ? envbuf : NULL; #else - char *libname = getenv(SDL_DYNAMIC_API_ENVVAR); + char *libname = getenv(SDL_DYNAMIC_API_ENVVAR); // This should NOT be SDL_getenv() #endif SDL_DYNAPI_ENTRYFN entry = NULL; // funcs from here by default. diff --git a/libs/SDL3/src/dynapi/SDL_dynapi.h b/libs/SDL3/src/dynapi/SDL_dynapi.h index 99ef9a9..786d86e 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi.h +++ b/libs/SDL3/src/dynapi/SDL_dynapi.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -57,12 +57,14 @@ #define SDL_DYNAMIC_API 0 #elif defined(SDL_PLATFORM_RISCOS) // probably not useful on RISC OS, since dlopen() can't be used when using static linking. #define SDL_DYNAMIC_API 0 -#elif defined(__clang_analyzer__) || defined(__INTELLISENSE__) || defined(SDL_THREAD_SAFETY_ANALYSIS) +#elif defined(__clang_analyzer__) || defined(__INTELLISENSE__) || defined(SDL_THREAD_SAFETY_ANALYSIS) || defined(__RESHARPER__) #define SDL_DYNAMIC_API 0 // Turn off for static analysis, so reports are more clear. #elif defined(SDL_PLATFORM_VITA) #define SDL_DYNAMIC_API 0 // vitasdk doesn't support dynamic linking #elif defined(SDL_PLATFORM_3DS) #define SDL_DYNAMIC_API 0 // devkitARM doesn't support dynamic linking +#elif defined(SDL_PLATFORM_NGAGE) +#define SDL_DYNAMIC_API 0 #elif defined(DYNAPI_NEEDS_DLOPEN) && !defined(HAVE_DLOPEN) #define SDL_DYNAMIC_API 0 // we need dlopen(), but don't have it.... #endif diff --git a/libs/SDL3/src/dynapi/SDL_dynapi.sym b/libs/SDL3/src/dynapi/SDL_dynapi.sym index 73f3484..ec2e97b 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi.sym +++ b/libs/SDL3/src/dynapi/SDL_dynapi.sym @@ -1234,6 +1234,43 @@ SDL3_0.0.0 { SDL_ClickTrayEntry; SDL_UpdateTrays; SDL_StretchSurface; + SDL_SetRelativeMouseTransform; + SDL_RenderTexture9GridTiled; + SDL_SetDefaultTextureScaleMode; + SDL_GetDefaultTextureScaleMode; + SDL_CreateGPURenderState; + SDL_SetGPURenderStateFragmentUniforms; + SDL_SetGPURenderState; + SDL_DestroyGPURenderState; + SDL_SetWindowProgressState; + SDL_SetWindowProgressValue; + SDL_GetWindowProgressState; + SDL_GetWindowProgressValue; + SDL_SetRenderTextureAddressMode; + SDL_GetRenderTextureAddressMode; + SDL_GetGPUDeviceProperties; + SDL_CreateGPURenderer; + SDL_PutAudioStreamPlanarData; + SDL_GetEventDescription; + SDL_PutAudioStreamDataNoCopy; + SDL_AddAtomicU32; + SDL_hid_get_properties; + SDL_GetPixelFormatFromGPUTextureFormat; + SDL_GetGPUTextureFormatFromPixelFormat; + SDL_SetTexturePalette; + SDL_GetTexturePalette; + SDL_GetGPURendererDevice; + SDL_LoadPNG_IO; + SDL_LoadPNG; + SDL_SavePNG_IO; + SDL_SavePNG; + SDL_GetSystemPageSize; + SDL_GetPenDeviceType; + SDL_CreateAnimatedCursor; + SDL_RotateSurface; + SDL_LoadSurface_IO; + SDL_LoadSurface; + SDL_SetWindowFillDocument; # extra symbols go here (don't modify this line) local: *; }; diff --git a/libs/SDL3/src/dynapi/SDL_dynapi_overrides.h b/libs/SDL3/src/dynapi/SDL_dynapi_overrides.h index 77fd553..bf8d642 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi_overrides.h +++ b/libs/SDL3/src/dynapi/SDL_dynapi_overrides.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -1259,3 +1259,41 @@ #define SDL_ClickTrayEntry SDL_ClickTrayEntry_REAL #define SDL_UpdateTrays SDL_UpdateTrays_REAL #define SDL_StretchSurface SDL_StretchSurface_REAL +#define SDL_SetRelativeMouseTransform SDL_SetRelativeMouseTransform_REAL +#define SDL_RenderTexture9GridTiled SDL_RenderTexture9GridTiled_REAL +#define SDL_SetDefaultTextureScaleMode SDL_SetDefaultTextureScaleMode_REAL +#define SDL_GetDefaultTextureScaleMode SDL_GetDefaultTextureScaleMode_REAL +#define SDL_CreateGPURenderState SDL_CreateGPURenderState_REAL +#define SDL_SetGPURenderStateFragmentUniforms SDL_SetGPURenderStateFragmentUniforms_REAL +#define SDL_SetGPURenderState SDL_SetGPURenderState_REAL +#define SDL_DestroyGPURenderState SDL_DestroyGPURenderState_REAL +#define SDL_SetWindowProgressState SDL_SetWindowProgressState_REAL +#define SDL_SetWindowProgressValue SDL_SetWindowProgressValue_REAL +#define SDL_GetWindowProgressState SDL_GetWindowProgressState_REAL +#define SDL_GetWindowProgressValue SDL_GetWindowProgressValue_REAL +#define SDL_SetRenderTextureAddressMode SDL_SetRenderTextureAddressMode_REAL +#define SDL_GetRenderTextureAddressMode SDL_GetRenderTextureAddressMode_REAL +#define SDL_GetGPUDeviceProperties SDL_GetGPUDeviceProperties_REAL +#define SDL_CreateGPURenderer SDL_CreateGPURenderer_REAL +#define SDL_PutAudioStreamPlanarData SDL_PutAudioStreamPlanarData_REAL +#define SDL_GetEventDescription SDL_GetEventDescription_REAL +#define SDL_PutAudioStreamDataNoCopy SDL_PutAudioStreamDataNoCopy_REAL +#define SDL_AddAtomicU32 SDL_AddAtomicU32_REAL +#define SDL_hid_get_properties SDL_hid_get_properties_REAL +#define SDL_GetPixelFormatFromGPUTextureFormat SDL_GetPixelFormatFromGPUTextureFormat_REAL +#define SDL_GetGPUTextureFormatFromPixelFormat SDL_GetGPUTextureFormatFromPixelFormat_REAL +#define JNI_OnLoad JNI_OnLoad_REAL +#define SDL_SetTexturePalette SDL_SetTexturePalette_REAL +#define SDL_GetTexturePalette SDL_GetTexturePalette_REAL +#define SDL_GetGPURendererDevice SDL_GetGPURendererDevice_REAL +#define SDL_LoadPNG_IO SDL_LoadPNG_IO_REAL +#define SDL_LoadPNG SDL_LoadPNG_REAL +#define SDL_SavePNG_IO SDL_SavePNG_IO_REAL +#define SDL_SavePNG SDL_SavePNG_REAL +#define SDL_GetSystemPageSize SDL_GetSystemPageSize_REAL +#define SDL_GetPenDeviceType SDL_GetPenDeviceType_REAL +#define SDL_CreateAnimatedCursor SDL_CreateAnimatedCursor_REAL +#define SDL_RotateSurface SDL_RotateSurface_REAL +#define SDL_LoadSurface_IO SDL_LoadSurface_IO_REAL +#define SDL_LoadSurface SDL_LoadSurface_REAL +#define SDL_SetWindowFillDocument SDL_SetWindowFillDocument_REAL diff --git a/libs/SDL3/src/dynapi/SDL_dynapi_procs.h b/libs/SDL3/src/dynapi/SDL_dynapi_procs.h index e86ac2a..9f28b68 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi_procs.h +++ b/libs/SDL3/src/dynapi/SDL_dynapi_procs.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -28,7 +28,7 @@ Also, this file gets included multiple times, don't add #pragma once, etc. */ -// direct jump magic can use these, the rest needs special code. +/* direct jump magic can use these, the rest needs special code. */ #ifndef SDL_DYNAPI_PROC_NO_VARARGS SDL_DYNAPI_PROC(size_t,SDL_IOprintf,(SDL_IOStream *a, SDL_PRINTF_FORMAT_STRING const char *b, ...),(a,b),return) SDL_DYNAPI_PROC(void,SDL_Log,(SDL_PRINTF_FORMAT_STRING const char *a, ...),(a),) @@ -47,7 +47,7 @@ SDL_DYNAPI_PROC(int,SDL_sscanf,(const char *a, SDL_SCANF_FORMAT_STRING const cha SDL_DYNAPI_PROC(int,SDL_swprintf,(SDL_OUT_Z_CAP(b) wchar_t *a, size_t b, SDL_PRINTF_FORMAT_STRING const wchar_t *c, ...),(a,b,c),return) #endif -// New API symbols are added at the end +/* New API symbols are added at the end */ SDL_DYNAPI_PROC(SDL_Surface*,SDL_AcquireCameraFrame,(SDL_Camera *a, Uint64 *b),(a,b),return) SDL_DYNAPI_PROC(SDL_GPUCommandBuffer*,SDL_AcquireGPUCommandBuffer,(SDL_GPUDevice *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_AcquireGPUSwapchainTexture,(SDL_GPUCommandBuffer *a, SDL_Window *b, SDL_GPUTexture **c, Uint32 *d, Uint32 *e),(a,b,c,d,e),return) @@ -137,7 +137,7 @@ SDL_DYNAPI_PROC(SDL_GPUSampler*,SDL_CreateGPUSampler,(SDL_GPUDevice *a, const SD SDL_DYNAPI_PROC(SDL_GPUShader*,SDL_CreateGPUShader,(SDL_GPUDevice *a, const SDL_GPUShaderCreateInfo *b),(a,b),return) SDL_DYNAPI_PROC(SDL_GPUTexture*,SDL_CreateGPUTexture,(SDL_GPUDevice *a, const SDL_GPUTextureCreateInfo *b),(a,b),return) SDL_DYNAPI_PROC(SDL_GPUTransferBuffer*,SDL_CreateGPUTransferBuffer,(SDL_GPUDevice *a, const SDL_GPUTransferBufferCreateInfo *b),(a,b),return) -SDL_DYNAPI_PROC(int,SDL_CreateHapticEffect,(SDL_Haptic *a, const SDL_HapticEffect *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_HapticEffectID,SDL_CreateHapticEffect,(SDL_Haptic *a, const SDL_HapticEffect *b),(a,b),return) SDL_DYNAPI_PROC(SDL_Mutex*,SDL_CreateMutex,(void),(),return) SDL_DYNAPI_PROC(SDL_Palette*,SDL_CreatePalette,(int a),(a),return) SDL_DYNAPI_PROC(SDL_Window*,SDL_CreatePopupWindow,(SDL_Window *a, int b, int c, int d, int e, SDL_WindowFlags f),(a,b,c,d,e,f),return) @@ -171,7 +171,7 @@ SDL_DYNAPI_PROC(void,SDL_DestroyCondition,(SDL_Condition *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyCursor,(SDL_Cursor *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyEnvironment,(SDL_Environment *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyGPUDevice,(SDL_GPUDevice *a),(a),) -SDL_DYNAPI_PROC(void,SDL_DestroyHapticEffect,(SDL_Haptic *a, int b),(a,b),) +SDL_DYNAPI_PROC(void,SDL_DestroyHapticEffect,(SDL_Haptic *a, SDL_HapticEffectID b),(a,b),) SDL_DYNAPI_PROC(void,SDL_DestroyMutex,(SDL_Mutex *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyPalette,(SDL_Palette *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyProcess,(SDL_Process *a),(a),) @@ -288,7 +288,7 @@ SDL_DYNAPI_PROC(const char*,SDL_GetCameraDriver,(int a),(a),return) SDL_DYNAPI_PROC(bool,SDL_GetCameraFormat,(SDL_Camera *a, SDL_CameraSpec *b),(a,b),return) SDL_DYNAPI_PROC(SDL_CameraID,SDL_GetCameraID,(SDL_Camera *a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_GetCameraName,(SDL_CameraID a),(a),return) -SDL_DYNAPI_PROC(int,SDL_GetCameraPermissionState,(SDL_Camera *a),(a),return) +SDL_DYNAPI_PROC(SDL_CameraPermissionState,SDL_GetCameraPermissionState,(SDL_Camera *a),(a),return) SDL_DYNAPI_PROC(SDL_CameraPosition,SDL_GetCameraPosition,(SDL_CameraID a),(a),return) SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetCameraProperties,(SDL_Camera *a),(a),return) SDL_DYNAPI_PROC(SDL_CameraSpec**,SDL_GetCameraSupportedFormats,(SDL_CameraID a, int *b),(a,b),return) @@ -386,7 +386,7 @@ SDL_DYNAPI_PROC(SDL_JoystickID*,SDL_GetGamepads,(int *a),(a),return) SDL_DYNAPI_PROC(SDL_MouseButtonFlags,SDL_GetGlobalMouseState,(float *a, float *b),(a,b),return) SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGlobalProperties,(void),(),return) SDL_DYNAPI_PROC(SDL_Window*,SDL_GetGrabbedWindow,(void),(),return) -SDL_DYNAPI_PROC(bool,SDL_GetHapticEffectStatus,(SDL_Haptic *a, int b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_GetHapticEffectStatus,(SDL_Haptic *a, SDL_HapticEffectID b),(a,b),return) SDL_DYNAPI_PROC(Uint32,SDL_GetHapticFeatures,(SDL_Haptic *a),(a),return) SDL_DYNAPI_PROC(SDL_Haptic*,SDL_GetHapticFromID,(SDL_HapticID a),(a),return) SDL_DYNAPI_PROC(SDL_HapticID,SDL_GetHapticID,(SDL_Haptic *a),(a),return) @@ -802,7 +802,7 @@ SDL_DYNAPI_PROC(bool,SDL_RumbleGamepadTriggers,(SDL_Gamepad *a, Uint16 b, Uint16 SDL_DYNAPI_PROC(bool,SDL_RumbleJoystick,(SDL_Joystick *a, Uint16 b, Uint16 c, Uint32 d),(a,b,c,d),return) SDL_DYNAPI_PROC(bool,SDL_RumbleJoystickTriggers,(SDL_Joystick *a, Uint16 b, Uint16 c, Uint32 d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_RunApp,(int a, char *b[], SDL_main_func c, void *d),(a,b,c,d),return) -SDL_DYNAPI_PROC(bool,SDL_RunHapticEffect,(SDL_Haptic *a, int b, Uint32 c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_RunHapticEffect,(SDL_Haptic *a, SDL_HapticEffectID b, Uint32 c),(a,b,c),return) SDL_DYNAPI_PROC(bool,SDL_SaveBMP,(SDL_Surface *a, const char *b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_SaveBMP_IO,(SDL_Surface *a, SDL_IOStream *b, bool c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_ScaleSurface,(SDL_Surface *a, int b, int c, SDL_ScaleMode d),(a,b,c,d),return) @@ -948,7 +948,7 @@ SDL_DYNAPI_PROC(void,SDL_SignalSemaphore,(SDL_Semaphore *a),(a),) SDL_DYNAPI_PROC(bool,SDL_StartTextInput,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_StartTextInputWithProperties,(SDL_Window *a, SDL_PropertiesID b),(a,b),return) SDL_DYNAPI_PROC(Uint32,SDL_StepUTF8,(const char **a, size_t *b),(a,b),return) -SDL_DYNAPI_PROC(bool,SDL_StopHapticEffect,(SDL_Haptic *a, int b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_StopHapticEffect,(SDL_Haptic *a, SDL_HapticEffectID b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_StopHapticEffects,(SDL_Haptic *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_StopHapticRumble,(SDL_Haptic *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_StopTextInput,(SDL_Window *a),(a),return) @@ -986,7 +986,7 @@ SDL_DYNAPI_PROC(void,SDL_UnmapGPUTransferBuffer,(SDL_GPUDevice *a, SDL_GPUTransf SDL_DYNAPI_PROC(void,SDL_UnregisterApp,(void),(),) SDL_DYNAPI_PROC(bool,SDL_UnsetEnvironmentVariable,(SDL_Environment *a, const char *b),(a,b),return) SDL_DYNAPI_PROC(void,SDL_UpdateGamepads,(void),(),) -SDL_DYNAPI_PROC(bool,SDL_UpdateHapticEffect,(SDL_Haptic *a, int b, const SDL_HapticEffect *c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_UpdateHapticEffect,(SDL_Haptic *a, SDL_HapticEffectID b, const SDL_HapticEffect *c),(a,b,c),return) SDL_DYNAPI_PROC(void,SDL_UpdateJoysticks,(void),(),) SDL_DYNAPI_PROC(bool,SDL_UpdateNVTexture,(SDL_Texture *a, const SDL_Rect *b, const Uint8 *c, int d, const Uint8 *e, int f),(a,b,c,d,e,f),return) SDL_DYNAPI_PROC(void,SDL_UpdateSensors,(void),(),) @@ -1267,3 +1267,41 @@ SDL_DYNAPI_PROC(bool,SDL_AudioStreamDevicePaused,(SDL_AudioStream *a),(a),return SDL_DYNAPI_PROC(void,SDL_ClickTrayEntry,(SDL_TrayEntry *a),(a),) SDL_DYNAPI_PROC(void,SDL_UpdateTrays,(void),(),) SDL_DYNAPI_PROC(bool,SDL_StretchSurface,(SDL_Surface *a,const SDL_Rect *b,SDL_Surface *c,const SDL_Rect *d,SDL_ScaleMode e),(a,b,c,d,e),return) +SDL_DYNAPI_PROC(bool,SDL_SetRelativeMouseTransform,(SDL_MouseMotionTransformCallback a,void *b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_RenderTexture9GridTiled,(SDL_Renderer *a,SDL_Texture *b,const SDL_FRect *c,float d,float e,float f,float g,float h,const SDL_FRect *i,float j),(a,b,c,d,e,f,g,h,i,j),return) +SDL_DYNAPI_PROC(bool,SDL_SetDefaultTextureScaleMode,(SDL_Renderer *a,SDL_ScaleMode b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_GetDefaultTextureScaleMode,(SDL_Renderer *a,SDL_ScaleMode *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_GPURenderState*,SDL_CreateGPURenderState,(SDL_Renderer *a,const SDL_GPURenderStateCreateInfo *b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_SetGPURenderStateFragmentUniforms,(SDL_GPURenderState *a,Uint32 b,const void *c,Uint32 d),(a,b,c,d),return) +SDL_DYNAPI_PROC(bool,SDL_SetGPURenderState,(SDL_Renderer *a,SDL_GPURenderState *b),(a,b),return) +SDL_DYNAPI_PROC(void,SDL_DestroyGPURenderState,(SDL_GPURenderState *a),(a),) +SDL_DYNAPI_PROC(bool,SDL_SetWindowProgressState,(SDL_Window *a,SDL_ProgressState b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_SetWindowProgressValue,(SDL_Window *a,float b),(a,b),return) +SDL_DYNAPI_PROC(SDL_ProgressState,SDL_GetWindowProgressState,(SDL_Window *a),(a),return) +SDL_DYNAPI_PROC(float,SDL_GetWindowProgressValue,(SDL_Window *a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_SetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode b,SDL_TextureAddressMode c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_GetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode *b,SDL_TextureAddressMode *c),(a,b,c),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGPUDeviceProperties,(SDL_GPUDevice *a),(a),return) +SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateGPURenderer,(SDL_GPUDevice *a,SDL_Window *b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamPlanarData,(SDL_AudioStream *a,const void * const*b,int c,int d),(a,b,c,d),return) +SDL_DYNAPI_PROC(int,SDL_GetEventDescription,(const SDL_Event *a,char *b,int c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamDataNoCopy,(SDL_AudioStream *a,const void *b,int c,SDL_AudioStreamDataCompleteCallback d,void *e),(a,b,c,d,e),return) +SDL_DYNAPI_PROC(Uint32,SDL_AddAtomicU32,(SDL_AtomicU32 *a,int b),(a,b),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_hid_get_properties,(SDL_hid_device *a),(a),return) +SDL_DYNAPI_PROC(SDL_PixelFormat,SDL_GetPixelFormatFromGPUTextureFormat,(SDL_GPUTextureFormat a),(a),return) +SDL_DYNAPI_PROC(SDL_GPUTextureFormat,SDL_GetGPUTextureFormatFromPixelFormat,(SDL_PixelFormat a),(a),return) +SDL_DYNAPI_PROC(Sint32,JNI_OnLoad,(JavaVM *a, void *b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_SetTexturePalette,(SDL_Texture *a,SDL_Palette *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Palette*,SDL_GetTexturePalette,(SDL_Texture *a),(a),return) +SDL_DYNAPI_PROC(SDL_GPUDevice*,SDL_GetGPURendererDevice,(SDL_Renderer *a),(a),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadPNG_IO,(SDL_IOStream *a,bool b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadPNG,(const char *a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_SavePNG_IO,(SDL_Surface *a,SDL_IOStream *b,bool c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_SavePNG,(SDL_Surface *a,const char *b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_GetSystemPageSize,(void),(),return) +SDL_DYNAPI_PROC(SDL_PenDeviceType,SDL_GetPenDeviceType,(SDL_PenID a),(a),return) +SDL_DYNAPI_PROC(SDL_Cursor*,SDL_CreateAnimatedCursor,(SDL_CursorFrameInfo *a,int b,int c,int d),(a,b,c,d),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_RotateSurface,(SDL_Surface *a,float b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadSurface_IO,(SDL_IOStream *a,bool b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadSurface,(const char *a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_SetWindowFillDocument,(SDL_Window *a,bool b),(a,b),return) diff --git a/libs/SDL3/src/dynapi/SDL_dynapi_unsupported.h b/libs/SDL3/src/dynapi/SDL_dynapi_unsupported.h index 143943a..46a307e 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi_unsupported.h +++ b/libs/SDL3/src/dynapi/SDL_dynapi_unsupported.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -33,20 +33,4 @@ typedef struct ID3D11Device ID3D11Device; typedef struct IDirect3DDevice9 IDirect3DDevice9; #endif -#ifndef SDL_PLATFORM_GDK -typedef struct XTaskQueueHandle XTaskQueueHandle; -#endif - -#ifndef SDL_PLATFORM_GDK -typedef struct XUserHandle XUserHandle; -#endif - -#ifndef SDL_PLATFORM_ANDROID -typedef void *SDL_RequestAndroidPermissionCallback; -#endif - -#ifndef SDL_PLATFORM_IOS -typedef void *SDL_iOSAnimationCallback; -#endif - #endif diff --git a/libs/SDL3/src/dynapi/gendynapi.py b/libs/SDL3/src/dynapi/gendynapi.py index 0915523..4071b75 100644 --- a/libs/SDL3/src/dynapi/gendynapi.py +++ b/libs/SDL3/src/dynapi/gendynapi.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # Simple DirectMedia Layer -# Copyright (C) 1997-2025 Sam Lantinga +# Copyright (C) 1997-2026 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 @@ -451,7 +451,7 @@ def get_header_list() -> list[Path]: return ret -# Write the new API in files: _procs.h _overrivides.h and .sym +# Write the new API in files: _procs.h _overrides.h and .sym def add_dyn_api(proc: SdlProcedure) -> None: decl_args: list[str] = [] call_args = [] diff --git a/libs/SDL3/src/events/SDL_categories.c b/libs/SDL3/src/events/SDL_categories.c index cb41f6f..fa12077 100644 --- a/libs/SDL3/src/events/SDL_categories.c +++ b/libs/SDL3/src/events/SDL_categories.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_categories_c.h b/libs/SDL3/src/events/SDL_categories_c.h index 31d926a..a1259e0 100644 --- a/libs/SDL3/src/events/SDL_categories_c.h +++ b/libs/SDL3/src/events/SDL_categories_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_clipboardevents.c b/libs/SDL3/src/events/SDL_clipboardevents.c index d5cf8ad..eb0e892 100644 --- a/libs/SDL3/src/events/SDL_clipboardevents.c +++ b/libs/SDL3/src/events/SDL_clipboardevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -29,17 +29,7 @@ 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. - * - * 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_CancelClipboardData(0); SDL_SaveClipboardMimeTypes((const char **)mime_types, num_mime_types); } diff --git a/libs/SDL3/src/events/SDL_clipboardevents_c.h b/libs/SDL3/src/events/SDL_clipboardevents_c.h index 0b0f628..03db8d6 100644 --- a/libs/SDL3/src/events/SDL_clipboardevents_c.h +++ b/libs/SDL3/src/events/SDL_clipboardevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_displayevents.c b/libs/SDL3/src/events/SDL_displayevents.c index e41f75c..08e5724 100644 --- a/libs/SDL3/src/events/SDL_displayevents.c +++ b/libs/SDL3/src/events/SDL_displayevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -26,6 +26,9 @@ void SDL_SendDisplayEvent(SDL_VideoDisplay *display, SDL_EventType displayevent, int data1, int data2) { + SDL_VideoDevice *_this; + bool post_event = true; + if (!display || display->id == 0) { return; } @@ -40,8 +43,14 @@ void SDL_SendDisplayEvent(SDL_VideoDisplay *display, SDL_EventType displayevent, break; } + // Only post if we are not currently quitting + _this = SDL_GetVideoDevice(); + if (_this == NULL || _this->is_quitting) { + post_event = false; + } + // Post the event, if desired - if (SDL_EventEnabled(displayevent)) { + if (post_event && SDL_EventEnabled(displayevent)) { SDL_Event event; event.type = displayevent; event.common.timestamp = 0; diff --git a/libs/SDL3/src/events/SDL_displayevents_c.h b/libs/SDL3/src/events/SDL_displayevents_c.h index ae0730c..9efffe0 100644 --- a/libs/SDL3/src/events/SDL_displayevents_c.h +++ b/libs/SDL3/src/events/SDL_displayevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_dropevents.c b/libs/SDL3/src/events/SDL_dropevents.c index 661f4f3..9a93f9c 100644 --- a/libs/SDL3/src/events/SDL_dropevents.c +++ b/libs/SDL3/src/events/SDL_dropevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_dropevents_c.h b/libs/SDL3/src/events/SDL_dropevents_c.h index efce0ac..dc987db 100644 --- a/libs/SDL3/src/events/SDL_dropevents_c.h +++ b/libs/SDL3/src/events/SDL_dropevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_events.c b/libs/SDL3/src/events/SDL_events.c index a151740..a8ac576 100644 --- a/libs/SDL3/src/events/SDL_events.c +++ b/libs/SDL3/src/events/SDL_events.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -29,12 +29,16 @@ #include "../audio/SDL_audio_c.h" #include "../camera/SDL_camera_c.h" #include "../timer/SDL_timer_c.h" +#include "../core/linux/SDL_udev.h" #ifndef SDL_JOYSTICK_DISABLED #include "../joystick/SDL_joystick_c.h" #endif #ifndef SDL_SENSOR_DISABLED #include "../sensor/SDL_sensor_c.h" #endif +#ifdef HAVE_DBUS_DBUS_H +#include "core/linux/SDL_dbus.h" +#endif #include "../video/SDL_sysvideo.h" #ifdef SDL_PLATFORM_ANDROID @@ -42,6 +46,10 @@ #include "../video/android/SDL_androidevents.h" #endif +#ifdef SDL_PLATFORM_UNIX +#include "../tray/SDL_tray_utils.h" +#endif + // An arbitrary limit so we don't have unbounded growth #define SDL_MAX_QUEUED_EVENTS 65535 @@ -51,6 +59,12 @@ // Determines how often to pump events if joysticks or sensors are actively being read #define EVENT_POLL_INTERVAL_NS SDL_MS_TO_NS(1) +// Determines how often to pump events if tray items are active +#define TRAY_POLL_INTERVAL_NS SDL_MS_TO_NS(50) + +// Determines how often to pump events if DBus is active +#define DBUS_POLL_INTERVAL_NS (3 * SDL_NS_PER_SECOND) + // 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)); @@ -430,26 +444,18 @@ static void SDLCALL SDL_EventLoggingChanged(void *userdata, const char *name, co SDL_EventLoggingVerbosity = (hint && *hint) ? SDL_clamp(SDL_atoi(hint), 0, 3) : 0; } -static void SDL_LogEvent(const SDL_Event *event) +int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) { + if (!event) { + return SDL_snprintf(buf, buflen, "(null)"); + } + static const char *pen_axisnames[] = { "PRESSURE", "XTILT", "YTILT", "DISTANCE", "ROTATION", "SLIDER", "TANGENTIAL_PRESSURE" }; SDL_COMPILE_TIME_ASSERT(pen_axisnames_array_matches, SDL_arraysize(pen_axisnames) == SDL_PEN_AXIS_COUNT); char name[64]; char details[128]; - // sensor/mouse/pen/finger motion are spammy, ignore these if they aren't demanded. - if ((SDL_EventLoggingVerbosity < 2) && - ((event->type == SDL_EVENT_MOUSE_MOTION) || - (event->type == SDL_EVENT_FINGER_MOTION) || - (event->type == SDL_EVENT_PEN_AXIS) || - (event->type == SDL_EVENT_PEN_MOTION) || - (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION) || - (event->type == SDL_EVENT_GAMEPAD_SENSOR_UPDATE) || - (event->type == SDL_EVENT_SENSOR_UPDATE))) { - return; - } - // this is to make (void)SDL_snprintf() calls cleaner. #define uint unsigned int @@ -466,8 +472,8 @@ static void SDL_LogEvent(const SDL_Event *event) } else { plusstr[0] = '\0'; } - (void)SDL_snprintf(details, sizeof(details), "%s (timestamp=%u windowid=%u code=%d data1=%p data2=%p)", - plusstr, (uint)event->user.timestamp, (uint)event->user.windowID, + (void)SDL_snprintf(details, sizeof(details), "%s (timestamp=%" SDL_PRIu64 " windowid=%u code=%d data1=%p data2=%p)", + plusstr, event->user.timestamp, (uint)event->user.windowID, (int)event->user.code, event->user.data1, event->user.data2); } @@ -479,7 +485,7 @@ static void SDL_LogEvent(const SDL_Event *event) SDL_strlcpy(details, " (THIS IS PROBABLY A BUG!)", sizeof(details)); break; SDL_EVENT_CASE(SDL_EVENT_QUIT) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u)", (uint)event->quit.timestamp); + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 ")", event->quit.timestamp); break; SDL_EVENT_CASE(SDL_EVENT_TERMINATING) break; @@ -505,8 +511,8 @@ static void SDL_LogEvent(const SDL_Event *event) #define SDL_RENDEREVENT_CASE(x) \ case x: \ SDL_strlcpy(name, #x, sizeof(name)); \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u event=%s windowid=%u)", \ - (uint)event->display.timestamp, name, (uint)event->render.windowID); \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " event=%s windowid=%u)", \ + event->display.timestamp, name, (uint)event->render.windowID); \ break SDL_RENDEREVENT_CASE(SDL_EVENT_RENDER_TARGETS_RESET); SDL_RENDEREVENT_CASE(SDL_EVENT_RENDER_DEVICE_RESET); @@ -515,8 +521,8 @@ static void SDL_LogEvent(const SDL_Event *event) #define SDL_DISPLAYEVENT_CASE(x) \ case x: \ SDL_strlcpy(name, #x, sizeof(name)); \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u display=%u event=%s data1=%d, data2=%d)", \ - (uint)event->display.timestamp, (uint)event->display.displayID, name, (int)event->display.data1, (int)event->display.data2); \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " display=%u event=%s data1=%d, data2=%d)", \ + event->display.timestamp, (uint)event->display.displayID, name, (int)event->display.data1, (int)event->display.data2); \ break SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_ORIENTATION); SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_ADDED); @@ -525,13 +531,14 @@ static void SDL_LogEvent(const SDL_Event *event) SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED); SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED); SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED); + SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED); #undef SDL_DISPLAYEVENT_CASE #define SDL_WINDOWEVENT_CASE(x) \ case x: \ SDL_strlcpy(name, #x, sizeof(name)); \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u event=%s data1=%d data2=%d)", \ - (uint)event->window.timestamp, (uint)event->window.windowID, name, (int)event->window.data1, (int)event->window.data2); \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u event=%s data1=%d data2=%d)", \ + event->window.timestamp, (uint)event->window.windowID, name, (int)event->window.data1, (int)event->window.data2); \ break SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_SHOWN); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_HIDDEN); @@ -540,7 +547,6 @@ static void SDL_LogEvent(const SDL_Event *event) SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_RESIZED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_METAL_VIEW_RESIZED); - SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_SAFE_AREA_CHANGED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_MINIMIZED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_MAXIMIZED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_RESTORED); @@ -553,6 +559,7 @@ static void SDL_LogEvent(const SDL_Event *event) SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_ICCPROF_CHANGED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_DISPLAY_CHANGED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED); + SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_SAFE_AREA_CHANGED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_OCCLUDED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_ENTER_FULLSCREEN); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_LEAVE_FULLSCREEN); @@ -560,7 +567,7 @@ static void SDL_LogEvent(const SDL_Event *event) SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_HDR_STATE_CHANGED); #undef SDL_WINDOWEVENT_CASE -#define PRINT_KEYDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%u)", (uint)event->kdevice.timestamp, (uint)event->kdevice.which) +#define PRINT_KEYDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%u)", event->kdevice.timestamp, (uint)event->kdevice.which) SDL_EVENT_CASE(SDL_EVENT_KEYBOARD_ADDED) PRINT_KEYDEV_EVENT(event); break; @@ -570,8 +577,8 @@ static void SDL_LogEvent(const SDL_Event *event) #undef PRINT_KEYDEV_EVENT #define PRINT_KEY_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u state=%s repeat=%s scancode=%u keycode=%u mod=0x%x)", \ - (uint)event->key.timestamp, (uint)event->key.windowID, (uint)event->key.which, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u state=%s repeat=%s scancode=%u keycode=%u mod=0x%x)", \ + event->key.timestamp, (uint)event->key.windowID, (uint)event->key.which, \ event->key.down ? "pressed" : "released", \ event->key.repeat ? "true" : "false", \ (uint)event->key.scancode, \ @@ -586,22 +593,26 @@ static void SDL_LogEvent(const SDL_Event *event) #undef PRINT_KEY_EVENT SDL_EVENT_CASE(SDL_EVENT_TEXT_EDITING) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u text='%s' start=%d length=%d)", - (uint)event->edit.timestamp, (uint)event->edit.windowID, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u text='%s' start=%d length=%d)", + event->edit.timestamp, (uint)event->edit.windowID, event->edit.text, (int)event->edit.start, (int)event->edit.length); break; SDL_EVENT_CASE(SDL_EVENT_TEXT_EDITING_CANDIDATES) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u num_candidates=%d selected_candidate=%d)", - (uint)event->edit_candidates.timestamp, (uint)event->edit_candidates.windowID, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u num_candidates=%d selected_candidate=%d)", + event->edit_candidates.timestamp, (uint)event->edit_candidates.windowID, (int)event->edit_candidates.num_candidates, (int)event->edit_candidates.selected_candidate); break; SDL_EVENT_CASE(SDL_EVENT_TEXT_INPUT) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u text='%s')", (uint)event->text.timestamp, (uint)event->text.windowID, event->text.text); + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u text='%s')", event->text.timestamp, (uint)event->text.windowID, event->text.text); + break; + SDL_EVENT_CASE(SDL_EVENT_SCREEN_KEYBOARD_SHOWN) + break; + SDL_EVENT_CASE(SDL_EVENT_SCREEN_KEYBOARD_HIDDEN) break; -#define PRINT_MOUSEDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%u)", (uint)event->mdevice.timestamp, (uint)event->mdevice.which) +#define PRINT_MOUSEDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%u)", event->mdevice.timestamp, (uint)event->mdevice.which) SDL_EVENT_CASE(SDL_EVENT_MOUSE_ADDED) PRINT_MOUSEDEV_EVENT(event); break; @@ -611,16 +622,16 @@ static void SDL_LogEvent(const SDL_Event *event) #undef PRINT_MOUSEDEV_EVENT SDL_EVENT_CASE(SDL_EVENT_MOUSE_MOTION) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u state=%u x=%g y=%g xrel=%g yrel=%g)", - (uint)event->motion.timestamp, (uint)event->motion.windowID, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u state=%u x=%g y=%g xrel=%g yrel=%g)", + event->motion.timestamp, (uint)event->motion.windowID, (uint)event->motion.which, (uint)event->motion.state, event->motion.x, event->motion.y, event->motion.xrel, event->motion.yrel); break; #define PRINT_MBUTTON_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u button=%u state=%s clicks=%u x=%g y=%g)", \ - (uint)event->button.timestamp, (uint)event->button.windowID, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u button=%u state=%s clicks=%u x=%g y=%g)", \ + event->button.timestamp, (uint)event->button.windowID, \ (uint)event->button.which, (uint)event->button.button, \ event->button.down ? "pressed" : "released", \ (uint)event->button.clicks, event->button.x, event->button.y) @@ -633,34 +644,34 @@ 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 integer_x=%d integer_y=%d direction=%s)", - (uint)event->wheel.timestamp, (uint)event->wheel.windowID, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u x=%g y=%g integer_x=%d integer_y=%d direction=%s)", + 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; SDL_EVENT_CASE(SDL_EVENT_JOYSTICK_AXIS_MOTION) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d axis=%u value=%d)", - (uint)event->jaxis.timestamp, (int)event->jaxis.which, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d axis=%u value=%d)", + event->jaxis.timestamp, (int)event->jaxis.which, (uint)event->jaxis.axis, (int)event->jaxis.value); break; SDL_EVENT_CASE(SDL_EVENT_JOYSTICK_BALL_MOTION) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d ball=%u xrel=%d yrel=%d)", - (uint)event->jball.timestamp, (int)event->jball.which, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d ball=%u xrel=%d yrel=%d)", + event->jball.timestamp, (int)event->jball.which, (uint)event->jball.ball, (int)event->jball.xrel, (int)event->jball.yrel); break; SDL_EVENT_CASE(SDL_EVENT_JOYSTICK_HAT_MOTION) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d hat=%u value=%u)", - (uint)event->jhat.timestamp, (int)event->jhat.which, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d hat=%u value=%u)", + event->jhat.timestamp, (int)event->jhat.which, (uint)event->jhat.hat, (uint)event->jhat.value); break; #define PRINT_JBUTTON_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d button=%u state=%s)", \ - (uint)event->jbutton.timestamp, (int)event->jbutton.which, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d button=%u state=%s)", \ + event->jbutton.timestamp, (int)event->jbutton.which, \ (uint)event->jbutton.button, event->jbutton.down ? "pressed" : "released") SDL_EVENT_CASE(SDL_EVENT_JOYSTICK_BUTTON_DOWN) PRINT_JBUTTON_EVENT(event); @@ -671,12 +682,12 @@ static void SDL_LogEvent(const SDL_Event *event) #undef PRINT_JBUTTON_EVENT SDL_EVENT_CASE(SDL_EVENT_JOYSTICK_BATTERY_UPDATED) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d state=%u percent=%d)", - (uint)event->jbattery.timestamp, (int)event->jbattery.which, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d state=%u percent=%d)", + event->jbattery.timestamp, (int)event->jbattery.which, event->jbattery.state, event->jbattery.percent); break; -#define PRINT_JOYDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d)", (uint)event->jdevice.timestamp, (int)event->jdevice.which) +#define PRINT_JOYDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d)", event->jdevice.timestamp, (int)event->jdevice.which) SDL_EVENT_CASE(SDL_EVENT_JOYSTICK_ADDED) PRINT_JOYDEV_EVENT(event); break; @@ -689,14 +700,14 @@ static void SDL_LogEvent(const SDL_Event *event) #undef PRINT_JOYDEV_EVENT SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_AXIS_MOTION) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d axis=%u value=%d)", - (uint)event->gaxis.timestamp, (int)event->gaxis.which, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d axis=%u value=%d)", + event->gaxis.timestamp, (int)event->gaxis.which, (uint)event->gaxis.axis, (int)event->gaxis.value); break; #define PRINT_CBUTTON_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d button=%u state=%s)", \ - (uint)event->gbutton.timestamp, (int)event->gbutton.which, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d button=%u state=%s)", \ + event->gbutton.timestamp, (int)event->gbutton.which, \ (uint)event->gbutton.button, event->gbutton.down ? "pressed" : "released") SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_BUTTON_DOWN) PRINT_CBUTTON_EVENT(event); @@ -706,7 +717,7 @@ static void SDL_LogEvent(const SDL_Event *event) break; #undef PRINT_CBUTTON_EVENT -#define PRINT_GAMEPADDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d)", (uint)event->gdevice.timestamp, (int)event->gdevice.which) +#define PRINT_GAMEPADDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d)", event->gdevice.timestamp, (int)event->gdevice.which) SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_ADDED) PRINT_GAMEPADDEV_EVENT(event); break; @@ -725,8 +736,8 @@ static void SDL_LogEvent(const SDL_Event *event) #undef PRINT_GAMEPADDEV_EVENT #define PRINT_CTOUCHPAD_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d touchpad=%d finger=%d x=%f y=%f pressure=%f)", \ - (uint)event->gtouchpad.timestamp, (int)event->gtouchpad.which, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d touchpad=%d finger=%d x=%f y=%f pressure=%f)", \ + event->gtouchpad.timestamp, (int)event->gtouchpad.which, \ (int)event->gtouchpad.touchpad, (int)event->gtouchpad.finger, \ event->gtouchpad.x, event->gtouchpad.y, event->gtouchpad.pressure) SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN) @@ -741,14 +752,14 @@ static void SDL_LogEvent(const SDL_Event *event) #undef PRINT_CTOUCHPAD_EVENT SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_SENSOR_UPDATE) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d sensor=%d data[0]=%f data[1]=%f data[2]=%f)", - (uint)event->gsensor.timestamp, (int)event->gsensor.which, (int)event->gsensor.sensor, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d sensor=%d data[0]=%f data[1]=%f data[2]=%f)", + event->gsensor.timestamp, (int)event->gsensor.which, (int)event->gsensor.sensor, event->gsensor.data[0], event->gsensor.data[1], event->gsensor.data[2]); break; #define PRINT_FINGER_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u touchid=%" SDL_PRIu64 " fingerid=%" SDL_PRIu64 " x=%f y=%f dx=%f dy=%f pressure=%f)", \ - (uint)event->tfinger.timestamp, event->tfinger.touchID, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " touchid=%" SDL_PRIu64 " fingerid=%" SDL_PRIu64 " x=%f y=%f dx=%f dy=%f pressure=%f)", \ + event->tfinger.timestamp, event->tfinger.touchID, \ event->tfinger.fingerID, event->tfinger.x, event->tfinger.y, \ event->tfinger.dx, event->tfinger.dy, event->tfinger.pressure) SDL_EVENT_CASE(SDL_EVENT_FINGER_DOWN) @@ -765,9 +776,23 @@ static void SDL_LogEvent(const SDL_Event *event) break; #undef PRINT_FINGER_EVENT +#define PRINT_PINCH_EVENT(event) \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " scale=%f)", \ + event->pinch.timestamp, event->pinch.scale) + SDL_EVENT_CASE(SDL_EVENT_PINCH_BEGIN) + PRINT_PINCH_EVENT(event); + break; + SDL_EVENT_CASE(SDL_EVENT_PINCH_UPDATE) + PRINT_PINCH_EVENT(event); + break; + SDL_EVENT_CASE(SDL_EVENT_PINCH_END) + PRINT_PINCH_EVENT(event); + break; +#undef PRINT_PINCH_EVENT + #define PRINT_PTOUCH_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u pen_state=%u x=%g y=%g eraser=%s state=%s)", \ - (uint)event->ptouch.timestamp, (uint)event->ptouch.windowID, (uint)event->ptouch.which, (uint)event->ptouch.pen_state, event->ptouch.x, event->ptouch.y, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u pen_state=%u x=%g y=%g eraser=%s state=%s)", \ + event->ptouch.timestamp, (uint)event->ptouch.windowID, (uint)event->ptouch.which, (uint)event->ptouch.pen_state, event->ptouch.x, event->ptouch.y, \ event->ptouch.eraser ? "yes" : "no", event->ptouch.down ? "down" : "up"); SDL_EVENT_CASE(SDL_EVENT_PEN_DOWN) PRINT_PTOUCH_EVENT(event); @@ -778,8 +803,8 @@ static void SDL_LogEvent(const SDL_Event *event) #undef PRINT_PTOUCH_EVENT #define PRINT_PPROXIMITY_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u)", \ - (uint)event->pproximity.timestamp, (uint)event->pproximity.windowID, (uint)event->pproximity.which); + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u)", \ + event->pproximity.timestamp, (uint)event->pproximity.windowID, (uint)event->pproximity.which); SDL_EVENT_CASE(SDL_EVENT_PEN_PROXIMITY_IN) PRINT_PPROXIMITY_EVENT(event); break; @@ -789,19 +814,19 @@ static void SDL_LogEvent(const SDL_Event *event) #undef PRINT_PPROXIMITY_EVENT SDL_EVENT_CASE(SDL_EVENT_PEN_AXIS) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u pen_state=%u x=%g y=%g axis=%s value=%g)", - (uint)event->paxis.timestamp, (uint)event->paxis.windowID, (uint)event->paxis.which, (uint)event->paxis.pen_state, event->paxis.x, event->paxis.y, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u pen_state=%u x=%g y=%g axis=%s value=%g)", + event->paxis.timestamp, (uint)event->paxis.windowID, (uint)event->paxis.which, (uint)event->paxis.pen_state, event->paxis.x, event->paxis.y, ((((int) event->paxis.axis) >= 0) && (event->paxis.axis < SDL_arraysize(pen_axisnames))) ? pen_axisnames[event->paxis.axis] : "[UNKNOWN]", event->paxis.value); break; SDL_EVENT_CASE(SDL_EVENT_PEN_MOTION) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u pen_state=%u x=%g y=%g)", - (uint)event->pmotion.timestamp, (uint)event->pmotion.windowID, (uint)event->pmotion.which, (uint)event->pmotion.pen_state, event->pmotion.x, event->pmotion.y); + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u pen_state=%u x=%g y=%g)", + event->pmotion.timestamp, (uint)event->pmotion.windowID, (uint)event->pmotion.which, (uint)event->pmotion.pen_state, event->pmotion.x, event->pmotion.y); break; #define PRINT_PBUTTON_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u pen_state=%u x=%g y=%g button=%u state=%s)", \ - (uint)event->pbutton.timestamp, (uint)event->pbutton.windowID, (uint)event->pbutton.which, (uint)event->pbutton.pen_state, event->pbutton.x, event->pbutton.y, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u pen_state=%u x=%g y=%g button=%u state=%s)", \ + event->pbutton.timestamp, (uint)event->pbutton.windowID, (uint)event->pbutton.which, (uint)event->pbutton.pen_state, event->pbutton.x, event->pbutton.y, \ (uint)event->pbutton.button, event->pbutton.down ? "down" : "up"); SDL_EVENT_CASE(SDL_EVENT_PEN_BUTTON_DOWN) PRINT_PBUTTON_EVENT(event); @@ -811,7 +836,7 @@ static void SDL_LogEvent(const SDL_Event *event) break; #undef PRINT_PBUTTON_EVENT -#define PRINT_DROP_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (data='%s' timestamp=%u windowid=%u x=%f y=%f)", event->drop.data, (uint)event->drop.timestamp, (uint)event->drop.windowID, event->drop.x, event->drop.y) +#define PRINT_DROP_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (data='%s' timestamp=%" SDL_PRIu64 " windowid=%u x=%f y=%f)", event->drop.data, event->drop.timestamp, (uint)event->drop.windowID, event->drop.x, event->drop.y) SDL_EVENT_CASE(SDL_EVENT_DROP_FILE) PRINT_DROP_EVENT(event); break; @@ -829,7 +854,7 @@ static void SDL_LogEvent(const SDL_Event *event) break; #undef PRINT_DROP_EVENT -#define PRINT_AUDIODEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%u recording=%s)", (uint)event->adevice.timestamp, (uint)event->adevice.which, event->adevice.recording ? "true" : "false") +#define PRINT_AUDIODEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%u recording=%s)", event->adevice.timestamp, (uint)event->adevice.which, event->adevice.recording ? "true" : "false") SDL_EVENT_CASE(SDL_EVENT_AUDIO_DEVICE_ADDED) PRINT_AUDIODEV_EVENT(event); break; @@ -841,7 +866,7 @@ static void SDL_LogEvent(const SDL_Event *event) break; #undef PRINT_AUDIODEV_EVENT -#define PRINT_CAMERADEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%u)", (uint)event->cdevice.timestamp, (uint)event->cdevice.which) +#define PRINT_CAMERADEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%u)", event->cdevice.timestamp, (uint)event->cdevice.which) SDL_EVENT_CASE(SDL_EVENT_CAMERA_DEVICE_ADDED) PRINT_CAMERADEV_EVENT(event); break; @@ -857,8 +882,8 @@ static void SDL_LogEvent(const SDL_Event *event) #undef PRINT_CAMERADEV_EVENT SDL_EVENT_CASE(SDL_EVENT_SENSOR_UPDATE) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d data[0]=%f data[1]=%f data[2]=%f data[3]=%f data[4]=%f data[5]=%f)", - (uint)event->sensor.timestamp, (int)event->sensor.which, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d data[0]=%f data[1]=%f data[2]=%f data[3]=%f data[4]=%f data[5]=%f)", + event->sensor.timestamp, (int)event->sensor.which, event->sensor.data[0], event->sensor.data[1], event->sensor.data[2], event->sensor.data[3], event->sensor.data[4], event->sensor.data[5]); break; @@ -880,12 +905,46 @@ static void SDL_LogEvent(const SDL_Event *event) } break; } +#undef uint + int retval = 0; if (name[0]) { - SDL_Log("SDL EVENT: %s%s", name, details); + retval = SDL_snprintf(buf, buflen, "%s%s", name, details); + } else if (buf && (buflen > 0)) { + *buf = '\0'; + } + return retval; +} + +static void SDL_LogEvent(const SDL_Event *event) +{ + if (!event) { + return; } -#undef uint + // sensor/mouse/pen/finger/pinch motion are spammy, ignore these if they aren't demanded. + if ((SDL_EventLoggingVerbosity < 2) && + ((event->type == SDL_EVENT_MOUSE_MOTION) || + (event->type == SDL_EVENT_FINGER_MOTION) || + (event->type == SDL_EVENT_PEN_AXIS) || + (event->type == SDL_EVENT_PEN_MOTION) || + (event->type == SDL_EVENT_PINCH_UPDATE) || + (event->type == SDL_EVENT_GAMEPAD_AXIS_MOTION) || + (event->type == SDL_EVENT_GAMEPAD_SENSOR_UPDATE) || + (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION) || + (event->type == SDL_EVENT_GAMEPAD_UPDATE_COMPLETE) || + (event->type == SDL_EVENT_JOYSTICK_AXIS_MOTION) || + (event->type == SDL_EVENT_JOYSTICK_UPDATE_COMPLETE) || + (event->type == SDL_EVENT_SENSOR_UPDATE))) { + return; + } + + char buf[256]; + const int rc = SDL_GetEventDescription(event, buf, sizeof (buf)); + SDL_assert(rc < sizeof (buf)); // if this overflows, we should make `buf` larger, but this is currently larger than the max SDL_GetEventDescription returns. + if (buf[0]) { + SDL_Log("SDL EVENT: %s", buf); + } } void SDL_StopEventLoop(void) @@ -1106,9 +1165,10 @@ static int SDL_PeepEventsInternal(SDL_Event *events, int numevents, SDL_EventAct return -1; } if (action == SDL_ADDEVENT) { - if (!events) { + CHECK_PARAM(!events) { SDL_UnlockMutex(SDL_EventQ.lock); - return SDL_InvalidParamError("events"); + SDL_InvalidParamError("events"); + return -1; } for (i = 0; i < numevents; ++i) { used += SDL_AddEvent(&events[i]); @@ -1398,6 +1458,10 @@ bool SDL_RunOnMainThread(SDL_MainThreadCallback callback, void *userdata, bool w void SDL_PumpEventMaintenance(void) { +#ifdef SDL_USE_LIBUDEV + SDL_UDEV_Poll(); +#endif + #ifndef SDL_AUDIO_DISABLED SDL_UpdateAudio(); #endif @@ -1420,6 +1484,10 @@ void SDL_PumpEventMaintenance(void) } #endif + SDL_SendPendingPenProximity(); + + SDL_UpdateCursorAnimation(); + SDL_UpdateTrays(); SDL_SendPendingSignalEvents(); // in case we had a signal handler fire, etc. @@ -1428,6 +1496,9 @@ void SDL_PumpEventMaintenance(void) // Run the system dependent event loops static void SDL_PumpEventsInternal(bool push_sentinel) { + // This should only be called on the main thread, check in debug builds + SDL_assert(SDL_IsMainThread()); + // Free any temporary memory from old events SDL_FreeTemporaryMemory(); @@ -1437,6 +1508,11 @@ static void SDL_PumpEventsInternal(bool push_sentinel) // Run any pending main thread callbacks SDL_RunMainThreadCallbacks(); +#ifdef SDL_USE_LIBDBUS + // DBus event processing is independent of the video subsystem + SDL_DBus_PumpEvents(); +#endif + #ifdef SDL_PLATFORM_ANDROID // Android event processing is independent of the video subsystem Android_PumpEvents(0); @@ -1501,6 +1577,17 @@ static Sint64 SDL_events_get_polling_interval(void) } #endif +#ifdef SDL_PLATFORM_UNIX + if (SDL_HasActiveTrays()) { + // Tray events on *nix platforms run separately from window system events, and need periodic polling + poll_intervalNS = SDL_min(poll_intervalNS, TRAY_POLL_INTERVAL_NS); + } +#endif + +#ifdef SDL_USE_LIBDBUS + // Wake periodically to pump DBus events + poll_intervalNS = SDL_min(poll_intervalNS, DBUS_POLL_INTERVAL_NS); +#endif return poll_intervalNS; } @@ -1646,6 +1733,8 @@ bool SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS) #ifdef SDL_PLATFORM_ANDROID for (;;) { + SDL_PumpEventsInternal(true); + if (SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST) > 0) { return true; } diff --git a/libs/SDL3/src/events/SDL_events_c.h b/libs/SDL3/src/events/SDL_events_c.h index e56ac47..377b04e 100644 --- a/libs/SDL3/src/events/SDL_events_c.h +++ b/libs/SDL3/src/events/SDL_events_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_eventwatch.c b/libs/SDL3/src/events/SDL_eventwatch.c index 08e7248..5d0ff35 100644 --- a/libs/SDL3/src/events/SDL_eventwatch.c +++ b/libs/SDL3/src/events/SDL_eventwatch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_eventwatch_c.h b/libs/SDL3/src/events/SDL_eventwatch_c.h index c9aea38..5c382a7 100644 --- a/libs/SDL3/src/events/SDL_eventwatch_c.h +++ b/libs/SDL3/src/events/SDL_eventwatch_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_keyboard.c b/libs/SDL3/src/events/SDL_keyboard.c index 7f1ac7a..0945339 100644 --- a/libs/SDL3/src/events/SDL_keyboard.c +++ b/libs/SDL3/src/events/SDL_keyboard.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -44,12 +44,6 @@ #define KEYCODE_OPTION_LATIN_LETTERS 0x04 #define DEFAULT_KEYCODE_OPTIONS (KEYCODE_OPTION_FRENCH_NUMBERS | KEYCODE_OPTION_LATIN_LETTERS) -typedef struct SDL_KeyboardInstance -{ - SDL_KeyboardID instance_id; - char *name; -} SDL_KeyboardInstance; - typedef struct SDL_Keyboard { // Data common to all keyboards @@ -58,18 +52,16 @@ typedef struct SDL_Keyboard Uint8 keysource[SDL_SCANCODE_COUNT]; bool keystate[SDL_SCANCODE_COUNT]; SDL_Keymap *keymap; - bool french_numbers; - bool latin_letters; - bool thai_keyboard; Uint32 keycode_options; bool autorelease_pending; Uint64 hardware_timestamp; - int next_reserved_scancode; } SDL_Keyboard; static SDL_Keyboard SDL_keyboard; static int SDL_keyboard_count; -static SDL_KeyboardInstance *SDL_keyboards; +static SDL_KeyboardID *SDL_keyboards; +static SDL_HashTable *SDL_keyboard_names; +static bool SDL_keyboard_quitting; static void SDLCALL SDL_KeycodeOptionsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { @@ -98,6 +90,9 @@ bool SDL_InitKeyboard(void) { SDL_AddHintCallback(SDL_HINT_KEYCODE_OPTIONS, SDL_KeycodeOptionsChanged, &SDL_keyboard); + + SDL_keyboard_names = SDL_CreateHashTable(0, true, SDL_HashID, SDL_KeyMatchID, SDL_DestroyHashValue, NULL); + return true; } @@ -115,14 +110,14 @@ bool SDL_IsKeyboard(Uint16 vendor, Uint16 product, int num_keys) static int SDL_GetKeyboardIndex(SDL_KeyboardID keyboardID) { for (int i = 0; i < SDL_keyboard_count; ++i) { - if (keyboardID == SDL_keyboards[i].instance_id) { + if (keyboardID == SDL_keyboards[i]) { return i; } } return -1; } -void SDL_AddKeyboard(SDL_KeyboardID keyboardID, const char *name, bool send_event) +void SDL_AddKeyboard(SDL_KeyboardID keyboardID, const char *name) { int keyboard_index = SDL_GetKeyboardIndex(keyboardID); if (keyboard_index >= 0) { @@ -132,26 +127,27 @@ void SDL_AddKeyboard(SDL_KeyboardID keyboardID, const char *name, bool send_even SDL_assert(keyboardID != 0); - SDL_KeyboardInstance *keyboards = (SDL_KeyboardInstance *)SDL_realloc(SDL_keyboards, (SDL_keyboard_count + 1) * sizeof(*keyboards)); + SDL_KeyboardID *keyboards = (SDL_KeyboardID *)SDL_realloc(SDL_keyboards, (SDL_keyboard_count + 1) * sizeof(*keyboards)); if (!keyboards) { return; } - SDL_KeyboardInstance *instance = &keyboards[SDL_keyboard_count]; - instance->instance_id = keyboardID; - instance->name = SDL_strdup(name ? name : ""); + keyboards[SDL_keyboard_count] = keyboardID; SDL_keyboards = keyboards; ++SDL_keyboard_count; - if (send_event) { - SDL_Event event; - SDL_zero(event); - event.type = SDL_EVENT_KEYBOARD_ADDED; - event.kdevice.which = keyboardID; - SDL_PushEvent(&event); + if (!name) { + name = "Keyboard"; } + SDL_InsertIntoHashTable(SDL_keyboard_names, (const void *)(uintptr_t)keyboardID, SDL_strdup(name), true); + + SDL_Event event; + SDL_zero(event); + event.type = SDL_EVENT_KEYBOARD_ADDED; + event.kdevice.which = keyboardID; + SDL_PushEvent(&event); } -void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID, bool send_event) +void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID) { int keyboard_index = SDL_GetKeyboardIndex(keyboardID); if (keyboard_index < 0) { @@ -159,14 +155,12 @@ void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID, bool send_event) return; } - SDL_free(SDL_keyboards[keyboard_index].name); - if (keyboard_index != SDL_keyboard_count - 1) { SDL_memmove(&SDL_keyboards[keyboard_index], &SDL_keyboards[keyboard_index + 1], (SDL_keyboard_count - keyboard_index - 1) * sizeof(SDL_keyboards[keyboard_index])); } --SDL_keyboard_count; - if (send_event) { + if (!SDL_keyboard_quitting) { SDL_Event event; SDL_zero(event); event.type = SDL_EVENT_KEYBOARD_REMOVED; @@ -185,14 +179,14 @@ SDL_KeyboardID *SDL_GetKeyboards(int *count) int i; SDL_KeyboardID *keyboards; - keyboards = (SDL_JoystickID *)SDL_malloc((SDL_keyboard_count + 1) * sizeof(*keyboards)); + keyboards = (SDL_KeyboardID *)SDL_malloc((SDL_keyboard_count + 1) * sizeof(*keyboards)); if (keyboards) { if (count) { *count = SDL_keyboard_count; } for (i = 0; i < SDL_keyboard_count; ++i) { - keyboards[i] = SDL_keyboards[i].instance_id; + keyboards[i] = SDL_keyboards[i]; } keyboards[i] = 0; } else { @@ -206,12 +200,17 @@ SDL_KeyboardID *SDL_GetKeyboards(int *count) const char *SDL_GetKeyboardNameForID(SDL_KeyboardID instance_id) { - int keyboard_index = SDL_GetKeyboardIndex(instance_id); - if (keyboard_index < 0) { + const char *name = NULL; + if (!SDL_FindInHashTable(SDL_keyboard_names, (const void *)(uintptr_t)instance_id, (const void **)&name)) { SDL_SetError("Keyboard %" SDL_PRIu32 " not found", instance_id); return NULL; } - return SDL_GetPersistentString(SDL_keyboards[keyboard_index].name); + if (!name) { + // SDL_strdup() failed during insert + SDL_OutOfMemory(); + return NULL; + } + return name; } void SDL_ResetKeyboard(void) @@ -229,19 +228,22 @@ void SDL_ResetKeyboard(void) } } -SDL_Keymap *SDL_GetCurrentKeymap(void) +SDL_Keymap *SDL_GetCurrentKeymap(bool ignore_options) { SDL_Keyboard *keyboard = &SDL_keyboard; + SDL_Keymap *keymap = SDL_keyboard.keymap; - if (keyboard->thai_keyboard) { - // Thai keyboards are QWERTY plus Thai characters, use the default QWERTY keymap - return NULL; - } + if (!ignore_options) { + if (keymap && keymap->thai_keyboard) { + // Thai keyboards are QWERTY plus Thai characters, use the default QWERTY keymap + return NULL; + } - if ((keyboard->keycode_options & KEYCODE_OPTION_LATIN_LETTERS) && - !keyboard->latin_letters) { - // We'll use the default QWERTY keymap - return NULL; + if ((keyboard->keycode_options & KEYCODE_OPTION_LATIN_LETTERS) && + keymap && !keymap->latin_letters) { + // We'll use the default QWERTY keymap + return NULL; + } } return keyboard->keymap; @@ -251,35 +253,39 @@ void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event) { SDL_Keyboard *keyboard = &SDL_keyboard; - if (keyboard->keymap) { + if (keyboard->keymap && keyboard->keymap->auto_release) { SDL_DestroyKeymap(keyboard->keymap); } keyboard->keymap = keymap; - // Detect French number row (all symbols) - keyboard->french_numbers = true; - for (int i = SDL_SCANCODE_1; i <= SDL_SCANCODE_0; ++i) { - if (SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE)) || - !SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_SHIFT))) { - keyboard->french_numbers = false; - break; - } - } + if (keymap && !keymap->layout_determined) { + keymap->layout_determined = true; - // Detect non-Latin keymap - keyboard->thai_keyboard = false; - keyboard->latin_letters = false; - for (int i = SDL_SCANCODE_A; i <= SDL_SCANCODE_D; ++i) { - SDL_Keycode key = SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE); - if (key <= 0xFF) { - keyboard->latin_letters = true; - break; + // Detect French number row (all symbols) + keymap->french_numbers = true; + for (int i = SDL_SCANCODE_1; i <= SDL_SCANCODE_0; ++i) { + if (SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE)) || + !SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_SHIFT))) { + keymap->french_numbers = false; + break; + } } - if (key >= 0x0E00 && key <= 0x0E7F) { - keyboard->thai_keyboard = true; - break; + // Detect non-Latin keymap + keymap->thai_keyboard = false; + keymap->latin_letters = false; + for (int i = SDL_SCANCODE_A; i <= SDL_SCANCODE_D; ++i) { + SDL_Keycode key = SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE); + if (key <= 0xFF) { + keymap->latin_letters = true; + break; + } + + if (key >= 0x0E00 && key <= 0x0E7F) { + keymap->thai_keyboard = true; + break; + } } } @@ -291,16 +297,12 @@ void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event) static SDL_Scancode GetNextReservedScancode(void) { SDL_Keyboard *keyboard = &SDL_keyboard; - SDL_Scancode scancode; - if (keyboard->next_reserved_scancode && keyboard->next_reserved_scancode < SDL_SCANCODE_RESERVED + 100) { - scancode = (SDL_Scancode)keyboard->next_reserved_scancode; - } else { - scancode = SDL_SCANCODE_RESERVED; + if (!keyboard->keymap) { + keyboard->keymap = SDL_CreateKeymap(true); } - keyboard->next_reserved_scancode = (int)scancode + 1; - return scancode; + return SDL_GetKeymapNextReservedScancode(keyboard->keymap); } static void SetKeymapEntry(SDL_Scancode scancode, SDL_Keymod modstate, SDL_Keycode keycode) @@ -308,7 +310,7 @@ static void SetKeymapEntry(SDL_Scancode scancode, SDL_Keymod modstate, SDL_Keyco SDL_Keyboard *keyboard = &SDL_keyboard; if (!keyboard->keymap) { - keyboard->keymap = SDL_CreateKeymap(); + keyboard->keymap = SDL_CreateKeymap(true); } SDL_SetKeymapEntry(keyboard->keymap, scancode, modstate, keycode); @@ -323,7 +325,9 @@ SDL_Window *SDL_GetKeyboardFocus(void) bool SDL_SetKeyboardFocus(SDL_Window *window) { +#if !defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_ANDROID) SDL_VideoDevice *video = SDL_GetVideoDevice(); +#endif SDL_Keyboard *keyboard = &SDL_keyboard; SDL_Mouse *mouse = SDL_GetMouse(); @@ -336,7 +340,23 @@ bool SDL_SetKeyboardFocus(SDL_Window *window) if (keyboard->focus && !window) { // We won't get anymore keyboard messages, so reset keyboard state SDL_ResetKeyboard(); + } + // See if the current window has lost focus + if (keyboard->focus && keyboard->focus != window) { + SDL_SendWindowEvent(keyboard->focus, SDL_EVENT_WINDOW_FOCUS_LOST, 0, 0); + +#if !defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_ANDROID) + // Ensures IME compositions are committed + if (SDL_TextInputActive(keyboard->focus)) { + if (video && video->StopTextInput) { + video->StopTextInput(video, keyboard->focus); + } + } +#endif // !SDL_PLATFORM_IOS && !SDL_PLATFORM_ANDROID + } + + if (keyboard->focus && !window) { // Also leave mouse relative mode if (mouse->relative_mode) { SDL_SetRelativeMouseMode(false); @@ -351,28 +371,18 @@ bool SDL_SetKeyboardFocus(SDL_Window *window) } } - // See if the current window has lost focus - if (keyboard->focus && keyboard->focus != window) { - SDL_SendWindowEvent(keyboard->focus, SDL_EVENT_WINDOW_FOCUS_LOST, 0, 0); - - // Ensures IME compositions are committed - if (SDL_TextInputActive(keyboard->focus)) { - if (video && video->StopTextInput) { - video->StopTextInput(video, keyboard->focus); - } - } - } - keyboard->focus = window; if (keyboard->focus) { SDL_SendWindowEvent(keyboard->focus, SDL_EVENT_WINDOW_FOCUS_GAINED, 0, 0); +#if !defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_ANDROID) if (SDL_TextInputActive(keyboard->focus)) { if (video && video->StartTextInput) { video->StartTextInput(video, keyboard->focus, keyboard->focus->text_input_props); } } +#endif // !SDL_PLATFORM_IOS && !SDL_PLATFORM_ANDROID } SDL_UpdateRelativeMouseMode(); @@ -475,7 +485,7 @@ SDL_Keycode SDL_GetKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate, b SDL_Keyboard *keyboard = &SDL_keyboard; if (key_event) { - SDL_Keymap *keymap = SDL_GetCurrentKeymap(); + SDL_Keymap *keymap = SDL_GetCurrentKeymap(false); bool numlock = (modstate & SDL_KMOD_NUM) != 0; SDL_Keycode keycode; @@ -483,7 +493,7 @@ SDL_Keycode SDL_GetKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate, b modstate = SDL_KMOD_NONE; if ((keyboard->keycode_options & KEYCODE_OPTION_FRENCH_NUMBERS) && - keyboard->french_numbers && + keymap && keymap->french_numbers && (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_0)) { // Add the shift state to generate a numeric keycode modstate |= SDL_KMOD_SHIFT; @@ -870,26 +880,33 @@ void SDL_SendEditingTextCandidates(char **candidates, int num_candidates, int se void SDL_QuitKeyboard(void) { + SDL_keyboard_quitting = true; + for (int i = SDL_keyboard_count; i--;) { - SDL_RemoveKeyboard(SDL_keyboards[i].instance_id, false); + SDL_RemoveKeyboard(SDL_keyboards[i]); } SDL_free(SDL_keyboards); SDL_keyboards = NULL; - if (SDL_keyboard.keymap) { + SDL_DestroyHashTable(SDL_keyboard_names); + SDL_keyboard_names = NULL; + + if (SDL_keyboard.keymap && SDL_keyboard.keymap->auto_release) { SDL_DestroyKeymap(SDL_keyboard.keymap); SDL_keyboard.keymap = NULL; } SDL_RemoveHintCallback(SDL_HINT_KEYCODE_OPTIONS, SDL_KeycodeOptionsChanged, &SDL_keyboard); + + SDL_keyboard_quitting = false; } const bool *SDL_GetKeyboardState(int *numkeys) { SDL_Keyboard *keyboard = &SDL_keyboard; - if (numkeys != (int *)0) { + if (numkeys) { *numkeys = SDL_SCANCODE_COUNT; } return keyboard->keystate; diff --git a/libs/SDL3/src/events/SDL_keyboard_c.h b/libs/SDL3/src/events/SDL_keyboard_c.h index ddfb5c5..a6bcfa0 100644 --- a/libs/SDL3/src/events/SDL_keyboard_c.h +++ b/libs/SDL3/src/events/SDL_keyboard_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -38,10 +38,10 @@ extern bool SDL_InitKeyboard(void); extern bool SDL_IsKeyboard(Uint16 vendor, Uint16 product, int num_keys); // A keyboard has been added to the system -extern void SDL_AddKeyboard(SDL_KeyboardID keyboardID, const char *name, bool send_event); +extern void SDL_AddKeyboard(SDL_KeyboardID keyboardID, const char *name); // A keyboard has been removed from the system -extern void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID, bool send_event); +extern void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID); // Set the mapping of scancode to key codes extern void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event); diff --git a/libs/SDL3/src/events/SDL_keymap.c b/libs/SDL3/src/events/SDL_keymap.c index bd08786..11f4df5 100644 --- a/libs/SDL3/src/events/SDL_keymap.c +++ b/libs/SDL3/src/events/SDL_keymap.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -23,22 +23,17 @@ #include "SDL_keymap_c.h" #include "SDL_keyboard_c.h" -struct SDL_Keymap -{ - SDL_HashTable *scancode_to_keycode; - SDL_HashTable *keycode_to_scancode; -}; - static SDL_Keycode SDL_GetDefaultKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate); static SDL_Scancode SDL_GetDefaultScancodeFromKey(SDL_Keycode key, SDL_Keymod *modstate); -SDL_Keymap *SDL_CreateKeymap(void) +SDL_Keymap *SDL_CreateKeymap(bool auto_release) { - SDL_Keymap *keymap = (SDL_Keymap *)SDL_malloc(sizeof(*keymap)); + SDL_Keymap *keymap = (SDL_Keymap *)SDL_calloc(1, sizeof(*keymap)); if (!keymap) { return NULL; } + keymap->auto_release = auto_release; 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) { @@ -102,16 +97,74 @@ void SDL_SetKeymapEntry(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod mo SDL_Keycode SDL_GetKeymapKeycode(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod modstate) { - SDL_Keycode keycode; + if (keymap) { + const void *value; + const SDL_Keymod normalized_modstate = NormalizeModifierStateForKeymap(modstate); + Uint32 key = ((Uint32)normalized_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; - } else { - keycode = SDL_GetDefaultKeyFromScancode(scancode, modstate); + // First, try the requested set of modifiers. + if (SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { + return (SDL_Keycode)(uintptr_t)value; + } + + // If the requested set of modifiers was not found, search for the key from the highest to lowest modifier levels. + if (normalized_modstate) { + SDL_Keymod caps_mask = normalized_modstate & SDL_KMOD_CAPS; + + for (int i = caps_mask ? 2 : 1; i; --i) { + // Shift level 5 + if (normalized_modstate & SDL_KMOD_LEVEL5) { + const SDL_Keymod shifted_modstate = SDL_KMOD_LEVEL5 | caps_mask; + key = ((Uint32)shifted_modstate << 16) | scancode; + + if (shifted_modstate != normalized_modstate && SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { + return (SDL_Keycode)(uintptr_t)value; + } + } + + // Shift level 4 (Level 3 + Shift) + if ((normalized_modstate & (SDL_KMOD_MODE | SDL_KMOD_SHIFT)) == (SDL_KMOD_MODE | SDL_KMOD_SHIFT)) { + const SDL_Keymod shifted_modstate = SDL_KMOD_MODE | SDL_KMOD_SHIFT | caps_mask; + key = ((Uint32)shifted_modstate << 16) | scancode; + + if (shifted_modstate != normalized_modstate && SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { + return (SDL_Keycode)(uintptr_t)value; + } + } + + // Shift level 3 + if (normalized_modstate & SDL_KMOD_MODE) { + const SDL_Keymod shifted_modstate = SDL_KMOD_MODE | caps_mask; + key = ((Uint32)shifted_modstate << 16) | scancode; + + if (shifted_modstate != normalized_modstate && SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { + return (SDL_Keycode)(uintptr_t)value; + } + } + + // Shift level 2 + if (normalized_modstate & SDL_KMOD_SHIFT) { + const SDL_Keymod shifted_modstate = SDL_KMOD_SHIFT | caps_mask; + key = ((Uint32)shifted_modstate << 16) | scancode; + + if (shifted_modstate != normalized_modstate && SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { + return (SDL_Keycode)(uintptr_t)value; + } + } + + // Shift Level 1 (unmodified) + key = ((Uint32)caps_mask << 16) | scancode; + if (SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { + return (SDL_Keycode)(uintptr_t)value; + } + + // Clear the capslock mask, if set. + caps_mask = SDL_KMOD_NONE; + } + } } - return keycode; + + return SDL_GetDefaultKeyFromScancode(scancode, modstate); } SDL_Scancode SDL_GetKeymapScancode(SDL_Keymap *keymap, SDL_Keycode keycode, SDL_Keymod *modstate) @@ -130,12 +183,34 @@ SDL_Scancode SDL_GetKeymapScancode(SDL_Keymap *keymap, SDL_Keycode keycode, SDL_ return scancode; } +SDL_Scancode SDL_GetKeymapNextReservedScancode(SDL_Keymap *keymap) +{ + SDL_Scancode scancode; + + if (!keymap) { + return SDL_SCANCODE_UNKNOWN; + } + + if (keymap->next_reserved_scancode && keymap->next_reserved_scancode < SDL_SCANCODE_RESERVED + 100) { + scancode = keymap->next_reserved_scancode; + } else { + scancode = SDL_SCANCODE_RESERVED; + } + keymap->next_reserved_scancode = scancode + 1; + + return scancode; +} + void SDL_DestroyKeymap(SDL_Keymap *keymap) { if (!keymap) { return; } + if (!keymap->auto_release && keymap == SDL_GetCurrentKeymap(true)) { + SDL_SetKeymap(NULL, false); + } + SDL_DestroyHashTable(keymap->scancode_to_keycode); SDL_DestroyHashTable(keymap->keycode_to_scancode); SDL_free(keymap); @@ -215,7 +290,7 @@ static const struct static SDL_Keycode SDL_GetDefaultKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate) { - if (((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) { + CHECK_PARAM(((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) { SDL_InvalidParamError("scancode"); return SDLK_UNKNOWN; } @@ -978,7 +1053,7 @@ static const char *SDL_extended_key_names[] = { bool SDL_SetScancodeName(SDL_Scancode scancode, const char *name) { - if (((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) { + CHECK_PARAM(((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) { return SDL_InvalidParamError("scancode"); } @@ -989,7 +1064,8 @@ bool SDL_SetScancodeName(SDL_Scancode scancode, const char *name) const char *SDL_GetScancodeName(SDL_Scancode scancode) { const char *name; - if (((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) { + + CHECK_PARAM(((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) { SDL_InvalidParamError("scancode"); return ""; } @@ -1006,7 +1082,7 @@ SDL_Scancode SDL_GetScancodeFromName(const char *name) { int i; - if (!name || !*name) { + CHECK_PARAM(!name || !*name) { SDL_InvalidParamError("name"); return SDL_SCANCODE_UNKNOWN; } @@ -1064,7 +1140,7 @@ const char *SDL_GetKeyName(SDL_Keycode key) // but the key name is defined as the letter printed on that key, // which is usually the shifted capital letter. if (key > 0x7F || (key >= 'a' && key <= 'z')) { - SDL_Keymap *keymap = SDL_GetCurrentKeymap(); + SDL_Keymap *keymap = SDL_GetCurrentKeymap(false); SDL_Keymod modstate; SDL_Scancode scancode = SDL_GetKeymapScancode(keymap, key, &modstate); if (scancode != SDL_SCANCODE_UNKNOWN && !(modstate & SDL_KMOD_SHIFT)) { @@ -1132,7 +1208,7 @@ SDL_Keycode SDL_GetKeyFromName(const char *name) // SDL_Keycode is defined as the unshifted key on the keyboard, // but the key name is defined as the letter printed on that key, // which is usually the shifted capital letter. - SDL_Keymap *keymap = SDL_GetCurrentKeymap(); + SDL_Keymap *keymap = SDL_GetCurrentKeymap(false); SDL_Keymod modstate; SDL_Scancode scancode = SDL_GetKeymapScancode(keymap, key, &modstate); if (scancode != SDL_SCANCODE_UNKNOWN && (modstate & (SDL_KMOD_SHIFT | SDL_KMOD_CAPS))) { diff --git a/libs/SDL3/src/events/SDL_keymap_c.h b/libs/SDL3/src/events/SDL_keymap_c.h index 9c80397..0ed7e07 100644 --- a/libs/SDL3/src/events/SDL_keymap_c.h +++ b/libs/SDL3/src/events/SDL_keymap_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -23,13 +23,27 @@ #ifndef SDL_keymap_c_h_ #define SDL_keymap_c_h_ -typedef struct SDL_Keymap SDL_Keymap; +typedef struct SDL_Keymap +{ + SDL_HashTable *scancode_to_keycode; + SDL_HashTable *keycode_to_scancode; + SDL_Scancode next_reserved_scancode; + bool auto_release; + bool layout_determined; + bool french_numbers; + bool latin_letters; + bool thai_keyboard; +} SDL_Keymap; -SDL_Keymap *SDL_GetCurrentKeymap(void); -SDL_Keymap *SDL_CreateKeymap(void); +/* This may return null even when a keymap is bound, depending on the current keyboard mapping options. + * Set 'ignore_options' to true to always return the keymap that is actually bound. + */ +SDL_Keymap *SDL_GetCurrentKeymap(bool ignore_options); +SDL_Keymap *SDL_CreateKeymap(bool auto_release); void SDL_SetKeymapEntry(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod modstate, SDL_Keycode keycode); SDL_Keycode SDL_GetKeymapKeycode(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod modstate); SDL_Scancode SDL_GetKeymapScancode(SDL_Keymap *keymap, SDL_Keycode keycode, SDL_Keymod *modstate); +SDL_Scancode SDL_GetKeymapNextReservedScancode(SDL_Keymap *keymap); void SDL_DestroyKeymap(SDL_Keymap *keymap); #endif // SDL_keymap_c_h_ diff --git a/libs/SDL3/src/events/SDL_keysym_to_keycode.c b/libs/SDL3/src/events/SDL_keysym_to_keycode.c index 7cbfcb6..5f6bbd3 100644 --- a/libs/SDL3/src/events/SDL_keysym_to_keycode.c +++ b/libs/SDL3/src/events/SDL_keysym_to_keycode.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_keysym_to_keycode_c.h b/libs/SDL3/src/events/SDL_keysym_to_keycode_c.h index 2321d20..ae782a5 100644 --- a/libs/SDL3/src/events/SDL_keysym_to_keycode_c.h +++ b/libs/SDL3/src/events/SDL_keysym_to_keycode_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_keysym_to_scancode.c b/libs/SDL3/src/events/SDL_keysym_to_scancode.c index 8d43ca3..f54de44 100644 --- a/libs/SDL3/src/events/SDL_keysym_to_scancode.c +++ b/libs/SDL3/src/events/SDL_keysym_to_scancode.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_keysym_to_scancode_c.h b/libs/SDL3/src/events/SDL_keysym_to_scancode_c.h index 8d0e214..b8c6db7 100644 --- a/libs/SDL3/src/events/SDL_keysym_to_scancode_c.h +++ b/libs/SDL3/src/events/SDL_keysym_to_scancode_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_mouse.c b/libs/SDL3/src/events/SDL_mouse.c index 92f9913..80bd649 100644 --- a/libs/SDL3/src/events/SDL_mouse.c +++ b/libs/SDL3/src/events/SDL_mouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -34,16 +34,12 @@ #define WARP_EMULATION_THRESHOLD_NS SDL_MS_TO_NS(30) -typedef struct SDL_MouseInstance -{ - SDL_MouseID instance_id; - char *name; -} SDL_MouseInstance; - // The mouse state static SDL_Mouse SDL_mouse; static int SDL_mouse_count; -static SDL_MouseInstance *SDL_mice; +static SDL_MouseID *SDL_mice; +static SDL_HashTable *SDL_mouse_names; +static bool SDL_mouse_initialized; // for mapping mouse events to touch static bool track_mouse_down = false; @@ -233,9 +229,9 @@ static void SDLCALL SDL_MouseRelativeCursorVisibleChanged(void *userdata, const { SDL_Mouse *mouse = (SDL_Mouse *)userdata; - mouse->relative_mode_cursor_visible = SDL_GetStringBoolean(hint, false); + mouse->relative_mode_hide_cursor = !(SDL_GetStringBoolean(hint, false)); - SDL_SetCursor(NULL); // Update cursor visibility + SDL_RedrawCursor(); // Update cursor visibility } static void SDLCALL SDL_MouseIntegerModeChanged(void *userdata, const char *name, const char *oldValue, const char *hint) @@ -254,6 +250,8 @@ bool SDL_PreInitMouse(void) { SDL_Mouse *mouse = SDL_GetMouse(); + SDL_mouse_initialized = true; + SDL_zerop(mouse); SDL_AddHintCallback(SDL_HINT_MOUSE_DOUBLE_CLICK_TIME, @@ -308,7 +306,9 @@ bool SDL_PreInitMouse(void) mouse->was_touch_mouse_events = false; // no touch to mouse movement event pending - mouse->cursor_shown = true; + mouse->cursor_visible = true; + + SDL_mouse_names = SDL_CreateHashTable(0, true, SDL_HashID, SDL_KeyMatchID, SDL_DestroyHashValue, NULL); return true; } @@ -339,14 +339,14 @@ bool SDL_IsMouse(Uint16 vendor, Uint16 product) static int SDL_GetMouseIndex(SDL_MouseID mouseID) { for (int i = 0; i < SDL_mouse_count; ++i) { - if (mouseID == SDL_mice[i].instance_id) { + if (mouseID == SDL_mice[i]) { return i; } } return -1; } -void SDL_AddMouse(SDL_MouseID mouseID, const char *name, bool send_event) +void SDL_AddMouse(SDL_MouseID mouseID, const char *name) { int mouse_index = SDL_GetMouseIndex(mouseID); if (mouse_index >= 0) { @@ -356,26 +356,27 @@ void SDL_AddMouse(SDL_MouseID mouseID, const char *name, bool send_event) SDL_assert(mouseID != 0); - SDL_MouseInstance *mice = (SDL_MouseInstance *)SDL_realloc(SDL_mice, (SDL_mouse_count + 1) * sizeof(*mice)); + SDL_MouseID *mice = (SDL_MouseID *)SDL_realloc(SDL_mice, (SDL_mouse_count + 1) * sizeof(*mice)); if (!mice) { return; } - SDL_MouseInstance *instance = &mice[SDL_mouse_count]; - instance->instance_id = mouseID; - instance->name = SDL_strdup(name ? name : ""); + mice[SDL_mouse_count] = mouseID; SDL_mice = mice; ++SDL_mouse_count; - if (send_event) { - SDL_Event event; - SDL_zero(event); - event.type = SDL_EVENT_MOUSE_ADDED; - event.mdevice.which = mouseID; - SDL_PushEvent(&event); + if (!name) { + name = "Mouse"; } + SDL_InsertIntoHashTable(SDL_mouse_names, (const void *)(uintptr_t)mouseID, SDL_strdup(name), true); + + SDL_Event event; + SDL_zero(event); + event.type = SDL_EVENT_MOUSE_ADDED; + event.mdevice.which = mouseID; + SDL_PushEvent(&event); } -void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event) +void SDL_RemoveMouse(SDL_MouseID mouseID) { int mouse_index = SDL_GetMouseIndex(mouseID); if (mouse_index < 0) { @@ -383,8 +384,6 @@ void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event) return; } - SDL_free(SDL_mice[mouse_index].name); - if (mouse_index != SDL_mouse_count - 1) { SDL_memmove(&SDL_mice[mouse_index], &SDL_mice[mouse_index + 1], (SDL_mouse_count - mouse_index - 1) * sizeof(SDL_mice[mouse_index])); } @@ -404,7 +403,7 @@ void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event) } } - if (send_event) { + if (SDL_mouse_initialized) { SDL_Event event; SDL_zero(event); event.type = SDL_EVENT_MOUSE_REMOVED; @@ -423,14 +422,14 @@ SDL_MouseID *SDL_GetMice(int *count) int i; SDL_MouseID *mice; - mice = (SDL_JoystickID *)SDL_malloc((SDL_mouse_count + 1) * sizeof(*mice)); + mice = (SDL_MouseID *)SDL_malloc((SDL_mouse_count + 1) * sizeof(*mice)); if (mice) { if (count) { *count = SDL_mouse_count; } for (i = 0; i < SDL_mouse_count; ++i) { - mice[i] = SDL_mice[i].instance_id; + mice[i] = SDL_mice[i]; } mice[i] = 0; } else { @@ -444,12 +443,17 @@ SDL_MouseID *SDL_GetMice(int *count) const char *SDL_GetMouseNameForID(SDL_MouseID instance_id) { - int mouse_index = SDL_GetMouseIndex(instance_id); - if (mouse_index < 0) { + const char *name = NULL; + if (!SDL_FindInHashTable(SDL_mouse_names, (const void *)(uintptr_t)instance_id, (const void **)&name)) { SDL_SetError("Mouse %" SDL_PRIu32 " not found", instance_id); return NULL; } - return SDL_GetPersistentString(SDL_mice[mouse_index].name); + if (!name) { + // SDL_strdup() failed during insert + SDL_OutOfMemory(); + return NULL; + } + return name; } void SDL_SetDefaultCursor(SDL_Cursor *cursor) @@ -597,7 +601,7 @@ void SDL_SetMouseFocus(SDL_Window *window) } // Update cursor visibility - SDL_SetCursor(NULL); + SDL_RedrawCursor(); } bool SDL_MousePositionInWindow(SDL_Window *window, float x, float y) @@ -723,14 +727,19 @@ static void SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL if (relative) { if (mouse->relative_mode) { - if (mouse->enable_relative_system_scale) { - if (mouse->ApplySystemScale) { - mouse->ApplySystemScale(mouse->system_scale_data, timestamp, window, mouseID, &x, &y); + if (mouse->InputTransform) { + void *data = mouse->input_transform_data; + mouse->InputTransform(data, timestamp, window, mouseID, &x, &y); + } else { + if (mouse->enable_relative_system_scale) { + if (mouse->ApplySystemScale) { + mouse->ApplySystemScale(mouse->system_scale_data, timestamp, window, mouseID, &x, &y); + } + } + if (mouse->enable_relative_speed_scale) { + x *= mouse->relative_speed_scale; + y *= mouse->relative_speed_scale; } - } - if (mouse->enable_relative_speed_scale) { - x *= mouse->relative_speed_scale; - y *= mouse->relative_speed_scale; } } else { if (mouse->enable_normal_speed_scale) { @@ -798,7 +807,7 @@ static void SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL } // Move the mouse cursor, if needed - if (mouse->cursor_shown && !mouse->relative_mode && + if (mouse->cursor_visible && !mouse->relative_mode && mouse->MoveCursor && mouse->cur_cursor) { mouse->MoveCursor(mouse->cur_cursor); } @@ -839,38 +848,40 @@ static SDL_MouseInputSource *GetMouseInputSource(SDL_Mouse *mouse, SDL_MouseID m SDL_MouseInputSource *source, *match = NULL, *sources; int i; - for (i = 0; i < mouse->num_sources; ++i) { - source = &mouse->sources[i]; - if (source->mouseID == mouseID) { - match = source; - break; - } - } - - if (!down && (!match || !(match->buttonstate & SDL_BUTTON_MASK(button)))) { - /* This might be a button release from a transition between mouse messages and raw input. - * See if there's another mouse source that already has that button down and use that. - */ + if (SDL_mouse_initialized) { for (i = 0; i < mouse->num_sources; ++i) { source = &mouse->sources[i]; - if ((source->buttonstate & SDL_BUTTON_MASK(button))) { + if (source->mouseID == mouseID) { match = source; break; } } - } - if (match) { - return match; - } - sources = (SDL_MouseInputSource *)SDL_realloc(mouse->sources, (mouse->num_sources + 1) * sizeof(*mouse->sources)); - if (sources) { - mouse->sources = sources; - ++mouse->num_sources; - source = &sources[mouse->num_sources - 1]; - SDL_zerop(source); - source->mouseID = mouseID; - return source; + if (!down && (!match || !(match->buttonstate & SDL_BUTTON_MASK(button)))) { + /* This might be a button release from a transition between mouse messages and raw input. + * See if there's another mouse source that already has that button down and use that. + */ + for (i = 0; i < mouse->num_sources; ++i) { + source = &mouse->sources[i]; + if ((source->buttonstate & SDL_BUTTON_MASK(button))) { + match = source; + break; + } + } + } + if (match) { + return match; + } + + sources = (SDL_MouseInputSource *)SDL_realloc(mouse->sources, (mouse->num_sources + 1) * sizeof(*mouse->sources)); + if (sources) { + mouse->sources = sources; + ++mouse->num_sources; + source = &sources[mouse->num_sources - 1]; + SDL_zerop(source); + source->mouseID = mouseID; + return source; + } } return NULL; } @@ -1073,12 +1084,16 @@ void SDL_QuitMouse(void) SDL_Cursor *cursor, *next; SDL_Mouse *mouse = SDL_GetMouse(); + SDL_mouse_initialized = false; + if (mouse->added_mouse_touch_device) { SDL_DelTouch(SDL_MOUSE_TOUCHID); + mouse->added_mouse_touch_device = false; } if (mouse->added_pen_touch_device) { SDL_DelTouch(SDL_PEN_TOUCHID); + mouse->added_pen_touch_device = false; } if (mouse->CaptureMouse) { @@ -1157,10 +1172,30 @@ void SDL_QuitMouse(void) SDL_MouseIntegerModeChanged, mouse); for (int i = SDL_mouse_count; i--; ) { - SDL_RemoveMouse(SDL_mice[i].instance_id, false); + SDL_RemoveMouse(SDL_mice[i]); } SDL_free(SDL_mice); SDL_mice = NULL; + + SDL_DestroyHashTable(SDL_mouse_names); + SDL_mouse_names = NULL; + + if (mouse->internal) { + SDL_free(mouse->internal); + mouse->internal = NULL; + } + SDL_zerop(mouse); +} + +bool SDL_SetRelativeMouseTransform(SDL_MouseMotionTransformCallback transform, void *userdata) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + if (mouse->relative_mode) { + return SDL_SetError("Can't set mouse transform while relative mode is active"); + } + mouse->InputTransform = transform; + mouse->input_transform_data = userdata; + return true; } SDL_MouseButtonFlags SDL_GetMouseState(float *x, float *y) @@ -1273,7 +1308,7 @@ static void SDL_MaybeEnableWarpEmulation(SDL_Window *window, float x, float y) { SDL_Mouse *mouse = SDL_GetMouse(); - if (!mouse->warp_emulation_prohibited && mouse->warp_emulation_hint && !mouse->cursor_shown && !mouse->warp_emulation_active) { + if (!mouse->warp_emulation_prohibited && mouse->warp_emulation_hint && !mouse->cursor_visible && !mouse->warp_emulation_active) { if (!window) { window = mouse->focus; } @@ -1287,8 +1322,9 @@ static void SDL_MaybeEnableWarpEmulation(SDL_Window *window, float x, float y) // Require two consecutive warps to the center within a certain timespan to enter warp emulation mode. const Uint64 now = SDL_GetTicksNS(); if (now - mouse->last_center_warp_time_ns < WARP_EMULATION_THRESHOLD_NS) { - if (SDL_SetRelativeMouseMode(true)) { - mouse->warp_emulation_active = true; + mouse->warp_emulation_active = true; + if (!SDL_SetRelativeMouseMode(true)) { + mouse->warp_emulation_active = false; } } @@ -1335,16 +1371,17 @@ bool SDL_SetRelativeMouseMode(bool enabled) } // Set the relative mode - if (!mouse->SetRelativeMouseMode || !mouse->SetRelativeMouseMode(enabled)) { - if (enabled) { - return SDL_SetError("No relative mode implementation available"); - } + if (!mouse->SetRelativeMouseMode) { + return SDL_Unsupported(); + } + if (!mouse->SetRelativeMouseMode(enabled)) { + return false; } mouse->relative_mode = enabled; if (enabled) { // Update cursor visibility before we potentially warp the mouse - SDL_SetCursor(NULL); + SDL_RedrawCursor(); } if (enabled && focusWindow) { @@ -1364,7 +1401,7 @@ bool SDL_SetRelativeMouseMode(bool enabled) if (!enabled) { // Update cursor visibility after we restore the mouse position - SDL_SetCursor(NULL); + SDL_RedrawCursor(); } // Flush pending mouse motion - ideally we would pump events, but that's not always safe @@ -1380,15 +1417,16 @@ bool SDL_GetRelativeMouseMode(void) return mouse->relative_mode; } -void SDL_UpdateRelativeMouseMode(void) +bool SDL_UpdateRelativeMouseMode(void) { SDL_Mouse *mouse = SDL_GetMouse(); SDL_Window *focus = SDL_GetKeyboardFocus(); bool relative_mode = (focus && (focus->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE)); - if (relative_mode != mouse->relative_mode) { - SDL_SetRelativeMouseMode(relative_mode); + if (relative_mode == mouse->relative_mode) { + return true; } + return SDL_SetRelativeMouseMode(relative_mode); } bool SDL_UpdateMouseCapture(bool force_release) @@ -1474,7 +1512,7 @@ SDL_Cursor *SDL_CreateCursor(const Uint8 *data, const Uint8 *mask, int w, int h, SDL_Surface *surface; SDL_Cursor *cursor; int x, y; - Uint32 *pixel; + Uint32 *pixels; Uint8 datab = 0, maskb = 0; const Uint32 black = 0xFF000000; const Uint32 white = 0xFFFFFFFF; @@ -1495,16 +1533,16 @@ SDL_Cursor *SDL_CreateCursor(const Uint8 *data, const Uint8 *mask, int w, int h, return NULL; } for (y = 0; y < h; ++y) { - pixel = (Uint32 *)((Uint8 *)surface->pixels + y * surface->pitch); + pixels = (Uint32 *)((Uint8 *)surface->pixels + y * surface->pitch); for (x = 0; x < w; ++x) { if ((x % 8) == 0) { datab = *data++; maskb = *mask++; } if (maskb & 0x80) { - *pixel++ = (datab & 0x80) ? black : white; + *pixels++ = (datab & 0x80) ? black : white; } else { - *pixel++ = (datab & 0x80) ? inverted : transparent; + *pixels++ = (datab & 0x80) ? inverted : transparent; } datab <<= 1; maskb <<= 1; @@ -1518,13 +1556,190 @@ SDL_Cursor *SDL_CreateCursor(const Uint8 *data, const Uint8 *mask, int w, int h, return cursor; } +static void SDL_DestroyCursorAnimation(SDL_CursorAnimation *animation) +{ + if (!animation) { + return; + } + + for (int i = 0; i < animation->num_frames; ++i) { + SDL_DestroyCursor(animation->frames[i]); + } + SDL_free(animation->frames); + SDL_free(animation->durations); + SDL_free(animation); +} + +static SDL_CursorAnimation *SDL_CreateCursorAnimation(SDL_CursorFrameInfo *frames, int frame_count, int hot_x, int hot_y) +{ + SDL_CursorAnimation *animation = (SDL_CursorAnimation *)SDL_calloc(1, sizeof(*animation)); + if (!animation) { + return NULL; + } + + animation->frames = (SDL_Cursor **)SDL_calloc(frame_count, sizeof(*animation->frames)); + animation->durations = (Uint32 *)SDL_calloc(frame_count, sizeof(*animation->durations)); + if (!animation->frames || !animation->durations) { + SDL_DestroyCursorAnimation(animation); + return NULL; + } + + for (int i = 0; i < frame_count; ++i) { + animation->frames[i] = SDL_CreateColorCursor(frames[i].surface, hot_x, hot_y); + if (!animation->frames[i]) { + SDL_DestroyCursorAnimation(animation); + return NULL; + } + + animation->durations[i] = frames[i].duration; + } + animation->num_frames = frame_count; + + return animation; +} + +void SDL_UpdateCursorAnimation(void) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + SDL_Cursor *cursor = mouse->cur_cursor; + + if (!cursor || !cursor->animation) { + return; + } + + if (!mouse->focus) { + return; + } + + SDL_CursorAnimation *animation = cursor->animation; + Uint32 duration = animation->durations[animation->current_frame]; + if (!duration) { + // We've reached the stop frame of the animation + return; + } + + Uint64 now = SDL_GetTicks(); + if (now < (animation->last_update + duration)) { + return; + } + + animation->current_frame = (animation->current_frame + 1) % animation->num_frames; + animation->last_update = now; + + SDL_RedrawCursor(); +} + +SDL_Cursor *SDL_CreateAnimatedCursor(SDL_CursorFrameInfo *frames, int frame_count, int hot_x, int hot_y) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + SDL_Cursor *cursor = NULL; + + CHECK_PARAM(!frames) { + SDL_InvalidParamError("frames"); + return NULL; + } + + CHECK_PARAM(!frames[0].surface) { + SDL_SetError("NULL surface in frame 0"); + return NULL; + } + + CHECK_PARAM(frame_count <= 0) { + SDL_InvalidParamError("frame_count"); + return NULL; + } + + if (frame_count == 1) { + return SDL_CreateColorCursor(frames[0].surface, hot_x, hot_y); + } + + // Allow specifying the hot spot via properties on the surface + SDL_PropertiesID props = SDL_GetSurfaceProperties(frames[0].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 + CHECK_PARAM((hot_x < 0) || (hot_y < 0) || + (hot_x >= frames[0].surface->w) || (hot_y >= frames[0].surface->h)) { + SDL_SetError("Cursor hot spot doesn't lie within cursor"); + return NULL; + } + + bool isstack; + SDL_CursorFrameInfo *temp_frames = SDL_small_alloc(SDL_CursorFrameInfo, frame_count, &isstack); + if (!temp_frames) { + return NULL; + } + SDL_memset(temp_frames, 0, sizeof(SDL_CursorFrameInfo) * frame_count); + + const int w = frames[0].surface->w; + const int h = frames[0].surface->h; + + for (int i = 0; i < frame_count; ++i) { + CHECK_PARAM(!frames[i].surface) { + SDL_SetError("Null surface in frame %i", i); + goto cleanup; + } + + // All cursor images should be the same size. + CHECK_PARAM(frames[i].surface->w != w || frames[i].surface->h != h) { + SDL_SetError("All frames in an animated sequence must have the same dimensions"); + goto cleanup; + } + + if (frames[i].surface->format == SDL_PIXELFORMAT_ARGB8888) { + temp_frames[i].surface = frames[i].surface; + } else { + SDL_Surface *temp = SDL_ConvertSurface(frames[i].surface, SDL_PIXELFORMAT_ARGB8888); + if (!temp) { + goto cleanup; + } + temp_frames[i].surface = temp; + } + temp_frames[i].duration = frames[i].duration; + } + + if (mouse->CreateAnimatedCursor) { + cursor = mouse->CreateAnimatedCursor(temp_frames, frame_count, hot_x, hot_y); + } else { + SDL_CursorAnimation *animation = SDL_CreateCursorAnimation(temp_frames, frame_count, hot_x, hot_y); + if (!animation) { + goto cleanup; + } + + cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor)); + if (!cursor) { + SDL_DestroyCursorAnimation(animation); + goto cleanup; + } + cursor->animation = animation; + } + + if (cursor) { + cursor->next = mouse->cursors; + mouse->cursors = cursor; + } + +cleanup: + // Clean up any temporary converted surfaces. + for (int i = 0; i < frame_count; ++i) { + if (temp_frames[i].surface && frames[i].surface != temp_frames[i].surface) { + SDL_DestroySurface(temp_frames[i].surface); + } + } + + SDL_small_free(temp_frames, isstack); + + return cursor; +} + SDL_Cursor *SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y) { SDL_Mouse *mouse = SDL_GetMouse(); SDL_Surface *temp = NULL; SDL_Cursor *cursor; - if (!surface) { + CHECK_PARAM(!surface) { SDL_InvalidParamError("surface"); return NULL; } @@ -1535,8 +1750,8 @@ SDL_Cursor *SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y) 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)) { + CHECK_PARAM((hot_x < 0) || (hot_y < 0) || + (hot_x >= surface->w) || (hot_y >= surface->h)) { SDL_SetError("Cursor hot spot doesn't lie within cursor"); return NULL; } @@ -1583,6 +1798,31 @@ SDL_Cursor *SDL_CreateSystemCursor(SDL_SystemCursor id) return cursor; } +void SDL_RedrawCursor(void) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + SDL_Cursor *cursor; + + if (mouse->focus) { + cursor = mouse->cur_cursor; + } else { + cursor = mouse->def_cursor; + } + + if (mouse->focus && (!mouse->cursor_visible || (mouse->relative_mode && mouse->relative_mode_hide_cursor))) { + cursor = NULL; + } + + if (cursor && cursor->animation) { + SDL_CursorAnimation *animation = cursor->animation; + cursor = animation->frames[animation->current_frame]; + } + + if (mouse->ShowCursor) { + mouse->ShowCursor(cursor); + } +} + /* SDL_SetCursor(NULL) can be used to force the cursor redraw, if this is desired for any reason. This is used when setting the video mode and when the SDL window gains the mouse focus. @@ -1591,7 +1831,7 @@ bool SDL_SetCursor(SDL_Cursor *cursor) { SDL_Mouse *mouse = SDL_GetMouse(); - // Return immediately if setting the cursor to the currently set one (fixes #7151) + // already on this cursor, no further action required if (cursor == mouse->cur_cursor) { return true; } @@ -1610,24 +1850,16 @@ bool SDL_SetCursor(SDL_Cursor *cursor) return SDL_SetError("Cursor not associated with the current mouse"); } } - mouse->cur_cursor = cursor; - } else { - if (mouse->focus) { - cursor = mouse->cur_cursor; - } else { - cursor = mouse->def_cursor; + if (cursor->animation) { + SDL_CursorAnimation *animation = cursor->animation; + animation->current_frame = 0; + animation->last_update = SDL_GetTicks(); } + mouse->cur_cursor = cursor; } - if (cursor && (!mouse->focus || (mouse->cursor_shown && (!mouse->relative_mode || mouse->relative_mode_cursor_visible)))) { - if (mouse->ShowCursor) { - mouse->ShowCursor(cursor); - } - } else { - if (mouse->ShowCursor) { - mouse->ShowCursor(NULL); - } - } + SDL_RedrawCursor(); + return true; } @@ -1676,6 +1908,10 @@ void SDL_DestroyCursor(SDL_Cursor *cursor) mouse->cursors = curr->next; } + if (curr->animation) { + SDL_DestroyCursorAnimation(curr->animation); + } + if (mouse->FreeCursor && curr->internal) { mouse->FreeCursor(curr); } else { @@ -1695,9 +1931,9 @@ bool SDL_ShowCursor(void) mouse->warp_emulation_active = false; } - if (!mouse->cursor_shown) { - mouse->cursor_shown = true; - SDL_SetCursor(NULL); + if (!mouse->cursor_visible) { + mouse->cursor_visible = true; + SDL_RedrawCursor(); } return true; } @@ -1706,9 +1942,9 @@ bool SDL_HideCursor(void) { SDL_Mouse *mouse = SDL_GetMouse(); - if (mouse->cursor_shown) { - mouse->cursor_shown = false; - SDL_SetCursor(NULL); + if (mouse->cursor_visible) { + mouse->cursor_visible = false; + SDL_RedrawCursor(); } return true; } @@ -1717,5 +1953,5 @@ bool SDL_CursorVisible(void) { SDL_Mouse *mouse = SDL_GetMouse(); - return mouse->cursor_shown; + return mouse->cursor_visible; } diff --git a/libs/SDL3/src/events/SDL_mouse_c.h b/libs/SDL3/src/events/SDL_mouse_c.h index 5927b8b..36c458d 100644 --- a/libs/SDL3/src/events/SDL_mouse_c.h +++ b/libs/SDL3/src/events/SDL_mouse_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -31,10 +31,24 @@ typedef struct SDL_CursorData SDL_CursorData; +struct SDL_Cursor; + +typedef struct +{ + Uint64 last_update; + int num_frames; + int current_frame; + struct SDL_Cursor **frames; + Uint32 *durations; +} SDL_CursorAnimation; + struct SDL_Cursor { - struct SDL_Cursor *next; + SDL_CursorAnimation *animation; + SDL_CursorData *internal; + + struct SDL_Cursor *next; }; typedef struct @@ -60,6 +74,9 @@ typedef struct // Create a cursor from a surface SDL_Cursor *(*CreateCursor)(SDL_Surface *surface, int hot_x, int hot_y); + // Create an animated cursor from a sequence of surfaces + SDL_Cursor *(*CreateAnimatedCursor)(SDL_CursorFrameInfo *frames, int frame_count, int hot_x, int hot_y); + // Create a system cursor SDL_Cursor *(*CreateSystemCursor)(SDL_SystemCursor id); @@ -87,10 +104,14 @@ typedef struct // Get absolute mouse coordinates. (x) and (y) are never NULL and set to zero before call. SDL_MouseButtonFlags (*GetGlobalMouseState)(float *x, float *y); - // Platform-specific system mouse transform - void (*ApplySystemScale)(void *internal, Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, float *x, float *y); + // Platform-specific system mouse transform applied in relative mode + SDL_MouseMotionTransformCallback ApplySystemScale; void *system_scale_data; + // User-defined mouse input transform applied in relative mode + SDL_MouseMotionTransformCallback InputTransform; + void *input_transform_data; + // integer mode data Uint8 integer_mode_flags; // 1 to enable mouse quantization, 2 to enable wheel quantization float integer_mode_residual_motion_x; @@ -110,7 +131,7 @@ typedef struct bool has_position; bool relative_mode; bool relative_mode_warp_motion; - bool relative_mode_cursor_visible; + bool relative_mode_hide_cursor; bool relative_mode_center; bool warp_emulation_hint; bool warp_emulation_active; @@ -144,7 +165,7 @@ typedef struct SDL_Cursor *cursors; SDL_Cursor *def_cursor; SDL_Cursor *cur_cursor; - bool cursor_shown; + bool cursor_visible; // Driver-dependent data. void *internal; @@ -160,20 +181,26 @@ extern void SDL_PostInitMouse(void); extern bool SDL_IsMouse(Uint16 vendor, Uint16 product); // A mouse has been added to the system -extern void SDL_AddMouse(SDL_MouseID mouseID, const char *name, bool send_event); +extern void SDL_AddMouse(SDL_MouseID mouseID, const char *name); // A mouse has been removed from the system -extern void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event); +extern void SDL_RemoveMouse(SDL_MouseID mouseID); // Get the mouse state structure extern SDL_Mouse *SDL_GetMouse(void); +// Set the default mouse cursor +extern void SDL_RedrawCursor(void); + // Set the default mouse cursor extern void SDL_SetDefaultCursor(SDL_Cursor *cursor); // Get the preferred default system cursor extern SDL_SystemCursor SDL_GetDefaultSystemCursor(void); +// Update the current cursor animation if needed +extern void SDL_UpdateCursorAnimation(void); + // Set the mouse focus window extern void SDL_SetMouseFocus(SDL_Window *window); @@ -198,7 +225,7 @@ extern void SDL_PerformWarpMouseInWindow(SDL_Window *window, float x, float y, b // Relative mouse mode extern bool SDL_SetRelativeMouseMode(bool enabled); extern bool SDL_GetRelativeMouseMode(void); -extern void SDL_UpdateRelativeMouseMode(void); +extern bool SDL_UpdateRelativeMouseMode(void); extern void SDL_DisableMouseWarpEmulation(void); // TODO RECONNECT: Set mouse state to "zero" diff --git a/libs/SDL3/src/events/SDL_pen.c b/libs/SDL3/src/events/SDL_pen.c index cd3730c..61d41e1 100644 --- a/libs/SDL3/src/events/SDL_pen.c +++ b/libs/SDL3/src/events/SDL_pen.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -37,6 +37,8 @@ typedef struct SDL_Pen float x; float y; SDL_PenInputFlags input_state; + bool pending_proximity_out; + SDL_WindowID pending_proximity_window_id; void *driverdata; } SDL_Pen; @@ -45,6 +47,7 @@ typedef struct SDL_Pen static SDL_RWLock *pen_device_rwlock = NULL; static SDL_Pen *pen_devices SDL_GUARDED_BY(pen_device_rwlock) = NULL; static int pen_device_count SDL_GUARDED_BY(pen_device_rwlock) = 0; +static SDL_AtomicInt pending_proximity_out; // You must hold pen_device_rwlock before calling this, and result is only safe while lock is held! // If SDL isn't initialized, grabbing the NULL lock is a no-op and there will be zero devices, so @@ -108,17 +111,9 @@ bool SDL_InitPen(void) void SDL_QuitPen(void) { + SDL_RemoveAllPenDevices(NULL, NULL); SDL_DestroyRWLock(pen_device_rwlock); pen_device_rwlock = NULL; - if (pen_devices) { - for (int i = pen_device_count; i--; ) { - SDL_free(pen_devices[i].name); - } - SDL_free(pen_devices); - pen_devices = NULL; - } - pen_device_count = 0; - pen_touching = 0; } #if 0 // not a public API at the moment. @@ -199,6 +194,15 @@ SDL_PenInputFlags SDL_GetPenStatus(SDL_PenID instance_id, float *axes, int num_a return result; } +SDL_PenDeviceType SDL_GetPenDeviceType(SDL_PenID instance_id) +{ + SDL_LockRWLockForReading(pen_device_rwlock); + const SDL_Pen *pen = FindPenByInstanceId(instance_id); + const SDL_PenDeviceType result = pen ? pen->info.device_type : SDL_PEN_DEVICE_TYPE_INVALID; + SDL_UnlockRWLock(pen_device_rwlock); + return result; +} + SDL_PenCapabilityFlags SDL_GetPenCapabilityFromAxis(SDL_PenAxis axis) { // the initial capability bits happen to match up, but as @@ -209,7 +213,7 @@ SDL_PenCapabilityFlags SDL_GetPenCapabilityFromAxis(SDL_PenAxis axis) return 0; // oh well. } -SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, const SDL_PenInfo *info, void *handle) +SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, SDL_Window *window, const SDL_PenInfo *info, void *handle, bool in_proximity) { SDL_assert(handle != NULL); // just allocate a Uint8 so you have a unique pointer if not needed! SDL_assert(SDL_FindPenByHandle(handle) == 0); // Backends shouldn't double-add pens! @@ -247,24 +251,21 @@ SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, const SDL_PenInfo SDL_free(namecpy); } - if (result && SDL_EventEnabled(SDL_EVENT_PEN_PROXIMITY_IN)) { - SDL_Event event; - SDL_zero(event); - event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_IN; - event.pproximity.timestamp = timestamp; - event.pproximity.which = result; - SDL_PushEvent(&event); + if (result && in_proximity) { + SDL_SendPenProximity(timestamp, result, window, true, true); } return result; } -void SDL_RemovePenDevice(Uint64 timestamp, SDL_PenID instance_id) +void SDL_RemovePenDevice(Uint64 timestamp, SDL_Window *window, SDL_PenID instance_id) { if (!instance_id) { return; } + SDL_SendPenProximity(timestamp, instance_id, window, false, true); // bye bye + SDL_LockRWLockForWriting(pen_device_rwlock); SDL_Pen *pen = FindPenByInstanceId(instance_id); if (pen) { @@ -290,15 +291,6 @@ void SDL_RemovePenDevice(Uint64 timestamp, SDL_PenID instance_id) } } SDL_UnlockRWLock(pen_device_rwlock); - - if (pen && SDL_EventEnabled(SDL_EVENT_PEN_PROXIMITY_OUT)) { - SDL_Event event; - SDL_zero(event); - event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_OUT; - event.pproximity.timestamp = timestamp; - event.pproximity.which = instance_id; - SDL_PushEvent(&event); - } } // This presumably is happening during video quit, so we don't send PROXIMITY_OUT events here. @@ -308,12 +300,16 @@ void SDL_RemoveAllPenDevices(void (*callback)(SDL_PenID instance_id, void *handl if (pen_device_count > 0) { SDL_assert(pen_devices != NULL); for (int i = 0; i < pen_device_count; i++) { - callback(pen_devices[i].instance_id, pen_devices[i].driverdata, userdata); + if (callback) { + callback(pen_devices[i].instance_id, pen_devices[i].driverdata, userdata); + } SDL_free(pen_devices[i].name); } } SDL_free(pen_devices); pen_devices = NULL; + pen_device_count = 0; + pen_touching = 0; SDL_UnlockRWLock(pen_device_rwlock); } @@ -460,6 +456,15 @@ void SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window } } +static void EnsurePenProximity(Uint64 timestamp, SDL_Pen *pen, SDL_Window *window) +{ + if (pen->pending_proximity_out) { + pen->pending_proximity_out = false; + } else if (!(pen->input_state & SDL_PEN_INPUT_IN_PROXIMITY)) { + SDL_SendPenProximity(timestamp, pen->instance_id, window, true, true); + } +} + void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, float x, float y) { bool send_event = false; @@ -472,6 +477,8 @@ void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *wind SDL_LockRWLockForReading(pen_device_rwlock); SDL_Pen *pen = FindPenByInstanceId(instance_id); if (pen) { + EnsurePenProximity(timestamp, pen, window); + if ((pen->x != x) || (pen->y != y)) { pen->x = x; // we could do an SDL_SetAtomicInt here if we run into trouble... pen->y = y; // we could do an SDL_SetAtomicInt here if we run into trouble... @@ -508,7 +515,9 @@ void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *wind } } else if (pen_touching == 0) { // send mouse motion (without a pressed button) for pens that aren't touching. // this might cause a little chaos if you have multiple pens hovering at the same time, but this seems unlikely in the real world, and also something you did to yourself. :) - SDL_SendMouseMotion(timestamp, window, SDL_PEN_MOUSEID, false, x, y); + if (mouse->pen_mouse_events) { + SDL_SendMouseMotion(timestamp, window, SDL_PEN_MOUSEID, false, x, y); + } } } } @@ -533,6 +542,8 @@ void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *wind SDL_LockRWLockForReading(pen_device_rwlock); SDL_Pen *pen = FindPenByInstanceId(instance_id); if (pen) { + EnsurePenProximity(timestamp, pen, window); + input_state = pen->input_state; const Uint32 flag = (Uint32) (1u << button); const bool current = ((input_state & flag) != 0); @@ -584,3 +595,72 @@ void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *wind } } +void SDL_SendPenProximity(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, bool in, bool immediate) +{ + bool send_event = false; + SDL_PenInputFlags input_state = 0; + + // note that this locks for _reading_ because the lock protects the + // pen_devices array from being reallocated from under us, not the data in it; + // we assume only one thread (in the backend) is modifying an individual pen at + // a time, so it can update input state cleanly here. + SDL_LockRWLockForReading(pen_device_rwlock); + SDL_Pen *pen = FindPenByInstanceId(instance_id); + if (pen) { + if (in || immediate) { + input_state = pen->input_state; + const bool in_proximity = ((input_state & SDL_PEN_INPUT_IN_PROXIMITY) != 0); + if (in_proximity != in) { + if (in) { + input_state |= SDL_PEN_INPUT_IN_PROXIMITY; + } else { + input_state &= ~SDL_PEN_INPUT_IN_PROXIMITY; + } + send_event = true; + pen->input_state = input_state; // we could do an SDL_SetAtomicInt here if we run into trouble... + } + pen->pending_proximity_out = false; + } else { + pen->pending_proximity_out = true; + pen->pending_proximity_window_id = (window ? window->id : 0); + SDL_SetAtomicInt(&pending_proximity_out, true); + } + } + SDL_UnlockRWLock(pen_device_rwlock); + + const Uint32 event_type = in ? SDL_EVENT_PEN_PROXIMITY_IN : SDL_EVENT_PEN_PROXIMITY_OUT; + if (send_event && SDL_EventEnabled(event_type)) { + SDL_Event event; + SDL_zero(event); + event.pproximity.type = event_type; + event.pproximity.timestamp = timestamp; + event.pproximity.windowID = window ? window->id : 0; + event.pproximity.which = instance_id; + SDL_PushEvent(&event); + } +} + +void SDL_SendPendingPenProximity(void) +{ + if (SDL_CompareAndSwapAtomicInt(&pending_proximity_out, true, false)) { + SDL_LockRWLockForReading(pen_device_rwlock); + for (int i = 0; i < pen_device_count; i++) { + SDL_Pen *pen = &pen_devices[i]; + if (pen->pending_proximity_out) { + pen->pending_proximity_out = false; + + SDL_Window *window = NULL; + if (pen->pending_proximity_window_id) { + window = SDL_GetWindowFromID(pen->pending_proximity_window_id); + if (!window) { + // The window is already gone, ignore this event + continue; + } + } + SDL_SendPenProximity(0, pen->instance_id, window, false, true); + } + } + SDL_UnlockRWLock(pen_device_rwlock); + } +} + diff --git a/libs/SDL3/src/events/SDL_pen_c.h b/libs/SDL3/src/events/SDL_pen_c.h index 1eff47f..0aca9a6 100644 --- a/libs/SDL3/src/events/SDL_pen_c.h +++ b/libs/SDL3/src/events/SDL_pen_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -36,6 +36,8 @@ typedef Uint32 SDL_PenCapabilityFlags; #define SDL_PEN_CAPABILITY_TANGENTIAL_PRESSURE (1u << 6) /**< Provides barrel pressure on SDL_PEN_AXIS_TANGENTIAL_PRESSURE. */ #define SDL_PEN_CAPABILITY_ERASER (1u << 7) /**< Pen also has an eraser tip. */ +// Rename before making this public as it clashes with SDL_PenDeviceType. +// Prior art in Android calls this "tool type". typedef enum SDL_PenSubtype { SDL_PEN_TYPE_UNKNOWN, /**< Unknown pen device */ @@ -53,15 +55,16 @@ typedef struct SDL_PenInfo Uint32 wacom_id; /**< For Wacom devices: wacom tool type ID, otherwise 0 (useful e.g. with libwacom) */ int num_buttons; /**< Number of pen buttons (not counting the pen tip), or -1 if unknown. */ SDL_PenSubtype subtype; /**< type of pen device */ + SDL_PenDeviceType device_type; } SDL_PenInfo; // Backend calls this when a new pen device is hotplugged, plus once for each pen already connected at startup. // Note that name and info are copied but currently unused; this is placeholder for a potentially more robust API later. // Both are allowed to be NULL. -extern SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, const SDL_PenInfo *info, void *handle); +extern SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, SDL_Window *window, const SDL_PenInfo *info, void *handle, bool in_proximity); // Backend calls this when an existing pen device is disconnected during runtime. They must free their own stuff separately. -extern void SDL_RemovePenDevice(Uint64 timestamp, SDL_PenID instance_id); +extern void SDL_RemovePenDevice(Uint64 timestamp, SDL_Window *window, SDL_PenID instance_id); // Backend can call this to remove all pens, probably during shutdown, with a callback to let them free their own handle. extern void SDL_RemoveAllPenDevices(void (*callback)(SDL_PenID instance_id, void *handle, void *userdata), void *userdata); @@ -78,6 +81,12 @@ extern void SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, SDL_Window // Backend calls this when a pen's button changes, to generate events and update state. extern void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, Uint8 button, bool down); +// Backend calls this when a pen's proximity changes, to generate events and update state. +extern void SDL_SendPenProximity(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, bool in, bool immediate); + +// Pumping events calls this to generate pending pen proximity events +extern void SDL_SendPendingPenProximity(void); + // Backend can optionally use this to find the SDL_PenID for the `handle` that was passed to SDL_AddPenDevice. extern SDL_PenID SDL_FindPenByHandle(void *handle); diff --git a/libs/SDL3/src/events/SDL_quit.c b/libs/SDL3/src/events/SDL_quit.c index 5456203..2128d1c 100644 --- a/libs/SDL3/src/events/SDL_quit.c +++ b/libs/SDL3/src/events/SDL_quit.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -46,8 +46,10 @@ static bool send_foregrounding_pending = false; static void SDL_HandleSIG(int sig) { - // Reset the signal handler +#ifndef HAVE_SIGACTION + // Reset the signal handler if it was installed with signal() (void)signal(sig, SDL_HandleSIG); +#endif // Send a quit event next time the event loop pumps. // We can't send it in signal handler; SDL_malloc() might be interrupted! diff --git a/libs/SDL3/src/events/SDL_scancode_tables.c b/libs/SDL3/src/events/SDL_scancode_tables.c index a780f13..d3cbf7a 100644 --- a/libs/SDL3/src/events/SDL_scancode_tables.c +++ b/libs/SDL3/src/events/SDL_scancode_tables.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_scancode_tables_c.h b/libs/SDL3/src/events/SDL_scancode_tables_c.h index c8ad5fc..8c2bf88 100644 --- a/libs/SDL3/src/events/SDL_scancode_tables_c.h +++ b/libs/SDL3/src/events/SDL_scancode_tables_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/SDL_touch.c b/libs/SDL3/src/events/SDL_touch.c index ec4acb1..b8ebb78 100644 --- a/libs/SDL3/src/events/SDL_touch.c +++ b/libs/SDL3/src/events/SDL_touch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -25,8 +25,21 @@ #include "SDL_events_c.h" #include "../video/SDL_sysvideo.h" -static int SDL_num_touch = 0; -static SDL_Touch **SDL_touchDevices = NULL; +static SDL_Mutex *SDL_touch_lock = NULL; // This needs to support recursive locks +static int SDL_touch_locked = 0; + +struct SDL_Touch +{ + SDL_TouchID id SDL_GUARDED_BY(SDL_touch_lock); + SDL_TouchDeviceType type SDL_GUARDED_BY(SDL_touch_lock); + int num_fingers SDL_GUARDED_BY(SDL_touch_lock); + int max_fingers SDL_GUARDED_BY(SDL_touch_lock); + SDL_Finger **fingers SDL_GUARDED_BY(SDL_touch_lock); + char *name SDL_GUARDED_BY(SDL_touch_lock); +}; + +static int SDL_num_touch SDL_GUARDED_BY(SDL_touch_lock) = 0; +static SDL_Touch **SDL_touchDevices SDL_GUARDED_BY(SDL_touch_lock) = NULL; // for mapping touch events to mice static bool finger_touching = false; @@ -36,31 +49,63 @@ static SDL_TouchID track_touchid; // Public functions bool SDL_InitTouch(void) { + SDL_touch_lock = SDL_CreateMutex(); return true; } +static void SDL_LockTouch(void) SDL_ACQUIRE(SDL_touch_lock) +{ + SDL_LockMutex(SDL_touch_lock); + ++SDL_touch_locked; +} + +static void SDL_UnlockTouch(void) SDL_RELEASE(SDL_touch_lock) +{ + --SDL_touch_locked; + SDL_UnlockMutex(SDL_touch_lock); +} + +static void SDL_AssertTouchLocked(void) SDL_ASSERT_CAPABILITY(SDL_touch_lock) +{ + SDL_assert(SDL_touch_locked > 0); +} + bool SDL_TouchDevicesAvailable(void) { - return SDL_num_touch > 0; + bool available; + + SDL_LockTouch(); + { + available = (SDL_num_touch > 0); + } + SDL_UnlockTouch(); + + return available; } SDL_TouchID *SDL_GetTouchDevices(int *count) { + SDL_TouchID *result; + if (count) { *count = 0; } - const int total = SDL_num_touch; - SDL_TouchID *result = (SDL_TouchID *) SDL_malloc(sizeof (SDL_TouchID) * (total + 1)); - if (result) { - for (int i = 0; i < total; i++) { - result[i] = SDL_touchDevices[i]->id; - } - result[total] = 0; - if (count) { - *count = SDL_num_touch; + SDL_LockTouch(); + { + const int total = SDL_num_touch; + result = (SDL_TouchID *)SDL_malloc(sizeof (SDL_TouchID) * (total + 1)); + if (result) { + for (int i = 0; i < total; i++) { + result[i] = SDL_touchDevices[i]->id; + } + result[total] = 0; + if (count) { + *count = SDL_num_touch; + } } } + SDL_UnlockTouch(); return result; } @@ -70,6 +115,8 @@ static int SDL_GetTouchIndex(SDL_TouchID id) int index; SDL_Touch *touch; + SDL_AssertTouchLocked(); + for (index = 0; index < SDL_num_touch; ++index) { touch = SDL_touchDevices[index]; if (touch->id == id) { @@ -81,11 +128,15 @@ static int SDL_GetTouchIndex(SDL_TouchID id) SDL_Touch *SDL_GetTouch(SDL_TouchID id) { + SDL_AssertTouchLocked(); + int index = SDL_GetTouchIndex(id); if (index < 0 || index >= SDL_num_touch) { - if (SDL_GetVideoDevice()->ResetTouch != NULL) { + if ((id == SDL_MOUSE_TOUCHID) || (id == SDL_PEN_TOUCHID)) { + // this is a virtual touch device, but for some reason they aren't added to the system. Just ignore it. + } else if ( SDL_GetVideoDevice()->ResetTouch) { SDL_SetError("Unknown touch id %d, resetting", (int)id); - (SDL_GetVideoDevice()->ResetTouch)(SDL_GetVideoDevice()); + SDL_GetVideoDevice()->ResetTouch(SDL_GetVideoDevice()); } else { SDL_SetError("Unknown touch device id %d, cannot reset", (int)id); } @@ -96,21 +147,40 @@ SDL_Touch *SDL_GetTouch(SDL_TouchID id) const char *SDL_GetTouchDeviceName(SDL_TouchID id) { - SDL_Touch *touch = SDL_GetTouch(id); - if (!touch) { - return NULL; + const char *name = NULL; + + SDL_LockTouch(); + { + SDL_Touch *touch = SDL_GetTouch(id); + if (touch) { + name = SDL_GetPersistentString(touch->name); + } } - return SDL_GetPersistentString(touch->name); + SDL_UnlockTouch(); + + return name; } SDL_TouchDeviceType SDL_GetTouchDeviceType(SDL_TouchID id) { - SDL_Touch *touch = SDL_GetTouch(id); - return touch ? touch->type : SDL_TOUCH_DEVICE_INVALID; + SDL_TouchDeviceType type = SDL_TOUCH_DEVICE_INVALID; + + SDL_LockTouch(); + { + SDL_Touch *touch = SDL_GetTouch(id); + if (touch) { + type = touch->type; + } + } + SDL_UnlockTouch(); + + return type; } static int SDL_GetFingerIndex(const SDL_Touch *touch, SDL_FingerID fingerid) { + SDL_AssertTouchLocked(); + int index; for (index = 0; index < touch->num_fingers; ++index) { if (touch->fingers[index]->id == fingerid) { @@ -122,6 +192,8 @@ static int SDL_GetFingerIndex(const SDL_Touch *touch, SDL_FingerID fingerid) static SDL_Finger *SDL_GetFinger(const SDL_Touch *touch, SDL_FingerID id) { + SDL_AssertTouchLocked(); + int index = SDL_GetFingerIndex(touch, id); if (index < 0 || index >= touch->num_fingers) { return NULL; @@ -138,27 +210,34 @@ SDL_Finger **SDL_GetTouchFingers(SDL_TouchID touchID, int *count) *count = 0; } - SDL_Touch *touch = SDL_GetTouch(touchID); - if (!touch) { - return NULL; - } + SDL_LockTouch(); + { + SDL_Touch *touch = SDL_GetTouch(touchID); + if (!touch) { + SDL_UnlockTouch(); + return NULL; + } - // Create a snapshot of the current finger state - fingers = (SDL_Finger **)SDL_malloc((touch->num_fingers + 1) * sizeof(*fingers) + touch->num_fingers * sizeof(**fingers)); - if (!fingers) { - return NULL; - } - finger_data = (SDL_Finger *)(fingers + (touch->num_fingers + 1)); + // Create a snapshot of the current finger state + fingers = (SDL_Finger **)SDL_malloc((touch->num_fingers + 1) * sizeof(*fingers) + touch->num_fingers * sizeof(**fingers)); + if (!fingers) { + SDL_UnlockTouch(); + return NULL; + } + finger_data = (SDL_Finger *)(fingers + (touch->num_fingers + 1)); - for (int i = 0; i < touch->num_fingers; ++i) { - fingers[i] = &finger_data[i]; - SDL_copyp(fingers[i], touch->fingers[i]); - } - fingers[touch->num_fingers] = NULL; + for (int i = 0; i < touch->num_fingers; ++i) { + fingers[i] = &finger_data[i]; + SDL_copyp(fingers[i], touch->fingers[i]); + } + fingers[touch->num_fingers] = NULL; - if (count) { - *count = touch->num_fingers; + if (count) { + *count = touch->num_fingers; + } } + SDL_UnlockTouch(); + return fingers; } @@ -169,36 +248,43 @@ int SDL_AddTouch(SDL_TouchID touchID, SDL_TouchDeviceType type, const char *name SDL_assert(touchID != 0); - index = SDL_GetTouchIndex(touchID); - if (index >= 0) { - return index; + SDL_LockTouch(); + { + index = SDL_GetTouchIndex(touchID); + if (index >= 0) { + SDL_UnlockTouch(); + return index; + } + + // Add the touch to the list of touch + touchDevices = (SDL_Touch **)SDL_realloc(SDL_touchDevices, + (SDL_num_touch + 1) * sizeof(*touchDevices)); + if (!touchDevices) { + SDL_UnlockTouch(); + return -1; + } + + SDL_touchDevices = touchDevices; + index = SDL_num_touch; + + SDL_touchDevices[index] = (SDL_Touch *)SDL_malloc(sizeof(*SDL_touchDevices[index])); + if (!SDL_touchDevices[index]) { + SDL_UnlockTouch(); + return -1; + } + + // Added touch to list + ++SDL_num_touch; + + // we're setting the touch properties + SDL_touchDevices[index]->id = touchID; + SDL_touchDevices[index]->type = type; + SDL_touchDevices[index]->num_fingers = 0; + SDL_touchDevices[index]->max_fingers = 0; + SDL_touchDevices[index]->fingers = NULL; + SDL_touchDevices[index]->name = SDL_strdup(name ? name : ""); } - - // Add the touch to the list of touch - touchDevices = (SDL_Touch **)SDL_realloc(SDL_touchDevices, - (SDL_num_touch + 1) * sizeof(*touchDevices)); - if (!touchDevices) { - return -1; - } - - SDL_touchDevices = touchDevices; - index = SDL_num_touch; - - SDL_touchDevices[index] = (SDL_Touch *)SDL_malloc(sizeof(*SDL_touchDevices[index])); - if (!SDL_touchDevices[index]) { - return -1; - } - - // Added touch to list - ++SDL_num_touch; - - // we're setting the touch properties - SDL_touchDevices[index]->id = touchID; - SDL_touchDevices[index]->type = type; - SDL_touchDevices[index]->num_fingers = 0; - SDL_touchDevices[index]->max_fingers = 0; - SDL_touchDevices[index]->fingers = NULL; - SDL_touchDevices[index]->name = SDL_strdup(name ? name : ""); + SDL_UnlockTouch(); return index; } @@ -209,6 +295,8 @@ static bool SDL_AddFinger(SDL_Touch *touch, SDL_FingerID fingerid, float x, floa SDL_assert(fingerid != 0); + SDL_AssertTouchLocked(); + if (touch->num_fingers == touch->max_fingers) { SDL_Finger **new_fingers; new_fingers = (SDL_Finger **)SDL_realloc(touch->fingers, (touch->max_fingers + 1) * sizeof(*touch->fingers)); @@ -233,6 +321,8 @@ static bool SDL_AddFinger(SDL_Touch *touch, SDL_FingerID fingerid, float x, floa static void SDL_DelFinger(SDL_Touch *touch, SDL_FingerID fingerid) { + SDL_AssertTouchLocked(); + int index = SDL_GetFingerIndex(touch, fingerid); if (index < 0) { return; @@ -254,30 +344,159 @@ void SDL_SendTouch(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_ SDL_Finger *finger; bool down = (type == SDL_EVENT_FINGER_DOWN); - SDL_Touch *touch = SDL_GetTouch(id); - if (!touch) { - return; - } - - SDL_Mouse *mouse = SDL_GetMouse(); - - // SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events - // SDL_HINT_VITA_TOUCH_MOUSE_DEVICE: controlling which touchpad should generate synthetic mouse events, PSVita-only + SDL_LockTouch(); { - // FIXME: maybe we should only restrict to a few SDL_TouchDeviceType - if ((id != SDL_MOUSE_TOUCHID) && (id != SDL_PEN_TOUCHID)) { -#ifdef SDL_PLATFORM_VITA - if (mouse->touch_mouse_events && ((mouse->vita_touch_mouse_device == id) || (mouse->vita_touch_mouse_device == 3))) { -#else - if (mouse->touch_mouse_events) { -#endif - if (window) { + SDL_Touch *touch = SDL_GetTouch(id); + if (!touch) { + SDL_UnlockTouch(); + return; + } + + SDL_Mouse *mouse = SDL_GetMouse(); + + // SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events + // SDL_HINT_VITA_TOUCH_MOUSE_DEVICE: controlling which touchpad should generate synthetic mouse events, PSVita-only + { + // FIXME: maybe we should only restrict to a few SDL_TouchDeviceType + if ((id != SDL_MOUSE_TOUCHID) && (id != SDL_PEN_TOUCHID)) { + #ifdef SDL_PLATFORM_VITA + if (mouse->touch_mouse_events && ((mouse->vita_touch_mouse_device == id) || (mouse->vita_touch_mouse_device == 3))) { + #else + if (mouse->touch_mouse_events) { + #endif + if (window) { + if (down) { + if (finger_touching == false) { + float pos_x = (x * (float)window->w); + float pos_y = (y * (float)window->h); + if (pos_x < 0) { + pos_x = 0; + } + if (pos_x > (float)(window->w - 1)) { + pos_x = (float)(window->w - 1); + } + if (pos_y < 0.0f) { + pos_y = 0.0f; + } + if (pos_y > (float)(window->h - 1)) { + pos_y = (float)(window->h - 1); + } + SDL_SendMouseMotion(timestamp, window, SDL_TOUCH_MOUSEID, false, pos_x, pos_y); + SDL_SendMouseButton(timestamp, window, SDL_TOUCH_MOUSEID, SDL_BUTTON_LEFT, true); + } + } else { + if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) { + SDL_SendMouseButton(timestamp, window, SDL_TOUCH_MOUSEID, SDL_BUTTON_LEFT, false); + } + } + } if (down) { if (finger_touching == false) { + finger_touching = true; + track_touchid = id; + track_fingerid = fingerid; + } + } else { + if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) { + finger_touching = false; + } + } + } + } + } + + // SDL_HINT_MOUSE_TOUCH_EVENTS: if not set, discard synthetic touch events coming from platform layer + if (!mouse->mouse_touch_events && (id == SDL_MOUSE_TOUCHID)) { + SDL_UnlockTouch(); + return; + } else if (!mouse->pen_touch_events && (id == SDL_PEN_TOUCHID)) { + SDL_UnlockTouch(); + return; + } + + finger = SDL_GetFinger(touch, fingerid); + if (down) { + if (finger) { + /* This finger is already down. + Assume the finger-up for the previous touch was lost, and send it. */ + SDL_SendTouch(timestamp, id, fingerid, window, SDL_EVENT_FINGER_CANCELED, x, y, pressure); + } + + if (!SDL_AddFinger(touch, fingerid, x, y, pressure)) { + SDL_UnlockTouch(); + return; + } + + if (SDL_EventEnabled(type)) { + SDL_Event event; + event.type = type; + event.common.timestamp = timestamp; + event.tfinger.touchID = id; + event.tfinger.fingerID = fingerid; + event.tfinger.x = x; + event.tfinger.y = y; + event.tfinger.dx = 0; + event.tfinger.dy = 0; + event.tfinger.pressure = pressure; + event.tfinger.windowID = window ? SDL_GetWindowID(window) : 0; + SDL_PushEvent(&event); + } + } else { + if (!finger) { + // This finger is already up + SDL_UnlockTouch(); + return; + } + + if (SDL_EventEnabled(type)) { + SDL_Event event; + event.type = type; + event.common.timestamp = timestamp; + event.tfinger.touchID = id; + event.tfinger.fingerID = fingerid; + // I don't trust the coordinates passed on fingerUp + event.tfinger.x = finger->x; + event.tfinger.y = finger->y; + event.tfinger.dx = 0; + event.tfinger.dy = 0; + event.tfinger.pressure = pressure; + event.tfinger.windowID = window ? SDL_GetWindowID(window) : 0; + SDL_PushEvent(&event); + } + + SDL_DelFinger(touch, fingerid); + } + } + SDL_UnlockTouch(); +} + +void SDL_SendTouchMotion(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_Window *window, + float x, float y, float pressure) +{ + SDL_Touch *touch; + SDL_Finger *finger; + float xrel, yrel, prel; + + SDL_LockTouch(); + { + touch = SDL_GetTouch(id); + if (!touch) { + SDL_UnlockTouch(); + return; + } + + SDL_Mouse *mouse = SDL_GetMouse(); + + // SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events + { + if ((id != SDL_MOUSE_TOUCHID) && (id != SDL_PEN_TOUCHID)) { + if (mouse->touch_mouse_events) { + if (window) { + if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) { float pos_x = (x * (float)window->w); float pos_y = (y * (float)window->h); - if (pos_x < 0) { - pos_x = 0; + if (pos_x < 0.0f) { + pos_x = 0.0f; } if (pos_x > (float)(window->w - 1)) { pos_x = (float)(window->w - 1); @@ -289,174 +508,62 @@ void SDL_SendTouch(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_ pos_y = (float)(window->h - 1); } SDL_SendMouseMotion(timestamp, window, SDL_TOUCH_MOUSEID, false, pos_x, pos_y); - SDL_SendMouseButton(timestamp, window, SDL_TOUCH_MOUSEID, SDL_BUTTON_LEFT, true); } - } else { - if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) { - SDL_SendMouseButton(timestamp, window, SDL_TOUCH_MOUSEID, SDL_BUTTON_LEFT, false); - } - } - } - if (down) { - if (finger_touching == false) { - finger_touching = true; - track_touchid = id; - track_fingerid = fingerid; - } - } else { - if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) { - finger_touching = false; } } } } - } - // SDL_HINT_MOUSE_TOUCH_EVENTS: if not set, discard synthetic touch events coming from platform layer - if (!mouse->mouse_touch_events && (id == SDL_MOUSE_TOUCHID)) { - return; - } else if (!mouse->pen_touch_events && (id == SDL_PEN_TOUCHID)) { - return; - } - - finger = SDL_GetFinger(touch, fingerid); - if (down) { - if (finger) { - /* This finger is already down. - Assume the finger-up for the previous touch was lost, and send it. */ - SDL_SendTouch(timestamp, id, fingerid, window, SDL_EVENT_FINGER_CANCELED, x, y, pressure); + // SDL_HINT_MOUSE_TOUCH_EVENTS: if not set, discard synthetic touch events coming from platform layer + if (!mouse->mouse_touch_events) { + if (id == SDL_MOUSE_TOUCHID) { + SDL_UnlockTouch(); + return; + } } - if (!SDL_AddFinger(touch, fingerid, x, y, pressure)) { + finger = SDL_GetFinger(touch, fingerid); + if (!finger) { + SDL_SendTouch(timestamp, id, fingerid, window, SDL_EVENT_FINGER_DOWN, x, y, pressure); + SDL_UnlockTouch(); return; } - if (SDL_EventEnabled(type)) { + xrel = x - finger->x; + yrel = y - finger->y; + prel = pressure - finger->pressure; + + // Drop events that don't change state + if (xrel == 0.0f && yrel == 0.0f && prel == 0.0f) { + #if 0 + printf("Touch event didn't change state - dropped!\n"); + #endif + SDL_UnlockTouch(); + return; + } + + // Update internal touch coordinates + finger->x = x; + finger->y = y; + finger->pressure = pressure; + + // Post the event, if desired + if (SDL_EventEnabled(SDL_EVENT_FINGER_MOTION)) { SDL_Event event; - event.type = type; + event.type = SDL_EVENT_FINGER_MOTION; event.common.timestamp = timestamp; event.tfinger.touchID = id; event.tfinger.fingerID = fingerid; event.tfinger.x = x; event.tfinger.y = y; - event.tfinger.dx = 0; - event.tfinger.dy = 0; + event.tfinger.dx = xrel; + event.tfinger.dy = yrel; event.tfinger.pressure = pressure; event.tfinger.windowID = window ? SDL_GetWindowID(window) : 0; SDL_PushEvent(&event); } - } else { - if (!finger) { - // This finger is already up - return; - } - - if (SDL_EventEnabled(type)) { - SDL_Event event; - event.type = type; - event.common.timestamp = timestamp; - event.tfinger.touchID = id; - event.tfinger.fingerID = fingerid; - // I don't trust the coordinates passed on fingerUp - event.tfinger.x = finger->x; - event.tfinger.y = finger->y; - event.tfinger.dx = 0; - event.tfinger.dy = 0; - event.tfinger.pressure = pressure; - event.tfinger.windowID = window ? SDL_GetWindowID(window) : 0; - SDL_PushEvent(&event); - } - - SDL_DelFinger(touch, fingerid); - } -} - -void SDL_SendTouchMotion(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_Window *window, - float x, float y, float pressure) -{ - SDL_Touch *touch; - SDL_Finger *finger; - float xrel, yrel, prel; - - touch = SDL_GetTouch(id); - if (!touch) { - return; - } - - SDL_Mouse *mouse = SDL_GetMouse(); - - // SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events - { - if ((id != SDL_MOUSE_TOUCHID) && (id != SDL_PEN_TOUCHID)) { - if (mouse->touch_mouse_events) { - if (window) { - if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) { - float pos_x = (x * (float)window->w); - float pos_y = (y * (float)window->h); - if (pos_x < 0.0f) { - pos_x = 0.0f; - } - if (pos_x > (float)(window->w - 1)) { - pos_x = (float)(window->w - 1); - } - if (pos_y < 0.0f) { - pos_y = 0.0f; - } - if (pos_y > (float)(window->h - 1)) { - pos_y = (float)(window->h - 1); - } - SDL_SendMouseMotion(timestamp, window, SDL_TOUCH_MOUSEID, false, pos_x, pos_y); - } - } - } - } - } - - // SDL_HINT_MOUSE_TOUCH_EVENTS: if not set, discard synthetic touch events coming from platform layer - if (mouse->mouse_touch_events == 0) { - if (id == SDL_MOUSE_TOUCHID) { - return; - } - } - - finger = SDL_GetFinger(touch, fingerid); - if (!finger) { - SDL_SendTouch(timestamp, id, fingerid, window, SDL_EVENT_FINGER_DOWN, x, y, pressure); - return; - } - - xrel = x - finger->x; - yrel = y - finger->y; - prel = pressure - finger->pressure; - - // Drop events that don't change state - if (xrel == 0.0f && yrel == 0.0f && prel == 0.0f) { -#if 0 - printf("Touch event didn't change state - dropped!\n"); -#endif - return; - } - - // Update internal touch coordinates - finger->x = x; - finger->y = y; - finger->pressure = pressure; - - // Post the event, if desired - if (SDL_EventEnabled(SDL_EVENT_FINGER_MOTION)) { - SDL_Event event; - event.type = SDL_EVENT_FINGER_MOTION; - event.common.timestamp = timestamp; - event.tfinger.touchID = id; - event.tfinger.fingerID = fingerid; - event.tfinger.x = x; - event.tfinger.y = y; - event.tfinger.dx = xrel; - event.tfinger.dy = yrel; - event.tfinger.pressure = pressure; - event.tfinger.windowID = window ? SDL_GetWindowID(window) : 0; - SDL_PushEvent(&event); } + SDL_UnlockTouch(); } void SDL_DelTouch(SDL_TouchID id) @@ -464,37 +571,66 @@ void SDL_DelTouch(SDL_TouchID id) int i, index; SDL_Touch *touch; - if (SDL_num_touch == 0) { - // We've already cleaned up, we won't find this device - return; - } + SDL_LockTouch(); + { + if (SDL_num_touch == 0) { + // We've already cleaned up, we won't find this device + SDL_UnlockTouch(); + return; + } - index = SDL_GetTouchIndex(id); - touch = SDL_GetTouch(id); - if (!touch) { - return; - } + index = SDL_GetTouchIndex(id); + touch = SDL_GetTouch(id); + if (!touch) { + SDL_UnlockTouch(); + return; + } - for (i = 0; i < touch->max_fingers; ++i) { - SDL_free(touch->fingers[i]); - } - SDL_free(touch->fingers); - SDL_free(touch->name); - SDL_free(touch); + for (i = 0; i < touch->max_fingers; ++i) { + SDL_free(touch->fingers[i]); + } + SDL_free(touch->fingers); + SDL_free(touch->name); + SDL_free(touch); - SDL_num_touch--; - SDL_touchDevices[index] = SDL_touchDevices[SDL_num_touch]; + SDL_num_touch--; + SDL_touchDevices[index] = SDL_touchDevices[SDL_num_touch]; + } + SDL_UnlockTouch(); } void SDL_QuitTouch(void) { int i; - for (i = SDL_num_touch; i--;) { - SDL_DelTouch(SDL_touchDevices[i]->id); - } - SDL_assert(SDL_num_touch == 0); + SDL_LockTouch(); + { + for (i = SDL_num_touch; i--;) { + SDL_DelTouch(SDL_touchDevices[i]->id); + } + SDL_assert(SDL_num_touch == 0); - SDL_free(SDL_touchDevices); - SDL_touchDevices = NULL; + SDL_free(SDL_touchDevices); + SDL_touchDevices = NULL; + } + SDL_UnlockTouch(); + + SDL_DestroyMutex(SDL_touch_lock); + SDL_touch_lock = NULL; } + +int SDL_SendPinch(SDL_EventType type, Uint64 timestamp, SDL_Window *window, float scale) +{ + /* Post the event, if desired */ + int posted = 0; + if (SDL_EventEnabled(type)) { + SDL_Event event; + event.type = type; + event.common.timestamp = timestamp; + event.pinch.scale = scale; + event.pinch.windowID = window ? SDL_GetWindowID(window) : 0; + posted = (SDL_PushEvent(&event) > 0); + } + return posted; +} + diff --git a/libs/SDL3/src/events/SDL_touch_c.h b/libs/SDL3/src/events/SDL_touch_c.h index db2d64b..2744305 100644 --- a/libs/SDL3/src/events/SDL_touch_c.h +++ b/libs/SDL3/src/events/SDL_touch_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -23,15 +23,7 @@ #ifndef SDL_touch_c_h_ #define SDL_touch_c_h_ -typedef struct SDL_Touch -{ - SDL_TouchID id; - SDL_TouchDeviceType type; - int num_fingers; - int max_fingers; - SDL_Finger **fingers; - char *name; -} SDL_Touch; +typedef struct SDL_Touch SDL_Touch; // Initialize the touch subsystem extern bool SDL_InitTouch(void); @@ -57,4 +49,7 @@ extern void SDL_DelTouch(SDL_TouchID id); // Shutdown the touch subsystem extern void SDL_QuitTouch(void); +// Send Gesture events +extern int SDL_SendPinch(SDL_EventType type, Uint64 timestamp, SDL_Window *window, float scale); + #endif // SDL_touch_c_h_ diff --git a/libs/SDL3/src/events/SDL_windowevents.c b/libs/SDL3/src/events/SDL_windowevents.c index e20cd3a..1f8a961 100644 --- a/libs/SDL3/src/events/SDL_windowevents.c +++ b/libs/SDL3/src/events/SDL_windowevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -56,7 +56,7 @@ void SDL_RemoveWindowEventWatch(SDL_WindowEventWatchPriority priority, SDL_Event SDL_RemoveEventWatchList(&SDL_window_event_watchers[priority], filter, userdata); } -static bool SDLCALL RemoveSupercededWindowEvents(void *userdata, SDL_Event *event) +static bool SDLCALL RemoveSupersededWindowEvents(void *userdata, SDL_Event *event) { SDL_Event *new_event = (SDL_Event *)userdata; @@ -70,6 +70,8 @@ static bool SDLCALL RemoveSupercededWindowEvents(void *userdata, SDL_Event *even bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data1, int data2) { + SDL_VideoDevice *_this; + bool post_event = true; bool posted = false; if (!window) { @@ -77,9 +79,6 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data } SDL_assert(SDL_ObjectValid(window, SDL_OBJECT_TYPE_WINDOW)); - if (window->is_destroying && windowevent != SDL_EVENT_WINDOW_DESTROYED) { - return false; - } switch (windowevent) { case SDL_EVENT_WINDOW_SHOWN: if (!(window->flags & SDL_WINDOW_HIDDEN)) { @@ -99,6 +98,12 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data case SDL_EVENT_WINDOW_MOVED: window->undefined_x = false; window->undefined_y = false; + /* Clear the pending display if this move was not the result of an explicit request, + * and the window is not scheduled to become fullscreen when shown. + */ + if (!window->last_position_pending && !(window->pending_flags & SDL_WINDOW_FULLSCREEN)) { + window->pending_displayID = 0; + } window->last_position_pending = false; if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { window->windowed.x = data1; @@ -185,10 +190,11 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data window->flags &= ~SDL_WINDOW_INPUT_FOCUS; break; case SDL_EVENT_WINDOW_DISPLAY_CHANGED: - if (data1 == 0 || (SDL_DisplayID)data1 == window->last_displayID) { + if (data1 == 0 || (SDL_DisplayID)data1 == window->displayID) { return false; } - window->last_displayID = (SDL_DisplayID)data1; + window->update_fullscreen_on_display_changed = true; + window->displayID = (SDL_DisplayID)data1; break; case SDL_EVENT_WINDOW_OCCLUDED: if (window->flags & SDL_WINDOW_OCCLUDED) { @@ -212,6 +218,19 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data break; } + if (window->is_destroying && windowevent != SDL_EVENT_WINDOW_DESTROYED) { + return false; + } + + // Only post if we are not currently quitting + _this = SDL_GetVideoDevice(); + if (_this == NULL || _this->is_quitting) { + post_event = false; + } + + // The window might be destroyed in an event handler, so cache the topmost status first. + const bool window_is_topmost = !window->parent; + // Post the event, if desired SDL_Event event; event.type = windowevent; @@ -223,7 +242,7 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data 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)) { + if (post_event && SDL_EventEnabled(windowevent)) { // Fixes queue overflow with move/resize events that aren't processed if (windowevent == SDL_EVENT_WINDOW_MOVED || windowevent == SDL_EVENT_WINDOW_RESIZED || @@ -231,67 +250,71 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data windowevent == SDL_EVENT_WINDOW_SAFE_AREA_CHANGED || windowevent == SDL_EVENT_WINDOW_EXPOSED || windowevent == SDL_EVENT_WINDOW_OCCLUDED) { - SDL_FilterEvents(RemoveSupercededWindowEvents, &event); + SDL_FilterEvents(RemoveSupersededWindowEvents, &event); } posted = SDL_PushEvent(&event); } - switch (windowevent) { - case SDL_EVENT_WINDOW_SHOWN: - SDL_OnWindowShown(window); - break; - case SDL_EVENT_WINDOW_HIDDEN: - SDL_OnWindowHidden(window); - break; - case SDL_EVENT_WINDOW_MOVED: - SDL_OnWindowMoved(window); - break; - case SDL_EVENT_WINDOW_RESIZED: - SDL_OnWindowResized(window); - break; - case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: - SDL_OnWindowPixelSizeChanged(window); - break; - case SDL_EVENT_WINDOW_MINIMIZED: - SDL_OnWindowMinimized(window); - break; - case SDL_EVENT_WINDOW_MAXIMIZED: - SDL_OnWindowMaximized(window); - break; - case SDL_EVENT_WINDOW_RESTORED: - SDL_OnWindowRestored(window); - break; - case SDL_EVENT_WINDOW_MOUSE_ENTER: - SDL_OnWindowEnter(window); - break; - case SDL_EVENT_WINDOW_MOUSE_LEAVE: - SDL_OnWindowLeave(window); - break; - case SDL_EVENT_WINDOW_FOCUS_GAINED: - SDL_OnWindowFocusGained(window); - break; - case SDL_EVENT_WINDOW_FOCUS_LOST: - SDL_OnWindowFocusLost(window); - break; - case SDL_EVENT_WINDOW_DISPLAY_CHANGED: - SDL_OnWindowDisplayChanged(window); - break; - default: - break; + // Ensure that the window is still valid, as it may have been destroyed in an event handler. + window = SDL_GetWindowFromID(event.window.windowID); + + if (window) { + switch (windowevent) { + case SDL_EVENT_WINDOW_SHOWN: + SDL_OnWindowShown(window); + break; + case SDL_EVENT_WINDOW_HIDDEN: + SDL_OnWindowHidden(window); + break; + case SDL_EVENT_WINDOW_MOVED: + SDL_OnWindowMoved(window); + break; + case SDL_EVENT_WINDOW_RESIZED: + SDL_OnWindowResized(window); + break; + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: + SDL_OnWindowPixelSizeChanged(window); + break; + case SDL_EVENT_WINDOW_MINIMIZED: + SDL_OnWindowMinimized(window); + break; + case SDL_EVENT_WINDOW_MAXIMIZED: + SDL_OnWindowMaximized(window); + break; + case SDL_EVENT_WINDOW_RESTORED: + SDL_OnWindowRestored(window); + break; + case SDL_EVENT_WINDOW_MOUSE_ENTER: + SDL_OnWindowEnter(window); + break; + case SDL_EVENT_WINDOW_MOUSE_LEAVE: + SDL_OnWindowLeave(window); + break; + case SDL_EVENT_WINDOW_FOCUS_GAINED: + SDL_OnWindowFocusGained(window); + break; + case SDL_EVENT_WINDOW_FOCUS_LOST: + SDL_OnWindowFocusLost(window); + break; + case SDL_EVENT_WINDOW_DISPLAY_CHANGED: + SDL_OnWindowDisplayChanged(window); + break; + default: + break; + } } - if (windowevent == SDL_EVENT_WINDOW_CLOSE_REQUESTED && !window->parent && !SDL_HasActiveTrays()) { - int toplevel_count = 0; - SDL_Window *n; - for (n = SDL_GetVideoDevice()->windows; n; n = n->next) { + if (windowevent == SDL_EVENT_WINDOW_CLOSE_REQUESTED && window_is_topmost && !SDL_HasActiveTrays()) { + int count = window ? 0 : 1; + for (SDL_Window *n = _this->windows; n; n = n->next) { if (!n->parent && !(n->flags & SDL_WINDOW_HIDDEN)) { - ++toplevel_count; + ++count; } } - if (toplevel_count <= 1) { + if (count <= 1) { if (SDL_GetHintBoolean(SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE, true)) { - SDL_SendQuit(); // This is the last toplevel window in the list so send the SDL_EVENT_QUIT event + SDL_SendQuit(); // This is the last window in the list, so send the SDL_EVENT_QUIT event } } } diff --git a/libs/SDL3/src/events/SDL_windowevents_c.h b/libs/SDL3/src/events/SDL_windowevents_c.h index 7204305..942e154 100644 --- a/libs/SDL3/src/events/SDL_windowevents_c.h +++ b/libs/SDL3/src/events/SDL_windowevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -23,6 +23,11 @@ #ifndef SDL_windowevents_c_h_ #define SDL_windowevents_c_h_ +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { SDL_WINDOW_EVENT_WATCH_EARLY, @@ -36,4 +41,9 @@ extern void SDL_RemoveWindowEventWatch(SDL_WindowEventWatchPriority priority, SD extern bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data1, int data2); +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif + #endif // SDL_windowevents_c_h_ diff --git a/libs/SDL3/src/events/blank_cursor.h b/libs/SDL3/src/events/blank_cursor.h index d8d9b4d..14ac863 100644 --- a/libs/SDL3/src/events/blank_cursor.h +++ b/libs/SDL3/src/events/blank_cursor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/default_cursor.h b/libs/SDL3/src/events/default_cursor.h index 9a76f94..5cda7b6 100644 --- a/libs/SDL3/src/events/default_cursor.h +++ b/libs/SDL3/src/events/default_cursor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/scancodes_darwin.h b/libs/SDL3/src/events/scancodes_darwin.h index c3a2d4c..0ca4d55 100644 --- a/libs/SDL3/src/events/scancodes_darwin.h +++ b/libs/SDL3/src/events/scancodes_darwin.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/scancodes_linux.h b/libs/SDL3/src/events/scancodes_linux.h index 2deeb9a..3088024 100644 --- a/libs/SDL3/src/events/scancodes_linux.h +++ b/libs/SDL3/src/events/scancodes_linux.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/scancodes_windows.h b/libs/SDL3/src/events/scancodes_windows.h index 6f35114..834e2fa 100644 --- a/libs/SDL3/src/events/scancodes_windows.h +++ b/libs/SDL3/src/events/scancodes_windows.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/events/scancodes_xfree86.h b/libs/SDL3/src/events/scancodes_xfree86.h index 81d625e..cb0baf6 100644 --- a/libs/SDL3/src/events/scancodes_xfree86.h +++ b/libs/SDL3/src/events/scancodes_xfree86.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/filesystem/SDL_filesystem.c b/libs/SDL3/src/filesystem/SDL_filesystem.c index b115019..43d31e0 100644 --- a/libs/SDL3/src/filesystem/SDL_filesystem.c +++ b/libs/SDL3/src/filesystem/SDL_filesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -27,7 +27,7 @@ bool SDL_RemovePath(const char *path) { - if (!path) { + CHECK_PARAM(!path) { return SDL_InvalidParamError("path"); } return SDL_SYS_RemovePath(path); @@ -35,9 +35,10 @@ bool SDL_RemovePath(const char *path) bool SDL_RenamePath(const char *oldpath, const char *newpath) { - if (!oldpath) { + CHECK_PARAM(!oldpath) { return SDL_InvalidParamError("oldpath"); - } else if (!newpath) { + } + CHECK_PARAM(!newpath) { return SDL_InvalidParamError("newpath"); } return SDL_SYS_RenamePath(oldpath, newpath); @@ -45,9 +46,10 @@ bool SDL_RenamePath(const char *oldpath, const char *newpath) bool SDL_CopyFile(const char *oldpath, const char *newpath) { - if (!oldpath) { + CHECK_PARAM(!oldpath) { return SDL_InvalidParamError("oldpath"); - } else if (!newpath) { + } + CHECK_PARAM(!newpath) { return SDL_InvalidParamError("newpath"); } return SDL_SYS_CopyFile(oldpath, newpath); @@ -55,7 +57,7 @@ bool SDL_CopyFile(const char *oldpath, const char *newpath) bool SDL_CreateDirectory(const char *path) { - if (!path) { + CHECK_PARAM(!path) { return SDL_InvalidParamError("path"); } @@ -116,9 +118,10 @@ bool SDL_CreateDirectory(const char *path) bool SDL_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback callback, void *userdata) { - if (!path) { + CHECK_PARAM(!path) { return SDL_InvalidParamError("path"); - } else if (!callback) { + } + CHECK_PARAM(!callback) { return SDL_InvalidParamError("callback"); } return SDL_SYS_EnumerateDirectory(path, callback, userdata); @@ -133,10 +136,9 @@ bool SDL_GetPathInfo(const char *path, SDL_PathInfo *info) } SDL_zerop(info); - if (!path) { + CHECK_PARAM(!path) { return SDL_InvalidParamError("path"); } - return SDL_SYS_GetPathInfo(path, info); } @@ -364,7 +366,7 @@ char **SDL_InternalGlobDirectory(const char *path, const char *pattern, SDL_Glob } *count = 0; - if (!path) { + CHECK_PARAM(!path) { SDL_InvalidParamError("path"); return NULL; } @@ -488,7 +490,8 @@ static char *CachedUserFolders[SDL_FOLDER_COUNT]; const char *SDL_GetUserFolder(SDL_Folder folder) { const int idx = (int) folder; - if ((idx < 0) || (idx >= SDL_arraysize(CachedUserFolders))) { + + CHECK_PARAM((idx < 0) || (idx >= SDL_arraysize(CachedUserFolders))) { SDL_InvalidParamError("folder"); return NULL; } @@ -502,6 +505,16 @@ const char *SDL_GetUserFolder(SDL_Folder folder) char *SDL_GetPrefPath(const char *org, const char *app) { + CHECK_PARAM(!app) { + SDL_InvalidParamError("app"); + return NULL; + } + + // if org is NULL, just make it "" so backends don't have to check both. + if (!org) { + org = ""; + } + return SDL_SYS_GetPrefPath(org, app); } diff --git a/libs/SDL3/src/filesystem/SDL_filesystem_c.h b/libs/SDL3/src/filesystem/SDL_filesystem_c.h index 8cfdcab..685cda2 100644 --- a/libs/SDL3/src/filesystem/SDL_filesystem_c.h +++ b/libs/SDL3/src/filesystem/SDL_filesystem_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/filesystem/SDL_sysfilesystem.h b/libs/SDL3/src/filesystem/SDL_sysfilesystem.h index f9f4c59..660c712 100644 --- a/libs/SDL3/src/filesystem/SDL_sysfilesystem.h +++ b/libs/SDL3/src/filesystem/SDL_sysfilesystem.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/filesystem/android/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/android/SDL_sysfilesystem.c index bb42409..7eddd28 100644 --- a/libs/SDL3/src/filesystem/android/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/android/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -31,9 +31,7 @@ char *SDL_SYS_GetBasePath(void) { - // The current working directory is / on Android - SDL_Unsupported(); - return NULL; + return SDL_strdup("./"); } char *SDL_SYS_GetPrefPath(const char *org, const char *app) diff --git a/libs/SDL3/src/filesystem/cocoa/SDL_sysfilesystem.m b/libs/SDL3/src/filesystem/cocoa/SDL_sysfilesystem.m index 5818764..26ee7cc 100644 --- a/libs/SDL3/src/filesystem/cocoa/SDL_sysfilesystem.m +++ b/libs/SDL3/src/filesystem/cocoa/SDL_sysfilesystem.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -69,14 +69,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) char *result = NULL; NSArray *array; - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - if (!org) { - org = ""; - } - #ifndef SDL_PLATFORM_TVOS array = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); #else @@ -106,13 +98,12 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) const size_t len = SDL_strlen(base) + SDL_strlen(org) + SDL_strlen(app) + 4; result = (char *)SDL_malloc(len); if (result != NULL) { - char *ptr; if (*org) { SDL_snprintf(result, len, "%s/%s/%s/", base, org, app); } else { SDL_snprintf(result, len, "%s/%s/", base, app); } - for (ptr = result + 1; *ptr; ptr++) { + for (char *ptr = result + 1; *ptr; ptr++) { if (*ptr == '/') { *ptr = '\0'; mkdir(result, 0700); @@ -136,7 +127,7 @@ char *SDL_SYS_GetUserFolder(SDL_Folder folder) return NULL; #else char *result = NULL; - const char* base; + const char *base; NSArray *array; NSSearchPathDirectory dir; NSString *str; diff --git a/libs/SDL3/src/filesystem/dummy/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/dummy/SDL_sysfilesystem.c index 5634c70..53bc50f 100644 --- a/libs/SDL3/src/filesystem/dummy/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/dummy/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -47,8 +47,12 @@ char *SDL_SYS_GetUserFolder(SDL_Folder folder) char *SDL_SYS_GetCurrentDirectory(void) { - SDL_Unsupported(); - return NULL; + const char *base = SDL_GetBasePath(); + if (!base) { + return NULL; + } + + return SDL_strdup(base); } #endif // SDL_FILESYSTEM_DUMMY || SDL_FILESYSTEM_DISABLED diff --git a/libs/SDL3/src/filesystem/dummy/SDL_sysfsops.c b/libs/SDL3/src/filesystem/dummy/SDL_sysfsops.c index d8553e9..ce8ca29 100644 --- a/libs/SDL3/src/filesystem/dummy/SDL_sysfsops.c +++ b/libs/SDL3/src/filesystem/dummy/SDL_sysfsops.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/filesystem/emscripten/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/emscripten/SDL_sysfilesystem.c index 29dc053..13427fc 100644 --- a/libs/SDL3/src/filesystem/emscripten/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/emscripten/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -42,17 +42,7 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) const char *append = "/libsdl/"; char *result; char *ptr = NULL; - size_t len = 0; - - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - if (!org) { - org = ""; - } - - len = SDL_strlen(append) + SDL_strlen(org) + SDL_strlen(app) + 3; + const size_t len = SDL_strlen(append) + SDL_strlen(org) + SDL_strlen(app) + 3; result = (char *)SDL_malloc(len); if (!result) { return NULL; diff --git a/libs/SDL3/src/filesystem/gdk/SDL_sysfilesystem.cpp b/libs/SDL3/src/filesystem/gdk/SDL_sysfilesystem.cpp index ffafe43..83abfad 100644 --- a/libs/SDL3/src/filesystem/gdk/SDL_sysfilesystem.cpp +++ b/libs/SDL3/src/filesystem/gdk/SDL_sysfilesystem.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -90,11 +90,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) HRESULT result; const char *csid = SDL_GetHint("SDL_GDK_SERVICE_CONFIGURATION_ID"); - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - // This should be set before calling SDL_GetPrefPath! if (!csid) { SDL_LogWarn(SDL_LOG_CATEGORY_SYSTEM, "Set SDL_GDK_SERVICE_CONFIGURATION_ID before calling SDL_GetPrefPath!"); @@ -111,7 +106,7 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) return NULL; } - folderPath = (char*) SDL_malloc(MAX_PATH); + folderPath = (char *)SDL_malloc(MAX_PATH); do { result = XGameSaveFilesGetFolderWithUiResult(&block, MAX_PATH, folderPath); } while (result == E_PENDING); @@ -142,9 +137,12 @@ char *SDL_SYS_GetUserFolder(SDL_Folder folder) return NULL; } -// TODO char *SDL_SYS_GetCurrentDirectory(void) { - SDL_Unsupported(); - return NULL; + const char *base = SDL_GetBasePath(); + if (!base) { + return NULL; + } + + return SDL_strdup(base); } diff --git a/libs/SDL3/src/filesystem/haiku/SDL_sysfilesystem.cc b/libs/SDL3/src/filesystem/haiku/SDL_sysfilesystem.cc index af8b5ab..380e05c 100644 --- a/libs/SDL3/src/filesystem/haiku/SDL_sysfilesystem.cc +++ b/libs/SDL3/src/filesystem/haiku/SDL_sysfilesystem.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -72,14 +72,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) const char *append = "/config/settings/"; size_t len = SDL_strlen(home); - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - if (!org) { - org = ""; - } - if (!len || (home[len - 1] == '/')) { ++append; // home empty or ends with separator, skip the one from append } diff --git a/libs/SDL3/src/filesystem/n3ds/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/n3ds/SDL_sysfilesystem.c index 8386a91..66dba9d 100644 --- a/libs/SDL3/src/filesystem/n3ds/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/n3ds/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -43,11 +43,6 @@ char *SDL_SYS_GetBasePath(void) char *SDL_SYS_GetPrefPath(const char *org, const char *app) { char *pref_path = NULL; - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - pref_path = MakePrefPath(app); if (!pref_path) { return NULL; diff --git a/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.c new file mode 100644 index 0000000..a7d5ea8 --- /dev/null +++ b/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.c @@ -0,0 +1,67 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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" + +extern void NGAGE_GetAppPath(char *path); + +char *SDL_SYS_GetBasePath(void) +{ + char app_path[512]; + NGAGE_GetAppPath(app_path); + char *base_path = SDL_strdup(app_path); + return base_path; +} + +char *SDL_SYS_GetPrefPath(const char *org, const char *app) +{ + char *pref_path = NULL; + if (SDL_asprintf(&pref_path, "C:/System/Apps/%s/%s/", org ? org : "SDL_App", app) < 0) { + return NULL; + } + return pref_path; +} + +char *SDL_SYS_GetUserFolder(SDL_Folder folder) +{ + const char *folder_path = NULL; + switch (folder) + { + case SDL_FOLDER_HOME: + folder_path = "C:/"; + break; + case SDL_FOLDER_PICTURES: + folder_path = "C:/Nokia/Pictures/"; + break; + case SDL_FOLDER_SAVEDGAMES: + folder_path = "C:/"; + break; + case SDL_FOLDER_SCREENSHOTS: + folder_path = "C:/Nokia/Pictures/"; + break; + case SDL_FOLDER_VIDEOS: + folder_path = "C:/Nokia/Videos/"; + break; + default: + folder_path = "C:/Nokia/Others/"; + break; + } + return SDL_strdup(folder_path); +} diff --git a/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.cpp b/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.cpp new file mode 100644 index 0000000..622c82e --- /dev/null +++ b/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.cpp @@ -0,0 +1,68 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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. +*/ +#ifdef __cplusplus +extern "C" { +#endif + +#include "SDL_internal.h" + +#ifdef __cplusplus +} +#endif + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void NGAGE_GetAppPath(char *path) +{ + TBuf<512> aPath; + + TFileName fullExePath = RProcess().FileName(); + + TParsePtrC parser(fullExePath); + aPath.Copy(parser.DriveAndPath()); + + TBuf8<512> utf8Path; // Temporary buffer for UTF-8 data. + CnvUtfConverter::ConvertFromUnicodeToUtf8(utf8Path, aPath); + + // Copy UTF-8 data to the provided char* buffer. + strncpy(path, (const char *)utf8Path.Ptr(), utf8Path.Length()); + path[utf8Path.Length()] = '\0'; + + // Replace backslashes with forward slashes. + for (int i = 0; i < utf8Path.Length(); i++) + { + if (path[i] == '\\') + { + path[i] = '/'; + } + } +} + +#ifdef __cplusplus +} +#endif diff --git a/libs/SDL3/src/filesystem/posix/SDL_sysfsops.c b/libs/SDL3/src/filesystem/posix/SDL_sysfsops.c index 015b8d4..1ab9f80 100644 --- a/libs/SDL3/src/filesystem/posix/SDL_sysfsops.c +++ b/libs/SDL3/src/filesystem/posix/SDL_sysfsops.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -35,30 +35,70 @@ #include #include +#ifdef SDL_PLATFORM_ANDROID +#include "../../core/android/SDL_android.h" +#endif + + bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata) { + char *apath = NULL; // absolute path (for Android, iOS, etc). Overrides `path`. + +#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_IOS) + if (*path != '/') { + #ifdef SDL_PLATFORM_ANDROID + SDL_asprintf(&apath, "%s/%s", SDL_GetAndroidInternalStoragePath(), path); + #elif defined(SDL_PLATFORM_IOS) + char *base = SDL_GetPrefPath("", ""); + if (!base) { + return false; + } + + SDL_asprintf(&apath, "%s%s", base, path); + SDL_free(base); + #endif + + if (!apath) { + return false; + } + } +#elif 0 // this is just for testing that `apath` works when you aren't on iOS or Android. + if (*path != '/') { + char *c = SDL_SYS_GetCurrentDirectory(); + SDL_asprintf(&apath, "%s%s", c, path); + SDL_free(c); + if (!apath) { + return false; + } + } +#endif + char *pathwithsep = NULL; - int pathwithseplen = SDL_asprintf(&pathwithsep, "%s/", path); + int pathwithseplen = SDL_asprintf(&pathwithsep, "%s/", apath ? apath : path); + const size_t extralen = apath ? (SDL_strlen(apath) - SDL_strlen(path)) : 0; + SDL_free(apath); if ((pathwithseplen == -1) || (!pathwithsep)) { return false; } // trim down to a single path separator at the end, in case the caller added one or more. pathwithseplen--; - while ((pathwithseplen >= 0) && (pathwithsep[pathwithseplen] == '/')) { + while ((pathwithseplen > 0) && (pathwithsep[pathwithseplen - 1] == '/')) { pathwithsep[pathwithseplen--] = '\0'; } DIR *dir = opendir(pathwithsep); if (!dir) { +#ifdef SDL_PLATFORM_ANDROID // Maybe it's an asset...? + const bool retval = Android_JNI_EnumerateAssetDirectory(pathwithsep + extralen, cb, userdata); + SDL_free(pathwithsep); + return retval; +#else SDL_free(pathwithsep); return SDL_SetError("Can't open directory: %s", strerror(errno)); +#endif } - // make sure there's a path separator at the end now for the actual callback. - pathwithsep[++pathwithseplen] = '/'; - pathwithsep[++pathwithseplen] = '\0'; - SDL_EnumerationResult result = SDL_ENUM_CONTINUE; struct dirent *ent; while ((result == SDL_ENUM_CONTINUE) && ((ent = readdir(dir)) != NULL)) { @@ -66,7 +106,7 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback if ((SDL_strcmp(name, ".") == 0) || (SDL_strcmp(name, "..") == 0)) { continue; } - result = cb(userdata, pathwithsep, name); + result = cb(userdata, pathwithsep + extralen, name); } closedir(dir); @@ -78,7 +118,41 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback bool SDL_SYS_RemovePath(const char *path) { - int rc = remove(path); + int rc; + +#ifdef SDL_PLATFORM_ANDROID + if (*path == '/') { + rc = remove(path); + } else { + char *apath = NULL; + SDL_asprintf(&apath, "%s/%s", SDL_GetAndroidInternalStoragePath(), path); + if (!apath) { + return false; + } + rc = remove(apath); + SDL_free(apath); + } +#elif defined(SDL_PLATFORM_IOS) + if (*path == '/') { + rc = remove(path); + } else { + char *base = SDL_GetPrefPath("", ""); + if (!base) { + return false; + } + + char *apath = NULL; + SDL_asprintf(&apath, "%s%s", base, path); + SDL_free(base); + if (!apath) { + return false; + } + rc = remove(apath); + SDL_free(apath); + } +#else + rc = remove(path); +#endif if (rc < 0) { if (errno == ENOENT) { // It's already gone, this is a success @@ -91,7 +165,65 @@ bool SDL_SYS_RemovePath(const char *path) bool SDL_SYS_RenamePath(const char *oldpath, const char *newpath) { - if (rename(oldpath, newpath) < 0) { + int rc; + +#ifdef SDL_PLATFORM_ANDROID + char *aoldpath = NULL; + char *anewpath = NULL; + if (*oldpath != '/') { + SDL_asprintf(&aoldpath, "%s/%s", SDL_GetAndroidInternalStoragePath(), oldpath); + if (!aoldpath) { + return false; + } + oldpath = aoldpath; + } + if (*newpath != '/') { + SDL_asprintf(&anewpath, "%s/%s", SDL_GetAndroidInternalStoragePath(), newpath); + if (!anewpath) { + SDL_free(aoldpath); + return false; + } + newpath = anewpath; + } + rc = rename(oldpath, newpath); + SDL_free(aoldpath); + SDL_free(anewpath); +#elif defined(SDL_PLATFORM_IOS) + char *base = NULL; + if (*oldpath != '/' || *newpath != '/') { + base = SDL_GetPrefPath("", ""); + if (!base) { + return false; + } + } + + char *aoldpath = NULL; + char *anewpath = NULL; + if (*oldpath != '/') { + SDL_asprintf(&aoldpath, "%s%s", base, oldpath); + if (!aoldpath) { + SDL_free(base); + return false; + } + oldpath = aoldpath; + } + if (*newpath != '/') { + SDL_asprintf(&anewpath, "%s%s", base, newpath); + if (!anewpath) { + SDL_free(base); + SDL_free(aoldpath); + return false; + } + newpath = anewpath; + } + rc = rename(oldpath, newpath); + SDL_free(base); + SDL_free(aoldpath); + SDL_free(anewpath); +#else + rc = rename(oldpath, newpath); +#endif + if (rc < 0) { return SDL_SetError("Can't rename path: %s", strerror(errno)); } return true; @@ -154,7 +286,41 @@ done: bool SDL_SYS_CreateDirectory(const char *path) { - const int rc = mkdir(path, 0770); + int rc; + +#ifdef SDL_PLATFORM_ANDROID + if (*path == '/') { + rc = mkdir(path, 0770); + } else { + char *apath = NULL; + SDL_asprintf(&apath, "%s/%s", SDL_GetAndroidInternalStoragePath(), path); + if (!apath) { + return false; + } + rc = mkdir(apath, 0770); + SDL_free(apath); + } +#elif defined(SDL_PLATFORM_IOS) + if (*path == '/') { + rc = mkdir(path, 0770); + } else { + char *base = SDL_GetPrefPath("", ""); + if (!base) { + return false; + } + + char *apath = NULL; + SDL_asprintf(&apath, "%s%s", base, path); + SDL_free(base); + if (!apath) { + return false; + } + rc = mkdir(apath, 0770); + SDL_free(apath); + } +#else + rc = mkdir(path, 0770); +#endif if (rc < 0) { const int origerrno = errno; if (origerrno == EEXIST) { @@ -171,7 +337,48 @@ bool SDL_SYS_CreateDirectory(const char *path) bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info) { struct stat statbuf; - const int rc = stat(path, &statbuf); + int rc; + +#ifdef SDL_PLATFORM_ANDROID + if (*path == '/') { + rc = stat(path, &statbuf); + } else { + char *apath = NULL; + SDL_asprintf(&apath, "%s/%s", SDL_GetAndroidInternalStoragePath(), path); + if (!apath) { + return false; + } + rc = stat(apath, &statbuf); + SDL_free(apath); + } + if (rc < 0) { + return Android_JNI_GetAssetPathInfo(path, info); + } +#elif defined(SDL_PLATFORM_IOS) + if (*path == '/') { + rc = stat(path, &statbuf); + } else { + char *base = SDL_GetPrefPath("", ""); + if (!base) { + return false; + } + + char *apath = NULL; + SDL_asprintf(&apath, "%s%s", base, path); + SDL_free(base); + if (!apath) { + return false; + } + rc = stat(apath, &statbuf); + SDL_free(apath); + + if (rc < 0) { + rc = stat(path, &statbuf); + } + } +#else + rc = stat(path, &statbuf); +#endif if (rc < 0) { return SDL_SetError("Can't stat: %s", strerror(errno)); } else if (S_ISREG(statbuf.st_mode)) { @@ -203,7 +410,7 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info) return true; } -// Note that this isn't actually part of filesystem, not fsops, but everything that uses posix fsops uses this implementation, even with separate filesystem code. +// Note that this is actually part of filesystem, not fsops, but everything that uses posix fsops uses this implementation, even with separate filesystem code. char *SDL_SYS_GetCurrentDirectory(void) { size_t buflen = 64; @@ -243,4 +450,3 @@ char *SDL_SYS_GetCurrentDirectory(void) } #endif // SDL_FSOPS_POSIX - diff --git a/libs/SDL3/src/filesystem/ps2/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/ps2/SDL_sysfilesystem.c index ca69c2b..87b44c9 100644 --- a/libs/SDL3/src/filesystem/ps2/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/ps2/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -80,15 +80,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) char *result = NULL; size_t len; - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - - if (!org) { - org = ""; - } - const char *base = SDL_GetBasePath(); if (!base) { return NULL; @@ -102,7 +93,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) } else { SDL_snprintf(result, len, "%s%s/", base, app); } - recursive_mkdir(result); } diff --git a/libs/SDL3/src/filesystem/psp/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/psp/SDL_sysfilesystem.c index 4b40055..18cd9d9 100644 --- a/libs/SDL3/src/filesystem/psp/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/psp/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -49,22 +49,12 @@ char *SDL_SYS_GetBasePath(void) char *SDL_SYS_GetPrefPath(const char *org, const char *app) { char *result = NULL; - size_t len; - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - const char *base = SDL_GetBasePath(); if (!base) { return NULL; } - if (!org) { - org = ""; - } - - len = SDL_strlen(base) + SDL_strlen(org) + SDL_strlen(app) + 4; + const size_t len = SDL_strlen(base) + SDL_strlen(org) + SDL_strlen(app) + 4; result = (char *)SDL_malloc(len); if (result) { if (*org) { @@ -72,7 +62,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) } else { SDL_snprintf(result, len, "%s%s/", base, app); } - mkdir(result, 0755); } diff --git a/libs/SDL3/src/filesystem/riscos/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/riscos/SDL_sysfilesystem.c index 95ed80f..d52b22a 100644 --- a/libs/SDL3/src/filesystem/riscos/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/riscos/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -155,23 +155,14 @@ char *SDL_SYS_GetBasePath(void) char *SDL_SYS_GetPrefPath(const char *org, const char *app) { char *canon, *dir, *result; - size_t len; _kernel_oserror *error; - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - if (!org) { - org = ""; - } - canon = canonicalisePath("", "Run$Path"); if (!canon) { return NULL; } - len = SDL_strlen(canon) + SDL_strlen(org) + SDL_strlen(app) + 4; + const size_t len = SDL_strlen(canon) + SDL_strlen(org) + SDL_strlen(app) + 4; dir = (char *)SDL_malloc(len); if (!dir) { SDL_free(canon); diff --git a/libs/SDL3/src/filesystem/unix/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/unix/SDL_sysfilesystem.c index b0f2dd5..9edbf6d 100644 --- a/libs/SDL3/src/filesystem/unix/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/unix/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -78,7 +78,7 @@ static char *search_path_for_binary(const char *bin) char *envr; size_t alloc_size; char *exe = NULL; - char *start = envr; + char *start; char *ptr; if (!envr_real) { @@ -86,7 +86,7 @@ static char *search_path_for_binary(const char *bin) return NULL; } - envr = SDL_strdup(envr_real); + start = envr = SDL_strdup(envr_real); if (!envr) { return NULL; } @@ -269,15 +269,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) const char *append; char *result = NULL; char *ptr = NULL; - size_t len = 0; - - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - if (!org) { - org = ""; - } if (!envr) { // You end up with "$HOME/.local/share/Game Name 2" @@ -292,7 +283,7 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) append = "/"; } - len = SDL_strlen(envr); + size_t len = SDL_strlen(envr); if (envr[len - 1] == '/') { append += 1; } @@ -377,7 +368,7 @@ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fa if (!config_home || config_home[0] == 0) { l = SDL_strlen (home_dir) + SDL_strlen ("/.config/user-dirs.dirs") + 1; - config_file = (char*) SDL_malloc (l); + config_file = (char *)SDL_malloc (l); if (!config_file) goto error; @@ -387,7 +378,7 @@ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fa else { l = SDL_strlen (config_home) + SDL_strlen ("/user-dirs.dirs") + 1; - config_file = (char*) SDL_malloc (l); + config_file = (char *)SDL_malloc (l); if (!config_file) goto error; @@ -449,7 +440,7 @@ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fa if (relative) { l = SDL_strlen (home_dir) + 1 + SDL_strlen (p) + 1; - user_dir = (char*) SDL_malloc (l); + user_dir = (char *)SDL_malloc (l); if (!user_dir) goto error2; @@ -458,7 +449,7 @@ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fa } else { - user_dir = (char*) SDL_malloc (SDL_strlen (p) + 1); + user_dir = (char *)SDL_malloc (SDL_strlen (p) + 1); if (!user_dir) goto error2; @@ -503,7 +494,7 @@ static char *xdg_user_dir_lookup (const char *type) // Special case desktop for historical compatibility if (SDL_strcmp(type, "DESKTOP") == 0) { size_t length = SDL_strlen(home_dir) + SDL_strlen("/Desktop") + 1; - user_dir = (char*) SDL_malloc(length); + user_dir = (char *)SDL_malloc(length); if (!user_dir) return NULL; diff --git a/libs/SDL3/src/filesystem/vita/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/vita/SDL_sysfilesystem.c index 8b65e8a..a23fcdc 100644 --- a/libs/SDL3/src/filesystem/vita/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/vita/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -46,19 +46,7 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) const char *envr = "ux0:/data/"; char *result = NULL; char *ptr = NULL; - size_t len = 0; - - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - if (!org) { - org = ""; - } - - len = SDL_strlen(envr); - - len += SDL_strlen(org) + SDL_strlen(app) + 3; + size_t len = SDL_strlen(envr) + SDL_strlen(org) + SDL_strlen(app) + 3; result = (char *)SDL_malloc(len); if (!result) { return NULL; diff --git a/libs/SDL3/src/filesystem/windows/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/windows/SDL_sysfilesystem.c index 39ba414..f2c59f4 100644 --- a/libs/SDL3/src/filesystem/windows/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/windows/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -110,14 +110,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) size_t new_wpath_len = 0; BOOL api_result = FALSE; - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - if (!org) { - org = ""; - } - hr = SHGetFolderPathW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, path); if (!SUCCEEDED(hr)) { WIN_SetErrorFromHRESULT("Couldn't locate our prefpath", hr); @@ -181,7 +173,7 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) char *SDL_SYS_GetUserFolder(SDL_Folder folder) { typedef HRESULT (WINAPI *pfnSHGetKnownFolderPath)(REFGUID /* REFKNOWNFOLDERID */, DWORD, HANDLE, PWSTR*); - HMODULE lib = LoadLibrary(L"Shell32.dll"); + HMODULE lib = LoadLibraryW(L"Shell32.dll"); pfnSHGetKnownFolderPath pSHGetKnownFolderPath = NULL; char *result = NULL; diff --git a/libs/SDL3/src/filesystem/windows/SDL_sysfsops.c b/libs/SDL3/src/filesystem/windows/SDL_sysfsops.c index 9c48ba9..f0fc0fd 100644 --- a/libs/SDL3/src/filesystem/windows/SDL_sysfsops.c +++ b/libs/SDL3/src/filesystem/windows/SDL_sysfsops.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -29,6 +29,10 @@ #include "../../core/windows/SDL_windows.h" #include "../SDL_sysfilesystem.h" +#ifndef COPY_FILE_NO_BUFFERING +#define COPY_FILE_NO_BUFFERING 0x00001000 +#endif + bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata) { SDL_EnumerationResult result = SDL_ENUM_CONTINUE; @@ -53,7 +57,7 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback // trim down to a single path separator at the end, in case the caller added one or more. patternlen--; - while ((patternlen >= 0) && ((pattern[patternlen] == '\\') || (pattern[patternlen] == '/'))) { + while ((patternlen > 0) && ((pattern[patternlen] == '\\') || (pattern[patternlen] == '/'))) { pattern[patternlen--] ='\0'; } pattern[++patternlen] = '\\'; diff --git a/libs/SDL3/src/gpu/SDL_gpu.c b/libs/SDL3/src/gpu/SDL_gpu.c index aea7ed5..9a68d9c 100644 --- a/libs/SDL3/src/gpu/SDL_gpu.c +++ b/libs/SDL3/src/gpu/SDL_gpu.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -23,7 +23,7 @@ // FIXME: This could probably use SDL_ObjectValid #define CHECK_DEVICE_MAGIC(device, retval) \ - if (device == NULL) { \ + CHECK_PARAM(device == NULL) { \ SDL_SetError("Invalid GPU device"); \ return retval; \ } @@ -61,6 +61,10 @@ return; \ } +#if 0 +// The below validation is too aggressive, since there are advanced situations +// where this is legal. This is being temporarily disabled for further review. +// See: https://github.com/libsdl-org/SDL/issues/13871 #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) { \ @@ -92,6 +96,10 @@ SDL_assert_release(!"Texture cannot be simultaneously bound as a depth stencil target and a storage texture!"); \ } \ } +#else +#define CHECK_SAMPLER_TEXTURES +#define CHECK_STORAGE_TEXTURES +#endif #define CHECK_GRAPHICS_PIPELINE_BOUND \ if (!((RenderPass *)render_pass)->graphics_pipeline) { \ @@ -310,11 +318,11 @@ static const SDL_GPUBootstrap *backends[] = { #ifdef SDL_GPU_METAL &MetalDriver, #endif -#ifdef SDL_GPU_VULKAN - &VulkanDriver, -#endif #ifdef SDL_GPU_D3D12 &D3D12Driver, +#endif +#ifdef SDL_GPU_VULKAN + &VulkanDriver, #endif NULL }; @@ -375,6 +383,7 @@ SDL_GPUGraphicsPipeline *SDL_GPU_FetchBlitPipeline( } else { blit_pipeline_create_info.fragment_shader = blit_from_2d_shader; } + blit_pipeline_create_info.rasterizer_state.enable_depth_clip = device->default_enable_depth_clip; blit_pipeline_create_info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1; blit_pipeline_create_info.multisample_state.enable_mask = false; @@ -590,7 +599,6 @@ static void SDL_GPU_CheckComputeBindings(SDL_GPUComputePass *compute_pass) static const SDL_GPUBootstrap * SDL_GPUSelectBackend(SDL_PropertiesID props) { Uint32 i; - SDL_GPUShaderFormat format_flags = 0; const char *gpudriver; SDL_VideoDevice *_this = SDL_GetVideoDevice(); @@ -599,25 +607,6 @@ static const SDL_GPUBootstrap * SDL_GPUSelectBackend(SDL_PropertiesID props) return NULL; } - if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOLEAN, false)) { - format_flags |= SDL_GPU_SHADERFORMAT_PRIVATE; - } - if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, false)) { - format_flags |= SDL_GPU_SHADERFORMAT_SPIRV; - } - if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN, false)) { - format_flags |= SDL_GPU_SHADERFORMAT_DXBC; - } - if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN, false)) { - format_flags |= SDL_GPU_SHADERFORMAT_DXIL; - } - if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN, false)) { - format_flags |= SDL_GPU_SHADERFORMAT_MSL; - } - if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN, false)) { - format_flags |= SDL_GPU_SHADERFORMAT_METALLIB; - } - gpudriver = SDL_GetHint(SDL_HINT_GPU_DRIVER); if (gpudriver == NULL) { gpudriver = SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, NULL); @@ -627,11 +616,7 @@ static const SDL_GPUBootstrap * SDL_GPUSelectBackend(SDL_PropertiesID props) if (gpudriver != NULL) { for (i = 0; backends[i]; i += 1) { if (SDL_strcasecmp(gpudriver, backends[i]->name) == 0) { - if (!(backends[i]->shader_formats & format_flags)) { - SDL_SetError("Required shader format for backend %s not provided!", gpudriver); - return NULL; - } - if (backends[i]->PrepareDriver(_this)) { + if (backends[i]->PrepareDriver(_this, props)) { return backends[i]; } } @@ -642,11 +627,7 @@ static const SDL_GPUBootstrap * SDL_GPUSelectBackend(SDL_PropertiesID props) } for (i = 0; backends[i]; i += 1) { - if ((backends[i]->shader_formats & format_flags) == 0) { - // Don't select a backend which doesn't support the app's shaders. - continue; - } - if (backends[i]->PrepareDriver(_this)) { + if (backends[i]->PrepareDriver(_this, props)) { return backends[i]; } } @@ -739,6 +720,7 @@ SDL_GPUDevice *SDL_CreateGPUDeviceWithProperties(SDL_PropertiesID props) selectedBackend = SDL_GPUSelectBackend(props); if (selectedBackend != NULL) { + SDL_DebugLogBackend("gpu", selectedBackend->name); debug_mode = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, true); preferLowPower = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, false); @@ -746,6 +728,15 @@ SDL_GPUDevice *SDL_CreateGPUDeviceWithProperties(SDL_PropertiesID props) if (result != NULL) { result->backend = selectedBackend->name; result->debug_mode = debug_mode; + if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, true)) { + result->default_enable_depth_clip = false; + } else { + result->default_enable_depth_clip = true; + result->validate_feature_depth_clamp_disabled = true; + } + if (!SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN, true)) { + result->validate_feature_anisotropy_disabled = true; + } } } return result; @@ -758,7 +749,6 @@ SDL_GPUDevice *SDL_CreateGPUDeviceWithProperties(SDL_PropertiesID props) void SDL_DestroyGPUDevice(SDL_GPUDevice *device) { CHECK_DEVICE_MAGIC(device, ); - device->DestroyDevice(device); } @@ -773,7 +763,7 @@ int SDL_GetNumGPUDrivers(void) const char * SDL_GetGPUDriver(int index) { - if (index < 0 || index >= SDL_GetNumGPUDrivers()) { + CHECK_PARAM(index < 0 || index >= SDL_GetNumGPUDrivers()) { SDL_InvalidParamError("index"); return NULL; } @@ -787,17 +777,21 @@ const char * SDL_GetGPUDriver(int index) const char * SDL_GetGPUDeviceDriver(SDL_GPUDevice *device) { CHECK_DEVICE_MAGIC(device, NULL); - return device->backend; } SDL_GPUShaderFormat SDL_GetGPUShaderFormats(SDL_GPUDevice *device) { CHECK_DEVICE_MAGIC(device, SDL_GPU_SHADERFORMAT_INVALID); - return device->shader_formats; } +SDL_PropertiesID SDL_GetGPUDeviceProperties(SDL_GPUDevice *device) +{ + CHECK_DEVICE_MAGIC(device, 0); + return device->GetDeviceProperties(device); +} + Uint32 SDL_GPUTextureFormatTexelBlockSize( SDL_GPUTextureFormat format) { @@ -971,6 +965,7 @@ SDL_GPUComputePipeline *SDL_CreateGPUComputePipeline( const SDL_GPUComputePipelineCreateInfo *createinfo) { CHECK_DEVICE_MAGIC(device, NULL); + if (createinfo == NULL) { SDL_InvalidParamError("createinfo"); return NULL; @@ -986,13 +981,35 @@ SDL_GPUComputePipeline *SDL_CreateGPUComputePipeline( return NULL; } if (createinfo->num_readwrite_storage_textures > MAX_COMPUTE_WRITE_TEXTURES) { + SDL_COMPILE_TIME_ASSERT(compute_write_textures, MAX_COMPUTE_WRITE_TEXTURES == 8); SDL_assert_release(!"Compute pipeline write-only texture count cannot be higher than 8!"); return NULL; } if (createinfo->num_readwrite_storage_buffers > MAX_COMPUTE_WRITE_BUFFERS) { + SDL_COMPILE_TIME_ASSERT(compute_write_buffers, MAX_COMPUTE_WRITE_BUFFERS == 8); SDL_assert_release(!"Compute pipeline write-only buffer count cannot be higher than 8!"); return NULL; } + if (createinfo->num_samplers > MAX_TEXTURE_SAMPLERS_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(compute_texture_samplers, MAX_TEXTURE_SAMPLERS_PER_STAGE == 16); + SDL_assert_release(!"Compute pipeline sampler count cannot be higher than 16!"); + return NULL; + } + if (createinfo->num_readonly_storage_textures > MAX_STORAGE_TEXTURES_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(compute_storage_textures, MAX_STORAGE_TEXTURES_PER_STAGE == 8); + SDL_assert_release(!"Compute pipeline readonly storage texture count cannot be higher than 8!"); + return NULL; + } + if (createinfo->num_readonly_storage_buffers > MAX_STORAGE_BUFFERS_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(compute_storage_buffers, MAX_STORAGE_BUFFERS_PER_STAGE == 8); + SDL_assert_release(!"Compute pipeline readonly storage buffer count cannot be higher than 8!"); + return NULL; + } + if (createinfo->num_uniform_buffers > MAX_UNIFORM_BUFFERS_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(compute_uniform_buffers, MAX_UNIFORM_BUFFERS_PER_STAGE == 4); + SDL_assert_release(!"Compute pipeline uniform buffer count cannot be higher than 4!"); + return NULL; + } if (createinfo->threadcount_x == 0 || createinfo->threadcount_y == 0 || createinfo->threadcount_z == 0) { @@ -1011,7 +1028,8 @@ SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline( const SDL_GPUGraphicsPipelineCreateInfo *graphicsPipelineCreateInfo) { CHECK_DEVICE_MAGIC(device, NULL); - if (graphicsPipelineCreateInfo == NULL) { + + CHECK_PARAM(graphicsPipelineCreateInfo == NULL) { SDL_InvalidParamError("graphicsPipelineCreateInfo"); return NULL; } @@ -1047,6 +1065,8 @@ SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline( CHECK_BLENDFACTOR_ENUM_INVALID(blend_state->src_alpha_blendfactor, NULL) CHECK_BLENDFACTOR_ENUM_INVALID(blend_state->dst_alpha_blendfactor, NULL) CHECK_BLENDOP_ENUM_INVALID(blend_state->alpha_blend_op, NULL) + + // TODO: validate that format support blending? } } if (graphicsPipelineCreateInfo->target_info.has_depth_stencil_target) { @@ -1060,11 +1080,24 @@ SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline( return NULL; } } + if (graphicsPipelineCreateInfo->multisample_state.enable_alpha_to_coverage) { + if (graphicsPipelineCreateInfo->target_info.num_color_targets < 1) { + SDL_assert_release(!"Alpha-to-coverage enabled but no color targets present!"); + return NULL; + } + if (!FormatHasAlpha(graphicsPipelineCreateInfo->target_info.color_target_descriptions[0].format)) { + SDL_assert_release(!"Format is not compatible with alpha-to-coverage!"); + return NULL; + } + + // TODO: validate that format supports belnding? This is only required on Metal. + } if (graphicsPipelineCreateInfo->vertex_input_state.num_vertex_buffers > 0 && graphicsPipelineCreateInfo->vertex_input_state.vertex_buffer_descriptions == NULL) { SDL_assert_release(!"Vertex buffer descriptions array pointer cannot be NULL!"); return NULL; } if (graphicsPipelineCreateInfo->vertex_input_state.num_vertex_buffers > MAX_VERTEX_BUFFERS) { + SDL_COMPILE_TIME_ASSERT(vertex_buffers, MAX_VERTEX_BUFFERS == 16); SDL_assert_release(!"The number of vertex buffer descriptions in a vertex input state must not exceed 16!"); return NULL; } @@ -1073,6 +1106,7 @@ SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline( return NULL; } if (graphicsPipelineCreateInfo->vertex_input_state.num_vertex_attributes > MAX_VERTEX_ATTRIBUTES) { + SDL_COMPILE_TIME_ASSERT(vertex_attributes, MAX_VERTEX_ATTRIBUTES == 16); SDL_assert_release(!"The number of vertex attributes in a vertex input state must not exceed 16!"); return NULL; } @@ -1112,6 +1146,12 @@ SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline( CHECK_STENCILOP_ENUM_INVALID(stencil_state->pass_op, NULL) CHECK_STENCILOP_ENUM_INVALID(stencil_state->depth_fail_op, NULL) } + + if (device->validate_feature_depth_clamp_disabled && + !graphicsPipelineCreateInfo->rasterizer_state.enable_depth_clip) { + SDL_assert_release(!"Rasterizer state enable_depth_clip must be set to true (FEATURE_DEPTH_CLAMPING disabled)"); + return NULL; + } } return device->CreateGraphicsPipeline( @@ -1124,11 +1164,20 @@ SDL_GPUSampler *SDL_CreateGPUSampler( const SDL_GPUSamplerCreateInfo *createinfo) { CHECK_DEVICE_MAGIC(device, NULL); - if (createinfo == NULL) { + + CHECK_PARAM(createinfo == NULL) { SDL_InvalidParamError("createinfo"); return NULL; } + if (device->debug_mode) { + if (device->validate_feature_anisotropy_disabled && + createinfo->enable_anisotropy) { + SDL_assert_release(!"enable_anisotropy must be set to false (FEATURE_ANISOTROPY disabled)"); + return NULL; + } + } + return device->CreateSampler( device->driverData, createinfo); @@ -1139,7 +1188,8 @@ SDL_GPUShader *SDL_CreateGPUShader( const SDL_GPUShaderCreateInfo *createinfo) { CHECK_DEVICE_MAGIC(device, NULL); - if (createinfo == NULL) { + + CHECK_PARAM(createinfo == NULL) { SDL_InvalidParamError("createinfo"); return NULL; } @@ -1153,6 +1203,26 @@ SDL_GPUShader *SDL_CreateGPUShader( SDL_assert_release(!"Incompatible shader format for GPU backend"); return NULL; } + if (createinfo->num_samplers > MAX_TEXTURE_SAMPLERS_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(shader_texture_samplers, MAX_TEXTURE_SAMPLERS_PER_STAGE == 16); + SDL_assert_release(!"Shader sampler count cannot be higher than 16!"); + return NULL; + } + if (createinfo->num_storage_textures > MAX_STORAGE_TEXTURES_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(shader_storage_textures, MAX_STORAGE_TEXTURES_PER_STAGE == 8); + SDL_assert_release(!"Shader storage texture count cannot be higher than 8!"); + return NULL; + } + if (createinfo->num_storage_buffers > MAX_STORAGE_BUFFERS_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(shader_storage_buffers, MAX_STORAGE_BUFFERS_PER_STAGE == 8); + SDL_assert_release(!"Shader storage buffer count cannot be higher than 8!"); + return NULL; + } + if (createinfo->num_uniform_buffers > MAX_UNIFORM_BUFFERS_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(shader_uniform_buffers, MAX_UNIFORM_BUFFERS_PER_STAGE == 4); + SDL_assert_release(!"Shader uniform buffer count cannot be higher than 4!"); + return NULL; + } } return device->CreateShader( @@ -1165,7 +1235,8 @@ SDL_GPUTexture *SDL_CreateGPUTexture( const SDL_GPUTextureCreateInfo *createinfo) { CHECK_DEVICE_MAGIC(device, NULL); - if (createinfo == NULL) { + + CHECK_PARAM(createinfo == NULL) { SDL_InvalidParamError("createinfo"); return NULL; } @@ -1307,11 +1378,18 @@ SDL_GPUBuffer *SDL_CreateGPUBuffer( const SDL_GPUBufferCreateInfo *createinfo) { CHECK_DEVICE_MAGIC(device, NULL); - if (createinfo == NULL) { + + CHECK_PARAM(createinfo == NULL) { SDL_InvalidParamError("createinfo"); return NULL; } + if (device->debug_mode) { + if (createinfo->size < 4) { + SDL_assert_release(!"Cannot create a buffer with size less than 4 bytes!"); + } + } + const char *debugName = SDL_GetStringProperty(createinfo->props, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, NULL); return device->CreateBuffer( @@ -1326,7 +1404,8 @@ SDL_GPUTransferBuffer *SDL_CreateGPUTransferBuffer( const SDL_GPUTransferBufferCreateInfo *createinfo) { CHECK_DEVICE_MAGIC(device, NULL); - if (createinfo == NULL) { + + CHECK_PARAM(createinfo == NULL) { SDL_InvalidParamError("createinfo"); return NULL; } @@ -1348,11 +1427,12 @@ void SDL_SetGPUBufferName( const char *text) { CHECK_DEVICE_MAGIC(device, ); - if (buffer == NULL) { + + CHECK_PARAM(buffer == NULL) { SDL_InvalidParamError("buffer"); return; } - if (text == NULL) { + CHECK_PARAM(text == NULL) { SDL_InvalidParamError("text"); } @@ -1368,11 +1448,12 @@ void SDL_SetGPUTextureName( const char *text) { CHECK_DEVICE_MAGIC(device, ); - if (texture == NULL) { + + CHECK_PARAM(texture == NULL) { SDL_InvalidParamError("texture"); return; } - if (text == NULL) { + CHECK_PARAM(text == NULL) { SDL_InvalidParamError("text"); } @@ -1386,11 +1467,11 @@ void SDL_InsertGPUDebugLabel( SDL_GPUCommandBuffer *command_buffer, const char *text) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } - if (text == NULL) { + CHECK_PARAM(text == NULL) { SDL_InvalidParamError("text"); return; } @@ -1408,11 +1489,11 @@ void SDL_PushGPUDebugGroup( SDL_GPUCommandBuffer *command_buffer, const char *name) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } - if (name == NULL) { + CHECK_PARAM(name == NULL) { SDL_InvalidParamError("name"); return; } @@ -1429,7 +1510,7 @@ void SDL_PushGPUDebugGroup( void SDL_PopGPUDebugGroup( SDL_GPUCommandBuffer *command_buffer) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } @@ -1449,7 +1530,8 @@ void SDL_ReleaseGPUTexture( SDL_GPUTexture *texture) { CHECK_DEVICE_MAGIC(device, ); - if (texture == NULL) { + + CHECK_PARAM(texture == NULL) { return; } @@ -1463,7 +1545,8 @@ void SDL_ReleaseGPUSampler( SDL_GPUSampler *sampler) { CHECK_DEVICE_MAGIC(device, ); - if (sampler == NULL) { + + CHECK_PARAM(sampler == NULL) { return; } @@ -1477,7 +1560,8 @@ void SDL_ReleaseGPUBuffer( SDL_GPUBuffer *buffer) { CHECK_DEVICE_MAGIC(device, ); - if (buffer == NULL) { + + CHECK_PARAM(buffer == NULL) { return; } @@ -1491,7 +1575,8 @@ void SDL_ReleaseGPUTransferBuffer( SDL_GPUTransferBuffer *transfer_buffer) { CHECK_DEVICE_MAGIC(device, ); - if (transfer_buffer == NULL) { + + CHECK_PARAM(transfer_buffer == NULL) { return; } @@ -1505,7 +1590,8 @@ void SDL_ReleaseGPUShader( SDL_GPUShader *shader) { CHECK_DEVICE_MAGIC(device, ); - if (shader == NULL) { + + CHECK_PARAM(shader == NULL) { return; } @@ -1519,7 +1605,8 @@ void SDL_ReleaseGPUComputePipeline( SDL_GPUComputePipeline *compute_pipeline) { CHECK_DEVICE_MAGIC(device, ); - if (compute_pipeline == NULL) { + + CHECK_PARAM(compute_pipeline == NULL) { return; } @@ -1533,7 +1620,8 @@ void SDL_ReleaseGPUGraphicsPipeline( SDL_GPUGraphicsPipeline *graphics_pipeline) { CHECK_DEVICE_MAGIC(device, ); - if (graphics_pipeline == NULL) { + + CHECK_PARAM(graphics_pipeline == NULL) { return; } @@ -1598,14 +1686,18 @@ void SDL_PushGPUVertexUniformData( const void *data, Uint32 length) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } - if (data == NULL) { + CHECK_PARAM(data == NULL) { SDL_InvalidParamError("data"); return; } + CHECK_PARAM(slot_index >= MAX_UNIFORM_BUFFERS_PER_STAGE) { + SDL_SetError("slot_index exceeds MAX_UNIFORM_BUFFERS_PER_STAGE"); + return; + } if (COMMAND_BUFFER_DEVICE->debug_mode) { CHECK_COMMAND_BUFFER @@ -1624,14 +1716,18 @@ void SDL_PushGPUFragmentUniformData( const void *data, Uint32 length) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } - if (data == NULL) { + CHECK_PARAM(data == NULL) { SDL_InvalidParamError("data"); return; } + CHECK_PARAM(slot_index >= MAX_UNIFORM_BUFFERS_PER_STAGE) { + SDL_SetError("slot_index exceeds MAX_UNIFORM_BUFFERS_PER_STAGE"); + return; + } if (COMMAND_BUFFER_DEVICE->debug_mode) { CHECK_COMMAND_BUFFER @@ -1650,14 +1746,18 @@ void SDL_PushGPUComputeUniformData( const void *data, Uint32 length) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } - if (data == NULL) { + CHECK_PARAM(data == NULL) { SDL_InvalidParamError("data"); return; } + CHECK_PARAM(slot_index >= MAX_UNIFORM_BUFFERS_PER_STAGE) { + SDL_SetError("slot_index exceeds MAX_UNIFORM_BUFFERS_PER_STAGE"); + return; + } if (COMMAND_BUFFER_DEVICE->debug_mode) { CHECK_COMMAND_BUFFER @@ -1680,16 +1780,16 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( { CommandBufferCommonHeader *commandBufferHeader; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return NULL; } - if (color_target_infos == NULL && num_color_targets > 0) { + CHECK_PARAM(color_target_infos == NULL && num_color_targets > 0) { SDL_InvalidParamError("color_target_infos"); return NULL; } - if (num_color_targets > MAX_COLOR_TARGET_BINDINGS) { + CHECK_PARAM(num_color_targets > MAX_COLOR_TARGET_BINDINGS) { SDL_SetError("num_color_targets exceeds MAX_COLOR_TARGET_BINDINGS"); return NULL; } @@ -1747,13 +1847,17 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( } if (depth_stencil_target_info != NULL) { - 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 (textureHeader->info.layer_count_or_depth > 255) { + SDL_assert_release(!"Cannot bind a depth texture with more than 255 layers!"); + 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; @@ -1785,6 +1889,8 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( 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; + } else { + commandBufferHeader->render_pass.depth_stencil_target = NULL; } } @@ -1795,11 +1901,11 @@ void SDL_BindGPUGraphicsPipeline( SDL_GPURenderPass *render_pass, SDL_GPUGraphicsPipeline *graphics_pipeline) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (graphics_pipeline == NULL) { + CHECK_PARAM(graphics_pipeline == NULL) { SDL_InvalidParamError("graphics_pipeline"); return; } @@ -1818,11 +1924,11 @@ void SDL_SetGPUViewport( SDL_GPURenderPass *render_pass, const SDL_GPUViewport *viewport) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (viewport == NULL) { + CHECK_PARAM(viewport == NULL) { SDL_InvalidParamError("viewport"); return; } @@ -1840,11 +1946,11 @@ void SDL_SetGPUScissor( SDL_GPURenderPass *render_pass, const SDL_Rect *scissor) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (scissor == NULL) { + CHECK_PARAM(scissor == NULL) { SDL_InvalidParamError("scissor"); return; } @@ -1862,7 +1968,7 @@ void SDL_SetGPUBlendConstants( SDL_GPURenderPass *render_pass, SDL_FColor blend_constants) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } @@ -1880,7 +1986,7 @@ void SDL_SetGPUStencilReference( SDL_GPURenderPass *render_pass, Uint8 reference) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } @@ -1900,7 +2006,7 @@ void SDL_BindGPUVertexBuffers( const SDL_GPUBufferBinding *bindings, Uint32 num_bindings) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } @@ -1925,7 +2031,7 @@ void SDL_BindGPUIndexBuffer( const SDL_GPUBufferBinding *binding, SDL_GPUIndexElementSize index_element_size) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } @@ -1950,19 +2056,23 @@ void SDL_BindGPUVertexSamplers( const SDL_GPUTextureSamplerBinding *texture_sampler_bindings, Uint32 num_bindings) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (texture_sampler_bindings == NULL && num_bindings > 0) { + CHECK_PARAM(texture_sampler_bindings == NULL && num_bindings > 0) { SDL_InvalidParamError("texture_sampler_bindings"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_TEXTURE_SAMPLERS_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_TEXTURE_SAMPLERS_PER_STAGE"); + return; + } if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS - if (!((CommandBufferCommonHeader*)RENDERPASS_COMMAND_BUFFER)->ignore_render_pass_texture_validation) + if (!((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->ignore_render_pass_texture_validation) { CHECK_SAMPLER_TEXTURES } @@ -1985,14 +2095,18 @@ void SDL_BindGPUVertexStorageTextures( SDL_GPUTexture *const *storage_textures, Uint32 num_bindings) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (storage_textures == NULL && num_bindings > 0) { + CHECK_PARAM(storage_textures == NULL && num_bindings > 0) { SDL_InvalidParamError("storage_textures"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_STORAGE_TEXTURES_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_STORAGE_TEXTURES_PER_STAGE"); + return; + } if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS @@ -2016,14 +2130,18 @@ void SDL_BindGPUVertexStorageBuffers( SDL_GPUBuffer *const *storage_buffers, Uint32 num_bindings) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (storage_buffers == NULL && num_bindings > 0) { + CHECK_PARAM(storage_buffers == NULL && num_bindings > 0) { SDL_InvalidParamError("storage_buffers"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_STORAGE_BUFFERS_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_STORAGE_BUFFERS_PER_STAGE"); + return; + } if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS @@ -2046,19 +2164,23 @@ void SDL_BindGPUFragmentSamplers( const SDL_GPUTextureSamplerBinding *texture_sampler_bindings, Uint32 num_bindings) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (texture_sampler_bindings == NULL && num_bindings > 0) { + CHECK_PARAM(texture_sampler_bindings == NULL && num_bindings > 0) { SDL_InvalidParamError("texture_sampler_bindings"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_TEXTURE_SAMPLERS_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_TEXTURE_SAMPLERS_PER_STAGE"); + return; + } if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS - if (!((CommandBufferCommonHeader*)RENDERPASS_COMMAND_BUFFER)->ignore_render_pass_texture_validation) { + if (!((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->ignore_render_pass_texture_validation) { CHECK_SAMPLER_TEXTURES } @@ -2080,14 +2202,18 @@ void SDL_BindGPUFragmentStorageTextures( SDL_GPUTexture *const *storage_textures, Uint32 num_bindings) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (storage_textures == NULL && num_bindings > 0) { + CHECK_PARAM(storage_textures == NULL && num_bindings > 0) { SDL_InvalidParamError("storage_textures"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_STORAGE_TEXTURES_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_STORAGE_TEXTURES_PER_STAGE"); + return; + } if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS @@ -2111,14 +2237,18 @@ void SDL_BindGPUFragmentStorageBuffers( SDL_GPUBuffer *const *storage_buffers, Uint32 num_bindings) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (storage_buffers == NULL && num_bindings > 0) { + CHECK_PARAM(storage_buffers == NULL && num_bindings > 0) { SDL_InvalidParamError("storage_buffers"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_STORAGE_BUFFERS_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_STORAGE_BUFFERS_PER_STAGE"); + return; + } if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS @@ -2143,7 +2273,7 @@ void SDL_DrawGPUIndexedPrimitives( Sint32 vertex_offset, Uint32 first_instance) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } @@ -2170,7 +2300,7 @@ void SDL_DrawGPUPrimitives( Uint32 first_vertex, Uint32 first_instance) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } @@ -2195,11 +2325,11 @@ void SDL_DrawGPUPrimitivesIndirect( Uint32 offset, Uint32 draw_count) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (buffer == NULL) { + CHECK_PARAM(buffer == NULL) { SDL_InvalidParamError("buffer"); return; } @@ -2223,11 +2353,11 @@ void SDL_DrawGPUIndexedPrimitivesIndirect( Uint32 offset, Uint32 draw_count) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (buffer == NULL) { + CHECK_PARAM(buffer == NULL) { SDL_InvalidParamError("buffer"); return; } @@ -2248,7 +2378,7 @@ void SDL_DrawGPUIndexedPrimitivesIndirect( void SDL_EndGPURenderPass( SDL_GPURenderPass *render_pass) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } @@ -2292,26 +2422,27 @@ SDL_GPUComputePass *SDL_BeginGPUComputePass( { CommandBufferCommonHeader *commandBufferHeader; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return NULL; } - if (storage_texture_bindings == NULL && num_storage_texture_bindings > 0) { + CHECK_PARAM(storage_texture_bindings == NULL && num_storage_texture_bindings > 0) { SDL_InvalidParamError("storage_texture_bindings"); return NULL; } - if (storage_buffer_bindings == NULL && num_storage_buffer_bindings > 0) { + CHECK_PARAM(storage_buffer_bindings == NULL && num_storage_buffer_bindings > 0) { SDL_InvalidParamError("storage_buffer_bindings"); return NULL; } - if (num_storage_texture_bindings > MAX_COMPUTE_WRITE_TEXTURES) { + CHECK_PARAM(num_storage_texture_bindings > MAX_COMPUTE_WRITE_TEXTURES) { SDL_InvalidParamError("num_storage_texture_bindings"); return NULL; } - if (num_storage_buffer_bindings > MAX_COMPUTE_WRITE_BUFFERS) { + CHECK_PARAM(num_storage_buffer_bindings > MAX_COMPUTE_WRITE_BUFFERS) { SDL_InvalidParamError("num_storage_buffer_bindings"); return NULL; } + if (COMMAND_BUFFER_DEVICE->debug_mode) { CHECK_COMMAND_BUFFER_RETURN_NULL CHECK_ANY_PASS_IN_PROGRESS("Cannot begin compute pass during another pass!", NULL) @@ -2365,11 +2496,11 @@ void SDL_BindGPUComputePipeline( SDL_GPUComputePass *compute_pass, SDL_GPUComputePipeline *compute_pipeline) { - if (compute_pass == NULL) { + CHECK_PARAM(compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; } - if (compute_pipeline == NULL) { + CHECK_PARAM(compute_pipeline == NULL) { SDL_InvalidParamError("compute_pipeline"); return; } @@ -2394,14 +2525,18 @@ void SDL_BindGPUComputeSamplers( const SDL_GPUTextureSamplerBinding *texture_sampler_bindings, Uint32 num_bindings) { - if (compute_pass == NULL) { + CHECK_PARAM(compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; } - if (texture_sampler_bindings == NULL && num_bindings > 0) { + CHECK_PARAM(texture_sampler_bindings == NULL && num_bindings > 0) { SDL_InvalidParamError("texture_sampler_bindings"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_TEXTURE_SAMPLERS_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_TEXTURE_SAMPLERS_PER_STAGE"); + return; + } if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS @@ -2424,14 +2559,18 @@ void SDL_BindGPUComputeStorageTextures( SDL_GPUTexture *const *storage_textures, Uint32 num_bindings) { - if (compute_pass == NULL) { + CHECK_PARAM(compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; } - if (storage_textures == NULL && num_bindings > 0) { + CHECK_PARAM(storage_textures == NULL && num_bindings > 0) { SDL_InvalidParamError("storage_textures"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_STORAGE_TEXTURES_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_STORAGE_TEXTURES_PER_STAGE"); + return; + } if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS @@ -2454,14 +2593,18 @@ void SDL_BindGPUComputeStorageBuffers( SDL_GPUBuffer *const *storage_buffers, Uint32 num_bindings) { - if (compute_pass == NULL) { + CHECK_PARAM(compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; } - if (storage_buffers == NULL && num_bindings > 0) { + CHECK_PARAM(storage_buffers == NULL && num_bindings > 0) { SDL_InvalidParamError("storage_buffers"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_STORAGE_BUFFERS_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_STORAGE_BUFFERS_PER_STAGE"); + return; + } if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS @@ -2484,7 +2627,7 @@ void SDL_DispatchGPUCompute( Uint32 groupcount_y, Uint32 groupcount_z) { - if (compute_pass == NULL) { + CHECK_PARAM(compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; } @@ -2507,7 +2650,7 @@ void SDL_DispatchGPUComputeIndirect( SDL_GPUBuffer *buffer, Uint32 offset) { - if (compute_pass == NULL) { + CHECK_PARAM(compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; } @@ -2529,7 +2672,7 @@ void SDL_EndGPUComputePass( { CommandBufferCommonHeader *commandBufferCommonHeader; - if (compute_pass == NULL) { + CHECK_PARAM(compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; } @@ -2561,7 +2704,8 @@ void *SDL_MapGPUTransferBuffer( bool cycle) { CHECK_DEVICE_MAGIC(device, NULL); - if (transfer_buffer == NULL) { + + CHECK_PARAM(transfer_buffer == NULL) { SDL_InvalidParamError("transfer_buffer"); return NULL; } @@ -2577,7 +2721,8 @@ void SDL_UnmapGPUTransferBuffer( SDL_GPUTransferBuffer *transfer_buffer) { CHECK_DEVICE_MAGIC(device, ); - if (transfer_buffer == NULL) { + + CHECK_PARAM(transfer_buffer == NULL) { SDL_InvalidParamError("transfer_buffer"); return; } @@ -2594,7 +2739,7 @@ SDL_GPUCopyPass *SDL_BeginGPUCopyPass( { CommandBufferCommonHeader *commandBufferHeader; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return NULL; } @@ -2622,11 +2767,11 @@ void SDL_UploadToGPUTexture( const SDL_GPUTextureRegion *destination, bool cycle) { - if (copy_pass == NULL) { + CHECK_PARAM(copy_pass == NULL) { SDL_InvalidParamError("copy_pass"); return; } - if (source == NULL) { + CHECK_PARAM(source == NULL) { SDL_InvalidParamError("source"); return; } @@ -2660,15 +2805,15 @@ void SDL_UploadToGPUBuffer( const SDL_GPUBufferRegion *destination, bool cycle) { - if (copy_pass == NULL) { + CHECK_PARAM(copy_pass == NULL) { SDL_InvalidParamError("copy_pass"); return; } - if (source == NULL) { + CHECK_PARAM(source == NULL) { SDL_InvalidParamError("source"); return; } - if (destination == NULL) { + CHECK_PARAM(destination == NULL) { SDL_InvalidParamError("destination"); return; } @@ -2701,15 +2846,15 @@ void SDL_CopyGPUTextureToTexture( Uint32 d, bool cycle) { - if (copy_pass == NULL) { + CHECK_PARAM(copy_pass == NULL) { SDL_InvalidParamError("copy_pass"); return; } - if (source == NULL) { + CHECK_PARAM(source == NULL) { SDL_InvalidParamError("source"); return; } - if (destination == NULL) { + CHECK_PARAM(destination == NULL) { SDL_InvalidParamError("destination"); return; } @@ -2724,6 +2869,13 @@ void SDL_CopyGPUTextureToTexture( SDL_assert_release(!"Destination texture cannot be NULL!"); return; } + + TextureCommonHeader *srcHeader = (TextureCommonHeader *)source->texture; + TextureCommonHeader *dstHeader = (TextureCommonHeader *)destination->texture; + if (srcHeader->info.format != dstHeader->info.format) { + SDL_assert_release(!"Source and destination textures must have the same format!"); + return; + } } COPYPASS_DEVICE->CopyTextureToTexture( @@ -2743,15 +2895,15 @@ void SDL_CopyGPUBufferToBuffer( Uint32 size, bool cycle) { - if (copy_pass == NULL) { + CHECK_PARAM(copy_pass == NULL) { SDL_InvalidParamError("copy_pass"); return; } - if (source == NULL) { + CHECK_PARAM(source == NULL) { SDL_InvalidParamError("source"); return; } - if (destination == NULL) { + CHECK_PARAM(destination == NULL) { SDL_InvalidParamError("destination"); return; } @@ -2781,15 +2933,15 @@ void SDL_DownloadFromGPUTexture( const SDL_GPUTextureRegion *source, const SDL_GPUTextureTransferInfo *destination) { - if (copy_pass == NULL) { + CHECK_PARAM(copy_pass == NULL) { SDL_InvalidParamError("copy_pass"); return; } - if (source == NULL) { + CHECK_PARAM(source == NULL) { SDL_InvalidParamError("source"); return; } - if (destination == NULL) { + CHECK_PARAM(destination == NULL) { SDL_InvalidParamError("destination"); return; } @@ -2817,15 +2969,15 @@ void SDL_DownloadFromGPUBuffer( const SDL_GPUBufferRegion *source, const SDL_GPUTransferBufferLocation *destination) { - if (copy_pass == NULL) { + CHECK_PARAM(copy_pass == NULL) { SDL_InvalidParamError("copy_pass"); return; } - if (source == NULL) { + CHECK_PARAM(source == NULL) { SDL_InvalidParamError("source"); return; } - if (destination == NULL) { + CHECK_PARAM(destination == NULL) { SDL_InvalidParamError("destination"); return; } @@ -2851,7 +3003,7 @@ void SDL_DownloadFromGPUBuffer( void SDL_EndGPUCopyPass( SDL_GPUCopyPass *copy_pass) { - if (copy_pass == NULL) { + CHECK_PARAM(copy_pass == NULL) { SDL_InvalidParamError("copy_pass"); return; } @@ -2872,11 +3024,11 @@ void SDL_GenerateMipmapsForGPUTexture( SDL_GPUCommandBuffer *command_buffer, SDL_GPUTexture *texture) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } - if (texture == NULL) { + CHECK_PARAM(texture == NULL) { SDL_InvalidParamError("texture"); return; } @@ -2914,11 +3066,11 @@ void SDL_BlitGPUTexture( SDL_GPUCommandBuffer *command_buffer, const SDL_GPUBlitInfo *info) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } - if (info == NULL) { + CHECK_PARAM(info == NULL) { SDL_InvalidParamError("info"); return; } @@ -2979,7 +3131,8 @@ bool SDL_WindowSupportsGPUSwapchainComposition( SDL_GPUSwapchainComposition swapchain_composition) { CHECK_DEVICE_MAGIC(device, false); - if (window == NULL) { + + CHECK_PARAM(window == NULL) { SDL_InvalidParamError("window"); return false; } @@ -3000,7 +3153,8 @@ bool SDL_WindowSupportsGPUPresentMode( SDL_GPUPresentMode present_mode) { CHECK_DEVICE_MAGIC(device, false); - if (window == NULL) { + + CHECK_PARAM(window == NULL) { SDL_InvalidParamError("window"); return false; } @@ -3020,7 +3174,8 @@ bool SDL_ClaimWindowForGPUDevice( SDL_Window *window) { CHECK_DEVICE_MAGIC(device, false); - if (window == NULL) { + + CHECK_PARAM(window == NULL) { return SDL_InvalidParamError("window"); } @@ -3038,7 +3193,8 @@ void SDL_ReleaseWindowFromGPUDevice( SDL_Window *window) { CHECK_DEVICE_MAGIC(device, ); - if (window == NULL) { + + CHECK_PARAM(window == NULL) { SDL_InvalidParamError("window"); return; } @@ -3055,7 +3211,8 @@ bool SDL_SetGPUSwapchainParameters( SDL_GPUPresentMode present_mode) { CHECK_DEVICE_MAGIC(device, false); - if (window == NULL) { + + CHECK_PARAM(window == NULL) { SDL_InvalidParamError("window"); return false; } @@ -3081,6 +3238,7 @@ bool SDL_SetGPUAllowedFramesInFlight( if (device->debug_mode) { if (allowed_frames_in_flight < 1 || allowed_frames_in_flight > 3) { + SDL_COMPILE_TIME_ASSERT(max_frames_in_flight, MAX_FRAMES_IN_FLIGHT == 3); SDL_assert_release(!"allowed_frames_in_flight value must be between 1 and 3!"); } } @@ -3096,7 +3254,8 @@ SDL_GPUTextureFormat SDL_GetGPUSwapchainTextureFormat( SDL_Window *window) { CHECK_DEVICE_MAGIC(device, SDL_GPU_TEXTUREFORMAT_INVALID); - if (window == NULL) { + + CHECK_PARAM(window == NULL) { SDL_InvalidParamError("window"); return SDL_GPU_TEXTUREFORMAT_INVALID; } @@ -3115,13 +3274,13 @@ bool SDL_AcquireGPUSwapchainTexture( { CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { return SDL_InvalidParamError("command_buffer"); } - if (window == NULL) { + CHECK_PARAM(window == NULL) { return SDL_InvalidParamError("window"); } - if (swapchain_texture == NULL) { + CHECK_PARAM(swapchain_texture == NULL) { return SDL_InvalidParamError("swapchain_texture"); } @@ -3150,7 +3309,7 @@ bool SDL_WaitForGPUSwapchain( { CHECK_DEVICE_MAGIC(device, false); - if (window == NULL) { + CHECK_PARAM(window == NULL) { return SDL_InvalidParamError("window"); } @@ -3168,13 +3327,13 @@ bool SDL_WaitAndAcquireGPUSwapchainTexture( { CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { return SDL_InvalidParamError("command_buffer"); } - if (window == NULL) { + CHECK_PARAM(window == NULL) { return SDL_InvalidParamError("window"); } - if (swapchain_texture == NULL) { + CHECK_PARAM(swapchain_texture == NULL) { return SDL_InvalidParamError("swapchain_texture"); } @@ -3202,7 +3361,7 @@ bool SDL_SubmitGPUCommandBuffer( { CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return false; } @@ -3229,7 +3388,7 @@ SDL_GPUFence *SDL_SubmitGPUCommandBufferAndAcquireFence( { CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return NULL; } @@ -3256,7 +3415,7 @@ bool SDL_CancelGPUCommandBuffer( { CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return false; } @@ -3288,7 +3447,8 @@ bool SDL_WaitForGPUFences( Uint32 num_fences) { CHECK_DEVICE_MAGIC(device, false); - if (fences == NULL && num_fences > 0) { + + CHECK_PARAM(fences == NULL && num_fences > 0) { SDL_InvalidParamError("fences"); return false; } @@ -3305,7 +3465,8 @@ bool SDL_QueryGPUFence( SDL_GPUFence *fence) { CHECK_DEVICE_MAGIC(device, false); - if (fence == NULL) { + + CHECK_PARAM(fence == NULL) { SDL_InvalidParamError("fence"); return false; } @@ -3320,7 +3481,8 @@ void SDL_ReleaseGPUFence( SDL_GPUFence *fence) { CHECK_DEVICE_MAGIC(device, ); - if (fence == NULL) { + + CHECK_PARAM(fence == NULL) { return; } @@ -3341,3 +3503,67 @@ Uint32 SDL_CalculateGPUTextureFormatSize( Uint32 blocksPerColumn = (height + blockHeight - 1) / blockHeight; return depth_or_layer_count * blocksPerRow * blocksPerColumn * SDL_GPUTextureFormatTexelBlockSize(format); } + +SDL_PixelFormat SDL_GetPixelFormatFromGPUTextureFormat(SDL_GPUTextureFormat format) +{ + switch (format) { + case SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM: + return SDL_PIXELFORMAT_BGRA4444; + case SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM: + return SDL_PIXELFORMAT_BGR565; + case SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM: + return SDL_PIXELFORMAT_BGRA5551; + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT: + return SDL_PIXELFORMAT_RGBA32; + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM: + return SDL_PIXELFORMAT_RGBA32; + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM: + return SDL_PIXELFORMAT_RGBA32; + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB: + return SDL_PIXELFORMAT_RGBA32; + case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM: + return SDL_PIXELFORMAT_BGRA32; + case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB: + return SDL_PIXELFORMAT_BGRA32; + case SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM: + return SDL_PIXELFORMAT_ABGR2101010; + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT: + return SDL_PIXELFORMAT_RGBA64; + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM: + return SDL_PIXELFORMAT_RGBA64; + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT: + return SDL_PIXELFORMAT_RGBA64_FLOAT; + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT: + return SDL_PIXELFORMAT_RGBA128_FLOAT; + default: + return SDL_PIXELFORMAT_UNKNOWN; + } +} + +SDL_GPUTextureFormat SDL_GetGPUTextureFormatFromPixelFormat(SDL_PixelFormat format) +{ + switch (format) { + case SDL_PIXELFORMAT_BGRA4444: + return SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM; + case SDL_PIXELFORMAT_BGR565: + return SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM; + case SDL_PIXELFORMAT_BGRA5551: + return SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM; + case SDL_PIXELFORMAT_BGRA32: + case SDL_PIXELFORMAT_BGRX32: + return SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM; + case SDL_PIXELFORMAT_RGBA32: + case SDL_PIXELFORMAT_RGBX32: + return SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; + case SDL_PIXELFORMAT_ABGR2101010: + return SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM; + case SDL_PIXELFORMAT_RGBA64: + return SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM; + case SDL_PIXELFORMAT_RGBA64_FLOAT: + return SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT; + case SDL_PIXELFORMAT_RGBA128_FLOAT: + return SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT; + default: + return SDL_GPU_TEXTUREFORMAT_INVALID; + } +} diff --git a/libs/SDL3/src/gpu/SDL_sysgpu.h b/libs/SDL3/src/gpu/SDL_sysgpu.h index 8469d94..8e0e349 100644 --- a/libs/SDL3/src/gpu/SDL_sysgpu.h +++ b/libs/SDL3/src/gpu/SDL_sysgpu.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -35,7 +35,7 @@ #define UNIFORM_BUFFER_SIZE 32768 #define MAX_VERTEX_BUFFERS 16 #define MAX_VERTEX_ATTRIBUTES 16 -#define MAX_COLOR_TARGET_BINDINGS 4 +#define MAX_COLOR_TARGET_BINDINGS 8 #define MAX_PRESENT_COUNT 16 #define MAX_FRAMES_IN_FLIGHT 3 @@ -446,6 +446,154 @@ static inline bool IsIntegerFormat( } } +static inline bool IsCompressedFormat( + SDL_GPUTextureFormat format) +{ + switch (format) { + case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_BC4_R_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC5_RG_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT: + case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT: + case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_FLOAT: + return true; + + default: + return false; + } +} + +static inline bool FormatHasAlpha( + SDL_GPUTextureFormat format) +{ + switch (format) { + case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_FLOAT: + // ASTC textures may or may not have alpha; return true as this is mainly intended for validation + return true; + + case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM: + 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_R8G8B8A8_UNORM: + case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM: + case SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM: + case SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM: + case SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM: + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM: + case SDL_GPU_TEXTUREFORMAT_A8_UNORM: + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM: + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM: + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT: + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT: + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT: + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT: + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT: + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT: + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT: + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT: + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB: + return true; + + default: + return false; + } +} + static inline Uint32 IndexSize(SDL_GPUIndexElementSize size) { return (size == SDL_GPU_INDEXELEMENTSIZE_16BIT) ? 2 : 4; @@ -517,10 +665,12 @@ typedef struct SDL_GPURenderer SDL_GPURenderer; struct SDL_GPUDevice { - // Quit + // Device void (*DestroyDevice)(SDL_GPUDevice *device); + SDL_PropertiesID (*GetDeviceProperties)(SDL_GPUDevice *device); + // State Creation SDL_GPUComputePipeline *(*CreateComputePipeline)( @@ -946,12 +1096,16 @@ struct SDL_GPUDevice // Store this for SDL_gpu.c's debug layer bool debug_mode; + bool default_enable_depth_clip; + bool validate_feature_depth_clamp_disabled; + bool validate_feature_anisotropy_disabled; }; #define ASSIGN_DRIVER_FUNC(func, name) \ result->func = name##_##func; #define ASSIGN_DRIVER(name) \ ASSIGN_DRIVER_FUNC(DestroyDevice, name) \ + ASSIGN_DRIVER_FUNC(GetDeviceProperties, name) \ ASSIGN_DRIVER_FUNC(CreateComputePipeline, name) \ ASSIGN_DRIVER_FUNC(CreateGraphicsPipeline, name) \ ASSIGN_DRIVER_FUNC(CreateSampler, name) \ @@ -1037,8 +1191,7 @@ struct SDL_GPUDevice typedef struct SDL_GPUBootstrap { const char *name; - const SDL_GPUShaderFormat shader_formats; - bool (*PrepareDriver)(SDL_VideoDevice *_this); + bool (*PrepareDriver)(SDL_VideoDevice *_this, SDL_PropertiesID props); SDL_GPUDevice *(*CreateDevice)(bool debug_mode, bool prefer_low_power, SDL_PropertiesID props); } SDL_GPUBootstrap; diff --git a/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c b/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c index 1ad359b..6a85c6e 100644 --- a/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c +++ b/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -23,6 +23,7 @@ #ifdef SDL_GPU_D3D12 +#include "../../events/SDL_windowevents_c.h" #include "../../core/windows/SDL_windows.h" #include "../../video/directx/SDL_d3d12.h" #include "../SDL_sysgpu.h" @@ -90,6 +91,8 @@ #define D3D12_DLL "d3d12_x.dll" #else #define D3D12_DLL "d3d12.dll" +#define USE_PIX_RUNTIME +#define WINPIXEVENTRUNTIME_DLL "WinPixEventRuntime.dll" #endif #define DXGI_DLL "dxgi.dll" #define DXGIDEBUG_DLL "dxgidebug.dll" @@ -108,9 +111,10 @@ #define CREATE_DXGI_FACTORY1_FUNC "CreateDXGIFactory1" #define DXGI_GET_DEBUG_INTERFACE_FUNC "DXGIGetDebugInterface" #define D3D12_GET_DEBUG_INTERFACE_FUNC "D3D12GetDebugInterface" -#define WINDOW_PROPERTY_DATA "SDL_GPUD3D12WindowPropertyData" -#define D3D_FEATURE_LEVEL_CHOICE D3D_FEATURE_LEVEL_11_1 -#define D3D_FEATURE_LEVEL_CHOICE_STR "11_1" +#define D3D12_GET_INTERFACE_FUNC "D3D12GetInterface" +#define WINDOW_PROPERTY_DATA "SDL.internal.gpu.d3d12.data" +#define D3D_FEATURE_LEVEL_CHOICE D3D_FEATURE_LEVEL_11_0 +#define D3D_FEATURE_LEVEL_CHOICE_STR "11_0" #define MAX_ROOT_SIGNATURE_PARAMETERS 64 #define D3D12_FENCE_UNSIGNALED_VALUE 0 #define D3D12_FENCE_SIGNAL_VALUE 1 @@ -121,18 +125,6 @@ #define SDL_GPU_SHADERSTAGE_COMPUTE (SDL_GPUShaderStage)2 -#define EXPAND_ELEMENTS_IF_NEEDED(arr, initialValue, type) \ - if (arr->count == arr->capacity) { \ - if (arr->capacity == 0) { \ - arr->capacity = initialValue; \ - } else { \ - arr->capacity *= 2; \ - } \ - arr->elements = (type *)SDL_realloc( \ - arr->elements, \ - arr->capacity * sizeof(type)); \ - } - #ifdef _WIN32 #define HRESULT_FMT "(0x%08lX)" #else @@ -140,8 +132,17 @@ #endif // Function Pointer Signatures -typedef HRESULT(WINAPI *PFN_CREATE_DXGI_FACTORY1)(const GUID *riid, void **ppFactory); -typedef HRESULT(WINAPI *PFN_DXGI_GET_DEBUG_INTERFACE)(const GUID *riid, void **ppDebug); +typedef HRESULT (WINAPI *pfnCreateDXGIFactory1)(const GUID *riid, void **ppFactory); +typedef HRESULT (WINAPI *pfnDXGIGetDebugInterface)(const GUID *riid, void **ppDebug); + +#ifdef USE_PIX_RUNTIME +#define PIX_BEGIN_EVENT_ON_COMMAND_LIST_FUNC "PIXBeginEventOnCommandList" +#define PIX_END_EVENT_ON_COMMAND_LIST_FUNC "PIXEndEventOnCommandList" +#define PIX_SET_MARKER_ON_COMMAND_LIST_FUNC "PIXSetMarkerOnCommandList" +typedef void(WINAPI* pfnBeginEventOnCommandList)(ID3D12GraphicsCommandList* commandList, UINT64 color, _In_ PCSTR formatString); +typedef void(WINAPI* pfnEndEventOnCommandList)(ID3D12GraphicsCommandList* commandList); +typedef void(WINAPI* pfnSetMarkerOnCommandList)(ID3D12GraphicsCommandList* commandList, UINT64 color, _In_ PCSTR formatString); +#endif // IIDs (from https://www.magnumdb.com/) static const IID D3D_IID_IDXGIFactory1 = { 0x770aae78, 0xf26f, 0x4dba, { 0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87 } }; @@ -176,6 +177,12 @@ static const IID D3D_IID_ID3D12Debug = { 0x344488b7, 0x6846, 0x474b, { 0xb9, 0x8 static const IID D3D_IID_ID3D12InfoQueue = { 0x0742a90b, 0xc387, 0x483f, { 0xb9, 0x46, 0x30, 0xa7, 0xe4, 0xe6, 0x14, 0x58 } }; static const IID D3D_IID_ID3D12InfoQueue1 = { 0x2852dd88, 0xb484, 0x4c0c, { 0xb6, 0xb1, 0x67, 0x16, 0x85, 0x00, 0xe6, 0x00 } }; +static const GUID D3D_CLSID_ID3D12SDKConfiguration = { 0x7cda6aca, 0xa03e, 0x49c8, { 0x94, 0x58, 0x03, 0x34, 0xd2, 0x0e, 0x07, 0xce } }; +static const GUID D3D_CLSID_ID3D12Debug = { 0xf2352aeb, 0xdd84, 0x49fe, { 0xb9, 0x7b, 0xa9, 0xdc, 0xfd, 0xcc, 0x1b, 0x4f } }; +static const IID D3D_IID_ID3D12SDKConfiguration = { 0xe9eb5314, 0x33aa, 0x42b2, { 0xa7, 0x18, 0xd7, 0x7f, 0x58, 0xb1, 0xf1, 0xc7 } }; +static const IID D3D_IID_ID3D12SDKConfiguration1 = { 0x8aaf9303, 0xad25, 0x48b9, { 0x9a, 0x57, 0xd9, 0xc3, 0x7e, 0x00, 0x9d, 0x9f } }; +static const IID D3D_IID_ID3D12DeviceFactory = { 0x61f307d3, 0xd34e, 0x4e7c, { 0x83, 0x74, 0x3b, 0xa4, 0xde, 0x23, 0xcc, 0xcb } }; + // Enums typedef enum D3D12BufferType @@ -256,7 +263,7 @@ static D3D12_BLEND_OP SDLToD3D12_BlendOp[] = { SDL_COMPILE_TIME_ASSERT(SDLToD3D12_BlendOp, SDL_arraysize(SDLToD3D12_BlendOp) == SDL_GPU_BLENDOP_MAX_ENUM_VALUE); // These are actually color formats. -// For some genius reason, D3D12 splits format capabilites for depth-stencil views. +// For some genius reason, D3D12 splits format capabilities for depth-stencil views. static DXGI_FORMAT SDLToD3D12_TextureFormat[] = { DXGI_FORMAT_UNKNOWN, // INVALID DXGI_FORMAT_A8_UNORM, // A8_UNORM @@ -831,6 +838,8 @@ typedef struct D3D12Sampler typedef struct D3D12WindowData { SDL_Window *window; + D3D12Renderer *renderer; + int refcount; #if (defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) D3D12XBOX_FRAME_PIPELINE_TOKEN frameToken; #else @@ -856,6 +865,14 @@ typedef struct D3D12PresentData Uint32 swapchainImageIndex; } D3D12PresentData; +#ifdef USE_PIX_RUNTIME +typedef struct WinPixEventRuntimeFns { + pfnBeginEventOnCommandList pBeginEventOnCommandList; + pfnEndEventOnCommandList pEndEventOnCommandList; + pfnSetMarkerOnCommandList pSetMarkerOnCommandList; +} WinPixEventRuntimeFns; +#endif + struct D3D12Renderer { // Reference to the parent device @@ -870,22 +887,28 @@ struct D3D12Renderer IDXGIAdapter1 *adapter; SDL_SharedObject *dxgi_dll; SDL_SharedObject *dxgidebug_dll; +#endif +#ifdef USE_PIX_RUNTIME + SDL_SharedObject *winpixeventruntime_dll; + WinPixEventRuntimeFns winpixeventruntimeFns; #endif ID3D12Debug *d3d12Debug; BOOL supportsTearing; SDL_SharedObject *d3d12_dll; ID3D12Device *device; - PFN_D3D12_SERIALIZE_ROOT_SIGNATURE D3D12SerializeRootSignature_func; - const char *semantic; + PFN_D3D12_SERIALIZE_ROOT_SIGNATURE pD3D12SerializeRootSignature; + char *semantic; SDL_iconv_t iconv; ID3D12CommandQueue *commandQueue; bool debug_mode; bool GPUUploadHeapSupported; + bool UnrestrictedBufferTextureCopyPitchSupported; // FIXME: these might not be necessary since we're not using custom heaps bool UMA; bool UMACacheCoherent; + SDL_PropertiesID props; Uint32 allowedFramesInFlight; // Indirect command signatures @@ -991,6 +1014,10 @@ struct D3D12CommandBuffer // Set at acquire time D3D12DescriptorHeap *gpuDescriptorHeaps[D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER + 1]; + D3D12DescriptorHeap **usedDescriptorHeaps; + Uint32 usedDescriptorHeapCount; + Uint32 usedDescriptorHeapCapacity; + D3D12UniformBuffer **usedUniformBuffers; Uint32 usedUniformBufferCount; Uint32 usedUniformBufferCapacity; @@ -1220,7 +1247,7 @@ static ID3D12CommandQueue *s_CommandQueue; #if defined(SDL_PLATFORM_XBOXONE) // These are not defined in d3d12_x.h. -typedef HRESULT (D3DAPI* PFN_D3D12_XBOX_CREATE_DEVICE)(_In_opt_ IGraphicsUnknown*, _In_ const D3D12XBOX_CREATE_DEVICE_PARAMETERS*, _In_ REFIID, _Outptr_opt_ void**); +typedef HRESULT (D3DAPI* PFN_D3D12_XBOX_CREATE_DEVICE)(_In_opt_ IGraphicsUnknown *, _In_ const D3D12XBOX_CREATE_DEVICE_PARAMETERS*, _In_ REFIID, _Outptr_opt_ void **); #define D3D12_STANDARD_MULTISAMPLE_PATTERN DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN #endif @@ -1286,7 +1313,6 @@ static void D3D12_INTERNAL_SetError( // Release / Cleanup static void D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - D3D12Renderer *renderer, D3D12StagingDescriptor *cpuDescriptor) { D3D12StagingDescriptorPool *pool = cpuDescriptor->pool; @@ -1300,7 +1326,6 @@ static void D3D12_INTERNAL_ReleaseStagingDescriptorHandle( } static void D3D12_INTERNAL_DestroyBuffer( - D3D12Renderer *renderer, D3D12Buffer *buffer) { if (!buffer) { @@ -1314,13 +1339,10 @@ static void D3D12_INTERNAL_DestroyBuffer( NULL); } D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &buffer->srvDescriptor); D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &buffer->uavDescriptor); D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &buffer->cbvDescriptor); if (buffer->handle) { @@ -1361,9 +1383,7 @@ static void D3D12_INTERNAL_ReleaseBufferContainer( } // Containers are just client handles, so we can free immediately - if (container->debugName) { - SDL_free(container->debugName); - } + SDL_free(container->debugName); SDL_free(container->buffers); SDL_free(container); @@ -1371,7 +1391,6 @@ static void D3D12_INTERNAL_ReleaseBufferContainer( } static void D3D12_INTERNAL_DestroyTexture( - D3D12Renderer *renderer, D3D12Texture *texture) { if (!texture) { @@ -1382,24 +1401,20 @@ static void D3D12_INTERNAL_DestroyTexture( if (subresource->rtvHandles) { for (Uint32 depthIndex = 0; depthIndex < subresource->depth; depthIndex += 1) { D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &subresource->rtvHandles[depthIndex]); } SDL_free(subresource->rtvHandles); } D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &subresource->uavHandle); D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &subresource->dsvHandle); } SDL_free(texture->subresources); D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &texture->srvHandle); if (texture->resource) { @@ -1443,9 +1458,7 @@ static void D3D12_INTERNAL_ReleaseTextureContainer( SDL_DestroyProperties(container->header.info.props); // Containers are just client handles, so we can destroy immediately - if (container->debugName) { - SDL_free(container->debugName); - } + SDL_free(container->debugName); SDL_free(container->textures); SDL_free(container); @@ -1453,11 +1466,9 @@ static void D3D12_INTERNAL_ReleaseTextureContainer( } static void D3D12_INTERNAL_DestroySampler( - D3D12Renderer *renderer, D3D12Sampler *sampler) { D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &sampler->handle); SDL_free(sampler); @@ -1588,6 +1599,7 @@ static void D3D12_INTERNAL_DestroyCommandBuffer(D3D12CommandBuffer *commandBuffe SDL_free(commandBuffer->usedSamplers); SDL_free(commandBuffer->usedGraphicsPipelines); SDL_free(commandBuffer->usedComputePipelines); + SDL_free(commandBuffer->usedDescriptorHeaps); SDL_free(commandBuffer->usedUniformBuffers); SDL_free(commandBuffer->textureDownloads); SDL_free(commandBuffer); @@ -1612,7 +1624,6 @@ static void D3D12_INTERNAL_DestroyRenderer(D3D12Renderer *renderer) // Release uniform buffers for (Uint32 i = 0; i < renderer->uniformBufferPoolCount; i += 1) { D3D12_INTERNAL_DestroyBuffer( - renderer, renderer->uniformBufferPool[i]->buffer); SDL_free(renderer->uniformBufferPool[i]); } @@ -1669,6 +1680,8 @@ static void D3D12_INTERNAL_DestroyRenderer(D3D12Renderer *renderer) SDL_free(renderer->graphicsPipelinesToDestroy); SDL_free(renderer->computePipelinesToDestroy); + SDL_DestroyProperties(renderer->props); + // Tear down D3D12 objects if (renderer->indirectDrawCommandSignature) { ID3D12CommandSignature_Release(renderer->indirectDrawCommandSignature); @@ -1722,7 +1735,13 @@ static void D3D12_INTERNAL_DestroyRenderer(D3D12Renderer *renderer) renderer->dxgidebug_dll = NULL; } #endif - renderer->D3D12SerializeRootSignature_func = NULL; +#ifdef USE_PIX_RUNTIME + if (renderer->winpixeventruntime_dll) { + SDL_UnloadObject(renderer->winpixeventruntime_dll); + renderer->winpixeventruntime_dll = NULL; + } +#endif + renderer->pD3D12SerializeRootSignature = NULL; if (renderer->iconv) { SDL_iconv_close(renderer->iconv); @@ -1734,6 +1753,7 @@ static void D3D12_INTERNAL_DestroyRenderer(D3D12Renderer *renderer) SDL_DestroyMutex(renderer->windowLock); SDL_DestroyMutex(renderer->fenceLock); SDL_DestroyMutex(renderer->disposeLock); + SDL_free(renderer->semantic); SDL_free(renderer); } @@ -1756,6 +1776,12 @@ static void D3D12_DestroyDevice(SDL_GPUDevice *device) SDL_free(device); } +static SDL_PropertiesID D3D12_GetDeviceProperties(SDL_GPUDevice *device) +{ + D3D12Renderer *renderer = (D3D12Renderer *)device->driverData; + return renderer->props; +} + // Barriers static inline Uint32 D3D12_INTERNAL_CalcSubresource( @@ -2083,9 +2109,7 @@ static void D3D12_SetBufferName( D3D12BufferContainer *container = (D3D12BufferContainer *)buffer; if (renderer->debug_mode && text != NULL) { - if (container->debugName != NULL) { - SDL_free(container->debugName); - } + SDL_free(container->debugName); container->debugName = SDL_strdup(text); @@ -2107,9 +2131,7 @@ static void D3D12_SetTextureName( D3D12TextureContainer *container = (D3D12TextureContainer *)texture; if (renderer->debug_mode && text != NULL) { - if (container->debugName != NULL) { - SDL_free(container->debugName); - } + SDL_free(container->debugName); container->debugName = SDL_strdup(text); @@ -2122,8 +2144,8 @@ static void D3D12_SetTextureName( } } -/* These debug functions are all marked as "for internal usage only" - * on D3D12... works on renderdoc! +/* These debug functions now require the PIX runtime under Windows to avoid validation + * layer errors. Calling them without the PIX runtime in your path is a no-op. */ static void D3D12_InsertDebugLabel( @@ -2131,6 +2153,13 @@ static void D3D12_InsertDebugLabel( const char *text) { D3D12CommandBuffer *d3d12CommandBuffer = (D3D12CommandBuffer *)commandBuffer; +#ifdef USE_PIX_RUNTIME + // Requires PIX runtime under Windows, no-op if DLL unavailable. + WinPixEventRuntimeFns *fns = &d3d12CommandBuffer->renderer->winpixeventruntimeFns; + if (fns->pSetMarkerOnCommandList) { + fns->pSetMarkerOnCommandList(d3d12CommandBuffer->graphicsCommandList, 0 /*default color*/, text); + } +#else WCHAR *wchar_text = WIN_UTF8ToStringW(text); ID3D12GraphicsCommandList_SetMarker( @@ -2140,6 +2169,7 @@ static void D3D12_InsertDebugLabel( (UINT)SDL_wcslen(wchar_text) * sizeof(WCHAR)); SDL_free(wchar_text); +#endif } static void D3D12_PushDebugGroup( @@ -2147,6 +2177,13 @@ static void D3D12_PushDebugGroup( const char *name) { D3D12CommandBuffer *d3d12CommandBuffer = (D3D12CommandBuffer *)commandBuffer; +#ifdef USE_PIX_RUNTIME + // Requires PIX runtime under Windows, no-op if DLL unavailable. + WinPixEventRuntimeFns *fns = &d3d12CommandBuffer->renderer->winpixeventruntimeFns; + if (fns->pBeginEventOnCommandList) { + fns->pBeginEventOnCommandList(d3d12CommandBuffer->graphicsCommandList, 0 /*default color*/, name); + } +#else WCHAR *wchar_text = WIN_UTF8ToStringW(name); ID3D12GraphicsCommandList_BeginEvent( @@ -2156,13 +2193,22 @@ static void D3D12_PushDebugGroup( (UINT)SDL_wcslen(wchar_text) * sizeof(WCHAR)); SDL_free(wchar_text); +#endif } static void D3D12_PopDebugGroup( SDL_GPUCommandBuffer *commandBuffer) { D3D12CommandBuffer *d3d12CommandBuffer = (D3D12CommandBuffer *)commandBuffer; +#ifdef USE_PIX_RUNTIME + // Requires PIX runtime under Windows, no-op if DLL unavailable. + WinPixEventRuntimeFns *fns = &d3d12CommandBuffer->renderer->winpixeventruntimeFns; + if (fns->pEndEventOnCommandList) { + fns->pEndEventOnCommandList(d3d12CommandBuffer->graphicsCommandList); + } +#else ID3D12GraphicsCommandList_EndEvent(d3d12CommandBuffer->graphicsCommandList); +#endif } // State Creation @@ -2229,15 +2275,15 @@ static D3D12StagingDescriptorPool *D3D12_INTERNAL_CreateStagingDescriptorPool( return NULL; } - D3D12StagingDescriptorPool *pool = (D3D12StagingDescriptorPool*) SDL_calloc(1, sizeof(D3D12StagingDescriptorPool)); + D3D12StagingDescriptorPool *pool = (D3D12StagingDescriptorPool *)SDL_calloc(1, sizeof(D3D12StagingDescriptorPool)); pool->heapCount = 1; - pool->heaps = (D3D12DescriptorHeap**) SDL_malloc(sizeof(D3D12DescriptorHeap*)); + pool->heaps = (D3D12DescriptorHeap **)SDL_malloc(sizeof(D3D12DescriptorHeap *)); pool->heaps[0] = heap; pool->freeDescriptorCapacity = STAGING_HEAP_DESCRIPTOR_COUNT; pool->freeDescriptorCount = STAGING_HEAP_DESCRIPTOR_COUNT; - pool->freeDescriptors = (D3D12StagingDescriptor*) SDL_malloc(STAGING_HEAP_DESCRIPTOR_COUNT * sizeof(D3D12StagingDescriptor)); + pool->freeDescriptors = (D3D12StagingDescriptor *)SDL_malloc(STAGING_HEAP_DESCRIPTOR_COUNT * sizeof(D3D12StagingDescriptor)); for (Uint32 i = 0; i < STAGING_HEAP_DESCRIPTOR_COUNT; i += 1) { pool->freeDescriptors[i].pool = pool; @@ -2267,12 +2313,12 @@ static bool D3D12_INTERNAL_ExpandStagingDescriptorPool( } pool->heapCount += 1; - pool->heaps = (D3D12DescriptorHeap**) SDL_realloc(pool->heaps, pool->heapCount * sizeof(D3D12DescriptorHeap*)); + pool->heaps = (D3D12DescriptorHeap **)SDL_realloc(pool->heaps, pool->heapCount * sizeof(D3D12DescriptorHeap *)); pool->heaps[pool->heapCount - 1] = heap; pool->freeDescriptorCapacity += STAGING_HEAP_DESCRIPTOR_COUNT; pool->freeDescriptorCount += STAGING_HEAP_DESCRIPTOR_COUNT; - pool->freeDescriptors = (D3D12StagingDescriptor*) SDL_realloc(pool->freeDescriptors, pool->freeDescriptorCapacity * sizeof(D3D12StagingDescriptor)); + pool->freeDescriptors = (D3D12StagingDescriptor *)SDL_realloc(pool->freeDescriptors, pool->freeDescriptorCapacity * sizeof(D3D12StagingDescriptor)); for (Uint32 i = 0; i < STAGING_HEAP_DESCRIPTOR_COUNT; i += 1) { pool->freeDescriptors[i].pool = pool; @@ -2284,6 +2330,28 @@ static bool D3D12_INTERNAL_ExpandStagingDescriptorPool( return true; } +static void D3D12_INTERNAL_TrackGPUDescriptorHeap( + D3D12CommandBuffer *commandBuffer, + D3D12DescriptorHeap *descriptorHeap) +{ + Uint32 i; + for (i = 0; i < commandBuffer->usedDescriptorHeapCount; i += 1) { + if (commandBuffer->usedDescriptorHeaps[i] == descriptorHeap) { + return; + } + } + + if (commandBuffer->usedDescriptorHeapCount == commandBuffer->usedDescriptorHeapCapacity) { + commandBuffer->usedDescriptorHeapCapacity += 1; + commandBuffer->usedDescriptorHeaps = (D3D12DescriptorHeap **)SDL_realloc( + commandBuffer->usedDescriptorHeaps, + commandBuffer->usedDescriptorHeapCapacity * sizeof(D3D12DescriptorHeap *)); + } + + commandBuffer->usedDescriptorHeaps[commandBuffer->usedDescriptorHeapCount] = descriptorHeap; + commandBuffer->usedDescriptorHeapCount += 1; +} + static D3D12DescriptorHeap *D3D12_INTERNAL_AcquireGPUDescriptorHeapFromPool( D3D12CommandBuffer *commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE descriptorHeapType) @@ -2305,6 +2373,7 @@ static D3D12DescriptorHeap *D3D12_INTERNAL_AcquireGPUDescriptorHeapFromPool( } SDL_UnlockMutex(pool->lock); + D3D12_INTERNAL_TrackGPUDescriptorHeap(commandBuffer, result); return result; } @@ -2572,7 +2641,7 @@ static D3D12GraphicsRootSignature *D3D12_INTERNAL_CreateGraphicsRootSignature( // Serialize the root signature ID3DBlob *serializedRootSignature; ID3DBlob *errorBlob; - HRESULT res = renderer->D3D12SerializeRootSignature_func(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &serializedRootSignature, &errorBlob); + HRESULT res = renderer->pD3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &serializedRootSignature, &errorBlob); if (FAILED(res)) { if (errorBlob) { @@ -2607,16 +2676,32 @@ static D3D12GraphicsRootSignature *D3D12_INTERNAL_CreateGraphicsRootSignature( return d3d12GraphicsRootSignature; } +static bool D3D12_INTERNAL_IsValidShaderBytecode( + const Uint8 *code, + size_t codeSize) +{ + // Both DXIL and DXBC bytecode have a 4 byte header containing `DXBC`. + if (codeSize < 4 || code == NULL) { + return false; + } + return SDL_memcmp(code, "DXBC", 4) == 0; +} + static bool D3D12_INTERNAL_CreateShaderBytecode( D3D12Renderer *renderer, - Uint32 stage, - SDL_GPUShaderFormat format, const Uint8 *code, size_t codeSize, - const char *entrypointName, + SDL_GPUShaderFormat format, void **pBytecode, size_t *pBytecodeSize) { + if (!D3D12_INTERNAL_IsValidShaderBytecode(code, codeSize)) { + if (format == SDL_GPU_SHADERFORMAT_DXBC) { + SET_STRING_ERROR_AND_RETURN("The provided shader code is not valid DXBC!", false); + } + SET_STRING_ERROR_AND_RETURN("The provided shader code is not valid DXIL!", false); + } + if (pBytecode != NULL) { *pBytecode = SDL_malloc(codeSize); if (!*pBytecode) { @@ -2786,7 +2871,7 @@ static D3D12ComputeRootSignature *D3D12_INTERNAL_CreateComputeRootSignature( ID3DBlob *serializedRootSignature; ID3DBlob *errorBlob; - HRESULT res = renderer->D3D12SerializeRootSignature_func( + HRESULT res = renderer->pD3D12SerializeRootSignature( &rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &serializedRootSignature, @@ -2829,19 +2914,15 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( const SDL_GPUComputePipelineCreateInfo *createinfo) { D3D12Renderer *renderer = (D3D12Renderer *)driverData; - void *bytecode; - size_t bytecodeSize; ID3D12PipelineState *pipelineState; if (!D3D12_INTERNAL_CreateShaderBytecode( renderer, - SDL_GPU_SHADERSTAGE_COMPUTE, - createinfo->format, createinfo->code, createinfo->code_size, - createinfo->entrypoint, - &bytecode, - &bytecodeSize)) { + createinfo->format, + NULL, + NULL)) { return NULL; } @@ -2850,13 +2931,12 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( createinfo); if (rootSignature == NULL) { - SDL_free(bytecode); SET_STRING_ERROR_AND_RETURN("Could not create root signature!", NULL); } D3D12_COMPUTE_PIPELINE_STATE_DESC pipelineDesc; - pipelineDesc.CS.pShaderBytecode = bytecode; - pipelineDesc.CS.BytecodeLength = bytecodeSize; + pipelineDesc.CS.pShaderBytecode = createinfo->code; + pipelineDesc.CS.BytecodeLength = createinfo->code_size; pipelineDesc.pRootSignature = rootSignature->handle; pipelineDesc.CachedPSO.CachedBlobSizeInBytes = 0; pipelineDesc.CachedPSO.pCachedBlob = NULL; @@ -2871,7 +2951,6 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( if (FAILED(res)) { D3D12_INTERNAL_SetError(renderer, "Could not create compute pipeline state", res); - SDL_free(bytecode); return NULL; } @@ -2880,7 +2959,6 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( if (!computePipeline) { ID3D12PipelineState_Release(pipelineState); - SDL_free(bytecode); return NULL; } @@ -2952,7 +3030,7 @@ static bool D3D12_INTERNAL_ConvertBlendState( } SDL_zerop(blendDesc); - blendDesc->AlphaToCoverageEnable = FALSE; + blendDesc->AlphaToCoverageEnable = pipelineInfo->multisample_state.enable_alpha_to_coverage; blendDesc->IndependentBlendEnable = FALSE; for (UINT i = 0; i < MAX_COLOR_TARGET_BINDINGS; i += 1) { @@ -3253,11 +3331,9 @@ static SDL_GPUShader *D3D12_CreateShader( if (!D3D12_INTERNAL_CreateShaderBytecode( renderer, - createinfo->stage, - createinfo->format, createinfo->code, createinfo->code_size, - createinfo->entrypoint, + createinfo->format, &bytecode, &bytecodeSize)) { return NULL; @@ -3387,7 +3463,7 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( (void **)&handle); if (FAILED(res)) { D3D12_INTERNAL_SetError(renderer, "Failed to create texture!", res); - D3D12_INTERNAL_DestroyTexture(renderer, texture); + D3D12_INTERNAL_DestroyTexture(texture); return NULL; } @@ -3451,7 +3527,7 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( texture->subresources = (D3D12TextureSubresource *)SDL_calloc( texture->subresourceCount, sizeof(D3D12TextureSubresource)); if (!texture->subresources) { - D3D12_INTERNAL_DestroyTexture(renderer, texture); + D3D12_INTERNAL_DestroyTexture(texture); return NULL; } for (Uint32 layerIndex = 0; layerIndex < layerCount; layerIndex += 1) { @@ -3524,7 +3600,12 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( dsvDesc.Format = SDLToD3D12_DepthFormat[createinfo->format]; dsvDesc.Flags = (D3D12_DSV_FLAGS)0; - if (isMultisample) { + if (createinfo->type == SDL_GPU_TEXTURETYPE_2D_ARRAY || createinfo->type == SDL_GPU_TEXTURETYPE_CUBE || createinfo->type == SDL_GPU_TEXTURETYPE_CUBE_ARRAY) { + dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DARRAY; + dsvDesc.Texture2DArray.MipSlice = levelIndex; + dsvDesc.Texture2DArray.FirstArraySlice = layerIndex; + dsvDesc.Texture2DArray.ArraySize = 1; + } else if (isMultisample) { dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMS; } else { dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; @@ -3731,7 +3812,7 @@ static D3D12Buffer *D3D12_INTERNAL_CreateBuffer( (void **)&handle); if (FAILED(res)) { D3D12_INTERNAL_SetError(renderer, "Could not create buffer!", res); - D3D12_INTERNAL_DestroyBuffer(renderer, buffer); + D3D12_INTERNAL_DestroyBuffer(buffer); return NULL; } @@ -3821,7 +3902,7 @@ static D3D12Buffer *D3D12_INTERNAL_CreateBuffer( (void **)&buffer->mapPointer); if (FAILED(res)) { D3D12_INTERNAL_SetError(renderer, "Failed to map upload buffer!", res); - D3D12_INTERNAL_DestroyBuffer(renderer, buffer); + D3D12_INTERNAL_DestroyBuffer(buffer); return NULL; } } @@ -4293,8 +4374,8 @@ static void D3D12_BeginRenderPass( if (depthStencilTargetInfo != NULL) { D3D12TextureContainer *container = (D3D12TextureContainer *)depthStencilTargetInfo->texture; - Uint32 h = container->header.info.height; - Uint32 w = container->header.info.width; + Uint32 h = container->header.info.height >> depthStencilTargetInfo->mip_level; + Uint32 w = container->header.info.width >> depthStencilTargetInfo->mip_level; // The framebuffer cannot be larger than the smallest target. @@ -4363,8 +4444,8 @@ static void D3D12_BeginRenderPass( D3D12TextureSubresource *subresource = D3D12_INTERNAL_PrepareTextureSubresourceForWrite( d3d12CommandBuffer, container, - 0, - 0, + depthStencilTargetInfo->layer, + depthStencilTargetInfo->mip_level, depthStencilTargetInfo->cycle, D3D12_RESOURCE_STATE_DEPTH_WRITE); @@ -5846,15 +5927,20 @@ static void D3D12_UploadToTexture( bool cycle) { D3D12CommandBuffer *d3d12CommandBuffer = (D3D12CommandBuffer *)commandBuffer; + D3D12Renderer *renderer = (D3D12Renderer *)d3d12CommandBuffer->renderer; D3D12BufferContainer *transferBufferContainer = (D3D12BufferContainer *)source->transfer_buffer; D3D12Buffer *temporaryBuffer = NULL; D3D12_TEXTURE_COPY_LOCATION sourceLocation; D3D12_TEXTURE_COPY_LOCATION destinationLocation; Uint32 pixelsPerRow = source->pixels_per_row; + Uint32 blockWidth; + Uint32 blockSize; Uint32 rowPitch; + Uint32 blockHeight; Uint32 alignedRowPitch; Uint32 rowsPerSlice = source->rows_per_layer; Uint32 bytesPerSlice; + Uint32 alignedBytesPerSlice; bool needsRealignment; bool needsPlacementCopy; @@ -5869,11 +5955,12 @@ static void D3D12_UploadToTexture( cycle, D3D12_RESOURCE_STATE_COPY_DEST); - /* D3D12 requires texture data row pitch to be 256 byte aligned, which is obviously insane. - * Instead of exposing that restriction to the client, which is a huge rake to step on, - * and a restriction that no other backend requires, we're going to copy data to a temporary buffer, - * copy THAT data to the texture, and then get rid of the temporary buffer ASAP. - * If we're lucky and the row pitch and depth pitch are already aligned, we can skip all of that. + /* Unless the UnrestrictedBufferTextureCopyPitchSupported feature is supported, D3D12 requires + * texture data row pitch to be 256 byte aligned, which is obviously insane. Instead of exposing + * that restriction to the client, which is a huge rake to step on, and a restriction that no + * other backend requires, we're going to copy data to a temporary buffer, copy THAT data to the + * texture, and then get rid of the temporary buffer ASAP. If we're lucky and the row pitch and + * depth pitch are already aligned, we can skip all of that. * * D3D12 also requires offsets to be 512 byte aligned. We'll fix that for the client and warn them as well. * @@ -5884,17 +5971,29 @@ static void D3D12_UploadToTexture( pixelsPerRow = destination->w; } - rowPitch = BytesPerRow(pixelsPerRow, textureContainer->header.info.format); - if (rowsPerSlice == 0) { rowsPerSlice = destination->h; } + blockWidth = Texture_GetBlockWidth(textureContainer->header.info.format); + blockSize = SDL_GPUTextureFormatTexelBlockSize(textureContainer->header.info.format); + rowPitch = (pixelsPerRow + (blockWidth - 1)) / blockWidth * blockSize; + blockHeight = (rowsPerSlice + (blockWidth - 1)) / blockWidth; + bytesPerSlice = rowsPerSlice * rowPitch; - alignedRowPitch = D3D12_INTERNAL_Align(rowPitch, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); - needsRealignment = rowsPerSlice != destination->h || rowPitch != alignedRowPitch; - needsPlacementCopy = source->offset % D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT != 0; + if (renderer->UnrestrictedBufferTextureCopyPitchSupported) { + alignedRowPitch = rowPitch; + needsRealignment = false; + needsPlacementCopy = false; + } else { + alignedRowPitch = (destination->w + (blockWidth - 1)) / blockWidth * blockSize; + alignedRowPitch = D3D12_INTERNAL_Align(alignedRowPitch, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); + needsRealignment = rowsPerSlice != destination->h || rowPitch != alignedRowPitch; + needsPlacementCopy = source->offset % D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT != 0; + } + + alignedBytesPerSlice = alignedRowPitch * destination->h; sourceLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; sourceLocation.PlacedFootprint.Footprint.Format = SDLToD3D12_TextureFormat[textureContainer->header.info.format]; @@ -5908,7 +6007,7 @@ static void D3D12_UploadToTexture( temporaryBuffer = D3D12_INTERNAL_CreateBuffer( d3d12CommandBuffer->renderer, 0, - alignedRowPitch * destination->h * destination->d, + alignedRowPitch * blockHeight * destination->d, D3D12_BUFFER_TYPE_UPLOAD, NULL); @@ -5919,30 +6018,25 @@ static void D3D12_UploadToTexture( sourceLocation.pResource = temporaryBuffer->handle; for (Uint32 sliceIndex = 0; sliceIndex < destination->d; sliceIndex += 1) { - // copy row count minus one to avoid overread - for (Uint32 rowIndex = 0; rowIndex < rowsPerSlice - 1; rowIndex += 1) { + for (Uint32 rowIndex = 0; rowIndex < blockHeight; rowIndex += 1) { SDL_memcpy( - temporaryBuffer->mapPointer + (sliceIndex * rowsPerSlice) + (rowIndex * alignedRowPitch), + temporaryBuffer->mapPointer + (sliceIndex * alignedBytesPerSlice) + (rowIndex * alignedRowPitch), transferBufferContainer->activeBuffer->mapPointer + source->offset + (sliceIndex * bytesPerSlice) + (rowIndex * rowPitch), - alignedRowPitch); + rowPitch); + } - Uint32 offset = source->offset + (sliceIndex * bytesPerSlice) + ((rowsPerSlice - 1) * rowPitch); - SDL_memcpy( - temporaryBuffer->mapPointer + (sliceIndex * rowsPerSlice) + ((rowsPerSlice - 1) * alignedRowPitch), - transferBufferContainer->activeBuffer->mapPointer + offset, - SDL_min(alignedRowPitch, transferBufferContainer->size - offset)); sourceLocation.PlacedFootprint.Footprint.Width = destination->w; - sourceLocation.PlacedFootprint.Footprint.Height = rowsPerSlice; + sourceLocation.PlacedFootprint.Footprint.Height = destination->h; sourceLocation.PlacedFootprint.Footprint.Depth = 1; - sourceLocation.PlacedFootprint.Offset = (sliceIndex * bytesPerSlice); + sourceLocation.PlacedFootprint.Offset = (sliceIndex * alignedBytesPerSlice); ID3D12GraphicsCommandList_CopyTextureRegion( d3d12CommandBuffer->graphicsCommandList, &destinationLocation, destination->x, destination->y, - sliceIndex, + destination->z + sliceIndex, &sourceLocation, NULL); } @@ -5951,11 +6045,15 @@ static void D3D12_UploadToTexture( D3D12_INTERNAL_ReleaseBuffer( d3d12CommandBuffer->renderer, temporaryBuffer); + + if (d3d12CommandBuffer->renderer->debug_mode) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Texture upload row pitch not aligned to 256 bytes! This is suboptimal on D3D12!"); + } } else if (needsPlacementCopy) { temporaryBuffer = D3D12_INTERNAL_CreateBuffer( d3d12CommandBuffer->renderer, 0, - alignedRowPitch * destination->h * destination->d, + alignedRowPitch * blockHeight * destination->d, D3D12_BUFFER_TYPE_UPLOAD, NULL); @@ -5966,13 +6064,13 @@ static void D3D12_UploadToTexture( SDL_memcpy( temporaryBuffer->mapPointer, transferBufferContainer->activeBuffer->mapPointer + source->offset, - alignedRowPitch * destination->h * destination->d); + alignedRowPitch * blockHeight * destination->d); sourceLocation.pResource = temporaryBuffer->handle; sourceLocation.PlacedFootprint.Offset = 0; sourceLocation.PlacedFootprint.Footprint.Width = destination->w; sourceLocation.PlacedFootprint.Footprint.Height = destination->h; - sourceLocation.PlacedFootprint.Footprint.Depth = 1; + sourceLocation.PlacedFootprint.Footprint.Depth = destination->d; ID3D12GraphicsCommandList_CopyTextureRegion( d3d12CommandBuffer->graphicsCommandList, @@ -5988,7 +6086,9 @@ static void D3D12_UploadToTexture( d3d12CommandBuffer->renderer, temporaryBuffer); - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Texture upload offset not aligned to 512 bytes! This is suboptimal on D3D12!"); + if (d3d12CommandBuffer->renderer->debug_mode) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Texture upload offset not aligned to 512 bytes! This is suboptimal on D3D12!"); + } } else { sourceLocation.pResource = transferBufferContainer->activeBuffer->handle; sourceLocation.PlacedFootprint.Offset = source->offset; @@ -6170,6 +6270,7 @@ static void D3D12_DownloadFromTexture( const SDL_GPUTextureTransferInfo *destination) { D3D12CommandBuffer *d3d12CommandBuffer = (D3D12CommandBuffer *)commandBuffer; + D3D12Renderer *renderer = d3d12CommandBuffer->renderer; D3D12_TEXTURE_COPY_LOCATION sourceLocation; D3D12_TEXTURE_COPY_LOCATION destinationLocation; Uint32 pixelsPerRow = destination->pixels_per_row; @@ -6187,11 +6288,12 @@ static void D3D12_DownloadFromTexture( D3D12BufferContainer *destinationContainer = (D3D12BufferContainer *)destination->transfer_buffer; D3D12Buffer *destinationBuffer = destinationContainer->activeBuffer; - /* D3D12 requires texture data row pitch to be 256 byte aligned, which is obviously insane. - * Instead of exposing that restriction to the client, which is a huge rake to step on, - * and a restriction that no other backend requires, we're going to copy data to a temporary buffer, - * copy THAT data to the texture, and then get rid of the temporary buffer ASAP. - * If we're lucky and the row pitch and depth pitch are already aligned, we can skip all of that. + /* Unless the UnrestrictedBufferTextureCopyPitchSupported feature is supported, D3D12 requires + * texture data row pitch to be 256 byte aligned, which is obviously insane. Instead of exposing + * that restriction to the client, which is a huge rake to step on, and a restriction that no + * other backend requires, we're going to copy data to a temporary buffer, copy THAT data to the + * texture, and then get rid of the temporary buffer ASAP. If we're lucky and the row pitch and + * depth pitch are already aligned, we can skip all of that. * * D3D12 also requires offsets to be 512 byte aligned. We'll fix that for the client and warn them as well. * @@ -6211,9 +6313,15 @@ static void D3D12_DownloadFromTexture( rowsPerSlice = source->h; } - alignedRowPitch = D3D12_INTERNAL_Align(rowPitch, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); - needsRealignment = rowsPerSlice != source->h || rowPitch != alignedRowPitch; - needsPlacementCopy = destination->offset % D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT != 0; + if (renderer->UnrestrictedBufferTextureCopyPitchSupported) { + alignedRowPitch = rowPitch; + needsRealignment = false; + needsPlacementCopy = false; + } else { + alignedRowPitch = D3D12_INTERNAL_Align(rowPitch, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); + needsRealignment = rowsPerSlice != source->h || rowPitch != alignedRowPitch; + needsPlacementCopy = destination->offset % D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT != 0; + } sourceLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; sourceLocation.SubresourceIndex = sourceSubresource->index; @@ -6259,6 +6367,9 @@ static void D3D12_DownloadFromTexture( destinationLocation.pResource = textureDownload->temporaryBuffer->handle; destinationLocation.PlacedFootprint.Offset = 0; + if (d3d12CommandBuffer->renderer->debug_mode) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Texture pitch or offset not aligned properly! This is suboptimal on D3D12!"); + } } else { destinationLocation.pResource = destinationBuffer->handle; destinationLocation.PlacedFootprint.Offset = destination->offset; @@ -6601,9 +6712,7 @@ static void D3D12_INTERNAL_DestroySwapchain( { renderer->commandQueue->PresentX(0, NULL, NULL); for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) { - D3D12_INTERNAL_DestroyTexture( - renderer, - windowData->textureContainers[i].activeTexture); + D3D12_INTERNAL_DestroyTexture(windowData->textureContainers[i].activeTexture); } } @@ -6619,9 +6728,7 @@ static bool D3D12_INTERNAL_ResizeSwapchain( // Clean up the previous swapchain textures for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) { - D3D12_INTERNAL_DestroyTexture( - renderer, - windowData->textureContainers[i].activeTexture); + D3D12_INTERNAL_DestroyTexture(windowData->textureContainers[i].activeTexture); } // Create a new swapchain @@ -6760,10 +6867,8 @@ static bool D3D12_INTERNAL_ResizeSwapchain( // Release views and clean up for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) { D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &windowData->textureContainers[i].activeTexture->srvHandle); D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &windowData->textureContainers[i].activeTexture->subresources[0].rtvHandles[0]); SDL_free(windowData->textureContainers[i].activeTexture->subresources[0].rtvHandles); @@ -6811,10 +6916,8 @@ static void D3D12_INTERNAL_DestroySwapchain( // Release views and clean up for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) { D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &windowData->textureContainers[i].activeTexture->srvHandle); D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &windowData->textureContainers[i].activeTexture->subresources[0].rtvHandles[0]); SDL_free(windowData->textureContainers[i].activeTexture->subresources[0].rtvHandles); @@ -7004,6 +7107,8 @@ static bool D3D12_ClaimWindow( return false; } windowData->window = window; + windowData->renderer = renderer; + windowData->refcount = 1; if (D3D12_INTERNAL_CreateSwapchain(renderer, windowData, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_VSYNC)) { SDL_SetPointerProperty(SDL_GetWindowProperties(window), WINDOW_PROPERTY_DATA, windowData); @@ -7019,13 +7124,16 @@ static bool D3D12_ClaimWindow( renderer->claimedWindowCount += 1; SDL_UnlockMutex(renderer->windowLock); - SDL_AddEventWatch(D3D12_INTERNAL_OnWindowResize, window); + SDL_AddWindowEventWatch(SDL_WINDOW_EVENT_WATCH_NORMAL, D3D12_INTERNAL_OnWindowResize, window); return true; } else { SDL_free(windowData); - SET_STRING_ERROR_AND_RETURN("Could not create swapchain, failed to claim window!", false); + return false; } + } else if (windowData->renderer == renderer) { + ++windowData->refcount; + return true; } else { SET_STRING_ERROR_AND_RETURN("Window already claimed", false); } @@ -7039,7 +7147,15 @@ static void D3D12_ReleaseWindow( D3D12WindowData *windowData = D3D12_INTERNAL_FetchWindowData(window); if (windowData == NULL) { - SET_STRING_ERROR_AND_RETURN("Window already unclaimed!", ); + return; + } + if (windowData->renderer != renderer) { + SDL_SetError("Window not claimed by this device"); + return; + } + if (windowData->refcount > 1) { + --windowData->refcount; + return; } D3D12_Wait(driverData); @@ -7067,7 +7183,7 @@ static void D3D12_ReleaseWindow( SDL_free(windowData); SDL_ClearProperty(SDL_GetWindowProperties(window), WINDOW_PROPERTY_DATA); - SDL_RemoveEventWatch(D3D12_INTERNAL_OnWindowResize, window); + SDL_RemoveWindowEventWatch(SDL_WINDOW_EVENT_WATCH_NORMAL, D3D12_INTERNAL_OnWindowResize, window); } static bool D3D12_SetSwapchainParameters( @@ -7280,6 +7396,11 @@ static bool D3D12_INTERNAL_AllocateCommandBuffer( commandBuffer->usedComputePipelines = (D3D12ComputePipeline **)SDL_calloc( commandBuffer->usedComputePipelineCapacity, sizeof(D3D12ComputePipeline *)); + commandBuffer->usedDescriptorHeapCapacity = 4; + commandBuffer->usedDescriptorHeapCount = 0; + commandBuffer->usedDescriptorHeaps = (D3D12DescriptorHeap **)SDL_calloc( + commandBuffer->usedDescriptorHeapCapacity, sizeof(D3D12DescriptorHeap *)); + commandBuffer->usedUniformBufferCapacity = 4; commandBuffer->usedUniformBufferCount = 0; commandBuffer->usedUniformBuffers = (D3D12UniformBuffer **)SDL_calloc( @@ -7525,7 +7646,7 @@ static bool D3D12_INTERNAL_AcquireSwapchainTexture( 1, &barrierDesc); - *swapchainTexture = (SDL_GPUTexture*)&windowData->textureContainers[swapchainIndex]; + *swapchainTexture = (SDL_GPUTexture *)&windowData->textureContainers[swapchainIndex]; return true; } @@ -7568,7 +7689,6 @@ static void D3D12_INTERNAL_PerformPendingDestroys(D3D12Renderer *renderer) for (Sint32 i = renderer->buffersToDestroyCount - 1; i >= 0; i -= 1) { if (SDL_GetAtomicInt(&renderer->buffersToDestroy[i]->referenceCount) == 0) { D3D12_INTERNAL_DestroyBuffer( - renderer, renderer->buffersToDestroy[i]); renderer->buffersToDestroy[i] = renderer->buffersToDestroy[renderer->buffersToDestroyCount - 1]; @@ -7579,7 +7699,6 @@ static void D3D12_INTERNAL_PerformPendingDestroys(D3D12Renderer *renderer) for (Sint32 i = renderer->texturesToDestroyCount - 1; i >= 0; i -= 1) { if (SDL_GetAtomicInt(&renderer->texturesToDestroy[i]->referenceCount) == 0) { D3D12_INTERNAL_DestroyTexture( - renderer, renderer->texturesToDestroy[i]); renderer->texturesToDestroy[i] = renderer->texturesToDestroy[renderer->texturesToDestroyCount - 1]; @@ -7590,7 +7709,6 @@ static void D3D12_INTERNAL_PerformPendingDestroys(D3D12Renderer *renderer) for (Sint32 i = renderer->samplersToDestroyCount - 1; i >= 0; i -= 1) { if (SDL_GetAtomicInt(&renderer->samplersToDestroy[i]->referenceCount) == 0) { D3D12_INTERNAL_DestroySampler( - renderer, renderer->samplersToDestroy[i]); renderer->samplersToDestroy[i] = renderer->samplersToDestroy[renderer->samplersToDestroyCount - 1]; @@ -7701,13 +7819,13 @@ static bool D3D12_INTERNAL_CleanCommandBuffer( NULL); CHECK_D3D12_ERROR_AND_RETURN("Could not reset command list", false); - // Return descriptor heaps to pool - D3D12_INTERNAL_ReturnGPUDescriptorHeapToPool( - renderer, - commandBuffer->gpuDescriptorHeaps[D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV]); - D3D12_INTERNAL_ReturnGPUDescriptorHeapToPool( - renderer, - commandBuffer->gpuDescriptorHeaps[D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER]); + // Return descriptor heaps to pool, pools own their own locks + for (i = 0; i < commandBuffer->usedDescriptorHeapCount; i += 1) { + D3D12_INTERNAL_ReturnGPUDescriptorHeapToPool( + renderer, + commandBuffer->usedDescriptorHeaps[i]); + } + commandBuffer->usedDescriptorHeapCount = 0; commandBuffer->gpuDescriptorHeaps[D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV] = NULL; commandBuffer->gpuDescriptorHeaps[D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER] = NULL; @@ -7940,7 +8058,7 @@ static bool D3D12_Submit( ID3D12Resource_Release(windowData->textureContainers[presentData->swapchainImageIndex].activeTexture->resource); #endif - windowData->inFlightFences[windowData->frameCounter] = (SDL_GPUFence*)d3d12CommandBuffer->inFlightFence; + windowData->inFlightFences[windowData->frameCounter] = (SDL_GPUFence *)d3d12CommandBuffer->inFlightFence; (void)SDL_AtomicIncRef(&d3d12CommandBuffer->inFlightFence->referenceCount); windowData->frameCounter = (windowData->frameCounter + 1) % renderer->allowedFramesInFlight; } @@ -8338,21 +8456,34 @@ static void D3D12_INTERNAL_InitBlitResources( } } -static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) +static bool D3D12_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) { #if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) return true; #else SDL_SharedObject *d3d12Dll; SDL_SharedObject *dxgiDll; - PFN_D3D12_CREATE_DEVICE D3D12CreateDeviceFunc; - PFN_CREATE_DXGI_FACTORY1 CreateDXGIFactoryFunc; + PFN_D3D12_CREATE_DEVICE pD3D12CreateDevice; + pfnCreateDXGIFactory1 pCreateDXGIFactory1; HRESULT res; ID3D12Device *device; IDXGIFactory1 *factory; IDXGIFactory4 *factory4; IDXGIFactory6 *factory6; IDXGIAdapter1 *adapter; + bool supports_64UAVs = false; + bool needs_64UAVs = !SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN, false); + + // Early check to see if the app has _any_ D3D12 formats, if not we don't + // have to fuss with loading D3D in the first place. + bool has_dxbc = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN, false); + bool has_dxil = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN, false); + bool supports_dxil = false; + // TODO SM7: bool has_spirv = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, false); + // TODO SM7: bool supports_spirv = false; + if (!has_dxbc && !has_dxil) { + return false; + } // Can we load D3D12? @@ -8362,10 +8493,10 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) return false; } - D3D12CreateDeviceFunc = (PFN_D3D12_CREATE_DEVICE)SDL_LoadFunction( + pD3D12CreateDevice = (PFN_D3D12_CREATE_DEVICE)SDL_LoadFunction( d3d12Dll, D3D12_CREATE_DEVICE_FUNC); - if (D3D12CreateDeviceFunc == NULL) { + if (pD3D12CreateDevice == NULL) { SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: Could not find function " D3D12_CREATE_DEVICE_FUNC " in " D3D12_DLL); SDL_UnloadObject(d3d12Dll); return false; @@ -8379,10 +8510,10 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) return false; } - CreateDXGIFactoryFunc = (PFN_CREATE_DXGI_FACTORY1)SDL_LoadFunction( + pCreateDXGIFactory1 = (pfnCreateDXGIFactory1)SDL_LoadFunction( dxgiDll, CREATE_DXGI_FACTORY1_FUNC); - if (CreateDXGIFactoryFunc == NULL) { + if (pCreateDXGIFactory1 == NULL) { SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: Could not find function " CREATE_DXGI_FACTORY1_FUNC " in " DXGI_DLL); SDL_UnloadObject(dxgiDll); return false; @@ -8391,7 +8522,7 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) // Can we create a device? // Create the DXGI factory - res = CreateDXGIFactoryFunc( + res = pCreateDXGIFactory1( &D3D_IID_IDXGIFactory1, (void **)&factory); if (FAILED(res)) { @@ -8441,21 +8572,76 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) return false; } - res = D3D12CreateDeviceFunc( + SDL_COMPILE_TIME_ASSERT(featurelevel, D3D_FEATURE_LEVEL_CHOICE < D3D_FEATURE_LEVEL_11_1); + + /* If Windows 11 is running and the app has neither DXIL nor TIER2 + * requirements, we can skip doing any device checks entirely + */ + if (!needs_64UAVs && !has_dxil && WIN_IsWindows11OrGreater()) { + IDXGIAdapter1_Release(adapter); + IDXGIFactory1_Release(factory); + + SDL_UnloadObject(d3d12Dll); + SDL_UnloadObject(dxgiDll); + + return true; + } + + res = pD3D12CreateDevice( (IUnknown *)adapter, D3D_FEATURE_LEVEL_CHOICE, D3D_GUID(D3D_IID_ID3D12Device), (void **)&device); - if (SUCCEEDED(res)) { + // Only check for Tier 2 resource binding if the app needs it + if (needs_64UAVs) { + D3D12_FEATURE_DATA_D3D12_OPTIONS featureOptions; + SDL_zero(featureOptions); + + res = ID3D12Device_CheckFeatureSupport( + device, + D3D12_FEATURE_D3D12_OPTIONS, + &featureOptions, + sizeof(featureOptions)); + if (SUCCEEDED(res) && featureOptions.ResourceBindingTier >= D3D12_RESOURCE_BINDING_TIER_2) { + supports_64UAVs = true; + } + } + + // Only check for SM6 support if DXIL is provided + if (has_dxil) { + D3D12_FEATURE_DATA_SHADER_MODEL shaderModel; + shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_0; + + res = ID3D12Device_CheckFeatureSupport( + device, + D3D12_FEATURE_SHADER_MODEL, + &shaderModel, + sizeof(shaderModel)); + if (SUCCEEDED(res) && shaderModel.HighestShaderModel >= D3D_SHADER_MODEL_6_0) { + supports_dxil = true; + } + } + ID3D12Device_Release(device); } + IDXGIAdapter1_Release(adapter); IDXGIFactory1_Release(factory); SDL_UnloadObject(d3d12Dll); SDL_UnloadObject(dxgiDll); + if (!supports_64UAVs && needs_64UAVs) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: Tier 2 Resource Binding is not supported"); + return false; + } + + if (!supports_dxil && !has_dxbc) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: DXIL is not supported and DXBC is not being provided"); + return false; + } + if (FAILED(res)) { SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: Could not create D3D12Device with feature level " D3D_FEATURE_LEVEL_CHOICE_STR); return false; @@ -8468,7 +8654,7 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) #if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) && defined(HAVE_IDXGIINFOQUEUE) static bool D3D12_INTERNAL_TryInitializeDXGIDebug(D3D12Renderer *renderer) { - PFN_DXGI_GET_DEBUG_INTERFACE DXGIGetDebugInterfaceFunc; + pfnDXGIGetDebugInterface pDXGIGetDebugInterface; HRESULT res; renderer->dxgidebug_dll = SDL_LoadObject(DXGIDEBUG_DLL); @@ -8476,19 +8662,19 @@ static bool D3D12_INTERNAL_TryInitializeDXGIDebug(D3D12Renderer *renderer) return false; } - DXGIGetDebugInterfaceFunc = (PFN_DXGI_GET_DEBUG_INTERFACE)SDL_LoadFunction( + pDXGIGetDebugInterface = (pfnDXGIGetDebugInterface)SDL_LoadFunction( renderer->dxgidebug_dll, DXGI_GET_DEBUG_INTERFACE_FUNC); - if (DXGIGetDebugInterfaceFunc == NULL) { + if (pDXGIGetDebugInterface == NULL) { return false; } - res = DXGIGetDebugInterfaceFunc(&D3D_IID_IDXGIDebug, (void **)&renderer->dxgiDebug); + res = pDXGIGetDebugInterface(&D3D_IID_IDXGIDebug, (void **)&renderer->dxgiDebug); if (FAILED(res)) { return false; } - res = DXGIGetDebugInterfaceFunc(&D3D_IID_IDXGIInfoQueue, (void **)&renderer->dxgiInfoQueue); + res = pDXGIGetDebugInterface(&D3D_IID_IDXGIInfoQueue, (void **)&renderer->dxgiInfoQueue); if (FAILED(res)) { return false; } @@ -8497,19 +8683,35 @@ static bool D3D12_INTERNAL_TryInitializeDXGIDebug(D3D12Renderer *renderer) } #endif -static bool D3D12_INTERNAL_TryInitializeD3D12Debug(D3D12Renderer *renderer) -{ - PFN_D3D12_GET_DEBUG_INTERFACE D3D12GetDebugInterfaceFunc; +static bool D3D12_INTERNAL_TryInitializeD3D12Debug(D3D12Renderer *renderer +#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + , ID3D12DeviceFactory * factory +#endif +) { + PFN_D3D12_GET_DEBUG_INTERFACE pD3D12GetDebugInterface; HRESULT res; - D3D12GetDebugInterfaceFunc = (PFN_D3D12_GET_DEBUG_INTERFACE)SDL_LoadFunction( +#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + if (factory) { + res = ID3D12DeviceFactory_GetConfigurationInterface(factory, &D3D_CLSID_ID3D12Debug, &D3D_IID_ID3D12Debug, (void **)&renderer->d3d12Debug); + + if (FAILED(res)) { + return false; + } + + ID3D12Debug_EnableDebugLayer(renderer->d3d12Debug); + return true; + } +#endif + + pD3D12GetDebugInterface = (PFN_D3D12_GET_DEBUG_INTERFACE)SDL_LoadFunction( renderer->d3d12_dll, D3D12_GET_DEBUG_INTERFACE_FUNC); - if (D3D12GetDebugInterfaceFunc == NULL) { + if (pD3D12GetDebugInterface == NULL) { return false; } - res = D3D12GetDebugInterfaceFunc(D3D_GUID(D3D_IID_ID3D12Debug), (void **)&renderer->d3d12Debug); + res = pD3D12GetDebugInterface(D3D_GUID(D3D_IID_ID3D12Debug), (void **)&renderer->d3d12Debug); if (FAILED(res)) { return false; } @@ -8672,19 +8874,26 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD PFN_D3D12_XBOX_CREATE_DEVICE D3D12XboxCreateDeviceFunc; D3D12XBOX_CREATE_DEVICE_PARAMETERS createDeviceParams; #else - PFN_CREATE_DXGI_FACTORY1 CreateDXGIFactoryFunc; + pfnCreateDXGIFactory1 pCreateDXGIFactory1; IDXGIFactory1 *factory1; IDXGIFactory5 *factory5; IDXGIFactory6 *factory6; DXGI_ADAPTER_DESC1 adapterDesc; LARGE_INTEGER umdVersion; - PFN_D3D12_CREATE_DEVICE D3D12CreateDeviceFunc; + PFN_D3D12_GET_INTERFACE pD3D12GetInterface; + PFN_D3D12_CREATE_DEVICE pD3D12CreateDevice; #endif D3D12_FEATURE_DATA_ARCHITECTURE architecture; D3D12_COMMAND_QUEUE_DESC queueDesc; + bool verboseLogs = SDL_GetBooleanProperty( + props, + SDL_PROP_GPU_DEVICE_CREATE_VERBOSE_BOOLEAN, + true); + renderer = (D3D12Renderer *)SDL_calloc(1, sizeof(D3D12Renderer)); + bool hasDxgiDebug = false; #if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) // Load the DXGI library renderer->dxgi_dll = SDL_LoadObject(DXGI_DLL); @@ -8695,25 +8904,50 @@ 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) { hasDxgiDebug = D3D12_INTERNAL_TryInitializeDXGIDebug(renderer); } #else - bool hasDxgiDebug = true; + hasDxgiDebug = true; +#endif + +#ifdef USE_PIX_RUNTIME + // Load the PIX runtime DLL so that we can set D3D12 debug events on command lists + renderer->winpixeventruntime_dll = SDL_LoadObject(WINPIXEVENTRUNTIME_DLL); + WinPixEventRuntimeFns *fns = &renderer->winpixeventruntimeFns; + if (renderer->winpixeventruntime_dll) { + // Load the specific functions we need from the PIX runtime + fns->pBeginEventOnCommandList = (pfnBeginEventOnCommandList)SDL_LoadFunction( + renderer->winpixeventruntime_dll, + PIX_BEGIN_EVENT_ON_COMMAND_LIST_FUNC); + fns->pEndEventOnCommandList = (pfnEndEventOnCommandList)SDL_LoadFunction( + renderer->winpixeventruntime_dll, + PIX_END_EVENT_ON_COMMAND_LIST_FUNC); + fns->pSetMarkerOnCommandList = (pfnSetMarkerOnCommandList)SDL_LoadFunction( + renderer->winpixeventruntime_dll, + PIX_SET_MARKER_ON_COMMAND_LIST_FUNC); + } else { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, + "WinPixEventRuntime.dll is not available. " + "It is required for SDL_Push/PopGPUDebugGroup and SDL_InsertGPUDebugLabel to function correctly. " + "See here for instructions on how to obtain it: https://devblogs.microsoft.com/pix/winpixeventruntime/"); + fns->pBeginEventOnCommandList = NULL; + fns->pEndEventOnCommandList = NULL; + fns->pSetMarkerOnCommandList = NULL; + } #endif // Load the CreateDXGIFactory1 function - CreateDXGIFactoryFunc = (PFN_CREATE_DXGI_FACTORY1)SDL_LoadFunction( + pCreateDXGIFactory1 = (pfnCreateDXGIFactory1)SDL_LoadFunction( renderer->dxgi_dll, CREATE_DXGI_FACTORY1_FUNC); - if (CreateDXGIFactoryFunc == NULL) { + if (pCreateDXGIFactory1 == NULL) { D3D12_INTERNAL_DestroyRenderer(renderer); SET_STRING_ERROR_AND_RETURN("Could not load function: " CREATE_DXGI_FACTORY1_FUNC, NULL); } // Create the DXGI factory - res = CreateDXGIFactoryFunc( + res = pCreateDXGIFactory1( &D3D_IID_IDXGIFactory1, (void **)&factory1); if (FAILED(res)) { @@ -8786,15 +9020,39 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD CHECK_D3D12_ERROR_AND_RETURN("Could not get adapter driver version", NULL); } - SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: D3D12"); - SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "D3D12 Adapter: %S", adapterDesc.Description); - SDL_LogInfo( - SDL_LOG_CATEGORY_GPU, - "D3D12 Driver: %d.%d.%d.%d", + renderer->props = SDL_CreateProperties(); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: D3D12"); + } + + // Record device name + char *deviceName = SDL_iconv_wchar_utf8(&adapterDesc.Description[0]); + SDL_SetStringProperty( + renderer->props, + SDL_PROP_GPU_DEVICE_NAME_STRING, + deviceName); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "D3D12 Adapter: %s", deviceName); + } + SDL_free(deviceName); + + // Record driver version + char driverVer[64]; + (void)SDL_snprintf( + driverVer, + SDL_arraysize(driverVer), + "%d.%d.%d.%d", HIWORD(umdVersion.HighPart), LOWORD(umdVersion.HighPart), HIWORD(umdVersion.LowPart), LOWORD(umdVersion.LowPart)); + SDL_SetStringProperty( + renderer->props, + SDL_PROP_GPU_DEVICE_DRIVER_VERSION_STRING, + driverVer); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "D3D12 Driver: %s", driverVer); + } #endif // Load the D3D library @@ -8814,26 +9072,66 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD SET_STRING_ERROR_AND_RETURN("Could not load function: D3D12XboxCreateDevice", NULL); } #else - D3D12CreateDeviceFunc = (PFN_D3D12_CREATE_DEVICE)SDL_LoadFunction( + pD3D12CreateDevice = (PFN_D3D12_CREATE_DEVICE)SDL_LoadFunction( renderer->d3d12_dll, D3D12_CREATE_DEVICE_FUNC); - if (D3D12CreateDeviceFunc == NULL) { + if (pD3D12CreateDevice == NULL) { D3D12_INTERNAL_DestroyRenderer(renderer); SET_STRING_ERROR_AND_RETURN("Could not load function: " D3D12_CREATE_DEVICE_FUNC, NULL); } #endif - renderer->D3D12SerializeRootSignature_func = (PFN_D3D12_SERIALIZE_ROOT_SIGNATURE)SDL_LoadFunction( + renderer->pD3D12SerializeRootSignature = (PFN_D3D12_SERIALIZE_ROOT_SIGNATURE)SDL_LoadFunction( renderer->d3d12_dll, D3D12_SERIALIZE_ROOT_SIGNATURE_FUNC); - if (renderer->D3D12SerializeRootSignature_func == NULL) { + if (renderer->pD3D12SerializeRootSignature == NULL) { D3D12_INTERNAL_DestroyRenderer(renderer); SET_STRING_ERROR_AND_RETURN("Could not load function: " D3D12_SERIALIZE_ROOT_SIGNATURE_FUNC, NULL); } +#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + // A device factory allows a D3D12 redistributable provided by the client to be loaded. + ID3D12DeviceFactory *factory = NULL; + + if (SDL_HasProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_PATH_STRING) && SDL_HasProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_VERSION_NUMBER)) { + int d3d12SDKVersion = SDL_GetNumberProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_VERSION_NUMBER, 0); + const char *d3d12SDKPath = SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_PATH_STRING, ".\\D3D12\\"); + + pD3D12GetInterface = (PFN_D3D12_GET_INTERFACE)SDL_LoadFunction( + renderer->d3d12_dll, + D3D12_GET_INTERFACE_FUNC); + if (pD3D12GetInterface == NULL) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not load D3D12GetInterface, custom D3D12 SDK will not load."); + } + + ID3D12SDKConfiguration *sdk_config = NULL; + + if (SUCCEEDED(pD3D12GetInterface(D3D_GUID(D3D_CLSID_ID3D12SDKConfiguration), D3D_GUID(D3D_IID_ID3D12SDKConfiguration), (void**) &sdk_config))) { + ID3D12SDKConfiguration1 *sdk_config1 = NULL; + if (SUCCEEDED(IUnknown_QueryInterface(sdk_config, &D3D_IID_ID3D12SDKConfiguration1, (void**) &sdk_config1))) { + if (SUCCEEDED(ID3D12SDKConfiguration1_CreateDeviceFactory(sdk_config1, d3d12SDKVersion, d3d12SDKPath, &D3D_IID_ID3D12DeviceFactory, (void**) &factory))) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Loaded vendored D3D12Core.dll"); + } else { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Failed to load vendored D3D12Core.dll"); + } + + ID3D12SDKConfiguration1_Release(sdk_config1); + } + + ID3D12SDKConfiguration_Release(sdk_config); + } else { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Failed to load vendored D3D12 SDK Configuration"); + } + } +#endif + // Initialize the D3D12 debug layer, if applicable if (debugMode) { - bool hasD3d12Debug = D3D12_INTERNAL_TryInitializeD3D12Debug(renderer); + bool hasD3d12Debug = D3D12_INTERNAL_TryInitializeD3D12Debug(renderer +#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + , factory +#endif + ); #if (defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) if (hasD3d12Debug) { SDL_LogInfo( @@ -8887,15 +9185,33 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD s_Device = renderer->device; } #else - res = D3D12CreateDeviceFunc( - (IUnknown *)renderer->adapter, - D3D_FEATURE_LEVEL_CHOICE, - D3D_GUID(D3D_IID_ID3D12Device), - (void **)&renderer->device); - if (FAILED(res)) { - D3D12_INTERNAL_DestroyRenderer(renderer); - CHECK_D3D12_ERROR_AND_RETURN("Could not create D3D12Device", NULL); + if (factory) { + ID3D12DeviceFactory_SetFlags(factory, D3D12_DEVICE_FACTORY_FLAG_ALLOW_RETURNING_EXISTING_DEVICE); + res = ID3D12DeviceFactory_CreateDevice( + factory, + (IUnknown *)renderer->adapter, + D3D_FEATURE_LEVEL_CHOICE, + D3D_GUID(D3D_IID_ID3D12Device), + (void**)&renderer->device); + + ID3D12DeviceFactory_Release(factory); + + if (FAILED(res)) { + D3D12_INTERNAL_DestroyRenderer(renderer); + CHECK_D3D12_ERROR_AND_RETURN("Could not create D3D12Device", NULL); + } + } else { + res = pD3D12CreateDevice( + (IUnknown *)renderer->adapter, + D3D_FEATURE_LEVEL_CHOICE, + D3D_GUID(D3D_IID_ID3D12Device), + (void **)&renderer->device); + + if (FAILED(res)) { + D3D12_INTERNAL_DestroyRenderer(renderer); + CHECK_D3D12_ERROR_AND_RETURN("Could not create D3D12Device", NULL); + } } // Initialize the D3D12 debug info queue, if applicable @@ -8937,6 +9253,22 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD } #endif + // Check for unrestricted texture-buffer copy pitch support + D3D12_FEATURE_DATA_D3D12_OPTIONS13 options13; + res = ID3D12Device_CheckFeatureSupport( + renderer->device, + D3D12_FEATURE_D3D12_OPTIONS13, + &options13, + sizeof(options13)); + + if (SUCCEEDED(res)) { + renderer->UnrestrictedBufferTextureCopyPitchSupported = options13.UnrestrictedBufferTextureCopyPitchSupported; + } + else + { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "CheckFeatureSupport for UnrestrictedBufferTextureCopyPitchSupported failed. You may need to provide a vendored D3D12Core.dll through the Agility SDK on older platforms."); + } + // Create command queue #if (defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) if (s_CommandQueue != NULL) { @@ -9146,7 +9478,7 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD renderer->debug_mode = debugMode; renderer->allowedFramesInFlight = 2; - renderer->semantic = SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING, "TEXCOORD"); + renderer->semantic = SDL_strdup(SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING, "TEXCOORD")); // Blit resources D3D12_INTERNAL_InitBlitResources(renderer); @@ -9206,7 +9538,6 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD SDL_GPUBootstrap D3D12Driver = { "direct3d12", - SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_DXBC, D3D12_PrepareDriver, D3D12_CreateDevice }; diff --git a/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m b/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m index 591bd05..c871388 100644 --- a/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m +++ b/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -31,7 +31,7 @@ // Defines #define METAL_FIRST_VERTEX_BUFFER_SLOT 14 -#define WINDOW_PROPERTY_DATA "SDL_GPUMetalWindowPropertyData" +#define WINDOW_PROPERTY_DATA "SDL.internal.gpu.metal.data" #define SDL_GPU_SHADERSTAGE_COMPUTE 2 #define TRACK_RESOURCE(resource, type, array, count, capacity) \ @@ -423,6 +423,8 @@ static MTLDepthClipMode SDLToMetal_DepthClipMode( // Structs +typedef struct MetalRenderer MetalRenderer; + typedef struct MetalTexture { id handle; @@ -452,6 +454,8 @@ typedef struct MetalFence typedef struct MetalWindowData { SDL_Window *window; + MetalRenderer *renderer; + int refcount; SDL_MetalView view; CAMetalLayer *layer; SDL_GPUPresentMode presentMode; @@ -523,8 +527,6 @@ typedef struct MetalUniformBuffer Uint32 drawOffset; } MetalUniformBuffer; -typedef struct MetalRenderer MetalRenderer; - typedef struct MetalCommandBuffer { CommandBufferCommonHeader common; @@ -631,6 +633,7 @@ struct MetalRenderer id queue; bool debugMode; + SDL_PropertiesID props; Uint32 allowedFramesInFlight; MetalWindowData **claimedWindows; @@ -753,11 +756,20 @@ static void METAL_DestroyDevice(SDL_GPUDevice *device) // Release the command queue renderer->queue = nil; + // Release properties + SDL_DestroyProperties(renderer->props); + // Free the primary structures SDL_free(renderer); SDL_free(device); } +static SDL_PropertiesID METAL_GetDeviceProperties(SDL_GPUDevice *device) +{ + MetalRenderer *renderer = (MetalRenderer *)device->driverData; + return renderer->props; +} + // Resource tracking static void METAL_INTERNAL_TrackBuffer( @@ -814,6 +826,17 @@ typedef struct MetalLibraryFunction id function; } MetalLibraryFunction; +static bool METAL_INTERNAL_IsValidMetalLibrary( + const Uint8 *code, + size_t codeSize) +{ + // Metal libraries have a 4 byte header containing `MTLB`. + if (codeSize < 4 || code == NULL) { + return false; + } + return SDL_memcmp(code, "MTLB", 4) == 0; +} + // This function assumes that it's called from within an autorelease pool static MetalLibraryFunction METAL_INTERNAL_CompileShader( MetalRenderer *renderer, @@ -842,6 +865,11 @@ static MetalLibraryFunction METAL_INTERNAL_CompileShader( options:nil error:&error]; } else if (format == SDL_GPU_SHADERFORMAT_METALLIB) { + if (!METAL_INTERNAL_IsValidMetalLibrary(code, codeSize)) { + SET_STRING_ERROR_AND_RETURN( + "The provided shader code is not a valid Metal library!", + libraryFunction); + } data = dispatch_data_create( code, codeSize, @@ -1090,6 +1118,13 @@ static SDL_GPUGraphicsPipeline *METAL_CreateGraphicsPipeline( SDL_assert_release(!"CreateGraphicsPipeline was passed a vertex shader for the fragment stage"); } } +#ifdef SDL_PLATFORM_VISIONOS + // The default is depth clipping enabled and it can't be changed + if (!createinfo->rasterizer_state.enable_depth_clip) { + SDL_assert_release(!"Rasterizer state enable_depth_clip must be true on this platform"); + } +#endif + pipelineDescriptor = [MTLRenderPipelineDescriptor new]; // Blend @@ -1114,6 +1149,7 @@ static SDL_GPUGraphicsPipeline *METAL_CreateGraphicsPipeline( // Multisample pipelineDescriptor.rasterSampleCount = SDLToMetal_SampleCount[createinfo->multisample_state.sample_count]; + pipelineDescriptor.alphaToCoverageEnabled = createinfo->multisample_state.enable_alpha_to_coverage; // Depth Stencil @@ -1778,7 +1814,8 @@ static void METAL_UploadToTexture( copyFromBuffer:bufferContainer->activeBuffer->handle sourceOffset:source->offset sourceBytesPerRow:BytesPerRow(destination->w, textureContainer->header.info.format) - sourceBytesPerImage:SDL_CalculateGPUTextureFormatSize(textureContainer->header.info.format, destination->w, destination->h, destination->d) + // sourceBytesPerImage expects the stride between 2D images (slices) of a 3D texture, not the size of the entire region + sourceBytesPerImage:SDL_CalculateGPUTextureFormatSize(textureContainer->header.info.format, destination->w, destination->h, 1) sourceSize:MTLSizeMake(destination->w, destination->h, destination->d) toTexture:metalTexture->handle destinationSlice:destination->layer @@ -2292,6 +2329,8 @@ static void METAL_BeginRenderPass( depthStencilTargetInfo->cycle); passDescriptor.depthAttachment.texture = texture->handle; + passDescriptor.depthAttachment.level = depthStencilTargetInfo->mip_level; + passDescriptor.depthAttachment.slice = depthStencilTargetInfo->layer; passDescriptor.depthAttachment.loadAction = SDLToMetal_LoadOp[depthStencilTargetInfo->load_op]; passDescriptor.depthAttachment.storeAction = SDLToMetal_StoreOp[depthStencilTargetInfo->store_op]; passDescriptor.depthAttachment.clearDepth = depthStencilTargetInfo->clear_depth; @@ -2325,8 +2364,8 @@ static void METAL_BeginRenderPass( if (depthStencilTargetInfo != NULL) { MetalTextureContainer *container = (MetalTextureContainer *)depthStencilTargetInfo->texture; - Uint32 w = container->header.info.width; - Uint32 h = container->header.info.height; + Uint32 w = container->header.info.width >> depthStencilTargetInfo->mip_level; + Uint32 h = container->header.info.height >> depthStencilTargetInfo->mip_level; if (w < vpWidth) { vpWidth = w; @@ -2372,6 +2411,7 @@ static void METAL_BindGraphicsPipeline( { @autoreleasepool { MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer; + MetalGraphicsPipeline *previousPipeline = metalCommandBuffer->graphics_pipeline; MetalGraphicsPipeline *pipeline = (MetalGraphicsPipeline *)graphicsPipeline; SDL_GPURasterizerState *rast = &pipeline->rasterizerState; Uint32 i; @@ -2384,7 +2424,9 @@ static void METAL_BindGraphicsPipeline( [metalCommandBuffer->renderEncoder setTriangleFillMode:SDLToMetal_PolygonMode[pipeline->rasterizerState.fill_mode]]; [metalCommandBuffer->renderEncoder setCullMode:SDLToMetal_CullMode[pipeline->rasterizerState.cull_mode]]; [metalCommandBuffer->renderEncoder setFrontFacingWinding:SDLToMetal_FrontFace[pipeline->rasterizerState.front_face]]; +#ifndef SDL_PLATFORM_VISIONOS [metalCommandBuffer->renderEncoder setDepthClipMode:SDLToMetal_DepthClipMode(pipeline->rasterizerState.enable_depth_clip)]; +#endif [metalCommandBuffer->renderEncoder setDepthBias:((rast->enable_depth_bias) ? rast->depth_bias_constant_factor : 0) slopeScale:((rast->enable_depth_bias) ? rast->depth_bias_slope_factor : 0) @@ -2414,6 +2456,17 @@ static void METAL_BindGraphicsPipeline( metalCommandBuffer); } } + + if (previousPipeline && previousPipeline != pipeline) { + // if the number of uniform buffers has changed, the storage buffers will move as well + // and need a rebind at their new locations + if (previousPipeline->header.num_vertex_uniform_buffers != pipeline->header.num_vertex_uniform_buffers) { + metalCommandBuffer->needVertexStorageBufferBind = true; + } + if (previousPipeline->header.num_fragment_uniform_buffers != pipeline->header.num_fragment_uniform_buffers) { + metalCommandBuffer->needFragmentStorageBufferBind = true; + } + } } } @@ -3594,7 +3647,7 @@ static bool METAL_SupportsSwapchainComposition( } // This function assumes that it's called from within an autorelease pool -static Uint8 METAL_INTERNAL_CreateSwapchain( +static bool METAL_INTERNAL_CreateSwapchain( MetalRenderer *renderer, MetalWindowData *windowData, SDL_GPUSwapchainComposition swapchainComposition, @@ -3666,7 +3719,7 @@ static Uint8 METAL_INTERNAL_CreateSwapchain( windowData->textureContainer.header.info.width = (Uint32)drawableSize.width; windowData->textureContainer.header.info.height = (Uint32)drawableSize.height; - return 1; + return true; } static bool METAL_SupportsPresentMode( @@ -3696,6 +3749,8 @@ static bool METAL_ClaimWindow( if (windowData == NULL) { windowData = (MetalWindowData *)SDL_calloc(1, sizeof(MetalWindowData)); windowData->window = window; + windowData->renderer = renderer; + windowData->refcount = 1; if (METAL_INTERNAL_CreateSwapchain(renderer, windowData, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_VSYNC)) { SDL_SetPointerProperty(SDL_GetWindowProperties(window), WINDOW_PROPERTY_DATA, windowData); @@ -3716,10 +3771,13 @@ static bool METAL_ClaimWindow( return true; } else { SDL_free(windowData); - SET_STRING_ERROR_AND_RETURN("Could not create swapchain, failed to claim window", false); + return false; } + } else if (windowData->renderer == renderer) { + ++windowData->refcount; + return true; } else { - SET_ERROR_AND_RETURN("%s", "Window already claimed!", false); + SET_STRING_ERROR_AND_RETURN("Window already claimed", false); } } } @@ -3733,7 +3791,15 @@ static void METAL_ReleaseWindow( MetalWindowData *windowData = METAL_INTERNAL_FetchWindowData(window); if (windowData == NULL) { - SET_STRING_ERROR_AND_RETURN("Window is not claimed by this SDL_GpuDevice", ); + return; + } + if (windowData->renderer != renderer) { + SDL_SetError("Window not claimed by this device"); + return; + } + if (windowData->refcount > 1) { + --windowData->refcount; + return; } METAL_Wait(driverData); @@ -3812,7 +3878,7 @@ static bool METAL_INTERNAL_AcquireSwapchainTexture( windowData = METAL_INTERNAL_FetchWindowData(window); if (windowData == NULL) { - SET_STRING_ERROR_AND_RETURN("Window is not claimed by this SDL_GpuDevice", false); + SET_STRING_ERROR_AND_RETURN("Window is not claimed by this SDL_GPUDevice", false); } // Update the window size @@ -4269,8 +4335,13 @@ static bool METAL_SupportsTextureFormat( // Device Creation -static bool METAL_PrepareDriver(SDL_VideoDevice *this) +static bool METAL_PrepareDriver(SDL_VideoDevice *this, SDL_PropertiesID props) { + if (!SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN, false) && + !SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN, false)) { + return false; + } + if (@available(macOS 10.14, iOS 13.0, tvOS 13.0, *)) { return (this->Metal_CreateView != NULL); } @@ -4435,6 +4506,11 @@ static SDL_GPUDevice *METAL_CreateDevice(bool debugMode, bool preferLowPower, SD id device = NULL; bool hasHardwareSupport = false; + bool verboseLogs = SDL_GetBooleanProperty( + props, + SDL_PROP_GPU_DEVICE_CREATE_VERBOSE_BOOLEAN, + true); + if (debugMode) { /* Due to a Metal driver quirk, once a MTLDevice has been created * with this environment variable set, the Metal validation layers @@ -4466,11 +4542,21 @@ static SDL_GPUDevice *METAL_CreateDevice(bool debugMode, bool preferLowPower, SD #ifdef SDL_PLATFORM_MACOS hasHardwareSupport = true; + bool allowMacFamily1 = SDL_GetBooleanProperty( + props, + SDL_PROP_GPU_DEVICE_CREATE_METAL_ALLOW_MACFAMILY1_BOOLEAN, + false); if (@available(macOS 10.15, *)) { - hasHardwareSupport = [device supportsFamily:MTLGPUFamilyMac2]; + hasHardwareSupport = allowMacFamily1 ? + [device supportsFamily:MTLGPUFamilyMac1] : + [device supportsFamily:MTLGPUFamilyMac2]; } else if (@available(macOS 10.14, *)) { - hasHardwareSupport = [device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1]; + hasHardwareSupport = allowMacFamily1 ? + [device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v4] : + [device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1]; } +#elif defined(SDL_PLATFORM_VISIONOS) + hasHardwareSupport = true; #else if (@available(iOS 13.0, tvOS 13.0, *)) { hasHardwareSupport = [device supportsFamily:MTLGPUFamilyApple3]; @@ -4488,12 +4574,20 @@ static SDL_GPUDevice *METAL_CreateDevice(bool debugMode, bool preferLowPower, SD renderer->device = device; renderer->queue = [device newCommandQueue]; - // Print driver info - SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: Metal"); - SDL_LogInfo( - SDL_LOG_CATEGORY_GPU, - "Metal Device: %s", - [device.name UTF8String]); + renderer->props = SDL_CreateProperties(); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: Metal"); + } + + // Record device name + const char *deviceName = [device.name UTF8String]; + SDL_SetStringProperty( + renderer->props, + SDL_PROP_GPU_DEVICE_NAME_STRING, + deviceName); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Metal Device: %s", deviceName); + } // Remember debug mode renderer->debugMode = debugMode; @@ -4568,7 +4662,6 @@ static SDL_GPUDevice *METAL_CreateDevice(bool debugMode, bool preferLowPower, SD SDL_GPUBootstrap MetalDriver = { "metal", - SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_METALLIB, METAL_PrepareDriver, METAL_CreateDevice }; diff --git a/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c b/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c index 547b7ca..ea75c15 100644 --- a/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -32,8 +32,7 @@ #include #include "../SDL_sysgpu.h" - -#define VULKAN_INTERNAL_clamp(val, min, max) SDL_max(min, SDL_min(val, max)) +#include "../../events/SDL_windowevents_c.h" // Global Vulkan Loader Entry Points @@ -58,6 +57,8 @@ typedef struct VulkanExtensions Uint8 KHR_driver_properties; // Only required for special implementations (i.e. MoltenVK) Uint8 KHR_portability_subset; + // Only required to detect devices using Dozen D3D12 driver + Uint8 MSFT_layered_driver; // Only required for decoding HDR ASTC textures Uint8 EXT_texture_compression_astc_hdr; } VulkanExtensions; @@ -69,7 +70,7 @@ typedef struct VulkanExtensions #define LARGE_ALLOCATION_INCREMENT 67108864 // 64 MiB #define MAX_UBO_SECTION_SIZE 4096 // 4 KiB #define DESCRIPTOR_POOL_SIZE 128 -#define WINDOW_PROPERTY_DATA "SDL_GPUVulkanWindowPropertyData" +#define WINDOW_PROPERTY_DATA "SDL.internal.gpu.vulkan.data" #define IDENTITY_SWIZZLE \ { \ @@ -79,51 +80,8 @@ typedef struct VulkanExtensions VK_COMPONENT_SWIZZLE_IDENTITY \ } -#define NULL_DESC_LAYOUT (VkDescriptorSetLayout)0 -#define NULL_PIPELINE_LAYOUT (VkPipelineLayout)0 -#define NULL_RENDER_PASS (SDL_GPURenderPass *)0 - -#define EXPAND_ELEMENTS_IF_NEEDED(arr, initialValue, type) \ - do { \ - if (arr->count == arr->capacity) { \ - if (arr->capacity == 0) { \ - arr->capacity = initialValue; \ - } else { \ - arr->capacity *= 2; \ - } \ - arr->elements = (type *)SDL_realloc( \ - arr->elements, \ - arr->capacity * sizeof(type)); \ - } \ - } while (0) - -#define MOVE_ARRAY_CONTENTS_AND_RESET(i, dstArr, dstCount, srcArr, srcCount) \ - do { \ - for ((i) = 0; (i) < (srcCount); (i) += 1) { \ - (dstArr)[i] = (srcArr)[i]; \ - } \ - (dstCount) = (srcCount); \ - (srcCount) = 0; \ - while (0) - // Conversions -static const Uint8 DEVICE_PRIORITY_HIGHPERFORMANCE[] = { - 0, // VK_PHYSICAL_DEVICE_TYPE_OTHER - 3, // VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU - 4, // VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU - 2, // VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU - 1 // VK_PHYSICAL_DEVICE_TYPE_CPU -}; - -static const Uint8 DEVICE_PRIORITY_LOWPOWER[] = { - 0, // VK_PHYSICAL_DEVICE_TYPE_OTHER - 4, // VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU - 3, // VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU - 2, // VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU - 1 // VK_PHYSICAL_DEVICE_TYPE_CPU -}; - static VkPresentModeKHR SDLToVK_PresentMode[] = { VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, @@ -474,6 +432,8 @@ static VkSamplerAddressMode SDLToVK_SamplerAddressMode[] = { // Structures +typedef struct VulkanRenderer VulkanRenderer; +typedef struct VulkanCommandPool VulkanCommandPool; typedef struct VulkanMemoryAllocation VulkanMemoryAllocation; typedef struct VulkanBuffer VulkanBuffer; typedef struct VulkanBufferContainer VulkanBufferContainer; @@ -708,9 +668,12 @@ typedef struct VulkanFramebuffer typedef struct WindowData { SDL_Window *window; + VulkanRenderer *renderer; + int refcount; SDL_GPUSwapchainComposition swapchainComposition; SDL_GPUPresentMode presentMode; bool needsSwapchainRecreate; + bool needsSurfaceRecreate; Uint32 swapchainCreateWidth; Uint32 swapchainCreateHeight; @@ -969,10 +932,6 @@ typedef struct VulkanFencePool Uint32 availableFenceCapacity; } VulkanFencePool; -typedef struct VulkanCommandPool VulkanCommandPool; - -typedef struct VulkanRenderer VulkanRenderer; - typedef struct VulkanCommandBuffer { CommandBufferCommonHeader common; @@ -1122,6 +1081,24 @@ struct VulkanCommandPool Uint32 inactiveCommandBufferCount; }; +// Feature Checks + +typedef struct VulkanFeatures +{ + Uint32 desiredApiVersion; + VkPhysicalDeviceFeatures desiredVulkan10DeviceFeatures; + VkPhysicalDeviceVulkan11Features desiredVulkan11DeviceFeatures; + VkPhysicalDeviceVulkan12Features desiredVulkan12DeviceFeatures; + VkPhysicalDeviceVulkan13Features desiredVulkan13DeviceFeatures; + + bool usesCustomVulkanOptions; + + Uint32 additionalDeviceExtensionCount; + const char **additionalDeviceExtensionNames; + Uint32 additionalInstanceExtensionCount; + const char **additionalInstanceExtensionNames; +} VulkanFeatures; + // Context struct VulkanRenderer @@ -1138,11 +1115,15 @@ struct VulkanRenderer bool debugMode; bool preferLowPower; + bool requireHardwareAcceleration; + SDL_PropertiesID props; Uint32 allowedFramesInFlight; VulkanExtensions supports; bool supportsDebugUtils; bool supportsColorspace; + bool supportsPhysicalDeviceProperties2; + bool supportsPortabilityEnumeration; bool supportsFillModeNonSolid; bool supportsMultiDrawIndirect; @@ -1241,6 +1222,7 @@ struct VulkanRenderer static bool VULKAN_INTERNAL_DefragmentMemory(VulkanRenderer *renderer, VulkanCommandBuffer *commandBuffer); static bool VULKAN_INTERNAL_BeginCommandBuffer(VulkanRenderer *renderer, VulkanCommandBuffer *commandBuffer); +static void VULKAN_ReleaseTexture(SDL_GPURenderer *driverData, SDL_GPUTexture *texture); static void VULKAN_ReleaseWindow(SDL_GPURenderer *driverData, SDL_Window *window); static bool VULKAN_Wait(SDL_GPURenderer *driverData); static bool VULKAN_WaitForFences(SDL_GPURenderer *driverData, bool waitAll, SDL_GPUFence *const *fences, Uint32 numFences); @@ -1347,7 +1329,6 @@ static inline Uint32 VULKAN_INTERNAL_NextHighestAlignment32( } static void VULKAN_INTERNAL_MakeMemoryUnavailable( - VulkanRenderer *renderer, VulkanMemoryAllocation *allocation) { Uint32 i, j; @@ -1397,7 +1378,6 @@ static void VULKAN_INTERNAL_MarkAllocationsForDefrag( renderer->allocationsToDefragCount += 1; VULKAN_INTERNAL_MakeMemoryUnavailable( - renderer, currentAllocator->allocations[allocationIndex]); } } @@ -1817,8 +1797,6 @@ static void VULKAN_INTERNAL_DeallocateMemory( static Uint8 VULKAN_INTERNAL_AllocateMemory( VulkanRenderer *renderer, - VkBuffer buffer, - VkImage image, Uint32 memoryTypeIndex, VkDeviceSize allocationSize, Uint8 isHostVisible, @@ -2099,8 +2077,6 @@ static Uint8 VULKAN_INTERNAL_BindResourceMemory( allocationResult = VULKAN_INTERNAL_AllocateMemory( renderer, - buffer, - image, memoryTypeIndex, allocationSize, isHostVisible, @@ -2397,24 +2373,6 @@ static Uint8 VULKAN_INTERNAL_BindMemoryForBuffer( // Resource tracking -#define ADD_TO_ARRAY_UNIQUE(resource, type, array, count, capacity) \ - Uint32 i; \ - \ - for (i = 0; i < commandBuffer->count; i += 1) { \ - if (commandBuffer->array[i] == resource) { \ - return; \ - } \ - } \ - \ - if (commandBuffer->count == commandBuffer->capacity) { \ - commandBuffer->capacity += 1; \ - commandBuffer->array = SDL_realloc( \ - commandBuffer->array, \ - commandBuffer->capacity * sizeof(type)); \ - } \ - commandBuffer->array[commandBuffer->count] = resource; \ - commandBuffer->count += 1; - #define TRACK_RESOURCE(resource, type, array, count, capacity) \ for (Sint32 i = commandBuffer->count - 1; i >= 0; i -= 1) { \ if (commandBuffer->array[i] == resource) { \ @@ -2493,7 +2451,6 @@ static void VULKAN_INTERNAL_TrackComputePipeline( } static void VULKAN_INTERNAL_TrackFramebuffer( - VulkanRenderer *renderer, VulkanCommandBuffer *commandBuffer, VulkanFramebuffer *framebuffer) { @@ -3215,7 +3172,7 @@ static void VULKAN_INTERNAL_DestroySampler( SDL_free(vulkanSampler); } -static void VULKAN_INTERNAL_DestroySwapchain( +static void VULKAN_INTERNAL_DestroySwapchainImage( VulkanRenderer *renderer, WindowData *windowData) { @@ -3241,22 +3198,6 @@ static void VULKAN_INTERNAL_DestroySwapchain( SDL_free(windowData->textureContainers); windowData->textureContainers = NULL; - if (windowData->swapchain) { - renderer->vkDestroySwapchainKHR( - renderer->logicalDevice, - windowData->swapchain, - NULL); - windowData->swapchain = VK_NULL_HANDLE; - } - - if (windowData->surface) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); - windowData->surface = VK_NULL_HANDLE; - } - for (i = 0; i < MAX_FRAMES_IN_FLIGHT; i += 1) { if (windowData->imageAvailableSemaphore[i]) { renderer->vkDestroySemaphore( @@ -3281,6 +3222,25 @@ static void VULKAN_INTERNAL_DestroySwapchain( windowData->imageCount = 0; } +static void VULKAN_INTERNAL_DestroySwapchain( + VulkanRenderer *renderer, + WindowData *windowData) +{ + if (windowData == NULL) { + return; + } + + VULKAN_INTERNAL_DestroySwapchainImage(renderer, windowData); + + if (windowData->swapchain) { + renderer->vkDestroySwapchainKHR( + renderer->logicalDevice, + windowData->swapchain, + NULL); + windowData->swapchain = VK_NULL_HANDLE; + } +} + static void VULKAN_INTERNAL_DestroyGraphicsPipelineResourceLayout( VulkanRenderer *renderer, VulkanGraphicsPipelineResourceLayout *resourceLayout) @@ -3357,7 +3317,7 @@ static void SDLCALL VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashDestroy(vo VulkanRenderer *renderer = (VulkanRenderer *)userdata; VulkanGraphicsPipelineResourceLayout *resourceLayout = (VulkanGraphicsPipelineResourceLayout *)value; VULKAN_INTERNAL_DestroyGraphicsPipelineResourceLayout(renderer, resourceLayout); - SDL_free((void*)key); + SDL_free((void *)key); } static Uint32 SDLCALL VULKAN_INTERNAL_ComputePipelineResourceLayoutHashFunction(void *userdata, const void *key) @@ -3388,7 +3348,7 @@ static void SDLCALL VULKAN_INTERNAL_ComputePipelineResourceLayoutHashDestroy(voi VulkanRenderer *renderer = (VulkanRenderer *)userdata; VulkanComputePipelineResourceLayout *resourceLayout = (VulkanComputePipelineResourceLayout *)value; VULKAN_INTERNAL_DestroyComputePipelineResourceLayout(renderer, resourceLayout); - SDL_free((void*)key); + SDL_free((void *)key); } static Uint32 SDLCALL VULKAN_INTERNAL_DescriptorSetLayoutHashFunction(void *userdata, const void *key) @@ -3421,7 +3381,7 @@ static void SDLCALL VULKAN_INTERNAL_DescriptorSetLayoutHashDestroy(void *userdat VulkanRenderer *renderer = (VulkanRenderer *)userdata; DescriptorSetLayout *layout = (DescriptorSetLayout *)value; VULKAN_INTERNAL_DestroyDescriptorSetLayout(renderer, layout); - SDL_free((void*)key); + SDL_free((void *)key); } static Uint32 SDLCALL VULKAN_INTERNAL_CommandPoolHashFunction(void *userdata, const void *key) @@ -4210,9 +4170,8 @@ static VulkanBuffer *VULKAN_INTERNAL_CreateBuffer( renderer->logicalDevice, buffer->buffer, NULL); - SDL_free(buffer); - return NULL; + SET_STRING_ERROR_AND_RETURN("Failed to bind memory for buffer!", NULL); } buffer->usedRegion->vulkanBuffer = buffer; // lol @@ -4399,7 +4358,9 @@ static bool VULKAN_INTERNAL_QuerySwapchainSupport( &supportsPresent); // Initialize these in case anything fails + outputDetails->formats = NULL; outputDetails->formatsLength = 0; + outputDetails->presentModes = NULL; outputDetails->presentModesLength = 0; if (!supportsPresent) { @@ -4422,22 +4383,33 @@ static bool VULKAN_INTERNAL_QuerySwapchainSupport( surface, &outputDetails->formatsLength, NULL); - CHECK_VULKAN_ERROR_AND_RETURN(result, vkGetPhysicalDeviceSurfaceFormatsKHR, false); + if (result != VK_SUCCESS) { + // Make sure the driver didn't mess up this value. + outputDetails->formatsLength = 0; + CHECK_VULKAN_ERROR_AND_RETURN(result, vkGetPhysicalDeviceSurfaceFormatsKHR, false); + } result = renderer->vkGetPhysicalDeviceSurfacePresentModesKHR( physicalDevice, surface, &outputDetails->presentModesLength, NULL); - CHECK_VULKAN_ERROR_AND_RETURN(result, vkGetPhysicalDeviceSurfacePresentModesKHR, false); + if (result != VK_SUCCESS) { + // Make sure the driver didn't mess up this value. + outputDetails->presentModesLength = 0; + // Reset this one, too. + outputDetails->formatsLength = 0; + CHECK_VULKAN_ERROR_AND_RETURN(result, vkGetPhysicalDeviceSurfacePresentModesKHR, false); + } // Generate the arrays, if applicable - outputDetails->formats = NULL; if (outputDetails->formatsLength != 0) { outputDetails->formats = (VkSurfaceFormatKHR *)SDL_malloc( sizeof(VkSurfaceFormatKHR) * outputDetails->formatsLength); if (!outputDetails->formats) { // OOM + outputDetails->formatsLength = 0; + outputDetails->presentModesLength = 0; return false; } @@ -4448,17 +4420,22 @@ static bool VULKAN_INTERNAL_QuerySwapchainSupport( outputDetails->formats); if (result != VK_SUCCESS) { SDL_free(outputDetails->formats); + outputDetails->formats = NULL; + outputDetails->formatsLength = 0; + outputDetails->presentModesLength = 0; CHECK_VULKAN_ERROR_AND_RETURN(result, vkGetPhysicalDeviceSurfaceFormatsKHR, false); } } - outputDetails->presentModes = NULL; if (outputDetails->presentModesLength != 0) { outputDetails->presentModes = (VkPresentModeKHR *)SDL_malloc( sizeof(VkPresentModeKHR) * outputDetails->presentModesLength); if (!outputDetails->presentModes) { // OOM SDL_free(outputDetails->formats); + outputDetails->formats = NULL; + outputDetails->formatsLength = 0; + outputDetails->presentModesLength = 0; return false; } @@ -4470,6 +4447,10 @@ static bool VULKAN_INTERNAL_QuerySwapchainSupport( if (result != VK_SUCCESS) { SDL_free(outputDetails->formats); SDL_free(outputDetails->presentModes); + outputDetails->formats = NULL; + outputDetails->presentModes = NULL; + outputDetails->formatsLength = 0; + outputDetails->presentModesLength = 0; CHECK_VULKAN_ERROR_AND_RETURN(result, vkGetPhysicalDeviceSurfacePresentModesKHR, false); } } @@ -4530,40 +4511,16 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( VkSemaphoreCreateInfo semaphoreCreateInfo; SwapchainSupportDetails swapchainSupportDetails; bool hasValidSwapchainComposition, hasValidPresentMode; + VkCompositeAlphaFlagsKHR compositeAlphaFlag = 0; Uint32 i; windowData->frameCounter = 0; - SDL_VideoDevice *_this = SDL_GetVideoDevice(); - SDL_assert(_this && _this->Vulkan_CreateSurface); - - // Each swapchain must have its own surface. - if (!_this->Vulkan_CreateSurface( - _this, - windowData->window, - renderer->instance, - NULL, // FIXME: VAllocationCallbacks - &windowData->surface)) { - return false; - } - SDL_assert(windowData->surface); - if (!VULKAN_INTERNAL_QuerySwapchainSupport( renderer, renderer->physicalDevice, windowData->surface, &swapchainSupportDetails)) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); - windowData->surface = VK_NULL_HANDLE; - if (swapchainSupportDetails.formatsLength > 0) { - SDL_free(swapchainSupportDetails.formats); - } - if (swapchainSupportDetails.presentModesLength > 0) { - SDL_free(swapchainSupportDetails.presentModes); - } return false; } @@ -4596,12 +4553,6 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( swapchainSupportDetails.presentModesLength); if (!hasValidSwapchainComposition || !hasValidPresentMode) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); - windowData->surface = VK_NULL_HANDLE; - if (swapchainSupportDetails.formatsLength > 0) { SDL_free(swapchainSupportDetails.formats); } @@ -4622,11 +4573,6 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( // NVIDIA + Win32 can return 0 extent when the window is minimized. Try again! if (swapchainSupportDetails.capabilities.currentExtent.width == 0 || swapchainSupportDetails.capabilities.currentExtent.height == 0) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); - windowData->surface = VK_NULL_HANDLE; if (swapchainSupportDetails.formatsLength > 0) { SDL_free(swapchainSupportDetails.formats); } @@ -4670,6 +4616,25 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( requestedImageCount = SDL_max(requestedImageCount, 3); } + // Default to opaque, if available, followed by inherit, and overwrite with a value that supports transparency, if necessary. + if (swapchainSupportDetails.capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) { + compositeAlphaFlag = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + } else if (swapchainSupportDetails.capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) { + compositeAlphaFlag = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + } + + if ((windowData->window->flags & SDL_WINDOW_TRANSPARENT) || !compositeAlphaFlag) { + if (swapchainSupportDetails.capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) { + compositeAlphaFlag = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; + } else if (swapchainSupportDetails.capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) { + compositeAlphaFlag = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; + } else if (swapchainSupportDetails.capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) { + compositeAlphaFlag = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + } else { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "SDL_WINDOW_TRANSPARENT flag set, but no suitable swapchain composite alpha value supported!"); + } + } + swapchainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; swapchainCreateInfo.pNext = NULL; swapchainCreateInfo.flags = 0; @@ -4691,17 +4656,21 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( #else swapchainCreateInfo.preTransform = swapchainSupportDetails.capabilities.currentTransform; #endif - swapchainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + swapchainCreateInfo.compositeAlpha = compositeAlphaFlag; swapchainCreateInfo.presentMode = SDLToVK_PresentMode[windowData->presentMode]; swapchainCreateInfo.clipped = VK_TRUE; - swapchainCreateInfo.oldSwapchain = VK_NULL_HANDLE; - + // The old swapchain could belong to a surface that no longer exists due to app switching. + swapchainCreateInfo.oldSwapchain = windowData->needsSurfaceRecreate ? (VkSwapchainKHR)0 : windowData->swapchain; vulkanResult = renderer->vkCreateSwapchainKHR( renderer->logicalDevice, &swapchainCreateInfo, NULL, &windowData->swapchain); + if (swapchainCreateInfo.oldSwapchain != VK_NULL_HANDLE) { + renderer->vkDestroySwapchainKHR(renderer->logicalDevice, swapchainCreateInfo.oldSwapchain, NULL); + } + if (swapchainSupportDetails.formatsLength > 0) { SDL_free(swapchainSupportDetails.formats); } @@ -4710,11 +4679,7 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( } if (vulkanResult != VK_SUCCESS) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); - windowData->surface = VK_NULL_HANDLE; + windowData->swapchain = VK_NULL_HANDLE; CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSwapchainKHR, false); } @@ -4729,15 +4694,10 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( sizeof(VulkanTextureContainer) * windowData->imageCount); if (!windowData->textureContainers) { // OOM - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); renderer->vkDestroySwapchainKHR( renderer->logicalDevice, windowData->swapchain, NULL); - windowData->surface = VK_NULL_HANDLE; windowData->swapchain = VK_NULL_HANDLE; return false; } @@ -4795,15 +4755,10 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( windowData->format, windowData->swapchainSwizzle, &windowData->textureContainers[i].activeTexture->subresources[0].renderTargetViews[0])) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); renderer->vkDestroySwapchainKHR( renderer->logicalDevice, windowData->swapchain, NULL); - windowData->surface = VK_NULL_HANDLE; windowData->swapchain = VK_NULL_HANDLE; return false; } @@ -4823,15 +4778,10 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( &windowData->imageAvailableSemaphore[i]); if (vulkanResult != VK_SUCCESS) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); renderer->vkDestroySwapchainKHR( renderer->logicalDevice, windowData->swapchain, NULL); - windowData->surface = VK_NULL_HANDLE; windowData->swapchain = VK_NULL_HANDLE; CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false); } @@ -4849,15 +4799,10 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( &windowData->renderFinishedSemaphore[i]); if (vulkanResult != VK_SUCCESS) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); renderer->vkDestroySwapchainKHR( renderer->logicalDevice, windowData->swapchain, NULL); - windowData->surface = VK_NULL_HANDLE; windowData->swapchain = VK_NULL_HANDLE; CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false); } @@ -4971,9 +4916,7 @@ static void VULKAN_DestroyDevice( j); } - if (renderer->memoryAllocator->subAllocators[i].allocations != NULL) { - SDL_free(renderer->memoryAllocator->subAllocators[i].allocations); - } + SDL_free(renderer->memoryAllocator->subAllocators[i].allocations); SDL_free(renderer->memoryAllocator->subAllocators[i].sortedFreeRegions); } @@ -5004,11 +4947,20 @@ static void VULKAN_DestroyDevice( renderer->vkDestroyDevice(renderer->logicalDevice, NULL); renderer->vkDestroyInstance(renderer->instance, NULL); + SDL_DestroyProperties(renderer->props); + SDL_free(renderer); SDL_free(device); SDL_Vulkan_UnloadLibrary(); } +static SDL_PropertiesID VULKAN_GetDeviceProperties( + SDL_GPUDevice *device) +{ + VulkanRenderer *renderer = (VulkanRenderer *)device->driverData; + return renderer->props; +} + static DescriptorSetCache *VULKAN_INTERNAL_AcquireDescriptorSetCache( VulkanRenderer *renderer) { @@ -5655,7 +5607,6 @@ static void VULKAN_PopDebugGroup( static VulkanTexture *VULKAN_INTERNAL_CreateTexture( VulkanRenderer *renderer, - bool transitionToDefaultLayout, const SDL_GPUTextureCreateInfo *createinfo) { VkResult vulkanResult; @@ -5883,18 +5834,6 @@ static VulkanTexture *VULKAN_INTERNAL_CreateTexture( &nameInfo); } - 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; } @@ -5961,7 +5900,6 @@ 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( @@ -6054,7 +5992,6 @@ static VulkanTextureSubresource *VULKAN_INTERNAL_PrepareTextureSubresourceForWri static VkRenderPass VULKAN_INTERNAL_CreateRenderPass( VulkanRenderer *renderer, - VulkanCommandBuffer *commandBuffer, const SDL_GPUColorTargetInfo *colorTargetInfos, Uint32 numColorTargets, const SDL_GPUDepthStencilTargetInfo *depthStencilTargetInfo) @@ -6089,7 +6026,6 @@ static VkRenderPass VULKAN_INTERNAL_CreateRenderPass( colorAttachmentReferences[colorAttachmentReferenceCount].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachmentDescriptionCount += 1; - colorAttachmentReferenceCount += 1; if (colorTargetInfos[i].store_op == SDL_GPU_STOREOP_RESOLVE || colorTargetInfos[i].store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) { VulkanTextureContainer *resolveContainer = (VulkanTextureContainer *)colorTargetInfos[i].resolve_texture; @@ -6104,12 +6040,16 @@ static VkRenderPass VULKAN_INTERNAL_CreateRenderPass( attachmentDescriptions[attachmentDescriptionCount].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachmentDescriptions[attachmentDescriptionCount].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - resolveReferences[resolveReferenceCount].attachment = attachmentDescriptionCount; - resolveReferences[resolveReferenceCount].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + resolveReferences[colorAttachmentReferenceCount].attachment = attachmentDescriptionCount; + resolveReferences[colorAttachmentReferenceCount].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachmentDescriptionCount += 1; resolveReferenceCount += 1; + } else { + resolveReferences[colorAttachmentReferenceCount].attachment = VK_ATTACHMENT_UNUSED; } + + colorAttachmentReferenceCount += 1; } subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; @@ -6432,7 +6372,7 @@ static SDL_GPUGraphicsPipeline *VULKAN_CreateGraphicsPipeline( multisampleStateCreateInfo.sampleShadingEnable = VK_FALSE; multisampleStateCreateInfo.minSampleShading = 1.0f; multisampleStateCreateInfo.pSampleMask = &sampleMask; - multisampleStateCreateInfo.alphaToCoverageEnable = VK_FALSE; + multisampleStateCreateInfo.alphaToCoverageEnable = createinfo->multisample_state.enable_alpha_to_coverage; multisampleStateCreateInfo.alphaToOneEnable = VK_FALSE; // Depth Stencil State @@ -6598,6 +6538,25 @@ static SDL_GPUGraphicsPipeline *VULKAN_CreateGraphicsPipeline( return (SDL_GPUGraphicsPipeline *)graphicsPipeline; } +static bool VULKAN_INTERNAL_IsValidShaderBytecode( + const Uint8 *code, + size_t codeSize) +{ + // SPIR-V bytecode has a 4 byte header containing 0x07230203. SPIR-V is + // defined as a stream of words and not a stream of bytes so both byte + // orders need to be considered. + // + // FIXME: It is uncertain if drivers are able to load both byte orders. If + // needed we may need to do an optional swizzle internally so apps can + // continue to treat shader code as an opaque blob. + if (codeSize < 4 || code == NULL) { + return false; + } + const Uint32 magic = 0x07230203; + const Uint32 magicInv = 0x03022307; + return SDL_memcmp(code, &magic, 4) == 0 || SDL_memcmp(code, &magicInv, 4) == 0; +} + static SDL_GPUComputePipeline *VULKAN_CreateComputePipeline( SDL_GPURenderer *driverData, const SDL_GPUComputePipelineCreateInfo *createinfo) @@ -6613,6 +6572,10 @@ static SDL_GPUComputePipeline *VULKAN_CreateComputePipeline( SET_STRING_ERROR_AND_RETURN("Incompatible shader format for Vulkan!", NULL); } + if (!VULKAN_INTERNAL_IsValidShaderBytecode(createinfo->code, createinfo->code_size)) { + SET_STRING_ERROR_AND_RETURN("The provided shader code is not valid SPIR-V!", NULL); + } + vulkanComputePipeline = SDL_malloc(sizeof(VulkanComputePipeline)); shaderModuleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; shaderModuleCreateInfo.pNext = NULL; @@ -6766,6 +6729,10 @@ static SDL_GPUShader *VULKAN_CreateShader( VkShaderModuleCreateInfo vkShaderModuleCreateInfo; VulkanRenderer *renderer = (VulkanRenderer *)driverData; + if (!VULKAN_INTERNAL_IsValidShaderBytecode(createinfo->code, createinfo->code_size)) { + SET_STRING_ERROR_AND_RETURN("The provided shader code is not valid SPIR-V!", NULL); + } + vulkanShader = SDL_malloc(sizeof(VulkanShader)); vkShaderModuleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; vkShaderModuleCreateInfo.pNext = NULL; @@ -6834,7 +6801,6 @@ static SDL_GPUTexture *VULKAN_CreateTexture( texture = VULKAN_INTERNAL_CreateTexture( renderer, - true, createinfo); if (texture == NULL) { @@ -6866,6 +6832,23 @@ static SDL_GPUTexture *VULKAN_CreateTexture( texture->container = container; texture->containerIndex = 0; + // Let's transition to the default barrier state, because for some reason Vulkan doesn't let us do that with initialLayout. + // Only do this after "container" is set, so the texture + // is fully initialized before any Submit that could trigger defrag. + { + VulkanCommandBuffer *barrierCommandBuffer = (VulkanCommandBuffer *)VULKAN_AcquireCommandBuffer((SDL_GPURenderer *)renderer); + VULKAN_INTERNAL_TextureTransitionToDefaultUsage( + renderer, + barrierCommandBuffer, + VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, + texture); + VULKAN_INTERNAL_TrackTexture(barrierCommandBuffer, texture); + if (!VULKAN_Submit((SDL_GPUCommandBuffer *)barrierCommandBuffer)) { + VULKAN_ReleaseTexture((SDL_GPURenderer *)renderer, (SDL_GPUTexture *)container); + return NULL; + } + } + return (SDL_GPUTexture *)container; } @@ -6962,9 +6945,7 @@ static void VULKAN_ReleaseTexture( SDL_DestroyProperties(vulkanTextureContainer->header.info.props); // Containers are just client handles, so we can destroy immediately - if (vulkanTextureContainer->debugName != NULL) { - SDL_free(vulkanTextureContainer->debugName); - } + SDL_free(vulkanTextureContainer->debugName); SDL_free(vulkanTextureContainer->textures); SDL_free(vulkanTextureContainer); @@ -7136,7 +7117,6 @@ static void VULKAN_ReleaseGraphicsPipeline( static VkRenderPass VULKAN_INTERNAL_FetchRenderPass( VulkanRenderer *renderer, - VulkanCommandBuffer *commandBuffer, const SDL_GPUColorTargetInfo *colorTargetInfos, Uint32 numColorTargets, const SDL_GPUDepthStencilTargetInfo *depthStencilTargetInfo) @@ -7162,6 +7142,8 @@ static VkRenderPass VULKAN_INTERNAL_FetchRenderPass( key.sampleCount = VK_SAMPLE_COUNT_1_BIT; if (numColorTargets > 0) { key.sampleCount = SDLToVK_SampleCount[((VulkanTextureContainer *)colorTargetInfos[0].texture)->header.info.sample_count]; + } else if (numColorTargets == 0 && depthStencilTargetInfo != NULL) { + key.sampleCount = SDLToVK_SampleCount[((VulkanTextureContainer *)depthStencilTargetInfo->texture)->header.info.sample_count]; } key.numColorTargets = numColorTargets; @@ -7194,7 +7176,6 @@ static VkRenderPass VULKAN_INTERNAL_FetchRenderPass( renderPassHandle = VULKAN_INTERNAL_CreateRenderPass( renderer, - commandBuffer, colorTargetInfos, numColorTargets, depthStencilTargetInfo); @@ -7271,8 +7252,8 @@ static VulkanFramebuffer *VULKAN_INTERNAL_FetchFramebuffer( } else { VulkanTextureSubresource *subresource = VULKAN_INTERNAL_FetchTextureSubresource( (VulkanTextureContainer *)depthStencilTargetInfo->texture, - 0, - 0); + depthStencilTargetInfo->layer, + depthStencilTargetInfo->mip_level); key.depthStencilAttachmentView = subresource->depthStencilView; } @@ -7327,8 +7308,8 @@ static VulkanFramebuffer *VULKAN_INTERNAL_FetchFramebuffer( if (depthStencilTargetInfo != NULL) { VulkanTextureSubresource *subresource = VULKAN_INTERNAL_FetchTextureSubresource( (VulkanTextureContainer *)depthStencilTargetInfo->texture, - 0, - 0); + depthStencilTargetInfo->layer, + depthStencilTargetInfo->mip_level); imageViewAttachments[attachmentCount] = subresource->depthStencilView; attachmentCount += 1; @@ -7808,8 +7789,8 @@ static void VULKAN_BeginRenderPass( if (depthStencilTargetInfo != NULL) { VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)depthStencilTargetInfo->texture; - w = textureContainer->header.info.width; - h = textureContainer->header.info.height; + w = textureContainer->header.info.width >> depthStencilTargetInfo->mip_level; + h = textureContainer->header.info.height >> depthStencilTargetInfo->mip_level; // The framebuffer cannot be larger than the smallest attachment. @@ -7864,8 +7845,8 @@ static void VULKAN_BeginRenderPass( renderer, vulkanCommandBuffer, textureContainer, - 0, - 0, + depthStencilTargetInfo->layer, + depthStencilTargetInfo->mip_level, depthStencilTargetInfo->cycle, VULKAN_TEXTURE_USAGE_MODE_DEPTH_STENCIL_ATTACHMENT); @@ -7878,7 +7859,6 @@ static void VULKAN_BeginRenderPass( renderPass = VULKAN_INTERNAL_FetchRenderPass( renderer, - vulkanCommandBuffer, colorTargetInfos, numColorTargets, depthStencilTargetInfo); @@ -7900,21 +7880,23 @@ static void VULKAN_BeginRenderPass( return; } - VULKAN_INTERNAL_TrackFramebuffer(renderer, vulkanCommandBuffer, framebuffer); + VULKAN_INTERNAL_TrackFramebuffer(vulkanCommandBuffer, framebuffer); // Set clear values clearValues = SDL_stack_alloc(VkClearValue, clearCount); - for (i = 0; i < totalColorAttachmentCount; i += 1) { - clearValues[i].color.float32[0] = colorTargetInfos[i].clear_color.r; - clearValues[i].color.float32[1] = colorTargetInfos[i].clear_color.g; - clearValues[i].color.float32[2] = colorTargetInfos[i].clear_color.b; - clearValues[i].color.float32[3] = colorTargetInfos[i].clear_color.a; + int clearIndex = 0; + for (i = 0; i < numColorTargets; i += 1) { + clearValues[clearIndex].color.float32[0] = colorTargetInfos[i].clear_color.r; + clearValues[clearIndex].color.float32[1] = colorTargetInfos[i].clear_color.g; + clearValues[clearIndex].color.float32[2] = colorTargetInfos[i].clear_color.b; + clearValues[clearIndex].color.float32[3] = colorTargetInfos[i].clear_color.a; + clearIndex += 1; if (colorTargetInfos[i].store_op == SDL_GPU_STOREOP_RESOLVE || colorTargetInfos[i].store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) { // Skip over the resolve texture, we're not clearing it - i += 1; + clearIndex += 1; } } @@ -9405,6 +9387,8 @@ static bool VULKAN_INTERNAL_AllocateCommandBuffer( commandBuffer->usedUniformBuffers = SDL_malloc( commandBuffer->usedUniformBufferCapacity * sizeof(VulkanUniformBuffer *)); + commandBuffer->swapchainRequested = false; + // Pool it! vulkanCommandPool->inactiveCommandBuffers[vulkanCommandPool->inactiveCommandBufferCount] = commandBuffer; @@ -9515,7 +9499,8 @@ static SDL_GPUCommandBuffer *VULKAN_AcquireCommandBuffer( VulkanCommandBuffer *commandBuffer = VULKAN_INTERNAL_GetInactiveCommandBufferFromPool(renderer, threadID); - commandBuffer->descriptorSetCache = VULKAN_INTERNAL_AcquireDescriptorSetCache(renderer); + DescriptorSetCache *descriptorSetCache = + VULKAN_INTERNAL_AcquireDescriptorSetCache(renderer); SDL_UnlockMutex(renderer->acquireCommandBufferLock); @@ -9523,6 +9508,8 @@ static SDL_GPUCommandBuffer *VULKAN_AcquireCommandBuffer( return NULL; } + commandBuffer->descriptorSetCache = descriptorSetCache; + // Reset state commandBuffer->currentComputePipeline = NULL; @@ -9587,6 +9574,7 @@ static SDL_GPUCommandBuffer *VULKAN_AcquireCommandBuffer( commandBuffer->autoReleaseFence = true; + commandBuffer->swapchainRequested = false; commandBuffer->isDefrag = 0; /* Reset the command buffer here to avoid resets being called @@ -9770,8 +9758,13 @@ static bool VULKAN_ClaimWindow( WindowData *windowData = VULKAN_INTERNAL_FetchWindowData(window); if (windowData == NULL) { - windowData = SDL_calloc(1, sizeof(WindowData)); + windowData = (WindowData *)SDL_calloc(1, sizeof(WindowData)); + if (!windowData) { + return false; + } windowData->window = window; + windowData->renderer = renderer; + windowData->refcount = 1; windowData->presentMode = SDL_GPU_PRESENTMODE_VSYNC; windowData->swapchainComposition = SDL_GPU_SWAPCHAINCOMPOSITION_SDR; @@ -9785,6 +9778,28 @@ static bool VULKAN_ClaimWindow( windowData->swapchainCreateHeight = h; #endif + SDL_VideoDevice *videoDevice = SDL_GetVideoDevice(); + if (!videoDevice) { + SDL_free(windowData); + return SDL_SetError("No video device found"); + } + + if (!videoDevice->Vulkan_CreateSurface) { + SDL_free(windowData); + return SDL_SetError("Video device does not implement Vulkan_CreateSurface"); + } + + // Each window must have its own surface. + if (!videoDevice->Vulkan_CreateSurface( + videoDevice, + windowData->window, + renderer->instance, + NULL, // FIXME: VAllocationCallbacks + &windowData->surface)) { + SDL_free(windowData); + return false; + } + Uint32 createSwapchainResult = VULKAN_INTERNAL_CreateSwapchain(renderer, windowData); if (createSwapchainResult == 1) { SDL_SetPointerProperty(SDL_GetWindowProperties(window), WINDOW_PROPERTY_DATA, windowData); @@ -9801,18 +9816,26 @@ static bool VULKAN_ClaimWindow( renderer->claimedWindowCount += 1; SDL_UnlockMutex(renderer->windowLock); - SDL_AddEventWatch(VULKAN_INTERNAL_OnWindowResize, window); + SDL_AddWindowEventWatch(SDL_WINDOW_EVENT_WATCH_NORMAL, VULKAN_INTERNAL_OnWindowResize, window); return true; } else if (createSwapchainResult == VULKAN_INTERNAL_TRY_AGAIN) { windowData->needsSwapchainRecreate = true; return true; } else { + // Failed to create swapchain, destroy surface and free data + renderer->vkDestroySurfaceKHR( + renderer->instance, + windowData->surface, + NULL); SDL_free(windowData); return false; } + } else if (windowData->renderer == renderer) { + ++windowData->refcount; + return true; } else { - SET_STRING_ERROR_AND_RETURN("Window already claimed!", false); + SET_STRING_ERROR_AND_RETURN("Window already claimed", false); } } @@ -9827,6 +9850,14 @@ static void VULKAN_ReleaseWindow( if (windowData == NULL) { return; } + if (windowData->renderer != renderer) { + SDL_SetError("Window not claimed by this device"); + return; + } + if (windowData->refcount > 1) { + --windowData->refcount; + return; + } VULKAN_Wait(driverData); @@ -9842,6 +9873,11 @@ static void VULKAN_ReleaseWindow( (VulkanRenderer *)driverData, windowData); + renderer->vkDestroySurfaceKHR( + renderer->instance, + windowData->surface, + NULL); + windowData->surface = VK_NULL_HANDLE; SDL_LockMutex(renderer->windowLock); for (i = 0; i < renderer->claimedWindowCount; i += 1) { @@ -9856,7 +9892,7 @@ static void VULKAN_ReleaseWindow( SDL_free(windowData); SDL_ClearProperty(SDL_GetWindowProperties(window), WINDOW_PROPERTY_DATA); - SDL_RemoveEventWatch(VULKAN_INTERNAL_OnWindowResize, window); + SDL_RemoveWindowEventWatch(SDL_WINDOW_EVENT_WATCH_NORMAL, VULKAN_INTERNAL_OnWindowResize, window); } static Uint32 VULKAN_INTERNAL_RecreateSwapchain( @@ -9878,7 +9914,12 @@ static Uint32 VULKAN_INTERNAL_RecreateSwapchain( } } +#ifdef SDL_VIDEO_DRIVER_PRIVATE + // Private platforms also invalidate the window, so don't try to preserve the surface/swapchain VULKAN_INTERNAL_DestroySwapchain(renderer, windowData); +#else + VULKAN_INTERNAL_DestroySwapchainImage(renderer, windowData); +#endif return VULKAN_INTERNAL_CreateSwapchain(renderer, windowData); } @@ -9943,6 +9984,24 @@ static bool VULKAN_INTERNAL_AcquireSwapchainTexture( return true; } + if (windowData->needsSurfaceRecreate) { + SDL_VideoDevice *videoDevice = SDL_GetVideoDevice(); + SDL_assert(videoDevice); + SDL_assert(videoDevice->Vulkan_CreateSurface); + renderer->vkDestroySurfaceKHR( + renderer->instance, + windowData->surface, + NULL); + if (!videoDevice->Vulkan_CreateSurface( + videoDevice, + windowData->window, + renderer->instance, + NULL, // FIXME: VAllocationCallbacks + &windowData->surface)) { + SET_STRING_ERROR_AND_RETURN("Failed to recreate Vulkan surface!", false); + } + } + // If window data marked as needing swapchain recreate, try to recreate if (windowData->needsSwapchainRecreate) { Uint32 recreateSwapchainResult = VULKAN_INTERNAL_RecreateSwapchain(renderer, windowData); @@ -9958,6 +10017,10 @@ static bool VULKAN_INTERNAL_AcquireSwapchainTexture( } return true; } + + // Unset this flag until after the swapchain has been recreated to let VULKAN_INTERNAL_CreateSwapchain() + // know whether it needs to pass the old swapchain or not. + windowData->needsSurfaceRecreate = false; } if (windowData->inFlightFences[windowData->frameCounter] != NULL) { @@ -10130,7 +10193,7 @@ static SDL_GPUTextureFormat VULKAN_GetSwapchainTextureFormat( SDL_GPURenderer *driverData, SDL_Window *window) { - VulkanRenderer *renderer = (VulkanRenderer*)driverData; + VulkanRenderer *renderer = (VulkanRenderer *)driverData; WindowData *windowData = VULKAN_INTERNAL_FetchWindowData(window); if (windowData == NULL) { @@ -10494,11 +10557,18 @@ static bool VULKAN_Wait( VkResult result; Sint32 i; + SDL_LockMutex(renderer->submitLock); + result = renderer->vkDeviceWaitIdle(renderer->logicalDevice); - CHECK_VULKAN_ERROR_AND_RETURN(result, vkDeviceWaitIdle, false); - - SDL_LockMutex(renderer->submitLock); + if (result != VK_SUCCESS) { + if (renderer->debugMode) { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s %s", "vkDeviceWaitIdle", VkErrorMessages(result)); + } + SDL_SetError("%s %s", "vkDeviceWaitIdle", VkErrorMessages(result)); + SDL_UnlockMutex(renderer->submitLock); + return false; + } for (i = renderer->submittedCommandBufferCount - 1; i >= 0; i -= 1) { commandBuffer = renderer->submittedCommandBuffers[i]; @@ -10642,12 +10712,23 @@ static bool VULKAN_Submit( if (presentResult == VK_SUCCESS || presentResult == VK_SUBOPTIMAL_KHR || presentResult == VK_ERROR_OUT_OF_DATE_KHR) { // If presenting, the swapchain is using the in-flight fence - presentData->windowData->inFlightFences[presentData->windowData->frameCounter] = (SDL_GPUFence*)vulkanCommandBuffer->inFlightFence; + presentData->windowData->inFlightFences[presentData->windowData->frameCounter] = (SDL_GPUFence *)vulkanCommandBuffer->inFlightFence; (void)SDL_AtomicIncRef(&vulkanCommandBuffer->inFlightFence->referenceCount); - if (presentResult == VK_SUBOPTIMAL_KHR || presentResult == VK_ERROR_OUT_OF_DATE_KHR) { +// On the Android platform, VK_SUBOPTIMAL_KHR is returned whenever the device is rotated. We'll just ignore this for now. +#ifndef SDL_PLATFORM_ANDROID + if (presentResult == VK_SUBOPTIMAL_KHR) { presentData->windowData->needsSwapchainRecreate = true; } +#endif + if (presentResult == VK_ERROR_OUT_OF_DATE_KHR) { + presentData->windowData->needsSwapchainRecreate = true; + } + } else if (presentResult == VK_ERROR_SURFACE_LOST_KHR) { + // Android can destroy the surface at any time when the app goes into the background, + // even after successfully acquiring a swapchain texture and before presenting it. + presentData->windowData->needsSwapchainRecreate = true; + presentData->windowData->needsSurfaceRecreate = true; } else { if (presentResult != VK_SUCCESS) { VULKAN_INTERNAL_ReleaseCommandBuffer(vulkanCommandBuffer); @@ -10750,8 +10831,6 @@ static bool VULKAN_INTERNAL_DefragmentMemory( VulkanMemoryUsedRegion *currentRegion = allocation->usedRegions[i]; if (currentRegion->isBuffer && !currentRegion->vulkanBuffer->markedForDestroy) { - currentRegion->vulkanBuffer->usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; - VulkanBuffer *newBuffer = VULKAN_INTERNAL_CreateBuffer( renderer, currentRegion->vulkanBuffer->size, @@ -10823,7 +10902,6 @@ static bool VULKAN_INTERNAL_DefragmentMemory( } else if (!currentRegion->isBuffer && !currentRegion->vulkanTexture->markedForDestroy) { VulkanTexture *newTexture = VULKAN_INTERNAL_CreateTexture( renderer, - false, ¤tRegion->vulkanTexture->container->header.info); if (newTexture == NULL) { @@ -10970,7 +11048,7 @@ static inline Uint8 CheckDeviceExtensions( supports->ext = 1; \ } CHECK(KHR_swapchain) - else CHECK(KHR_maintenance1) else CHECK(KHR_driver_properties) 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(MSFT_layered_driver) else CHECK(EXT_texture_compression_astc_hdr) #undef CHECK } @@ -10985,6 +11063,7 @@ static inline Uint32 GetDeviceExtensionCount(VulkanExtensions *supports) supports->KHR_maintenance1 + supports->KHR_driver_properties + supports->KHR_portability_subset + + supports->MSFT_layered_driver + supports->EXT_texture_compression_astc_hdr); } @@ -11001,6 +11080,7 @@ static inline void CreateDeviceExtensionArray( CHECK(KHR_maintenance1) CHECK(KHR_driver_properties) CHECK(KHR_portability_subset) + CHECK(MSFT_layered_driver) CHECK(EXT_texture_compression_astc_hdr) #undef CHECK } @@ -11023,7 +11103,10 @@ static Uint8 VULKAN_INTERNAL_CheckInstanceExtensions( const char **requiredExtensions, Uint32 requiredExtensionsLength, bool *supportsDebugUtils, - bool *supportsColorspace) + bool *supportsColorspace, + bool *supportsPhysicalDeviceProperties2, + bool *supportsPortabilityEnumeration, + int *firstUnsupportedExtensionIndex) { Uint32 extensionCount, i; VkExtensionProperties *availableExtensions; @@ -11046,6 +11129,7 @@ static Uint8 VULKAN_INTERNAL_CheckInstanceExtensions( availableExtensions, extensionCount)) { allExtensionsSupported = 0; + *firstUnsupportedExtensionIndex = i; break; } } @@ -11062,12 +11146,48 @@ static Uint8 VULKAN_INTERNAL_CheckInstanceExtensions( availableExtensions, extensionCount); + // Only needed for KHR_driver_properties! + *supportsPhysicalDeviceProperties2 = SupportsInstanceExtension( + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, + availableExtensions, + extensionCount); + + // Only needed for MoltenVK! + *supportsPortabilityEnumeration = SupportsInstanceExtension( + VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, + availableExtensions, + extensionCount); + SDL_free(availableExtensions); return allExtensionsSupported; } +static Uint8 CheckOptInDeviceExtensions(VulkanFeatures *features, + Uint32 numExtensions, + VkExtensionProperties *availableExtensions, + const char **missingExtensionName) { + Uint8 supportsAll = 1; + for (Uint32 extensionIdx = 0; extensionIdx < features->additionalDeviceExtensionCount; extensionIdx++) { + bool found = false; + for (Uint32 searchIdx = 0; searchIdx < numExtensions; searchIdx++) { + if (SDL_strcmp(features->additionalDeviceExtensionNames[extensionIdx], availableExtensions[searchIdx].extensionName) == 0) { + found = true; + break; + } + } + if (!found) { + supportsAll = 0; + *missingExtensionName = features->additionalDeviceExtensionNames[extensionIdx]; + break; + } + } + + return supportsAll; +} + static Uint8 VULKAN_INTERNAL_CheckDeviceExtensions( VulkanRenderer *renderer, + VulkanFeatures *features, VkPhysicalDevice physicalDevice, VulkanExtensions *physicalDeviceExtensions) { @@ -11093,6 +11213,19 @@ static Uint8 VULKAN_INTERNAL_CheckDeviceExtensions( extensionCount, physicalDeviceExtensions); + if (features->usesCustomVulkanOptions) { + const char *missingExtensionName; + if (!CheckOptInDeviceExtensions(features, extensionCount, availableExtensions, &missingExtensionName)) { + SDL_assert(missingExtensionName); + if (renderer->debugMode) { + SDL_LogError(SDL_LOG_CATEGORY_GPU, + "Required Vulkan device extension '%s' not supported", + missingExtensionName); + } + allExtensionsSupported = 0; + } + } + SDL_free(availableExtensions); return allExtensionsSupported; } @@ -11130,7 +11263,471 @@ static Uint8 VULKAN_INTERNAL_CheckValidationLayers( return layerFound; } -static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) +#define CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, feature, result) \ + if (requested->feature && !supported->feature) { \ + SDL_LogVerbose( \ + SDL_LOG_CATEGORY_GPU, \ + "SDL GPU Vulkan: Application requested unsupported physical device feature '" #feature "'"); \ + result = false; \ + } + +static bool VULKAN_INTERNAL_ValidateOptInVulkan10Features(VkPhysicalDeviceFeatures *requested, VkPhysicalDeviceFeatures *supported) +{ + if (requested && supported) { + bool result = true; + + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, robustBufferAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, fullDrawIndexUint32, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, imageCubeArray, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, independentBlend, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, geometryShader, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, tessellationShader, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sampleRateShading, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, dualSrcBlend, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, logicOp, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiDrawIndirect, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, drawIndirectFirstInstance, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, depthClamp, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, depthBiasClamp, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, fillModeNonSolid, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, depthBounds, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, wideLines, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, largePoints, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, alphaToOne, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiViewport, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, samplerAnisotropy, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, textureCompressionETC2, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, textureCompressionASTC_LDR, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, textureCompressionBC, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, occlusionQueryPrecise, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, pipelineStatisticsQuery, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, vertexPipelineStoresAndAtomics, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, fragmentStoresAndAtomics, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderTessellationAndGeometryPointSize, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderImageGatherExtended, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageExtendedFormats, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageMultisample, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageReadWithoutFormat, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageWriteWithoutFormat, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderUniformBufferArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderSampledImageArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageBufferArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderClipDistance, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderCullDistance, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderFloat64, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInt64, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInt16, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderResourceResidency, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderResourceMinLod, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseBinding, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidencyBuffer, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidencyImage2D, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidencyImage3D, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidency2Samples, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidency4Samples, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidency8Samples, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidency16Samples, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidencyAliased, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, variableMultisampleRate, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, inheritedQueries, result) + + return result; + } else { + return false; + } +} + +static bool VULKAN_INTERNAL_ValidateOptInVulkan11Features(VkPhysicalDeviceVulkan11Features *requested, VkPhysicalDeviceVulkan11Features *supported) +{ + if (requested && supported) { + bool result = true; + + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storageBuffer16BitAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, uniformAndStorageBuffer16BitAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storagePushConstant16, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storageInputOutput16, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiview, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiviewGeometryShader, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiviewTessellationShader, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, variablePointersStorageBuffer, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, variablePointers, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, protectedMemory, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, samplerYcbcrConversion, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderDrawParameters, result) + + return result; + } else { + return false; + } +} + +static bool VULKAN_INTERNAL_ValidateOptInVulkan12Features(VkPhysicalDeviceVulkan12Features *requested, VkPhysicalDeviceVulkan12Features *supported) +{ + if (requested && supported) { + bool result = true; + + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, samplerMirrorClampToEdge, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, drawIndirectCount, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storageBuffer8BitAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, uniformAndStorageBuffer8BitAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storagePushConstant8, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderBufferInt64Atomics, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderSharedInt64Atomics, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderFloat16, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInt8, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInputAttachmentArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderUniformTexelBufferArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageTexelBufferArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderUniformBufferArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderSampledImageArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageBufferArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInputAttachmentArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderUniformTexelBufferArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageTexelBufferArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingUniformBufferUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingSampledImageUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingStorageImageUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingStorageBufferUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingUniformTexelBufferUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingStorageTexelBufferUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingUpdateUnusedWhilePending, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingPartiallyBound, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingVariableDescriptorCount, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, runtimeDescriptorArray, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, samplerFilterMinmax, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, scalarBlockLayout, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, imagelessFramebuffer, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, uniformBufferStandardLayout, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderSubgroupExtendedTypes, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, separateDepthStencilLayouts, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, hostQueryReset, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, timelineSemaphore, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, bufferDeviceAddress, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, bufferDeviceAddressCaptureReplay, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, bufferDeviceAddressMultiDevice, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, vulkanMemoryModel, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, vulkanMemoryModelDeviceScope, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, vulkanMemoryModelAvailabilityVisibilityChains, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderOutputViewportIndex, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderOutputLayer, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, subgroupBroadcastDynamicId, result) + + return result; + } else { + return false; + } +} + +static bool VULKAN_INTERNAL_ValidateOptInVulkan13Features(VkPhysicalDeviceVulkan13Features *requested, VkPhysicalDeviceVulkan13Features *supported) +{ + if (requested && supported) { + bool result = true; + + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, robustImageAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, inlineUniformBlock, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingInlineUniformBlockUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, pipelineCreationCacheControl, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, privateData, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderDemoteToHelperInvocation, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderTerminateInvocation, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, subgroupSizeControl, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, computeFullSubgroups, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, synchronization2, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, textureCompressionASTC_HDR, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderZeroInitializeWorkgroupMemory, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, dynamicRendering, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderIntegerDotProduct, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, maintenance4, result) + + return result; + } else { + return false; + } +} + +#undef CHECK_OPTIONAL_DEVICE_FEATURE + +static bool VULKAN_INTERNAL_ValidateOptInFeatures(VulkanRenderer *renderer, VulkanFeatures *features, VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures *vk10Features) +{ + bool supportsAllFeatures = true; + + int minorVersion = VK_API_VERSION_MINOR(features->desiredApiVersion); + + if (minorVersion < 1) { + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan10Features(&features->desiredVulkan10DeviceFeatures, vk10Features); + } else if (minorVersion < 2) { + // Query device features using the pre-1.2 structures + VkPhysicalDevice16BitStorageFeatures storage = { 0 }; + storage.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES; + + VkPhysicalDeviceMultiviewFeatures multiview = { 0 }; + multiview.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; + + VkPhysicalDeviceProtectedMemoryFeatures protectedMem = { 0 }; + protectedMem.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES; + + VkPhysicalDeviceSamplerYcbcrConversionFeatures ycbcr = { 0 }; + ycbcr.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES; + + VkPhysicalDeviceShaderDrawParametersFeatures drawParams = { 0 }; + drawParams.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES; + + VkPhysicalDeviceVariablePointersFeatures varPointers = { 0 }; + varPointers.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES; + + VkPhysicalDeviceFeatures2 supportedFeatureList = { 0 }; + supportedFeatureList.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + supportedFeatureList.pNext = &storage; + storage.pNext = &multiview; + multiview.pNext = &protectedMem; + protectedMem.pNext = &ycbcr; + ycbcr.pNext = &drawParams; + drawParams.pNext = &varPointers; + + renderer->vkGetPhysicalDeviceFeatures2(physicalDevice, &supportedFeatureList); + + // Pack the results into the post-1.2 structure for easier checking + VkPhysicalDeviceVulkan11Features vk11Features = { 0 }; + vk11Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; + vk11Features.storageBuffer16BitAccess = storage.storageBuffer16BitAccess; + vk11Features.uniformAndStorageBuffer16BitAccess = storage.uniformAndStorageBuffer16BitAccess; + vk11Features.storagePushConstant16 = storage.storagePushConstant16; + vk11Features.storageInputOutput16 = storage.storageInputOutput16; + vk11Features.multiview = multiview.multiview; + vk11Features.multiviewGeometryShader = multiview.multiviewGeometryShader; + vk11Features.multiviewTessellationShader = multiview.multiviewTessellationShader; + vk11Features.protectedMemory = protectedMem.protectedMemory; + vk11Features.samplerYcbcrConversion = ycbcr.samplerYcbcrConversion; + vk11Features.shaderDrawParameters = drawParams.shaderDrawParameters; + vk11Features.variablePointers = varPointers.variablePointers; + vk11Features.variablePointersStorageBuffer = varPointers.variablePointersStorageBuffer; + + // Check support + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan10Features(&features->desiredVulkan10DeviceFeatures, vk10Features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan11Features(&features->desiredVulkan11DeviceFeatures, &vk11Features); + } else { + VkPhysicalDeviceVulkan11Features vk11Features = { 0 }; + vk11Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; + + VkPhysicalDeviceVulkan12Features vk12Features = { 0 }; + vk12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + + VkPhysicalDeviceVulkan13Features vk13Features = { 0 }; + vk13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + + VkPhysicalDeviceFeatures2 supportedFeatureList = { 0 }; + supportedFeatureList.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + supportedFeatureList.pNext = &vk11Features; + vk11Features.pNext = &vk12Features; + vk12Features.pNext = &vk13Features; + + renderer->vkGetPhysicalDeviceFeatures2(physicalDevice, &supportedFeatureList); + + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan10Features(&features->desiredVulkan10DeviceFeatures, vk10Features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan11Features(&features->desiredVulkan11DeviceFeatures, &vk11Features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan12Features(&features->desiredVulkan12DeviceFeatures, &vk12Features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan13Features(&features->desiredVulkan13DeviceFeatures, &vk13Features); + } + + return supportsAllFeatures; +} + +static void VULKAN_INTERNAL_AddDeviceFeatures(VkBool32 *firstFeature, VkBool32 *lastFeature, VkBool32 *firstFeatureToAdd) +{ + while (firstFeature <= lastFeature) { + *firstFeature = (*firstFeature | *firstFeatureToAdd); + firstFeature++; + firstFeatureToAdd++; + } +} + +static bool VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_11(VkPhysicalDeviceFeatures *dst10, + VkPhysicalDeviceVulkan11Features *dst11, + VkBaseOutStructure *src) +{ + bool hasAdded = false; + switch (src->sType) { + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2: + { + VkPhysicalDeviceFeatures2 *newFeatures = (VkPhysicalDeviceFeatures2 *)src; + VULKAN_INTERNAL_AddDeviceFeatures(&dst10->robustBufferAccess, + &dst10->inheritedQueries, + &newFeatures->features.robustBufferAccess); + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES: + { + VkPhysicalDevice16BitStorageFeatures *newFeatures = (VkPhysicalDevice16BitStorageFeatures *)src; + dst11->storageBuffer16BitAccess |= newFeatures->storageBuffer16BitAccess; + dst11->uniformAndStorageBuffer16BitAccess |= newFeatures->uniformAndStorageBuffer16BitAccess; + dst11->storagePushConstant16 |= newFeatures->storagePushConstant16; + dst11->storageInputOutput16 |= newFeatures->storageInputOutput16; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES: + { + VkPhysicalDeviceMultiviewFeatures *newFeatures = (VkPhysicalDeviceMultiviewFeatures *)src; + dst11->multiview |= newFeatures->multiview; + dst11->multiviewGeometryShader |= newFeatures->multiviewGeometryShader; + dst11->multiviewTessellationShader |= newFeatures->multiviewTessellationShader; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES: + { + VkPhysicalDeviceProtectedMemoryFeatures *newFeatures = (VkPhysicalDeviceProtectedMemoryFeatures *)src; + dst11->protectedMemory |= newFeatures->protectedMemory; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES: + { + VkPhysicalDeviceSamplerYcbcrConversionFeatures *newFeatures = (VkPhysicalDeviceSamplerYcbcrConversionFeatures *)src; + dst11->samplerYcbcrConversion |= newFeatures->samplerYcbcrConversion; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES: + { + VkPhysicalDeviceShaderDrawParametersFeatures *newFeatures = (VkPhysicalDeviceShaderDrawParametersFeatures *)src; + dst11->shaderDrawParameters |= newFeatures->shaderDrawParameters; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES: + { + VkPhysicalDeviceVariablePointersFeatures *newFeatures = (VkPhysicalDeviceVariablePointersFeatures *)src; + dst11->variablePointers |= newFeatures->variablePointers; + dst11->variablePointersStorageBuffer |= newFeatures->variablePointersStorageBuffer; + hasAdded = true; + } break; + + default: + break; + } + + return hasAdded; +} + +static bool VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_12_Or_Later(VkPhysicalDeviceFeatures *dst10, + VkPhysicalDeviceVulkan11Features *dst11, + VkPhysicalDeviceVulkan12Features *dst12, + VkPhysicalDeviceVulkan13Features *dst13, + Uint32 apiVersion, + VkBaseOutStructure *src) +{ + int minorVersion = VK_API_VERSION_MINOR(apiVersion); + SDL_assert(apiVersion >= 2); + bool hasAdded = VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_11(dst10, dst11, src); + if (!hasAdded) { + switch (src->sType) { + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES: + { + VkPhysicalDeviceVulkan11Features *newFeatures = (VkPhysicalDeviceVulkan11Features *)src; + VULKAN_INTERNAL_AddDeviceFeatures(&dst11->storageBuffer16BitAccess, + &dst11->shaderDrawParameters, + &newFeatures->storageBuffer16BitAccess); + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES: + { + VkPhysicalDeviceVulkan12Features *newFeatures = (VkPhysicalDeviceVulkan12Features *)src; + VULKAN_INTERNAL_AddDeviceFeatures(&dst12->samplerMirrorClampToEdge, + &dst12->subgroupBroadcastDynamicId, + &newFeatures->samplerMirrorClampToEdge); + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES: + { + if (minorVersion >= 3) { + VkPhysicalDeviceVulkan13Features *newFeatures = (VkPhysicalDeviceVulkan13Features *)src; + VULKAN_INTERNAL_AddDeviceFeatures(&dst13->robustImageAccess, + &dst13->maintenance4, + &newFeatures->robustImageAccess); + hasAdded = true; + } + } break; + + default: + break; + } + } + + return hasAdded; +} + +static void VULKAN_INTERNAL_AddOptInVulkanOptions(SDL_PropertiesID props, VulkanRenderer *renderer, VulkanFeatures *features) +{ + if (SDL_HasProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER)) { + SDL_GPUVulkanOptions *options = (SDL_GPUVulkanOptions *)SDL_GetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER, NULL); + if (options) { + features->usesCustomVulkanOptions = true; + features->desiredApiVersion = options->vulkan_api_version; + + SDL_zero(features->desiredVulkan11DeviceFeatures); + SDL_zero(features->desiredVulkan12DeviceFeatures); + SDL_zero(features->desiredVulkan13DeviceFeatures); + features->desiredVulkan11DeviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; + features->desiredVulkan12DeviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + features->desiredVulkan13DeviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + + // Handle requested device features + VkPhysicalDeviceFeatures *vk10Features = &features->desiredVulkan10DeviceFeatures; + VkPhysicalDeviceVulkan11Features *vk11Features = &features->desiredVulkan11DeviceFeatures; + VkPhysicalDeviceVulkan12Features *vk12Features = &features->desiredVulkan12DeviceFeatures; + VkPhysicalDeviceVulkan13Features *vk13Features = &features->desiredVulkan13DeviceFeatures; + + if (options->vulkan_10_physical_device_features) { + VkPhysicalDeviceFeatures *deviceFeatures = (VkPhysicalDeviceFeatures *)options->vulkan_10_physical_device_features; + VULKAN_INTERNAL_AddDeviceFeatures(&vk10Features->robustBufferAccess, + &vk10Features->inheritedQueries, + &deviceFeatures->robustBufferAccess); + } + + int minorVersion = VK_API_VERSION_MINOR(features->desiredApiVersion); + bool supportsHigherLevelFeatures = minorVersion > 0; + if (supportsHigherLevelFeatures && options->feature_list) { + if (minorVersion < 2) { + // Iterate through the entire list and combine all requested features + VkBaseOutStructure *nextStructure = (VkBaseOutStructure *)options->feature_list; + while (nextStructure) { + VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_11(vk10Features, vk11Features, nextStructure); + nextStructure = nextStructure->pNext; + } + } else { + // Iterate through the entire list and combine all requested features + VkBaseOutStructure *nextStructure = (VkBaseOutStructure *)options->feature_list; + while (nextStructure) { + VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_12_Or_Later(vk10Features, + vk11Features, + vk12Features, + vk13Features, + features->desiredApiVersion, + nextStructure); + nextStructure = nextStructure->pNext; + } + } + } + + features->additionalDeviceExtensionCount = options->device_extension_count; + features->additionalDeviceExtensionNames = options->device_extension_names; + features->additionalInstanceExtensionCount = options->instance_extension_count; + features->additionalInstanceExtensionNames = options->instance_extension_names; + } else if (renderer->debugMode) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, + "VULKAN_INTERNAL_AddOptInVulkanOptions: Additional options property was set, but value was null. This may be a bug."); + } + } +} + +static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer, VulkanFeatures *features) { VkResult vulkanResult; VkApplicationInfo appInfo; @@ -11147,7 +11744,9 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) appInfo.applicationVersion = 0; appInfo.pEngineName = "SDLGPU"; appInfo.engineVersion = SDL_VERSION; - appInfo.apiVersion = VK_MAKE_VERSION(1, 3, 0); + appInfo.apiVersion = features->usesCustomVulkanOptions + ? features->desiredApiVersion + : VK_MAKE_VERSION(1, 0, 0); createFlags = 0; @@ -11161,40 +11760,53 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) return 0; } + Uint32 extraInstanceExtensionCount = features->additionalInstanceExtensionCount; + const char** extraInstanceExtensionNames = features->additionalInstanceExtensionNames; + /* Extra space for the following extensions: * VK_KHR_get_physical_device_properties2 * VK_EXT_swapchain_colorspace * VK_EXT_debug_utils * VK_KHR_portability_enumeration + * + * Plus additional opt-in extensions. */ instanceExtensionNames = SDL_stack_alloc( const char *, - instanceExtensionCount + 4); - SDL_memcpy((void *)instanceExtensionNames, originalInstanceExtensionNames, instanceExtensionCount * sizeof(const char *)); + instanceExtensionCount + 4 + extraInstanceExtensionCount); + const char** nextInstanceExtensionNamePtr = instanceExtensionNames; + SDL_memcpy((void *)nextInstanceExtensionNamePtr, originalInstanceExtensionNames, instanceExtensionCount * sizeof(const char *)); + nextInstanceExtensionNamePtr += instanceExtensionCount; - // Core since 1.1 - instanceExtensionNames[instanceExtensionCount++] = - VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; - -#ifdef SDL_PLATFORM_APPLE - instanceExtensionNames[instanceExtensionCount++] = - VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; - createFlags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; -#endif + if (extraInstanceExtensionCount > 0) { + SDL_memcpy((void *)nextInstanceExtensionNamePtr, extraInstanceExtensionNames, extraInstanceExtensionCount * sizeof(const char *)); + nextInstanceExtensionNamePtr += extraInstanceExtensionCount; + } + int firstUnsupportedExtensionIndex = 0; if (!VULKAN_INTERNAL_CheckInstanceExtensions( instanceExtensionNames, - instanceExtensionCount, + instanceExtensionCount + extraInstanceExtensionCount, &renderer->supportsDebugUtils, - &renderer->supportsColorspace)) { + &renderer->supportsColorspace, + &renderer->supportsPhysicalDeviceProperties2, + &renderer->supportsPortabilityEnumeration, + &firstUnsupportedExtensionIndex)) { + if (renderer->debugMode) { + SDL_LogError(SDL_LOG_CATEGORY_GPU, + "Required Vulkan instance extension '%s' not supported", + instanceExtensionNames[firstUnsupportedExtensionIndex]); + } + SDL_SetError("Required Vulkan instance extension '%s' not supported", + instanceExtensionNames[firstUnsupportedExtensionIndex]); SDL_stack_free((char *)instanceExtensionNames); - SET_STRING_ERROR_AND_RETURN("Required Vulkan instance extensions not supported", false); + return false; } if (renderer->supportsDebugUtils) { // Append the debug extension - instanceExtensionNames[instanceExtensionCount++] = - VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + *nextInstanceExtensionNamePtr++ = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + instanceExtensionCount++; } else { SDL_LogWarn( SDL_LOG_CATEGORY_GPU, @@ -11204,8 +11816,20 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) if (renderer->supportsColorspace) { // Append colorspace extension - instanceExtensionNames[instanceExtensionCount++] = - VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME; + *nextInstanceExtensionNamePtr++ = VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME; + instanceExtensionCount++; + } + + if (renderer->supportsPhysicalDeviceProperties2) { + // Append KHR_physical_device_properties2 extension + *nextInstanceExtensionNamePtr++ = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; + instanceExtensionCount++; + } + + if (renderer->supportsPortabilityEnumeration) { + *nextInstanceExtensionNamePtr++ = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + instanceExtensionCount++; + createFlags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; } createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; @@ -11213,7 +11837,7 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) createInfo.flags = createFlags; createInfo.pApplicationInfo = &appInfo; createInfo.ppEnabledLayerNames = layerNames; - createInfo.enabledExtensionCount = instanceExtensionCount; + createInfo.enabledExtensionCount = instanceExtensionCount + extraInstanceExtensionCount; createInfo.ppEnabledExtensionNames = instanceExtensionNames; if (renderer->debugMode) { createInfo.enabledLayerCount = SDL_arraysize(layerNames); @@ -11239,57 +11863,193 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) return 1; } -static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( +static bool VULKAN_INTERNAL_GetDeviceRank( VulkanRenderer *renderer, VkPhysicalDevice physicalDevice, VulkanExtensions *physicalDeviceExtensions, - Uint32 *queueFamilyIndex, - Uint8 *deviceRank) + Uint64 *deviceRank) { - Uint32 queueFamilyCount, queueFamilyRank, queueFamilyBest; - VkQueueFamilyProperties *queueProps; - bool supportsPresent; - VkPhysicalDeviceProperties deviceProperties; - VkPhysicalDeviceFeatures deviceFeatures; - Uint32 i; - + static const Uint8 DEVICE_PRIORITY_HIGHPERFORMANCE[] = { + 0, // VK_PHYSICAL_DEVICE_TYPE_OTHER + 3, // VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU + 4, // VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU + 2, // VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU + 1 // VK_PHYSICAL_DEVICE_TYPE_CPU + }; + static const Uint8 DEVICE_PRIORITY_LOWPOWER[] = { + 0, // VK_PHYSICAL_DEVICE_TYPE_OTHER + 4, // VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU + 3, // VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU + 2, // VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU + 1 // VK_PHYSICAL_DEVICE_TYPE_CPU + }; const Uint8 *devicePriority = renderer->preferLowPower ? DEVICE_PRIORITY_LOWPOWER : DEVICE_PRIORITY_HIGHPERFORMANCE; + bool isConformant; - /* Get the device rank before doing any checks, in case one fails. - * Note: If no dedicated device exists, one that supports our features - * would be fine + VkPhysicalDeviceType deviceType; + if (physicalDeviceExtensions->KHR_driver_properties || physicalDeviceExtensions->MSFT_layered_driver) { + VkPhysicalDeviceProperties2KHR physicalDeviceProperties; + VkPhysicalDeviceDriverPropertiesKHR physicalDeviceDriverProperties = { 0 }; + VkPhysicalDeviceLayeredDriverPropertiesMSFT physicalDeviceLayeredDriverProperties = { 0 }; + void** ppNext = &physicalDeviceProperties.pNext; + + physicalDeviceProperties.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + + if (physicalDeviceExtensions->KHR_driver_properties) { + *ppNext = &physicalDeviceDriverProperties; + ppNext = &physicalDeviceDriverProperties.pNext; + + physicalDeviceDriverProperties.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR; + } + + if (physicalDeviceExtensions->MSFT_layered_driver) { + *ppNext = &physicalDeviceLayeredDriverProperties; + ppNext = &physicalDeviceLayeredDriverProperties.pNext; + + physicalDeviceLayeredDriverProperties.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LAYERED_DRIVER_PROPERTIES_MSFT; + } + + *ppNext = NULL; + renderer->vkGetPhysicalDeviceProperties2KHR( + physicalDevice, + &physicalDeviceProperties); + + if (physicalDeviceExtensions->KHR_driver_properties) { + isConformant = (physicalDeviceDriverProperties.conformanceVersion.major >= 1); + } else { + isConformant = true; // We can't check this, so just assume it's conformant + } + + if (physicalDeviceExtensions->MSFT_layered_driver && physicalDeviceLayeredDriverProperties.underlyingAPI != VK_LAYERED_DRIVER_UNDERLYING_API_NONE_MSFT) { + /* Rank Dozen above CPU, but below INTEGRATED. + * This is needed for WSL specifically. + */ + deviceType = VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU; + + /* Dozen hasn't been tested for conformance and it probably won't be, + * but WSL may need this so let's be generous. + * -flibit + */ + isConformant = true; + } else { + deviceType = physicalDeviceProperties.properties.deviceType; + } + } else { + VkPhysicalDeviceProperties physicalDeviceProperties; + renderer->vkGetPhysicalDeviceProperties( + physicalDevice, + &physicalDeviceProperties); + deviceType = physicalDeviceProperties.deviceType; + isConformant = true; // We can't check this, so just assume it's conformant + } + + if (renderer->requireHardwareAcceleration) { + if (deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && + deviceType != VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU && + deviceType != VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU) { + // In addition to CPU, "Other" drivers (including layered drivers) don't count as hardware-accelerated + return 0; + } + } + + /* As far as I know, the only drivers available to users that are also + * non-conformant are incomplete Mesa drivers and Vulkan-on-12. hasvk is one + * example of a non-conformant driver that's built by default. + * -flibit */ - renderer->vkGetPhysicalDeviceProperties( - physicalDevice, - &deviceProperties); - if (*deviceRank < devicePriority[deviceProperties.deviceType]) { + if (!isConformant) { + return 0; + } + + /* Apply a large bias on the devicePriority so that we always respect the order in the priority arrays. + * We also rank by e.g. VRAM which should have less influence than the device type. + */ + Uint64 devicePriorityValue = devicePriority[deviceType] * 1000000; + + if (*deviceRank < devicePriorityValue) { /* This device outranks the best device we've found so far! * This includes a dedicated GPU that has less features than an * integrated GPU, because this is a freak case that is almost * never intentionally desired by the end user */ - *deviceRank = devicePriority[deviceProperties.deviceType]; - } else if (*deviceRank > devicePriority[deviceProperties.deviceType]) { + *deviceRank = devicePriorityValue; + } else if (*deviceRank > devicePriorityValue) { /* Device is outranked by a previous device, don't even try to * run a query and reset the rank to avoid overwrites */ *deviceRank = 0; - return 0; + return false; } + /* If we prefer high performance, sum up all device local memory (rounded to megabytes) + * to deviceRank. In the niche case of someone having multiple dedicated GPUs in the same + * system, this theoretically picks the most powerful one (or at least the one with the + * most memory!) + * + * We do this *after* discarding all non suitable devices, which means if this computer + * has multiple dedicated GPUs that all meet our criteria, *and* the user asked for high + * performance, then we always pick the GPU with more VRAM. + */ + if (!renderer->preferLowPower) { + Uint32 i; + Uint64 videoMemory = 0; + VkPhysicalDeviceMemoryProperties deviceMemory; + renderer->vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemory); + for (i = 0; i < deviceMemory.memoryHeapCount; i++) { + VkMemoryHeap heap = deviceMemory.memoryHeaps[i]; + if (heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) { + videoMemory += heap.size; + } + } + // Round it to megabytes (as per the vulkan spec videoMemory is in bytes) + Uint64 videoMemoryRounded = videoMemory / 1024 / 1024; + *deviceRank += videoMemoryRounded; + } + + return true; +} + +static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( + VulkanRenderer *renderer, + VulkanFeatures *features, + VkPhysicalDevice physicalDevice, + VulkanExtensions *physicalDeviceExtensions, + Uint32 *queueFamilyIndex) +{ + Uint32 queueFamilyCount, queueFamilyRank, queueFamilyBest; + VkQueueFamilyProperties *queueProps; + bool supportsPresent; + VkPhysicalDeviceFeatures deviceFeatures; + Uint32 i; + renderer->vkGetPhysicalDeviceFeatures( physicalDevice, &deviceFeatures); - if (!deviceFeatures.independentBlend || - !deviceFeatures.imageCubeArray || - !deviceFeatures.depthClamp || - !deviceFeatures.shaderClipDistance || - !deviceFeatures.drawIndirectFirstInstance) { + + if ((!deviceFeatures.independentBlend && features->desiredVulkan10DeviceFeatures.independentBlend) || + (!deviceFeatures.imageCubeArray && features->desiredVulkan10DeviceFeatures.imageCubeArray) || + (!deviceFeatures.depthClamp && features->desiredVulkan10DeviceFeatures.depthClamp) || + (!deviceFeatures.shaderClipDistance && features->desiredVulkan10DeviceFeatures.shaderClipDistance) || + (!deviceFeatures.drawIndirectFirstInstance && features->desiredVulkan10DeviceFeatures.drawIndirectFirstInstance) || + (!deviceFeatures.sampleRateShading && features->desiredVulkan10DeviceFeatures.sampleRateShading) || + (!deviceFeatures.samplerAnisotropy && features->desiredVulkan10DeviceFeatures.samplerAnisotropy)) { return 0; } + // Check opt-in device features + if (features->usesCustomVulkanOptions) { + bool supportsAllFeatures = VULKAN_INTERNAL_ValidateOptInFeatures(renderer, features, physicalDevice, &deviceFeatures); + if (!supportsAllFeatures) { + return 0; + } + } + if (!VULKAN_INTERNAL_CheckDeviceExtensions( renderer, + features, physicalDevice, physicalDeviceExtensions)) { return 0; @@ -11373,15 +12133,15 @@ static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( return 1; } -static Uint8 VULKAN_INTERNAL_DeterminePhysicalDevice(VulkanRenderer *renderer) +static Uint8 VULKAN_INTERNAL_DeterminePhysicalDevice(VulkanRenderer *renderer, VulkanFeatures *features) { VkResult vulkanResult; VkPhysicalDevice *physicalDevices; VulkanExtensions *physicalDeviceExtensions; Uint32 i, physicalDeviceCount; Sint32 suitableIndex; - Uint32 queueFamilyIndex, suitableQueueFamilyIndex; - Uint8 deviceRank, highestRank; + Uint32 suitableQueueFamilyIndex; + Uint64 highestRank; vulkanResult = renderer->vkEnumeratePhysicalDevices( renderer->instance, @@ -11427,12 +12187,24 @@ static Uint8 VULKAN_INTERNAL_DeterminePhysicalDevice(VulkanRenderer *renderer) suitableQueueFamilyIndex = 0; highestRank = 0; for (i = 0; i < physicalDeviceCount; i += 1) { + Uint32 queueFamilyIndex; + Uint64 deviceRank; + + if (!VULKAN_INTERNAL_IsDeviceSuitable( + renderer, + features, + physicalDevices[i], + &physicalDeviceExtensions[i], + &queueFamilyIndex)) { + // Device does not meet the minimum requirements, skip it entirely + continue; + } + deviceRank = highestRank; - if (VULKAN_INTERNAL_IsDeviceSuitable( + if (VULKAN_INTERNAL_GetDeviceRank( renderer, physicalDevices[i], &physicalDeviceExtensions[i], - &queueFamilyIndex, &deviceRank)) { /* Use this for rendering. * Note that this may override a previous device that @@ -11441,17 +12213,6 @@ static Uint8 VULKAN_INTERNAL_DeterminePhysicalDevice(VulkanRenderer *renderer) suitableIndex = i; suitableQueueFamilyIndex = queueFamilyIndex; highestRank = deviceRank; - } else if (deviceRank > highestRank) { - /* In this case, we found a... "realer?" GPU, - * but it doesn't actually support our Vulkan. - * We should disqualify all devices below as a - * result, because if we don't we end up - * ignoring real hardware and risk using - * something like LLVMpipe instead! - * -flibit - */ - suitableIndex = -1; - highestRank = deviceRank; } } @@ -11496,11 +12257,11 @@ static Uint8 VULKAN_INTERNAL_DeterminePhysicalDevice(VulkanRenderer *renderer) } static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( - VulkanRenderer *renderer) + VulkanRenderer *renderer, + VulkanFeatures *features) { VkResult vulkanResult; VkDeviceCreateInfo deviceCreateInfo; - VkPhysicalDeviceFeatures desiredDeviceFeatures; VkPhysicalDeviceFeatures haveDeviceFeatures; VkPhysicalDevicePortabilitySubsetFeaturesKHR portabilityFeatures; const char **deviceExtensions; @@ -11524,21 +12285,13 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( // specifying used device features - SDL_zero(desiredDeviceFeatures); - desiredDeviceFeatures.independentBlend = VK_TRUE; - desiredDeviceFeatures.samplerAnisotropy = VK_TRUE; - desiredDeviceFeatures.imageCubeArray = VK_TRUE; - desiredDeviceFeatures.depthClamp = VK_TRUE; - desiredDeviceFeatures.shaderClipDistance = VK_TRUE; - desiredDeviceFeatures.drawIndirectFirstInstance = VK_TRUE; - if (haveDeviceFeatures.fillModeNonSolid) { - desiredDeviceFeatures.fillModeNonSolid = VK_TRUE; + features->desiredVulkan10DeviceFeatures.fillModeNonSolid = VK_TRUE; renderer->supportsFillModeNonSolid = true; } if (haveDeviceFeatures.multiDrawIndirect) { - desiredDeviceFeatures.multiDrawIndirect = VK_TRUE; + features->desiredVulkan10DeviceFeatures.multiDrawIndirect = VK_TRUE; renderer->supportsMultiDrawIndirect = true; } @@ -11579,15 +12332,67 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( deviceCreateInfo.enabledExtensionCount); CreateDeviceExtensionArray(&renderer->supports, deviceExtensions); deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions; - deviceCreateInfo.pEnabledFeatures = &desiredDeviceFeatures; - VkPhysicalDeviceVulkan11Features device_features_vulkan11 = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES, - .pNext = (void *)deviceCreateInfo.pNext, - .shaderDrawParameters = VK_TRUE, - }; + VkPhysicalDeviceFeatures2 featureList; + int minor = VK_VERSION_MINOR(features->desiredApiVersion); - deviceCreateInfo.pNext = &device_features_vulkan11; + struct { + VkPhysicalDevice16BitStorageFeatures storage; + VkPhysicalDeviceMultiviewFeatures multiview; + VkPhysicalDeviceProtectedMemoryFeatures protectedMem; + VkPhysicalDeviceSamplerYcbcrConversionFeatures ycbcr; + VkPhysicalDeviceShaderDrawParametersFeatures drawParams; + VkPhysicalDeviceVariablePointersFeatures varPointers; + } legacyFeatures; + + if (features->usesCustomVulkanOptions && minor > 0) { + featureList.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + featureList.features = features->desiredVulkan10DeviceFeatures; + if (minor > 1) { + featureList.pNext = &features->desiredVulkan11DeviceFeatures; + features->desiredVulkan11DeviceFeatures.pNext = &features->desiredVulkan12DeviceFeatures; + features->desiredVulkan12DeviceFeatures.pNext = minor > 2 ? &features->desiredVulkan13DeviceFeatures : NULL; + features->desiredVulkan13DeviceFeatures.pNext = NULL; + } else { + // Break VkPhysicalDeviceVulkan11Features into pre 1.2 structures for Vulkan 1.1 Support + SDL_zero(legacyFeatures); + + legacyFeatures.storage.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES; + legacyFeatures.storage.storageBuffer16BitAccess = features->desiredVulkan11DeviceFeatures.storageBuffer16BitAccess; + legacyFeatures.storage.storageInputOutput16 = features->desiredVulkan11DeviceFeatures.storageInputOutput16; + legacyFeatures.storage.storagePushConstant16 = features->desiredVulkan11DeviceFeatures.storagePushConstant16; + legacyFeatures.storage.uniformAndStorageBuffer16BitAccess = features->desiredVulkan11DeviceFeatures.uniformAndStorageBuffer16BitAccess; + + legacyFeatures.multiview.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; + legacyFeatures.multiview.multiview = features->desiredVulkan11DeviceFeatures.multiview; + legacyFeatures.multiview.multiviewGeometryShader = features->desiredVulkan11DeviceFeatures.multiviewGeometryShader; + legacyFeatures.multiview.multiviewTessellationShader = features->desiredVulkan11DeviceFeatures.multiviewTessellationShader; + + legacyFeatures.protectedMem.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES; + legacyFeatures.protectedMem.protectedMemory = features->desiredVulkan11DeviceFeatures.protectedMemory; + + legacyFeatures.ycbcr.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES; + legacyFeatures.ycbcr.samplerYcbcrConversion = features->desiredVulkan11DeviceFeatures.samplerYcbcrConversion; + + legacyFeatures.drawParams.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES; + legacyFeatures.drawParams.shaderDrawParameters = features->desiredVulkan11DeviceFeatures.shaderDrawParameters; + + legacyFeatures.varPointers.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES; + legacyFeatures.varPointers.variablePointers = features->desiredVulkan11DeviceFeatures.variablePointers; + legacyFeatures.varPointers.variablePointersStorageBuffer = features->desiredVulkan11DeviceFeatures.variablePointersStorageBuffer; + + featureList.pNext = &legacyFeatures.storage; + legacyFeatures.storage.pNext = &legacyFeatures.multiview; + legacyFeatures.multiview.pNext = &legacyFeatures.protectedMem; + legacyFeatures.protectedMem.pNext = &legacyFeatures.ycbcr; + legacyFeatures.ycbcr.pNext = &legacyFeatures.drawParams; + legacyFeatures.drawParams.pNext = &legacyFeatures.varPointers; + } + deviceCreateInfo.pEnabledFeatures = NULL; + deviceCreateInfo.pNext = &featureList; + } else { + deviceCreateInfo.pEnabledFeatures = &features->desiredVulkan10DeviceFeatures; + } vulkanResult = renderer->vkCreateDevice( renderer->physicalDevice, @@ -11652,11 +12457,31 @@ static void VULKAN_INTERNAL_LoadEntryPoints(void) } static bool VULKAN_INTERNAL_PrepareVulkan( - VulkanRenderer *renderer) + VulkanRenderer *renderer, + VulkanFeatures *features, + SDL_PropertiesID props) { VULKAN_INTERNAL_LoadEntryPoints(); - if (!VULKAN_INTERNAL_CreateInstance(renderer)) { + SDL_zerop(features); + + // Opt out device features (higher compatibility in exchange for reduced functionality) + features->desiredVulkan10DeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + features->desiredVulkan10DeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + features->desiredVulkan10DeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + features->desiredVulkan10DeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + + // These features have near universal support so they are always enabled + features->desiredVulkan10DeviceFeatures.independentBlend = VK_TRUE; + features->desiredVulkan10DeviceFeatures.sampleRateShading = VK_TRUE; + features->desiredVulkan10DeviceFeatures.imageCubeArray = VK_TRUE; + + // Handle opt-in device features + VULKAN_INTERNAL_AddOptInVulkanOptions(props, renderer, features); + + renderer->requireHardwareAcceleration = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_REQUIRE_HARDWARE_ACCELERATION_BOOLEAN, false); + + if (!VULKAN_INTERNAL_CreateInstance(renderer, features)) { SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Vulkan: Could not create Vulkan instance"); return false; } @@ -11665,19 +12490,24 @@ static bool VULKAN_INTERNAL_PrepareVulkan( renderer->func = (PFN_##func)vkGetInstanceProcAddr(renderer->instance, #func); #include "SDL_gpu_vulkan_vkfuncs.h" - if (!VULKAN_INTERNAL_DeterminePhysicalDevice(renderer)) { + if (!VULKAN_INTERNAL_DeterminePhysicalDevice(renderer, features)) { SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Vulkan: Failed to determine a suitable physical device"); return false; } return true; } -static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this) +static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) { // Set up dummy VulkanRenderer VulkanRenderer *renderer; + VulkanFeatures features; bool result = false; + if (!SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, false)) { + return false; + } + if (_this->Vulkan_CreateSurface == NULL) { return false; } @@ -11688,10 +12518,16 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this) renderer = (VulkanRenderer *)SDL_calloc(1, sizeof(*renderer)); if (renderer) { - result = VULKAN_INTERNAL_PrepareVulkan(renderer); + // This needs to be set early for log filtering + renderer->debugMode = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, false); + + renderer->preferLowPower = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, false); + + result = VULKAN_INTERNAL_PrepareVulkan(renderer, &features, props); if (result) { renderer->vkDestroyInstance(renderer->instance, NULL); } + SDL_free(renderer); } SDL_Vulkan_UnloadLibrary(); @@ -11702,10 +12538,16 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this) static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, SDL_PropertiesID props) { VulkanRenderer *renderer; + VulkanFeatures features; SDL_GPUDevice *result; Uint32 i; + bool verboseLogs = SDL_GetBooleanProperty( + props, + SDL_PROP_GPU_DEVICE_CREATE_VERBOSE_BOOLEAN, + true); + if (!SDL_Vulkan_LoadLibrary(NULL)) { SDL_assert(!"This should have failed in PrepareDevice first!"); return NULL; @@ -11721,36 +12563,114 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->preferLowPower = preferLowPower; renderer->allowedFramesInFlight = 2; - if (!VULKAN_INTERNAL_PrepareVulkan(renderer)) { + if (!VULKAN_INTERNAL_PrepareVulkan(renderer, &features, props)) { SET_STRING_ERROR("Failed to initialize Vulkan!"); SDL_free(renderer); SDL_Vulkan_UnloadLibrary(); return NULL; } - SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: Vulkan"); - SDL_LogInfo( - SDL_LOG_CATEGORY_GPU, - "Vulkan Device: %s", - renderer->physicalDeviceProperties.properties.deviceName); - if (renderer->supports.KHR_driver_properties) { - SDL_LogInfo( - SDL_LOG_CATEGORY_GPU, - "Vulkan Driver: %s %s", - renderer->physicalDeviceDriverProperties.driverName, - renderer->physicalDeviceDriverProperties.driverInfo); - SDL_LogInfo( - SDL_LOG_CATEGORY_GPU, - "Vulkan Conformance: %u.%u.%u", - renderer->physicalDeviceDriverProperties.conformanceVersion.major, - renderer->physicalDeviceDriverProperties.conformanceVersion.minor, - renderer->physicalDeviceDriverProperties.conformanceVersion.patch); - } else { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "KHR_driver_properties unsupported! Bother your vendor about this!"); + renderer->props = SDL_CreateProperties(); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: Vulkan"); } - if (!VULKAN_INTERNAL_CreateLogicalDevice( - renderer)) { + // Record device name + const char *deviceName = renderer->physicalDeviceProperties.properties.deviceName; + SDL_SetStringProperty( + renderer->props, + SDL_PROP_GPU_DEVICE_NAME_STRING, + deviceName); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Vulkan Device: %s", deviceName); + } + + // Record driver version. This is provided as a backup if + // VK_KHR_driver_properties is not available but as most drivers support it + // this property should be rarely used. + // + // This uses a vendor-specific encoding and it isn't well documented. The + // vendor ID is the registered PCI ID of the vendor and can be found in + // online databases. + char driverVer[64]; + Uint32 rawDriverVer = renderer->physicalDeviceProperties.properties.driverVersion; + Uint32 vendorId = renderer->physicalDeviceProperties.properties.vendorID; + if (vendorId == 0x10de) { + // Nvidia uses 10|8|8|6 encoding. + (void)SDL_snprintf( + driverVer, + SDL_arraysize(driverVer), + "%d.%d.%d.%d", + (rawDriverVer >> 22) & 0x3ff, + (rawDriverVer >> 14) & 0xff, + (rawDriverVer >> 6) & 0xff, + rawDriverVer & 0x3f); + } +#ifdef SDL_PLATFORM_WINDOWS + else if (vendorId == 0x8086) { + // Intel uses 18|14 encoding on Windows only. + (void)SDL_snprintf( + driverVer, + SDL_arraysize(driverVer), + "%d.%d", + (rawDriverVer >> 14) & 0x3ffff, + rawDriverVer & 0x3fff); + } +#endif + else { + // Assume standard Vulkan 10|10|12 encoding for everything else. AMD and + // Mesa are known to use this encoding. + (void)SDL_snprintf( + driverVer, + SDL_arraysize(driverVer), + "%d.%d.%d", + (rawDriverVer >> 22) & 0x3ff, + (rawDriverVer >> 12) & 0x3ff, + rawDriverVer & 0xfff); + } + SDL_SetStringProperty( + renderer->props, + SDL_PROP_GPU_DEVICE_DRIVER_VERSION_STRING, + driverVer); + // Log this only if VK_KHR_driver_properties is not available. + + if (renderer->supports.KHR_driver_properties) { + // Record driver name and version + const char *driverName = renderer->physicalDeviceDriverProperties.driverName; + const char *driverInfo = renderer->physicalDeviceDriverProperties.driverInfo; + SDL_SetStringProperty( + renderer->props, + SDL_PROP_GPU_DEVICE_DRIVER_NAME_STRING, + driverName); + SDL_SetStringProperty( + renderer->props, + SDL_PROP_GPU_DEVICE_DRIVER_INFO_STRING, + driverInfo); + if (verboseLogs) { + // FIXME: driverInfo can be a multiline string. + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Vulkan Driver: %s %s", driverName, driverInfo); + } + + // Record conformance level + if (verboseLogs) { + char conformance[64]; + (void)SDL_snprintf( + conformance, + SDL_arraysize(conformance), + "%u.%u.%u.%u", + renderer->physicalDeviceDriverProperties.conformanceVersion.major, + renderer->physicalDeviceDriverProperties.conformanceVersion.minor, + renderer->physicalDeviceDriverProperties.conformanceVersion.subminor, + renderer->physicalDeviceDriverProperties.conformanceVersion.patch); + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Vulkan Conformance: %s", conformance); + } + } else { + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Vulkan Driver: %s", driverVer); + } + } + + if (!VULKAN_INTERNAL_CreateLogicalDevice(renderer, &features)) { SET_STRING_ERROR("Failed to create logical device!"); SDL_free(renderer); SDL_Vulkan_UnloadLibrary(); @@ -11758,7 +12678,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S } // FIXME: just move this into this function - result = (SDL_GPUDevice *)SDL_malloc(sizeof(SDL_GPUDevice)); + result = (SDL_GPUDevice *)SDL_calloc(1, sizeof(SDL_GPUDevice)); ASSIGN_DRIVER(VULKAN) result->driverData = (SDL_GPURenderer *)renderer; @@ -11956,7 +12876,6 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S SDL_GPUBootstrap VulkanDriver = { "vulkan", - SDL_GPU_SHADERFORMAT_SPIRV, VULKAN_PrepareDriver, VULKAN_CreateDevice }; diff --git a/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan_vkfuncs.h b/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan_vkfuncs.h index 7316eb9..a56beff 100644 --- a/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan_vkfuncs.h +++ b/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan_vkfuncs.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -51,6 +51,9 @@ VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceImageFormatProperties) VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceMemoryProperties) VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceProperties) +// Vulkan 1.1 (Needed for opt-in feature checks) +VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceFeatures2) + // VK_KHR_get_physical_device_properties2, needed for KHR_driver_properties VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceProperties2KHR) diff --git a/libs/SDL3/src/haptic/SDL_haptic.c b/libs/SDL3/src/haptic/SDL_haptic.c index 1c11db6..5eaf095 100644 --- a/libs/SDL3/src/haptic/SDL_haptic.c +++ b/libs/SDL3/src/haptic/SDL_haptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -21,6 +21,9 @@ #include "SDL_internal.h" #include "SDL_syshaptic.h" +#ifdef SDL_JOYSTICK_HIDAPI +#include "hidapi/SDL_hidapihaptic.h" +#endif #include "SDL_haptic_c.h" #include "../joystick/SDL_joystick_c.h" // For SDL_IsJoystickValid #include "../SDL_hints_c.h" @@ -104,15 +107,25 @@ static int SDL_Haptic_Get_Naxes(Uint16 vid, Uint16 pid) static SDL_Haptic *SDL_haptics = NULL; -#define CHECK_HAPTIC_MAGIC(haptic, result) \ - if (!SDL_ObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC)) { \ - SDL_InvalidParamError("haptic"); \ - return result; \ +#define CHECK_HAPTIC_MAGIC(haptic, result) \ + CHECK_PARAM(!SDL_ObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC)) { \ + SDL_InvalidParamError("haptic"); \ + return result; \ } bool SDL_InitHaptics(void) { - return SDL_SYS_HapticInit(); + if (!SDL_SYS_HapticInit()) { + return false; + } + #ifdef SDL_JOYSTICK_HIDAPI + if (!SDL_HIDAPI_HapticInit()) { + SDL_SYS_HapticQuit(); + return false; + } + #endif + + return true; } static bool SDL_GetHapticIndex(SDL_HapticID instance_id, int *driver_index) @@ -205,7 +218,6 @@ SDL_Haptic *SDL_OpenHaptic(SDL_HapticID instance_id) } // Initialize the haptic device - SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); haptic->instance_id = instance_id; haptic->rumble_id = -1; if (!SDL_SYS_HapticOpen(haptic)) { @@ -227,6 +239,8 @@ SDL_Haptic *SDL_OpenHaptic(SDL_HapticID instance_id) haptic->next = SDL_haptics; SDL_haptics = haptic; + SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); + // Disable autocenter and set gain to max. if (haptic->supported & SDL_HAPTIC_GAIN) { SDL_SetHapticGain(haptic, 100); @@ -295,7 +309,11 @@ bool SDL_IsJoystickHaptic(SDL_Joystick *joystick) // Must be a valid joystick if (SDL_IsJoystickValid(joystick) && !SDL_IsGamepad(SDL_GetJoystickID(joystick))) { + #ifdef SDL_JOYSTICK_HIDAPI + result = SDL_SYS_JoystickIsHaptic(joystick) || SDL_HIDAPI_JoystickIsHaptic(joystick); + #else result = SDL_SYS_JoystickIsHaptic(joystick); + #endif } } SDL_UnlockJoysticks(); @@ -310,16 +328,8 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) SDL_LockJoysticks(); { - // Must be a valid joystick - if (!SDL_IsJoystickValid(joystick)) { - SDL_SetError("Haptic: Joystick isn't valid."); - SDL_UnlockJoysticks(); - return NULL; - } - - // Joystick must be haptic - if (SDL_IsGamepad(SDL_GetJoystickID(joystick)) || - !SDL_SYS_JoystickIsHaptic(joystick)) { + // Joystick must be valid and haptic + if (!SDL_IsJoystickHaptic(joystick)) { SDL_SetError("Haptic: Joystick isn't a haptic device."); SDL_UnlockJoysticks(); return NULL; @@ -328,7 +338,11 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) hapticlist = SDL_haptics; // Check to see if joystick's haptic is already open while (hapticlist) { + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_SYS_JoystickSameHaptic(hapticlist, joystick) || SDL_HIDAPI_JoystickSameHaptic(hapticlist, joystick)) { + #else if (SDL_SYS_JoystickSameHaptic(hapticlist, joystick)) { + #endif haptic = hapticlist; ++haptic->ref_count; SDL_UnlockJoysticks(); @@ -349,6 +363,16 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) */ SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); haptic->rumble_id = -1; + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_JoystickIsHaptic(joystick)) { + if (!SDL_HIDAPI_HapticOpenFromJoystick(haptic, joystick)) { + SDL_SetError("Haptic: SDL_HIDAPI_HapticOpenFromJoystick failed."); + SDL_free(haptic); + SDL_UnlockJoysticks(); + return NULL; + } + } else + #endif if (!SDL_SYS_HapticOpenFromJoystick(haptic, joystick)) { SDL_SetError("Haptic: SDL_SYS_HapticOpenFromJoystick failed."); SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false); @@ -379,12 +403,22 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) haptic->next = SDL_haptics; SDL_haptics = haptic; + SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); + + // Disable autocenter and set gain to max. + if (haptic->supported & SDL_HAPTIC_GAIN) { + SDL_SetHapticGain(haptic, 100); + } + if (haptic->supported & SDL_HAPTIC_AUTOCENTER) { + SDL_SetHapticAutocenter(haptic, 0); + } + return haptic; } void SDL_CloseHaptic(SDL_Haptic *haptic) { - int i; + SDL_HapticEffectID i; SDL_Haptic *hapticlist; SDL_Haptic *hapticlistprev; @@ -395,13 +429,20 @@ void SDL_CloseHaptic(SDL_Haptic *haptic) return; } - // Close it, properly removing effects if needed - for (i = 0; i < haptic->neffects; i++) { - if (haptic->effects[i].hweffect != NULL) { - SDL_DestroyHapticEffect(haptic, i); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + SDL_HIDAPI_HapticClose(haptic); + } else + #endif + { + // Close it, properly removing effects if needed + for (i = 0; i < haptic->neffects; i++) { + if (haptic->effects[i].hweffect != NULL) { + SDL_DestroyHapticEffect(haptic, i); + } } + SDL_SYS_HapticClose(haptic); } - SDL_SYS_HapticClose(haptic); SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false); // Remove from the list @@ -433,6 +474,9 @@ void SDL_QuitHaptics(void) SDL_CloseHaptic(SDL_haptics); } + #ifdef SDL_JOYSTICK_HIDAPI + SDL_HIDAPI_HapticQuit(); + #endif SDL_SYS_HapticQuit(); } @@ -468,7 +512,7 @@ bool SDL_HapticEffectSupported(SDL_Haptic *haptic, const SDL_HapticEffect *effec { CHECK_HAPTIC_MAGIC(haptic, false); - if (!effect) { + CHECK_PARAM(!effect) { return false; } @@ -478,13 +522,13 @@ bool SDL_HapticEffectSupported(SDL_Haptic *haptic, const SDL_HapticEffect *effec return false; } -int SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect) +SDL_HapticEffectID SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect) { - int i; + SDL_HapticEffectID i; CHECK_HAPTIC_MAGIC(haptic, -1); - if (!effect) { + CHECK_PARAM(!effect) { SDL_InvalidParamError("effect"); return -1; } @@ -495,6 +539,12 @@ int SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect) return -1; } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticNewEffect(haptic, effect); + } + #endif + // See if there's a free slot for (i = 0; i < haptic->neffects; i++) { if (haptic->effects[i].hweffect == NULL) { @@ -514,7 +564,7 @@ int SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect) return -1; } -static bool ValidEffect(SDL_Haptic *haptic, int effect) +static bool ValidEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect) { if ((effect < 0) || (effect >= haptic->neffects)) { SDL_SetError("Haptic: Invalid effect identifier."); @@ -523,23 +573,29 @@ static bool ValidEffect(SDL_Haptic *haptic, int effect) return true; } -bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, int effect, const SDL_HapticEffect *data) +bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect, const SDL_HapticEffect *data) { CHECK_HAPTIC_MAGIC(haptic, false); - if (!ValidEffect(haptic, effect)) { + CHECK_PARAM(!ValidEffect(haptic, effect)) { return false; } - if (!data) { + CHECK_PARAM(!data) { return SDL_InvalidParamError("data"); } // Can't change type dynamically. - if (data->type != haptic->effects[effect].effect.type) { + CHECK_PARAM(data->type != haptic->effects[effect].effect.type) { return SDL_SetError("Haptic: Updating effect type is illegal."); } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticUpdateEffect(haptic, effect, data); + } + #endif + // Updates the effect if (!SDL_SYS_HapticUpdateEffect(haptic, &haptic->effects[effect], data)) { return false; @@ -550,10 +606,16 @@ bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, int effect, const SDL_HapticEffe return true; } -bool SDL_RunHapticEffect(SDL_Haptic *haptic, int effect, Uint32 iterations) +bool SDL_RunHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect, Uint32 iterations) { CHECK_HAPTIC_MAGIC(haptic, false); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticRunEffect(haptic, effect, iterations); + } + #endif + if (!ValidEffect(haptic, effect)) { return false; } @@ -566,10 +628,16 @@ bool SDL_RunHapticEffect(SDL_Haptic *haptic, int effect, Uint32 iterations) return true; } -bool SDL_StopHapticEffect(SDL_Haptic *haptic, int effect) +bool SDL_StopHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect) { CHECK_HAPTIC_MAGIC(haptic, false); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticStopEffect(haptic, effect); + } + #endif + if (!ValidEffect(haptic, effect)) { return false; } @@ -582,10 +650,17 @@ bool SDL_StopHapticEffect(SDL_Haptic *haptic, int effect) return true; } -void SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect) +void SDL_DestroyHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect) { CHECK_HAPTIC_MAGIC(haptic,); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + SDL_HIDAPI_HapticDestroyEffect(haptic, effect); + return; + } + #endif + if (!ValidEffect(haptic, effect)) { return; } @@ -598,10 +673,16 @@ void SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect) SDL_SYS_HapticDestroyEffect(haptic, &haptic->effects[effect]); } -bool SDL_GetHapticEffectStatus(SDL_Haptic *haptic, int effect) +bool SDL_GetHapticEffectStatus(SDL_Haptic *haptic, SDL_HapticEffectID effect) { CHECK_HAPTIC_MAGIC(haptic, false); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticGetEffectStatus(haptic, effect); + } + #endif + if (!ValidEffect(haptic, effect)) { return false; } @@ -648,6 +729,12 @@ bool SDL_SetHapticGain(SDL_Haptic *haptic, int gain) real_gain = gain; } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticSetGain(haptic, real_gain); + } + #endif + return SDL_SYS_HapticSetGain(haptic, real_gain); } @@ -663,6 +750,12 @@ bool SDL_SetHapticAutocenter(SDL_Haptic *haptic, int autocenter) return SDL_SetError("Haptic: Autocenter must be between 0 and 100."); } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticSetAutocenter(haptic, autocenter); + } + #endif + return SDL_SYS_HapticSetAutocenter(haptic, autocenter); } @@ -674,6 +767,12 @@ bool SDL_PauseHaptic(SDL_Haptic *haptic) return SDL_SetError("Haptic: Device does not support setting pausing."); } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticPause(haptic); + } + #endif + return SDL_SYS_HapticPause(haptic); } @@ -685,6 +784,12 @@ bool SDL_ResumeHaptic(SDL_Haptic *haptic) return true; // Not going to be paused, so we pretend it's unpaused. } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticResume(haptic); + } + #endif + return SDL_SYS_HapticResume(haptic); } @@ -692,6 +797,12 @@ bool SDL_StopHapticEffects(SDL_Haptic *haptic) { CHECK_HAPTIC_MAGIC(haptic, false); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticStopAll(haptic); + } + #endif + return SDL_SYS_HapticStopAll(haptic); } diff --git a/libs/SDL3/src/haptic/SDL_haptic_c.h b/libs/SDL3/src/haptic/SDL_haptic_c.h index 3f5ce87..0ccaf86 100644 --- a/libs/SDL3/src/haptic/SDL_haptic_c.h +++ b/libs/SDL3/src/haptic/SDL_haptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/haptic/SDL_syshaptic.h b/libs/SDL3/src/haptic/SDL_syshaptic.h index ec60a71..74e78f6 100644 --- a/libs/SDL3/src/haptic/SDL_syshaptic.h +++ b/libs/SDL3/src/haptic/SDL_syshaptic.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -52,7 +52,7 @@ struct SDL_Haptic struct haptic_hwdata *hwdata; // Driver dependent int ref_count; // Count for multiple opens - int rumble_id; // ID of rumble effect for simple rumble API. + SDL_HapticEffectID rumble_id; // ID of rumble effect for simple rumble API. SDL_HapticEffect rumble_effect; // Rumble effect. struct SDL_Haptic *next; // pointer to next haptic we have allocated }; diff --git a/libs/SDL3/src/haptic/android/SDL_syshaptic.c b/libs/SDL3/src/haptic/android/SDL_syshaptic.c index d155dbc..760854e 100644 --- a/libs/SDL3/src/haptic/android/SDL_syshaptic.c +++ b/libs/SDL3/src/haptic/android/SDL_syshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/haptic/android/SDL_syshaptic_c.h b/libs/SDL3/src/haptic/android/SDL_syshaptic_c.h index 4f58f4f..ac16356 100644 --- a/libs/SDL3/src/haptic/android/SDL_syshaptic_c.h +++ b/libs/SDL3/src/haptic/android/SDL_syshaptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/haptic/darwin/SDL_syshaptic.c b/libs/SDL3/src/haptic/darwin/SDL_syshaptic.c index b48a3fe..4034320 100644 --- a/libs/SDL3/src/haptic/darwin/SDL_syshaptic.c +++ b/libs/SDL3/src/haptic/darwin/SDL_syshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -785,7 +785,7 @@ static bool SDL_SYS_ToFFEFFECT(SDL_Haptic *haptic, FFEFFECT *dest, const SDL_Hap DWORD *axes = NULL; // Set global stuff. - SDL_memset(dest, 0, sizeof(FFEFFECT)); + SDL_zerop(dest); dest->dwSize = sizeof(FFEFFECT); // Set the structure size. dest->dwSamplePeriod = 0; // Not used by us. dest->dwGain = 10000; // Gain is set globally, not locally. @@ -1166,7 +1166,7 @@ bool SDL_SYS_HapticUpdateEffect(SDL_Haptic *haptic, FFEFFECT temp; // Get the effect. - SDL_memset(&temp, 0, sizeof(FFEFFECT)); + SDL_zero(temp); if (!SDL_SYS_ToFFEFFECT(haptic, &temp, data)) { goto err_update; } diff --git a/libs/SDL3/src/haptic/darwin/SDL_syshaptic_c.h b/libs/SDL3/src/haptic/darwin/SDL_syshaptic_c.h index 03c67ec..0465cc2 100644 --- a/libs/SDL3/src/haptic/darwin/SDL_syshaptic_c.h +++ b/libs/SDL3/src/haptic/darwin/SDL_syshaptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/haptic/dummy/SDL_syshaptic.c b/libs/SDL3/src/haptic/dummy/SDL_syshaptic.c index 81f711f..d44c05c 100644 --- a/libs/SDL3/src/haptic/dummy/SDL_syshaptic.c +++ b/libs/SDL3/src/haptic/dummy/SDL_syshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.c b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.c new file mode 100644 index 0000000..fbcbd0f --- /dev/null +++ b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.c @@ -0,0 +1,317 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Katharine Chui + + 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_JOYSTICK_HIDAPI + +#include "SDL_hidapihaptic.h" +#include "SDL_hidapihaptic_c.h" +#include "SDL3/SDL_mutex.h" +#include "SDL3/SDL_error.h" + +typedef struct haptic_list_node +{ + SDL_Haptic *haptic; + struct haptic_list_node *next; +} haptic_list_node; + +static haptic_list_node *haptic_list_head = NULL; +static SDL_Mutex *haptic_list_mutex = NULL; + +static SDL_HIDAPI_HapticDriver *drivers[] = { + #ifdef SDL_HAPTIC_HIDAPI_LG4FF + &SDL_HIDAPI_HapticDriverLg4ff, + #endif + NULL +}; + +bool SDL_HIDAPI_HapticInit(void) +{ + haptic_list_head = NULL; + haptic_list_mutex = SDL_CreateMutex(); + if (haptic_list_mutex == NULL) { + return SDL_OutOfMemory(); + } + return true; +} + +bool SDL_HIDAPI_HapticIsHidapi(SDL_Haptic *haptic) +{ + haptic_list_node *cur; + bool ret = false; + + SDL_LockMutex(haptic_list_mutex); + cur = haptic_list_head; + while (cur != NULL) { + if (cur->haptic == haptic) { + ret = true; + break; + } + cur = cur->next; + } + + SDL_UnlockMutex(haptic_list_mutex); + + return ret; +} + + +bool SDL_HIDAPI_JoystickIsHaptic(SDL_Joystick *joystick) +{ + const int numdrivers = SDL_arraysize(drivers) - 1; + int i; + + SDL_AssertJoysticksLocked(); + + if (joystick->driver != &SDL_HIDAPI_JoystickDriver) { + return false; + } + + for (i = 0; i < numdrivers; ++i) { + if (drivers[i]->JoystickSupported(joystick)) { + return true; + } + } + return false; +} + +bool SDL_HIDAPI_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick) +{ + const int numdrivers = SDL_arraysize(drivers) - 1; + int i; + + SDL_AssertJoysticksLocked(); + + if (joystick->driver != &SDL_HIDAPI_JoystickDriver) { + return SDL_SetError("Cannot open hidapi haptic from non hidapi joystick"); + } + + for (i = 0; i < numdrivers; ++i) { + if (drivers[i]->JoystickSupported(joystick)) { + SDL_HIDAPI_HapticDevice *device; + haptic_list_node *list_node; + // the driver is responsible for calling SDL_SetError + void *ctx = drivers[i]->Open(joystick); + if (ctx == NULL) { + return false; + } + + device = SDL_malloc(sizeof(SDL_HIDAPI_HapticDevice)); + if (device == NULL) { + SDL_HIDAPI_HapticDevice temp; + temp.ctx = ctx; + temp.driver = drivers[i]; + temp.joystick = joystick; + temp.driver->Close(&temp); + return SDL_OutOfMemory(); + } + + device->driver = drivers[i]; + device->haptic = haptic; + device->joystick = joystick; + device->ctx = ctx; + + list_node = SDL_malloc(sizeof(haptic_list_node)); + if (list_node == NULL) { + device->driver->Close(device); + SDL_free(device); + return SDL_OutOfMemory(); + } + + haptic->hwdata = (struct haptic_hwdata *)device; + + // this is outside of the syshaptic driver + + haptic->neffects = device->driver->NumEffects(device); + haptic->nplaying = device->driver->NumEffectsPlaying(device); + haptic->supported = device->driver->GetFeatures(device); + haptic->naxes = device->driver->NumAxes(device); + haptic->neffects = device->driver->NumEffects(device); + haptic->effects = (struct haptic_effect *)SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects); + if (haptic->effects == NULL) { + device->driver->Close(device); + SDL_free(device); + return SDL_OutOfMemory(); + } + SDL_memset(haptic->effects, 0, sizeof(struct haptic_effect) * haptic->neffects); + + // outside of SYS_HAPTIC + haptic->instance_id = 255; + + list_node->haptic = haptic; + list_node->next = NULL; + + // grab a joystick ref so that it doesn't get fully destroyed before the haptic is closed + SDL_OpenJoystick(SDL_GetJoystickID(joystick)); + + SDL_LockMutex(haptic_list_mutex); + if (haptic_list_head == NULL) { + haptic_list_head = list_node; + } else { + haptic_list_node *cur = haptic_list_head; + while(cur->next != NULL) { + cur = cur->next; + } + cur->next = list_node; + } + SDL_UnlockMutex(haptic_list_mutex); + + return true; + } + } + + return SDL_SetError("No supported HIDAPI haptic driver found for joystick"); +} + +bool SDL_HIDAPI_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick) +{ + SDL_HIDAPI_HapticDevice *device; + + SDL_AssertJoysticksLocked(); + if (joystick->driver != &SDL_HIDAPI_JoystickDriver) { + return false; + } + + device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + + if (joystick == device->joystick) { + return true; + } + return false; +} + +void SDL_HIDAPI_HapticClose(SDL_Haptic *haptic) +{ + haptic_list_node *cur, *last; + + SDL_LockMutex(haptic_list_mutex); + + cur = haptic_list_head; + last = NULL; + while (cur != NULL) { + if (cur->haptic == haptic) { + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + + device->driver->Close(device); + + // a reference was grabbed during open, now release it + SDL_CloseJoystick(device->joystick); + + if (cur == haptic_list_head) { + haptic_list_head = cur->next; + } else { + last->next = cur->next; + } + + SDL_free(device->ctx); + SDL_free(device); + SDL_free(haptic->effects); + SDL_free(cur); + SDL_UnlockMutex(haptic_list_mutex); + return; + } + last = cur; + cur = cur->next; + } + + SDL_UnlockMutex(haptic_list_mutex); +} + +void SDL_HIDAPI_HapticQuit(void) +{ + // the list is cleared in SDL_haptic.c + if (haptic_list_mutex != NULL) { + SDL_DestroyMutex(haptic_list_mutex); + haptic_list_mutex = NULL; + } +} + +SDL_HapticEffectID SDL_HIDAPI_HapticNewEffect(SDL_Haptic *haptic, const SDL_HapticEffect *base) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + SDL_HapticEffectID new_id = device->driver->CreateEffect(device, base); + if (new_id >= 0){ + haptic->effects[new_id].effect = *base; + } + return new_id; +} + +bool SDL_HIDAPI_HapticUpdateEffect(SDL_Haptic *haptic, SDL_HapticEffectID id, const SDL_HapticEffect *data) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->UpdateEffect(device, id, data); +} + +bool SDL_HIDAPI_HapticRunEffect(SDL_Haptic *haptic, SDL_HapticEffectID id, Uint32 iterations) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->RunEffect(device, id, iterations); +} + +bool SDL_HIDAPI_HapticStopEffect(SDL_Haptic *haptic, SDL_HapticEffectID id) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->StopEffect(device, id); +} + +void SDL_HIDAPI_HapticDestroyEffect(SDL_Haptic *haptic, SDL_HapticEffectID id) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + device->driver->DestroyEffect(device, id); +} + +bool SDL_HIDAPI_HapticGetEffectStatus(SDL_Haptic *haptic, SDL_HapticEffectID id) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->GetEffectStatus(device, id); +} + +bool SDL_HIDAPI_HapticSetGain(SDL_Haptic *haptic, int gain) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->SetGain(device, gain); +} + +bool SDL_HIDAPI_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->SetAutocenter(device, autocenter); +} + +bool SDL_HIDAPI_HapticPause(SDL_Haptic *haptic) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->Pause(device); +} + +bool SDL_HIDAPI_HapticResume(SDL_Haptic *haptic) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->Resume(device); +} + +bool SDL_HIDAPI_HapticStopAll(SDL_Haptic *haptic) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->StopEffects(device); +} + +#endif //SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.h b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.h new file mode 100644 index 0000000..4734bbc --- /dev/null +++ b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.h @@ -0,0 +1,48 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Katharine Chui + + 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. +*/ + +/* + All hid command sent and effect rendering are ported from https://github.com/berarma/new-lg4ff +*/ + +#ifndef SDL_hidapihaptic_h_ +#define SDL_hidapihaptic_h_ + +bool SDL_HIDAPI_HapticInit(void); +bool SDL_HIDAPI_HapticIsHidapi(SDL_Haptic *haptic); +bool SDL_HIDAPI_JoystickIsHaptic(SDL_Joystick *joystick); +bool SDL_HIDAPI_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick); +bool SDL_HIDAPI_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick); +void SDL_HIDAPI_HapticClose(SDL_Haptic *haptic); +void SDL_HIDAPI_HapticQuit(void); +SDL_HapticEffectID SDL_HIDAPI_HapticNewEffect(SDL_Haptic *haptic, const SDL_HapticEffect *base); +bool SDL_HIDAPI_HapticUpdateEffect(SDL_Haptic *haptic, SDL_HapticEffectID id, const SDL_HapticEffect *data); +bool SDL_HIDAPI_HapticRunEffect(SDL_Haptic *haptic, SDL_HapticEffectID id, Uint32 iterations); +bool SDL_HIDAPI_HapticStopEffect(SDL_Haptic *haptic, SDL_HapticEffectID id); +void SDL_HIDAPI_HapticDestroyEffect(SDL_Haptic *haptic, SDL_HapticEffectID id); +bool SDL_HIDAPI_HapticGetEffectStatus(SDL_Haptic *haptic, SDL_HapticEffectID id); +bool SDL_HIDAPI_HapticSetGain(SDL_Haptic *haptic, int gain); +bool SDL_HIDAPI_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter); +bool SDL_HIDAPI_HapticPause(SDL_Haptic *haptic); +bool SDL_HIDAPI_HapticResume(SDL_Haptic *haptic); +bool SDL_HIDAPI_HapticStopAll(SDL_Haptic *haptic); + +#endif //SDL_hidapihaptic_h_ diff --git a/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_c.h b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_c.h new file mode 100644 index 0000000..7d67881 --- /dev/null +++ b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_c.h @@ -0,0 +1,70 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Katharine Chui + + 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_hidapihaptic_c_h_ +#define SDL_hidapihaptic_c_h_ + +#include "SDL3/SDL_haptic.h" +#include "SDL3/SDL_joystick.h" +#include "../SDL_syshaptic.h" +#include "../../joystick/SDL_joystick_c.h" // accessing _SDL_JoystickDriver +#include "../../joystick/SDL_sysjoystick.h" // accessing _SDL_Joystick + +#define SDL_HAPTIC_HIDAPI_LG4FF + +typedef struct SDL_HIDAPI_HapticDriver SDL_HIDAPI_HapticDriver; +typedef struct SDL_HIDAPI_HapticDevice +{ + SDL_Haptic *haptic; /* related haptic ref */ + SDL_Joystick *joystick; /* related hidapi joystick */ + SDL_HIDAPI_HapticDriver *driver; /* driver to use */ + void *ctx; /* driver specific context */ +} SDL_HIDAPI_HapticDevice; + +struct SDL_HIDAPI_HapticDriver +{ + bool (*JoystickSupported)(SDL_Joystick *joystick); /* return true if haptic can be opened from the joystick */ + void *(*Open)(SDL_Joystick *joystick); /* returns a driver context allocated with SDL_malloc, or null if it cannot be allocated */ + + /* functions below need to handle the possibility of a null joystick instance, indicating the absence of the joystick */ + void (*Close)(SDL_HIDAPI_HapticDevice *device); /* cleanup resources allocated during Open, do NOT free driver context created in Open */ + + /* below mirror SDL_haptic.h effect interfaces */ + int (*NumEffects)(SDL_HIDAPI_HapticDevice *device); /* returns supported number of effects the device can store */ + int (*NumEffectsPlaying)(SDL_HIDAPI_HapticDevice *device); /* returns supported number of effects the device can play concurrently */ + Uint32 (*GetFeatures)(SDL_HIDAPI_HapticDevice *device); /* returns supported effects in a bitmask */ + int (*NumAxes)(SDL_HIDAPI_HapticDevice *device); /* returns the number of haptic axes */ + SDL_HapticEffectID (*CreateEffect)(SDL_HIDAPI_HapticDevice *device, const SDL_HapticEffect *data); /* returns effect id if created correctly, negative number on error */ + bool (*UpdateEffect)(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id, const SDL_HapticEffect *data); /* returns true on success, false on error */ + bool (*RunEffect)(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id, Uint32 iterations); /* returns true on success, false on error */ + bool (*StopEffect)(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id); /* returns true on success, false on error */ + void (*DestroyEffect)(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id); + bool (*GetEffectStatus)(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id); /* returns true if playing, false if not playing or on error */ + bool (*SetGain)(SDL_HIDAPI_HapticDevice *device, int gain); /* gain 0 - 100, returns true on success, false on error */ + bool (*SetAutocenter)(SDL_HIDAPI_HapticDevice *device, int autocenter); /* autocenter 0 - 100, returns true on success, false on error */ + bool (*Pause)(SDL_HIDAPI_HapticDevice *device); /* returns true on success, false on error */ + bool (*Resume)(SDL_HIDAPI_HapticDevice *device); /* returns true on success, false on error */ + bool (*StopEffects)(SDL_HIDAPI_HapticDevice *device); /* returns true on success, false on error */ +}; + +extern SDL_HIDAPI_HapticDriver SDL_HIDAPI_HapticDriverLg4ff; + +#endif //SDL_joystick_c_h_ diff --git a/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c new file mode 100644 index 0000000..7e6b3dc --- /dev/null +++ b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -0,0 +1,1264 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Simon Wood + Copyright (C) 2025 Michal Malý + Copyright (C) 2025 Bernat Arlandis + Copyright (C) 2025 Katharine Chui + + 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_JOYSTICK_HIDAPI + +#include "SDL_hidapihaptic_c.h" + +#ifdef SDL_HAPTIC_HIDAPI_LG4FF + +#include "SDL3/SDL_thread.h" +#include "SDL3/SDL_mutex.h" +#include "SDL3/SDL_timer.h" + +#include + +#define USB_VENDOR_ID_LOGITECH 0x046d +#define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f +#define USB_DEVICE_ID_LOGITECH_G27_WHEEL 0xc29b +#define USB_DEVICE_ID_LOGITECH_G25_WHEEL 0xc299 +#define USB_DEVICE_ID_LOGITECH_DFGT_WHEEL 0xc29a +#define USB_DEVICE_ID_LOGITECH_DFP_WHEEL 0xc298 +#define USB_DEVICE_ID_LOGITECH_WHEEL 0xc294 + +static Uint32 supported_device_ids[] = { + USB_DEVICE_ID_LOGITECH_G29_WHEEL, + USB_DEVICE_ID_LOGITECH_G27_WHEEL, + USB_DEVICE_ID_LOGITECH_G25_WHEEL, + USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, + USB_DEVICE_ID_LOGITECH_DFP_WHEEL, + USB_DEVICE_ID_LOGITECH_WHEEL +}; + + + +#define LG4FF_MAX_EFFECTS 16 + +#define FF_EFFECT_STARTED 0 +#define FF_EFFECT_ALLSET 1 +#define FF_EFFECT_PLAYING 2 +#define FF_EFFECT_UPDATING 3 + +struct lg4ff_effect_state { + SDL_HapticEffect effect; + Uint64 start_at; + Uint64 play_at; + Uint64 stop_at; + Uint32 flags; + Uint64 time_playing; + Uint64 updated_at; + Uint32 phase; + Uint32 phase_adj; + Uint32 count; + + double direction_gain; + Sint32 slope; + + bool allocated; +}; + +struct lg4ff_effect_parameters { + Sint32 level; + Sint32 d1; + Sint32 d2; + Sint32 k1; + Sint32 k2; + Uint32 clip; +}; + +struct lg4ff_slot { + Sint32 id; + struct lg4ff_effect_parameters parameters; + Uint8 current_cmd[7]; + Uint32 cmd_op; + bool is_updated; + Uint32 effect_type; +}; + +typedef struct lg4ff_device { + Uint16 product_id; + Uint16 release_number; + struct lg4ff_effect_state states[LG4FF_MAX_EFFECTS]; + struct lg4ff_slot slots[4]; + Sint32 effects_used; + + Sint32 gain; + Sint32 app_gain; + + Sint32 spring_level; + Sint32 damper_level; + Sint32 friction_level; + + Sint32 peak_ffb_level; + + SDL_Joystick *hid_handle; + + bool stop_thread; + SDL_Thread *thread; + char thread_name_buf[256]; + + SDL_Mutex *mutex; + + bool is_ffex; +} lg4ff_device; + +static SDL_INLINE Uint64 get_time_ms(void) { + return SDL_GetTicks(); +} + +#define test_bit(bit, field) (*(field) & (1 << bit)) +#define __set_bit(bit, field) {*(field) = *(field) | (1 << bit);} +#define __clear_bit(bit, field) {*(field) = *(field) & ~(1 << bit);} +#define sin_deg(in) (double)(SDL_sin((double)(in) * SDL_PI_D / 180.0)) + +#define time_after_eq(a, b) (a >= b) +#define time_before(a, b) (a < b) +#define time_diff(a, b) (a - b) + +#define STOP_EFFECT(state) ((state)->flags = 0) + +#define CLAMP_VALUE_U16(x) ((Uint16)((x) > 0xffff ? 0xffff : (x))) +#define SCALE_VALUE_U16(x, bits) (CLAMP_VALUE_U16(x) >> (16 - bits)) +#define CLAMP_VALUE_S16(x) ((Uint16)((x) <= -0x8000 ? -0x8000 : ((x) > 0x7fff ? 0x7fff : (x)))) +#define TRANSLATE_FORCE(x) ((CLAMP_VALUE_S16(x) + 0x8000) >> 8) +#define SCALE_COEFF(x, bits) SCALE_VALUE_U16(abs32(x) * 2, bits) + +static SDL_INLINE Sint32 abs32(Sint32 x) { + return x < 0 ? -x : x; +} +static SDL_INLINE Sint64 abs64(Sint64 x) { + return x < 0 ? -x : x; +} + +static SDL_INLINE bool effect_is_periodic(const SDL_HapticEffect *effect) +{ + + return effect->type == SDL_HAPTIC_SINE || + effect->type == SDL_HAPTIC_TRIANGLE || + effect->type == SDL_HAPTIC_SAWTOOTHUP || + effect->type == SDL_HAPTIC_SAWTOOTHDOWN || + effect->type == SDL_HAPTIC_SQUARE; +} + +static SDL_INLINE bool effect_is_condition(const SDL_HapticEffect *effect) +{ + return effect->type == SDL_HAPTIC_SPRING || + effect->type == SDL_HAPTIC_DAMPER || + effect->type == SDL_HAPTIC_FRICTION; +} + +// linux SDL_syshaptic.c SDL_SYS_ToDirection +static Uint16 to_linux_direction(SDL_HapticDirection *src) +{ + Uint32 tmp; + + switch (src->type) { + case SDL_HAPTIC_POLAR: + tmp = ((src->dir[0] % 36000) * 0x8000) / 18000; /* convert to range [0,0xFFFF] */ + return (Uint16)tmp; + + case SDL_HAPTIC_SPHERICAL: + /* + We convert to polar, because that's the only supported direction on Linux. + The first value of a spherical direction is practically the same as a + Polar direction, except that we have to add 90 degrees. It is the angle + from EAST {1,0} towards SOUTH {0,1}. + --> add 9000 + --> finally convert to [0,0xFFFF] as in case SDL_HAPTIC_POLAR. + */ + tmp = ((src->dir[0]) + 9000) % 36000; /* Convert to polars */ + tmp = (tmp * 0x8000) / 18000; /* convert to range [0,0xFFFF] */ + return (Uint16)tmp; + + case SDL_HAPTIC_CARTESIAN: + if (!src->dir[1]) { + return (Uint16) (src->dir[0] >= 0 ? 0x4000 : 0xC000); + } else if (!src->dir[0]) { + return (Uint16) (src->dir[1] >= 0 ? 0x8000 : 0); + } else { + float f = (float)SDL_atan2(src->dir[1], src->dir[0]); /* Ideally we'd use fixed point math instead of floats... */ + /* + SDL_atan2 takes the parameters: Y-axis-value and X-axis-value (in that order) + - Y-axis-value is the second coordinate (from center to SOUTH) + - X-axis-value is the first coordinate (from center to EAST) + We add 36000, because SDL_atan2 also returns negative values. Then we practically + have the first spherical value. Therefore we proceed as in case + SDL_HAPTIC_SPHERICAL and add another 9000 to get the polar value. + --> add 45000 in total + --> finally convert to [0,0xFFFF] as in case SDL_HAPTIC_POLAR. + */ + tmp = (((Sint32) (f * 18000. / SDL_PI_D)) + 45000) % 36000; + tmp = (tmp * 0x8000) / 18000; /* convert to range [0,0xFFFF] */ + return (Uint16)tmp; + } + case SDL_HAPTIC_STEERING_AXIS: + return 0x4000; + default: + SDL_assert(0); + } + + return 0; +} + +static Uint16 get_effect_direction(SDL_HapticEffect *effect) +{ + Uint16 direction = 0; + if (effect_is_periodic(effect)) { + direction = to_linux_direction(&effect->periodic.direction); + } else if (effect_is_condition(effect)) { + direction = to_linux_direction(&effect->condition.direction); + } else { + switch(effect->type) { + case SDL_HAPTIC_CONSTANT: + direction = to_linux_direction(&effect->constant.direction); + break; + case SDL_HAPTIC_RAMP: + direction = to_linux_direction(&effect->ramp.direction); + break; + default: + SDL_assert(0); + } + } + + return direction; +} + +static Uint32 get_effect_replay_length(SDL_HapticEffect *effect) +{ + Uint32 length = 0; + if (effect_is_periodic(effect)) { + length = effect->periodic.length; + } else if (effect_is_condition(effect)) { + length = effect->condition.length; + } else { + switch(effect->type) { + case SDL_HAPTIC_CONSTANT: + length = effect->constant.length; + break; + case SDL_HAPTIC_RAMP: + length = effect->ramp.length; + break; + default: + SDL_assert(0); + } + } + + if (length == SDL_HAPTIC_INFINITY) { + length = 0; + } + + return length; +} + +static Uint16 get_effect_replay_delay(SDL_HapticEffect *effect) +{ + Uint16 delay = 0; + if (effect_is_periodic(effect)) { + delay = effect->periodic.delay; + } else if (effect_is_condition(effect)) { + delay = effect->condition.delay; + } else { + switch(effect->type) { + case SDL_HAPTIC_CONSTANT: + delay = effect->constant.delay; + break; + case SDL_HAPTIC_RAMP: + delay = effect->ramp.delay; + break; + default: + SDL_assert(0); + } + } + + return delay; +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static int lg4ff_play_effect(struct lg4ff_device *device, SDL_HapticEffectID effect_id, int value) +{ + struct lg4ff_effect_state *state; + Uint64 now = get_time_ms(); + + state = &device->states[effect_id]; + + if (value > 0) { + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + STOP_EFFECT(state); + } else { + device->effects_used++; + } + __set_bit(FF_EFFECT_STARTED, &state->flags); + state->start_at = now; + state->count = value; + } else { + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + STOP_EFFECT(state); + device->effects_used--; + } + } + + return 0; +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static int lg4ff_upload_effect(struct lg4ff_device *device, const SDL_HapticEffect *effect, SDL_HapticEffectID id) +{ + struct lg4ff_effect_state *state; + Uint64 now = get_time_ms(); + + if (effect_is_periodic(effect) && effect->periodic.period == 0) { + return -1; + } + + state = &device->states[id]; + + if (test_bit(FF_EFFECT_STARTED, &state->flags) && effect->type != state->effect.type) { + return -1; + } + + state->effect = *effect; + + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + __set_bit(FF_EFFECT_UPDATING, &state->flags); + state->updated_at = now; + } + + return 0; +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static void lg4ff_update_state(struct lg4ff_effect_state *state, const Uint64 now) +{ + SDL_HapticEffect *effect = &state->effect; + Uint64 phase_time; + Uint16 effect_direction = get_effect_direction(effect); + + if (!test_bit(FF_EFFECT_ALLSET, &state->flags)) { + state->play_at = state->start_at + get_effect_replay_delay(effect); + if (!test_bit(FF_EFFECT_UPDATING, &state->flags)) { + state->updated_at = state->play_at; + } + state->direction_gain = sin_deg(effect_direction * 360 / 0x10000); + if (effect_is_periodic(effect)) { + state->phase_adj = effect->periodic.phase * 360 / effect->periodic.period; + } + if (get_effect_replay_length(effect)) { + state->stop_at = state->play_at + get_effect_replay_length(effect); + } + } + __set_bit(FF_EFFECT_ALLSET, &state->flags); + + if (test_bit(FF_EFFECT_UPDATING, &state->flags)) { + __clear_bit(FF_EFFECT_PLAYING, &state->flags); + state->play_at = state->updated_at + get_effect_replay_delay(effect); + state->direction_gain = sin_deg(effect_direction * 360 / 0x10000); + if (get_effect_replay_length(effect)) { + state->stop_at = state->updated_at + get_effect_replay_length(effect); + } + if (effect_is_periodic(effect)) { + state->phase_adj = state->phase; + } + } + __clear_bit(FF_EFFECT_UPDATING, &state->flags); + + state->slope = 0; + if (effect->type == SDL_HAPTIC_RAMP && effect->ramp.length && (effect->ramp.length - effect->ramp.attack_length - effect->ramp.fade_length) != 0) { + state->slope = ((effect->ramp.end - effect->ramp.start) << 16) / (effect->ramp.length - effect->ramp.attack_length - effect->ramp.fade_length); + } + + if (!test_bit(FF_EFFECT_PLAYING, &state->flags) && time_after_eq(now, + state->play_at) && (get_effect_replay_length(effect) == 0 || + time_before(now, state->stop_at))) { + __set_bit(FF_EFFECT_PLAYING, &state->flags); + } + + if (test_bit(FF_EFFECT_PLAYING, &state->flags)) { + state->time_playing = time_diff(now, state->play_at); + if (effect_is_periodic(effect)) { + phase_time = time_diff(now, state->updated_at); + state->phase = (phase_time % effect->periodic.period) * 360 / effect->periodic.period; + state->phase += state->phase_adj % 360; + } + } +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static Sint32 lg4ff_calculate_constant(struct lg4ff_effect_state *state) +{ + SDL_HapticConstant *constant = (SDL_HapticConstant *)&state->effect; + Sint32 level_sign; + Sint32 level = constant->level; + Sint32 d, t; + + if (state->time_playing < constant->attack_length) { + level_sign = level < 0 ? -1 : 1; + d = level - level_sign * constant->attack_level; + level = (Sint32) (level_sign * constant->attack_level + d * state->time_playing / constant->attack_length); + } else if (constant->length && constant->fade_length) { + t = (Sint32) (state->time_playing - constant->length + constant->fade_length); + if (t > 0) { + level_sign = level < 0 ? -1 : 1; + d = level - level_sign * constant->fade_level; + level = level - d * t / constant->fade_length; + } + } + + return (Sint32)(state->direction_gain * level); +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static Sint32 lg4ff_calculate_ramp(struct lg4ff_effect_state *state) +{ + SDL_HapticRamp *ramp = (SDL_HapticRamp *)&state->effect; + Sint32 level_sign; + Sint32 level; + Sint32 d, t; + + if (state->time_playing < ramp->attack_length) { + level = ramp->start; + level_sign = level < 0 ? -1 : 1; + t = (Sint32) (ramp->attack_length - state->time_playing); + d = level - level_sign * ramp->attack_level; + level = level_sign * ramp->attack_level + d * t / ramp->attack_length; + } else if (ramp->length && state->time_playing >= ramp->length - ramp->fade_length && ramp->fade_length) { + level = ramp->end; + level_sign = level < 0 ? -1 : 1; + t = (Sint32) (state->time_playing - ramp->length + ramp->fade_length); + d = level_sign * ramp->fade_level - level; + level = level - d * t / ramp->fade_length; + } else { + t = (Sint32) (state->time_playing - ramp->attack_length); + level = ramp->start + ((t * state->slope) >> 16); + } + + return (Sint32)(state->direction_gain * level); +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static Sint32 lg4ff_calculate_periodic(struct lg4ff_effect_state *state) +{ + SDL_HapticPeriodic *periodic = (SDL_HapticPeriodic *)&state->effect; + Sint32 magnitude = periodic->magnitude; + Sint32 magnitude_sign = magnitude < 0 ? -1 : 1; + Sint32 level = periodic->offset; + Sint32 d, t; + + if (state->time_playing < periodic->attack_length) { + d = magnitude - magnitude_sign * periodic->attack_level; + magnitude = (Sint32) (magnitude_sign * periodic->attack_level + d * state->time_playing / periodic->attack_length); + } else if (periodic->length && periodic->fade_length) { + t = (Sint32) (state->time_playing - get_effect_replay_length(&state->effect) + periodic->fade_length); + if (t > 0) { + d = magnitude - magnitude_sign * periodic->fade_level; + magnitude = magnitude - d * t / periodic->fade_length; + } + } + + switch (periodic->type) { + case SDL_HAPTIC_SINE: + level += (Sint32)(sin_deg(state->phase) * magnitude); + break; + case SDL_HAPTIC_SQUARE: + level += (state->phase < 180 ? 1 : -1) * magnitude; + break; + case SDL_HAPTIC_TRIANGLE: + level += (Sint32) (abs64((Sint64)state->phase * magnitude * 2 / 360 - magnitude) * 2 - magnitude); + break; + case SDL_HAPTIC_SAWTOOTHUP: + level += state->phase * magnitude * 2 / 360 - magnitude; + break; + case SDL_HAPTIC_SAWTOOTHDOWN: + level += magnitude - state->phase * magnitude * 2 / 360; + break; + default: + SDL_assert(0); + } + + return (Sint32)(state->direction_gain * level); +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static void lg4ff_calculate_spring(struct lg4ff_effect_state *state, struct lg4ff_effect_parameters *parameters) +{ + SDL_HapticCondition *condition = (SDL_HapticCondition *)&state->effect; + + parameters->d1 = ((Sint32)condition->center[0]) - condition->deadband[0] / 2; + parameters->d2 = ((Sint32)condition->center[0]) + condition->deadband[0] / 2; + parameters->k1 = condition->left_coeff[0]; + parameters->k2 = condition->right_coeff[0]; + parameters->clip = (Uint16)condition->right_sat[0]; +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static void lg4ff_calculate_resistance(struct lg4ff_effect_state *state, struct lg4ff_effect_parameters *parameters) +{ + SDL_HapticCondition *condition = (SDL_HapticCondition *)&state->effect; + + parameters->k1 = condition->left_coeff[0]; + parameters->k2 = condition->right_coeff[0]; + parameters->clip = (Uint16)condition->right_sat[0]; +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_parameters *parameters) +{ + Uint8 original_cmd[7]; + Sint32 d1; + Sint32 d2; + Sint32 k1; + Sint32 k2; + Sint32 s1; + Sint32 s2; + + SDL_memcpy(original_cmd, slot->current_cmd, sizeof(original_cmd)); + + if ((original_cmd[0] & 0xf) == 1) { + original_cmd[0] = (original_cmd[0] & 0xf0) + 0xc; + } + + if (slot->effect_type == SDL_HAPTIC_CONSTANT) { + if (slot->cmd_op == 0) { + slot->cmd_op = 1; + } else { + slot->cmd_op = 0xc; + } + } else { + if (parameters->clip == 0) { + slot->cmd_op = 3; + } else if (slot->cmd_op == 3) { + slot->cmd_op = 1; + } else { + slot->cmd_op = 0xc; + } + } + + slot->current_cmd[0] = (Uint8)((0x10 << slot->id) + slot->cmd_op); + + if (slot->cmd_op == 3) { + slot->current_cmd[1] = 0; + slot->current_cmd[2] = 0; + slot->current_cmd[3] = 0; + slot->current_cmd[4] = 0; + slot->current_cmd[5] = 0; + slot->current_cmd[6] = 0; + } else { + switch (slot->effect_type) { + case SDL_HAPTIC_CONSTANT: + slot->current_cmd[1] = 0x00; + slot->current_cmd[2] = 0; + slot->current_cmd[3] = 0; + slot->current_cmd[4] = 0; + slot->current_cmd[5] = 0; + slot->current_cmd[6] = 0; + slot->current_cmd[2 + slot->id] = TRANSLATE_FORCE(parameters->level); + break; + case SDL_HAPTIC_SPRING: + d1 = SCALE_VALUE_U16(((parameters->d1) + 0x8000) & 0xffff, 11); + d2 = SCALE_VALUE_U16(((parameters->d2) + 0x8000) & 0xffff, 11); + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + k1 = abs32(parameters->k1); + k2 = abs32(parameters->k2); + if (k1 < 2048) { + d1 = 0; + } else { + k1 -= 2048; + } + if (k2 < 2048) { + d2 = 2047; + } else { + k2 -= 2048; + } + slot->current_cmd[1] = 0x0b; + slot->current_cmd[2] = (Uint8)(d1 >> 3); + slot->current_cmd[3] = (Uint8)(d2 >> 3); + slot->current_cmd[4] = (SCALE_COEFF(k2, 4) << 4) + SCALE_COEFF(k1, 4); + slot->current_cmd[5] = (Uint8)(((d2 & 7) << 5) + ((d1 & 7) << 1) + (s2 << 4) + s1); + slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); + break; + case SDL_HAPTIC_DAMPER: + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + slot->current_cmd[1] = 0x0c; + slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 4); + slot->current_cmd[3] = (Uint8)s1; + slot->current_cmd[4] = SCALE_COEFF(parameters->k2, 4); + slot->current_cmd[5] = (Uint8)s2; + slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); + break; + case SDL_HAPTIC_FRICTION: + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + slot->current_cmd[1] = 0x0e; + slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 8); + slot->current_cmd[3] = SCALE_COEFF(parameters->k2, 8); + slot->current_cmd[4] = SCALE_VALUE_U16(parameters->clip, 8); + slot->current_cmd[5] = (Uint8)((s2 << 4) + s1); + slot->current_cmd[6] = 0; + break; + } + } + + if (SDL_memcmp(original_cmd, slot->current_cmd, sizeof(original_cmd))) { + slot->is_updated = 1; + } +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static int lg4ff_init_slots(struct lg4ff_device *device) +{ + struct lg4ff_effect_parameters parameters; + Uint8 cmd[7] = {0}; + int i; + bool ret; + + // Set/unset fixed loop mode + cmd[0] = 0x0d; + //cmd[1] = fixed_loop ? 1 : 0; + cmd[1] = 0; + ret = SDL_SendJoystickEffect(device->hid_handle, cmd, 7); + if (!ret) { + return -1; + } + + SDL_zero(device->states); + SDL_zero(device->slots); + SDL_zero(parameters); + + device->slots[0].effect_type = SDL_HAPTIC_CONSTANT; + device->slots[1].effect_type = SDL_HAPTIC_SPRING; + device->slots[2].effect_type = SDL_HAPTIC_DAMPER; + device->slots[3].effect_type = SDL_HAPTIC_FRICTION; + + for (i = 0; i < 4; i++) { + device->slots[i].id = i; + lg4ff_update_slot(&device->slots[i], ¶meters); + ret = SDL_SendJoystickEffect(device->hid_handle, cmd, 7); + if (!ret) { + return -1; + } + device->slots[i].is_updated = 0; + } + + return 0; +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static int lg4ff_timer(struct lg4ff_device *device) +{ + struct lg4ff_slot *slot; + struct lg4ff_effect_state *state; + struct lg4ff_effect_parameters parameters[4]; + Uint64 now = get_time_ms(); + Uint16 gain; + Sint32 count; + Sint32 effect_id; + int i; + Sint32 ffb_level; + int status = 0; + + // XXX how to detect stacked up effects here? + + SDL_zeroa(parameters); + + gain = (Uint16)((Uint32)device->gain * device->app_gain / 0xffff); + + count = device->effects_used; + + for (effect_id = 0; effect_id < LG4FF_MAX_EFFECTS; effect_id++) { + + if (!count) { + break; + } + + state = &device->states[effect_id]; + + if (!test_bit(FF_EFFECT_STARTED, &state->flags)) { + continue; + } + + count--; + + if (test_bit(FF_EFFECT_ALLSET, &state->flags)) { + if (get_effect_replay_length(&state->effect) && time_after_eq(now, state->stop_at)) { + STOP_EFFECT(state); + if (!--state->count) { + device->effects_used--; + continue; + } + __set_bit(FF_EFFECT_STARTED, &state->flags); + state->start_at = state->stop_at; + } + } + + lg4ff_update_state(state, now); + + if (!test_bit(FF_EFFECT_PLAYING, &state->flags)) { + continue; + } + + if (effect_is_periodic(&state->effect)) { + parameters[0].level += lg4ff_calculate_periodic(state); + } else { + switch (state->effect.type) { + case SDL_HAPTIC_CONSTANT: + parameters[0].level += lg4ff_calculate_constant(state); + break; + case SDL_HAPTIC_RAMP: + parameters[0].level += lg4ff_calculate_ramp(state); + break; + case SDL_HAPTIC_SPRING: + lg4ff_calculate_spring(state, ¶meters[1]); + break; + case SDL_HAPTIC_DAMPER: + lg4ff_calculate_resistance(state, ¶meters[2]); + break; + case SDL_HAPTIC_FRICTION: + lg4ff_calculate_resistance(state, ¶meters[3]); + break; + } + } + } + + parameters[0].level = (Sint32)((Sint64)parameters[0].level * gain / 0xffff); + parameters[1].clip = parameters[1].clip * device->spring_level / 100; + parameters[2].clip = parameters[2].clip * device->damper_level / 100; + parameters[3].clip = parameters[3].clip * device->friction_level / 100; + + ffb_level = abs32(parameters[0].level); + for (i = 1; i < 4; i++) { + parameters[i].k1 = (Sint32)((Sint64)parameters[i].k1 * gain / 0xffff); + parameters[i].k2 = (Sint32)((Sint64)parameters[i].k2 * gain / 0xffff); + parameters[i].clip = parameters[i].clip * gain / 0xffff; + ffb_level = (Sint32)(ffb_level + parameters[i].clip * 0x7fff / 0xffff); + } + if (ffb_level > device->peak_ffb_level) { + device->peak_ffb_level = ffb_level; + } + + for (i = 0; i < 4; i++) { + slot = &device->slots[i]; + lg4ff_update_slot(slot, ¶meters[i]); + if (slot->is_updated) { + bool ret = SDL_SendJoystickEffect(device->hid_handle, slot->current_cmd, 7); + if (!ret) { + status = -1; + } + slot->is_updated = 0; + } + } + + return status; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_JoystickSupported(SDL_Joystick *joystick) +{ + Uint16 vendor_id = SDL_GetJoystickVendor(joystick); + Uint16 product_id = SDL_GetJoystickProduct(joystick); + if (vendor_id != USB_VENDOR_ID_LOGITECH) { + return false; + } + for (int i = 0;i < SDL_arraysize(supported_device_ids);i++) { + if (supported_device_ids[i] == product_id) { + return true; + } + } + return false; +} + +static int SDLCALL SDL_HIDAPI_HapticDriverLg4ff_ThreadFunction(void *ctx_in) +{ + lg4ff_device *ctx = (lg4ff_device *)ctx_in; + while (true) { + if (ctx->stop_thread) { + return 0; + } + SDL_LockMutex(ctx->mutex); + lg4ff_timer(ctx); + SDL_UnlockMutex(ctx->mutex); + SDL_Delay(2); + } +} + +static int SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt(const char *env_name, int min, int max, int def) +{ + const char *env = SDL_getenv(env_name); + int value = 0; + if (env == NULL) { + return def; + } + value = SDL_atoi(env); + if (value < min) { + value = min; + } + if (value > max) { + value = max; + } + return value; +} + +/* + ffex identification method by: + Simon Wood + Michal Malý + lg4ff_init + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static void *SDL_HIDAPI_HapticDriverLg4ff_Open(SDL_Joystick *joystick) +{ + lg4ff_device *ctx; + if (!SDL_HIDAPI_HapticDriverLg4ff_JoystickSupported(joystick)) { + SDL_SetError("Device not supported by the lg4ff hidapi haptic driver"); + return NULL; + } + + ctx = SDL_malloc(sizeof(lg4ff_device)); + if (ctx == NULL) { + SDL_OutOfMemory(); + return NULL; + } + SDL_zerop(ctx); + + ctx->hid_handle = joystick; + if (lg4ff_init_slots(ctx) != 0) { + SDL_SetError("lg4ff hidapi driver failed initializing effect slots"); + SDL_free(ctx); + return NULL; + } + + ctx->mutex = SDL_CreateMutex(); + if (ctx->mutex == NULL) { + SDL_free(ctx); + return NULL; + } + + ctx->spring_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_SPRING", 0, 100, 30); + ctx->damper_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_DAMPER", 0, 100, 30); + ctx->friction_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_FRICTION", 0, 100, 30); + ctx->gain = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_GAIN", 0, 65535, 65535); + ctx->app_gain = 65535; + + ctx->product_id = SDL_GetJoystickProduct(joystick); + ctx->release_number = SDL_GetJoystickProductVersion(joystick); + + SDL_snprintf(ctx->thread_name_buf, sizeof(ctx->thread_name_buf), "SDL_hidapihaptic_lg4ff %d %04x:%04x", SDL_GetJoystickID(joystick), USB_VENDOR_ID_LOGITECH, ctx->product_id); + ctx->stop_thread = false; + ctx->thread = SDL_CreateThread(SDL_HIDAPI_HapticDriverLg4ff_ThreadFunction, ctx->thread_name_buf, ctx); + + if (ctx->product_id == USB_DEVICE_ID_LOGITECH_WHEEL && + (ctx->release_number >> 8) == 0x21 && + (ctx->release_number & 0xff) == 0x00) { + ctx->is_ffex = true; + } else { + ctx->is_ffex = false; + } + + return ctx; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_StopEffects(SDL_HIDAPI_HapticDevice *device) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + int i; + + SDL_LockMutex(ctx->mutex); + for (i = 0;i < LG4FF_MAX_EFFECTS;i++) { + struct lg4ff_effect_state *state = &ctx->states[i]; + STOP_EFFECT(state); + } + SDL_UnlockMutex(ctx->mutex); + + return true; +} + +static void SDL_HIDAPI_HapticDriverLg4ff_Close(SDL_HIDAPI_HapticDevice *device) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + + SDL_HIDAPI_HapticDriverLg4ff_StopEffects(device); + + // let effects finish in lg4ff_timer + SDL_Delay(50); + + ctx->stop_thread = true; + SDL_WaitThread(ctx->thread, NULL); + SDL_DestroyMutex(ctx->mutex); +} + +static int SDL_HIDAPI_HapticDriverLg4ff_NumEffects(SDL_HIDAPI_HapticDevice *device) +{ + return LG4FF_MAX_EFFECTS; +} + +static Uint32 SDL_HIDAPI_HapticDriverLg4ff_GetFeatures(SDL_HIDAPI_HapticDevice *device) +{ + return SDL_HAPTIC_CONSTANT | + SDL_HAPTIC_SPRING | + SDL_HAPTIC_DAMPER | + SDL_HAPTIC_AUTOCENTER | + SDL_HAPTIC_SINE | + SDL_HAPTIC_SQUARE | + SDL_HAPTIC_TRIANGLE | + SDL_HAPTIC_SAWTOOTHUP | + SDL_HAPTIC_SAWTOOTHDOWN | + SDL_HAPTIC_RAMP | + SDL_HAPTIC_FRICTION | + SDL_HAPTIC_STATUS | + SDL_HAPTIC_GAIN; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_EffectSupported(SDL_HIDAPI_HapticDevice *device, const SDL_HapticEffect *effect) { + Uint32 features = SDL_HIDAPI_HapticDriverLg4ff_GetFeatures(device); + return (features & effect->type)? true : false; +} + +static int SDL_HIDAPI_HapticDriverLg4ff_NumAxes(SDL_HIDAPI_HapticDevice *device) +{ + return 1; +} + +static SDL_HapticEffectID SDL_HIDAPI_HapticDriverLg4ff_CreateEffect(SDL_HIDAPI_HapticDevice *device, const SDL_HapticEffect *data) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + SDL_HapticEffectID i; + SDL_HapticEffectID state_slot = -1; + int ret; + if (!SDL_HIDAPI_HapticDriverLg4ff_EffectSupported(device, data)) { + SDL_SetError("Unsupported effect"); + return -1; + } + + SDL_LockMutex(ctx->mutex); + for (i = 0;i < LG4FF_MAX_EFFECTS;i++) { + if (!ctx->states[i].allocated) { + state_slot = i; + break; + } + } + if (state_slot == -1) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("All effect slots in-use"); + return -1; + } + + ret = lg4ff_upload_effect(ctx, data, state_slot); + SDL_UnlockMutex(ctx->mutex); + if (ret == 0) { + ctx->states[state_slot].allocated = true; + return state_slot; + } else { + SDL_SetError("Bad effect parameters"); + return -1; + } +} + +// assumes ctx->mutex locked +static bool lg4ff_effect_slot_valid_active(lg4ff_device *ctx, SDL_HapticEffectID id) +{ + if (id >= LG4FF_MAX_EFFECTS || id < 0) { + return false; + } + if (!ctx->states[id].allocated) { + return false; + } + return true; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_UpdateEffect(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id, const SDL_HapticEffect *data) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + int ret; + + SDL_LockMutex(ctx->mutex); + if (!lg4ff_effect_slot_valid_active(ctx, id)) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Bad effect id"); + return false; + } + + ret = lg4ff_upload_effect(ctx, data, id); + SDL_UnlockMutex(ctx->mutex); + + return ret == 0; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_RunEffect(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id, Uint32 iterations) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + int ret; + + SDL_LockMutex(ctx->mutex); + if (!lg4ff_effect_slot_valid_active(ctx, id)) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Bad effect id"); + return false; + } + + ret = lg4ff_play_effect(ctx, id, iterations); + SDL_UnlockMutex(ctx->mutex); + + return ret == 0; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_StopEffect(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id) +{ + return SDL_HIDAPI_HapticDriverLg4ff_RunEffect(device, id, 0); +} + +static void SDL_HIDAPI_HapticDriverLg4ff_DestroyEffect(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + struct lg4ff_effect_state *state; + + SDL_LockMutex(ctx->mutex); + if (!lg4ff_effect_slot_valid_active(ctx, id)) { + SDL_UnlockMutex(ctx->mutex); + return; + } + + state = &ctx->states[id]; + STOP_EFFECT(state); + state->allocated = false; + + SDL_UnlockMutex(ctx->mutex); +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_GetEffectStatus(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + bool ret = false; + + SDL_LockMutex(ctx->mutex); + if (!lg4ff_effect_slot_valid_active(ctx, id)) { + SDL_UnlockMutex(ctx->mutex); + return false; + } + + if (test_bit(FF_EFFECT_STARTED, &ctx->states[id].flags)) { + ret = true; + } + SDL_UnlockMutex(ctx->mutex); + + return ret; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_SetGain(SDL_HIDAPI_HapticDevice *device, int gain) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + if (gain > 100) { + gain = 100; + } + if (gain < 0) { + gain = 0; + } + ctx->app_gain = (65535 * gain) / 100; + return true; +} + +/* + *Ported* + Original functions by: + Simon Wood + Michal Malý + lg4ff_set_autocenter_default lg4ff_set_autocenter_ffex + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static bool SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter(SDL_HIDAPI_HapticDevice *device, int autocenter) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + Uint8 cmd[7] = {0}; + bool ret; + + if (autocenter < 0) { + autocenter = 0; + } + if (autocenter > 100) { + autocenter = 100; + } + + SDL_LockMutex(ctx->mutex); + if (ctx->is_ffex) { + int magnitude = (90 * autocenter) / 100; + + cmd[0] = 0xfe; + cmd[1] = 0x03; + cmd[2] = (Uint8)((Uint16)magnitude >> 14); + cmd[3] = (Uint8)((Uint16)magnitude >> 14); + cmd[4] = (Uint8)magnitude; + + ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); + if (!ret) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Failed sending autocenter command"); + return false; + } + } else { + Uint32 expand_a; + Uint32 expand_b; + int magnitude = (65535 * autocenter) / 100; + + // first disable + cmd[0] = 0xf5; + + ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); + if (!ret) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Failed sending autocenter disable command"); + return false; + } + + if (magnitude == 0) { + SDL_UnlockMutex(ctx->mutex); + return true; + } + + // set strength + if (magnitude <= 0xaaaa) { + expand_a = 0x0c * magnitude; + expand_b = 0x80 * magnitude; + } else { + expand_a = (0x0c * 0xaaaa) + 0x06 * (magnitude - 0xaaaa); + expand_b = (0x80 * 0xaaaa) + 0xff * (magnitude - 0xaaaa); + } + expand_a = expand_a >> 1; + + SDL_zeroa(cmd); + cmd[0] = 0xfe; + cmd[1] = 0x0d; + cmd[2] = (Uint8)(expand_a / 0xaaaa); + cmd[3] = (Uint8)(expand_a / 0xaaaa); + cmd[4] = (Uint8)(expand_b / 0xaaaa); + + ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); + if (!ret) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Failed sending autocenter magnitude command"); + return false; + } + + // enable + SDL_zeroa(cmd); + cmd[0] = 0x14; + + ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); + if (!ret) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Failed sending autocenter enable command"); + return false; + } + } + SDL_UnlockMutex(ctx->mutex); + return true; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_Pause(SDL_HIDAPI_HapticDevice *device) +{ + return SDL_Unsupported(); +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_Resume(SDL_HIDAPI_HapticDevice *device) +{ + return SDL_Unsupported(); +} + +SDL_HIDAPI_HapticDriver SDL_HIDAPI_HapticDriverLg4ff = { + SDL_HIDAPI_HapticDriverLg4ff_JoystickSupported, + SDL_HIDAPI_HapticDriverLg4ff_Open, + SDL_HIDAPI_HapticDriverLg4ff_Close, + SDL_HIDAPI_HapticDriverLg4ff_NumEffects, + SDL_HIDAPI_HapticDriverLg4ff_NumEffects, + SDL_HIDAPI_HapticDriverLg4ff_GetFeatures, + SDL_HIDAPI_HapticDriverLg4ff_NumAxes, + SDL_HIDAPI_HapticDriverLg4ff_CreateEffect, + SDL_HIDAPI_HapticDriverLg4ff_UpdateEffect, + SDL_HIDAPI_HapticDriverLg4ff_RunEffect, + SDL_HIDAPI_HapticDriverLg4ff_StopEffect, + SDL_HIDAPI_HapticDriverLg4ff_DestroyEffect, + SDL_HIDAPI_HapticDriverLg4ff_GetEffectStatus, + SDL_HIDAPI_HapticDriverLg4ff_SetGain, + SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter, + SDL_HIDAPI_HapticDriverLg4ff_Pause, + SDL_HIDAPI_HapticDriverLg4ff_Resume, + SDL_HIDAPI_HapticDriverLg4ff_StopEffects, +}; + +#endif //SDL_HAPTIC_HIDAPI_LG4FF +#endif //SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/haptic/linux/SDL_syshaptic.c b/libs/SDL3/src/haptic/linux/SDL_syshaptic.c index 842f577..e107562 100644 --- a/libs/SDL3/src/haptic/linux/SDL_syshaptic.c +++ b/libs/SDL3/src/haptic/linux/SDL_syshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -603,7 +603,7 @@ void SDL_SYS_HapticClose(SDL_Haptic *haptic) } // Clear the rest. - SDL_memset(haptic, 0, sizeof(SDL_Haptic)); + SDL_zerop(haptic); } /* @@ -725,7 +725,7 @@ static bool SDL_SYS_ToFFEffect(struct ff_effect *dest, const SDL_HapticEffect *s const SDL_HapticLeftRight *leftright; // Clear up - SDL_memset(dest, 0, sizeof(struct ff_effect)); + SDL_zerop(dest); switch (src->type) { case SDL_HAPTIC_CONSTANT: @@ -1117,13 +1117,12 @@ bool SDL_SYS_HapticResume(SDL_Haptic *haptic) */ bool SDL_SYS_HapticStopAll(SDL_Haptic *haptic) { - int i, ret; + int i; // Linux does not support this natively so we have to loop. for (i = 0; i < haptic->neffects; i++) { if (haptic->effects[i].hweffect != NULL) { - ret = SDL_SYS_HapticStopEffect(haptic, &haptic->effects[i]); - if (ret < 0) { + if (!SDL_SYS_HapticStopEffect(haptic, &haptic->effects[i])) { return SDL_SetError("Haptic: Error while trying to stop all playing effects."); } } diff --git a/libs/SDL3/src/haptic/windows/SDL_dinputhaptic.c b/libs/SDL3/src/haptic/windows/SDL_dinputhaptic.c index 79c4b35..fa374e4 100644 --- a/libs/SDL3/src/haptic/windows/SDL_dinputhaptic.c +++ b/libs/SDL3/src/haptic/windows/SDL_dinputhaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -599,7 +599,7 @@ static bool SDL_SYS_ToDIEFFECT(SDL_Haptic *haptic, DIEFFECT *dest, DWORD *axes; // Set global stuff. - SDL_memset(dest, 0, sizeof(DIEFFECT)); + SDL_zerop(dest); dest->dwSize = sizeof(DIEFFECT); // Set the structure size. dest->dwSamplePeriod = 0; // Not used by us. dest->dwGain = 10000; // Gain is set globally, not locally. @@ -946,6 +946,103 @@ err_effectdone: return false; } +BOOL DIGetDirectionUpdateFlag(DIEFFECT *before, DIEFFECT *after) +{ + if (before->cAxes != after->cAxes) { + return true; + } + // rglDirection must be non-null for DIEP_DIRECTION to be a valid flag + if (after->rglDirection == NULL) { + return false; + } + if (before->rglDirection == NULL) { + return true; + } + return SDL_memcmp(before->rglDirection, after->rglDirection, sizeof(LONG) * before->cAxes) != 0; +} + +BOOL DIGetEnvelopeUpdateFlag(DIEFFECT* before, DIEFFECT* after) +{ + if (before->lpEnvelope == NULL && after->lpEnvelope == NULL) { + return false; + } + // A null lpEnvelope is valid for DIEP_ENVELOPE (clears the envelope from the effect) + if (before->lpEnvelope == NULL || after->lpEnvelope == NULL) { + return true; + } + return SDL_memcmp(before->lpEnvelope, after->lpEnvelope, sizeof(DIENVELOPE)) != 0; +} + +BOOL DIGetTypeSpecificParamsUpdateFlag(DIEFFECT *before, DIEFFECT *after) +{ + // Shouldn't happen since this implies an effect's type somehow changed, but need to check to avoid an out-of-bounds memcmp + if (before->cbTypeSpecificParams != after->cbTypeSpecificParams) { + return true; + } + // lpvTypeSpecificParams must be non-null for the DIEP_TYPESPECIFICPARAMS flag. + if (after->lpvTypeSpecificParams == NULL) { + return false; + } + if (before->lpvTypeSpecificParams == NULL) { + return true; + } + return SDL_memcmp(before->lpvTypeSpecificParams, after->lpvTypeSpecificParams, before->cbTypeSpecificParams) != 0; +} + +/* + Calculate the exact flags needed when updating an existing DirectInput haptic effect. +*/ +DWORD DICalculateUpdateFlags(DIEFFECT *before, DIEFFECT *after) +{ + DWORD flags = 0; + + if (DIGetDirectionUpdateFlag(before, after)) { + flags |= DIEP_DIRECTION; + } + + if (before->dwDuration != after->dwDuration) { + flags |= DIEP_DURATION; + } + + if (DIGetEnvelopeUpdateFlag(before, after)) { + flags |= DIEP_ENVELOPE; + } + + if (before->dwStartDelay != after->dwStartDelay) { + flags |= DIEP_STARTDELAY; + } + + if (before->dwTriggerButton != after->dwTriggerButton) { + flags |= DIEP_TRIGGERBUTTON; + } + + if (before->dwTriggerRepeatInterval != after->dwTriggerRepeatInterval) { + flags |= DIEP_TRIGGERREPEATINTERVAL; + } + + if (DIGetTypeSpecificParamsUpdateFlag(before, after)) { + flags |= DIEP_TYPESPECIFICPARAMS; + } + + if (flags == 0) { + /* Awkward: SDL_UpdateHapticEffect was called, but nothing was changed. + * Calling IDirectInputEffect_SetParameters with no flags is nonsense, + * so our options are to either send all the flags, or exit early. + * Sending all the flags seems like the safer option: The programmer may be trying + * to force an update for some reason (e.g. driver bug workaround?). Conversely, + * if the programmer doesn't want IDirectInputEffect_SetParameters to be called, they + * can just avoid calling SDL_UpdateHapticEffect when there's no changes. */ + flags = DIEP_DIRECTION | + DIEP_DURATION | + DIEP_ENVELOPE | + DIEP_STARTDELAY | + DIEP_TRIGGERBUTTON | + DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS; + } + + return flags; +} + bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data) { HRESULT ret; @@ -953,19 +1050,12 @@ bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *eff DIEFFECT temp; // Get the effect. - SDL_memset(&temp, 0, sizeof(DIEFFECT)); + SDL_zero(temp); if (!SDL_SYS_ToDIEFFECT(haptic, &temp, data)) { goto err_update; } - /* Set the flags. Might be worthwhile to diff temp with loaded effect and - * only change those parameters. */ - flags = DIEP_DIRECTION | - DIEP_DURATION | - DIEP_ENVELOPE | - DIEP_STARTDELAY | - DIEP_TRIGGERBUTTON | - DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS; + flags = DICalculateUpdateFlags(&effect->hweffect->effect, &temp); // Create the actual effect. ret = diff --git a/libs/SDL3/src/haptic/windows/SDL_dinputhaptic_c.h b/libs/SDL3/src/haptic/windows/SDL_dinputhaptic_c.h index d6265c9..a06c3e4 100644 --- a/libs/SDL3/src/haptic/windows/SDL_dinputhaptic_c.h +++ b/libs/SDL3/src/haptic/windows/SDL_dinputhaptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/haptic/windows/SDL_windowshaptic.c b/libs/SDL3/src/haptic/windows/SDL_windowshaptic.c index e21ab91..e58faad 100644 --- a/libs/SDL3/src/haptic/windows/SDL_windowshaptic.c +++ b/libs/SDL3/src/haptic/windows/SDL_windowshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/haptic/windows/SDL_windowshaptic_c.h b/libs/SDL3/src/haptic/windows/SDL_windowshaptic_c.h index c4b6928..202cb87 100644 --- a/libs/SDL3/src/haptic/windows/SDL_windowshaptic_c.h +++ b/libs/SDL3/src/haptic/windows/SDL_windowshaptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/hidapi/SDL_hidapi.c b/libs/SDL3/src/hidapi/SDL_hidapi.c index 2378db6..0dda4d4 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi.c +++ b/libs/SDL3/src/hidapi/SDL_hidapi.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -32,6 +32,7 @@ #include "SDL_hidapi_c.h" #include "../joystick/usb_ids.h" +#include "../joystick/SDL_joystick_c.h" #include "../SDL_hints_c.h" // Initial type declarations @@ -40,6 +41,15 @@ #ifndef SDL_HIDAPI_DISABLED +#ifdef SDL_LIBUSB_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "hidabi-libusb", + "Support for joysticks through libusb", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_LIBUSB_DYNAMIC +) +#endif + #if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) #include "../core/windows/SDL_windows.h" #endif @@ -378,18 +388,20 @@ static void HIDAPI_UpdateDiscovery(void) } #if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) -#if 0 // just let the usual SDL_PumpEvents loop dispatch these, fixing bug 4286. --ryan. - // We'll only get messages on the same thread that created the window - if (SDL_GetCurrentThreadID() == SDL_HIDAPI_discovery.m_nThreadID) { - MSG msg; - while (PeekMessage(&msg, SDL_HIDAPI_discovery.m_hwndMsg, 0, 0, PM_NOREMOVE)) { - if (GetMessageA(&msg, SDL_HIDAPI_discovery.m_hwndMsg, 0, 0) != 0) { - TranslateMessage(&msg); - DispatchMessage(&msg); + if (SDL_IsVideoThread()) { + // just let the usual SDL_PumpEvents loop dispatch these, fixing bug 2998. --ryan. + } else { + // We'll only get messages on the same thread that created the window + if (SDL_GetCurrentThreadID() == SDL_HIDAPI_discovery.m_nThreadID) { + MSG msg; + while (PeekMessage(&msg, SDL_HIDAPI_discovery.m_hwndMsg, 0, 0, PM_NOREMOVE)) { + if (GetMessageA(&msg, SDL_HIDAPI_discovery.m_hwndMsg, 0, 0) != 0) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } } } } -#endif #endif // defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) #ifdef SDL_PLATFORM_MACOS @@ -531,8 +543,8 @@ static void HIDAPI_ShutdownDiscovery(void) // Platform HIDAPI Implementation #define HIDAPI_USING_SDL_RUNTIME -#define HIDAPI_IGNORE_DEVICE(BUS, VID, PID, USAGE_PAGE, USAGE) \ - SDL_HIDAPI_ShouldIgnoreDevice(BUS, VID, PID, USAGE_PAGE, USAGE) +#define HIDAPI_IGNORE_DEVICE(BUS, VID, PID, USAGE_PAGE, USAGE, LIBUSB) \ + SDL_HIDAPI_ShouldIgnoreDevice(BUS, VID, PID, USAGE_PAGE, USAGE, LIBUSB) struct PLATFORM_hid_device_; typedef struct PLATFORM_hid_device_ PLATFORM_hid_device; @@ -689,131 +701,77 @@ typedef struct DRIVER_hid_device_ DRIVER_hid_device; #ifdef HAVE_LIBUSB // libusb HIDAPI Implementation -// Include this now, for our dynamically-loaded libusb context -#include +#include "../misc/SDL_libusb.h" -static struct -{ - SDL_SharedObject *libhandle; +static SDL_LibUSBContext *libusb_ctx; - /* *INDENT-OFF* */ // clang-format off - int (LIBUSB_CALL *init)(libusb_context **ctx); - void (LIBUSB_CALL *exit)(libusb_context *ctx); - ssize_t (LIBUSB_CALL *get_device_list)(libusb_context *ctx, libusb_device ***list); - void (LIBUSB_CALL *free_device_list)(libusb_device **list, int unref_devices); - int (LIBUSB_CALL *get_device_descriptor)(libusb_device *dev, struct libusb_device_descriptor *desc); - int (LIBUSB_CALL *get_active_config_descriptor)(libusb_device *dev, struct libusb_config_descriptor **config); - int (LIBUSB_CALL *get_config_descriptor)( - libusb_device *dev, - uint8_t config_index, - struct libusb_config_descriptor **config - ); - void (LIBUSB_CALL *free_config_descriptor)(struct libusb_config_descriptor *config); - uint8_t (LIBUSB_CALL *get_bus_number)(libusb_device *dev); - int (LIBUSB_CALL *get_port_numbers)(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len); - uint8_t (LIBUSB_CALL *get_device_address)(libusb_device *dev); - int (LIBUSB_CALL *open)(libusb_device *dev, libusb_device_handle **dev_handle); - void (LIBUSB_CALL *close)(libusb_device_handle *dev_handle); - libusb_device *(LIBUSB_CALL *get_device)(libusb_device_handle *dev_handle); - int (LIBUSB_CALL *claim_interface)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *release_interface)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *kernel_driver_active)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *detach_kernel_driver)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *attach_kernel_driver)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *set_interface_alt_setting)(libusb_device_handle *dev, int interface_number, int alternate_setting); - struct libusb_transfer * (LIBUSB_CALL *alloc_transfer)(int iso_packets); - int (LIBUSB_CALL *submit_transfer)(struct libusb_transfer *transfer); - int (LIBUSB_CALL *cancel_transfer)(struct libusb_transfer *transfer); - void (LIBUSB_CALL *free_transfer)(struct libusb_transfer *transfer); - int (LIBUSB_CALL *control_transfer)( - libusb_device_handle *dev_handle, - uint8_t request_type, - uint8_t bRequest, - uint16_t wValue, - uint16_t wIndex, - unsigned char *data, - uint16_t wLength, - unsigned int timeout - ); - int (LIBUSB_CALL *interrupt_transfer)( - libusb_device_handle *dev_handle, - unsigned char endpoint, - unsigned char *data, - int length, - int *actual_length, - unsigned int timeout - ); - int (LIBUSB_CALL *handle_events)(libusb_context *ctx); - int (LIBUSB_CALL *handle_events_completed)(libusb_context *ctx, int *completed); - const char * (LIBUSB_CALL *error_name)(int errcode); -/* *INDENT-ON* */ // clang-format on - -} libusb_ctx; - -#define libusb_init libusb_ctx.init -#define libusb_exit libusb_ctx.exit -#define libusb_get_device_list libusb_ctx.get_device_list -#define libusb_free_device_list libusb_ctx.free_device_list -#define libusb_get_device_descriptor libusb_ctx.get_device_descriptor -#define libusb_get_active_config_descriptor libusb_ctx.get_active_config_descriptor -#define libusb_get_config_descriptor libusb_ctx.get_config_descriptor -#define libusb_free_config_descriptor libusb_ctx.free_config_descriptor -#define libusb_get_bus_number libusb_ctx.get_bus_number -#define libusb_get_port_numbers libusb_ctx.get_port_numbers -#define libusb_get_device_address libusb_ctx.get_device_address -#define libusb_open libusb_ctx.open -#define libusb_close libusb_ctx.close -#define libusb_get_device libusb_ctx.get_device -#define libusb_claim_interface libusb_ctx.claim_interface -#define libusb_release_interface libusb_ctx.release_interface -#define libusb_kernel_driver_active libusb_ctx.kernel_driver_active -#define libusb_detach_kernel_driver libusb_ctx.detach_kernel_driver -#define libusb_attach_kernel_driver libusb_ctx.attach_kernel_driver -#define libusb_set_interface_alt_setting libusb_ctx.set_interface_alt_setting -#define libusb_alloc_transfer libusb_ctx.alloc_transfer -#define libusb_submit_transfer libusb_ctx.submit_transfer -#define libusb_cancel_transfer libusb_ctx.cancel_transfer -#define libusb_free_transfer libusb_ctx.free_transfer -#define libusb_control_transfer libusb_ctx.control_transfer -#define libusb_interrupt_transfer libusb_ctx.interrupt_transfer -#define libusb_handle_events libusb_ctx.handle_events -#define libusb_handle_events_completed libusb_ctx.handle_events_completed -#define libusb_error_name libusb_ctx.error_name +#define libusb_init libusb_ctx->init +#define libusb_exit libusb_ctx->exit +#define libusb_get_device_list libusb_ctx->get_device_list +#define libusb_free_device_list libusb_ctx->free_device_list +#define libusb_get_device_descriptor libusb_ctx->get_device_descriptor +#define libusb_get_active_config_descriptor libusb_ctx->get_active_config_descriptor +#define libusb_get_config_descriptor libusb_ctx->get_config_descriptor +#define libusb_free_config_descriptor libusb_ctx->free_config_descriptor +#define libusb_get_bus_number libusb_ctx->get_bus_number +#define libusb_get_port_numbers libusb_ctx->get_port_numbers +#define libusb_get_device_address libusb_ctx->get_device_address +#define libusb_open libusb_ctx->open +#define libusb_close libusb_ctx->close +#define libusb_get_device libusb_ctx->get_device +#define libusb_claim_interface libusb_ctx->claim_interface +#define libusb_release_interface libusb_ctx->release_interface +#define libusb_kernel_driver_active libusb_ctx->kernel_driver_active +#define libusb_detach_kernel_driver libusb_ctx->detach_kernel_driver +#define libusb_attach_kernel_driver libusb_ctx->attach_kernel_driver +#define libusb_set_interface_alt_setting libusb_ctx->set_interface_alt_setting +#define libusb_alloc_transfer libusb_ctx->alloc_transfer +#define libusb_submit_transfer libusb_ctx->submit_transfer +#define libusb_cancel_transfer libusb_ctx->cancel_transfer +#define libusb_free_transfer libusb_ctx->free_transfer +#define libusb_control_transfer libusb_ctx->control_transfer +#define libusb_interrupt_transfer libusb_ctx->interrupt_transfer +#define libusb_bulk_transfer libusb_ctx->bulk_transfer +#define libusb_handle_events libusb_ctx->handle_events +#define libusb_handle_events_completed libusb_ctx->handle_events_completed +#define libusb_error_name libusb_ctx->error_name struct LIBUSB_hid_device_; typedef struct LIBUSB_hid_device_ LIBUSB_hid_device; -#define free_hid_device LIBUSB_free_hid_device -#define hid_close LIBUSB_hid_close -#define hid_device LIBUSB_hid_device -#define hid_device_ LIBUSB_hid_device_ -#define hid_enumerate LIBUSB_hid_enumerate -#define hid_error LIBUSB_hid_error -#define hid_exit LIBUSB_hid_exit -#define hid_free_enumeration LIBUSB_hid_free_enumeration -#define hid_get_device_info LIBUSB_hid_get_device_info -#define hid_get_feature_report LIBUSB_hid_get_feature_report -#define hid_get_indexed_string LIBUSB_hid_get_indexed_string -#define hid_get_input_report LIBUSB_hid_get_input_report -#define hid_get_manufacturer_string LIBUSB_hid_get_manufacturer_string -#define hid_get_product_string LIBUSB_hid_get_product_string -#define hid_get_report_descriptor LIBUSB_hid_get_report_descriptor -#define hid_get_serial_number_string LIBUSB_hid_get_serial_number_string -#define hid_init LIBUSB_hid_init -#define hid_open LIBUSB_hid_open -#define hid_open_path LIBUSB_hid_open_path -#define hid_read LIBUSB_hid_read -#define hid_read_timeout LIBUSB_hid_read_timeout -#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 -#define read_thread LIBUSB_read_thread -#define return_data LIBUSB_return_data +#define free_hid_device LIBUSB_free_hid_device +#define get_usb_code_for_current_locale LIBUSB_get_usb_code_for_current_locale +#define hid_close LIBUSB_hid_close +#define hid_device LIBUSB_hid_device +#define hid_device_ LIBUSB_hid_device_ +#define hid_enumerate LIBUSB_hid_enumerate +#define hid_error LIBUSB_hid_error +#define hid_exit LIBUSB_hid_exit +#define hid_free_enumeration LIBUSB_hid_free_enumeration +#define hid_get_device_info LIBUSB_hid_get_device_info +#define hid_get_feature_report LIBUSB_hid_get_feature_report +#define hid_get_indexed_string LIBUSB_hid_get_indexed_string +#define hid_get_input_report LIBUSB_hid_get_input_report +#define hid_get_manufacturer_string LIBUSB_hid_get_manufacturer_string +#define hid_get_product_string LIBUSB_hid_get_product_string +#define hid_get_report_descriptor LIBUSB_hid_get_report_descriptor +#define hid_get_serial_number_string LIBUSB_hid_get_serial_number_string +#define hid_init LIBUSB_hid_init +#define hid_open LIBUSB_hid_open +#define hid_open_path LIBUSB_hid_open_path +#define hid_read LIBUSB_hid_read +#define hid_read_timeout LIBUSB_hid_read_timeout +#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_libusb_wrap_sys_device LIBUSB_hid_libusb_wrap_sys_device +#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 +#define read_thread LIBUSB_read_thread +#define return_data LIBUSB_return_data #include "SDL_hidapi_libusb.h" @@ -843,6 +801,7 @@ typedef struct LIBUSB_hid_device_ LIBUSB_hid_device; #undef libusb_free_transfer #undef libusb_control_transfer #undef libusb_interrupt_transfer +#undef libusb_bulk_transfer #undef libusb_handle_events #undef libusb_handle_events_completed #undef libusb_error_name @@ -877,6 +836,10 @@ typedef struct LIBUSB_hid_device_ LIBUSB_hid_device; #undef read_thread #undef return_data +#endif // HAVE_LIBUSB + +#endif // !SDL_HIDAPI_DISABLED + /* If the platform has any backend other than libusb, try to avoid using * libusb as the main backend for devices, since it detaches drivers and * therefore makes devices inaccessible to the rest of the OS. @@ -888,26 +851,25 @@ typedef struct LIBUSB_hid_device_ LIBUSB_hid_device; static const struct { Uint16 vendor; Uint16 product; -} SDL_libusb_whitelist[] = { - { 0x057e, 0x0337 } // Nintendo WUP-028, Wii U/Switch GameCube Adapter +} SDL_libusb_required[] = { + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER }, + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER }, + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT }, + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT }, + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_PRO }, }; -static bool IsInWhitelist(Uint16 vendor, Uint16 product) +static bool RequiresLibUSB(Uint16 vendor, Uint16 product) { - int i; - for (i = 0; i < SDL_arraysize(SDL_libusb_whitelist); i += 1) { - if (vendor == SDL_libusb_whitelist[i].vendor && - product == SDL_libusb_whitelist[i].product) { + for (int i = 0; i < SDL_arraysize(SDL_libusb_required); ++i) { + if (vendor == SDL_libusb_required[i].vendor && + product == SDL_libusb_required[i].product) { return true; } } return false; } -#endif // HAVE_LIBUSB - -#endif // !SDL_HIDAPI_DISABLED - #if defined(HAVE_PLATFORM_BACKEND) || defined(HAVE_DRIVER_BACKEND) // We have another way to get HID devices, so use the whitelist to get devices where libusb is preferred #define SDL_HINT_HIDAPI_LIBUSB_WHITELIST_DEFAULT true @@ -917,6 +879,7 @@ static bool IsInWhitelist(Uint16 vendor, Uint16 product) #endif // HAVE_PLATFORM_BACKEND || HAVE_DRIVER_BACKEND static bool use_libusb_whitelist = SDL_HINT_HIDAPI_LIBUSB_WHITELIST_DEFAULT; +static bool use_libusb_gamecube = true; // Shared HIDAPI Implementation @@ -1004,13 +967,14 @@ struct SDL_hid_device void *device; const struct hidapi_backend *backend; SDL_hid_device_info info; + SDL_PropertiesID props; }; #if defined(HAVE_PLATFORM_BACKEND) || defined(HAVE_DRIVER_BACKEND) || defined(HAVE_LIBUSB) static SDL_hid_device *CreateHIDDeviceWrapper(void *device, const struct hidapi_backend *backend) { - SDL_hid_device *wrapper = (SDL_hid_device *)SDL_malloc(sizeof(*wrapper)); + SDL_hid_device *wrapper = (SDL_hid_device *)SDL_calloc(1, sizeof(*wrapper)); SDL_SetObjectValid(wrapper, SDL_OBJECT_TYPE_HIDAPI_DEVICE, true); wrapper->device = device; wrapper->backend = backend; @@ -1082,9 +1046,7 @@ static void SDLCALL OnlyControllersChanged(void *userdata, const char *name, con static void SDLCALL IgnoredDevicesChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { - if (SDL_hidapi_ignored_devices) { - SDL_free(SDL_hidapi_ignored_devices); - } + SDL_free(SDL_hidapi_ignored_devices); if (hint && *hint) { SDL_hidapi_ignored_devices = SDL_strdup(hint); } else { @@ -1092,8 +1054,22 @@ static void SDLCALL IgnoredDevicesChanged(void *userdata, const char *name, cons } } -bool SDL_HIDAPI_ShouldIgnoreDevice(int bus, Uint16 vendor_id, Uint16 product_id, Uint16 usage_page, Uint16 usage) +bool SDL_HIDAPI_ShouldIgnoreDevice(int bus, Uint16 vendor_id, Uint16 product_id, Uint16 usage_page, Uint16 usage, bool libusb) { + if (libusb) { + if (use_libusb_whitelist && !RequiresLibUSB(vendor_id, product_id)) { + return true; + } + if (!use_libusb_gamecube && + vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) { + return true; + } + } else { + if (RequiresLibUSB(vendor_id, product_id)) { + return true; + } + } + // See if there are any devices we should skip in enumeration if (SDL_hidapi_only_controllers && usage_page) { if (vendor_id == USB_VENDOR_VALVE) { @@ -1109,6 +1085,17 @@ bool SDL_HIDAPI_ShouldIgnoreDevice(int bus, Uint16 vendor_id, Uint16 product_id, (usage == USB_USAGE_GENERIC_KEYBOARD || usage == USB_USAGE_GENERIC_MOUSE)) { return true; } + } else if (vendor_id == USB_VENDOR_FLYDIGI_V1 && product_id == USB_PRODUCT_FLYDIGI_V1_GAMEPAD) { + if (usage_page == USB_USAGEPAGE_VENDOR_FLYDIGI) { + return false; + } + return true; + } else if (vendor_id == USB_VENDOR_FLYDIGI_V2 && + (product_id == USB_PRODUCT_FLYDIGI_V2_APEX || product_id == USB_PRODUCT_FLYDIGI_V2_VADER)) { + if (usage_page == USB_USAGEPAGE_VENDOR_FLYDIGI) { + return false; + } + return true; } else if (usage_page == USB_USAGEPAGE_GENERIC_DESKTOP && (usage == USB_USAGE_GENERIC_JOYSTICK || usage == USB_USAGE_GENERIC_GAMEPAD || usage == USB_USAGE_GENERIC_MULTIAXISCONTROLLER)) { // This is a controller @@ -1158,74 +1145,20 @@ int SDL_hid_init(void) use_libusb_whitelist = SDL_GetHintBoolean(SDL_HINT_HIDAPI_LIBUSB_WHITELIST, SDL_HINT_HIDAPI_LIBUSB_WHITELIST_DEFAULT); + use_libusb_gamecube = SDL_GetHintBoolean(SDL_HINT_HIDAPI_LIBUSB_GAMECUBE, true); #ifdef HAVE_LIBUSB if (!SDL_GetHintBoolean(SDL_HINT_HIDAPI_LIBUSB, true)) { SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "libusb disabled with SDL_HINT_HIDAPI_LIBUSB"); - libusb_ctx.libhandle = NULL; } else { ++attempts; -#ifdef SDL_LIBUSB_DYNAMIC - libusb_ctx.libhandle = SDL_LoadObject(SDL_LIBUSB_DYNAMIC); -#else - libusb_ctx.libhandle = (void *)1; -#endif - if (libusb_ctx.libhandle != NULL) { - bool loaded = true; -#ifdef SDL_LIBUSB_DYNAMIC -#define LOAD_LIBUSB_SYMBOL(type, func) \ - if (!(libusb_ctx.func = (type)SDL_LoadFunction(libusb_ctx.libhandle, "libusb_" #func))) { \ - loaded = false; \ - } -#else -#define LOAD_LIBUSB_SYMBOL(type, func) \ - libusb_ctx.func = libusb_##func; -#endif - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context **), init) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_context *), exit) - LOAD_LIBUSB_SYMBOL(ssize_t (LIBUSB_CALL *)(libusb_context *, libusb_device ***), get_device_list) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_device **, int), free_device_list) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, struct libusb_device_descriptor *), get_device_descriptor) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, struct libusb_config_descriptor **), get_active_config_descriptor) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, uint8_t, struct libusb_config_descriptor **), get_config_descriptor) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_config_descriptor *), free_config_descriptor) - LOAD_LIBUSB_SYMBOL(uint8_t (LIBUSB_CALL *)(libusb_device *), get_bus_number) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len), get_port_numbers) - LOAD_LIBUSB_SYMBOL(uint8_t (LIBUSB_CALL *)(libusb_device *), get_device_address) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, libusb_device_handle **), open) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_device_handle *), close) - LOAD_LIBUSB_SYMBOL(libusb_device * (LIBUSB_CALL *)(libusb_device_handle *dev_handle), get_device) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), claim_interface) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), release_interface) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), kernel_driver_active) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), detach_kernel_driver) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), attach_kernel_driver) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int, int), set_interface_alt_setting) - LOAD_LIBUSB_SYMBOL(struct libusb_transfer * (LIBUSB_CALL *)(int), alloc_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(struct libusb_transfer *), submit_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(struct libusb_transfer *), cancel_transfer) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_transfer *), free_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, uint8_t, uint8_t, uint16_t, uint16_t, unsigned char *, uint16_t, unsigned int), control_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), interrupt_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *), handle_events) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *, int *), handle_events_completed) - LOAD_LIBUSB_SYMBOL(const char * (LIBUSB_CALL *)(int), error_name) -#undef LOAD_LIBUSB_SYMBOL - - if (!loaded) { -#ifdef SDL_LIBUSB_DYNAMIC - SDL_UnloadObject(libusb_ctx.libhandle); -#endif - libusb_ctx.libhandle = NULL; - // SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, SDL_LIBUSB_DYNAMIC " found but could not load function"); - } else if (LIBUSB_hid_init() < 0) { -#ifdef SDL_LIBUSB_DYNAMIC - SDL_UnloadObject(libusb_ctx.libhandle); -#endif - libusb_ctx.libhandle = NULL; - } else { - ++success; - } + if (!SDL_InitLibUSB(&libusb_ctx)) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Couldn't load libusb"); + } else if (LIBUSB_hid_init() < 0) { + SDL_QuitLibUSB(); + libusb_ctx = NULL; + } else { + ++success; } } #endif // HAVE_LIBUSB @@ -1279,12 +1212,10 @@ int SDL_hid_exit(void) #endif // HAVE_PLATFORM_BACKEND #ifdef HAVE_LIBUSB - if (libusb_ctx.libhandle) { + if (libusb_ctx) { result |= LIBUSB_hid_exit(); -#ifdef SDL_LIBUSB_DYNAMIC - SDL_UnloadObject(libusb_ctx.libhandle); -#endif - libusb_ctx.libhandle = NULL; + SDL_QuitLibUSB(); + libusb_ctx = NULL; } #endif // HAVE_LIBUSB @@ -1379,34 +1310,6 @@ static void RemoveDeviceFromEnumeration(const char *driver_name, struct hid_devi } #endif // HAVE_LIBUSB || HAVE_PLATFORM_BACKEND -#ifdef HAVE_LIBUSB -static void RemoveNonWhitelistedDevicesFromEnumeration(struct hid_device_info **devs, void (*free_device_info)(struct hid_device_info *)) -{ - struct hid_device_info *last = NULL, *curr, *next; - - for (curr = *devs; curr; curr = next) { - next = curr->next; - - if (!IsInWhitelist(curr->vendor_id, curr->product_id)) { -#ifdef DEBUG_HIDAPI - SDL_Log("Device was not in libusb whitelist, skipping: %ls %ls 0x%.4hx/0x%.4hx/%d", - curr->manufacturer_string, curr->product_string, curr->vendor_id, curr->product_id, curr->interface_number); -#endif - if (last) { - last->next = next; - } else { - *devs = next; - } - - curr->next = NULL; - free_device_info(curr); - continue; - } - last = curr; - } -} -#endif // HAVE_LIBUSB - struct SDL_hid_device_info *SDL_hid_enumerate(unsigned short vendor_id, unsigned short product_id) { struct hid_device_info *driver_devs = NULL; @@ -1425,12 +1328,8 @@ struct SDL_hid_device_info *SDL_hid_enumerate(unsigned short vendor_id, unsigned #endif #ifdef HAVE_LIBUSB - if (libusb_ctx.libhandle) { + if (libusb_ctx) { usb_devs = LIBUSB_hid_enumerate(vendor_id, product_id); - - if (use_libusb_whitelist) { - RemoveNonWhitelistedDevicesFromEnumeration(&usb_devs, LIBUSB_hid_free_enumeration); - } } #endif // HAVE_LIBUSB @@ -1526,10 +1425,12 @@ SDL_hid_device *SDL_hid_open(unsigned short vendor_id, unsigned short product_id #endif // HAVE_DRIVER_BACKEND #ifdef HAVE_LIBUSB - if (libusb_ctx.libhandle != NULL) { + if (libusb_ctx) { pDevice = LIBUSB_hid_open(vendor_id, product_id, serial_number); if (pDevice != NULL) { - return CreateHIDDeviceWrapper(pDevice, &LIBUSB_Backend); + SDL_hid_device *dev = CreateHIDDeviceWrapper(pDevice, &LIBUSB_Backend); + SDL_SetPointerProperty(SDL_hid_get_properties(dev), SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER, ((LIBUSB_hid_device *)pDevice)->device_handle); + return dev; } } #endif // HAVE_LIBUSB @@ -1565,10 +1466,12 @@ SDL_hid_device *SDL_hid_open_path(const char *path) #endif // HAVE_DRIVER_BACKEND #ifdef HAVE_LIBUSB - if (libusb_ctx.libhandle != NULL) { + if (libusb_ctx) { pDevice = LIBUSB_hid_open_path(path); if (pDevice != NULL) { - return CreateHIDDeviceWrapper(pDevice, &LIBUSB_Backend); + SDL_hid_device *dev = CreateHIDDeviceWrapper(pDevice, &LIBUSB_Backend); + SDL_SetPointerProperty(SDL_hid_get_properties(dev), SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER, ((LIBUSB_hid_device *)pDevice)->device_handle); + return dev; } } #endif // HAVE_LIBUSB @@ -1578,6 +1481,16 @@ SDL_hid_device *SDL_hid_open_path(const char *path) return NULL; } +SDL_PropertiesID SDL_hid_get_properties(SDL_hid_device *device) +{ + CHECK_DEVICE_MAGIC(device, 0); + + if (!device->props) { + device->props = SDL_CreateProperties(); + } + return device->props; +} + int SDL_hid_write(SDL_hid_device *device, const unsigned char *data, size_t length) { CHECK_DEVICE_MAGIC(device, -1); @@ -1632,6 +1545,7 @@ int SDL_hid_close(SDL_hid_device *device) CHECK_DEVICE_MAGIC(device, -1); device->backend->hid_close(device->device); + SDL_DestroyProperties(device->props); DeleteHIDDeviceWrapper(device); return 0; } @@ -1693,60 +1607,3 @@ void SDL_hid_ble_scan(bool active) hid_ble_scan(active); #endif } - -#ifdef HAVE_ENABLE_GAMECUBE_ADAPTORS -// This is needed to enable input for Nyko and EVORETRO GameCube adaptors -void SDL_EnableGameCubeAdaptors(void) -{ -#ifdef HAVE_LIBUSB - libusb_context *context = NULL; - libusb_device **devs = NULL; - libusb_device_handle *handle = NULL; - struct libusb_device_descriptor desc; - ssize_t i, num_devs; - int kernel_detached = 0; - - if (libusb_ctx.libhandle == NULL) { - return; - } - - if (libusb_ctx.init(&context) == 0) { - num_devs = libusb_ctx.get_device_list(context, &devs); - for (i = 0; i < num_devs; ++i) { - if (libusb_ctx.get_device_descriptor(devs[i], &desc) != 0) { - continue; - } - - if (desc.idVendor != 0x057e || desc.idProduct != 0x0337) { - continue; - } - - if (libusb_ctx.open(devs[i], &handle) != 0) { - continue; - } - - if (libusb_ctx.kernel_driver_active(handle, 0)) { - if (libusb_ctx.detach_kernel_driver(handle, 0) == 0) { - kernel_detached = 1; - } - } - - if (libusb_ctx.claim_interface(handle, 0) == 0) { - libusb_ctx.control_transfer(handle, 0x21, 11, 0x0001, 0, NULL, 0, 1000); - libusb_ctx.release_interface(handle, 0); - } - - if (kernel_detached) { - libusb_ctx.attach_kernel_driver(handle, 0); - } - - libusb_ctx.close(handle); - } - - libusb_ctx.free_device_list(devs, 1); - - libusb_ctx.exit(context); - } -#endif // HAVE_LIBUSB -} -#endif // HAVE_ENABLE_GAMECUBE_ADAPTORS diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_android.h b/libs/SDL3/src/hidapi/SDL_hidapi_android.h index 2f3851f..6d349cb 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_android.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_android.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_c.h b/libs/SDL3/src/hidapi/SDL_hidapi_c.h index 6d94f77..a4be1c7 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_c.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -22,14 +22,5 @@ /* Return true if the HIDAPI should ignore a device during enumeration */ -extern bool SDL_HIDAPI_ShouldIgnoreDevice(int bus_type, Uint16 vendor_id, Uint16 product_id, Uint16 usage_page, Uint16 usage); +extern bool SDL_HIDAPI_ShouldIgnoreDevice(int bus_type, Uint16 vendor_id, Uint16 product_id, Uint16 usage_page, Uint16 usage, bool libusb); -#ifdef SDL_JOYSTICK_HIDAPI -#ifdef HAVE_LIBUSB -#define HAVE_ENABLE_GAMECUBE_ADAPTORS -#endif - -#ifdef HAVE_ENABLE_GAMECUBE_ADAPTORS -extern void SDL_EnableGameCubeAdaptors(void); -#endif -#endif /* SDL_JOYSTICK_HIDAPI */ diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_ios.h b/libs/SDL3/src/hidapi/SDL_hidapi_ios.h index f58f10d..6aa71c8 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_ios.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_ios.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_libusb.h b/libs/SDL3/src/hidapi/SDL_hidapi_libusb.h index ed8b4a3..a68ac49 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_libusb.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_libusb.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -82,7 +82,6 @@ #define wcsncpy SDL_wcslcpy -#ifndef SDL_PLATFORM_FREEBSD /* this is awkwardly inlined, so we need to re-implement it here * so we can override the libusb_control_transfer call */ static int SDL_libusb_get_string_descriptor(libusb_device_handle *dev, @@ -93,7 +92,23 @@ static int SDL_libusb_get_string_descriptor(libusb_device_handle *dev, data, (uint16_t)length, 1000); /* Endpoint 0 IN */ } #define libusb_get_string_descriptor SDL_libusb_get_string_descriptor -#endif /* SDL_PLATFORM_FREEBSD */ + +/* this is inlined, except on FreeBSD, so we need to re-implement it here */ +static void SDL_libusb_fill_interrupt_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *buffer, int length, + libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_INTERRUPT; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->user_data = user_data; + transfer->callback = callback; +} +#define libusb_fill_interrupt_transfer SDL_libusb_fill_interrupt_transfer #define HIDAPI_THREAD_MODEL_INCLUDE "hidapi_thread_sdl.h" #ifndef LIBUSB_API_VERSION diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_linux.h b/libs/SDL3/src/hidapi/SDL_hidapi_linux.h index 29723d7..8418e2e 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_linux.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_linux.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_mac.h b/libs/SDL3/src/hidapi/SDL_hidapi_mac.h index 820b67d..7ec8e4e 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_mac.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_mac.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_netbsd.h b/libs/SDL3/src/hidapi/SDL_hidapi_netbsd.h index 1b39e22..8bd5a8e 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_netbsd.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_netbsd.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_steamxbox.h b/libs/SDL3/src/hidapi/SDL_hidapi_steamxbox.h index b6294a3..41cecb5 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_steamxbox.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_steamxbox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_windows.h b/libs/SDL3/src/hidapi/SDL_hidapi_windows.h index 91f71c2..0ab9a6a 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_windows.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_windows.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/hidapi/android/hid.cpp b/libs/SDL3/src/hidapi/android/hid.cpp index 887390e..9349129 100644 --- a/libs/SDL3/src/hidapi/android/hid.cpp +++ b/libs/SDL3/src/hidapi/android/hid.cpp @@ -319,7 +319,7 @@ private: hid_buffer_entry *m_pFree; }; -static jbyteArray NewByteArray( JNIEnv* env, const uint8_t *pData, size_t nDataLen ) +static jbyteArray NewByteArray( JNIEnv *env, const uint8_t *pData, size_t nDataLen ) { jbyteArray array = env->NewByteArray( (jsize)nDataLen ); jbyte *pBuf = env->GetByteArrayElements( array, NULL ); @@ -333,7 +333,7 @@ static char *CreateStringFromJString( JNIEnv *env, const jstring &sString ) { size_t nLength = env->GetStringUTFLength( sString ); const char *pjChars = env->GetStringUTFChars( sString, NULL ); - char *psString = (char*)malloc( nLength + 1 ); + char *psString = (char *)malloc( nLength + 1 ); SDL_memcpy( psString, pjChars, nLength ); psString[ nLength ] = '\0'; env->ReleaseStringUTFChars( sString, pjChars ); @@ -344,7 +344,7 @@ static wchar_t *CreateWStringFromJString( JNIEnv *env, const jstring &sString ) { size_t nLength = env->GetStringLength( sString ); const jchar *pjChars = env->GetStringChars( sString, NULL ); - wchar_t *pwString = (wchar_t*)malloc( ( nLength + 1 ) * sizeof( wchar_t ) ); + wchar_t *pwString = (wchar_t *)malloc( ( nLength + 1 ) * sizeof( wchar_t ) ); wchar_t *pwChars = pwString; for ( size_t iIndex = 0; iIndex < nLength; ++iIndex ) { @@ -358,7 +358,7 @@ static wchar_t *CreateWStringFromJString( JNIEnv *env, const jstring &sString ) static wchar_t *CreateWStringFromWString( const wchar_t *pwSrc ) { size_t nLength = SDL_wcslen( pwSrc ); - wchar_t *pwString = (wchar_t*)malloc( ( nLength + 1 ) * sizeof( wchar_t ) ); + wchar_t *pwString = (wchar_t *)malloc( ( nLength + 1 ) * sizeof( wchar_t ) ); SDL_memcpy( pwString, pwSrc, nLength * sizeof( wchar_t ) ); pwString[ nLength ] = '\0'; return pwString; @@ -997,7 +997,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(J hid_device_ref pDevice = FindDevice( nDeviceID ); if ( pDevice ) { - pDevice->ProcessInput( reinterpret_cast< const uint8_t* >( pBuf ), nBufSize ); + pDevice->ProcessInput( reinterpret_cast< const uint8_t * >( pBuf ), nBufSize ); } env->ReleaseByteArrayElements(value, pBuf, 0); @@ -1013,7 +1013,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReportResponse hid_device_ref pDevice = FindDevice( nDeviceID ); if ( pDevice ) { - pDevice->ProcessReportResponse( reinterpret_cast< const uint8_t* >( pBuf ), nBufSize ); + pDevice->ProcessReportResponse( reinterpret_cast< const uint8_t * >( pBuf ), nBufSize ); } env->ReleaseByteArrayElements(value, pBuf, 0); @@ -1092,7 +1092,7 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor const hid_device_info *info = pDevice->GetDeviceInfo(); /* See if there are any devices we should skip in enumeration */ - if (SDL_HIDAPI_ShouldIgnoreDevice(HID_API_BUS_UNKNOWN, info->vendor_id, info->product_id, 0, 0)) { + if (SDL_HIDAPI_ShouldIgnoreDevice(HID_API_BUS_UNKNOWN, info->vendor_id, info->product_id, 0, 0, false)) { continue; } @@ -1375,7 +1375,7 @@ int hid_get_report_descriptor(hid_device *device, unsigned char *buf, size_t buf return -1; } -HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device) +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *device) { return NULL; } diff --git a/libs/SDL3/src/hidapi/ios/hid.m b/libs/SDL3/src/hidapi/ios/hid.m index 29e0782..e1171ff 100644 --- a/libs/SDL3/src/hidapi/ios/hid.m +++ b/libs/SDL3/src/hidapi/ios/hid.m @@ -63,6 +63,7 @@ #define VALVE_USB_VID 0x28DE #define D0G_BLE2_PID 0x1106 +#define TRITON_BLE_PID 0x1303 typedef uint32_t uint32; typedef uint64_t uint64; @@ -76,7 +77,8 @@ typedef uint64_t uint64; #define VALVE_SERVICE @"100F6C32-1735-4313-B402-38567131E5F3" // (READ/NOTIFICATIONS) -#define VALVE_INPUT_CHAR @"100F6C33-1735-4313-B402-38567131E5F3" +#define VALVE_INPUT_CHAR_0x1106 @"100F6C33-1735-4313-B402-38567131E5F3" +#define VALVE_INPUT_CHAR_0x1303 @"100F6C7A-1735-4313-B402-38567131E5F3" //  (READ/WRITE) #define VALVE_REPORT_CHAR @"100F6C34-1735-4313-B402-38567131E5F3" @@ -101,21 +103,7 @@ typedef struct typedef struct { uint8_t id; - union { - bluetoothSegment segment; - struct { - uint8_t segmentHeader; - uint8_t featureReportMessageID; - uint8_t length; - uint8_t settingIdentifier; - union { - uint16_t usPayload; - uint32_t uPayload; - uint64_t ulPayload; - uint8_t ucPayload[15]; - }; - }; - }; + bluetoothSegment segment; } hidFeatureReport; #pragma pack(pop) @@ -125,34 +113,62 @@ size_t GetBluetoothSegmentSize(bluetoothSegment *segment) return segment->length + 3; } -#define RingBuffer_cbElem 19 -#define RingBuffer_nElem 4096 +#define RingBuffer_nElem 256 typedef struct { int _first, _last; - uint8_t _data[ ( RingBuffer_nElem * RingBuffer_cbElem ) ]; + int _cbElem; + uint8_t *_data; pthread_mutex_t accessLock; } RingBuffer; -static void RingBuffer_init( RingBuffer *this ) +static RingBuffer *RingBuffer_alloc( int cbElem ) { + RingBuffer *this = (RingBuffer *)malloc( sizeof(*this) ); + if (!this) +{ + return NULL; + } + this->_first = -1; this->_last = 0; + this->_cbElem = cbElem; + this->_data = (uint8_t *)malloc(RingBuffer_nElem * cbElem); + if ( !this->_data ) + { + free( this ); + return NULL; + } pthread_mutex_init( &this->accessLock, 0 ); + return this; +} + +static void RingBuffer_free( RingBuffer *this ) +{ + if ( this ) + { + free( this->_data ); + free( this ); + } } static bool RingBuffer_write( RingBuffer *this, const uint8_t *src ) { + if ( !this ) + { + return false; + } + pthread_mutex_lock( &this->accessLock ); - memcpy( &this->_data[ this->_last ], src, RingBuffer_cbElem ); + memcpy( &this->_data[ this->_last ], src, this->_cbElem ); if ( this->_first == -1 ) { this->_first = this->_last; } - this->_last = ( this->_last + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem); + this->_last = ( this->_last + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem); if ( this->_last == this->_first ) { - this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem); + this->_first = ( this->_first + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem); pthread_mutex_unlock( &this->accessLock ); return false; } @@ -162,14 +178,19 @@ static bool RingBuffer_write( RingBuffer *this, const uint8_t *src ) static bool RingBuffer_read( RingBuffer *this, uint8_t *dst ) { + if ( !this ) + { + return false; + } + pthread_mutex_lock( &this->accessLock ); if ( this->_first == -1 ) { pthread_mutex_unlock( &this->accessLock ); return false; } - memcpy( dst, &this->_data[ this->_first ], RingBuffer_cbElem ); - this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem); + memcpy( dst, &this->_data[ this->_first ], this->_cbElem ); + this->_first = ( this->_first + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem); if ( this->_first == this->_last ) { this->_first = -1; @@ -191,12 +212,14 @@ typedef enum @interface HIDBLEDevice : NSObject { - RingBuffer _inputReports; - uint8_t _featureReport[20]; + RingBuffer *_inputReports; + NSData *_featureReport; + NSMutableDictionary *_outputReports; BLEDeviceWaitState _waitStateForReadFeatureReport; BLEDeviceWaitState _waitStateForWriteFeatureReport; } +@property (nonatomic, readwrite) uint16_t pid; @property (nonatomic, readwrite) bool connected; @property (nonatomic, readwrite) bool ready; @@ -205,6 +228,7 @@ typedef enum @property (nonatomic, strong) CBCharacteristic *bleCharacteristicReport; - (id)initWithPeripheral:(CBPeripheral *)peripheral; +- (void)onDisconnect; @end @@ -278,8 +302,7 @@ typedef enum HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral]; if ( steamController ) { - steamController.connected = NO; - steamController.ready = NO; + [steamController onDisconnect]; [self.centralManager cancelPeripheralConnection:peripheral]; } } @@ -403,7 +426,7 @@ typedef enum NSLog( @"CoreBluetooth BLE hardware is powered on and ready" ); // at startup, if we have no already attached peripherals, do a 20s scan for new unpaired devices, - // otherwise callers should occaisionally do additional scans. we don't want to continuously be + // otherwise callers should occasionally do additional scans. we don't want to continuously be // scanning because it drains battery, causes other nearby people to have a hard time pairing their // Steam Controllers, and may also trigger firmware weirdness when a device attempts to start // the pairing sequence multiple times concurrently @@ -474,8 +497,7 @@ typedef enum HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral]; if ( steamController ) { - steamController.connected = NO; - steamController.ready = NO; + [steamController onDisconnect]; [self.deviceMap removeObjectForKey:peripheral]; } } @@ -500,12 +522,14 @@ static void process_pending_events(void) { if ( self = [super init] ) { - RingBuffer_init( &_inputReports ); + self.pid = 0; + _inputReports = NULL; + _outputReports = [[NSMutableDictionary alloc] init]; + _connected = NO; + _ready = NO; self.bleSteamController = nil; self.bleCharacteristicInput = nil; self.bleCharacteristicReport = nil; - _connected = NO; - _ready = NO; } return self; } @@ -514,7 +538,9 @@ static void process_pending_events(void) { if ( self = [super init] ) { - RingBuffer_init( &_inputReports ); + self.pid = 0; + _inputReports = NULL; + _outputReports = [[NSMutableDictionary alloc] init]; _connected = NO; _ready = NO; self.bleSteamController = peripheral; @@ -528,6 +554,18 @@ static void process_pending_events(void) return self; } +- (void)onDisconnect +{ + self.connected = NO; + self.ready = NO; + + if ( _inputReports ) + { + RingBuffer_free( _inputReports ); + _inputReports = NULL; + } +} + - (void)setConnected:(bool)connected { _connected = connected; @@ -543,94 +581,134 @@ static void process_pending_events(void) - (size_t)read_input_report:(uint8_t *)dst { - if ( RingBuffer_read( &_inputReports, dst+1 ) ) + if ( RingBuffer_read( _inputReports, dst+1 ) ) { - *dst = 0x03; - return 20; + switch ( self.pid ) + { + case D0G_BLE2_PID: + *dst = 0x03; + break; + case TRITON_BLE_PID: + *dst = 0x42; + break; + default: + abort(); + } + return _inputReports->_cbElem + 1; } return 0; } - (int)send_report:(const uint8_t *)data length:(size_t)length { + if ( self.pid == D0G_BLE2_PID ) + { [_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse]; return (int)length; } -- (int)send_feature_report:(hidFeatureReport *)report + // We need to look up the correct characteristic for this output report + if ( length > 0 ) + { + CBCharacteristic *aChar = [_outputReports objectForKey:[NSNumber numberWithInt:data[0]]]; + if ( aChar != nil ) + { + [_bleSteamController writeValue:[NSData dataWithBytes:&data[1] length:(length - 1)] forCharacteristic:aChar type:CBCharacteristicWriteWithResponse]; + return (int)length; + } + } + return -1; +} + +- (int)send_feature_report:(hidFeatureReport *)report length:(size_t)length { #if FEATURE_REPORT_LOGGING uint8_t *reportBytes = (uint8_t *)report; - NSLog( @"HIDBLE:send_feature_report (%02zu/19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", GetBluetoothSegmentSize( report->segment ), + NSLog( @"HIDBLE:send_feature_report (%02zu/19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", length, reportBytes[1], reportBytes[2], reportBytes[3], reportBytes[4], reportBytes[5], reportBytes[6], reportBytes[7], reportBytes[8], reportBytes[9], reportBytes[10], reportBytes[11], reportBytes[12], reportBytes[13], reportBytes[14], reportBytes[15], reportBytes[16], reportBytes[17], reportBytes[18], reportBytes[19] ); #endif - int sendSize = (int)GetBluetoothSegmentSize( &report->segment ); - if ( sendSize > 20 ) - sendSize = 20; - #if 1 // fire-and-forget - we are going to not wait for the response here because all Steam Controller BLE send_feature_report's are ignored, // except errors. - [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse]; + [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:MIN(length, 64)] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse]; // pretend we received a result anybody cares about - return 19; + return (int)length; #else // this is technically the correct send_feature_report logic if you want to make sure it gets through and is // acknowledged or errors out _waitStateForWriteFeatureReport = BLEDeviceWaitState_Waiting; - [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize + [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:MIN(length, 64) ] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse]; - while ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Waiting ) + while ( _connected && _waitStateForWriteFeatureReport == BLEDeviceWaitState_Waiting ) { process_pending_events(); } - if ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Error ) + if ( !_connected || _waitStateForWriteFeatureReport == BLEDeviceWaitState_Error ) { _waitStateForWriteFeatureReport = BLEDeviceWaitState_None; return -1; } _waitStateForWriteFeatureReport = BLEDeviceWaitState_None; - return 19; + return (int)length; #endif } -- (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer +- (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer length:(size_t)length { _waitStateForReadFeatureReport = BLEDeviceWaitState_Waiting; [_bleSteamController readValueForCharacteristic:_bleCharacteristicReport]; - while ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting ) - process_pending_events(); + while ( _connected && _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting ) + { + process_pending_events(); + } - if ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Error ) + if ( !_connected || _waitStateForReadFeatureReport == BLEDeviceWaitState_Error ) { _waitStateForReadFeatureReport = BLEDeviceWaitState_None; return -1; } - memcpy( buffer, _featureReport, sizeof(_featureReport) ); + int amount = 0; + if ( _featureReport.length > 0 ) + { + uint8_t *data = (uint8_t *)_featureReport.bytes; + if ( *data == *buffer ) + { + amount = (int)MIN( length, _featureReport.length ); + memcpy( buffer, _featureReport.bytes, amount ); + } + else + { + // Leave the report in the buffer + amount = (int)MIN( length - 1, _featureReport.length ); + memcpy( &buffer[ 1 ], _featureReport.bytes, amount ); + ++amount; + } + } _waitStateForReadFeatureReport = BLEDeviceWaitState_None; #if FEATURE_REPORT_LOGGING - NSLog( @"HIDBLE:get_feature_report (19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", - buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], - buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], - buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18], - buffer[19] ); + NSLog( @"HIDBLE:get_feature_report (%lu/%zu) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", + _featureReport.length, length, + buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], + buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], + buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18], + buffer[19] ); #endif - return 19; + return amount; } #pragma mark CBPeripheralDelegate Implementation @@ -667,8 +745,14 @@ static void process_pending_events(void) { NSLog( @"Found Characteristic %@", aChar ); - if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR]] ) + if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1106]] ) { + self.pid = D0G_BLE2_PID; + self.bleCharacteristicInput = aChar; + } + else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1303]] ) + { + self.pid = TRITON_BLE_PID; self.bleCharacteristicInput = aChar; } else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] ) @@ -676,6 +760,21 @@ static void process_pending_events(void) self.bleCharacteristicReport = aChar; [self.bleSteamController discoverDescriptorsForCharacteristic: aChar]; } + else + { + NSString *UUIDString = [aChar.UUID UUIDString]; + int report_id = 0; + if ( sscanf( UUIDString.UTF8String, "100F6C%x", &report_id ) == 1 && report_id > 0x35 ) + { + report_id -= 0x35; + //NSLog( @"Found characteristic for output report 0x%.2x", report_id ); + + if (report_id >= 0x80) { + // An output report + [_outputReports setObject:aChar forKey:[NSNumber numberWithInt:report_id]]; + } + } + } } } } @@ -690,17 +789,33 @@ static void process_pending_events(void) if ( self.ready == NO ) { self.ready = YES; + if ( _inputReports == NULL ) + { + int cbElem = 0; + switch ( self.pid ) + { + case D0G_BLE2_PID: + cbElem = 19; + break; + case TRITON_BLE_PID: + cbElem = 53; + break; + default: + abort(); + } + _inputReports = RingBuffer_alloc( cbElem ); + } HIDBLEManager.sharedInstance.nPendingPairs -= 1; } if ( [characteristic.UUID isEqual:_bleCharacteristicInput.UUID] ) { NSData *data = [characteristic value]; - if ( data.length != 19 ) + if ( _inputReports && data.length != _inputReports->_cbElem ) { - NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 19", (unsigned long)data.length ); + NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly %d", (unsigned long)data.length, _inputReports->_cbElem ); } - if ( !RingBuffer_write( &_inputReports, (const uint8_t *)data.bytes ) ) + if ( !RingBuffer_write( _inputReports, (const uint8_t *)data.bytes ) ) { uint64_t ticksNow = mach_approximate_time(); if ( ticksNow - s_ticksLastOverflowReport > (5ull * NSEC_PER_SEC / 10) ) @@ -712,8 +827,6 @@ static void process_pending_events(void) } else if ( [characteristic.UUID isEqual:_bleCharacteristicReport.UUID] ) { - memset( _featureReport, 0, sizeof(_featureReport) ); - if ( error != nil ) { NSLog( @"HIDBLE: get_feature_report error: %@", error ); @@ -721,12 +834,7 @@ static void process_pending_events(void) } else { - NSData *data = [characteristic value]; - if ( data.length != 20 ) - { - NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 20", (unsigned long)data.length ); - } - memcpy( _featureReport, data.bytes, MIN( data.length, sizeof(_featureReport) ) ); + _featureReport = [characteristic value]; _waitStateForReadFeatureReport = BLEDeviceWaitState_Complete; } } @@ -750,7 +858,7 @@ static void process_pending_events(void) - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { - NSLog( @"didUpdateNotifcationStateForCharacteristic %@ (%@)", characteristic, error ); + NSLog( @"didUpdateNotificationStateForCharacteristic %@ (%@)", characteristic, error ); } @end @@ -850,7 +958,7 @@ static struct hid_device_info *create_device_info_for_hid_device(HIDBLEDevice *d memset( device_info, 0, sizeof(struct hid_device_info) ); device_info->path = strdup( device.bleSteamController.identifier.UUIDString.UTF8String ); device_info->vendor_id = VALVE_USB_VID; - device_info->product_id = D0G_BLE2_PID; + device_info->product_id = device.pid; device_info->product_string = wcsdup( L"Steam Controller" ); device_info->manufacturer_string = wcsdup( L"Valve Corporation" ); device_info->bus_type = HID_API_BUS_BLUETOOTH; @@ -861,14 +969,6 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, { @autoreleasepool { struct hid_device_info *root = NULL; - /* See if there are any devices we should skip in enumeration */ - if (SDL_HIDAPI_ShouldIgnoreDevice(HID_API_BUS_BLUETOOTH, VALVE_USB_VID, D0G_BLE2_PID, 0, 0)) { - return NULL; - } - - if ( ( vendor_id == 0 || vendor_id == VALVE_USB_VID ) && - ( product_id == 0 || product_id == D0G_BLE2_PID ) ) - { HIDBLEManager *bleManager = HIDBLEManager.sharedInstance; [bleManager updateConnectedSteamControllers:false]; NSEnumerator *devices = [bleManager.deviceMap objectEnumerator]; @@ -891,11 +991,22 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, } continue; } + + if ( ( vendor_id != 0 && vendor_id != VALVE_USB_VID ) || + ( product_id != 0 && product_id != device.pid ) ) + { + continue; + } + + /* See if there are any devices we should skip in enumeration */ + if (SDL_HIDAPI_ShouldIgnoreDevice(HID_API_BUS_BLUETOOTH, VALVE_USB_VID, device.pid, 0, 0, false)) { + continue; + } + struct hid_device_info *device_info = create_device_info_for_hid_device(device); device_info->next = root; root = device_info; } - } return root; }} @@ -975,7 +1086,7 @@ int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char if ( !device_handle.connected ) return -1; - return [device_handle send_feature_report:(hidFeatureReport *)(void *)data]; + return [device_handle send_feature_report:(hidFeatureReport *)(void *)data length:length]; } int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) @@ -985,7 +1096,7 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, if ( !device_handle.connected ) return -1; - size_t written = [device_handle get_feature_report:data[0] into:data]; + size_t written = [device_handle get_feature_report:data[0] into:data length:length]; return written == length-1 ? (int)length : (int)written; } @@ -1018,7 +1129,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t NSLog( @"hid_read_timeout with non-zero wait" ); } int result = (int)[device_handle read_input_report:data]; -#if FEATURE_REPORT_LOGGING +#if 0 //FEATURE_REPORT_LOGGING NSLog( @"HIDBLE:hid_read_timeout (%d) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", result, data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], diff --git a/libs/SDL3/src/hidapi/libusb/hid.c b/libs/SDL3/src/hidapi/libusb/hid.c index 21fa1c0..1636843 100644 --- a/libs/SDL3/src/hidapi/libusb/hid.c +++ b/libs/SDL3/src/hidapi/libusb/hid.c @@ -260,19 +260,19 @@ static int get_usage(uint8_t *report_descriptor, size_t size, } if (key_cmd == 0x4) { - *usage_page = get_bytes(report_descriptor, size, data_len, i); + *usage_page = (unsigned short)get_bytes(report_descriptor, size, data_len, i); usage_page_found = 1; //printf("Usage Page: %x\n", (uint32_t)*usage_page); } if (key_cmd == 0x8) { if (data_len == 4) { /* Usages 5.5 / Usage Page 6.2.2.7 */ - *usage_page = get_bytes(report_descriptor, size, 2, i + 2); + *usage_page = (unsigned short)get_bytes(report_descriptor, size, 2, i + 2); usage_page_found = 1; - *usage = get_bytes(report_descriptor, size, 2, i); + *usage = (unsigned short)get_bytes(report_descriptor, size, 2, i); usage_found = 1; } else { - *usage = get_bytes(report_descriptor, size, data_len, i); + *usage = (unsigned short)get_bytes(report_descriptor, size, data_len, i); usage_found = 1; } //printf("Usage: %x\n", (uint32_t)*usage); @@ -288,7 +288,7 @@ static int get_usage(uint8_t *report_descriptor, size_t size, return -1; /* failure */ } -#if defined(__FreeBSD__) && __FreeBSD__ < 10 +#if defined(__FreeBSD__) && __FreeBSD__ < 10 && !defined(libusb_get_string_descriptor) /* The libusb version included in FreeBSD < 10 doesn't have this function. In mainline libusb, it's inlined in libusb.h. This function will bear a striking resemblance to that one, because there's about one way to code it. @@ -640,7 +640,7 @@ static int hid_get_report_descriptor_libusb(libusb_device_handle *handle, int in /* Get the HID Report Descriptor. See USB HID Specification, section 7.1.1 */ - int res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8), interface_num, tmp, expected_report_descriptor_size, 5000); + int res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8), (uint16_t)interface_num, tmp, expected_report_descriptor_size, 5000); if (res >= 0) { if (res > (int)buf_size) res = (int)buf_size; @@ -671,6 +671,8 @@ static void fill_device_info_usage(struct hid_device_info *cur_dev, libusb_devic cur_dev->usage_page = page; cur_dev->usage = usage; + + free(hid_report_descriptor); } #ifdef INVASIVE_GET_USAGE @@ -884,9 +886,13 @@ static int is_xboxone(unsigned short vendor_id, const struct libusb_interface_de 0x1532, /* Razer Wildcat */ 0x20d6, /* PowerA */ 0x24c6, /* PowerA */ + 0x294b, /* Snakebyte */ 0x2dc8, /* 8BitDo */ 0x2e24, /* Hyperkin */ + 0x2e95, /* SCUF */ + 0x3285, /* Nacon */ 0x3537, /* GameSir */ + 0x366c, /* ByoWave */ }; if (intf_desc->bInterfaceNumber == 0 && @@ -976,7 +982,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, #ifdef HIDAPI_IGNORE_DEVICE /* See if there are any devices we should skip in enumeration */ - if (HIDAPI_IGNORE_DEVICE(HID_API_BUS_USB, dev_vid, dev_pid, 0, 0)) { + if (HIDAPI_IGNORE_DEVICE(HID_API_BUS_USB, dev_vid, dev_pid, 0, 0, true)) { continue; } #endif @@ -1191,7 +1197,7 @@ static void *read_thread(void *param) dev->transfer = libusb_alloc_transfer(0); libusb_fill_interrupt_transfer(dev->transfer, dev->device_handle, - dev->input_endpoint, + (unsigned char)dev->input_endpoint, buf, (int) length, read_callback, @@ -1261,6 +1267,7 @@ static void init_xbox360(libusb_device_handle *device_handle, unsigned short idV if ((idVendor == 0x05ac && idProduct == 0x055b) /* Gamesir-G3w */ || (idVendor == 0x20d6 && idProduct == 0x4010) /* PowerA Battle Dragon Advanced Wireless Controller */ || + (idVendor == 0x20d6 && idProduct == 0x4025) /* PowerA OPS v1 Wireless Controller */ || idVendor == 0x0f0d /* Hori Xbox controllers */) { unsigned char data[20]; @@ -1610,8 +1617,8 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t res = libusb_control_transfer(dev->device_handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, 0x09/*HID Set_Report*/, - (2/*HID output*/ << 8) | report_number, - dev->interface, + (uint16_t)((2/*HID output*/ << 8) | report_number), + (uint16_t)dev->interface, (unsigned char *)data, (uint16_t)length, 1000/*timeout millis*/); @@ -1627,7 +1634,7 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t /* Use the interrupt out endpoint */ int actual_length; res = libusb_interrupt_transfer(dev->device_handle, - dev->output_endpoint, + (unsigned char)dev->output_endpoint, (unsigned char*)data, (int) length, &actual_length, 1000); @@ -1776,8 +1783,8 @@ int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char res = libusb_control_transfer(dev->device_handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, 0x09/*HID set_report*/, - (3/*HID feature*/ << 8) | report_number, - dev->interface, + (uint16_t)((3/*HID feature*/ << 8) | report_number), + (uint16_t)dev->interface, (unsigned char *)data, (uint16_t)length, 1000/*timeout millis*/); @@ -1807,8 +1814,8 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, res = libusb_control_transfer(dev->device_handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN, 0x01/*HID get_report*/, - (3/*HID feature*/ << 8) | report_number, - dev->interface, + (uint16_t)((3/*HID feature*/ << 8) | report_number), + (uint16_t)dev->interface, (unsigned char *)data, (uint16_t)length, 1000/*timeout millis*/); @@ -1837,8 +1844,8 @@ int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned c res = libusb_control_transfer(dev->device_handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN, 0x01/*HID get_report*/, - (1/*HID Input*/ << 8) | report_number, - dev->interface, + (uint16_t)((1/*HID Input*/ << 8) | report_number), + (uint16_t)dev->interface, (unsigned char *)data, (uint16_t)length, 1000/*timeout millis*/); @@ -1930,7 +1937,7 @@ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index { wchar_t *str; - str = get_usb_string(dev->device_handle, string_index); + str = get_usb_string(dev->device_handle, (uint8_t)string_index); if (str) { wcsncpy(string, str, maxlen); string[maxlen-1] = L'\0'; @@ -2118,7 +2125,7 @@ uint16_t get_usb_code_for_current_locale(void) /* Chop off the encoding part, and make it lower case. */ ptr = search_string; while (*ptr) { - *ptr = tolower(*ptr); + *ptr = (char)tolower(*ptr); if (*ptr == '.') { *ptr = '\0'; break; @@ -2139,7 +2146,7 @@ uint16_t get_usb_code_for_current_locale(void) /* Chop off the variant. Chop it off at the '_'. */ ptr = search_string; while (*ptr) { - *ptr = tolower(*ptr); + *ptr = (char)tolower(*ptr); if (*ptr == '_') { *ptr = '\0'; break; diff --git a/libs/SDL3/src/hidapi/libusb/hidapi_thread_sdl.h b/libs/SDL3/src/hidapi/libusb/hidapi_thread_sdl.h index 1f5c7db..8503999 100644 --- a/libs/SDL3/src/hidapi/libusb/hidapi_thread_sdl.h +++ b/libs/SDL3/src/hidapi/libusb/hidapi_thread_sdl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/hidapi/linux/hid.c b/libs/SDL3/src/hidapi/linux/hid.c index fa303d7..c0cb6a7 100644 --- a/libs/SDL3/src/hidapi/linux/hid.c +++ b/libs/SDL3/src/hidapi/linux/hid.c @@ -147,7 +147,7 @@ static void register_error_str(wchar_t **error_str, const char *msg) #endif } -/* Semilar to register_error_str, but allows passing a format string with va_list args into this function. */ +/* Similar to register_error_str, but allows passing a format string with va_list args into this function. */ static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) { char msg[256]; @@ -906,7 +906,7 @@ static struct hid_device_info * create_device_info_for_device(struct udev_device cur_dev = root; while (cur_dev) { - if (HIDAPI_IGNORE_DEVICE(cur_dev->bus_type, cur_dev->vendor_id, cur_dev->product_id, cur_dev->usage_page, cur_dev->usage)) { + if (HIDAPI_IGNORE_DEVICE(cur_dev->bus_type, cur_dev->vendor_id, cur_dev->product_id, cur_dev->usage_page, cur_dev->usage, false)) { struct hid_device_info *tmp = cur_dev; cur_dev = tmp->next; diff --git a/libs/SDL3/src/hidapi/mac/hid.c b/libs/SDL3/src/hidapi/mac/hid.c index 8acb6da..4936fe1 100644 --- a/libs/SDL3/src/hidapi/mac/hid.c +++ b/libs/SDL3/src/hidapi/mac/hid.c @@ -596,7 +596,7 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, #ifdef HIDAPI_IGNORE_DEVICE /* See if there are any devices we should skip in enumeration */ - if (HIDAPI_IGNORE_DEVICE(get_bus_type(dev), dev_vid, dev_pid, usage_page, usage)) { + if (HIDAPI_IGNORE_DEVICE(get_bus_type(dev), dev_vid, dev_pid, usage_page, usage, false)) { free(cur_dev); return NULL; } @@ -1424,7 +1424,7 @@ void HID_API_EXPORT hid_close(hid_device *dev) UPD: The crash part was true in/until some version of macOS. Starting with macOS 10.15, there is an opposite effect in some environments: - crash happenes if IOHIDDeviceClose() is not called. + crash happens if IOHIDDeviceClose() is not called. Not leaking a resource in all tested environments. */ if (is_macos_10_10_or_greater || !dev->disconnected) { diff --git a/libs/SDL3/src/hidapi/netbsd/hid.c b/libs/SDL3/src/hidapi/netbsd/hid.c index 82f34d4..d88630d 100644 --- a/libs/SDL3/src/hidapi/netbsd/hid.c +++ b/libs/SDL3/src/hidapi/netbsd/hid.c @@ -96,7 +96,7 @@ static void register_error_str(wchar_t **error_str, const char *msg) *error_str = utf8_to_wchar_t(msg); } -/* Semilar to register_error_str, but allows passing a format string with va_list args into this function. */ +/* Similar to register_error_str, but allows passing a format string with va_list args into this function. */ static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) { char msg[256]; diff --git a/libs/SDL3/src/hidapi/windows/hid.c b/libs/SDL3/src/hidapi/windows/hid.c index 87aa639..aa67a46 100644 --- a/libs/SDL3/src/hidapi/windows/hid.c +++ b/libs/SDL3/src/hidapi/windows/hid.c @@ -991,9 +991,7 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor break; } - if (device_interface_list != NULL) { - free(device_interface_list); - } + free(device_interface_list); // This should NOT be SDL_free() device_interface_list = (wchar_t*)calloc(len, sizeof(wchar_t)); if (device_interface_list == NULL) { @@ -1044,7 +1042,7 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor HidP_GetCaps(pp_data, &caps); HidD_FreePreparsedData(pp_data); } - if (HIDAPI_IGNORE_DEVICE(bus_type, attrib.VendorID, attrib.ProductID, caps.UsagePage, caps.Usage)) { + if (HIDAPI_IGNORE_DEVICE(bus_type, attrib.VendorID, attrib.ProductID, caps.UsagePage, caps.Usage, false)) { goto cont_close; } #endif @@ -1399,6 +1397,11 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char } } if (!res) { + if (GetLastError() == ERROR_OPERATION_ABORTED) { + /* The read request was issued on another thread. + This is harmless, so just ignore it. */ + return 0; + } register_winapi_error(dev, L"hid_read_timeout/GetOverlappedResult"); } diff --git a/libs/SDL3/src/io/SDL_asyncio.c b/libs/SDL3/src/io/SDL_asyncio.c index 51adf6b..3146137 100644 --- a/libs/SDL3/src/io/SDL_asyncio.c +++ b/libs/SDL3/src/io/SDL_asyncio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -23,34 +23,39 @@ #include "SDL_sysasyncio.h" #include "SDL_asyncio_c.h" -static const char *AsyncFileModeValid(const char *mode) +static const char *AsyncFileModeValid(const char *mode, bool *readonly) { - static const struct { const char *valid; const char *with_binary; } mode_map[] = { - { "r", "rb" }, - { "w", "wb" }, - { "r+","r+b" }, - { "w+", "w+b" } + static const struct { const char *valid; const char *with_binary; bool readonly; } mode_map[] = { + { "r", "rb", true }, + { "w", "wb", false }, + { "r+","r+b", false }, + { "w+", "w+b", false } }; for (int i = 0; i < SDL_arraysize(mode_map); i++) { if (SDL_strcmp(mode, mode_map[i].valid) == 0) { + *readonly = mode_map[i].readonly; return mode_map[i].with_binary; } } + + *readonly = false; return NULL; } SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode) { - if (!file) { + CHECK_PARAM(!file) { SDL_InvalidParamError("file"); return NULL; - } else if (!mode) { + } + CHECK_PARAM(!mode) { SDL_InvalidParamError("mode"); return NULL; } - const char *binary_mode = AsyncFileModeValid(mode); + bool readonly = false; + const char *binary_mode = AsyncFileModeValid(mode, &readonly); if (!binary_mode) { SDL_SetError("Unsupported file mode"); return NULL; @@ -61,6 +66,8 @@ SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode) return NULL; } + asyncio->readonly = readonly; + asyncio->lock = SDL_CreateMutex(); if (!asyncio->lock) { SDL_free(asyncio); @@ -78,7 +85,7 @@ SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode) Sint64 SDL_GetAsyncIOSize(SDL_AsyncIO *asyncio) { - if (!asyncio) { + CHECK_PARAM(!asyncio) { SDL_InvalidParamError("asyncio"); return -1; } @@ -87,11 +94,13 @@ Sint64 SDL_GetAsyncIOSize(SDL_AsyncIO *asyncio) static bool RequestAsyncIO(bool reading, SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata) { - if (!asyncio) { + CHECK_PARAM(!asyncio) { return SDL_InvalidParamError("asyncio"); - } else if (!ptr) { + } + CHECK_PARAM(!ptr) { return SDL_InvalidParamError("ptr"); - } else if (!queue) { + } + CHECK_PARAM(!queue) { return SDL_InvalidParamError("queue"); } @@ -143,9 +152,10 @@ bool SDL_WriteAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 siz bool SDL_CloseAsyncIO(SDL_AsyncIO *asyncio, bool flush, SDL_AsyncIOQueue *queue, void *userdata) { - if (!asyncio) { + CHECK_PARAM(!asyncio) { return SDL_InvalidParamError("asyncio"); - } else if (!queue) { + } + CHECK_PARAM(!queue) { return SDL_InvalidParamError("queue"); } @@ -215,7 +225,8 @@ static bool GetAsyncIOTaskOutcome(SDL_AsyncIOTask *task, SDL_AsyncIOOutcome *out outcome->userdata = task->app_userdata; // Take the completed task out of the SDL_AsyncIO that created it. - SDL_LockMutex(asyncio->lock); + SDL_Mutex *lock = asyncio->lock; + SDL_LockMutex(lock); LINKED_LIST_UNLINK(task, asyncio); // see if it's time to queue a pending close request (close requested and no other pending tasks) SDL_AsyncIOTask *closing = asyncio->closing; @@ -228,7 +239,7 @@ static bool GetAsyncIOTaskOutcome(SDL_AsyncIOTask *task, SDL_AsyncIOOutcome *out SDL_AddAtomicInt(&closing->queue->tasks_inflight, -1); } } - SDL_UnlockMutex(task->asyncio->lock); + SDL_UnlockMutex(lock); // was this the result of a closing task? Finally destroy the asyncio. bool retval = true; @@ -298,9 +309,10 @@ void SDL_QuitAsyncIO(void) bool SDL_LoadFileAsync(const char *file, SDL_AsyncIOQueue *queue, void *userdata) { - if (!file) { + CHECK_PARAM(!file) { return SDL_InvalidParamError("file"); - } else if (!queue) { + } + CHECK_PARAM(!queue) { return SDL_InvalidParamError("queue"); } diff --git a/libs/SDL3/src/io/SDL_asyncio_c.h b/libs/SDL3/src/io/SDL_asyncio_c.h index 861ba1a..0a8ca78 100644 --- a/libs/SDL3/src/io/SDL_asyncio_c.h +++ b/libs/SDL3/src/io/SDL_asyncio_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/io/SDL_iostream.c b/libs/SDL3/src/io/SDL_iostream.c index 989f3b9..9639a66 100644 --- a/libs/SDL3/src/io/SDL_iostream.c +++ b/libs/SDL3/src/io/SDL_iostream.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -94,10 +94,12 @@ static HANDLE SDLCALL windows_file_open(const char *filename, const char *mode) // "r" = reading, file must exist // "w" = writing, truncate existing, file may not exist + // "wx"= writing, file must not exist // "r+"= reading or writing, file must exist // "a" = writing, append file may not exist // "a+"= append + read, file may not exist // "w+" = read, write, truncate. file may not exist + // "w+x"= read, write, file must not exist must_exist = (SDL_strchr(mode, 'r') != NULL) ? OPEN_EXISTING : 0; truncate = (SDL_strchr(mode, 'w') != NULL) ? CREATE_ALWAYS : 0; @@ -105,6 +107,10 @@ static HANDLE SDLCALL windows_file_open(const char *filename, const char *mode) a_mode = (SDL_strchr(mode, 'a') != NULL) ? OPEN_ALWAYS : 0; w_right = (a_mode || SDL_strchr(mode, '+') || truncate) ? GENERIC_WRITE : 0; + if (truncate && (SDL_strchr(mode, 'x') != NULL)) { + truncate = CREATE_NEW; + } + if (!r_right && !w_right) { return INVALID_HANDLE_VALUE; // inconsistent mode } @@ -219,16 +225,19 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size, DWORD error = GetLastError(); switch (error) { case ERROR_BROKEN_PIPE: - case ERROR_HANDLE_EOF: + *status = SDL_IO_STATUS_EOF; break; case ERROR_NO_DATA: *status = SDL_IO_STATUS_NOT_READY; break; default: + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error reading from datastream"); break; } - return 0; + return 0; // !!! FIXME: this should return the bytes read from any readahead we finished out before this (the `iodata->left > 0` code above). In that case, fail on the next read. + } else if (bytes == 0) { + *status = SDL_IO_STATUS_EOF; } read_ahead = SDL_min(total_need, bytes); SDL_memcpy(ptr, iodata->data, read_ahead); @@ -240,16 +249,19 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size, DWORD error = GetLastError(); switch (error) { case ERROR_BROKEN_PIPE: - case ERROR_HANDLE_EOF: + *status = SDL_IO_STATUS_EOF; break; case ERROR_NO_DATA: *status = SDL_IO_STATUS_NOT_READY; break; default: + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error reading from datastream"); break; } - return 0; + return 0; // !!! FIXME: this should return the bytes read from any readahead we finished out before this (the `iodata->left > 0` code above). In that case, fail on the next read. + } else if (bytes == 0) { + *status = SDL_IO_STATUS_EOF; } total_read += bytes; } @@ -263,6 +275,7 @@ static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t if (iodata->left) { if (!SetFilePointer(iodata->h, -(LONG)iodata->left, NULL, FILE_CURRENT)) { + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error seeking in datastream"); return 0; } @@ -274,16 +287,17 @@ static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t LARGE_INTEGER windowsoffset; windowsoffset.QuadPart = 0; if (!SetFilePointerEx(iodata->h, windowsoffset, &windowsoffset, FILE_END)) { + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error seeking in datastream"); return 0; } } if (!WriteFile(iodata->h, ptr, (DWORD)size, &bytes, NULL)) { + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error writing to datastream"); return 0; - } - if (bytes == 0 && size > 0) { + } else if (bytes == 0 && size > 0) { *status = SDL_IO_STATUS_NOT_READY; } return bytes; @@ -415,15 +429,36 @@ static size_t SDLCALL fd_read(void *userdata, void *ptr, size_t size, SDL_IOStat ssize_t bytes; do { bytes = read(iodata->fd, ptr, size); - } while (bytes < 0 && errno == EINTR); + } while ((bytes < 0) && (errno == EINTR)); - if (bytes < 0) { + ssize_t result = bytes; + if ((bytes > 0) && (bytes < size)) { // was it a short read, EOF, or error? + // try to read the difference, so we can rule out a short read. + do { + result = read(iodata->fd, ((Uint8 *) ptr) + bytes, size - bytes); + } while ((result < 0) && (errno == EINTR)); + + if (result > 0) { + bytes += result; + result = bytes; + SDL_assert(bytes <= size); + } + } + + if (result < 0) { if (errno == EAGAIN) { *status = SDL_IO_STATUS_NOT_READY; } else { + *status = SDL_IO_STATUS_ERROR; SDL_SetError("Error reading from datastream: %s", strerror(errno)); } - bytes = 0; + if (bytes < 0) { + bytes = 0; + } + } else if (result == 0) { + *status = SDL_IO_STATUS_EOF; + } else if (result < size) { + *status = SDL_IO_STATUS_NOT_READY; } return (size_t)bytes; } @@ -434,16 +469,36 @@ static size_t SDLCALL fd_write(void *userdata, const void *ptr, size_t size, SDL ssize_t bytes; do { bytes = write(iodata->fd, ptr, size); - } while (bytes < 0 && errno == EINTR); + } while ((bytes < 0) && (errno == EINTR)); - if (bytes < 0) { + ssize_t result = bytes; + if ((bytes > 0) && (bytes < size)) { // was it a short write, or error? + // try to write the difference, so we can rule out a short read. + do { + result = write(iodata->fd, ((Uint8 *) ptr) + bytes, size - bytes); + } while ((result < 0) && (errno == EINTR)); + + if (result > 0) { + bytes += result; + result = bytes; + SDL_assert(bytes <= size); + } + } + + if (result < 0) { if (errno == EAGAIN) { *status = SDL_IO_STATUS_NOT_READY; } else { + *status = SDL_IO_STATUS_ERROR; SDL_SetError("Error writing to datastream: %s", strerror(errno)); } - bytes = 0; + if (bytes < 0) { + bytes = 0; + } + } else if (result < size) { + *status = SDL_IO_STATUS_NOT_READY; } + return (size_t)bytes; } @@ -455,7 +510,8 @@ static bool SDLCALL fd_flush(void *userdata, SDL_IOStatus *status) result = SDL_fdatasync(iodata->fd); } while (result < 0 && errno == EINTR); - if (result < 0) { + // We get EINVAL when flushing a pipe, just make that a no-op + if (result < 0 && errno != EINVAL) { return SDL_SetError("Error flushing datastream: %s", strerror(errno)); } return true; @@ -606,12 +662,18 @@ static size_t SDLCALL stdio_read(void *userdata, void *ptr, size_t size, SDL_IOS { IOStreamStdioData *iodata = (IOStreamStdioData *) userdata; const size_t bytes = fread(ptr, 1, size, iodata->fp); - if (bytes == 0 && ferror(iodata->fp)) { - if (errno == EAGAIN) { - *status = SDL_IO_STATUS_NOT_READY; - clearerr(iodata->fp); + if (bytes < size) { + if (ferror(iodata->fp)) { + if (errno == EAGAIN) { + *status = SDL_IO_STATUS_NOT_READY; + clearerr(iodata->fp); + } else { + *status = SDL_IO_STATUS_ERROR; + SDL_SetError("Error reading from datastream: %s", strerror(errno)); + } } else { - SDL_SetError("Error reading from datastream: %s", strerror(errno)); + SDL_assert(feof(iodata->fp)); + *status = SDL_IO_STATUS_EOF; } } return bytes; @@ -626,6 +688,7 @@ static size_t SDLCALL stdio_write(void *userdata, const void *ptr, size_t size, *status = SDL_IO_STATUS_NOT_READY; clearerr(iodata->fp); } else { + *status = SDL_IO_STATUS_ERROR; SDL_SetError("Error writing to datastream: %s", strerror(errno)); } } @@ -716,6 +779,7 @@ typedef struct IOStreamMemData Uint8 *base; Uint8 *here; Uint8 *stop; + SDL_PropertiesID props; } IOStreamMemData; static Sint64 SDLCALL mem_size(void *userdata) @@ -768,17 +832,31 @@ static size_t mem_io(void *userdata, void *dst, const void *src, size_t size) static size_t SDLCALL mem_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) { IOStreamMemData *iodata = (IOStreamMemData *) userdata; - return mem_io(userdata, ptr, iodata->here, size); + const size_t retval = mem_io(userdata, ptr, iodata->here, size); + if ((retval < size) && (iodata->stop == iodata->here)) { + *status = SDL_IO_STATUS_EOF; + } + return retval; } static size_t SDLCALL mem_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status) { IOStreamMemData *iodata = (IOStreamMemData *) userdata; - return mem_io(userdata, iodata->here, ptr, size); + const size_t retval = mem_io(userdata, iodata->here, ptr, size); + if ((retval < size) && (iodata->stop == iodata->here)) { + SDL_SetError("Memory buffer is full"); + *status = SDL_IO_STATUS_ERROR; + } + return retval; } static bool SDLCALL mem_close(void *userdata) { + IOStreamMemData *iodata = (IOStreamMemData *) userdata; + SDL_free_func free_func = SDL_GetPointerProperty(iodata->props, SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC_POINTER, NULL); + if (free_func) { + free_func(iodata->base); + } SDL_free(userdata); return true; } @@ -803,11 +881,11 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) { SDL_IOStream *iostr = NULL; - if (!file || !*file) { + CHECK_PARAM(!file || !*file) { SDL_InvalidParamError("file"); return NULL; } - if (!mode || !*mode) { + CHECK_PARAM(!mode || !*mode) { SDL_InvalidParamError("mode"); return NULL; } @@ -885,6 +963,43 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) } } +#elif defined(SDL_PLATFORM_IOS) + + // Try to open the file on the filesystem first + FILE *fp = NULL; + if (*file == '/') { + fp = fopen(file, mode); + } else { + // We can't write to the current directory, so use a writable path + char *base = SDL_GetPrefPath("", ""); + if (!base) { + return NULL; + } + + char *path = NULL; + SDL_asprintf(&path, "%s%s", base, file); + SDL_free(base); + if (!path) { + return NULL; + } + + fp = fopen(path, mode); + SDL_free(path); + + if (!fp) { + fp = fopen(file, mode); + } + } + + if (!fp) { + SDL_SetError("Couldn't open %s: %s", file, strerror(errno)); + } else if (!IsRegularFileOrPipe(fp)) { + fclose(fp); + SDL_SetError("%s is not a regular file or pipe", file); + } else { + iostr = SDL_IOFromFP(fp, true); + } + #elif defined(SDL_PLATFORM_WINDOWS) HANDLE handle = windows_file_open(file, mode); if (handle != INVALID_HANDLE_VALUE) { @@ -903,7 +1018,6 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) SDL_SetError("Couldn't open %s: %s", file, strerror(errno)); } else if (!IsRegularFileOrPipe(fp)) { fclose(fp); - fp = NULL; SDL_SetError("%s is not a regular file or pipe", file); } else { iostr = SDL_IOFromFP(fp, true); @@ -919,12 +1033,9 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) SDL_IOStream *SDL_IOFromMem(void *mem, size_t size) { - if (!mem) { + CHECK_PARAM(size && !mem) { SDL_InvalidParamError("mem"); return NULL; - } else if (!size) { - SDL_InvalidParamError("size"); - return NULL; } IOStreamMemData *iodata = (IOStreamMemData *) SDL_calloc(1, sizeof (*iodata)); @@ -950,6 +1061,7 @@ SDL_IOStream *SDL_IOFromMem(void *mem, size_t size) } else { const SDL_PropertiesID props = SDL_GetIOProperties(iostr); if (props) { + iodata->props = props; SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_MEMORY_POINTER, mem); SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER, size); } @@ -959,12 +1071,9 @@ SDL_IOStream *SDL_IOFromMem(void *mem, size_t size) SDL_IOStream *SDL_IOFromConstMem(const void *mem, size_t size) { - if (!mem) { + CHECK_PARAM(size && !mem) { SDL_InvalidParamError("mem"); return NULL; - } else if (!size) { - SDL_InvalidParamError("size"); - return NULL; } IOStreamMemData *iodata = (IOStreamMemData *) SDL_calloc(1, sizeof (*iodata)); @@ -990,6 +1099,7 @@ SDL_IOStream *SDL_IOFromConstMem(const void *mem, size_t size) } else { const SDL_PropertiesID props = SDL_GetIOProperties(iostr); if (props) { + iodata->props = props; SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_MEMORY_POINTER, (void *)mem); SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER, size); } @@ -1019,7 +1129,11 @@ static Sint64 SDLCALL dynamic_mem_seek(void *userdata, Sint64 offset, SDL_IOWhen static size_t SDLCALL dynamic_mem_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) { IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; - return mem_io(&iodata->data, ptr, iodata->data.here, size); + const size_t retval = mem_io(&iodata->data, ptr, iodata->data.here, size); + if ((retval < size) && (iodata->data.stop == iodata->data.here)) { + *status = SDL_IO_STATUS_EOF; + } + return retval; } static bool dynamic_mem_realloc(IOStreamDynamicMemData *iodata, size_t size) @@ -1052,21 +1166,22 @@ static size_t SDLCALL dynamic_mem_write(void *userdata, const void *ptr, size_t if (size > (size_t)(iodata->data.stop - iodata->data.here)) { if (size > (size_t)(iodata->end - iodata->data.here)) { if (!dynamic_mem_realloc(iodata, size)) { + *status = SDL_IO_STATUS_ERROR; return 0; } } iodata->data.stop = iodata->data.here + size; } - return mem_io(&iodata->data, iodata->data.here, ptr, size); + const size_t retval = mem_io(&iodata->data, iodata->data.here, ptr, size); + SDL_assert(retval == size); // we should have allocated enough to cover this! + return retval; } static bool SDLCALL dynamic_mem_close(void *userdata) { const IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; void *mem = SDL_GetPointerProperty(SDL_GetIOProperties(iodata->stream), SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, NULL); - if (mem) { - SDL_free(mem); - } + SDL_free(mem); SDL_free(userdata); return true; } @@ -1097,7 +1212,7 @@ SDL_IOStream *SDL_IOFromDynamicMem(void) SDL_IOStatus SDL_GetIOStatus(SDL_IOStream *context) { - if (!context) { + CHECK_PARAM(!context) { SDL_InvalidParamError("context"); return SDL_IO_STATUS_ERROR; } @@ -1106,11 +1221,11 @@ SDL_IOStatus SDL_GetIOStatus(SDL_IOStream *context) SDL_IOStream *SDL_OpenIO(const SDL_IOStreamInterface *iface, void *userdata) { - if (!iface) { + CHECK_PARAM(!iface) { SDL_InvalidParamError("iface"); return NULL; } - if (iface->version < sizeof(*iface)) { + CHECK_PARAM(iface->version < sizeof(*iface)) { // Update this to handle older versions of this interface SDL_SetError("Invalid interface, should be initialized with SDL_INIT_INTERFACE()"); return NULL; @@ -1146,7 +1261,7 @@ void *SDL_LoadFile_IO(SDL_IOStream *src, size_t *datasize, bool closeio) char *data = NULL, *newdata; bool loading_chunks = false; - if (!src) { + CHECK_PARAM(!src) { SDL_InvalidParamError("src"); goto done; } @@ -1227,12 +1342,12 @@ bool SDL_SaveFile_IO(SDL_IOStream *src, const void *data, size_t datasize, bool size_t size_total = 0; bool success = true; - if (!src) { + CHECK_PARAM(!src) { SDL_InvalidParamError("src"); goto done; } - if (!data && datasize > 0) { + CHECK_PARAM(!data && datasize > 0) { SDL_InvalidParamError("data"); goto done; } @@ -1275,7 +1390,7 @@ bool SDL_SaveFile(const char *file, const void *data, size_t datasize) SDL_PropertiesID SDL_GetIOProperties(SDL_IOStream *context) { - if (!context) { + CHECK_PARAM(!context) { SDL_InvalidParamError("context"); return 0; } @@ -1288,9 +1403,10 @@ SDL_PropertiesID SDL_GetIOProperties(SDL_IOStream *context) Sint64 SDL_GetIOSize(SDL_IOStream *context) { - if (!context) { + CHECK_PARAM(!context) { return SDL_InvalidParamError("context"); } + if (!context->iface.size) { Sint64 pos, size; @@ -1308,10 +1424,12 @@ Sint64 SDL_GetIOSize(SDL_IOStream *context) Sint64 SDL_SeekIO(SDL_IOStream *context, Sint64 offset, SDL_IOWhence whence) { - if (!context) { + CHECK_PARAM(!context) { SDL_InvalidParamError("context"); return -1; - } else if (!context->iface.seek) { + } + + if (!context->iface.seek) { SDL_Unsupported(); return -1; } @@ -1325,60 +1443,48 @@ Sint64 SDL_TellIO(SDL_IOStream *context) size_t SDL_ReadIO(SDL_IOStream *context, void *ptr, size_t size) { - size_t bytes; - - if (!context) { + CHECK_PARAM(!context) { SDL_InvalidParamError("context"); return 0; - } else if (!context->iface.read) { + } + + if (!context->iface.read) { context->status = SDL_IO_STATUS_WRITEONLY; SDL_Unsupported(); return 0; } + if (size == 0) { + return 0; // context->status doesn't change for this. + } + context->status = SDL_IO_STATUS_READY; SDL_ClearError(); - if (size == 0) { - return 0; - } - - bytes = context->iface.read(context->userdata, ptr, size, &context->status); - if (bytes == 0 && context->status == SDL_IO_STATUS_READY) { - if (*SDL_GetError()) { - context->status = SDL_IO_STATUS_ERROR; - } else { - context->status = SDL_IO_STATUS_EOF; - } - } - return bytes; + return context->iface.read(context->userdata, ptr, size, &context->status); } size_t SDL_WriteIO(SDL_IOStream *context, const void *ptr, size_t size) { - size_t bytes; - - if (!context) { + CHECK_PARAM(!context) { SDL_InvalidParamError("context"); return 0; - } else if (!context->iface.write) { + } + + if (!context->iface.write) { context->status = SDL_IO_STATUS_READONLY; SDL_Unsupported(); return 0; } + if (size == 0) { + return 0; // context->status doesn't change for this. + } + context->status = SDL_IO_STATUS_READY; SDL_ClearError(); - if (size == 0) { - return 0; - } - - bytes = context->iface.write(context->userdata, ptr, size, &context->status); - if ((bytes == 0) && (context->status == SDL_IO_STATUS_READY)) { - context->status = SDL_IO_STATUS_ERROR; - } - return bytes; + return context->iface.write(context->userdata, ptr, size, &context->status); } size_t SDL_IOprintf(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) @@ -1420,7 +1526,7 @@ bool SDL_FlushIO(SDL_IOStream *context) { bool result = true; - if (!context) { + CHECK_PARAM(!context) { return SDL_InvalidParamError("context"); } diff --git a/libs/SDL3/src/io/SDL_iostream_c.h b/libs/SDL3/src/io/SDL_iostream_c.h index c04fe20..fadb98b 100644 --- a/libs/SDL3/src/io/SDL_iostream_c.h +++ b/libs/SDL3/src/io/SDL_iostream_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/io/SDL_sysasyncio.h b/libs/SDL3/src/io/SDL_sysasyncio.h index fbe41d1..b0ca21b 100644 --- a/libs/SDL3/src/io/SDL_sysasyncio.h +++ b/libs/SDL3/src/io/SDL_sysasyncio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -124,6 +124,7 @@ struct SDL_AsyncIO SDL_AsyncIOTask tasks; SDL_AsyncIOTask *closing; // The close task, which isn't queued until all pending work for this file is done. bool oneshot; // true if this is a SDL_LoadFileAsync open. + bool readonly; // true if this file is opened read-only. }; // This is implemented for various platforms; param validation is done before calling this. Open file, fill in iface and userdata. diff --git a/libs/SDL3/src/io/generic/SDL_asyncio_generic.c b/libs/SDL3/src/io/generic/SDL_asyncio_generic.c index 4c2a562..76ece03 100644 --- a/libs/SDL3/src/io/generic/SDL_asyncio_generic.c +++ b/libs/SDL3/src/io/generic/SDL_asyncio_generic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/io/io_uring/SDL_asyncio_liburing.c b/libs/SDL3/src/io/io_uring/SDL_asyncio_liburing.c index 07e8c24..a15218a 100644 --- a/libs/SDL3/src/io/io_uring/SDL_asyncio_liburing.c +++ b/libs/SDL3/src/io/io_uring/SDL_asyncio_liburing.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -44,9 +44,17 @@ static bool (*AsyncIOFromFile)(const char *file, const char *mode, SDL_AsyncIO * // we never link directly to liburing. // (this says "-ffi" which sounds like a scripting language binding thing, but the non-ffi version // is static-inline code we can't lookup with dlsym. This is by design.) -static const char *liburing_library = "liburing-ffi.so.2"; +#define SDL_DRIVER_LIBURING_DYNAMIC "liburing-ffi.so.2" +static const char *liburing_library = SDL_DRIVER_LIBURING_DYNAMIC; static void *liburing_handle = NULL; +SDL_ELF_NOTE_DLOPEN( + "io-io_uring", + "Support for async IO through liburing", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_DRIVER_LIBURING_DYNAMIC +) + #define SDL_LIBURING_FUNCS \ SDL_LIBURING_FUNC(int, io_uring_queue_init, (unsigned entries, struct io_uring *ring, unsigned flags)) \ SDL_LIBURING_FUNC(struct io_uring_probe *,io_uring_get_probe,(void)) \ @@ -244,7 +252,7 @@ static SDL_AsyncIOTask *ProcessCQE(LibUringAsyncIOQueueData *queuedata, struct i } } - if ((task->type == SDL_ASYNCIO_TASK_CLOSE) && task->flush) { + if (task && (task->type == SDL_ASYNCIO_TASK_CLOSE) && task->flush) { task->flush = false; task = NULL; // don't return this one, it's a linked task, so it'll arrive in a later CQE. } @@ -512,10 +520,12 @@ static void MaybeInitializeLibUring(void) { if (SDL_ShouldInit(&liburing_init)) { if (LoadLibUring()) { + SDL_DebugLogBackend("asyncio", "liburing"); CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_liburing; QuitAsyncIO = SDL_SYS_QuitAsyncIO_liburing; AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_liburing; } else { // can't use liburing? Use the "generic" threadpool implementation instead. + SDL_DebugLogBackend("asyncio", "generic"); CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_Generic; QuitAsyncIO = SDL_SYS_QuitAsyncIO_Generic; AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_Generic; diff --git a/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.c b/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.c index b42f539..55e6067 100644 --- a/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.c +++ b/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.h b/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.h index b931390..43f79d3 100644 --- a/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.h +++ b/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/io/windows/SDL_asyncio_windows_ioring.c b/libs/SDL3/src/io/windows/SDL_asyncio_windows_ioring.c index 2d38efc..81a5d84 100644 --- a/libs/SDL3/src/io/windows/SDL_asyncio_windows_ioring.c +++ b/libs/SDL3/src/io/windows/SDL_asyncio_windows_ioring.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -50,7 +50,7 @@ static void *ioring_handle = NULL; SDL_IORING_FUNC(BOOL, IsIoRingOpSupported, (HIORING ioRing, IORING_OP_CODE op)) \ SDL_IORING_FUNC(HRESULT, CreateIoRing, (IORING_VERSION ioringVersion, IORING_CREATE_FLAGS flags, UINT32 submissionQueueSize, UINT32 completionQueueSize, HIORING* h)) \ SDL_IORING_FUNC(HRESULT, GetIoRingInfo, (HIORING ioRing, IORING_INFO* info)) \ - SDL_IORING_FUNC(HRESULT, SubmitIoRing, (HIORING ioRing, UINT32 waitOperations, UINT32 milliseconds, UINT32* submittedEntries)) \ + SDL_IORING_FUNC(HRESULT, SubmitIoRing, (HIORING ioRing, UINT32 waitOperations, UINT32 milliseconds, UINT32 * submittedEntries)) \ SDL_IORING_FUNC(HRESULT, CloseIoRing, (HIORING ioRing)) \ SDL_IORING_FUNC(HRESULT, PopIoRingCompletion, (HIORING ioRing, IORING_CQE* cqe)) \ SDL_IORING_FUNC(HRESULT, SetIoRingCompletionEvent, (HIORING ioRing, HANDLE hEvent)) \ @@ -200,8 +200,14 @@ static SDL_AsyncIOTask *ProcessCQE(WinIoRingAsyncIOQueueData *queuedata, IORING_ task = NULL; // it already finished or was too far along to cancel, so we'll pick up the actual results later. } } else if (FAILED(cqe->ResultCode)) { - task->result = SDL_ASYNCIO_FAILURE; - // !!! FIXME: fill in task->error. + if ((task->type == SDL_ASYNCIO_TASK_CLOSE) && (cqe->ResultCode == E_ACCESSDENIED) && task->asyncio->readonly) { + // we push all close requests through as flushes, as there is currently no async close operation and flushing writes to disk is the time-consuming part. + // However, flushing a read-only handle generates an error, so we catch this specific situation and ignore it. This approach still makes the task go + // through the IoRing so we can handle this all in the same place otherwise. The actual close happens below. + } else { + task->result = SDL_ASYNCIO_FAILURE; + // !!! FIXME: fill in task->error. + } } else { if ((task->type == SDL_ASYNCIO_TASK_WRITE) && (((Uint64) cqe->Information) < task->requested_size)) { task->result = SDL_ASYNCIO_FAILURE; // it's always a failure on short writes. @@ -511,10 +517,12 @@ static void MaybeInitializeWinIoRing(void) { if (SDL_ShouldInit(&ioring_init)) { if (LoadWinIoRing()) { + SDL_DebugLogBackend("asyncio", "ioring"); CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_ioring; QuitAsyncIO = SDL_SYS_QuitAsyncIO_ioring; AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_ioring; } else { // can't use ioring? Use the "generic" threadpool implementation instead. + SDL_DebugLogBackend("asyncio", "generic"); CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_Generic; QuitAsyncIO = SDL_SYS_QuitAsyncIO_Generic; AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_Generic; diff --git a/libs/SDL3/src/joystick/SDL_gamepad.c b/libs/SDL3/src/joystick/SDL_gamepad.c index 5bb2808..e052690 100644 --- a/libs/SDL3/src/joystick/SDL_gamepad.c +++ b/libs/SDL3/src/joystick/SDL_gamepad.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -29,13 +29,17 @@ #include "SDL_gamepad_db.h" #include "controller_type.h" #include "usb_ids.h" +#include "hidapi/SDL_hidapi_flydigi.h" #include "hidapi/SDL_hidapi_nintendo.h" +#include "hidapi/SDL_hidapi_sinput.h" #include "../events/SDL_events_c.h" +#include "../SDL_hints_c.h" - -#ifdef SDL_PLATFORM_ANDROID +#ifdef SDL_PLATFORM_WIN32 +#include "../core/windows/SDL_windows.h" #endif + // Many gamepads turn the center button into an instantaneous button press #define SDL_MINIMUM_GUIDE_BUTTON_DELAY_MS 250 @@ -54,14 +58,32 @@ #define SDL_GAMEPAD_SDKLE_FIELD "sdk<=:" #define SDL_GAMEPAD_SDKLE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_SDKLE_FIELD) +// Helper function to add button mapping +#define SDL_ADD_BUTTON_MAPPING(sdl_name, button_id, maxlen) \ + do { \ + char temp[32]; \ + (void)SDL_snprintf(temp, sizeof(temp), "%s:b%d,", sdl_name, button_id); \ + SDL_strlcat(mapping_string, temp, maxlen); \ + } while (0) + +// Helper function to add axis mapping +#define SDL_ADD_AXIS_MAPPING(sdl_name, axis_id, maxlen) \ + do { \ + char temp[32]; \ + (void)SDL_snprintf(temp, sizeof(temp), "%s:a%d,", sdl_name, axis_id); \ + SDL_strlcat(mapping_string, temp, maxlen); \ + } while (0) + static bool SDL_gamepads_initialized; static SDL_Gamepad *SDL_gamepads SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static SDL_HashTable *SDL_gamepad_names SDL_GUARDED_BY(SDL_joystick_lock) = NULL; // The face button style of a gamepad typedef enum { SDL_GAMEPAD_FACE_STYLE_UNKNOWN, SDL_GAMEPAD_FACE_STYLE_ABXY, + SDL_GAMEPAD_FACE_STYLE_AXBY, SDL_GAMEPAD_FACE_STYLE_BAYX, SDL_GAMEPAD_FACE_STYLE_SONY, } SDL_GamepadFaceStyle; @@ -128,12 +150,12 @@ struct SDL_Gamepad #undef _guarded -#define CHECK_GAMEPAD_MAGIC(gamepad, result) \ - if (!SDL_ObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD) || \ - !SDL_IsJoystickValid(gamepad->joystick)) { \ - SDL_InvalidParamError("gamepad"); \ - SDL_UnlockJoysticks(); \ - return result; \ +#define CHECK_GAMEPAD_MAGIC(gamepad, result) \ + CHECK_PARAM(!SDL_ObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD) || \ + !SDL_IsJoystickValid(gamepad->joystick)) { \ + SDL_InvalidParamError("gamepad"); \ + SDL_UnlockJoysticks(); \ + return result; \ } static SDL_vidpid_list SDL_allowed_gamepads = { @@ -149,6 +171,51 @@ static SDL_vidpid_list SDL_ignored_gamepads = { false }; +/* + List of words in gamepad names that indicate that the gamepad should not be detected. + See also `initial_blacklist_devices` in SDL_joystick.c +*/ + +enum SDL_GamepadBlacklistWordsPosition { + GAMEPAD_BLACKLIST_BEGIN, + GAMEPAD_BLACKLIST_END, + GAMEPAD_BLACKLIST_ANYWHERE, +}; + +struct SDL_GamepadBlacklistWords { + const char* str; + enum SDL_GamepadBlacklistWordsPosition pos; +}; + +static const struct SDL_GamepadBlacklistWords SDL_gamepad_blacklist_words[] = { +#ifdef SDL_PLATFORM_LINUX + {" Motion Sensors", GAMEPAD_BLACKLIST_END}, // Don't treat the PS3 and PS4 motion controls as a separate gamepad + {" IMU", GAMEPAD_BLACKLIST_END}, // Don't treat the Nintendo IMU as a separate gamepad + {" Touchpad", GAMEPAD_BLACKLIST_END}, // "Sony Interactive Entertainment DualSense Wireless Controller Touchpad" + + // Don't treat the Wii extension controls as a separate gamepad + {" Accelerometer", GAMEPAD_BLACKLIST_END}, + {" IR", GAMEPAD_BLACKLIST_END}, + {" Motion Plus", GAMEPAD_BLACKLIST_END}, + {" Nunchuk", GAMEPAD_BLACKLIST_END}, +#endif + + // The Google Pixel fingerprint sensor, as well as other fingerprint sensors, reports itself as a joystick + {"uinput-", GAMEPAD_BLACKLIST_BEGIN}, + + {"Synaptics ", GAMEPAD_BLACKLIST_ANYWHERE}, // "Synaptics TM2768-001", "SynPS/2 Synaptics TouchPad" + {"Trackpad", GAMEPAD_BLACKLIST_ANYWHERE}, + {"Clickpad", GAMEPAD_BLACKLIST_ANYWHERE}, + // "PG-90215 Keyboard", "Usb Keyboard Usb Keyboard Consumer Control", "Framework Laptop 16 Keyboard Module - ISO System Control" + {" Keyboard", GAMEPAD_BLACKLIST_ANYWHERE}, + {" Laptop ", GAMEPAD_BLACKLIST_ANYWHERE}, // "Framework Laptop 16 Numpad Module System Control" + {"Mouse ", GAMEPAD_BLACKLIST_BEGIN}, // "Mouse passthrough" + {" Pen", GAMEPAD_BLACKLIST_END}, // "Wacom One by Wacom S Pen" + {" Finger", GAMEPAD_BLACKLIST_END}, // "Wacom HID 495F Finger" + {" LED ", GAMEPAD_BLACKLIST_ANYWHERE}, // "ASRock LED Controller" + {" Thelio ", GAMEPAD_BLACKLIST_ANYWHERE}, // "System76 Thelio Io 2" +}; + static GamepadMapping_t *SDL_PrivateAddMappingForGUID(SDL_GUID jGUID, const char *mappingString, bool *existing, SDL_GamepadMappingPriority priority); static void SDL_PrivateLoadButtonMapping(SDL_Gamepad *gamepad, GamepadMapping_t *pGamepadMapping); static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id, bool create_mapping); @@ -307,6 +374,47 @@ static void RecenterGamepad(SDL_Gamepad *gamepad) } } +static const char *SDL_UpdateGamepadNameForID(SDL_JoystickID instance_id) +{ + const char *current_name = NULL; + + SDL_AssertJoysticksLocked(); + + GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, true); + if (mapping) { + if (SDL_strcmp(mapping->name, "*") == 0) { + current_name = SDL_GetJoystickNameForID(instance_id); + } else { + current_name = mapping->name; + } + } + + if (!SDL_gamepad_names) { + return SDL_GetPersistentString(current_name); + } + + char *name = NULL; + bool found = SDL_FindInHashTable(SDL_gamepad_names, (const void *)(uintptr_t)instance_id, (const void **)&name); + if (!current_name) { + if (!found) { + SDL_SetError("Gamepad %" SDL_PRIu32 " not found", instance_id); + return NULL; + } + if (!name) { + // SDL_strdup() failed during insert + SDL_OutOfMemory(); + return NULL; + } + return name; + } + + if (!name || SDL_strcmp(name, current_name) != 0) { + name = SDL_strdup(current_name); + SDL_InsertIntoHashTable(SDL_gamepad_names, (const void *)(uintptr_t)instance_id, name, true); + } + return name; +} + void SDL_PrivateGamepadAdded(SDL_JoystickID instance_id) { SDL_Event event; @@ -315,6 +423,8 @@ void SDL_PrivateGamepadAdded(SDL_JoystickID instance_id) return; } + SDL_UpdateGamepadNameForID(instance_id); + event.type = SDL_EVENT_GAMEPAD_ADDED; event.common.timestamp = 0; event.gdevice.which = instance_id; @@ -564,10 +674,14 @@ static void PopMappingChangeTracking(void) GamepadMapping_t *old_mapping = gamepad ? gamepad->mapping : tracker->joystick_mappings[i]; if (new_mapping && !old_mapping) { - SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)true, true); + if (s_gamepadInstanceIDs) { + SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)true, true); + } SDL_PrivateGamepadAdded(joystick); } else if (old_mapping && !new_mapping) { - SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)false, true); + if (s_gamepadInstanceIDs) { + 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) { @@ -687,6 +801,276 @@ static GamepadMapping_t *SDL_CreateMappingForAndroidGamepad(SDL_GUID guid) } #endif // SDL_PLATFORM_ANDROID +/* +* Helper function to apply SInput decoded styles to the mapping string +*/ +static inline void SDL_SInputStylesMapExtraction(SDL_SInputStyles_t* styles, char* mapping_string, size_t mapping_string_len) +{ + int current_button = 0; + int current_axis = 0; + int misc_buttons = 0; + int misc_button, misc_end; + bool digital_triggers = false; + bool dualstage_triggers = false; + int bumpers = 0; + bool left_stick = false; + bool right_stick = false; + int paddle_pairs = 0; + char misc_label[32]; + + // Determine how many misc buttons are used + switch (styles->misc_style) { + case SINPUT_MISCSTYLE_1: + misc_buttons = 1; + break; + + case SINPUT_MISCSTYLE_2: + misc_buttons = 2; + break; + + case SINPUT_MISCSTYLE_3: + misc_buttons = 3; + break; + + case SINPUT_MISCSTYLE_4: + misc_buttons = 4; + break; + + default: + break; + } + // The share button is reserved as misc1, additional buttons start at misc2 + misc_button = 2; + misc_end = misc_button + misc_buttons; + + // Analog joysticks (always come first in axis mapping) + switch (styles->analog_style) { + case SINPUT_ANALOGSTYLE_LEFTONLY: + SDL_ADD_AXIS_MAPPING("leftx", current_axis++, mapping_string_len); + SDL_ADD_AXIS_MAPPING("lefty", current_axis++, mapping_string_len); + left_stick = true; + break; + + case SINPUT_ANALOGSTYLE_LEFTRIGHT: + SDL_ADD_AXIS_MAPPING("leftx", current_axis++, mapping_string_len); + SDL_ADD_AXIS_MAPPING("lefty", current_axis++, mapping_string_len); + SDL_ADD_AXIS_MAPPING("rightx", current_axis++, mapping_string_len); + SDL_ADD_AXIS_MAPPING("righty", current_axis++, mapping_string_len); + left_stick = true; + right_stick = true; + break; + + case SINPUT_ANALOGSTYLE_RIGHTONLY: + SDL_ADD_AXIS_MAPPING("rightx", current_axis++, mapping_string_len); + SDL_ADD_AXIS_MAPPING("righty", current_axis++, mapping_string_len); + right_stick = true; + break; + + default: + break; + } + + // Bumpers + switch (styles->bumper_style) { + case SINPUT_BUMPERSTYLE_ONE: + bumpers = 1; + break; + + case SINPUT_BUMPERSTYLE_TWO: + bumpers = 2; + break; + + default: + break; + } + + // Analog triggers + switch (styles->trigger_style) { + // Analog triggers + case SINPUT_TRIGGERSTYLE_ANALOG: + SDL_ADD_AXIS_MAPPING("lefttrigger", current_axis++, mapping_string_len); + SDL_ADD_AXIS_MAPPING("righttrigger", current_axis++, mapping_string_len); + break; + + // Digital triggers + case SINPUT_TRIGGERSTYLE_DIGITAL: + digital_triggers = true; + break; + + // Analog triggers with digital press + case SINPUT_TRIGGERSTYLE_DUALSTAGE: + SDL_ADD_AXIS_MAPPING("lefttrigger", current_axis++, mapping_string_len); + SDL_ADD_AXIS_MAPPING("righttrigger", current_axis++, mapping_string_len); + dualstage_triggers = true; + break; + + default: + break; + } + + switch (styles->paddle_style) { + case SINPUT_PADDLESTYLE_TWO: + paddle_pairs = 1; + break; + + case SINPUT_PADDLESTYLE_FOUR: + paddle_pairs = 2; + break; + + default: + break; + } + + // Digital button mappings + // ABXY buttons (always applied as South, East, West, North) + SDL_ADD_BUTTON_MAPPING("a", current_button++, mapping_string_len); // South (typically A on Xbox, X on PlayStation) + SDL_ADD_BUTTON_MAPPING("b", current_button++, mapping_string_len); // East (typically B on Xbox, Circle on PlayStation) + SDL_ADD_BUTTON_MAPPING("x", current_button++, mapping_string_len); // West (typically X on Xbox, Square on PlayStation) + SDL_ADD_BUTTON_MAPPING("y", current_button++, mapping_string_len); // North (typically Y on Xbox, Triangle on PlayStation) + + // D-Pad (always applied) + SDL_strlcat(mapping_string, "dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,", mapping_string_len); + + // Left and Right stick buttons + if (left_stick) { + SDL_ADD_BUTTON_MAPPING("leftstick", current_button++, mapping_string_len); + } + if (right_stick) { + SDL_ADD_BUTTON_MAPPING("rightstick", current_button++, mapping_string_len); + } + + // Digital shoulder buttons (L/R Shoulder) + if (bumpers > 0) { + SDL_ADD_BUTTON_MAPPING("leftshoulder", current_button++, mapping_string_len); + } + if (bumpers > 1) { + SDL_ADD_BUTTON_MAPPING("rightshoulder", current_button++, mapping_string_len); + } + + // Digital trigger buttons (capability overrides analog) + if (digital_triggers) { + SDL_ADD_BUTTON_MAPPING("lefttrigger", current_button++, mapping_string_len); + SDL_ADD_BUTTON_MAPPING("righttrigger", current_button++, mapping_string_len); + } else if (dualstage_triggers) { + // Dual-stage trigger buttons are appended as MISC buttons + // By convention the trigger buttons are misc3 and misc4 for GameCube style controllers + if (misc_end < 3) { + misc_end = 3; + } + SDL_snprintf(misc_label, sizeof(misc_label), "misc%d", misc_end++); + SDL_ADD_BUTTON_MAPPING(misc_label, current_button++, mapping_string_len); + SDL_snprintf(misc_label, sizeof(misc_label), "misc%d", misc_end++); + SDL_ADD_BUTTON_MAPPING(misc_label, current_button++, mapping_string_len); + } + + // Paddle 1/2 + if (paddle_pairs > 0) { + // Paddle 2 is first for left/right order of SInput + SDL_ADD_BUTTON_MAPPING("paddle2", current_button++, mapping_string_len); + SDL_ADD_BUTTON_MAPPING("paddle1", current_button++, mapping_string_len); + } + + // Start/Plus + SDL_ADD_BUTTON_MAPPING("start", current_button++, mapping_string_len); + + // Back/Minus, Guide/Home, Share/Capture + switch (styles->meta_style) { + case SINPUT_METASTYLE_BACK: + SDL_ADD_BUTTON_MAPPING("back", current_button++, mapping_string_len); + break; + + case SINPUT_METASTYLE_BACKGUIDE: + SDL_ADD_BUTTON_MAPPING("back", current_button++, mapping_string_len); + SDL_ADD_BUTTON_MAPPING("guide", current_button++, mapping_string_len); + break; + + case SINPUT_METASTYLE_BACKGUIDESHARE: + SDL_ADD_BUTTON_MAPPING("back", current_button++, mapping_string_len); + SDL_ADD_BUTTON_MAPPING("guide", current_button++, mapping_string_len); + SDL_ADD_BUTTON_MAPPING("misc1", current_button++, mapping_string_len); + break; + + default: + break; + } + + // Paddle 3/4 + if (paddle_pairs > 1) { + // Paddle 4 is first for left/right order of SInput + SDL_ADD_BUTTON_MAPPING("paddle4", current_button++, mapping_string_len); + SDL_ADD_BUTTON_MAPPING("paddle3", current_button++, mapping_string_len); + } + + // Touchpad buttons + switch (styles->touch_style) { + case SINPUT_TOUCHSTYLE_SINGLE: + SDL_ADD_BUTTON_MAPPING("touchpad", current_button++, mapping_string_len); + break; + + case SINPUT_TOUCHSTYLE_DOUBLE: + SDL_ADD_BUTTON_MAPPING("touchpad", current_button++, mapping_string_len); + // Add the second touchpad button at the end of the misc buttons + SDL_snprintf(misc_label, sizeof(misc_label), "misc%d", misc_end++); + SDL_ADD_BUTTON_MAPPING(misc_label, current_button++, mapping_string_len); + break; + + default: + break; + } + + for (int i = 0; i < misc_buttons; ++i) { + SDL_snprintf(misc_label, sizeof(misc_label), "misc%d", misc_button++); + SDL_ADD_BUTTON_MAPPING(misc_label, current_button++, mapping_string_len); + } +} + +/* +* Helper function to decode SInput features information packed into version +*/ +static void SDL_CreateMappingStringForSInputGamepad(Uint16 vendor, Uint16 product, Uint8 sub_product, Uint16 version, Uint8 face_style, char* mapping_string, size_t mapping_string_len) +{ + SDL_SInputStyles_t decoded = { 0 }; + + switch (face_style) { + default: + SDL_strlcat(mapping_string, "face:abxy,", mapping_string_len); + break; + case 2: + SDL_strlcat(mapping_string, "face:axby,", mapping_string_len); + break; + case 3: + SDL_strlcat(mapping_string, "face:bayx,", mapping_string_len); + break; + case 4: + SDL_strlcat(mapping_string, "face:sony,", mapping_string_len); + break; + } + + // Interpret the mapping string + // dynamically based on the feature responses + decoded.misc_style = (SInput_MiscStyleType)(version % SINPUT_MISCSTYLE_MAX); + version /= SINPUT_MISCSTYLE_MAX; + + decoded.touch_style = (SInput_TouchStyleType)(version % SINPUT_TOUCHSTYLE_MAX); + version /= SINPUT_TOUCHSTYLE_MAX; + + decoded.meta_style = (SInput_MetaStyleType)(version % SINPUT_METASTYLE_MAX); + version /= SINPUT_METASTYLE_MAX; + + decoded.paddle_style = (SInput_PaddleStyleType)(version % SINPUT_PADDLESTYLE_MAX); + version /= SINPUT_PADDLESTYLE_MAX; + + decoded.trigger_style = (SInput_TriggerStyleType)(version % SINPUT_TRIGGERSTYLE_MAX); + version /= SINPUT_TRIGGERSTYLE_MAX; + + decoded.bumper_style = (SInput_BumperStyleType)(version % SINPUT_BUMPERSTYLE_MAX); + version /= SINPUT_BUMPERSTYLE_MAX; + + decoded.analog_style = (SInput_AnalogStyleType)(version % SINPUT_ANALOGSTYLE_MAX); + + SDL_SInputStylesMapExtraction(&decoded, mapping_string, mapping_string_len); +} + /* * Helper function to guess at a mapping for HIDAPI gamepads */ @@ -696,17 +1080,52 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) char mapping_string[1024]; Uint16 vendor; Uint16 product; + Uint16 version; SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string)); - SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, &version, NULL); + + if (SDL_IsJoystickWheel(vendor, product)) { + // We don't want to pick up Logitech FFB wheels here + // Some versions of WINE will also not treat devices that show up as gamepads as wheels + return NULL; + } if ((vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) || (vendor == USB_VENDOR_DRAGONRISE && (product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 || - product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2))) { + product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 || + product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3))) { // GameCube driver has 12 buttons and 6 axes - SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b2,y:b3,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "a:b0,b:b2,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b1,y:b3,misc3:b11,misc4:b10,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER) { + SDL_strlcat(mapping_string, "a:b1,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,leftshoulder:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a5,rightx:a2,righty:a3,start:b5,x:b0,y:b2,misc1:b8,misc2:b9,misc3:b10,misc4:b11,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_PRO) { + 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,misc1:b11,misc2:b12,paddle1:b13,paddle2:b14,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT) { + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false)) { + // Vertical mode + SDL_strlcat(mapping_string, "back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b11,paddle2:b14,paddle4:b16,", sizeof(mapping_string)); + } else { + // Mini gamepad mode + SDL_strlcat(mapping_string, "a:b1,b:b2,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b0,paddle2:b14,paddle4:b16,", sizeof(mapping_string)); + } + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT) { + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false)) { + // Vertical mode + SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,misc2:b12,paddle1:b13,paddle3:b15,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + } else { + // Mini gamepad mode + SDL_strlcat(mapping_string, "a:b1,b:b3,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b0,y:b2,misc2:b12,paddle1:b13,paddle3:b15,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + } + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR) { + 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,misc1:b11,misc2:b12,paddle1:b13,paddle2:b14,paddle3:b15,paddle4:b16,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); } else if (vendor == USB_VENDOR_NINTENDO && (guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCLeft || guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCRight || @@ -734,7 +1153,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,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)); + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b11,misc2:b4,rightshoulder:b10,righttrigger:b7,start:b6,x:a5,y:b2,", sizeof(mapping_string)); break; case k_eSwitchDeviceInfoControllerType_SEGA_Genesis: SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b11,", sizeof(mapping_string)); @@ -771,13 +1190,45 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) } break; } + } else if (vendor == USB_VENDOR_8BITDO && + (product == USB_PRODUCT_8BITDO_SF30_PRO || + product == USB_PRODUCT_8BITDO_SF30_PRO_BT || + product == USB_PRODUCT_8BITDO_SN30_PRO || + product == USB_PRODUCT_8BITDO_SN30_PRO_BT || + product == USB_PRODUCT_8BITDO_PRO_2 || + product == USB_PRODUCT_8BITDO_PRO_2_BT || + product == USB_PRODUCT_8BITDO_PRO_3)) { + SDL_strlcat(mapping_string, "a:b1,b:b0,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:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + if (product == USB_PRODUCT_8BITDO_PRO_2 || product == USB_PRODUCT_8BITDO_PRO_2_BT) { + SDL_strlcat(mapping_string, "paddle1:b14,paddle2:b13,", sizeof(mapping_string)); + } else if (product == USB_PRODUCT_8BITDO_PRO_3) { + SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,paddle3:b14,paddle4:b13,", sizeof(mapping_string)); + } + } else if (vendor == USB_VENDOR_8BITDO && + (product == USB_PRODUCT_8BITDO_SF30_PRO || + product == USB_PRODUCT_8BITDO_SF30_PRO_BT)) { + // This controller has no guide button + SDL_strlcat(mapping_string, "a:b1,b:b0,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,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,", sizeof(mapping_string)); + } else if (SDL_IsJoystickSInputController(vendor, product)) { + + Uint8 face_style = (guid.data[15] & 0xE0) >> 5; + Uint8 sub_product = guid.data[15] & 0x1F; + + SDL_CreateMappingStringForSInputGamepad(vendor, product, sub_product, version, face_style, mapping_string, sizeof(mapping_string)); } else { // 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_IsJoystickGameCube(vendor, product)) { + SDL_strlcat(mapping_string, "a:b0,b:b2,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:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); + } else { + 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_IsJoystickSteamController(vendor, product)) { // Steam controllers have 2 back paddle buttons - SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,", sizeof(mapping_string)); + } else if (SDL_IsJoystickSteamTriton(vendor, product)) { + // Second generation Steam controllers have 4 back paddle buttons + SDL_strlcat(mapping_string, "misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15", sizeof(mapping_string)); } else if (SDL_IsJoystickNintendoSwitchPro(vendor, product) || SDL_IsJoystickNintendoSwitchProInputOnly(vendor, product)) { // Nintendo Switch Pro controllers have a screenshot button @@ -790,18 +1241,33 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); } else if (SDL_IsJoystickGoogleStadiaController(vendor, product)) { // The Google Stadia controller has a share button and a Google Assistant button - SDL_strlcat(mapping_string, "misc1:b11,misc2:b12", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "misc1:b11,misc2:b12,", sizeof(mapping_string)); } else if (SDL_IsJoystickNVIDIASHIELDController(vendor, product)) { // The NVIDIA SHIELD controller has a share button between back and start buttons SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); if (product == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) { // The original SHIELD controller has a touchpad and plus/minus buttons as well - SDL_strlcat(mapping_string, "touchpad:b12,misc2:b13,misc3:b14", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "touchpad:b12,misc2:b13,misc3:b14,", sizeof(mapping_string)); } } else if (SDL_IsJoystickHoriSteamController(vendor, product)) { /* The Wireless HORIPad for Steam has QAM, Steam, Capsense L/R Sticks, 2 rear buttons, and 2 misc buttons */ SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc2:b11,misc3:b16,misc4:b17", sizeof(mapping_string)); + } else if (SDL_IsJoystickFlydigiController(vendor, product)) { + SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,paddle3:b13,paddle4:b14,", sizeof(mapping_string)); + if (guid.data[15] >= SDL_FLYDIGI_VADER2) { + // Vader series of controllers have C/Z buttons + SDL_strlcat(mapping_string, "misc2:b15,misc3:b16,", sizeof(mapping_string)); + if (guid.data[15] == SDL_FLYDIGI_VADER5_PRO) { + // Vader 5 has additional shoulder macro buttons and a circle button + SDL_strlcat(mapping_string, "misc4:b17,misc5:b18,misc6:b19", sizeof(mapping_string)); + } + } else if (guid.data[15] == SDL_FLYDIGI_APEX5) { + // Apex 5 has additional shoulder macro buttons + SDL_strlcat(mapping_string, "misc2:b15,misc3:b16,", sizeof(mapping_string)); + } + } else if (vendor == USB_VENDOR_8BITDO && product == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { + SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,paddle3:b14,paddle4:b13,", sizeof(mapping_string)); } else { switch (SDL_GetGamepadTypeFromGUID(guid, NULL)) { case SDL_GAMEPAD_TYPE_PS4: @@ -931,7 +1397,8 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMappingForGUID(SDL_GUID guid, bool { GamepadMapping_t *mapping; - mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, true, adding_mapping); + // Try first with an exact match on version and CRC + mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, true, true); if (mapping) { return mapping; } @@ -941,10 +1408,19 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMappingForGUID(SDL_GUID guid, bool return NULL; } - // Try harder to get the best match, or create a mapping + // Try without CRC match + mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, true, false); + if (mapping) { + return mapping; + } + // Try without version match if (SDL_JoystickGUIDUsesVersion(guid)) { - // Try again, ignoring the version + mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, false, true); + if (mapping) { + return mapping; + } + mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, false, false); if (mapping) { return mapping; @@ -984,7 +1460,8 @@ static const char *map_StringForGamepadType[] = { "switchpro", "joyconleft", "joyconright", - "joyconpair" + "joyconpair", + "gamecube" }; SDL_COMPILE_TIME_ASSERT(map_StringForGamepadType, SDL_arraysize(map_StringForGamepadType) == SDL_GAMEPAD_TYPE_COUNT); @@ -1099,7 +1576,7 @@ SDL_COMPILE_TIME_ASSERT(map_StringForGamepadButton, SDL_arraysize(map_StringForG /* * convert a string to its enum equivalent */ -static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, bool baxy) +static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, bool axby, bool baxy) { int i; @@ -1109,7 +1586,17 @@ static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, for (i = 0; i < SDL_arraysize(map_StringForGamepadButton); ++i) { if (SDL_strcasecmp(str, map_StringForGamepadButton[i]) == 0) { - if (baxy) { + if (axby) { + // Need to swap face buttons + switch (i) { + case SDL_GAMEPAD_BUTTON_EAST: + return SDL_GAMEPAD_BUTTON_WEST; + case SDL_GAMEPAD_BUTTON_WEST: + return SDL_GAMEPAD_BUTTON_EAST; + default: + break; + } + } else if (baxy) { // Need to swap face buttons switch (i) { case SDL_GAMEPAD_BUTTON_SOUTH: @@ -1131,7 +1618,7 @@ static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, } SDL_GamepadButton SDL_GetGamepadButtonFromString(const char *str) { - return SDL_PrivateGetGamepadButtonFromString(str, false); + return SDL_PrivateGetGamepadButtonFromString(str, false, false); } /* @@ -1158,6 +1645,7 @@ static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szG char half_axis_output = 0; int i; SDL_GamepadBinding *new_bindings; + bool axby_mapping = false; bool baxy_mapping = false; SDL_AssertJoysticksLocked(); @@ -1168,12 +1656,18 @@ static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szG half_axis_output = *szGameButton++; } + if (SDL_strstr(gamepad->mapping->mapping, ",hint:SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1") != NULL) { + axby_mapping = true; + } if (SDL_strstr(gamepad->mapping->mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) { baxy_mapping = true; } + // FIXME: We fix these up when loading the mapping, does this ever get hit? + //SDL_assert(!axby_mapping && !baxy_mapping); + axis = SDL_GetGamepadAxisFromString(szGameButton); - button = SDL_PrivateGetGamepadButtonFromString(szGameButton, baxy_mapping); + button = SDL_PrivateGetGamepadButtonFromString(szGameButton, axby_mapping, baxy_mapping); if (axis != SDL_GAMEPAD_AXIS_INVALID) { bind.output_type = SDL_GAMEPAD_BINDTYPE_AXIS; bind.output.axis.axis = axis; @@ -1264,7 +1758,7 @@ static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szG static bool SDL_PrivateParseGamepadConfigString(SDL_Gamepad *gamepad, const char *pchString) { char szGameButton[20]; - char szJoystickButton[20]; + char szJoystickButton[128]; bool bGameButton = true; int i = 0; const char *pchPos = pchString; @@ -1339,6 +1833,8 @@ static SDL_GamepadFaceStyle SDL_GetGamepadFaceStyleFromString(const char *string { if (SDL_strcmp(string, "abxy") == 0) { return SDL_GAMEPAD_FACE_STYLE_ABXY; + } else if (SDL_strcmp(string, "axby") == 0) { + return SDL_GAMEPAD_FACE_STYLE_AXBY; } else if (SDL_strcmp(string, "bayx") == 0) { return SDL_GAMEPAD_FACE_STYLE_BAYX; } else if (SDL_strcmp(string, "sony") == 0) { @@ -1360,6 +1856,8 @@ static SDL_GamepadFaceStyle SDL_GetGamepadFaceStyleForGamepadType(SDL_GamepadTyp case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: return SDL_GAMEPAD_FACE_STYLE_BAYX; + case SDL_GAMEPAD_TYPE_GAMECUBE: + return SDL_GAMEPAD_FACE_STYLE_AXBY; default: return SDL_GAMEPAD_FACE_STYLE_ABXY; } @@ -1386,6 +1884,11 @@ static void SDL_UpdateGamepadFaceStyle(SDL_Gamepad *gamepad) } } + if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN && + SDL_strstr(gamepad->mapping->mapping, "SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS") != NULL) { + // This controller uses GameCube button style + gamepad->face_style = SDL_GAMEPAD_FACE_STYLE_AXBY; + } if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN && SDL_strstr(gamepad->mapping->mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") != NULL) { // This controller uses Nintendo button style @@ -1398,6 +1901,8 @@ static void SDL_UpdateGamepadFaceStyle(SDL_Gamepad *gamepad) static void SDL_FixupHIDAPIMapping(SDL_Gamepad *gamepad) { + SDL_AssertJoysticksLocked(); + // Check to see if we need fixup bool need_fixup = false; for (int i = 0; i < gamepad->num_bindings; ++i) { @@ -1729,17 +2234,6 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMappingForNameAndGUID(const char * SDL_AssertJoysticksLocked(); mapping = SDL_PrivateGetGamepadMappingForGUID(guid, false); -#ifdef SDL_PLATFORM_LINUX - if (!mapping && name) { - if (SDL_strstr(name, "Xbox 360 Wireless Receiver")) { - // The Linux driver xpad.c maps the wireless dpad to buttons - bool existing; - mapping = SDL_PrivateAddMappingForGUID(guid, - "none,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT); - } - } -#endif // SDL_PLATFORM_LINUX return mapping; } @@ -1866,14 +2360,25 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id return mapping; } +static bool SDL_PrivateIsGamepadPlatformMatch(const char *platform, size_t platform_len) +{ +#ifdef SDL_PLATFORM_MACOS + // We also accept the older SDL2 platform name for macOS + if (SDL_strncasecmp(platform, "Mac OS X", platform_len) == 0) { + return true; + } +#endif + + return SDL_strncasecmp(platform, SDL_GetPlatform(), platform_len) == 0; +} + /* * Add or update an entry into the Mappings Database */ int SDL_AddGamepadMappingsFromIO(SDL_IOStream *src, bool closeio) { - const char *platform = SDL_GetPlatform(); int gamepads = 0; - char *buf, *line, *line_end, *tmp, *comma, line_platform[64]; + char *buf, *line, *line_end, *tmp, *comma, *platform; size_t db_size; size_t platform_len; @@ -1902,13 +2407,11 @@ int SDL_AddGamepadMappingsFromIO(SDL_IOStream *src, bool closeio) tmp += SDL_GAMEPAD_PLATFORM_FIELD_SIZE; comma = SDL_strchr(tmp, ','); if (comma) { - platform_len = comma - tmp + 1; - if (platform_len + 1 < SDL_arraysize(line_platform)) { - SDL_strlcpy(line_platform, tmp, platform_len); - if (SDL_strncasecmp(line_platform, platform, platform_len) == 0 && - SDL_AddGamepadMapping(line) > 0) { - gamepads++; - } + platform = tmp; + platform_len = comma - platform; + if (SDL_PrivateIsGamepadPlatformMatch(platform, platform_len) && + SDL_AddGamepadMapping(line) > 0) { + gamepads++; } } } @@ -1926,7 +2429,11 @@ int SDL_AddGamepadMappingsFromIO(SDL_IOStream *src, bool closeio) int SDL_AddGamepadMappingsFromFile(const char *file) { - return SDL_AddGamepadMappingsFromIO(SDL_IOFromFile(file, "rb"), true); + SDL_IOStream *stream = SDL_IOFromFile(file, "rb"); + if (!stream) { + return -1; + } + return SDL_AddGamepadMappingsFromIO(stream, true); } bool SDL_ReloadGamepadMappings(void) @@ -1951,7 +2458,37 @@ bool SDL_ReloadGamepadMappings(void) return true; } -static char *SDL_ConvertMappingToPositional(const char *mapping) +static char *SDL_ConvertMappingToPositionalAXBY(const char *mapping) +{ + // Add space for '!' and null terminator + size_t length = SDL_strlen(mapping) + 1 + 1; + char *remapped = (char *)SDL_malloc(length); + if (remapped) { + char *button_B; + char *button_X; + char *hint; + + SDL_strlcpy(remapped, mapping, length); + button_B = SDL_strstr(remapped, ",b:"); + button_X = SDL_strstr(remapped, ",x:"); + hint = SDL_strstr(remapped, "hint:SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS"); + + if (button_B) { + button_B[1] = 'x'; + } + if (button_X) { + button_X[1] = 'b'; + } + if (hint) { + hint += 5; + SDL_memmove(hint + 1, hint, SDL_strlen(hint) + 1); + *hint = '!'; + } + } + return remapped; +} + +static char *SDL_ConvertMappingToPositionalBAXY(const char *mapping) { // Add space for '!' and null terminator size_t length = SDL_strlen(mapping) + 1 + 1; @@ -1964,23 +2501,23 @@ static char *SDL_ConvertMappingToPositional(const char *mapping) char *hint; SDL_strlcpy(remapped, mapping, length); - button_A = SDL_strstr(remapped, "a:"); - button_B = SDL_strstr(remapped, "b:"); - button_X = SDL_strstr(remapped, "x:"); - button_Y = SDL_strstr(remapped, "y:"); + button_A = SDL_strstr(remapped, ",a:"); + button_B = SDL_strstr(remapped, ",b:"); + button_X = SDL_strstr(remapped, ",x:"); + button_Y = SDL_strstr(remapped, ",y:"); hint = SDL_strstr(remapped, "hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS"); if (button_A) { - *button_A = 'b'; + button_A[1] = 'b'; } if (button_B) { - *button_B = 'a'; + button_B[1] = 'a'; } if (button_X) { - *button_X = 'y'; + button_X[1] = 'y'; } if (button_Y) { - *button_Y = 'x'; + button_Y[1] = 'x'; } if (hint) { hint += 5; @@ -1996,9 +2533,11 @@ static char *SDL_ConvertMappingToPositional(const char *mapping) */ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMappingPriority priority) { + char *appended = NULL; char *remapped = NULL; char *pchGUID; - SDL_GUID jGUID; + SDL_GUID guid; + Uint16 vendor, product; bool is_default_mapping = false; bool is_xinput_mapping = false; bool existing = false; @@ -2007,11 +2546,33 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa SDL_AssertJoysticksLocked(); - if (!mappingString) { + CHECK_PARAM(!mappingString) { SDL_InvalidParamError("mappingString"); return -1; } + pchGUID = SDL_PrivateGetGamepadGUIDFromMappingString(mappingString); + if (!pchGUID) { + SDL_SetError("Couldn't parse GUID from %s", mappingString); + return -1; + } + if (!SDL_strcasecmp(pchGUID, "default")) { + is_default_mapping = true; + } else if (!SDL_strcasecmp(pchGUID, "xinput")) { + is_xinput_mapping = true; + } + guid = SDL_StringToGUID(pchGUID); + SDL_free(pchGUID); + + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + if (SDL_IsJoystickGameCube(vendor, product) && + SDL_strstr(mappingString, "SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS") == NULL) { + SDL_asprintf(&appended, "%shint:SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", mappingString); + if (appended) { + mappingString = appended; + } + } + { // Extract and verify the hint field const char *tmp; @@ -2043,20 +2604,37 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa default_value = false; } - if (SDL_strcmp(hint, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") == 0) { + if (SDL_strcmp(hint, "SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS") == 0) { // This hint is used to signal whether the mapping uses positional buttons or not if (negate) { // This mapping uses positional buttons, we can use it as-is } else { // This mapping uses labeled buttons, we need to swap them to positional - remapped = SDL_ConvertMappingToPositional(mappingString); + remapped = SDL_ConvertMappingToPositionalAXBY(mappingString); if (!remapped) { goto done; } mappingString = remapped; } + } else if (SDL_strcmp(hint, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") == 0) { + // This hint is used to signal whether the mapping uses positional buttons or not + if (negate) { + // This mapping uses positional buttons, we can use it as-is + } else { + // This mapping uses labeled buttons, we need to swap them to positional + remapped = SDL_ConvertMappingToPositionalBAXY(mappingString); + if (!remapped) { + goto done; + } + mappingString = remapped; + } + } else { - value = SDL_GetHintBoolean(hint, default_value); + const char *hint_value = SDL_GetHint(hint); + if (!hint_value) { + hint_value = SDL_getenv_unsafe(hint); + } + value = SDL_GetStringBoolean(hint_value, default_value); if (negate) { value = !value; } @@ -2068,7 +2646,7 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa } } -#ifdef ANDROID +#ifdef SDL_PLATFORM_ANDROID { // Extract and verify the SDK version const char *tmp; @@ -2091,20 +2669,7 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa } #endif - pchGUID = SDL_PrivateGetGamepadGUIDFromMappingString(mappingString); - if (!pchGUID) { - SDL_SetError("Couldn't parse GUID from %s", mappingString); - goto done; - } - if (!SDL_strcasecmp(pchGUID, "default")) { - is_default_mapping = true; - } else if (!SDL_strcasecmp(pchGUID, "xinput")) { - is_xinput_mapping = true; - } - jGUID = SDL_StringToGUID(pchGUID); - SDL_free(pchGUID); - - pGamepadMapping = SDL_PrivateAddMappingForGUID(jGUID, mappingString, &existing, priority); + pGamepadMapping = SDL_PrivateAddMappingForGUID(guid, mappingString, &existing, priority); if (!pGamepadMapping) { goto done; } @@ -2120,9 +2685,8 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa result = 1; } done: - if (remapped) { - SDL_free(remapped); - } + SDL_free(appended); + SDL_free(remapped); return result; } @@ -2321,7 +2885,7 @@ bool SDL_SetGamepadMapping(SDL_JoystickID instance_id, const char *mapping) SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id); bool result = false; - if (SDL_memcmp(&guid, &s_zeroGUID, sizeof(guid)) == 0) { + CHECK_PARAM(SDL_memcmp(&guid, &s_zeroGUID, sizeof(guid)) == 0) { return SDL_InvalidParamError("instance_id"); } @@ -2434,6 +2998,10 @@ bool SDL_InitGamepads(void) SDL_gamepads_initialized = true; + SDL_LockJoysticks(); + + SDL_gamepad_names = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, SDL_DestroyHashValue, NULL); + // Watch for joystick events and fire gamepad ones if needed SDL_AddEventWatch(SDL_GamepadEventWatcher, NULL); @@ -2448,6 +3016,8 @@ bool SDL_InitGamepads(void) SDL_free(joysticks); } + SDL_UnlockJoysticks(); + return true; } @@ -2494,19 +3064,10 @@ SDL_JoystickID *SDL_GetGamepads(int *count) const char *SDL_GetGamepadNameForID(SDL_JoystickID instance_id) { - const char *result = NULL; + const char *result; SDL_LockJoysticks(); - { - GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, true); - if (mapping) { - if (SDL_strcmp(mapping->name, "*") == 0) { - result = SDL_GetJoystickNameForID(instance_id); - } else { - result = SDL_GetPersistentString(mapping->name); - } - } - } + result = SDL_UpdateGamepadNameForID(instance_id); SDL_UnlockJoysticks(); return result; @@ -2611,7 +3172,7 @@ char *SDL_GetGamepadMappingForID(SDL_JoystickID instance_id) } /* - * Return 1 if the joystick with this name and GUID is a supported gamepad + * Return true if the joystick with this name and GUID is a supported gamepad */ bool SDL_IsGamepadNameAndGUID(const char *name, SDL_GUID guid) { @@ -2631,7 +3192,7 @@ bool SDL_IsGamepadNameAndGUID(const char *name, SDL_GUID guid) } /* - * Return 1 if the joystick at this device index is a supported gamepad + * Return true if the joystick at this device index is a supported gamepad */ bool SDL_IsGamepad(SDL_JoystickID instance_id) { @@ -2640,7 +3201,8 @@ bool SDL_IsGamepad(SDL_JoystickID instance_id) SDL_LockJoysticks(); { const void *value; - if (SDL_FindInHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, &value)) { + if (s_gamepadInstanceIDs && + SDL_FindInHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, &value)) { result = (bool)(uintptr_t)value; } else { if (SDL_PrivateGetGamepadMapping(instance_id, true) != NULL) { @@ -2648,11 +3210,12 @@ bool SDL_IsGamepad(SDL_JoystickID instance_id) } else { result = false; } - if (!s_gamepadInstanceIDs) { 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, true); + if (s_gamepadInstanceIDs) { + SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, (void *)(uintptr_t)result, true); + } } } SDL_UnlockJoysticks(); @@ -2661,46 +3224,49 @@ bool SDL_IsGamepad(SDL_JoystickID instance_id) } /* - * Return 1 if the gamepad should be ignored by SDL + * Return true if the gamepad should be ignored by SDL */ bool SDL_ShouldIgnoreGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) { -#ifdef SDL_PLATFORM_LINUX - if (SDL_endswith(name, " Motion Sensors")) { - // Don't treat the PS3 and PS4 motion controls as a separate gamepad - return true; - } - if (SDL_strncmp(name, "Nintendo ", 9) == 0 && SDL_strstr(name, " IMU") != NULL) { - // Don't treat the Nintendo IMU as a separate gamepad - return true; - } - if (SDL_endswith(name, " Accelerometer") || - SDL_endswith(name, " IR") || - SDL_endswith(name, " Motion Plus") || - SDL_endswith(name, " Nunchuk")) { - // Don't treat the Wii extension controls as a separate gamepad - return true; - } -#endif + int i; + for (i = 0; i < SDL_arraysize(SDL_gamepad_blacklist_words); i++) { + const struct SDL_GamepadBlacklistWords *blacklist_word = &SDL_gamepad_blacklist_words[i]; - if (name && SDL_strcmp(name, "uinput-fpc") == 0) { - // The Google Pixel fingerprint sensor reports itself as a joystick - return true; + switch (blacklist_word->pos) { + case GAMEPAD_BLACKLIST_BEGIN: + if (SDL_startswith(name, blacklist_word->str)) { + return true; + } + break; + + case GAMEPAD_BLACKLIST_END: + if (SDL_endswith(name, blacklist_word->str)) { + return true; + } + break; + + case GAMEPAD_BLACKLIST_ANYWHERE: + if (SDL_strstr(name, blacklist_word->str) != NULL) { + return true; + } + break; + } } + const char *hint = SDL_getenv_unsafe("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD"); + bool allow_steam_virtual_gamepad = SDL_GetStringBoolean(hint, false); #ifdef SDL_PLATFORM_WIN32 - if (SDL_GetHintBoolean("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", false) && - SDL_GetHintBoolean("STEAM_COMPAT_PROTON", false)) { - // We are launched by Steam and running under Proton + if (allow_steam_virtual_gamepad && WIN_IsWine()) { + // We are launched by Steam and running under Proton or Wine // We can't tell whether this controller is a Steam Virtual Gamepad, - // so assume that Proton is doing the appropriate filtering of controllers + // so assume that is doing the appropriate filtering of controllers // and anything we see here is fine to use. return false; } #endif // SDL_PLATFORM_WIN32 if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) { - return !SDL_GetHintBoolean("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", false); + return !allow_steam_virtual_gamepad; } if (SDL_allowed_gamepads.num_included_entries > 0) { @@ -2997,6 +3563,24 @@ static SDL_GamepadButtonLabel SDL_GetGamepadButtonLabelForFaceStyle(SDL_GamepadF break; } break; + case SDL_GAMEPAD_FACE_STYLE_AXBY: + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + label = SDL_GAMEPAD_BUTTON_LABEL_A; + break; + case SDL_GAMEPAD_BUTTON_EAST: + label = SDL_GAMEPAD_BUTTON_LABEL_X; + break; + case SDL_GAMEPAD_BUTTON_WEST: + label = SDL_GAMEPAD_BUTTON_LABEL_B; + break; + case SDL_GAMEPAD_BUTTON_NORTH: + label = SDL_GAMEPAD_BUTTON_LABEL_Y; + break; + default: + break; + } + break; case SDL_GAMEPAD_FACE_STYLE_BAYX: switch (button) { case SDL_GAMEPAD_BUTTON_SOUTH: @@ -3741,6 +4325,11 @@ void SDL_QuitGamepads(void) SDL_CloseGamepad(SDL_gamepads); } + if (SDL_gamepad_names) { + SDL_DestroyHashTable(SDL_gamepad_names); + SDL_gamepad_names = NULL; + } + SDL_UnlockJoysticks(); } diff --git a/libs/SDL3/src/joystick/SDL_gamepad_c.h b/libs/SDL3/src/joystick/SDL_gamepad_c.h index f1b1d10..ff65743 100644 --- a/libs/SDL3/src/joystick/SDL_gamepad_c.h +++ b/libs/SDL3/src/joystick/SDL_gamepad_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/SDL_gamepad_db.h b/libs/SDL3/src/joystick/SDL_gamepad_db.h index 1222cea..476ead6 100644 --- a/libs/SDL3/src/joystick/SDL_gamepad_db.h +++ b/libs/SDL3/src/joystick/SDL_gamepad_db.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -46,6 +46,7 @@ static const char *s_GamepadMappings[] = { #endif #ifdef SDL_JOYSTICK_DINPUT "03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,", + "03000000c82d00001930000000000000,8BitDo 64 Bluetooth Controller,a:b0,b:b1,back:-a5,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a2,misc1:b17,misc2:+a3,rightshoulder:b7,righttrigger:b9,start:b11,x:+a5,y:-a3,", "03000000c82d00000090000000000000,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,", "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,", @@ -197,7 +198,6 @@ static const char *s_GamepadMappings[] = { "03000000380700003888000000000000,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "030000002a0600001024000000000000,Matricom,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:a3,righty:a4,start:b9,x:b2,y:b3,", "03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,", - "03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", "030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,", "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: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,", @@ -207,7 +207,8 @@ static const char *s_GamepadMappings[] = { "03000000152000000182000000000000,NGDS,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:a3,righty:a4,start:b9,x:b3,y:b0,", "030000005509000000b4000000000000,NVIDIA Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000004b120000014d000000000000,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,", - "03000000790000004318000000000000,Nintendo GameCube Controller,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,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,", + "03000000790000004318000000000000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", + "03000000790000004418000000000000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", "03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,", "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,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,", @@ -242,6 +243,7 @@ static const char *s_GamepadMappings[] = { "03000000d62000002640000000000000,PowerA OPS v1 Wireless Controller,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:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", "03000000d62000003340000000000000,PowerA OPS v3 Pro Wireless Controller,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:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", "03000000d62000006dca000000000000,PowerA Pro Ex,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:a3,start:b9,x:b0,y:b3,", + "03000000d620000011a7000000000000,PowerA Wired GameCube 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:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000d62000009557000000000000,Pro Elite PS3 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:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000d62000009f31000000000000,Pro Ex mini PS3 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:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000d6200000c757000000000000,Pro Ex mini PS3 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:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", @@ -310,7 +312,6 @@ static const char *s_GamepadMappings[] = { "03000000110100003114000000000000,SteelSeries Stratus Duo,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:a3,righty:a4,start:b11,x:b3,y:b4,", "03000000381000001814000000000000,SteelSeries Stratus XL,a:b0,b:b1,back:b18,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b19,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b2,y:b3,", "03000000110100001914000000000000,SteelSeries,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", - "03000000d620000011a7000000000000,Switch,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:a3,start:b9,x:b0,y:b3,", "030000004f04000007d0000000000000,T Mini Wireless,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:a3,start:b9,x:b0,y:b3,", "03000000fa1900000706000000000000,Team 5,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,", "03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,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,", @@ -339,6 +340,7 @@ static const char *s_GamepadMappings[] = { "03000000172700004431000000000000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,", "03000000c0160000e105000000000000,Xin-Mo Dual Arcade,crc:2246,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 */ "03000000790000004f18000000000000,ZD-T Android,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:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000073500000400000000000000,ZENAIM ARCADE 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:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,", "03000000120c0000101e000000000000,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,", "03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", "03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", @@ -347,6 +349,7 @@ static const char *s_GamepadMappings[] = { "03000000101c0000171c000000000000,uRage Gamepad,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,", #endif #ifdef SDL_PLATFORM_MACOS + "03000000c82d00001930000000000000,8BitDo 64 Bluetooth Controller,a:b0,b:b1,back:-a3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b17,misc2:+a2,misc3:b10,rightshoulder:b7,righttrigger:b9,start:b11,x:+a3,y:-a2,", "03000000c82d00000090000001000000,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,", "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,", @@ -413,13 +416,13 @@ static const char *s_GamepadMappings[] = { "03000000380700005082000000010000,Mad Catz FightPad PRO (PS4),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,", "03000000380700008433000000010000,Mad Catz FightStick TE S+ (PS3),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:a3,start:b9,x:b0,y:b3,", "03000000380700008483000000010000,Mad Catz FightStick TE S+ (PS4),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,", - "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,", + "03000000790000004418000000010000,Nintendo 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,", "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,", @@ -481,12 +484,15 @@ static const char *s_GamepadMappings[] = { "030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,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,", "03000000172700004431000029010000,XiaoMi Game Controller,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:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,", "03000000c0160000e105000000040000,Xin-Mo Dual Arcade,crc:82d5,a:b2,b:b4,back:b18,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,rightshoulder:b8,righttrigger:b10,start:b16,x:b0,y:b6,", /* Ultimate Atari Fight Stick */ + "03000000073500000400000000010000,ZENAIM ARCADE 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:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,", "03000000120c0000100e000000010000,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,", "03000000120c0000101e000000010000,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,", "03000000830500006020000000010000,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,", "03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,", #endif #if defined(SDL_JOYSTICK_LINUX) || defined(SDL_PLATFORM_OPENBSD) + "03000000c82d00001930000011010000,8BitDo 64 Bluetooth Controller,a:b0,b:b1,back:-a3,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:b17,misc2:+a2,misc3:b10,rightshoulder:b7,righttrigger:a4,start:b11,x:+a3,y:-a2,", + "05000000c82d00001930000001000000,8BitDo 64 Bluetooth Controller,a:b0,b:b1,back:-a3,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:b17,misc2:+a2,misc3:b10,rightshoulder:b7,righttrigger:a4,start:b11,x:+a3,y:-a2,", "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,", @@ -498,16 +504,14 @@ static const char *s_GamepadMappings[] = { "03000000c82d00000190000011010000,8BitDo NES30 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,", "05000000203800000900000000010000,8BitDo NES30 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:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00002038000000010000,8BitDo NES30 Pro,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,", - "03000000c82d00000020000000000000,8BitDo Pro 2 Wired Controller for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "06000000c82d00000020000006010000,8BitDo Pro 2 Wired Controller for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000c82d00000660000011010000,8BitDo Pro 2,a:b1,b:b0,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:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,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:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000960000011010000,8BitDo Pro 3,a:b1,b:b0,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,paddle1:b17,paddle2:b16,paddle3:b2,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00000061000000010000,8BitDo SF30 Pro,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,", "05000000102800000900000000010000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00003028000000010000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000260000011010000,8BitDo SN30 Pro+,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:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00000261000000010000,8BitDo SN30 Pro+,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:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000160000011010000,8BitDo SN30 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,", "030000003512000020ab000010010000,8BitDo SNES30 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,", "05000000202800000900000000010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00001b30000001000000,8BitDo Ultimate 2C Wireless,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,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", /* Bluetooth */ @@ -524,9 +528,6 @@ static const char *s_GamepadMappings[] = { "05000000050b00000045000040000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,", "03000000050b00000579000011010000,ASUS ROG Kunai 3 Gamepad,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:b36,paddle1:b52,paddle2:b53,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "05000000050b00000679000000010000,ASUS ROG Kunai 3 Gamepad,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:b21,paddle1:b22,paddle2:b23,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", - "030000006f0e00003901000020060000,Afterglow Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000006f0e00003901000000430000,Afterglow Prismatic Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000006f0e00001302000000010000,Afterglow,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "05000000491900000204000021000000,Amazon Fire Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b17,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "03000000491900001904000011010000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,", @@ -537,7 +538,6 @@ static const char *s_GamepadMappings[] = { "03000000503200000210000000000000,Atari Game Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a2,righty:a3,start:b8,x:b2,y:b3,", "05000000503200000210000000000000,Atari Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,", "05000000503200000210000000000000128804098,Atari Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,", - "030000005e0400008e02000047010000,Atari Xbox 360 Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000c62400001b89000011010000,BDA MOGA XP5-X Plus,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,", "03000000d62000002a79000011010000,BDA PS4 Fightpad,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,", "03000000120c0000f70e000011010000,Brook Universal Fighting Board,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:,lefty:,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:,righty:,start:b9,x:b0,y:b3,", @@ -549,25 +549,21 @@ static const char *s_GamepadMappings[] = { "050000004c050000f20d000000010000,DualSense Edge Wireless 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,", "030000006f0e00003001000001010000,EA Sports PS3 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:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000790000001100000010010000,Elecom Gamepad,crc:e86c,a:b2,b:b3,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b0,y:b1,", - "0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "0500000047532067616d657061640000,GS Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,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,", - "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,", + "03000000341a000005f7000010010000,GameCube {HuiJia USB box},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,", "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,", - "030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000008f0e00000800000010010000,Gasia Co. Ltd PS(R) Gamepad,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,", - "030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000f0250000c183000010010000,Goodbetterbest Ltd USB 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:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000d11800000094000011010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", "05000000d11800000094000000010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", "03000000280400000140000000010000,Gravis Gamepad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,", "030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", - "03000000c9110000f055000011010000,HJC Game 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,", "030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,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,", "030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,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,", "030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,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,", @@ -581,30 +577,23 @@ static const char *s_GamepadMappings[] = { "030000000d0f0000aa00000011010000,HORI Real Arcade Pro,a:b2,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:b3,y:b0,", "030000000d0f00006e00000011010000,HORIPAD 4 (PS3),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:a3,start:b9,x:b0,y:b3,", "030000000d0f00006600000011010000,HORIPAD 4 (PS4),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,", - "030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,", "030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "030000000d0f00008600000002010000,Hori Fighting Commander,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:a2,righty:a3,start:b9,x:b2,y:b3,", - "03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,", - "03000000242e00008816000001010000,Hyperkin X91,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000d80400008200000003000000,IMS PCU#0 Gamepad Interface,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b5,x:b3,y:b2,", "03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,", "05000000491900000204000000000000,Ipega PG-9087S,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,", "030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,", "03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,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,", "03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,", - "030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000006d040000d1ca000011010000,Logitech Chillstream,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,", "030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,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,", "030000006d04000016c2000010010000,Logitech Dual Action,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,", "030000006d04000016c2000011010000,Logitech Dual Action,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,", - "030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),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,", /* Guide button doesn't seem to be sent in DInput mode. */ - "030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000006d04000018c2000010010000,Logitech RumblePad 2,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,", "030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,", "03000000c62400002b89000011010000,MOGA XP5-A Plus,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,", @@ -616,24 +605,12 @@ static const char *s_GamepadMappings[] = { "03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),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,", "03000000380700008433000011010000,Mad Catz FightStick TE S+ (PS3),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:a3,start:b9,x:b0,y:b3,", "03000000380700008483000011010000,Mad Catz FightStick TE S+ (PS4),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,", - "03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,", - "03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", - "03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", - "03000000790000004418000010010000,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,", "03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,", "030000005e0400000e00000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,", - "030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,", - "030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,", - "030000005e0400008902000020010000,Microsoft Xbox Controller S,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,", "05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", "030000006b140000010c000010010000,NACON GC-400ES,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,", "030000001008000001e5000010010000,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,", @@ -642,7 +619,9 @@ static const char *s_GamepadMappings[] = { "05000000550900001472000001000000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,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,", "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,", + "0300000085320000030c000011010000,Nacon GC100,a:b2,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:b3,y:b0,", + "03000000790000004318000010010000,Nintendo 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,", + "03000000790000004418000010010000,Nintendo 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,", "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,", @@ -654,7 +633,6 @@ static const char *s_GamepadMappings[] = { "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,", "050000007e0500000720000001800000,Nintendo Switch Joy-Con (R),a:b1,b:b2,guide:b9,leftshoulder:b4,leftstick:b10,leftx:a1~,lefty:a0,rightshoulder:b6,start:b8,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000d620000013a7000011010000,Nintendo Switch PowerA 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:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000d620000011a7000011010000,Nintendo Switch PowerA Core Plus 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:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000007e0500000920000011810000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000004c69632050726f20436f6e00,Nintendo Switch Pro Controller,crc:15b7,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,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b1,b:b0,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:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", @@ -666,9 +644,7 @@ static const char *s_GamepadMappings[] = { "030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,", "05000000362800000100000002010000,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:a5,rightx:a3,righty:a4,x:b1,y:b2,", "05000000362800000100000003010000,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:a5,rightx:a3,righty:a4,x:b1,y:b2,", - "030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,", "03000000ff1100003133000010010000,PC Game Controller,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,", - "030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000006f0e00000901000011010000,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,", "03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,", "03000000341a00003608000011010000,PS3 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:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", @@ -701,8 +677,7 @@ static const char *s_GamepadMappings[] = { "050000004c050000e60c000000010000,PS5 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,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "050000004c050000e60c000000810000,PS5 Controller,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:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,", "030000004c050000da0c000011010000,Playstation Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,", - "03000000c62400003a54000001010000,PowerA XBox One Controller,a:b0,b:b1,back:b6,dpdown:h0.7,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000d620000011a7000011010000,PowerA Wired GameCube 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:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,", "03000000222c00000225000011010000,Qanba Dragon Arcade Joystick (PS3),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:a3,start:b9,x:b0,y:b3,", "03000000222c00000025000011010000,Qanba Dragon Arcade Joystick (PS4),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,", @@ -711,25 +686,18 @@ static const char *s_GamepadMappings[] = { "03000000222c00000020000011010000,Qanba Drone Arcade Joystick (PS4),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,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,", "03000000222c00000223000011010000,Qanba Obsidian Arcade Joystick (PS3),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:a3,start:b9,x:b0,y:b3,", "03000000222c00000023000011010000,Qanba Obsidian Arcade Joystick (PS4),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,", - "030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000321500000204000011010000,Razer Panthera (PS3),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:a3,start:b9,x:b0,y:b3,", "03000000321500000104000011010000,Razer Panthera (PS4),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,", "03000000321500000010000011010000,Razer RAIJU,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,", "03000000321500000507000000010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,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,", "03000000321500000011000011010000,Razer Raion Fightpad for PS4,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,", - "030000008916000000fe000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "03000000c6240000045d000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", "050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", - "0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,", "03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000006b140000130d000011010000,Revolution Pro Controller 3,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,", "030000006b140000010d000011010000,Revolution Pro 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,", "030000006f0e00001e01000011010000,Rock Candy PS3 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:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", - "030000006f0e00004601000001010000,Rock Candy Xbox One Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000006f0e00001f01000000010000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000632500007505000010010000,SHANWAN PS3/PC Gamepad,a:b2,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:b3,y:b0,", "03000000341a00000908000010010000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,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,", "03000000457500002211000010010000,SZMY-POWER PC 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:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", @@ -743,8 +711,6 @@ static const char *s_GamepadMappings[] = { "03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,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,", "03000000632500002305000010010000,ShanWan USB Gamepad,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,", "03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,", - "030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", "03000000de2800000112000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,", "03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", @@ -760,7 +726,6 @@ static const char *s_GamepadMappings[] = { "03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "0500000011010000311400001b010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b32,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,", "05000000110100001914000009010000,SteelSeries Stratus XL,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b18,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,", - "03000000ad1b000038f0000090040000,Street Fighter IV FightStick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,", "0300000000f00000f100000000010000,Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,", "030000004f0400000ed0000011010000,ThrustMaster eSwap PRO 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,", @@ -781,23 +746,18 @@ static const char *s_GamepadMappings[] = { "030000006f0e00000702000011010000,Victrix Pro Fight Stick for PS4,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,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "030000000d0f0000ab01000011010000,Wireless HORIPAD For Steam,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,misc2:b2,misc3:b16,misc4:b17,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "050000000d0f00009601000091000000,Wireless HORIPAD For Steam,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,misc2:b2,misc3:b16,misc4:b17,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", - "030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,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,", "0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,", - "030000005e040000a102000014010000,Xbox 360 Wireless Receiver (XBOX),a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", "050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,guide:b16,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,", "05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,", "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 */ + "03000000073500000400000011010000,ZENAIM ARCADE 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:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,", "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,", + "03000000790000002201000011010000,ZhiXu GuliKit Controller D,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,", "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,", @@ -857,8 +817,8 @@ static const char *s_GamepadMappings[] = { "050000005e040000fd020000ff7f3f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", "050000005e040000e00200000ffe3f80,Xbox One Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a2,righty:a3,start:b10,x:b17,y:b2,", "050000005e040000fd020000ffff3f00,Xbox One Wireless Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", - "050000005e040000120b000000783f80,Xbox Series X Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", - "050000005e040000130b0000ffff3f00,Xbox Series X Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", + "050000005e040000120b000000783f80,Xbox Series X Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", + "050000005e040000130b0000ffff3f00,Xbox Series X Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", "050000005e04000091020000ff073f80,Xbox Wireless Controller,a:b0,b:b1,back:b4,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,", /* The DPAD doesn't seem to work on this controller on Android TV? */ "050000001727000044310000ffff3f80,XiaoMi Game Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a4,righty:a5,start:b6,x:b2,y:b3,", "0500000083050000602000000ffe0000,iBuffalo SNES Controller,a:b1,b:b0,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b15,rightshoulder:b16,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", @@ -888,6 +848,8 @@ static const char *s_GamepadMappings[] = { "050000004c050000cc090000ff870001,PS4 Controller,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,touchpad:b11,x:b2,y:b3,", "050000004c050000e60c0000df870000,PS5 Controller,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,touchpad:b10,x:b2,y:b3,", "050000004c050000e60c0000ff870000,PS5 Controller,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,touchpad:b11,x:b2,y:b3,", + "05000000ac050000040000008e586d04,PlayStation VR2 Sense Controller (L),crc:9342,+leftx:+a3,+lefty:+a1,-leftx:+a2,-lefty:+a4,back:b2,leftstick:b4,lefttrigger:+a7,paddle2:b3,x:b0,y:b1,", + "05000000ac050000040000000eb86d04,PlayStation VR2 Sense Controller (R),crc:334b,+rightx:+a3,+righty:+a1,-rightx:+a2,-righty:+a4,a:b0,b:b1,paddle1:b3,rightstick:b4,righttrigger:+a7,start:b2,", "05000000ac0500000300000043006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,", "050000005e040000050b0000df070001,Xbox Elite Wireless Controller,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,paddle1:b10,paddle2:b12,paddle3:b11,paddle4:b13,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", "050000005e040000050b0000ff070001,Xbox Elite Wireless Controller,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,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", @@ -897,7 +859,7 @@ static const char *s_GamepadMappings[] = { "050000005e040000e0020000ff070000,Xbox Wireless Controller,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,", #endif #ifdef SDL_JOYSTICK_EMSCRIPTEN - "default,Standard Gamepad,a:b0,b:b1,back:b8,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b16,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,", + "default,*,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", #endif #ifdef SDL_JOYSTICK_PS2 "0000000050533220436f6e74726f6c00,PS2 Controller,crc:ed87,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,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,", diff --git a/libs/SDL3/src/joystick/SDL_joystick.c b/libs/SDL3/src/joystick/SDL_joystick.c index 3ad49bb..621adbd 100644 --- a/libs/SDL3/src/joystick/SDL_joystick.c +++ b/libs/SDL3/src/joystick/SDL_joystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -123,8 +123,175 @@ static bool SDL_joystick_being_added; static SDL_Joystick *SDL_joysticks SDL_GUARDED_BY(SDL_joystick_lock) = NULL; static int SDL_joystick_player_count SDL_GUARDED_BY(SDL_joystick_lock) = 0; static SDL_JoystickID *SDL_joystick_players SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static SDL_HashTable *SDL_joystick_names SDL_GUARDED_BY(SDL_joystick_lock) = NULL; static bool SDL_joystick_allows_background_events = false; +static Uint32 initial_old_xboxone_controllers[] = { + MAKE_VIDPID(0x0000, 0x6686), + MAKE_VIDPID(0x0079, 0x18a1), + MAKE_VIDPID(0x0079, 0x18c2), + MAKE_VIDPID(0x0079, 0x18c8), + MAKE_VIDPID(0x0079, 0x18cf), + MAKE_VIDPID(0x03f0, 0x0495), + MAKE_VIDPID(0x045e, 0x02d1), + MAKE_VIDPID(0x045e, 0x02dd), + MAKE_VIDPID(0x045e, 0x02e0), + MAKE_VIDPID(0x045e, 0x02e3), + MAKE_VIDPID(0x045e, 0x02ea), + MAKE_VIDPID(0x045e, 0x02fd), + MAKE_VIDPID(0x045e, 0x02ff), + MAKE_VIDPID(0x045e, 0x0867), + MAKE_VIDPID(0x045e, 0x0b00), + MAKE_VIDPID(0x045e, 0x0b05), + MAKE_VIDPID(0x045e, 0x0b0a), + MAKE_VIDPID(0x045e, 0x0b0c), + MAKE_VIDPID(0x045e, 0x0b20), + MAKE_VIDPID(0x045e, 0x0b21), + MAKE_VIDPID(0x045e, 0x0b22), + MAKE_VIDPID(0x046d, 0x0000), + MAKE_VIDPID(0x046d, 0x1004), + MAKE_VIDPID(0x046d, 0x1007), + MAKE_VIDPID(0x046d, 0x1008), + MAKE_VIDPID(0x046d, 0xf301), + MAKE_VIDPID(0x0738, 0x02a0), + MAKE_VIDPID(0x0738, 0x4a01), + MAKE_VIDPID(0x0738, 0x7263), + MAKE_VIDPID(0x0738, 0xb738), + MAKE_VIDPID(0x0738, 0xcb29), + MAKE_VIDPID(0x0738, 0xf401), + MAKE_VIDPID(0x0c12, 0x0e17), + MAKE_VIDPID(0x0c12, 0x0e1c), + MAKE_VIDPID(0x0c12, 0x0e22), + MAKE_VIDPID(0x0c12, 0x0e30), + MAKE_VIDPID(0x0d62, 0x9a1a), + MAKE_VIDPID(0x0d62, 0x9a1b), + MAKE_VIDPID(0x0e00, 0x0e00), + MAKE_VIDPID(0x0e6f, 0x012a), + MAKE_VIDPID(0x0e6f, 0x0139), + MAKE_VIDPID(0x0e6f, 0x013B), + MAKE_VIDPID(0x0e6f, 0x013a), + MAKE_VIDPID(0x0e6f, 0x0145), + MAKE_VIDPID(0x0e6f, 0x0146), + MAKE_VIDPID(0x0e6f, 0x0152), + MAKE_VIDPID(0x0e6f, 0x015b), + MAKE_VIDPID(0x0e6f, 0x015c), + MAKE_VIDPID(0x0e6f, 0x015d), + MAKE_VIDPID(0x0e6f, 0x015f), + MAKE_VIDPID(0x0e6f, 0x0160), + MAKE_VIDPID(0x0e6f, 0x0161), + MAKE_VIDPID(0x0e6f, 0x0162), + MAKE_VIDPID(0x0e6f, 0x0163), + MAKE_VIDPID(0x0e6f, 0x0164), + MAKE_VIDPID(0x0e6f, 0x0165), + MAKE_VIDPID(0x0e6f, 0x0166), + MAKE_VIDPID(0x0e6f, 0x0167), + MAKE_VIDPID(0x0e6f, 0x0205), + MAKE_VIDPID(0x0e6f, 0x0206), + MAKE_VIDPID(0x0e6f, 0x0246), + MAKE_VIDPID(0x0e6f, 0x0261), + MAKE_VIDPID(0x0e6f, 0x0262), + MAKE_VIDPID(0x0e6f, 0x02a0), + MAKE_VIDPID(0x0e6f, 0x02a1), + MAKE_VIDPID(0x0e6f, 0x02a2), + MAKE_VIDPID(0x0e6f, 0x02a3), + MAKE_VIDPID(0x0e6f, 0x02a4), + MAKE_VIDPID(0x0e6f, 0x02a5), + MAKE_VIDPID(0x0e6f, 0x02a6), + MAKE_VIDPID(0x0e6f, 0x02a7), + MAKE_VIDPID(0x0e6f, 0x02a8), + MAKE_VIDPID(0x0e6f, 0x02a9), + MAKE_VIDPID(0x0e6f, 0x02aa), + MAKE_VIDPID(0x0e6f, 0x02ab), + MAKE_VIDPID(0x0e6f, 0x02ac), + MAKE_VIDPID(0x0e6f, 0x02ad), + MAKE_VIDPID(0x0e6f, 0x02ae), + MAKE_VIDPID(0x0e6f, 0x02af), + MAKE_VIDPID(0x0e6f, 0x02b0), + MAKE_VIDPID(0x0e6f, 0x02b1), + MAKE_VIDPID(0x0e6f, 0x02b2), + MAKE_VIDPID(0x0e6f, 0x02b3), + MAKE_VIDPID(0x0e6f, 0x02b5), + MAKE_VIDPID(0x0e6f, 0x02b6), + MAKE_VIDPID(0x0e6f, 0x02b8), + MAKE_VIDPID(0x0e6f, 0x02bd), + MAKE_VIDPID(0x0e6f, 0x02be), + MAKE_VIDPID(0x0e6f, 0x02bf), + MAKE_VIDPID(0x0e6f, 0x02c0), + MAKE_VIDPID(0x0e6f, 0x02c1), + MAKE_VIDPID(0x0e6f, 0x02c2), + MAKE_VIDPID(0x0e6f, 0x02c3), + MAKE_VIDPID(0x0e6f, 0x02c4), + MAKE_VIDPID(0x0e6f, 0x02c5), + MAKE_VIDPID(0x0e6f, 0x02c6), + MAKE_VIDPID(0x0e6f, 0x02c7), + MAKE_VIDPID(0x0e6f, 0x02c8), + MAKE_VIDPID(0x0e6f, 0x02c9), + MAKE_VIDPID(0x0e6f, 0x02ca), + MAKE_VIDPID(0x0e6f, 0x02cb), + MAKE_VIDPID(0x0e6f, 0x02cd), + MAKE_VIDPID(0x0e6f, 0x02ce), + MAKE_VIDPID(0x0e6f, 0x02cf), + MAKE_VIDPID(0x0e6f, 0x02d5), + MAKE_VIDPID(0x0e6f, 0x0346), + MAKE_VIDPID(0x0e6f, 0x0446), + MAKE_VIDPID(0x0e6f, 0xf501), + MAKE_VIDPID(0x0f0d, 0x0063), + MAKE_VIDPID(0x0f0d, 0x0067), + MAKE_VIDPID(0x0f0d, 0x0078), + MAKE_VIDPID(0x0f0d, 0x0097), + MAKE_VIDPID(0x0f0d, 0x00ba), + MAKE_VIDPID(0x0f0d, 0x00c0), + MAKE_VIDPID(0x0f0d, 0x00c5), + MAKE_VIDPID(0x0f0d, 0x00d8), + MAKE_VIDPID(0x0f0d, 0x00ed), + MAKE_VIDPID(0x0fff, 0x02a1), + MAKE_VIDPID(0x12ab, 0x0304), + MAKE_VIDPID(0x1430, 0x0291), + MAKE_VIDPID(0x1430, 0x02a9), + MAKE_VIDPID(0x1430, 0x070b), + MAKE_VIDPID(0x1430, 0x0719), + MAKE_VIDPID(0x146b, 0x0611), + MAKE_VIDPID(0x1532, 0x0a00), + MAKE_VIDPID(0x1532, 0x0a03), + MAKE_VIDPID(0x1532, 0x0a14), + MAKE_VIDPID(0x1532, 0x0a15), + MAKE_VIDPID(0x16d0, 0x0f3f), + MAKE_VIDPID(0x1bad, 0x028e), + MAKE_VIDPID(0x1bad, 0x02a0), + MAKE_VIDPID(0x1bad, 0x5500), + MAKE_VIDPID(0x20ab, 0x55ef), + MAKE_VIDPID(0x24c6, 0x541a), + MAKE_VIDPID(0x24c6, 0x542a), + MAKE_VIDPID(0x24c6, 0x543a), + MAKE_VIDPID(0x24c6, 0x5509), + MAKE_VIDPID(0x24c6, 0x551a), + MAKE_VIDPID(0x24c6, 0x561a), + MAKE_VIDPID(0x24c6, 0x581a), + MAKE_VIDPID(0x24c6, 0x591a), + MAKE_VIDPID(0x24c6, 0x592a), + MAKE_VIDPID(0x24c6, 0x791a), + MAKE_VIDPID(0x2516, 0x0069), + MAKE_VIDPID(0x25b1, 0x0360), + MAKE_VIDPID(0x2c22, 0x2203), + MAKE_VIDPID(0x2e24, 0x0652), + MAKE_VIDPID(0x2e24, 0x1618), + MAKE_VIDPID(0x2e24, 0x1688), + MAKE_VIDPID(0x2f24, 0x0011), + MAKE_VIDPID(0x2f24, 0x002e), + MAKE_VIDPID(0x2f24, 0x0050), + MAKE_VIDPID(0x2f24, 0x0053), + MAKE_VIDPID(0x2f24, 0x008f), + MAKE_VIDPID(0x2f24, 0x0091), + MAKE_VIDPID(0x2f24, 0x00b7), + MAKE_VIDPID(0xd2d2, 0xd2d2), +}; +static SDL_vidpid_list old_xboxone_controllers = { + "SDL_JOYSTICK_OLD_XBOXONE_CONTROLLERS", 0, 0, NULL, + "SDL_JOYSTICK_OLD_XBOXONE_CONTROLLERS_EXCLUDED", 0, 0, NULL, + SDL_arraysize(initial_old_xboxone_controllers), initial_old_xboxone_controllers, + false +}; + static Uint32 initial_arcadestick_devices[] = { MAKE_VIDPID(0x0079, 0x181a), // Venom Arcade Stick MAKE_VIDPID(0x0079, 0x181b), // Venom Arcade Stick @@ -273,11 +440,17 @@ static Uint32 initial_blacklist_devices[] = { MAKE_VIDPID(0x04d9, 0x8009), // OBINLB USB-HID Keyboard (Anne Pro II) MAKE_VIDPID(0x04d9, 0xa292), // OBINLB USB-HID Keyboard (Anne Pro II) MAKE_VIDPID(0x04d9, 0xa293), // OBINLB USB-HID Keyboard (Anne Pro II) + MAKE_VIDPID(0x04f2, 0xa13c), // HP Deluxe Webcam KQ246AA + MAKE_VIDPID(0x0e6f, 0x018a), // PDP REALMz Wireless Controller for Switch, USB charging MAKE_VIDPID(0x1532, 0x0266), // Razer Huntsman V2 Analog, non-functional DInput device 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(0x31e3, 0x1310), // Wooting 60HE (ARM) + MAKE_VIDPID(0x3297, 0x1969), // Moonlander MK1 Keyboard MAKE_VIDPID(0x3434, 0x0211), // Keychron K1 Pro System Control + MAKE_VIDPID(0x3434, 0x0353), // Keychron V5 System Control + MAKE_VIDPID(0x3434, 0xd030), // Keychron Link }; static SDL_vidpid_list blacklist_devices = { SDL_HINT_JOYSTICK_BLACKLIST_DEVICES, 0, 0, NULL, @@ -313,7 +486,14 @@ static SDL_vidpid_list flightstick_devices = { }; static Uint32 initial_gamecube_devices[] = { + MAKE_VIDPID(0x0079, 0x1843), // DragonRise GameCube Controller Adapter + MAKE_VIDPID(0x0079, 0x1844), // DragonRise GameCube Controller Adapter + MAKE_VIDPID(0x0079, 0x1846), // DragonRise GameCube Controller Adapter + MAKE_VIDPID(0x057e, 0x0337), // Nintendo Wii U GameCube Controller Adapter + MAKE_VIDPID(0x057e, 0x2073), // Nintendo Switch 2 NSO GameCube Controller + MAKE_VIDPID(0x0926, 0x8888), // Cyber Gadget GameCube Controller MAKE_VIDPID(0x0e6f, 0x0185), // PDP Wired Fight Pad Pro for Nintendo Switch + MAKE_VIDPID(0x1a34, 0xf705), // GameCube {HuiJia USB box} MAKE_VIDPID(0x20d6, 0xa711), // PowerA Wired Controller Nintendo GameCube Style }; static SDL_vidpid_list gamecube_devices = { @@ -440,18 +620,18 @@ static SDL_vidpid_list zero_centered_devices = { false }; -#define CHECK_JOYSTICK_MAGIC(joystick, result) \ - if (!SDL_ObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK)) { \ - SDL_InvalidParamError("joystick"); \ - SDL_UnlockJoysticks(); \ - return result; \ +#define CHECK_JOYSTICK_MAGIC(joystick, result) \ + CHECK_PARAM(!SDL_ObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK)) { \ + SDL_InvalidParamError("joystick"); \ + SDL_UnlockJoysticks(); \ + return result; \ } -#define CHECK_JOYSTICK_VIRTUAL(joystick, result) \ - if (!joystick->is_virtual) { \ - SDL_SetError("joystick isn't virtual"); \ - SDL_UnlockJoysticks(); \ - return result; \ +#define CHECK_JOYSTICK_VIRTUAL(joystick, result) \ + CHECK_PARAM(!joystick->is_virtual) { \ + SDL_SetError("joystick isn't virtual"); \ + SDL_UnlockJoysticks(); \ + return result; \ } bool SDL_JoysticksInitialized(void) @@ -656,8 +836,9 @@ bool SDL_InitJoysticks(void) SDL_joysticks_initialized = true; - SDL_InitGamepadMappings(); + SDL_joystick_names = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, SDL_DestroyHashValue, NULL); + SDL_LoadVIDPIDList(&old_xboxone_controllers); SDL_LoadVIDPIDList(&arcadestick_devices); SDL_LoadVIDPIDList(&blacklist_devices); SDL_LoadVIDPIDList(&flightstick_devices); @@ -667,6 +848,8 @@ bool SDL_InitJoysticks(void) SDL_LoadVIDPIDList(&wheel_devices); SDL_LoadVIDPIDList(&zero_centered_devices); + SDL_InitGamepadMappings(); + // See if we should allow joystick events while in the background SDL_AddHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, SDL_JoystickAllowBackgroundEventsChanged, NULL); @@ -802,20 +985,54 @@ const SDL_SteamVirtualGamepadInfo *SDL_GetJoystickVirtualGamepadInfoForID(SDL_Jo /* * Get the implementation dependent name of a joystick */ -const char *SDL_GetJoystickNameForID(SDL_JoystickID instance_id) +static const char *SDL_UpdateJoystickNameForID(SDL_JoystickID instance_id) { SDL_JoystickDriver *driver; int device_index; - const char *name = NULL; + const char *current_name = NULL; const SDL_SteamVirtualGamepadInfo *info; - SDL_LockJoysticks(); + SDL_AssertJoysticksLocked(); + info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id); if (info) { - name = SDL_GetPersistentString(info->name); + current_name = info->name; } else if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { - name = SDL_GetPersistentString(driver->GetDeviceName(device_index)); + current_name = driver->GetDeviceName(device_index); } + + if (!SDL_joystick_names) { + return SDL_GetPersistentString(current_name); + } + + char *name = NULL; + bool found = SDL_FindInHashTable(SDL_joystick_names, (const void *)(uintptr_t)instance_id, (const void **)&name); + if (!current_name) { + if (!found) { + SDL_SetError("Joystick %" SDL_PRIu32 " not found", instance_id); + return NULL; + } + if (!name) { + // SDL_strdup() failed during insert + SDL_OutOfMemory(); + return NULL; + } + return name; + } + + if (!name || SDL_strcmp(name, current_name) != 0) { + name = SDL_strdup(current_name); + SDL_InsertIntoHashTable(SDL_joystick_names, (const void *)(uintptr_t)instance_id, name, true); + } + return name; +} + +const char *SDL_GetJoystickNameForID(SDL_JoystickID instance_id) +{ + const char *name; + + SDL_LockJoysticks(); + name = SDL_UpdateJoystickNameForID(instance_id); SDL_UnlockJoysticks(); return name; @@ -865,6 +1082,8 @@ static bool SDL_JoystickAxesCenteredAtZero(SDL_Joystick *joystick) { // printf("JOYSTICK '%s' VID/PID 0x%.4x/0x%.4x AXES: %d\n", joystick->name, vendor, product, joystick->naxes); + SDL_AssertJoysticksLocked(); + if (joystick->naxes == 2) { // Assume D-pad or thumbstick style axes are centered at 0 return true; @@ -1144,8 +1363,6 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id) 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)) { @@ -1483,9 +1700,17 @@ int SDL_GetNumJoystickHats(SDL_Joystick *joystick) */ int SDL_GetNumJoystickBalls(SDL_Joystick *joystick) { - CHECK_JOYSTICK_MAGIC(joystick, -1); + int result; - return joystick->nballs; + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, -1); + + result = joystick->nballs; + } + SDL_UnlockJoysticks(); + + return result; } /* @@ -2046,6 +2271,7 @@ void SDL_QuitJoysticks(void) SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, SDL_JoystickAllowBackgroundEventsChanged, NULL); + SDL_FreeVIDPIDList(&old_xboxone_controllers); SDL_FreeVIDPIDList(&arcadestick_devices); SDL_FreeVIDPIDList(&blacklist_devices); SDL_FreeVIDPIDList(&flightstick_devices); @@ -2057,6 +2283,11 @@ void SDL_QuitJoysticks(void) SDL_QuitGamepadMappings(); + if (SDL_joystick_names) { + SDL_DestroyHashTable(SDL_joystick_names); + SDL_joystick_names = NULL; + } + SDL_joysticks_quitting = false; SDL_joysticks_initialized = false; @@ -2166,6 +2397,8 @@ void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id) SDL_SetJoystickIDForPlayerIndex(player_index, instance_id); } + SDL_UpdateJoystickNameForID(instance_id); + { SDL_Event event; @@ -2459,6 +2692,8 @@ static void SendSteamHandleUpdateEvents(void) SDL_Joystick *joystick; const SDL_SteamVirtualGamepadInfo *info; + SDL_AssertJoysticksLocked(); + // Check to see if any Steam handles changed for (joystick = SDL_joysticks; joystick; joystick = joystick->next) { bool changed = false; @@ -2499,7 +2734,7 @@ void SDL_UpdateJoysticks(void) Uint64 now; SDL_Joystick *joystick; - if (!SDL_WasInit(SDL_INIT_JOYSTICK)) { + if (!SDL_joysticks_initialized) { return; } @@ -2779,10 +3014,14 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons } else if (vendor == 0x0001 && product == 0x0001) { type = SDL_GAMEPAD_TYPE_STANDARD; - } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT) { + } else if (vendor == USB_VENDOR_NINTENDO && + (product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT || + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT)) { type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT; - } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT) { + } else if (vendor == USB_VENDOR_NINTENDO && + (product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT || + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT)) { if (name && SDL_strstr(name, "NES Controller") != NULL) { // We don't have a type for the Nintendo Online NES Controller type = SDL_GAMEPAD_TYPE_STANDARD; @@ -2797,12 +3036,13 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT; } - } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) { + } else if (vendor == USB_VENDOR_NINTENDO && + (product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR || + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR)) { type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR; } else if (forUI && SDL_IsJoystickGameCube(vendor, product)) { - // We don't have a type for the Nintendo GameCube controller - type = SDL_GAMEPAD_TYPE_STANDARD; + type = SDL_GAMEPAD_TYPE_GAMECUBE; } else { switch (GuessControllerType(vendor, product)) { @@ -2904,78 +3144,12 @@ bool SDL_IsJoystickXboxOneElite(Uint16 vendor_id, Uint16 product_id) bool SDL_IsJoystickXboxSeriesX(Uint16 vendor_id, Uint16 product_id) { - if (vendor_id == USB_VENDOR_MICROSOFT) { - if (product_id == USB_PRODUCT_XBOX_SERIES_X || - product_id == USB_PRODUCT_XBOX_SERIES_X_BLE) { - return true; - } + // Most new controllers have the share button, so we'll default to true and + // have a list of older XBox One controllers that are known not to have it. + if (SDL_VIDPIDInList(vendor_id, product_id, &old_xboxone_controllers)) { + return false; } - if (vendor_id == USB_VENDOR_PDP) { - if (product_id == USB_PRODUCT_XBOX_SERIES_X_VICTRIX_GAMBIT || - product_id == USB_PRODUCT_XBOX_SERIES_X_PDP_BLUE || - product_id == USB_PRODUCT_XBOX_SERIES_X_PDP_AFTERGLOW) { - return true; - } - } - if (vendor_id == USB_VENDOR_POWERA_ALT) { - if ((product_id >= 0x2001 && product_id <= 0x201a) || - product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO2 || - product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO4 || - product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_USB || - product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_DONGLE || - product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_MOGA_XP_ULTRA || - product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_SPECTRA) { - return true; - } - } - 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_TAIKO_DRUM_CONTROLLER) { - return true; - } - } - if (vendor_id == USB_VENDOR_HP) { - if (product_id == USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX || - product_id == USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX_RGB) { - return true; - } - } - if (vendor_id == USB_VENDOR_RAZER) { - if (product_id == USB_PRODUCT_RAZER_WOLVERINE_V2 || - product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_CHROMA || - product_id == USB_PRODUCT_RAZER_WOLVERINE_V3_PRO) { - return true; - } - } - if (vendor_id == USB_VENDOR_THRUSTMASTER) { - if (product_id == USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_SERIES_X) { - return true; - } - } - if (vendor_id == USB_VENDOR_TURTLE_BEACH) { - if (product_id == USB_PRODUCT_TURTLE_BEACH_SERIES_X_REACT_R || - product_id == USB_PRODUCT_TURTLE_BEACH_SERIES_X_RECON) { - return true; - } - } - if (vendor_id == USB_VENDOR_8BITDO) { - if (product_id == USB_PRODUCT_8BITDO_XBOX_CONTROLLER1 || - product_id == USB_PRODUCT_8BITDO_XBOX_CONTROLLER2) { - return true; - } - } - if (vendor_id == USB_VENDOR_GAMESIR) { - if (product_id == USB_PRODUCT_GAMESIR_G7) { - return true; - } - } - if (vendor_id == USB_VENDOR_ASUS) { - if (product_id == USB_PRODUCT_ROG_RAIKIRI) { - return true; - } - } - return false; + return true; } bool SDL_IsJoystickBluetoothXboxOne(Uint16 vendor_id, Uint16 product_id) @@ -3054,7 +3228,9 @@ bool SDL_IsJoystickNintendoSwitchJoyConGrip(Uint16 vendor_id, Uint16 product_id) bool SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product_id) { - return vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; + return vendor_id == USB_VENDOR_NINTENDO && + (product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR || + product_id == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR); } bool SDL_IsJoystickGameCube(Uint16 vendor_id, Uint16 product_id) @@ -3100,12 +3276,47 @@ bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id) return vendor_id == USB_VENDOR_HORI && (product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER || product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT); } +bool SDL_IsJoystickSInputController(Uint16 vendor_id, Uint16 product_id) +{ + if (vendor_id == USB_VENDOR_RASPBERRYPI) { + if (product_id == USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC || + product_id == USB_PRODUCT_HANDHELDLEGEND_PROGCC || + product_id == USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE || + product_id == USB_PRODUCT_BONZIRICHANNEL_FIREBIRD || + product_id == USB_PRODUCT_VOIDGAMING_PS4FIREBIRD) { + return true; + } + } + return false; +} + +bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id) +{ + if (vendor_id == USB_VENDOR_FLYDIGI_V1) { + if (product_id == USB_PRODUCT_FLYDIGI_V1_GAMEPAD) { + return true; + } + } + if (vendor_id == USB_VENDOR_FLYDIGI_V2) { + if (product_id == USB_PRODUCT_FLYDIGI_V2_APEX || product_id == USB_PRODUCT_FLYDIGI_V2_VADER) { + return true; + } + } + return false; +} + bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id) { EControllerType eType = GuessControllerType(vendor_id, product_id); return eType == k_eControllerType_SteamControllerNeptune; } +bool SDL_IsJoystickSteamTriton(Uint16 vendor_id, Uint16 product_id) +{ + EControllerType eType = GuessControllerType(vendor_id, product_id); + return eType == k_eControllerType_SteamControllerTriton; +} + bool SDL_IsJoystickXInput(SDL_GUID guid) { return (guid.data[14] == 'x') ? true : false; @@ -3136,7 +3347,7 @@ bool SDL_IsJoystickVIRTUAL(SDL_GUID guid) return (guid.data[14] == 'v') ? true : false; } -static bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id) +bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id) { return SDL_VIDPIDInList(vendor_id, product_id, &wheel_devices); } @@ -3672,9 +3883,7 @@ static void SDL_LoadVIDPIDListFromHint(const char *hint, int *num_entries, int * (*entries)[(*num_entries)++] = entry; } - if (file) { - SDL_free(file); - } + SDL_free(file); } void SDL_LoadVIDPIDListFromHints(SDL_vidpid_list *list, const char *included_list, const char *excluded_list) @@ -3752,9 +3961,15 @@ void SDL_LoadVIDPIDList(SDL_vidpid_list *list) if (list->included_hint_name) { included_list = SDL_GetHint(list->included_hint_name); + if (!included_list) { + included_list = SDL_getenv_unsafe(list->included_hint_name); + } } if (list->excluded_hint_name) { excluded_list = SDL_GetHint(list->excluded_hint_name); + if (!excluded_list) { + excluded_list = SDL_getenv_unsafe(list->excluded_hint_name); + } } SDL_LoadVIDPIDListFromHints(list, included_list, excluded_list); } diff --git a/libs/SDL3/src/joystick/SDL_joystick_c.h b/libs/SDL3/src/joystick/SDL_joystick_c.h index d931cf7..55665db 100644 --- a/libs/SDL3/src/joystick/SDL_joystick_c.h +++ b/libs/SDL3/src/joystick/SDL_joystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -135,9 +135,18 @@ extern bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id); // Function to return whether a joystick is a HORI Steam controller extern bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id); +// Function to return whether a joystick is an SInput (Open Format) controller +extern bool SDL_IsJoystickSInputController(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick is a Flydigi controller +extern bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id); + // Function to return whether a joystick is a Steam Deck extern bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id); +// Function to return whether a joystick is a Steam Triton +extern bool SDL_IsJoystickSteamTriton(Uint16 vendor_id, Uint16 product_id); + // Function to return whether a joystick guid comes from the XInput driver extern bool SDL_IsJoystickXInput(SDL_GUID guid); @@ -156,6 +165,9 @@ extern bool SDL_IsJoystickRAWINPUT(SDL_GUID guid); // Function to return whether a joystick guid comes from the Virtual driver extern bool SDL_IsJoystickVIRTUAL(SDL_GUID guid); +// Function to return whether a joystick is a wheel +bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id); + // Function to return whether a joystick should be ignored extern bool SDL_ShouldIgnoreJoystick(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name); diff --git a/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.c b/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.c index 6b253de..7d1ef1a 100644 --- a/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.c +++ b/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -23,6 +23,9 @@ #include "SDL_joystick_c.h" #include "SDL_steam_virtual_gamepad.h" +#ifdef SDL_PLATFORM_LINUX +#include "../core/unix/SDL_appid.h" +#endif #ifdef SDL_PLATFORM_WIN32 #include "../core/windows/SDL_windows.h" #else @@ -30,8 +33,6 @@ #include #endif -#define SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE "SteamVirtualGamepadInfo" - static char *SDL_steam_virtual_gamepad_info_file SDL_GUARDED_BY(SDL_joystick_lock) = NULL; static Uint64 SDL_steam_virtual_gamepad_info_file_mtime SDL_GUARDED_BY(SDL_joystick_lock) = 0; static Uint64 SDL_steam_virtual_gamepad_info_check_time SDL_GUARDED_BY(SDL_joystick_lock) = 0; @@ -132,8 +133,17 @@ void SDL_InitSteamVirtualGamepadInfo(void) return; } - file = SDL_GetHint(SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE); + file = SDL_getenv_unsafe("SteamVirtualGamepadInfo"); if (file && *file) { +#ifdef SDL_PLATFORM_LINUX + // Older versions of Wine will blacklist the Steam Virtual Gamepad if + // it appears to have the real controller's VID/PID, so ignore this. + const char *exe = SDL_GetExeName(); + if (exe && SDL_strcmp(exe, "wine64-preloader") == 0) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Wine launched by Steam, ignoring SteamVirtualGamepadInfo"); + return; + } +#endif SDL_steam_virtual_gamepad_info_file = SDL_strdup(file); } SDL_UpdateSteamVirtualGamepadInfo(); diff --git a/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.h b/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.h index 65696ea..35135f5 100644 --- a/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.h +++ b/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/SDL_sysjoystick.h b/libs/SDL3/src/joystick/SDL_sysjoystick.h index 041ebc3..47ce42c 100644 --- a/libs/SDL3/src/joystick/SDL_sysjoystick.h +++ b/libs/SDL3/src/joystick/SDL_sysjoystick.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/android/SDL_sysjoystick.c b/libs/SDL3/src/joystick/android/SDL_sysjoystick.c index 9a3402e..feba3cd 100644 --- a/libs/SDL3/src/joystick/android/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/android/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -106,7 +106,8 @@ static int keycode_to_SDL(int keycode) button = SDL_GAMEPAD_BUTTON_GUIDE; break; case AKEYCODE_BUTTON_L2: - button = 15; + case AKEYCODE_MEDIA_RECORD: + button = SDL_GAMEPAD_BUTTON_MISC1; break; case AKEYCODE_BUTTON_R2: button = 16; @@ -305,7 +306,7 @@ bool Android_OnHat(int device_id, int hat_id, int x, int y) return false; } -void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, bool can_rumble) +void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, bool can_rumble, bool has_rgb_led) { SDL_joylist_item *item; SDL_GUID guid; @@ -328,6 +329,10 @@ void Android_AddJoystick(int device_id, const char *name, const char *desc, int goto done; } + if (SDL_ShouldIgnoreJoystick(vendor_id, product_id, 0, name)) { + goto done; + } + #ifdef DEBUG_JOYSTICK SDL_Log("Joystick: %s, descriptor %s, vendor = 0x%.4x, product = 0x%.4x, %d axes, %d hats", name, desc, vendor_id, product_id, naxes, nhats); #endif @@ -376,6 +381,7 @@ void Android_AddJoystick(int device_id, const char *name, const char *desc, int item->naxes = naxes; item->nhats = nhats; item->can_rumble = can_rumble; + item->has_rgb_led = has_rgb_led; item->device_instance = SDL_GetNextObjectID(); if (!SDL_joylist_tail) { SDL_joylist = SDL_joylist_tail = item; @@ -577,6 +583,10 @@ static bool ANDROID_JoystickOpen(SDL_Joystick *joystick, int device_index) SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); } + if (item->has_rgb_led) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true); + } + return true; } @@ -603,7 +613,15 @@ static bool ANDROID_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_r static bool ANDROID_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) { - return SDL_Unsupported(); + SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; + if (!item) { + return SDL_SetError("SetLED failed, device disconnected"); + } + if (!item->has_rgb_led) { + return SDL_Unsupported(); + } + Android_JNI_JoystickSetLED(item->device_id, red, green, blue); + return true; } static bool ANDROID_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) diff --git a/libs/SDL3/src/joystick/android/SDL_sysjoystick_c.h b/libs/SDL3/src/joystick/android/SDL_sysjoystick_c.h index febb228..48f7ae2 100644 --- a/libs/SDL3/src/joystick/android/SDL_sysjoystick_c.h +++ b/libs/SDL3/src/joystick/android/SDL_sysjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -32,7 +32,7 @@ extern bool Android_OnPadDown(int device_id, int keycode); extern bool Android_OnPadUp(int device_id, int keycode); extern bool Android_OnJoy(int device_id, int axisnum, float value); extern bool Android_OnHat(int device_id, int hat_id, int x, int y); -extern void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, bool can_rumble); +extern void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, bool can_rumble, bool has_rgb_led); extern void Android_RemoveJoystick(int device_id); // A linked list of available joysticks @@ -46,6 +46,7 @@ typedef struct SDL_joylist_item int nbuttons, naxes, nhats; int dpad_state; bool can_rumble; + bool has_rgb_led; struct SDL_joylist_item *next; } SDL_joylist_item; diff --git a/libs/SDL3/src/joystick/apple/SDL_mfijoystick.m b/libs/SDL3/src/joystick/apple/SDL_mfijoystick.m index 811a9f1..2ef055e 100644 --- a/libs/SDL3/src/joystick/apple/SDL_mfijoystick.m +++ b/libs/SDL3/src/joystick/apple/SDL_mfijoystick.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -27,6 +27,10 @@ #include "../usb_ids.h" #include "../../events/SDL_events_c.h" +#ifdef SDL_VIDEO_DRIVER_UIKIT +#include "../../video/uikit/SDL_uikitvideo.h" +#endif + #include "SDL_mfijoystick_c.h" @@ -158,6 +162,13 @@ static bool IsControllerSwitchJoyConPair(GCController *controller) } return false; } +static bool IsControllerNVIDIASHIELD(GCController *controller) +{ + if ([controller.vendorName hasPrefix:@"NVIDIA Controller"]) { + return true; + } + return false; +} static bool IsControllerStadia(GCController *controller) { if ([controller.vendorName hasPrefix:@"Stadia"]) { @@ -302,6 +313,12 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle if (controller.vendorName) { name = controller.vendorName.UTF8String; + } else { + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if (controller.productCategory) { + name = controller.productCategory.UTF8String; + } + } } if (!name) { @@ -314,7 +331,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle NSLog(@"Product name: %@\n", controller.vendorName); NSLog(@"Product category: %@\n", controller.productCategory); NSLog(@"Elements available:\n"); - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { NSDictionary *elements = controller.physicalInputProfile.elements; for (id key in controller.physicalInputProfile.buttons) { NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital"); @@ -333,6 +350,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle device->is_ps5 = IsControllerPS5(controller); device->is_switch_pro = IsControllerSwitchPro(controller); device->is_switch_joycon_pair = IsControllerSwitchJoyConPair(controller); + device->is_shield = IsControllerNVIDIASHIELD(controller); device->is_stadia = IsControllerStadia(controller); device->is_backbone_one = IsControllerBackboneOne(controller); device->is_switch_joyconL = IsControllerSwitchJoyConL(controller); @@ -344,9 +362,18 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle (device->is_ps5 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS5)) || (device->is_switch_pro && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO)) || (device->is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 0, "")) || + (device->is_shield && HIDAPI_IsDevicePresent(USB_VENDOR_NVIDIA, USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104, 0, "")) || (device->is_stadia && HIDAPI_IsDevicePresent(USB_VENDOR_GOOGLE, USB_PRODUCT_GOOGLE_STADIA_CONTROLLER, 0, "")) || (device->is_switch_joyconL && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT, 0, "")) || - (device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, ""))) { + (device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, "")) || + (SDL_strstr(name, "GameCube Controller Adapter") && + (HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER, 0, "") || + HIDAPI_IsDevicePresent(USB_VENDOR_DRAGONRISE, USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1, 0, "") || + HIDAPI_IsDevicePresent(USB_VENDOR_DRAGONRISE, USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2, 0, "") || + HIDAPI_IsDevicePresent(USB_VENDOR_DRAGONRISE, USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3, 0, ""))) || + (SDL_strcmp(name, "8Bitdo SN30 Pro") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO_BT, 0, ""))) || + (SDL_strcmp(name, "8BitDo Pro 2") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2_BT, 0, ""))) || + (SDL_startswith(name, "8BitDo Ultimate 2 Wireless") && HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS, 0, ""))) { // The HIDAPI driver is taking care of this device return false; } @@ -362,7 +389,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle return false; } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) { device->has_dualshock_touchpad = TRUE; } @@ -417,7 +444,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle } else if (device->is_switch_joyconR) { vendor = USB_VENDOR_NINTENDO; product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT; - } else if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + } else if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { vendor = USB_VENDOR_APPLE; product = 4; subtype = 4; @@ -440,7 +467,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle return false; } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { NSDictionary *elements = controller.physicalInputProfile.elements; // Provide both axes and analog buttons as SDL axes @@ -480,7 +507,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle subtype = 4; #ifdef DEBUG_CONTROLLER_PROFILE - NSLog(@"Elements used:\n", controller.vendorName); + NSLog(@"Elements used:\n"); for (id key in device->buttons) { NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital"); } @@ -571,7 +598,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle } Uint16 signature; - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { signature = 0; signature = SDL_crc16(signature, device->name, SDL_strlen(device->name)); for (id key in device->axes) { @@ -725,7 +752,7 @@ static bool IOS_JoystickInit(void) } #ifdef SDL_PLATFORM_MACOS - if (@available(macOS 10.16, *)) { + if (@available(macOS 11.0, *)) { // Continue with initialization on macOS 11+ } else { return true; @@ -782,6 +809,10 @@ static bool IOS_JoystickInit(void) SDL_UnlockJoysticks(); }]; #endif // SDL_JOYSTICK_MFI + +#ifdef SDL_VIDEO_DRIVER_UIKIT + UIKit_SetGameControllerInteraction(true); +#endif } return true; @@ -887,7 +918,7 @@ static bool IOS_JoystickOpen(SDL_Joystick *joystick, int device_index) }; } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = joystick->hwdata->controller; GCMotion *motion = controller.motion; if (motion && motion.hasRotationRate) { @@ -898,7 +929,7 @@ static bool IOS_JoystickOpen(SDL_Joystick *joystick, int device_index) } } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = joystick->hwdata->controller; for (id key in controller.physicalInputProfile.buttons) { GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key]; @@ -908,7 +939,7 @@ static bool IOS_JoystickOpen(SDL_Joystick *joystick, int device_index) } } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = device->controller; if (controller.light) { SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true); @@ -969,7 +1000,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) Uint64 timestamp = SDL_GetTicksNS(); #ifdef DEBUG_CONTROLLER_STATE - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (controller.physicalInputProfile) { for (id key in controller.physicalInputProfile.buttons) { GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key]; @@ -995,7 +1026,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) } #endif // DEBUG_CONTROLLER_STATE - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { NSDictionary *elements = controller.physicalInputProfile.elements; NSDictionary *buttons = controller.physicalInputProfile.buttons; @@ -1125,7 +1156,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) } } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (device->has_dualshock_touchpad) { GCControllerDirectionPad *dpad; @@ -1145,7 +1176,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) } } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCMotion *motion = controller.motion; if (motion && motion.sensorsActive) { float data[3]; @@ -1167,7 +1198,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) } } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCDeviceBattery *battery = controller.battery; if (battery) { SDL_PowerState state = SDL_POWERSTATE_UNKNOWN; @@ -1196,8 +1227,8 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) #ifdef SDL_JOYSTICK_MFI @interface SDL3_RumbleMotor : NSObject -@property(nonatomic, strong) CHHapticEngine *engine API_AVAILABLE(macos(10.16), ios(13.0), tvos(14.0)); -@property(nonatomic, strong) id player API_AVAILABLE(macos(10.16), ios(13.0), tvos(14.0)); +@property(nonatomic, strong) CHHapticEngine *engine API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0)); +@property(nonatomic, strong) id player API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0)); @property bool active; @end @@ -1208,7 +1239,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) - (void)cleanup { @autoreleasepool { - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (self.player != nil) { [self.player cancelAndReturnError:nil]; self.player = nil; @@ -1224,7 +1255,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) - (bool)setIntensity:(float)intensity { @autoreleasepool { - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { NSError *error = nil; CHHapticDynamicParameter *param; @@ -1271,7 +1302,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) } } -- (id)initWithController:(GCController *)controller locality:(GCHapticsLocality)locality API_AVAILABLE(macos(10.16), ios(14.0), tvos(14.0)) +- (id)initWithController:(GCController *)controller locality:(GCHapticsLocality)locality API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) { @autoreleasepool { NSError *error; @@ -1373,7 +1404,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) static SDL3_RumbleContext *IOS_JoystickInitRumble(GCController *controller) { @autoreleasepool { - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { SDL3_RumbleMotor *low_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftHandle]; SDL3_RumbleMotor *high_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightHandle]; SDL3_RumbleMotor *left_trigger_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftTrigger]; @@ -1400,7 +1431,7 @@ static bool IOS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumb return SDL_SetError("Controller is no longer connected"); } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (!device->rumble && device->controller && device->controller.haptics) { SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller); if (rumble) { @@ -1426,7 +1457,7 @@ static bool IOS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumbl return SDL_SetError("Controller is no longer connected"); } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (!device->rumble && device->controller && device->controller.haptics) { SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller); if (rumble) { @@ -1452,7 +1483,7 @@ static bool IOS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, U return SDL_SetError("Controller is no longer connected"); } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = device->controller; GCDeviceLight *light = controller.light; if (light) { @@ -1480,7 +1511,7 @@ static bool IOS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) return SDL_SetError("Controller is no longer connected"); } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = device->controller; GCMotion *motion = controller.motion; if (motion) { @@ -1531,7 +1562,7 @@ static void IOS_JoystickClose(SDL_Joystick *joystick) controller.controllerPausedHandler = nil; controller.playerIndex = -1; - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { for (id key in controller.physicalInputProfile.buttons) { GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key]; if ([button isBoundToSystemGesture]) { @@ -1573,6 +1604,10 @@ static void IOS_JoystickQuit(void) while (deviceList != NULL) { IOS_RemoveJoystickDevice(deviceList); } + +#ifdef SDL_VIDEO_DRIVER_UIKIT + UIKit_SetGameControllerInteraction(false); +#endif } numjoysticks = 0; @@ -1585,7 +1620,7 @@ static bool IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping * return false; } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { int axis = 0; for (id key in device->axes) { if ([(NSString *)key isEqualToString:@"Left Thumbstick X Axis"] || @@ -1722,7 +1757,7 @@ bool IOS_SupportedHIDDevice(IOHIDDeviceRef device) return false; } - if (@available(macOS 10.16, *)) { + if (@available(macOS 11.0, *)) { const int MAX_ATTEMPTS = 3; for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { if ([GCController supportsHIDDevice:device]) { @@ -1741,7 +1776,7 @@ bool IOS_SupportedHIDDevice(IOHIDDeviceRef device) /* NOLINTNEXTLINE(readability-non-const-parameter): getCString takes a non-const char* */ static void GetAppleSFSymbolsNameForElement(GCControllerElement *element, char *name) { - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (element) { [element.sfSymbolsName getCString:name maxLength:255 encoding:NSASCIIStringEncoding]; } @@ -1750,7 +1785,7 @@ static void GetAppleSFSymbolsNameForElement(GCControllerElement *element, char * static GCControllerDirectionPad *GetDirectionalPadForController(GCController *controller) { - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { return controller.physicalInputProfile.dpads[GCInputDirectionPad]; } @@ -1773,7 +1808,7 @@ const char *IOS_GetAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_Gamepad #ifdef SDL_JOYSTICK_MFI if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) { - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller; NSDictionary *elements = controller.physicalInputProfile.elements; switch (button) { @@ -1889,7 +1924,7 @@ const char *IOS_GetAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAx #ifdef SDL_JOYSTICK_MFI if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) { - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller; NSDictionary *elements = controller.physicalInputProfile.elements; switch (axis) { diff --git a/libs/SDL3/src/joystick/apple/SDL_mfijoystick_c.h b/libs/SDL3/src/joystick/apple/SDL_mfijoystick_c.h index 783b3f4..6e275c0 100644 --- a/libs/SDL3/src/joystick/apple/SDL_mfijoystick_c.h +++ b/libs/SDL3/src/joystick/apple/SDL_mfijoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -53,6 +53,7 @@ typedef struct joystick_hwdata bool is_switch_joycon_pair; bool is_switch_joyconL; bool is_switch_joyconR; + bool is_shield; bool is_stadia; bool is_backbone_one; int is_siri_remote; diff --git a/libs/SDL3/src/joystick/bsd/SDL_bsdjoystick.c b/libs/SDL3/src/joystick/bsd/SDL_bsdjoystick.c index b3fd3e9..75716e3 100644 --- a/libs/SDL3/src/joystick/bsd/SDL_bsdjoystick.c +++ b/libs/SDL3/src/joystick/bsd/SDL_bsdjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -710,10 +710,10 @@ static void BSD_JoystickUpdate(SDL_Joystick *joy) //default: // no-op } - SDL_PrivateJoystickHat(joy, 0, (dpad[0] * HAT_UP) | - (dpad[1] * HAT_DOWN) | - (dpad[2] * HAT_RIGHT) | - (dpad[3] * HAT_LEFT) ); + SDL_SendJoystickHat(timestamp, joy, 0, (dpad[0] * HAT_UP) | + (dpad[1] * HAT_DOWN) | + (dpad[2] * HAT_RIGHT) | + (dpad[3] * HAT_LEFT) ); #endif break; } diff --git a/libs/SDL3/src/joystick/controller_list.h b/libs/SDL3/src/joystick/controller_list.h index c6f4f42..5cf4e2c 100644 --- a/libs/SDL3/src/joystick/controller_list.h +++ b/libs/SDL3/src/joystick/controller_list.h @@ -159,6 +159,8 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x1532, 0x100b ), k_eControllerType_PS5Controller, NULL }, // Razer Wolverine V2 Pro (Wired) { MAKE_CONTROLLER_ID( 0x1532, 0x100c ), k_eControllerType_PS5Controller, NULL }, // Razer Wolverine V2 Pro (Wireless) { MAKE_CONTROLLER_ID( 0x1532, 0x1012 ), k_eControllerType_PS5Controller, NULL }, // Razer Kitsune + { MAKE_CONTROLLER_ID( 0x1532, 0x1024 ), k_eControllerType_PS5Controller, NULL }, // Razer Raiju V5 Pro (PS5 mode wired) + { MAKE_CONTROLLER_ID( 0x1532, 0x1026 ), k_eControllerType_PS5Controller, NULL }, // Razer Raiju V5 Pro (PS5 mode with dongle) { MAKE_CONTROLLER_ID( 0x3285, 0x0d18 ), k_eControllerType_PS5Controller, NULL }, // NACON Revolution 5 Pro (PS5 mode with dongle) { MAKE_CONTROLLER_ID( 0x3285, 0x0d19 ), k_eControllerType_PS5Controller, NULL }, // NACON Revolution 5 Pro (PS5 mode wired) { MAKE_CONTROLLER_ID( 0x358a, 0x0104 ), k_eControllerType_PS5Controller, NULL }, // Backbone One PlayStation Edition for iOS @@ -418,7 +420,7 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x20d6, 0x2012 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X Cuphead EnWired Controller - Mugman { MAKE_CONTROLLER_ID( 0x20d6, 0x2015 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Blue Hint { MAKE_CONTROLLER_ID( 0x20d6, 0x2016 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Green Hint - { MAKE_CONTROLLER_ID( 0x20d6, 0x2017 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Cntroller - Arctic Camo + { MAKE_CONTROLLER_ID( 0x20d6, 0x2017 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Arctic Camo { MAKE_CONTROLLER_ID( 0x20d6, 0x2018 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Arc Lightning { MAKE_CONTROLLER_ID( 0x20d6, 0x2019 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Royal Purple { MAKE_CONTROLLER_ID( 0x20d6, 0x201a ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Nebula @@ -435,14 +437,14 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x24c6, 0x592a ), k_eControllerType_XBoxOneController, NULL }, // BDA XB1 Spectra Pro { MAKE_CONTROLLER_ID( 0x24c6, 0x791a ), k_eControllerType_XBoxOneController, NULL }, // PowerA Fusion Fight Pad { MAKE_CONTROLLER_ID( 0x2dc8, 0x2002 ), k_eControllerType_XBoxOneController, NULL }, // 8BitDo Ultimate Wired Controller for Xbox - { MAKE_CONTROLLER_ID( 0x2dc8, 0x3106 ), k_eControllerType_XBoxOneController, NULL }, // 8Bitdo Ultimate Wired Controller. Windows, Android, Switch. + { MAKE_CONTROLLER_ID( 0x2dc8, 0x3106 ), k_eControllerType_XBoxOneController, NULL }, // 8BitDo Ultimate Wired Controller. Windows, Android, Switch. + { MAKE_CONTROLLER_ID( 0x2dc8, 0x310a ), k_eControllerType_XBoxOneController, NULL }, // 8BitDo Ultimate 2C Wireless Controller { MAKE_CONTROLLER_ID( 0x2e24, 0x0652 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin Duke { MAKE_CONTROLLER_ID( 0x2e24, 0x1618 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin Duke { MAKE_CONTROLLER_ID( 0x2e24, 0x1688 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin X91 { MAKE_CONTROLLER_ID( 0x146b, 0x0611 ), k_eControllerType_XBoxOneController, NULL }, // Xbox Controller Mode for NACON Revolution 3 // These have been added via Minidump for unrecognized Xinput controller assert - { MAKE_CONTROLLER_ID( 0x0000, 0x0000 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x045e, 0x02a2 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller - Microsoft VID { MAKE_CONTROLLER_ID( 0x0e6f, 0x1414 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x0e6f, 0x0159 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller @@ -468,7 +470,6 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x046d, 0x0291 ), k_eControllerType_XBox360Controller, NULL }, // logitech xinput { MAKE_CONTROLLER_ID( 0x0079, 0x18d3 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x0f0d, 0x00b1 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0x0001, 0x0001 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x0079, 0x188e ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x0079, 0x187c ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x0079, 0x189c ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller @@ -481,10 +482,8 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0xf0d, 0xed ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xf0d, 0xc0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xe6f, 0x152 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2a7 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x46d, 0x1007 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xe6f, 0x2b8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2a8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x79, 0x18a1 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller // Added from Minidumps 10-9-19 @@ -525,14 +524,7 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0xd62, 0x9a1b ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xe00, 0xe00 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xe6f, 0x12a ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2a1 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2a2 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2a5 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xe6f, 0x2b2 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2bd ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2bf ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2c0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2c6 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xf0d, 0x97 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xf0d, 0xba ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xf0d, 0xd8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller @@ -549,8 +541,11 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x05ac, 0x0002 ), k_eControllerType_AppleController, NULL }, // MFI Standard Gamepad (generic entry for iOS/tvOS) { MAKE_CONTROLLER_ID( 0x057e, 0x2006 ), k_eControllerType_SwitchJoyConLeft, NULL }, // Nintendo Switch Joy-Con (Left) + { MAKE_CONTROLLER_ID( 0x057e, 0x2067 ), k_eControllerType_SwitchJoyConLeft, NULL }, // Nintendo Switch 2 Joy-Con (Left) { MAKE_CONTROLLER_ID( 0x057e, 0x2007 ), k_eControllerType_SwitchJoyConRight, NULL }, // Nintendo Switch Joy-Con (Right) + { MAKE_CONTROLLER_ID( 0x057e, 0x2066 ), k_eControllerType_SwitchJoyConRight, NULL }, // Nintendo Switch 2 Joy-Con (Right) { MAKE_CONTROLLER_ID( 0x057e, 0x2008 ), k_eControllerType_SwitchJoyConPair, NULL }, // Nintendo Switch Joy-Con (Left+Right Combined) + { MAKE_CONTROLLER_ID( 0x057e, 0x2068 ), k_eControllerType_SwitchJoyConPair, NULL }, // Nintendo Switch 2 Joy-Con (Left+Right Combined) // This same controller ID is spoofed by many 3rd-party Switch controllers. // The ones we currently know of are: @@ -559,6 +554,7 @@ static const ControllerDescription_t arrControllers[] = { // * ZhiXu Gamepad Wireless // * Sunwaytek Wireless Motion Controller for Nintendo Switch { MAKE_CONTROLLER_ID( 0x057e, 0x2009 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Switch Pro Controller + { MAKE_CONTROLLER_ID( 0x057e, 0x2069 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Switch 2 Pro Controller //{ MAKE_CONTROLLER_ID( 0x057e, 0x2017 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online SNES Controller //{ MAKE_CONTROLLER_ID( 0x057e, 0x2019 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online N64 Controller //{ MAKE_CONTROLLER_ID( 0x057e, 0x201e ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online SEGA Genesis Controller @@ -583,6 +579,7 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x0e6f, 0x0186 ), k_eControllerType_SwitchProController, NULL }, // PDP Afterglow Wireless Switch Controller - working gyro. USB is for charging only. Many later "Wireless" line devices w/ gyro also use this vid/pid { MAKE_CONTROLLER_ID( 0x0e6f, 0x0187 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Rockcandy Wired Controller { MAKE_CONTROLLER_ID( 0x0e6f, 0x0188 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Afterglow Wired Deluxe+ Audio Controller + { MAKE_CONTROLLER_ID( 0x0e6f, 0x018c ), k_eControllerType_SwitchProController, "PDP REALMz Wireless Controller" }, // PDP REALMz Wireless Controller for Switch { MAKE_CONTROLLER_ID( 0x0f0d, 0x00aa ), k_eControllerType_SwitchInputOnlyController, NULL }, // HORI Real Arcade Pro V Hayabusa in Switch Mode { MAKE_CONTROLLER_ID( 0x20d6, 0xa711 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Wired Controller Plus/PowerA Wired Controller Nintendo GameCube Style { MAKE_CONTROLLER_ID( 0x20d6, 0xa712 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Fusion Fight Pad @@ -591,9 +588,10 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x20d6, 0xa715 ), k_eControllerType_SwitchInputOnlyController, NULL }, // Power A Fusion Wireless Arcade Stick (USB Mode) Over BT is shows up as 057e 2009 { MAKE_CONTROLLER_ID( 0x20d6, 0xa716 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Fusion Pro Controller - USB requires toggling switch on back of device { MAKE_CONTROLLER_ID( 0x20d6, 0xa718 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Nano Wired Controller - { MAKE_CONTROLLER_ID( 0x33dd, 0x0001 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Black - { MAKE_CONTROLLER_ID( 0x33dd, 0x0002 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch ?? - { MAKE_CONTROLLER_ID( 0x33dd, 0x0003 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Red + { MAKE_CONTROLLER_ID( 0x33dd, 0x0001 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Black + { MAKE_CONTROLLER_ID( 0x33dd, 0x0002 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch ?? + { MAKE_CONTROLLER_ID( 0x33dd, 0x0003 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Red + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00f0 ), k_eControllerType_SwitchInputOnlyController, NULL }, // HORI Taiko Controller For Switch // Valve products { MAKE_CONTROLLER_ID( 0x0000, 0x11fb ), k_eControllerType_MobileTouch, NULL }, // Streaming mobile touch virtual controls @@ -606,4 +604,8 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x28de, 0x1201 ), k_eControllerType_SteamControllerV2, NULL }, // Valve wired Steam Controller (HEADCRAB) { MAKE_CONTROLLER_ID( 0x28de, 0x1202 ), k_eControllerType_SteamControllerV2, NULL }, // Valve Bluetooth Steam Controller (HEADCRAB) { MAKE_CONTROLLER_ID( 0x28de, 0x1205 ), k_eControllerType_SteamControllerNeptune, NULL }, // Valve Steam Deck Builtin Controller + { MAKE_CONTROLLER_ID( 0x28de, 0x1302 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Triton Controller + { MAKE_CONTROLLER_ID( 0x28de, 0x1303 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Triton Controller (BLE) + { MAKE_CONTROLLER_ID( 0x28de, 0x1304 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Proteus Dongle (Proprietary) + { MAKE_CONTROLLER_ID( 0x28de, 0x1305 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Nereid Dongle (Proprietary) }; diff --git a/libs/SDL3/src/joystick/controller_type.h b/libs/SDL3/src/joystick/controller_type.h index 155c8ad..bb94840 100644 --- a/libs/SDL3/src/joystick/controller_type.h +++ b/libs/SDL3/src/joystick/controller_type.h @@ -39,6 +39,8 @@ typedef enum k_eControllerType_SteamControllerV2 = 3, k_eControllerType_SteamControllerNeptune = 4, + k_eControllerType_SteamControllerTriton = 10, + // Other Controllers k_eControllerType_UnknownNonSteamController = 30, k_eControllerType_XBox360Controller = 31, diff --git a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c index e87ab82..209f7d8 100644 --- a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c +++ b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h index 3a70d2b..ec017ea 100644 --- a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h +++ b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/dummy/SDL_sysjoystick.c b/libs/SDL3/src/joystick/dummy/SDL_sysjoystick.c index 8240d45..113df7a 100644 --- a/libs/SDL3/src/joystick/dummy/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/dummy/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick.c b/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick.c index b481d5d..42f12b0 100644 --- a/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -27,6 +27,7 @@ #include "SDL_sysjoystick_c.h" #include "../SDL_joystick_c.h" +#include "../usb_ids.h" static SDL_joylist_item *JoystickByIndex(int index); @@ -34,10 +35,60 @@ static SDL_joylist_item *SDL_joylist = NULL; static SDL_joylist_item *SDL_joylist_tail = NULL; static int numjoysticks = 0; +EM_JS(int, SDL_GetEmscriptenJoystickVendor, (int device_index), { + // Let's assume that if we're calling these function then the gamepad object definitely exists + let gamepad = navigator['getGamepads']()[device_index]; + + // Chrome, Edge, Opera: Wireless Controller (STANDARD GAMEPAD Vendor: 054c Product: 09cc) + let vendor_str = 'Vendor: '; + if (gamepad['id']['indexOf'](vendor_str) > 0) { + let vendor_str_index = gamepad['id']['indexOf'](vendor_str) + vendor_str['length']; + return parseInt(gamepad['id']['substr'](vendor_str_index, 4), 16); + } + + // Firefox, Safari: 046d-c216-Logitech Dual Action (or 46d-c216-Logicool Dual Action) + let id_split = gamepad['id']['split']('-'); + if (id_split['length'] > 1 && !isNaN(parseInt(id_split[0], 16))) { + return parseInt(id_split[0], 16); + } + + return 0; +}); + +EM_JS(int, SDL_GetEmscriptenJoystickProduct, (int device_index), { + let gamepad = navigator['getGamepads']()[device_index]; + + // Chrome, Edge, Opera: Wireless Controller (STANDARD GAMEPAD Vendor: 054c Product: 09cc) + let product_str = 'Product: '; + if (gamepad['id']['indexOf'](product_str) > 0) { + let product_str_index = gamepad['id']['indexOf'](product_str) + product_str['length']; + return parseInt(gamepad['id']['substr'](product_str_index, 4), 16); + } + + // Firefox, Safari: 046d-c216-Logitech Dual Action (or 46d-c216-Logicool Dual Action) + let id_split = gamepad['id']['split']('-'); + if (id_split['length'] > 1 && !isNaN(parseInt(id_split[1], 16))) { + return parseInt(id_split[1], 16); + } + + return 0; +}); + +EM_JS(int, SDL_IsEmscriptenJoystickXInput, (int device_index), { + let gamepad = navigator['getGamepads']()[device_index]; + + // Chrome, Edge, Opera: Xbox 360 Controller (XInput STANDARD GAMEPAD) + // Firefox: xinput + // TODO: Safari + return gamepad['id']['toLowerCase']()['indexOf']('xinput') >= 0; +}); + static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { SDL_joylist_item *item; int i; + Uint16 vendor, product; + bool is_xinput; SDL_LockJoysticks(); @@ -53,12 +104,32 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep SDL_zerop(item); item->index = gamepadEvent->index; - item->name = SDL_CreateJoystickName(0, 0, NULL, gamepadEvent->id); + vendor = SDL_GetEmscriptenJoystickVendor(gamepadEvent->index); + product = SDL_GetEmscriptenJoystickProduct(gamepadEvent->index); + is_xinput = SDL_IsEmscriptenJoystickXInput(gamepadEvent->index); + + // Use a generic VID/PID representing an XInput controller + if (!vendor && !product && is_xinput) { + vendor = USB_VENDOR_MICROSOFT; + product = USB_PRODUCT_XBOX360_XUSB_CONTROLLER; + } + + item->name = SDL_CreateJoystickName(vendor, product, NULL, gamepadEvent->id); if (!item->name) { SDL_free(item); goto done; } + if (vendor && product) { + item->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_UNKNOWN, vendor, product, 0, NULL, item->name, 0, 0); + } else { + item->guid = SDL_CreateJoystickGUIDForName(item->name); + } + + if (is_xinput) { + item->guid.data[14] = 'x'; // See SDL_IsJoystickXInput + } + item->mapping = SDL_strdup(gamepadEvent->mapping); if (!item->mapping) { SDL_free(item->name); @@ -66,19 +137,74 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep goto done; } - item->naxes = gamepadEvent->numAxes; - item->nbuttons = gamepadEvent->numButtons; + const int real_button_count = gamepadEvent->numButtons; + const int real_axis_count = gamepadEvent->numAxes; + int first_trigger_button = -1; + int first_hat_button = -1; + int num_buttons = gamepadEvent->numButtons; + int num_axes = gamepadEvent->numAxes; + bool triggers_are_buttons = false; + if ((SDL_strcmp(gamepadEvent->mapping, "standard") == 0) && (num_buttons >= 16)) { // maps to a game console gamepad layout, turn the d-pad into a hat, treat triggers as analog. + num_buttons -= 4; // 4 dpad buttons become a hat. + first_hat_button = 12; + + if (num_axes == 4) { // Chrome gives the triggers analog button values, Firefox exposes them as extra axes. Both have the digital buttons. + num_axes += 2; // the two trigger "buttons" + triggers_are_buttons = true; + } + + // dump the digital trigger buttons in any case. + first_trigger_button = 6; + num_buttons -= 2; + } + + item->first_hat_button = first_hat_button; + item->first_trigger_button = first_trigger_button; + item->triggers_are_buttons = triggers_are_buttons; + item->nhats = (first_hat_button >= 0) ? 1 : 0; + item->naxes = num_axes; + item->nbuttons = num_buttons; item->device_instance = SDL_GetNextObjectID(); item->timestamp = gamepadEvent->timestamp; - for (i = 0; i < item->naxes; i++) { + int buttonidx = 0; + for (i = 0; i < real_button_count; i++, buttonidx++) { + if (buttonidx == first_hat_button) { + buttonidx += 4; // skip these buttons, we're treating them as hat input. + } else if (buttonidx == first_trigger_button) { + buttonidx += 2; // skip these buttons, we're treating them as axes. + } + item->analogButton[i] = gamepadEvent->analogButton[buttonidx]; + item->digitalButton[i] = gamepadEvent->digitalButton[buttonidx]; + } + + for (i = 0; i < real_axis_count; i++) { item->axis[i] = gamepadEvent->axis[i]; } - for (i = 0; i < item->nbuttons; i++) { - item->analogButton[i] = gamepadEvent->analogButton[i]; - item->digitalButton[i] = gamepadEvent->digitalButton[i]; + if (item->triggers_are_buttons) { + item->axis[real_axis_count] = (gamepadEvent->analogButton[first_trigger_button] * 2.0f) - 1.0f; + item->axis[real_axis_count+1] = (gamepadEvent->analogButton[first_trigger_button+1] * 2.0f) - 1.0f; + } + + SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons. + if (first_hat_button != -1) { + Uint8 value = SDL_HAT_CENTERED; + // this currently expects the first button to be up, then down, then left, then right. + if (gamepadEvent->digitalButton[first_hat_button + 0]) { + value |= SDL_HAT_UP; + } + if (gamepadEvent->digitalButton[first_hat_button + 1]) { + value |= SDL_HAT_DOWN; + } + if (gamepadEvent->digitalButton[first_hat_button + 2]) { + value |= SDL_HAT_LEFT; + } + if (gamepadEvent->digitalButton[first_hat_button + 3]) { + value |= SDL_HAT_RIGHT; + } + item->hat = value; } if (!SDL_joylist_tail) { @@ -305,6 +431,7 @@ static SDL_JoystickID EMSCRIPTEN_JoystickGetDeviceInstanceID(int device_index) static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index) { SDL_joylist_item *item = JoystickByDeviceIndex(device_index); + bool rumble_available = false; if (!item) { return SDL_SetError("No such device"); @@ -317,12 +444,27 @@ static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index) joystick->hwdata = (struct joystick_hwdata *)item; item->joystick = joystick; - // HTML5 Gamepad API doesn't say anything about these - joystick->nhats = 0; - + // HTML5 Gamepad API doesn't offer hats, but we can fake it from the d-pad buttons on the "standard" mapping. + joystick->nhats = item->nhats; joystick->nbuttons = item->nbuttons; joystick->naxes = item->naxes; + rumble_available = MAIN_THREAD_EM_ASM_INT({ + let gamepads = navigator['getGamepads'](); + if (!gamepads) { + return 0; + } + let gamepad = gamepads[$0]; + if (!gamepad || !gamepad['vibrationActuator']) { + return 0; + } + return 1; + }, item->index); + + if (rumble_available) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + } + return true; } @@ -344,28 +486,65 @@ static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick) result = emscripten_get_gamepad_status(item->index, &gamepadState); if (result == EMSCRIPTEN_RESULT_SUCCESS) { if (gamepadState.timestamp == 0 || gamepadState.timestamp != item->timestamp) { - for (i = 0; i < item->nbuttons; i++) { - if (item->digitalButton[i] != gamepadState.digitalButton[i]) { - bool down = (gamepadState.digitalButton[i] != 0); + const int first_hat_button = item->first_hat_button; + const int first_trigger_button = item->first_trigger_button; + const int real_button_count = gamepadState.numButtons; + const int real_axis_count = gamepadState.numAxes; + + int buttonidx = 0; + for (i = 0; i < real_button_count; i++, buttonidx++) { + if (buttonidx == first_hat_button) { + buttonidx += 4; // skip these buttons, we're treating them as hat input. + } else if (buttonidx == first_trigger_button) { + buttonidx += 2; // skip these buttons, we're treating them as axes. + } + if (item->digitalButton[i] != gamepadState.digitalButton[buttonidx]) { + const bool down = (gamepadState.digitalButton[buttonidx] != 0); SDL_SendJoystickButton(timestamp, item->joystick, i, down); } // store values to compare them in the next update - item->analogButton[i] = gamepadState.analogButton[i]; - item->digitalButton[i] = gamepadState.digitalButton[i]; + item->analogButton[i] = gamepadState.analogButton[buttonidx]; + item->digitalButton[i] = gamepadState.digitalButton[buttonidx]; } - for (i = 0; i < item->naxes; i++) { + for (i = 0; i < real_axis_count; i++) { if (item->axis[i] != gamepadState.axis[i]) { - // do we need to do conversion? - SDL_SendJoystickAxis(timestamp, item->joystick, i, - (Sint16)(32767. * gamepadState.axis[i])); + SDL_SendJoystickAxis(timestamp, item->joystick, i, (Sint16)(32767.0f * gamepadState.axis[i])); + item->axis[i] = gamepadState.axis[i]; } - - // store to compare in next update - item->axis[i] = gamepadState.axis[i]; } + if (item->triggers_are_buttons) { + for (i = 0; i < 2; i++) { + if (item->axis[real_axis_count+i] != gamepadState.analogButton[first_trigger_button+i]) { + SDL_SendJoystickAxis(timestamp, item->joystick, real_axis_count+i, (Sint16)(32767.0f * ((gamepadState.analogButton[first_trigger_button+i] * 2.0f) - 1.0f))); + item->axis[real_axis_count+i] = gamepadState.analogButton[first_trigger_button+i]; + } + } + } + + SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons. + if (item->nhats) { + Uint8 value = SDL_HAT_CENTERED; + // this currently expects the first button to be up, then down, then left, then right. + if (gamepadState.digitalButton[first_hat_button + 0]) { + value |= SDL_HAT_UP; + } else if (gamepadState.digitalButton[first_hat_button + 1]) { + value |= SDL_HAT_DOWN; + } + if (gamepadState.digitalButton[first_hat_button + 2]) { + value |= SDL_HAT_LEFT; + } else if (gamepadState.digitalButton[first_hat_button + 3]) { + value |= SDL_HAT_RIGHT; + } + if (item->hat != value) { + item->hat = value; + SDL_SendJoystickHat(timestamp, item->joystick, 0, value); + } + } + + item->timestamp = gamepadState.timestamp; } } @@ -383,14 +562,34 @@ static void EMSCRIPTEN_JoystickClose(SDL_Joystick *joystick) static SDL_GUID EMSCRIPTEN_JoystickGetDeviceGUID(int device_index) { - // the GUID is just the name for now - const char *name = EMSCRIPTEN_JoystickGetDeviceName(device_index); - return SDL_CreateJoystickGUIDForName(name); + return JoystickByDeviceIndex(device_index)->guid; } static bool EMSCRIPTEN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) { - return SDL_Unsupported(); + SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; + + // clang-format off + bool result = MAIN_THREAD_EM_ASM_INT({ + let gamepads = navigator['getGamepads'](); + if (!gamepads) { + return 0; + } + let gamepad = gamepads[$0]; + if (!gamepad || !gamepad['vibrationActuator']) { + return 0; + } + + gamepad['vibrationActuator']['playEffect']('dual-rumble', { + 'startDelay': 0, + 'duration': 3000, + 'weakMagnitude': $2 / 0xFFFF, + 'strongMagnitude': $1 / 0xFFFF, + }); + return 1; + }, item->index, low_frequency_rumble, high_frequency_rumble); + + return result; } static bool EMSCRIPTEN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) diff --git a/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick_c.h b/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick_c.h index e03a27c..df1fda8 100644 --- a/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick_c.h +++ b/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -34,12 +34,18 @@ typedef struct SDL_joylist_item char *mapping; SDL_JoystickID device_instance; SDL_Joystick *joystick; + int first_hat_button; + int first_trigger_button; + bool triggers_are_buttons; + int nhats; + SDL_GUID guid; int nbuttons; int naxes; double timestamp; - double axis[64]; + double axis[64]; // !!! FIXME: don't hardcode 64 on all of these. double analogButton[64]; EM_BOOL digitalButton[64]; + Uint8 hat; // there is (currently) only ever one of these, faked from the d-pad buttons. struct SDL_joylist_item *next; } SDL_joylist_item; diff --git a/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.c b/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.cpp similarity index 80% rename from libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.c rename to libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.cpp index 6cf0a90..efe6795 100644 --- a/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.c +++ b/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -24,6 +24,7 @@ #include "../SDL_sysjoystick.h" #include "../usb_ids.h" +#include "../../core/windows/SDL_windows.h" #include "../../core/windows/SDL_gameinput.h" // Default value for SDL_HINT_JOYSTICK_GAMEINPUT @@ -33,6 +34,11 @@ #define SDL_GAMEINPUT_DEFAULT false #endif +// Enable sensor support in GameInput 2.0, once we have a device that can be used for testing +#if GAMEINPUT_API_VERSION >= 2 +//#define GAMEINPUT_SENSOR_SUPPORT +#endif + enum { SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE = 11 @@ -46,6 +52,7 @@ typedef struct GAMEINPUT_InternalDevice SDL_GUID guid; // generated by SDL SDL_JoystickID device_instance; // generated by SDL const GameInputDeviceInfo *info; + int steam_virtual_gamepad_slot; bool isAdded; bool isDeleteRequested; } GAMEINPUT_InternalDevice; @@ -66,7 +73,7 @@ typedef struct joystick_hwdata static GAMEINPUT_InternalList g_GameInputList = { NULL }; static IGameInput *g_pGameInput = NULL; -static GameInputCallbackToken g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE; +static GameInputCallbackToken g_GameInputCallbackToken = 0; static Uint64 g_GameInputTimestampOffset; static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info) @@ -77,6 +84,18 @@ static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info) return false; } +#if GAMEINPUT_API_VERSION >= 1 +static int GetSteamVirtualGamepadSlot(const char *device_path) +{ + int slot = -1; + + // The format for the raw input device path is documented here: + // https://partner.steamgames.com/doc/features/steam_controller/steam_input_gamepad_emulation_bestpractices + (void)SDL_sscanf(device_path, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%*X&%*X&%*X#%d#%*u", &slot); + return slot; +} +#endif // GAMEINPUT_API_VERSION >= 1 + static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) { GAMEINPUT_InternalDevice **devicelist = NULL; @@ -93,15 +112,22 @@ static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) SDL_AssertJoysticksLocked(); - info = IGameInputDevice_GetDeviceInfo(pDevice); - if (info->capabilities & GameInputDeviceCapabilityWireless) { +#if GAMEINPUT_API_VERSION >= 1 + HRESULT hr = pDevice->GetDeviceInfo(&info); + if (FAILED(hr)) { + return WIN_SetErrorFromHRESULT("IGameInputDevice::GetDeviceInfo", hr); + } +#else + info = pDevice->GetDeviceInfo(); +#endif + if (false /*info->capabilities & GameInputDeviceCapabilityWireless*/) { bus = SDL_HARDWARE_BUS_BLUETOOTH; } else { bus = SDL_HARDWARE_BUS_USB; } vendor = info->vendorId; product = info->productId; - version = (info->firmwareVersion.major << 8) | info->firmwareVersion.minor; + //version = (info->firmwareVersion.major << 8) | info->firmwareVersion.minor; if (SDL_JoystickHandledByAnotherDriver(&SDL_GAMEINPUT_JoystickDriver, vendor, product, version, "")) { return true; @@ -130,23 +156,30 @@ static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) // Generate a device path for (idx = 0; idx < APP_LOCAL_DEVICE_ID_SIZE; ++idx) { SDL_snprintf(tmp, SDL_arraysize(tmp), "%02hhX", info->deviceId.value[idx]); - SDL_strlcat(elem->path, tmp, SDL_arraysize(tmp)); - } - - if (info->deviceStrings) { - // In theory we could get the manufacturer and product strings here, but they're NULL for all the controllers I've tested + SDL_strlcat(elem->path, tmp, SDL_arraysize(elem->path)); } +#if GAMEINPUT_API_VERSION >= 1 if (info->displayName) { - // This could give us a product string, but it's NULL for all the controllers I've tested + product_string = info->displayName; } +#else + if (info->displayName) { + product_string = info->displayName->data; + } +#endif - IGameInputDevice_AddRef(pDevice); + pDevice->AddRef(); elem->device = pDevice; elem->name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string); elem->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, manufacturer_string, product_string, 'g', 0); elem->device_instance = SDL_GetNextObjectID(); elem->info = info; +#if GAMEINPUT_API_VERSION >= 1 + elem->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(info->pnpPath); +#else + elem->steam_virtual_gamepad_slot = -1; +#endif g_GameInputList.devices = devicelist; g_GameInputList.devices[g_GameInputList.count++] = elem; @@ -168,7 +201,7 @@ static bool GAMEINPUT_InternalRemoveByIndex(int idx) elem = g_GameInputList.devices[idx]; if (elem) { - IGameInputDevice_Release(elem->device); + elem->device->Release(); SDL_free(elem->name); SDL_free(elem); } @@ -199,8 +232,8 @@ static GAMEINPUT_InternalDevice *GAMEINPUT_InternalFindByIndex(int idx) static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback( _In_ GameInputCallbackToken callbackToken, - _In_ void* context, - _In_ IGameInputDevice* device, + _In_ void *context, + _In_ IGameInputDevice *device, _In_ uint64_t timestamp, _In_ GameInputDeviceStatus currentStatus, _In_ GameInputDeviceStatus previousStatus) @@ -232,10 +265,11 @@ static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback( } static void GAMEINPUT_JoystickDetect(void); +static void GAMEINPUT_JoystickQuit(void); static bool GAMEINPUT_JoystickInit(void) { - HRESULT hR; + HRESULT hr; if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT)) { return true; @@ -245,21 +279,27 @@ static bool GAMEINPUT_JoystickInit(void) return false; } - hR = IGameInput_RegisterDeviceCallback(g_pGameInput, - NULL, +#if GAMEINPUT_API_VERSION >= 2 + // Allow background controller input + // SDL manages focus policy at a higher level, so we can set this unconditionally. + g_pGameInput->SetFocusPolicy(GameInputEnableBackgroundInput | GameInputEnableBackgroundGuideButton | GameInputEnableBackgroundShareButton); +#endif + + hr = g_pGameInput->RegisterDeviceCallback(NULL, GameInputKindController, GameInputDeviceConnected, GameInputBlockingEnumeration, NULL, GAMEINPUT_InternalJoystickDeviceCallback, &g_GameInputCallbackToken); - if (FAILED(hR)) { - return SDL_SetError("IGameInput::RegisterDeviceCallback failure with HRESULT of %08lX", hR); + if (FAILED(hr)) { + GAMEINPUT_JoystickQuit(); + return WIN_SetErrorFromHRESULT("IGameInput::RegisterDeviceCallback", hr); } // Calculate the relative offset between SDL timestamps and GameInput timestamps Uint64 now = SDL_GetTicksNS(); - uint64_t timestampUS = IGameInput_GetCurrentTimestamp(g_pGameInput); + uint64_t timestampUS = g_pGameInput->GetCurrentTimestamp(); g_GameInputTimestampOffset = (SDL_NS_TO_US(now) - timestampUS); GAMEINPUT_JoystickDetect(); @@ -292,7 +332,7 @@ static void GAMEINPUT_JoystickDetect(void) elem->isAdded = true; } - if (elem->isDeleteRequested || !(IGameInputDevice_GetDeviceStatus(elem->device) & GameInputDeviceConnected)) { + if (elem->isDeleteRequested || !(elem->device->GetDeviceStatus() & GameInputDeviceConnected)) { SDL_PrivateJoystickRemoved(elem->device_instance); GAMEINPUT_InternalRemoveByIndex(idx--); } @@ -333,7 +373,7 @@ static const char *GAMEINPUT_JoystickGetDevicePath(int device_index) static int GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) { - return -1; + return GAMEINPUT_InternalFindByIndex(device_index)->steam_virtual_gamepad_slot; } static int GAMEINPUT_JoystickGetDevicePlayerIndex(int device_index) @@ -357,6 +397,7 @@ static SDL_JoystickID GAMEINPUT_JoystickGetDeviceInstanceID(int device_index) static void GAMEINPUT_UpdatePowerInfo(SDL_Joystick *joystick, IGameInputDevice *device) { +#if 0 GameInputBatteryState battery_state; SDL_PowerState state; int percent = 0; @@ -385,10 +426,10 @@ static void GAMEINPUT_UpdatePowerInfo(SDL_Joystick *joystick, IGameInputDevice * percent = (int)SDL_roundf((battery_state.remainingCapacity / battery_state.fullChargeCapacity) * 100.0f); } SDL_SendJoystickPowerInfo(joystick, state, percent); +#endif } -#ifdef IGameInput_RegisterSystemButtonCallback - +#if GAMEINPUT_API_VERSION >= 1 static void CALLBACK GAMEINPUT_InternalSystemButtonCallback( _In_ GameInputCallbackToken callbackToken, _In_ void * context, @@ -415,8 +456,7 @@ static void CALLBACK GAMEINPUT_InternalSystemButtonCallback( SDL_UnlockJoysticks(); } } - -#endif // IGameInput_RegisterSystemButtonCallback +#endif // GAMEINPUT_API_VERSION >= 1 static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) { @@ -441,19 +481,15 @@ static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) joystick->nbuttons = 11; joystick->nhats = 1; -#ifdef IGameInput_RegisterSystemButtonCallback +#if GAMEINPUT_API_VERSION >= 1 if (info->supportedSystemButtons != GameInputSystemButtonNone) { if (info->supportedSystemButtons & GameInputSystemButtonShare) { ++joystick->nbuttons; } -#if 1 // The C macro in GameInput.h version 10.0.26100 refers to a focus policy which I guess has been removed from the final API? -#undef IGameInput_RegisterSystemButtonCallback -#define IGameInput_RegisterSystemButtonCallback(This, device, buttonFilter, context, callbackFunc, callbackToken) ((This)->lpVtbl->RegisterSystemButtonCallback(This, device, buttonFilter, context, callbackFunc, callbackToken)) -#endif - IGameInput_RegisterSystemButtonCallback(g_pGameInput, elem->device, (GameInputSystemButtonGuide | GameInputSystemButtonShare), joystick, GAMEINPUT_InternalSystemButtonCallback, &hwdata->system_button_callback_token); + g_pGameInput->RegisterSystemButtonCallback(elem->device, (GameInputSystemButtonGuide | GameInputSystemButtonShare), joystick, GAMEINPUT_InternalSystemButtonCallback, &hwdata->system_button_callback_token); } -#endif // IGameInput_RegisterSystemButtonCallback +#endif // GAMEINPUT_API_VERSION >= 1 } else { joystick->naxes = info->controllerAxisCount; joystick->nbuttons = info->controllerButtonCount; @@ -467,21 +503,17 @@ static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); } - if (info->supportedInput & GameInputKindTouch) { - SDL_PrivateJoystickAddTouchpad(joystick, info->touchPointCount); - } - - if (info->supportedInput & GameInputKindMotion) { +#ifdef GAMEINPUT_SENSOR_SUPPORT + if (info->supportedInput & GameInputKindSensors) { // FIXME: What's the sensor update rate? - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); - } - - if (info->capabilities & GameInputDeviceCapabilityWireless) { - joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; - } else { - joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED; + if (info->sensorsInfo->supportedSensors & GameInputSensorsGyrometer) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); + } + if (info->sensorsInfo->supportedSensors & GameInputSensorsAccelerometer) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); + } } +#endif return true; } @@ -492,7 +524,7 @@ static bool GAMEINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequenc GameInputRumbleParams *params = &hwdata->rumbleParams; params->lowFrequency = (float)low_frequency_rumble / (float)SDL_MAX_UINT16; params->highFrequency = (float)high_frequency_rumble / (float)SDL_MAX_UINT16; - IGameInputDevice_SetRumbleState(hwdata->devref->device, params); + hwdata->devref->device->SetRumbleState(params); return true; } @@ -503,7 +535,7 @@ static bool GAMEINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left GameInputRumbleParams *params = &hwdata->rumbleParams; params->leftTrigger = (float)left_rumble / (float)SDL_MAX_UINT16; params->rightTrigger = (float)right_rumble / (float)SDL_MAX_UINT16; - IGameInputDevice_SetRumbleState(hwdata->devref->device, params); + hwdata->devref->device->SetRumbleState(params); return true; } @@ -531,15 +563,15 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) IGameInputReading *reading = NULL; Uint64 timestamp; GameInputGamepadState state; - HRESULT hR; + HRESULT hr; - hR = IGameInput_GetCurrentReading(g_pGameInput, info->supportedInput, device, &reading); - if (FAILED(hR)) { + hr = g_pGameInput->GetCurrentReading(info->supportedInput, device, &reading); + if (FAILED(hr)) { // don't SetError here since there can be a legitimate case when there's no reading avail return; } - timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + g_GameInputTimestampOffset); + timestamp = SDL_US_TO_NS(reading->GetTimestamp() + g_GameInputTimestampOffset); if (GAMEINPUT_InternalIsGamepad(info)) { static WORD s_XInputButtons[] = { @@ -557,7 +589,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) }; Uint8 btnidx = 0, hat = 0; - if (IGameInputReading_GetGamepadState(reading, &state)) { + if (reading->GetGamepadState(&state)) { for (btnidx = 0; btnidx < SDL_arraysize(s_XInputButtons); ++btnidx) { WORD button_mask = s_XInputButtons[btnidx]; if (!button_mask) { @@ -599,7 +631,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) if (button_state) { uint32_t i; - uint32_t button_count = IGameInputReading_GetControllerButtonState(reading, info->controllerButtonCount, button_state); + uint32_t button_count = reading->GetControllerButtonState(info->controllerButtonCount, button_state); for (i = 0; i < button_count; ++i) { SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, button_state[i]); } @@ -609,7 +641,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) #define CONVERT_AXIS(v) (Sint16)((v)*65535.0f - 32768.0f) if (axis_state) { uint32_t i; - uint32_t axis_count = IGameInputReading_GetControllerAxisState(reading, info->controllerAxisCount, axis_state); + uint32_t axis_count = reading->GetControllerAxisState(info->controllerAxisCount, axis_state); for (i = 0; i < axis_count; ++i) { SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, CONVERT_AXIS(axis_state[i])); } @@ -619,7 +651,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) if (switch_state) { uint32_t i; - uint32_t switch_count = IGameInputReading_GetControllerSwitchState(reading, info->controllerSwitchCount, switch_state); + uint32_t switch_count = reading->GetControllerSwitchState(info->controllerSwitchCount, switch_state); for (i = 0; i < switch_count; ++i) { Uint8 hat; switch (switch_state[i]) { @@ -658,40 +690,47 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) } } - if (info->supportedInput & GameInputKindTouch) { - GameInputTouchState *touch_state = SDL_stack_alloc(GameInputTouchState, info->touchPointCount); - if (touch_state) { - uint32_t i; - uint32_t touch_count = IGameInputReading_GetTouchState(reading, info->touchPointCount, touch_state); - for (i = 0; i < touch_count; ++i) { - GameInputTouchState *touch = &touch_state[i]; - // FIXME: We should use touch->touchId to track fingers instead of using i below - SDL_SendJoystickTouchpad(timestamp, joystick, 0, i, true, touch->positionX * info->touchSensorInfo[i].resolutionX, touch->positionY * info->touchSensorInfo[0].resolutionY, touch->pressure); - } - SDL_stack_free(touch_state); - } - } - +#ifdef GAMEINPUT_SENSOR_SUPPORT if (hwdata->report_sensors) { - GameInputMotionState motion_state; + GameInputSensorsState sensor_state; - if (IGameInputReading_GetMotionState(reading, &motion_state)) { - // FIXME: How do we interpret the motion data? + if (reading->GetSensorsState(&sensor_state)) { + if ((info->sensorsInfo->supportedSensors & GameInputSensorsGyrometer) != 0) { + float data[3] = { + sensor_state.angularVelocityInRadPerSecX, + sensor_state.angularVelocityInRadPerSecY, + sensor_state.angularVelocityInRadPerSecZ + }; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, timestamp, data, SDL_arraysize(data)); + } + if ((info->sensorsInfo->supportedSensors & GameInputSensorsAccelerometer) != 0) { + float data[3] = { + sensor_state.accelerationInGX * SDL_STANDARD_GRAVITY, + sensor_state.accelerationInGY * SDL_STANDARD_GRAVITY, + sensor_state.accelerationInGZ * SDL_STANDARD_GRAVITY + }; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, data, SDL_arraysize(data)); + } } } +#endif // GAMEINPUT_SENSOR_SUPPORT - IGameInputReading_Release(reading); + reading->Release(); // FIXME: We can poll this at a much lower rate GAMEINPUT_UpdatePowerInfo(joystick, device); } -static void GAMEINPUT_JoystickClose(SDL_Joystick* joystick) +static void GAMEINPUT_JoystickClose(SDL_Joystick *joystick) { GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; if (hwdata->system_button_callback_token) { - IGameInput_UnregisterCallback(g_pGameInput, hwdata->system_button_callback_token, 5000); +#if GAMEINPUT_API_VERSION >= 1 + g_pGameInput->UnregisterCallback(hwdata->system_button_callback_token); +#else + g_pGameInput->UnregisterCallback(hwdata->system_button_callback_token, 10000); +#endif } SDL_free(hwdata); @@ -702,8 +741,14 @@ static void GAMEINPUT_JoystickQuit(void) { if (g_pGameInput) { // free the callback - IGameInput_UnregisterCallback(g_pGameInput, g_GameInputCallbackToken, /*timeoutInUs:*/ 10000); - g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE; + if (g_GameInputCallbackToken) { +#if GAMEINPUT_API_VERSION >= 1 + g_pGameInput->UnregisterCallback(g_GameInputCallbackToken); +#else + g_pGameInput->UnregisterCallback(g_GameInputCallbackToken, 10000); +#endif + g_GameInputCallbackToken = 0; + } // free the list while (g_GameInputList.count > 0) { @@ -738,7 +783,7 @@ static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMap out->back.kind = EMappingKind_Button; out->back.target = SDL_GAMEPAD_BUTTON_BACK; -#ifdef IGameInput_RegisterSystemButtonCallback +#if GAMEINPUT_API_VERSION >= 1 if (elem->info->supportedSystemButtons & GameInputSystemButtonGuide) { out->guide.kind = EMappingKind_Button; out->guide.target = SDL_GAMEPAD_BUTTON_GUIDE; @@ -748,7 +793,7 @@ static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMap out->misc1.kind = EMappingKind_Button; out->misc1.target = SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE; } -#endif +#endif // GAMEINPUT_API_VERSION >= 1 out->start.kind = EMappingKind_Button; out->start.target = SDL_GAMEPAD_BUTTON_START; diff --git a/libs/SDL3/src/joystick/haiku/SDL_haikujoystick.cc b/libs/SDL3/src/joystick/haiku/SDL_haikujoystick.cc index 977e4fc..c52d9de 100644 --- a/libs/SDL3/src/joystick/haiku/SDL_haikujoystick.cc +++ b/libs/SDL3/src/joystick/haiku/SDL_haikujoystick.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_8bitdo.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_8bitdo.c new file mode 100644 index 0000000..8d70f4a --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_8bitdo.c @@ -0,0 +1,711 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_8BITDO + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_8BITDO_PROTOCOL +#endif + +enum +{ + SDL_GAMEPAD_BUTTON_8BITDO_L4 = 11, + SDL_GAMEPAD_BUTTON_8BITDO_R4, + SDL_GAMEPAD_BUTTON_8BITDO_PL, + SDL_GAMEPAD_BUTTON_8BITDO_PR, + SDL_GAMEPAD_NUM_8BITDO_BUTTONS, +}; + +#define SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID 0x06 +#define SDL_8BITDO_REPORTID_SDL_REPORTID 0x04 +#define SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID 0x03 +#define SDL_8BITDO_BT_REPORTID_SDL_REPORTID 0x01 + +#define SDL_8BITDO_SENSOR_TIMESTAMP_ENABLE 0xAA +#define ABITDO_ACCEL_SCALE 4096.f +#define ABITDO_GYRO_MAX_DEGREES_PER_SECOND 2000.f + + +#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \ + (((Uint32)(B)) << 8) | \ + (((Uint32)(C)) << 16) | \ + (((Uint32)(D)) << 24)) + + +typedef struct +{ + bool sensors_supported; + bool sensors_enabled; + bool touchpad_01_supported; + bool touchpad_02_supported; + bool rumble_supported; + bool rumble_type; + bool rgb_supported; + bool player_led_supported; + bool powerstate_supported; + bool sensor_timestamp_supported; + Uint8 serial[6]; + Uint16 version; + Uint16 version_beta; + float accelScale; + float gyroScale; + Uint8 last_state[USB_PACKET_LENGTH]; + Uint64 sensor_timestamp; // Nanoseconds. Simulate onboard clock. Different models have different rates vs different connection styles. + Uint64 sensor_timestamp_interval; + Uint32 last_tick; +} SDL_Driver8BitDo_Context; + +#pragma pack(push,1) +typedef struct +{ + bool sensors_supported; + bool touchpad_01_supported; + bool touchpad_02_supported; + bool rumble_supported; + bool rumble_type; + bool rgb_supported; + Uint8 device_type; + Uint8 serial[6]; + Uint16 version; + Uint16 version_beta; + Uint16 pid; +} ABITDO_DEVICE_INFO; + +typedef struct +{ + // Accelerometer values + short sAccelX; + short sAccelY; + short sAccelZ; + + // Gyroscope values + short sGyroX; + short sGyroY; + short sGyroZ; +} ABITDO_SENSORS; + +#pragma pack(pop) + + +static void HIDAPI_Driver8BitDo_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, callback, userdata); +} + +static void HIDAPI_Driver8BitDo_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, callback, userdata); +} + +static bool HIDAPI_Driver8BitDo_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length) +{ + SDL_memset(report, 0, length); + report[0] = report_id; + return SDL_hid_get_feature_report(dev, report, length); +} + +static bool HIDAPI_Driver8BitDo_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (vendor_id == USB_VENDOR_8BITDO) { + switch (product_id) { + case USB_PRODUCT_8BITDO_SF30_PRO: + case USB_PRODUCT_8BITDO_SF30_PRO_BT: + case USB_PRODUCT_8BITDO_SN30_PRO: + case USB_PRODUCT_8BITDO_SN30_PRO_BT: + case USB_PRODUCT_8BITDO_PRO_2: + case USB_PRODUCT_8BITDO_PRO_2_BT: + case USB_PRODUCT_8BITDO_PRO_3: + case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: + return true; + default: + break; + } + } + return false; +} + +static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + device->context = ctx; + + if (device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { + // The Ultimate 2 Wireless v1.02 firmware has 12 byte reports, v1.03 firmware has 34 byte reports + const int ULTIMATE2_WIRELESS_V103_REPORT_SIZE = 34; + const int MAX_ATTEMPTS = 3; + + for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { + Uint8 data[USB_PACKET_LENGTH]; + int size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 80); + if (size == 0) { + // Try again + continue; + } + if (size >= ULTIMATE2_WIRELESS_V103_REPORT_SIZE) { + ctx->sensors_supported = true; + ctx->rumble_supported = true; + ctx->powerstate_supported = true; + } + break; + } + } else { + Uint8 data[USB_PACKET_LENGTH]; + const int MAX_ATTEMPTS = 5; + for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { + int size = ReadFeatureReport(device->dev, SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID, data, sizeof(data)); + if (size > 0) { +#ifdef DEBUG_8BITDO_PROTOCOL + HIDAPI_DumpPacket("8BitDo features packet: size = %d", data, size); +#endif + ctx->sensors_supported = true; + ctx->rumble_supported = true; + ctx->powerstate_supported = true; + + if (size >= 14 && data[13] == SDL_8BITDO_SENSOR_TIMESTAMP_ENABLE) { + ctx->sensor_timestamp_supported = true; + } + + // Set the serial number to the Bluetooth MAC address + if (size >= 12 && data[10] != 0) { + char serial[18]; + (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", + data[10], data[9], data[8], data[7], data[6], data[5]); + HIDAPI_SetDeviceSerial(device, serial); + } + break; + } + + // Try again + SDL_Delay(10); + } + } + + if (device->product_id == USB_PRODUCT_8BITDO_SF30_PRO || device->product_id == USB_PRODUCT_8BITDO_SF30_PRO_BT) { + HIDAPI_SetDeviceName(device, "8BitDo SF30 Pro"); + } else if (device->product_id == USB_PRODUCT_8BITDO_SN30_PRO || device->product_id == USB_PRODUCT_8BITDO_SN30_PRO_BT) { + HIDAPI_SetDeviceName(device, "8BitDo SN30 Pro"); + } else if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT) { + HIDAPI_SetDeviceName(device, "8BitDo Pro 2"); + } else if (device->product_id == USB_PRODUCT_8BITDO_PRO_3) { + HIDAPI_SetDeviceName(device, "8BitDo Pro 3"); + } + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_Driver8BitDo_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_Driver8BitDo_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static Uint64 HIDAPI_Driver8BitDo_GetIMURateForProductID(SDL_HIDAPI_Device *device) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + + // TODO: If sensor time stamp is sent, these fixed settings from observation can be replaced + switch (device->product_id) { + case USB_PRODUCT_8BITDO_SF30_PRO: + case USB_PRODUCT_8BITDO_SF30_PRO_BT: + case USB_PRODUCT_8BITDO_SN30_PRO: + case USB_PRODUCT_8BITDO_SN30_PRO_BT: + if (device->is_bluetooth) { + // Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool + return 70; // Observed to be anywhere between 60-90 hz. Possibly lossy in current state + } else if (ctx->sensor_timestamp_supported) { + // This firmware appears to update at 200 Hz over USB + return 200; + } else { + // This firmware appears to update at 100 Hz over USB + return 100; + } + case USB_PRODUCT_8BITDO_PRO_2: + case USB_PRODUCT_8BITDO_PRO_2_BT: // Note, labeled as "BT" but appears this way when wired. + case USB_PRODUCT_8BITDO_PRO_3: + if (device->is_bluetooth) { + // Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool + return 85; // Observed Bluetooth packet rate seems to be 80-90hz + } else if (ctx->sensor_timestamp_supported) { + // This firmware appears to update at 200 Hz over USB + return 200; + } else { + // This firmware appears to update at 100 Hz over USB + return 100; + } + case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: + if (device->is_bluetooth) { + // Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool + return 120; // Observed Bluetooth packet rate seems to be 120hz + } else { + // This firmware appears to update at 1000 Hz over USB dongle + return 1000; + } + default: + return 120; + } +} + +#ifndef DEG2RAD +#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) +#endif + +static bool HIDAPI_Driver8BitDo_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + SDL_zeroa(ctx->last_state); + + // Initialize the joystick capabilities + if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || + device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT || + device->product_id == USB_PRODUCT_8BITDO_PRO_3 || + device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { + // This controller has additional buttons + joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS; + } else { + joystick->nbuttons = 11; + } + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + if (ctx->sensors_supported) { + + // Different 8Bitdo controllers in different connection modes have different polling rates. + const Uint64 imu_polling_rate = HIDAPI_Driver8BitDo_GetIMURateForProductID(device); + ctx->sensor_timestamp_interval = SDL_NS_PER_SECOND / imu_polling_rate; + + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, (float)imu_polling_rate); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, (float)imu_polling_rate); + + ctx->accelScale = SDL_STANDARD_GRAVITY / ABITDO_ACCEL_SCALE; + // Hardware senses +/- N Degrees per second mapped to +/- INT16_MAX + ctx->gyroScale = DEG2RAD(ABITDO_GYRO_MAX_DEGREES_PER_SECOND) / INT16_MAX; + } + + return true; +} + +static bool HIDAPI_Driver8BitDo_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + if (ctx->rumble_supported) { + Uint8 rumble_packet[5] = { 0x05, 0x00, 0x00, 0x00, 0x00 }; + rumble_packet[1] = low_frequency_rumble >> 8; + rumble_packet[2] = high_frequency_rumble >> 8; + + if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; + } else { + return SDL_Unsupported(); + } +} + +static bool HIDAPI_Driver8BitDo_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_Driver8BitDo_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + Uint32 caps = 0; + if (ctx->rumble_supported) { + caps |= SDL_JOYSTICK_CAP_RUMBLE; + } + if (ctx->rgb_supported) { + caps |= SDL_JOYSTICK_CAP_RGB_LED; + } + return caps; +} + +static bool HIDAPI_Driver8BitDo_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_Driver8BitDo_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_Driver8BitDo_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + if (ctx->sensors_supported) { + ctx->sensors_enabled = enabled; + return true; + } + return SDL_Unsupported(); +} + +static void HIDAPI_Driver8BitDo_HandleOldStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[2] != data[2]) { + Uint8 hat; + + switch (data[2]) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + if (ctx->last_state[0] != data[0]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x80) != 0)); + } + + if (ctx->last_state[1] != data[1]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x40) != 0)); + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (data[1] & 0x01) ? SDL_MAX_SINT16 : SDL_MIN_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (data[1] & 0x02) ? SDL_MAX_SINT16 : SDL_MIN_SINT16); + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + axis = READ_STICK_AXIS(3); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(4); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(5); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(6); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + switch (data[0]) { + case SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID: // Firmware without enhanced mode + case SDL_8BITDO_REPORTID_SDL_REPORTID: // Enhanced mode USB report + case SDL_8BITDO_BT_REPORTID_SDL_REPORTID: // Enhanced mode Bluetooth report + break; + default: + // We don't know how to handle this report + return; + } + + if (ctx->last_state[1] != data[1]) { + Uint8 hat; + + switch (data[1]) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + + if (ctx->last_state[8] != data[8]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[8] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[8] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[8] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[8] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[8] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[8] & 0x80) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_PL, ((data[8] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_PR, ((data[8] & 0x04) != 0)); + } + + if (ctx->last_state[9] != data[9]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[9] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[9] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[9] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[9] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[9] & 0x40) != 0)); + } + + if (size > 10 && ctx->last_state[10] != data[10]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_L4, ((data[10] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_R4, ((data[10] & 0x02) != 0)); + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + axis = READ_STICK_AXIS(2); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(3); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(4); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(5); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + +#define READ_TRIGGER_AXIS(offset) \ + (Sint16)(((int)data[offset] * 257) - 32768) + { + axis = READ_TRIGGER_AXIS(7); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = READ_TRIGGER_AXIS(6); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + } +#undef READ_TRIGGER_AXIS + + if (ctx->powerstate_supported) { + SDL_PowerState state; + int percent; + Uint8 status = data[14] >> 7; + Uint8 level = (data[14] & 0x7f); + if (level == 100) { + status = 2; + } + switch (status) { + case 0: + state = SDL_POWERSTATE_ON_BATTERY; + percent = level; + break; + case 1: + state = SDL_POWERSTATE_CHARGING; + percent = level; + break; + case 2: + state = SDL_POWERSTATE_CHARGED; + percent = 100; + break; + default: + state = SDL_POWERSTATE_UNKNOWN; + percent = 0; + break; + } + SDL_SendJoystickPowerInfo(joystick, state, percent); + } + + if (ctx->sensors_enabled) { + Uint64 sensor_timestamp; + float values[3]; + ABITDO_SENSORS *sensors = (ABITDO_SENSORS *)&data[15]; + + if (ctx->sensor_timestamp_supported) { + Uint32 delta; + Uint32 tick = LOAD32(data[27], data[28], data[29], data[30]); + + if (ctx->last_tick) { + if (ctx->last_tick < tick) { + delta = (tick - ctx->last_tick); + } else { + delta = (SDL_MAX_UINT32 - ctx->last_tick + tick + 1); + } + // Sanity check the delta value + if (delta < 100000) { + ctx->sensor_timestamp_interval = SDL_US_TO_NS(delta); + } + } + ctx->last_tick = tick; + } + + // Note: we cannot use the time stamp of the receiving computer due to packet delay creating "spiky" timings. + // The imu time stamp is intended to be the sample time of the on-board hardware. + // In the absence of time stamp data from the data[], we can simulate that by + // advancing a time stamp by the observed/known imu clock rate. This is 8ms = 125 Hz + sensor_timestamp = ctx->sensor_timestamp; + ctx->sensor_timestamp += ctx->sensor_timestamp_interval; + + // This device's IMU values are reported differently from SDL + // Thus we perform a rotation of the coordinate system to match the SDL standard. + + // By observation of this device: + // Hardware x is reporting roll (rotation about the power jack's axis) + // Hardware y is reporting pitch (rotation about the horizontal axis) + // Hardware z is reporting yaw (rotation about the joysticks' center axis) + values[0] = -sensors->sGyroY * ctx->gyroScale; // Rotation around pitch axis + values[1] = sensors->sGyroZ * ctx->gyroScale; // Rotation around yaw axis + values[2] = -sensors->sGyroX * ctx->gyroScale; // Rotation around roll axis + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, values, 3); + + // By observation of this device: + // Accelerometer X is positive when front of the controller points toward the sky. + // Accelerometer y is positive when left side of the controller points toward the sky. + // Accelerometer Z is positive when sticks point toward the sky. + values[0] = -sensors->sAccelY * ctx->accelScale; // Acceleration along pitch axis + values[1] = sensors->sAccelZ * ctx->accelScale; // Acceleration along yaw axis + values[2] = -sensors->sAccelX * ctx->accelScale; // Acceleration along roll axis + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, values, 3); + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_Driver8BitDo_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_8BITDO_PROTOCOL + HIDAPI_DumpPacket("8BitDo packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + if (size == 9) { + // Old firmware USB report for the SF30 Pro and SN30 Pro controllers + HIDAPI_Driver8BitDo_HandleOldStatePacket(joystick, ctx, data, size); + } else { + HIDAPI_Driver8BitDo_HandleStatePacket(joystick, ctx, data, size); + } + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_Driver8BitDo_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +static void HIDAPI_Driver8BitDo_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_Driver8BitDo = { + SDL_HINT_JOYSTICK_HIDAPI_8BITDO, + true, + HIDAPI_Driver8BitDo_RegisterHints, + HIDAPI_Driver8BitDo_UnregisterHints, + HIDAPI_Driver8BitDo_IsEnabled, + HIDAPI_Driver8BitDo_IsSupportedDevice, + HIDAPI_Driver8BitDo_InitDevice, + HIDAPI_Driver8BitDo_GetDevicePlayerIndex, + HIDAPI_Driver8BitDo_SetDevicePlayerIndex, + HIDAPI_Driver8BitDo_UpdateDevice, + HIDAPI_Driver8BitDo_OpenJoystick, + HIDAPI_Driver8BitDo_RumbleJoystick, + HIDAPI_Driver8BitDo_RumbleJoystickTriggers, + HIDAPI_Driver8BitDo_GetJoystickCapabilities, + HIDAPI_Driver8BitDo_SetJoystickLED, + HIDAPI_Driver8BitDo_SendJoystickEffect, + HIDAPI_Driver8BitDo_SetJoystickSensorsEnabled, + HIDAPI_Driver8BitDo_CloseJoystick, + HIDAPI_Driver8BitDo_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_8BITDO + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_combined.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_combined.c index 5426edb..e9bbfd9 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_combined.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_combined.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -76,9 +76,7 @@ static bool HIDAPI_DriverCombined_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Jo child = device->children[i]; child->driver->CloseJoystick(child, joystick); } - if (serial) { - SDL_free(serial); - } + SDL_free(serial); return false; } @@ -102,9 +100,7 @@ static bool HIDAPI_DriverCombined_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Jo } // Update the joystick with the combined serial numbers - if (joystick->serial) { - SDL_free(joystick->serial); - } + SDL_free(joystick->serial); joystick->serial = serial; return true; diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.c new file mode 100644 index 0000000..96b8b96 --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.c @@ -0,0 +1,1007 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" +#include "SDL_hidapi_flydigi.h" + +#ifdef SDL_JOYSTICK_HIDAPI_FLYDIGI + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_FLYDIGI_PROTOCOL +#endif + +#ifndef DEG2RAD +#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) +#endif + +enum +{ + SDL_GAMEPAD_BUTTON_FLYDIGI_M1 = 11, + SDL_GAMEPAD_BUTTON_FLYDIGI_M2, + SDL_GAMEPAD_BUTTON_FLYDIGI_M3, + SDL_GAMEPAD_BUTTON_FLYDIGI_M4, + SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS +}; + +/* Rate of IMU Sensor Packets over wireless dongle observed in testcontroller at 1000hz */ +#define SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ 1000 +#define SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ) +/* Rate of IMU Sensor Packets over wired connection observed in testcontroller at 500hz */ +#define SENSOR_INTERVAL_VADER4_PRO_WIRED_RATE_HZ 500 +#define SENSOR_INTERVAL_VADER4_PRO_WIRED_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER4_PRO_WIRED_RATE_HZ) +/* Rate of IMU Sensor Packets over wired connection observed in testcontroller at 500hz */ +#define SENSOR_INTERVAL_VADER5_PRO_RATE_HZ 500 +#define SENSOR_INTERVAL_VADER5_PRO_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER5_PRO_RATE_HZ) + +/* Rate of IMU Sensor Packets over wireless dongle observed in testcontroller at 295hz */ +#define SENSOR_INTERVAL_APEX5_DONGLE_RATE_HZ 295 +#define SENSOR_INTERVAL_APEX5_DONGLE_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_APEX5_DONGLE_RATE_HZ) +/* Rate of IMU Sensor Packets over wired connection observed in testcontroller at 970hz */ +#define SENSOR_INTERVAL_APEX5_WIRED_RATE_HZ 970 +#define SENSOR_INTERVAL_APEX5_WIRED_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_APEX5_WIRED_RATE_HZ) + +#define FLYDIGI_ACQUIRE_CONTROLLER_HEARTBEAT_TIME 1000 * 30 + +#define FLYDIGI_V1_CMD_REPORT_ID 0x05 +#define FLYDIGI_V1_HAPTIC_COMMAND 0x0F +#define FLYDIGI_V1_GET_INFO_COMMAND 0xEC + +#define FLYDIGI_V2_CMD_REPORT_ID 0x03 +#define FLYDIGI_V2_MAGIC1 0x5A +#define FLYDIGI_V2_MAGIC2 0xA5 +#define FLYDIGI_V2_GET_INFO_COMMAND 0x01 +#define FLYDIGI_V2_GET_STATUS_COMMAND 0x10 +#define FLYDIGI_V2_SET_STATUS_COMMAND 0x11 +#define FLYDIGI_V2_HAPTIC_COMMAND 0x12 +#define FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND 0x1C +#define FLYDIGI_V2_INPUT_REPORT 0xEF + +typedef struct +{ + SDL_HIDAPI_Device *device; + Uint8 deviceID; + bool available; + bool has_cz; + bool has_lmrm; + bool has_circle; + bool wireless; + bool sensors_supported; + bool sensors_enabled; + Uint16 firmware_version; + Uint64 sensor_timestamp_ns; // Simulate onboard clock. Advance by known time step. Nanoseconds. + Uint64 sensor_timestamp_step_ns; // Based on observed rate of receipt of IMU sensor packets. + float accelScale; + float gyroScale; + Uint64 next_heartbeat; + Uint64 last_packet; + Uint8 last_state[USB_PACKET_LENGTH]; +} SDL_DriverFlydigi_Context; + + +static void HIDAPI_DriverFlydigi_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, callback, userdata); +} + +static void HIDAPI_DriverFlydigi_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, callback, userdata); +} + +static bool HIDAPI_DriverFlydigi_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverFlydigi_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (SDL_IsJoystickFlydigiController(vendor_id, product_id)) { + if (vendor_id == USB_VENDOR_FLYDIGI_V1) { + if (interface_number == 2) { + // Early controllers have their custom protocol on interface 2 + return true; + } + } else { + // Newer controllers have their custom protocol on interface 1 or 2, but + // only expose one HID interface, so we'll accept any interface we see. + return true; + } + } + return false; +} + +static void HIDAPI_DriverFlydigi_UpdateDeviceIdentity(SDL_HIDAPI_Device *device) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; + + Uint8 controller_type = SDL_FLYDIGI_UNKNOWN; + switch (ctx->deviceID) { + case 19: + controller_type = SDL_FLYDIGI_APEX2; + break; + case 24: + case 26: + case 29: + controller_type = SDL_FLYDIGI_APEX3; + break; + case 84: + controller_type = SDL_FLYDIGI_APEX4; + break; + case 20: + case 21: + case 23: + controller_type = SDL_FLYDIGI_VADER2; + break; + case 22: + controller_type = SDL_FLYDIGI_VADER2_PRO; + break; + case 28: + controller_type = SDL_FLYDIGI_VADER3; + break; + case 80: + case 81: + controller_type = SDL_FLYDIGI_VADER3_PRO; + break; + case 85: + case 91: + case 105: + controller_type = SDL_FLYDIGI_VADER4_PRO; + break; + case 128: + case 129: + controller_type = SDL_FLYDIGI_APEX5; + break; + case 130: + controller_type = SDL_FLYDIGI_VADER5_PRO; + break; + case 133: + case 134: + controller_type = SDL_FLYDIGI_APEX5; + break; + default: + // Try to guess from the name of the controller + if (SDL_strcasestr(device->name, "VADER") != NULL) { + if (SDL_strstr(device->name, "VADER2") != NULL) { + controller_type = SDL_FLYDIGI_VADER2; + } else if (SDL_strstr(device->name, "VADER3") != NULL) { + controller_type = SDL_FLYDIGI_VADER3; + } else if (SDL_strstr(device->name, "VADER4") != NULL) { + controller_type = SDL_FLYDIGI_VADER4_PRO; + } else if (SDL_strstr(device->name, "Vader 5") != NULL) { + controller_type = SDL_FLYDIGI_VADER5_PRO; + } + } else if (SDL_strstr(device->name, "APEX") != NULL) { + if (SDL_strstr(device->name, "APEX2") != NULL) { + controller_type = SDL_FLYDIGI_APEX2; + } else if (SDL_strstr(device->name, "APEX3") != NULL) { + controller_type = SDL_FLYDIGI_APEX3; + } else if (SDL_strstr(device->name, "APEX4") != NULL) { + controller_type = SDL_FLYDIGI_APEX4; + } else if (SDL_strstr(device->name, "APEX5") != NULL) { + controller_type = SDL_FLYDIGI_APEX5; + } + } + break; + } + device->guid.data[15] = controller_type; + + // This is the previous sensor default of 125hz. + // Override this in the switch statement below based on observed sensor packet rate. + ctx->sensor_timestamp_step_ns = SDL_NS_PER_SECOND / 125; + + switch (controller_type) { + case SDL_FLYDIGI_APEX2: + HIDAPI_SetDeviceName(device, "Flydigi Apex 2"); + break; + case SDL_FLYDIGI_APEX3: + HIDAPI_SetDeviceName(device, "Flydigi Apex 3"); + break; + case SDL_FLYDIGI_APEX4: + // The Apex 4 controller has sensors, but they're only reported when gyro mouse is enabled + HIDAPI_SetDeviceName(device, "Flydigi Apex 4"); + break; + case SDL_FLYDIGI_APEX5: + HIDAPI_SetDeviceName(device, "Flydigi Apex 5"); + ctx->has_lmrm = true; + ctx->sensors_supported = true; + ctx->accelScale = SDL_STANDARD_GRAVITY / 4096.0f; + ctx->gyroScale = DEG2RAD(2000.0f); + ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_APEX5_DONGLE_NS : SENSOR_INTERVAL_APEX5_WIRED_NS; + break; + case SDL_FLYDIGI_VADER2: + // The Vader 2 controller has sensors, but they're only reported when gyro mouse is enabled + HIDAPI_SetDeviceName(device, "Flydigi Vader 2"); + ctx->has_cz = true; + break; + case SDL_FLYDIGI_VADER2_PRO: + HIDAPI_SetDeviceName(device, "Flydigi Vader 2 Pro"); + ctx->has_cz = true; + break; + case SDL_FLYDIGI_VADER3: + HIDAPI_SetDeviceName(device, "Flydigi Vader 3"); + ctx->has_cz = true; + break; + case SDL_FLYDIGI_VADER3_PRO: + HIDAPI_SetDeviceName(device, "Flydigi Vader 3 Pro"); + ctx->has_cz = true; + ctx->sensors_supported = true; + ctx->accelScale = SDL_STANDARD_GRAVITY / 256.0f; + ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS : SENSOR_INTERVAL_VADER4_PRO_WIRED_NS; + break; + case SDL_FLYDIGI_VADER4_PRO: + HIDAPI_SetDeviceName(device, "Flydigi Vader 4 Pro"); + ctx->has_cz = true; + ctx->sensors_supported = true; + ctx->accelScale = SDL_STANDARD_GRAVITY / 256.0f; + ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS : SENSOR_INTERVAL_VADER4_PRO_WIRED_NS; + break; + case SDL_FLYDIGI_VADER5_PRO: + HIDAPI_SetDeviceName(device, "Flydigi Vader 5 Pro"); + ctx->has_cz = true; + ctx->has_lmrm = true; + ctx->has_circle = true; + ctx->sensors_supported = true; + ctx->accelScale = SDL_STANDARD_GRAVITY / 4096.0f; + ctx->gyroScale = DEG2RAD(2000.0f); + ctx->sensor_timestamp_step_ns = SENSOR_INTERVAL_VADER5_PRO_NS; + break; + default: + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Unknown FlyDigi controller with ID %d, name '%s'", ctx->deviceID, device->name); + break; + } +} + +static void HIDAPI_DriverFlydigi_SetAvailable(SDL_HIDAPI_Device *device, bool available) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; + + if (available == ctx->available) { + return; + } + + if (available) { + if (device->num_joysticks == 0) { + HIDAPI_JoystickConnected(device, NULL); + } + } else { + if (device->num_joysticks > 0) { + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + } + ctx->available = available; +} + +static bool HIDAPI_DriverFlydigi_InitControllerV1(SDL_HIDAPI_Device *device) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; + + // Detecting the Vader 2 can take over 1000 read retries, so be generous here + for (int attempt = 0; ctx->deviceID == 0 && attempt < 30; ++attempt) { + const Uint8 request[] = { FLYDIGI_V1_CMD_REPORT_ID, FLYDIGI_V1_GET_INFO_COMMAND, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + // This write will occasionally return -1, so ignore failure here and try again + (void)SDL_hid_write(device->dev, request, sizeof(request)); + + // Read the reply + for (int i = 0; i < 100; ++i) { + SDL_Delay(1); + + Uint8 data[USB_PACKET_LENGTH]; + int size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0); + if (size < 0) { + break; + } + if (size == 0) { + continue; + } + +#ifdef DEBUG_FLYDIGI_PROTOCOL + HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size); +#endif + if (size == 32 && data[15] == 236) { + ctx->deviceID = data[3]; + ctx->firmware_version = LOAD16(data[9], data[10]); + + char serial[9]; + (void)SDL_snprintf(serial, sizeof(serial), "%.2x%.2x%.2x%.2x", data[5], data[6], data[7], data[8]); + HIDAPI_SetDeviceSerial(device, serial); + + // The Vader 2 with firmware 6.0.4.9 doesn't report the connection state + if (ctx->firmware_version >= 0x6400) { + switch (data[13]) { + case 0: + // Wireless connection + ctx->wireless = true; + break; + case 1: + // Wired connection + ctx->wireless = false; + break; + default: + break; + } + } + + // Done! + break; + } + } + } + + HIDAPI_DriverFlydigi_UpdateDeviceIdentity(device); + + HIDAPI_DriverFlydigi_SetAvailable(device, true); + + return true; +} + +static bool GetReply(SDL_HIDAPI_Device* device, Uint8 command, Uint8* data, size_t length) +{ + for (int i = 0; i < 100; ++i) { + SDL_Delay(1); + + int size = SDL_hid_read_timeout(device->dev, data, length, 0); + if (size < 0) { + break; + } + if (size == 0) { + continue; + } + +#ifdef DEBUG_FLYDIGI_PROTOCOL + HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size); +#endif + + if (size == 32) { + if (data[1] == FLYDIGI_V2_MAGIC1 && data[2] == FLYDIGI_V2_MAGIC2) { + // Skip the report ID + SDL_memmove(&data[0], &data[1], size - 1); + data[size - 1] = 0; + } + if (data[0] == FLYDIGI_V2_MAGIC1 && data[1] == FLYDIGI_V2_MAGIC2 && data[2] == command) { + return true; + } + } + } + return false; +} + +static bool SDL_HIDAPI_Flydigi_SendStatusRequest(SDL_HIDAPI_Device *device) +{ + const Uint8 cmd[] = { + FLYDIGI_V2_CMD_REPORT_ID, + FLYDIGI_V2_MAGIC1, + FLYDIGI_V2_MAGIC2, + FLYDIGI_V2_GET_STATUS_COMMAND + }; + if (SDL_hid_write(device->dev, cmd, sizeof(cmd)) < 0) { + return SDL_SetError("Couldn't query controller status"); + } + return true; +} + +static void HIDAPI_DriverFlydigi_HandleStatusResponse(SDL_HIDAPI_Device *device, Uint8 *data, int size) +{ + if (data[9] == 1) { + HIDAPI_DriverFlydigi_SetAvailable(device, true); + } else { + // Click "Allow third-party apps to take over mappings" in the FlyDigi Space Station app + HIDAPI_DriverFlydigi_SetAvailable(device, false); + } +} + +static bool SDL_HIDAPI_Flydigi_SendAcquireRequest(SDL_HIDAPI_Device *device, bool acquire) +{ + const Uint8 cmd[32] = { + FLYDIGI_V2_CMD_REPORT_ID, + FLYDIGI_V2_MAGIC1, + FLYDIGI_V2_MAGIC2, + FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND, + 23, + acquire ? 1 : 0, + 'S', 'D', 'L', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + if (SDL_hid_write(device->dev, cmd, sizeof(cmd)) < 0) { + return SDL_SetError("Couldn't send acquire command"); + } + return true; +} + +static void HIDAPI_DriverFlydigi_HandleAcquireResponse(SDL_HIDAPI_Device *device, Uint8 *data, int size) +{ + if (data[5] != 1 && data[6] == 0) { + // Controller acquiring failed or has been disabled + HIDAPI_DriverFlydigi_SetAvailable(device, false); + } +} + +static bool HIDAPI_DriverFlydigi_InitControllerV2(SDL_HIDAPI_Device *device) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; + + Uint8 data[USB_PACKET_LENGTH]; + const Uint8 query_info[] = { FLYDIGI_V2_CMD_REPORT_ID, FLYDIGI_V2_MAGIC1, FLYDIGI_V2_MAGIC2, FLYDIGI_V2_GET_INFO_COMMAND, 2, 0 }; + if (SDL_hid_write(device->dev, query_info, sizeof(query_info)) < 0) { + return SDL_SetError("Couldn't query controller info"); + } + if (!GetReply(device, FLYDIGI_V2_GET_INFO_COMMAND, data, sizeof(data))) { + return SDL_SetError("Couldn't get controller info"); + } + + // Check the firmware version + Uint16 min_firmware_version; + ctx->firmware_version = LOAD16(data[16], data[15]); + switch (device->product_id) { + case USB_PRODUCT_FLYDIGI_V2_APEX: + // Minimum supported firmware version, Apex 5 + min_firmware_version = 0x7031; + break; + case USB_PRODUCT_FLYDIGI_V2_VADER: + // Minimum supported firmware version, Vader 5 Pro + min_firmware_version = 0x7141; + break; + default: + // Unknown product, presumably this version is okay? + min_firmware_version = 0; + break; + } + if (ctx->firmware_version < min_firmware_version) { + return SDL_SetError("Unsupported firmware version"); + } + + switch (data[6]) { + case 1: + // Wired connection + ctx->wireless = false; + break; + case 2: + // Wireless connection + ctx->wireless = true; + break; + default: + break; + } + ctx->deviceID = data[5]; + + HIDAPI_DriverFlydigi_UpdateDeviceIdentity(device); + + // See whether we can acquire the controller + SDL_HIDAPI_Flydigi_SendStatusRequest(device); + + return true; +} + +static bool HIDAPI_DriverFlydigi_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + + device->context = ctx; + + if (device->vendor_id == USB_VENDOR_FLYDIGI_V1) { + return HIDAPI_DriverFlydigi_InitControllerV1(device); + } else { + return HIDAPI_DriverFlydigi_InitControllerV2(device); + } +} + +static int HIDAPI_DriverFlydigi_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverFlydigi_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static bool HIDAPI_DriverFlydigi_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + SDL_zeroa(ctx->last_state); + + // Initialize the joystick capabilities + joystick->nbuttons = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS; + if (ctx->has_cz) { + joystick->nbuttons += 2; + } + if (ctx->has_lmrm) { + joystick->nbuttons += 2; + } + if (ctx->has_circle) { + joystick->nbuttons += 1; + } + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + if (ctx->wireless) { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; + } + + if (ctx->sensors_supported) { + const float flSensorRate = ctx->wireless ? (float)SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ : (float)SENSOR_INTERVAL_VADER4_PRO_WIRED_RATE_HZ; + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, flSensorRate); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, flSensorRate); + } + return true; +} + +static bool HIDAPI_DriverFlydigi_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + if (device->vendor_id == USB_VENDOR_FLYDIGI_V1) { + Uint8 rumble_packet[] = { FLYDIGI_V1_CMD_REPORT_ID, FLYDIGI_V1_HAPTIC_COMMAND, 0x00, 0x00 }; + rumble_packet[2] = low_frequency_rumble >> 8; + rumble_packet[3] = high_frequency_rumble >> 8; + + if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + } else { + Uint8 rumble_packet[] = { FLYDIGI_V2_CMD_REPORT_ID, FLYDIGI_V2_MAGIC1, FLYDIGI_V2_MAGIC2, FLYDIGI_V2_HAPTIC_COMMAND, 6, 0, 0, 0, 0, 0 }; + rumble_packet[5] = low_frequency_rumble >> 8; + rumble_packet[6] = high_frequency_rumble >> 8; + + if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + } + return true; +} + +static bool HIDAPI_DriverFlydigi_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverFlydigi_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + return SDL_JOYSTICK_CAP_RUMBLE; +} + +static bool HIDAPI_DriverFlydigi_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverFlydigi_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverFlydigi_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; + if (ctx->sensors_supported) { + ctx->sensors_enabled = enabled; + return true; + } + return SDL_Unsupported(); +} + +static void HIDAPI_DriverFlydigi_HandleStatePacketV1(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + Uint8 extra_button_index = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS; + + if (ctx->last_state[9] != data[9]) { + Uint8 hat; + + switch (data[9] & 0x0F) { + case 0x01u: + hat = SDL_HAT_UP; + break; + case 0x02u | 0x01u: + hat = SDL_HAT_RIGHTUP; + break; + case 0x02u: + hat = SDL_HAT_RIGHT; + break; + case 0x02u | 0x04u: + hat = SDL_HAT_RIGHTDOWN; + break; + case 0x04u: + hat = SDL_HAT_DOWN; + break; + case 0x08u | 0x04u: + hat = SDL_HAT_LEFTDOWN; + break; + case 0x08u: + hat = SDL_HAT_LEFT; + break; + case 0x08u | 0x01u: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[9] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[9] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[9] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[9] & 0x80) != 0)); + } + + if (ctx->last_state[10] != data[10]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[10] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[10] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[10] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[10] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[10] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[10] & 0x80) != 0)); + } + + if (ctx->last_state[7] != data[7]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M1, ((data[7] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M2, ((data[7] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M3, ((data[7] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M4, ((data[7] & 0x20) != 0)); + if (ctx->has_cz) { + SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[7] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[7] & 0x02) != 0)); + } + } + + if (ctx->last_state[8] != data[8]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[8] & 0x08) != 0)); + // The '+' button is used to toggle gyro mouse mode, so don't pass that to the application + // SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[8] & 0x01) != 0)); + // The '-' button is only available on the Vader 2, for simplicity let's ignore that + // SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[8] & 0x10) != 0)); + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + axis = READ_STICK_AXIS(17); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(19); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(21); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(22); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + +#define READ_TRIGGER_AXIS(offset) \ + (Sint16)(((int)data[offset] * 257) - 32768) + { + axis = READ_TRIGGER_AXIS(23); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = READ_TRIGGER_AXIS(24); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + } +#undef READ_TRIGGER_AXIS + + if (ctx->sensors_enabled) { + Uint64 sensor_timestamp; + float values[3]; + + // Advance the imu sensor time stamp based on the observed rate of receipt of packets in the testcontroller app. + // This varies between Product ID and connection type. + sensor_timestamp = ctx->sensor_timestamp_ns; + ctx->sensor_timestamp_ns += ctx->sensor_timestamp_step_ns; + + // Pitch and yaw scales may be receiving extra filtering for the sake of bespoke direct mouse output. + // As result, roll has a different scaling factor than pitch and yaw. + // These values were estimated using the testcontroller tool in lieux of hard data sheet references. + const float flPitchAndYawScale = DEG2RAD(72000.0f); + const float flRollScale = DEG2RAD(1200.0f); + + values[0] = HIDAPI_RemapVal(-1.0f * LOAD16(data[26], data[27]), INT16_MIN, INT16_MAX, -flPitchAndYawScale, flPitchAndYawScale); + values[1] = HIDAPI_RemapVal(-1.0f * LOAD16(data[18], data[20]), INT16_MIN, INT16_MAX, -flPitchAndYawScale, flPitchAndYawScale); + values[2] = HIDAPI_RemapVal(-1.0f * LOAD16(data[29], data[30]), INT16_MIN, INT16_MAX, -flRollScale, flRollScale); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, values, 3); + + const float flAccelScale = ctx->accelScale; + values[0] = -LOAD16(data[11], data[12]) * flAccelScale; // Acceleration along pitch axis + values[1] = LOAD16(data[15], data[16]) * flAccelScale; // Acceleration along yaw axis + values[2] = LOAD16(data[13], data[14]) * flAccelScale; // Acceleration along roll axis + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, values, 3); + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static void HIDAPI_DriverFlydigi_HandlePacketV1(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) +{ + if (data[0] != 0x04 || data[1] != 0xFE) { + // We don't know how to handle this report, ignore it + return; + } + + if (joystick) { + HIDAPI_DriverFlydigi_HandleStatePacketV1(joystick, ctx, data, size); + } +} + +static void HIDAPI_DriverFlydigi_HandleStatePacketV2(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + Uint8 extra_button_index = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS; + + if (ctx->last_state[11] != data[11]) { + Uint8 hat; + + switch (data[11] & 0x0F) { + case 0x01u: + hat = SDL_HAT_UP; + break; + case 0x02u | 0x01u: + hat = SDL_HAT_RIGHTUP; + break; + case 0x02u: + hat = SDL_HAT_RIGHT; + break; + case 0x02u | 0x04u: + hat = SDL_HAT_RIGHTDOWN; + break; + case 0x04u: + hat = SDL_HAT_DOWN; + break; + case 0x08u | 0x04u: + hat = SDL_HAT_LEFTDOWN; + break; + case 0x08u: + hat = SDL_HAT_LEFT; + break; + case 0x08u | 0x01u: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[11] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[11] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[11] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[11] & 0x80) != 0)); + } + + if (ctx->last_state[12] != data[12]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[12] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[12] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[12] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[12] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[12] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[12] & 0x80) != 0)); + } + + if (ctx->last_state[13] != data[13]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M1, ((data[13] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M2, ((data[13] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M3, ((data[13] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M4, ((data[13] & 0x20) != 0)); + if (ctx->has_cz) { + SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[13] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[13] & 0x02) != 0)); + } + if (ctx->has_lmrm) { + SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[13] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[13] & 0x80) != 0)); + } + } else { + if (ctx->has_cz) { + extra_button_index += 2; + } + if (ctx->has_lmrm) { + extra_button_index += 2; + } + } + + if (ctx->last_state[14] != data[14]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[14] & 0x08) != 0)); + if (ctx->has_circle) { + SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[14] & 0x01) != 0)); + } + } + + axis = LOAD16(data[3], data[4]); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = -LOAD16(data[5], data[6]); + if (axis <= -32768) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = LOAD16(data[7], data[8]); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = -LOAD16(data[9], data[10]); + if (axis <= -32768) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + +#define READ_TRIGGER_AXIS(offset) \ + (Sint16)(((int)data[offset] * 257) - 32768) + { + axis = READ_TRIGGER_AXIS(15); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = READ_TRIGGER_AXIS(16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + } +#undef READ_TRIGGER_AXIS + + if (ctx->sensors_enabled) { + Uint64 sensor_timestamp; + float values[3]; + + // Advance the imu sensor time stamp based on the observed rate of receipt of packets in the testcontroller app. + // This varies between Product ID and connection type. + sensor_timestamp = ctx->sensor_timestamp_ns; + ctx->sensor_timestamp_ns += ctx->sensor_timestamp_step_ns; + + const float flGyroScale = ctx->gyroScale; + values[0] = HIDAPI_RemapVal((float)LOAD16(data[17], data[18]), INT16_MIN, INT16_MAX, -flGyroScale, flGyroScale); + values[1] = HIDAPI_RemapVal((float)LOAD16(data[21], data[22]), INT16_MIN, INT16_MAX, -flGyroScale, flGyroScale); + values[2] = HIDAPI_RemapVal(-(float)LOAD16(data[19], data[20]), INT16_MIN, INT16_MAX, -flGyroScale, flGyroScale); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, values, 3); + + const float flAccelScale = ctx->accelScale; + values[0] = LOAD16(data[23], data[24]) * flAccelScale; // Acceleration along pitch axis + values[1] = LOAD16(data[27], data[28]) * flAccelScale; // Acceleration along yaw axis + values[2] = -LOAD16(data[25], data[26]) * flAccelScale; // Acceleration along roll axis + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, values, 3); + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static void HIDAPI_DriverFlydigi_HandleStatusUpdate(SDL_HIDAPI_Device *device, Uint8 *data, int size) +{ + // The status changed, see if we can acquire the controller now + SDL_HIDAPI_Flydigi_SendStatusRequest(device); +} + +static void HIDAPI_DriverFlydigi_HandlePacketV2(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) +{ + if (size > 0 && data[0] != 0x5A) { + // If first byte is not 0x5A, it must be REPORT_ID, we need to remove it. + ++data; + --size; + } + if (size < 31 || data[0] != FLYDIGI_V2_MAGIC1 || data[1] != FLYDIGI_V2_MAGIC2) { + // We don't know how to handle this report, ignore it + return; + } + + switch (data[2]) { + case FLYDIGI_V2_SET_STATUS_COMMAND: + HIDAPI_DriverFlydigi_HandleStatusUpdate(ctx->device, data, size); + break; + case FLYDIGI_V2_GET_STATUS_COMMAND: + HIDAPI_DriverFlydigi_HandleStatusResponse(ctx->device, data, size); + break; + case FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND: + HIDAPI_DriverFlydigi_HandleAcquireResponse(ctx->device, data, size); + break; + case FLYDIGI_V2_INPUT_REPORT: + if (joystick) { + HIDAPI_DriverFlydigi_HandleStatePacketV2(joystick, ctx, data, size); + } + break; + default: + // We don't recognize this command, ignore it + break; + } +} + +static bool HIDAPI_DriverFlydigi_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + Uint64 now = SDL_GetTicks(); + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } + + if (device->vendor_id == USB_VENDOR_FLYDIGI_V2 && joystick) { + if (!ctx->next_heartbeat || now >= ctx->next_heartbeat) { + SDL_HIDAPI_Flydigi_SendAcquireRequest(device, true); + ctx->next_heartbeat = now + FLYDIGI_ACQUIRE_CONTROLLER_HEARTBEAT_TIME; + } + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_FLYDIGI_PROTOCOL + HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size); +#endif + ctx->last_packet = now; + + if (device->vendor_id == USB_VENDOR_FLYDIGI_V1) { + HIDAPI_DriverFlydigi_HandlePacketV1(joystick, ctx, data, size); + } else { + HIDAPI_DriverFlydigi_HandlePacketV2(joystick, ctx, data, size); + } + } + + if (device->vendor_id == USB_VENDOR_FLYDIGI_V2) { + // If we haven't gotten a packet in a while, check to make sure we can still acquire it + const int INPUT_TIMEOUT_MS = 100; + if (now >= (ctx->last_packet + INPUT_TIMEOUT_MS)) { + ctx->next_heartbeat = now; + } + } + + if (size < 0 && device->num_joysticks > 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverFlydigi_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + // Don't unacquire the controller, someone else might be using it too. + // The controller will automatically unacquire itself after a little while + //SDL_HIDAPI_Flydigi_SendAcquireRequest(device, false); +} + +static void HIDAPI_DriverFlydigi_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverFlydigi = { + SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, + true, + HIDAPI_DriverFlydigi_RegisterHints, + HIDAPI_DriverFlydigi_UnregisterHints, + HIDAPI_DriverFlydigi_IsEnabled, + HIDAPI_DriverFlydigi_IsSupportedDevice, + HIDAPI_DriverFlydigi_InitDevice, + HIDAPI_DriverFlydigi_GetDevicePlayerIndex, + HIDAPI_DriverFlydigi_SetDevicePlayerIndex, + HIDAPI_DriverFlydigi_UpdateDevice, + HIDAPI_DriverFlydigi_OpenJoystick, + HIDAPI_DriverFlydigi_RumbleJoystick, + HIDAPI_DriverFlydigi_RumbleJoystickTriggers, + HIDAPI_DriverFlydigi_GetJoystickCapabilities, + HIDAPI_DriverFlydigi_SetJoystickLED, + HIDAPI_DriverFlydigi_SendJoystickEffect, + HIDAPI_DriverFlydigi_SetJoystickSensorsEnabled, + HIDAPI_DriverFlydigi_CloseJoystick, + HIDAPI_DriverFlydigi_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_FLYDIGI + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.h b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.h new file mode 100644 index 0000000..cf60bed --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.h @@ -0,0 +1,38 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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. +*/ + +// These are values used in the controller type byte of the controller GUID + +typedef enum +{ + SDL_FLYDIGI_UNKNOWN, + SDL_FLYDIGI_APEX2 = (1 << 0), + SDL_FLYDIGI_APEX3, + SDL_FLYDIGI_APEX4, + SDL_FLYDIGI_APEX5, + SDL_FLYDIGI_VADER2 = (1 << 4), + SDL_FLYDIGI_VADER2_PRO, + SDL_FLYDIGI_VADER3, + SDL_FLYDIGI_VADER3_PRO, + SDL_FLYDIGI_VADER4_PRO, + SDL_FLYDIGI_VADER5_PRO, +} SDL_FlyDigiControllerType; + diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c index c03d927..5b39b8b 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -23,6 +23,7 @@ #ifdef SDL_JOYSTICK_HIDAPI #include "../../SDL_hints_c.h" +#include "../../misc/SDL_libusb.h" #include "../SDL_sysjoystick.h" #include "SDL_hidapijoystick_c.h" #include "SDL_hidapi_rumble.h" @@ -76,7 +77,8 @@ static bool HIDAPI_DriverGameCube_IsSupportedDevice(SDL_HIDAPI_Device *device, c } if (vendor_id == USB_VENDOR_DRAGONRISE && (product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 || - product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2)) { + product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 || + product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3)) { // EVORETRO GameCube Controller Adapter return true; } @@ -101,6 +103,83 @@ static void SDLCALL SDL_JoystickGameCubeRumbleBrakeHintChanged(void *userdata, c } } +static bool HIDAPI_DriverGameCube_EnableAdapter(SDL_HIDAPI_Device *device) +{ +#ifdef HAVE_LIBUSB + // Need to close the device while sending USB commands to it + SDL_hid_close(device->dev); + + // This is needed to enable input for Nyko and EVORETRO GameCube adapters + SDL_LibUSBContext *libusb_ctx; + if (SDL_InitLibUSB(&libusb_ctx)) { + libusb_context *context = NULL; + libusb_device **devs = NULL; + libusb_device_handle *handle = NULL; + struct libusb_device_descriptor desc; + ssize_t i, num_devs; + bool kernel_detached = false; + + if (libusb_ctx->init(&context) == 0) { + num_devs = libusb_ctx->get_device_list(context, &devs); + for (i = 0; i < num_devs; ++i) { + if (libusb_ctx->get_device_descriptor(devs[i], &desc) != 0) { + continue; + } + + if (desc.idVendor != USB_VENDOR_NINTENDO || + desc.idProduct != USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) { + continue; + } + + if (libusb_ctx->open(devs[i], &handle) != 0) { + continue; + } + + if (libusb_ctx->kernel_driver_active(handle, 0)) { + if (libusb_ctx->detach_kernel_driver(handle, 0) == 0) { + kernel_detached = true; + } + } + + if (libusb_ctx->claim_interface(handle, 0) == 0) { + libusb_ctx->control_transfer(handle, 0x21, 11, 0x0001, 0, NULL, 0, 1000); + libusb_ctx->release_interface(handle, 0); + } + + if (kernel_detached) { + libusb_ctx->attach_kernel_driver(handle, 0); + } + + libusb_ctx->close(handle); + } + + libusb_ctx->free_device_list(devs, 1); + + libusb_ctx->exit(context); + } + SDL_QuitLibUSB(); + } + + // Reopen the device now that we're done + device->dev = SDL_hid_open_path(device->path); + if (!device->dev) { + return false; + } +#endif // HAVE_LIBUSB + + Uint8 initMagic = 0x13; + if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "HIDAPI_DriverGameCube_InitDevice(): Couldn't initialize WUP-028"); + return false; + } + + // Wait for the adapter to initialize + SDL_Delay(10); + + return true; +} + static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) { SDL_DriverGameCube_Context *ctx; @@ -108,13 +187,8 @@ static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) Uint8 *curSlot; Uint8 i; int size; - Uint8 initMagic = 0x13; Uint8 rumbleMagic = 0x11; -#ifdef HAVE_ENABLE_GAMECUBE_ADAPTORS - SDL_EnableGameCubeAdaptors(); -#endif - ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx)); if (!ctx) { return false; @@ -128,19 +202,21 @@ static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) } if (ctx->pc_mode) { +#ifdef SDL_PLATFORM_WIN32 + // We get a separate device for each slot ResetAxisRange(ctx, 0); HIDAPI_JoystickConnected(device, &ctx->joysticks[0]); +#else + for (i = 0; i < MAX_CONTROLLERS; ++i) { + ResetAxisRange(ctx, i); + HIDAPI_JoystickConnected(device, &ctx->joysticks[i]); + } +#endif } else { - // This is all that's needed to initialize the device. Really! - if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) { - SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, - "HIDAPI_DriverGameCube_InitDevice(): Couldn't initialize WUP-028"); + if (!HIDAPI_DriverGameCube_EnableAdapter(device)) { return false; } - // Wait for the adapter to initialize - SDL_Delay(10); - // Add all the applicable joysticks while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) { #ifdef DEBUG_GAMECUBE_PROTOCOL @@ -199,14 +275,25 @@ 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, bool invert_c_stick) +static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, Uint8 slot, const Uint8 *packet, bool invert_c_stick) { SDL_Joystick *joystick; - const Uint8 i = 0; // We have a separate context for each connected controller in PC mode, just use the first index + Uint8 i; Uint8 v; Sint16 axis_value; Uint64 timestamp = SDL_GetTicksNS(); +#ifdef SDL_PLATFORM_WIN32 + // We get a separate device for each slot + i = 0; +#else + i = slot; + if (i >= MAX_CONTROLLERS) { + // Invalid packet? + return; + } +#endif + joystick = SDL_GetJoystickFromID(ctx->joysticks[i]); if (!joystick) { // Hasn't been opened yet, skip @@ -356,11 +443,11 @@ static bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device) // 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); + HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet[0] - 1, &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); + HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, 0, packet, false); } else { // How do we handle this packet? } diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gip.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gip.c new file mode 100644 index 0000000..18df94e --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gip.c @@ -0,0 +1,2958 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_JOYSTICK_HIDAPI + +#include "../../events/SDL_keyboard_c.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_GIP + +// This driver is based on the Microsoft GIP spec at: +// https://aka.ms/gipdocs +// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gipusb/e7c90904-5e21-426e-b9ad-d82adeee0dbc + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_XBOX_PROTOCOL +#endif + +#define MAX_MESSAGE_LENGTH 0x4000 +#define MAX_ATTACHMENTS 8 + +#define GIP_DATA_CLASS_COMMAND (0u << 5) +#define GIP_DATA_CLASS_LOW_LATENCY (1u << 5) +#define GIP_DATA_CLASS_STANDARD_LATENCY (2u << 5) +#define GIP_DATA_CLASS_AUDIO (3u << 5) + +#define GIP_DATA_CLASS_SHIFT 5 +#define GIP_DATA_CLASS_MASK (7u << 5) + +/* System messages */ +#define GIP_CMD_PROTO_CONTROL 0x01 +#define GIP_CMD_HELLO_DEVICE 0x02 +#define GIP_CMD_STATUS_DEVICE 0x03 +#define GIP_CMD_METADATA 0x04 +#define GIP_CMD_SET_DEVICE_STATE 0x05 +#define GIP_CMD_SECURITY 0x06 +#define GIP_CMD_GUIDE_BUTTON 0x07 +#define GIP_CMD_AUDIO_CONTROL 0x08 +#define GIP_CMD_LED 0x0a +#define GIP_CMD_HID_REPORT 0x0b +#define GIP_CMD_FIRMWARE 0x0c +#define GIP_CMD_EXTENDED 0x1e +#define GIP_CMD_DEBUG 0x1f +#define GIP_AUDIO_DATA 0x60 + +/* Navigation vendor messages */ +#define GIP_CMD_DIRECT_MOTOR 0x09 +#define GIP_LL_INPUT_REPORT 0x20 +#define GIP_LL_OVERFLOW_INPUT_REPORT 0x26 + +/* Wheel and ArcadeStick vendor messages */ +#define GIP_CMD_INITIAL_REPORTS_REQUEST 0x0a +#define GIP_LL_STATIC_CONFIGURATION 0x21 +#define GIP_LL_BUTTON_INFO_REPORT 0x22 + +/* Wheel vendor messages */ +#define GIP_CMD_SET_APPLICATION_MEMORY 0x0b +#define GIP_CMD_SET_EQUATIONS_STATES 0x0c +#define GIP_CMD_SET_EQUATION 0x0d + +/* FlightStick vendor messages */ +#define GIP_CMD_DEVICE_CAPABILITIES 0x00 +#define GIP_CMD_LED_CAPABILITIES 0x01 +#define GIP_CMD_SET_LED_STATE 0x02 + +/* Undocumented Elite 2 vendor messages */ +#define GIP_CMD_RAW_REPORT 0x0c +#define GIP_CMD_GUIDE_COLOR 0x0e +#define GIP_SL_ELITE_CONFIG 0x4d + +#define GIP_BTN_OFFSET_XBE1 28 +#define GIP_BTN_OFFSET_XBE2 14 + +#define GIP_FLAG_FRAGMENT (1u << 7) +#define GIP_FLAG_INIT_FRAG (1u << 6) +#define GIP_FLAG_SYSTEM (1u << 5) +#define GIP_FLAG_ACME (1u << 4) +#define GIP_FLAG_ATTACHMENT_MASK 0x7 + +#define GIP_AUDIO_FORMAT_NULL 0 +#define GIP_AUDIO_FORMAT_8000HZ_1CH 1 +#define GIP_AUDIO_FORMAT_8000HZ_2CH 2 +#define GIP_AUDIO_FORMAT_12000HZ_1CH 3 +#define GIP_AUDIO_FORMAT_12000HZ_2CH 4 +#define GIP_AUDIO_FORMAT_16000HZ_1CH 5 +#define GIP_AUDIO_FORMAT_16000HZ_2CH 6 +#define GIP_AUDIO_FORMAT_20000HZ_1CH 7 +#define GIP_AUDIO_FORMAT_20000HZ_2CH 8 +#define GIP_AUDIO_FORMAT_24000HZ_1CH 9 +#define GIP_AUDIO_FORMAT_24000HZ_2CH 10 +#define GIP_AUDIO_FORMAT_32000HZ_1CH 11 +#define GIP_AUDIO_FORMAT_32000HZ_2CH 12 +#define GIP_AUDIO_FORMAT_40000HZ_1CH 13 +#define GIP_AUDIO_FORMAT_40000HZ_2CH 14 +#define GIP_AUDIO_FORMAT_48000HZ_1CH 15 +#define GIP_AUDIO_FORMAT_48000HZ_2CH 16 +#define GIP_AUDIO_FORMAT_48000HZ_6CH 32 +#define GIP_AUDIO_FORMAT_48000HZ_8CH 33 + +/* Protocol Control constants */ +#define GIP_CONTROL_CODE_ACK 0 +#define GIP_CONTROL_CODE_NACK 1 /* obsolete */ +#define GIP_CONTROL_CODE_UNK 2 /* obsolete */ +#define GIP_CONTROL_CODE_AB 3 /* obsolete */ +#define GIP_CONTROL_CODE_MPER 4 /* obsolete */ +#define GIP_CONTROL_CODE_STOP 5 /* obsolete */ +#define GIP_CONTROL_CODE_START 6 /* obsolete */ +#define GIP_CONTROL_CODE_ERR 7 /* obsolete */ + +/* Status Device constants */ +#define GIP_POWER_LEVEL_OFF 0 +#define GIP_POWER_LEVEL_STANDBY 1 /* obsolete */ +#define GIP_POWER_LEVEL_FULL 2 + +#define GIP_NOT_CHARGING 0 +#define GIP_CHARGING 1 +#define GIP_CHARGE_ERROR 2 + +#define GIP_BATTERY_ABSENT 0 +#define GIP_BATTERY_STANDARD 1 +#define GIP_BATTERY_RECHARGEABLE 2 + +#define GIP_BATTERY_CRITICAL 0 +#define GIP_BATTERY_LOW 1 +#define GIP_BATTERY_MEDIUM 2 +#define GIP_BATTERY_FULL 3 + +#define GIP_EVENT_FAULT 0x0002 + +#define GIP_FAULT_UNKNOWN 0 +#define GIP_FAULT_HARD 1 +#define GIP_FAULT_NMI 2 +#define GIP_FAULT_SVC 3 +#define GIP_FAULT_PEND_SV 4 +#define GIP_FAULT_SMART_PTR 5 +#define GIP_FAULT_MCU 6 +#define GIP_FAULT_BUS 7 +#define GIP_FAULT_USAGE 8 +#define GIP_FAULT_RADIO_HANG 9 +#define GIP_FAULT_WATCHDOG 10 +#define GIP_FAULT_LINK_STALL 11 +#define GIP_FAULT_ASSERTION 12 + +/* Metadata constants */ +#define GIP_MESSAGE_FLAG_BIG_ENDIAN (1u << 0) +#define GIP_MESSAGE_FLAG_RELIABLE (1u << 1) +#define GIP_MESSAGE_FLAG_SEQUENCED (1u << 2) +#define GIP_MESSAGE_FLAG_DOWNSTREAM (1u << 3) +#define GIP_MESSAGE_FLAG_UPSTREAM (1u << 4) +#define GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE (1u << 5) + +#define GIP_DATA_TYPE_CUSTOM 1 +#define GIP_DATA_TYPE_AUDIO 2 +#define GIP_DATA_TYPE_SECURITY 3 +#define GIP_DATA_TYPE_GIP 4 + +/* Set Device State constants */ +#define GIP_STATE_START 0 +#define GIP_STATE_STOP 1 +#define GIP_STATE_STANDBY 2 /* obsolete */ +#define GIP_STATE_FULL_POWER 3 +#define GIP_STATE_OFF 4 +#define GIP_STATE_QUIESCE 5 +#define GIP_STATE_UNK6 6 +#define GIP_STATE_RESET 7 + +/* Guide Button Status constants */ +#define GIP_LED_GUIDE 0 +#define GIP_LID_IR 1 /* deprecated */ + +#define GIP_LED_GUIDE_OFF 0 +#define GIP_LED_GUIDE_ON 1 +#define GIP_LED_GUIDE_FAST_BLINK 2 +#define GIP_LED_GUIDE_SLOW_BLINK 3 +#define GIP_LED_GUIDE_CHARGING_BLINK 4 +#define GIP_LED_GUIDE_RAMP_TO_LEVEL 0xd + +#define GIP_LED_IR_OFF 0 +#define GIP_LED_IR_ON_100MS 1 +#define GIP_LED_IR_PATTERN 4 + +/* Direct Motor Command constants */ +#define GIP_MOTOR_RIGHT_VIBRATION (1u << 0) +#define GIP_MOTOR_LEFT_VIBRATION (1u << 1) +#define GIP_MOTOR_RIGHT_IMPULSE (1u << 2) +#define GIP_MOTOR_LEFT_IMPULSE (1u << 3) +#define GIP_MOTOR_ALL 0xF + +/* Extended Command constants */ +#define GIP_EXTCMD_GET_CAPABILITIES 0x00 +#define GIP_EXTCMD_GET_TELEMETRY_DATA 0x01 +#define GIP_EXTCMD_GET_SERIAL_NUMBER 0x04 + +#define GIP_EXTENDED_STATUS_OK 0 +#define GIP_EXTENDED_STATUS_NOT_SUPPORTED 1 +#define GIP_EXTENDED_STATUS_NOT_READY 2 +#define GIP_EXTENDED_STATUS_ACCESS_DENIED 3 +#define GIP_EXTENDED_STATUS_FAILED 4 + +/* Internal constants, not part of protocol */ +#define GIP_HELLO_TIMEOUT 2000 +#define GIP_ACME_TIMEOUT 10 + +#define GIP_DEFAULT_IN_SYSTEM_MESSAGES 0x5e +#define GIP_DEFAULT_OUT_SYSTEM_MESSAGES 0x472 + +#define GIP_FEATURE_CONSOLE_FUNCTION_MAP (1u << 0) +#define GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW (1u << 1) +#define GIP_FEATURE_ELITE_BUTTONS (1u << 2) +#define GIP_FEATURE_DYNAMIC_LATENCY_INPUT (1u << 3) +#define GIP_FEATURE_SECURITY_OPT_OUT (1u << 4) +#define GIP_FEATURE_MOTOR_CONTROL (1u << 5) +#define GIP_FEATURE_GUIDE_COLOR (1u << 6) +#define GIP_FEATURE_EXTENDED_SET_DEVICE_STATE (1u << 7) + +#define GIP_QUIRK_NO_HELLO (1u << 0) +#define GIP_QUIRK_BROKEN_METADATA (1u << 1) +#define GIP_QUIRK_NO_IMPULSE_VIBRATION (1u << 2) + +typedef enum +{ + GIP_METADATA_NONE = 0, + GIP_METADATA_GOT = 1, + GIP_METADATA_FAKED = 2, + GIP_METADATA_PENDING = 3, +} GIP_MetadataStatus; + +#ifndef VK_LWIN +#define VK_LWIN 0x5b +#endif + +typedef enum +{ + GIP_TYPE_UNKNOWN = -1, + GIP_TYPE_GAMEPAD = 0, + GIP_TYPE_ARCADE_STICK = 1, + GIP_TYPE_WHEEL = 2, + GIP_TYPE_FLIGHT_STICK = 3, + GIP_TYPE_NAVIGATION_CONTROLLER = 4, + GIP_TYPE_CHATPAD = 5, + GIP_TYPE_HEADSET = 6, +} GIP_AttachmentType; + +typedef enum +{ + GIP_RUMBLE_STATE_IDLE, + GIP_RUMBLE_STATE_QUEUED, + GIP_RUMBLE_STATE_BUSY, +} GIP_RumbleState; + +typedef enum +{ + GIP_BTN_FMT_UNKNOWN, + GIP_BTN_FMT_XBE1, + GIP_BTN_FMT_XBE2_RAW, + GIP_BTN_FMT_XBE2_4, + GIP_BTN_FMT_XBE2_5, +} GIP_EliteButtonFormat; + +/* These come across the wire as little-endian, so let's store them in-memory as such so we can memcmp */ +#define MAKE_GUID(NAME, A, B, C, D0, D1, D2, D3, D4, D5, D6, D7) \ + static const GUID NAME = { SDL_Swap32LE(A), SDL_Swap16LE(B), SDL_Swap16LE(C), { D0, D1, D2, D3, D4, D5, D6, D7 } } + +typedef struct GUID +{ + Uint32 a; + Uint16 b; + Uint16 c; + Uint8 d[8]; +} GUID; +SDL_COMPILE_TIME_ASSERT(GUID, sizeof(GUID) == 16); + +MAKE_GUID(GUID_ArcadeStick, 0x332054cc, 0xa34b, 0x41d5, 0xa3, 0x4a, 0xa6, 0xa6, 0x71, 0x1e, 0xc4, 0xb3); +MAKE_GUID(GUID_DynamicLatencyInput, 0x87f2e56b, 0xc3bb, 0x49b1, 0x82, 0x65, 0xff, 0xff, 0xf3, 0x77, 0x99, 0xee); +MAKE_GUID(GUID_FlightStick, 0x03f1a011, 0xefe9, 0x4cc1, 0x96, 0x9c, 0x38, 0xdc, 0x55, 0xf4, 0x04, 0xd0); +MAKE_GUID(GUID_IHeadset, 0xbc25d1a3, 0xc24e, 0x4992, 0x9d, 0xda, 0xef, 0x4f, 0x12, 0x3e, 0xf5, 0xdc); +MAKE_GUID(GUID_IConsoleFunctionMap_InputReport, 0xecddd2fe, 0xd387, 0x4294, 0xbd, 0x96, 0x1a, 0x71, 0x2e, 0x3d, 0xc7, 0x7d); +MAKE_GUID(GUID_IConsoleFunctionMap_OverflowInputReport, 0x137d4bd0, 0x9347, 0x4472, 0xaa, 0x26, 0x8c, 0x34, 0xa0, 0x8f, 0xf9, 0xbd); +MAKE_GUID(GUID_IController, 0x9776ff56, 0x9bfd, 0x4581, 0xad, 0x45, 0xb6, 0x45, 0xbb, 0xa5, 0x26, 0xd6); +MAKE_GUID(GUID_IDevAuthPCOptOut, 0x7a34ce77, 0x7de2, 0x45c6, 0x8c, 0xa4, 0x00, 0x42, 0xc0, 0x8b, 0xd9, 0x4a); +MAKE_GUID(GUID_IEliteButtons, 0x37d19ff7, 0xb5c6, 0x49d1, 0xa7, 0x5e, 0x03, 0xb2, 0x4b, 0xef, 0x8c, 0x89); +MAKE_GUID(GUID_IGamepad, 0x082e402c, 0x07df, 0x45e1, 0xa5, 0xab, 0xa3, 0x12, 0x7a, 0xf1, 0x97, 0xb5); +MAKE_GUID(GUID_NavigationController, 0xb8f31fe7, 0x7386, 0x40e9, 0xa9, 0xf8, 0x2f, 0x21, 0x26, 0x3a, 0xcf, 0xb7); +MAKE_GUID(GUID_Wheel, 0x646979cf, 0x6b71, 0x4e96, 0x8d, 0xf9, 0x59, 0xe3, 0x98, 0xd7, 0x42, 0x0c); + +/* + * The following GUIDs are observed, but the exact meanings aren't known, so + * for now we document them but don't use them anywhere. + * + * MAKE_GUID(GUID_GamepadEmu, 0xe2e5f1bc, 0xa6e6, 0x41a2, 0x8f, 0x43, 0x33, 0xcf, 0xa2, 0x51, 0x09, 0x81); + * MAKE_GUID(GUID_IAudioOnly, 0x92844cd1, 0xf7c8, 0x49ef, 0x97, 0x77, 0x46, 0x7d, 0xa7, 0x08, 0xad, 0x10); + * MAKE_GUID(GUID_IControllerProfileModeState, 0xf758dc66, 0x022c, 0x48b8, 0xa4, 0xf6, 0x45, 0x7b, 0xa8, 0x0e, 0x2a, 0x5b); + * MAKE_GUID(GUID_ICustomAudio, 0x63fd9cc9, 0x94ee, 0x4b5d, 0x9c, 0x4d, 0x8b, 0x86, 0x4c, 0x14, 0x9c, 0xac); + * MAKE_GUID(GUID_IExtendedDeviceFlags, 0x34ad9b1e, 0x36ad, 0x4fb5, 0x8a, 0xc7, 0x17, 0x23, 0x4c, 0x9f, 0x54, 0x6f); + * MAKE_GUID(GUID_IProgrammableGamepad, 0x31c1034d, 0xb5b7, 0x4551, 0x98, 0x13, 0x87, 0x69, 0xd4, 0xa0, 0xe4, 0xf9); + * MAKE_GUID(GUID_IVirtualDevice, 0xdfd26825, 0x110a, 0x4e94, 0xb9, 0x37, 0xb2, 0x7c, 0xe4, 0x7b, 0x25, 0x40); + * MAKE_GUID(GUID_OnlineDevAuth, 0x632b1fd1, 0xa3e9, 0x44f9, 0x84, 0x20, 0x5c, 0xe3, 0x44, 0xa0, 0x64, 0x04); + * + * Seen on Elite Controller, Adaptive Controller: 9ebd00a3-b5e6-4c08-a33b-673126459ec4 + * Seen on Adaptive Controller: ce1e58c5-221c-4bdb-9c24-bf3941601320 + * Seen on Elite 2 Controller: f758dc66-022c-48b8-a4f6-457ba80e2a5b (IControllerProfileModeState) + * Seen on Elite 2 Controller: 31c1034d-b5b7-4551-9813-8769d4a0e4f9 (IProgrammableGamepad) + * Seen on Elite 2 Controller: 34ad9b1e-36ad-4fb5-8ac7-17234c9f546f (IExtendedDeviceFlags) + * Seen on Elite 2 Controller: 88e0b694-6bd9-4416-a560-e7fafdfa528f + * Seen on Elite 2 Controller: ea96c8c0-b216-448b-be80-7e5deb0698e2 + */ + +static const int GIP_DataClassMtu[8] = { 64, 64, 64, 2048, 0, 0, 0, 0 }; + +typedef struct GIP_Quirks +{ + Uint16 vendor_id; + Uint16 product_id; + Uint8 attachment_index; + Uint32 added_features; + Uint32 filtered_features; + Uint32 quirks; + Uint32 extra_in_system[8]; + Uint32 extra_out_system[8]; + GIP_AttachmentType device_type; + Uint8 extra_buttons; + Uint8 extra_axes; +} GIP_Quirks; + +static const GIP_Quirks quirks[] = { + { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1, 0, + .added_features = GIP_FEATURE_ELITE_BUTTONS, + .filtered_features = GIP_FEATURE_CONSOLE_FUNCTION_MAP }, + + { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2, 0, + .added_features = GIP_FEATURE_ELITE_BUTTONS | GIP_FEATURE_DYNAMIC_LATENCY_INPUT | GIP_FEATURE_CONSOLE_FUNCTION_MAP | GIP_FEATURE_GUIDE_COLOR | GIP_FEATURE_EXTENDED_SET_DEVICE_STATE, + .extra_in_system = { 1 << GIP_CMD_FIRMWARE }, + .extra_out_system = { 1 << GIP_CMD_FIRMWARE } }, + + { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_SERIES_X, 0, + .added_features = GIP_FEATURE_DYNAMIC_LATENCY_INPUT }, + + { USB_VENDOR_PDP, USB_PRODUCT_PDP_ROCK_CANDY, 0, + .quirks = GIP_QUIRK_NO_HELLO }, + + { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_FIGHTPAD, 0, + .filtered_features = GIP_FEATURE_MOTOR_CONTROL }, + + { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_CLASSIC, 0, + .quirks = GIP_QUIRK_NO_IMPULSE_VIBRATION }, + + { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_SPECTRA_PRO, 0, + .quirks = GIP_QUIRK_NO_IMPULSE_VIBRATION }, + + { USB_VENDOR_RAZER, USB_PRODUCT_RAZER_ATROX, 0, + .filtered_features = GIP_FEATURE_MOTOR_CONTROL, + .device_type = GIP_TYPE_ARCADE_STICK }, + + { USB_VENDOR_THRUSTMASTER, USB_PRODUCT_THRUSTMASTER_T_FLIGHT_HOTAS_ONE, 0, + .filtered_features = GIP_FEATURE_MOTOR_CONTROL, + .device_type = GIP_TYPE_FLIGHT_STICK, + .extra_buttons = 5, + .extra_axes = 3 }, + + {0}, +}; + +typedef struct GIP_Header +{ + Uint8 message_type; + Uint8 flags; + Uint8 sequence_id; + Uint64 length; +} GIP_Header; + +typedef struct GIP_AudioFormat +{ + Uint8 inbound; + Uint8 outbound; +} GIP_AudioFormat; + +typedef struct GIP_DeviceMetadata +{ + Uint8 num_audio_formats; + Uint8 num_preferred_types; + Uint8 num_supported_interfaces; + Uint8 hid_descriptor_size; + + Uint32 in_system_messages[8]; + Uint32 out_system_messages[8]; + + GIP_AudioFormat *audio_formats; + char **preferred_types; + GUID *supported_interfaces; + Uint8 *hid_descriptor; + + GIP_AttachmentType device_type; +} GIP_DeviceMetadata; + +typedef struct GIP_MessageMetadata +{ + Uint8 type; + Uint16 length; + Uint16 data_type; + Uint32 flags; + Uint16 period; + Uint16 persistence_timeout; +} GIP_MessageMetadata; + +typedef struct GIP_Metadata +{ + Uint16 version_major; + Uint16 version_minor; + + GIP_DeviceMetadata device; + + Uint8 num_messages; + GIP_MessageMetadata *message_metadata; +} GIP_Metadata; + +struct GIP_Device; +typedef struct GIP_Attachment +{ + struct GIP_Device *device; + Uint8 attachment_index; + SDL_JoystickID joystick; + SDL_KeyboardID keyboard; + + Uint8 fragment_message; + Uint16 total_length; + Uint8 *fragment_data; + Uint32 fragment_offset; + Uint64 fragment_timer; + int fragment_retries; + + Uint16 firmware_major_version; + Uint16 firmware_minor_version; + + GIP_MetadataStatus got_metadata; + Uint64 metadata_next; + int metadata_retries; + GIP_Metadata metadata; + + Uint8 seq_system; + Uint8 seq_security; + Uint8 seq_extended; + Uint8 seq_audio; + Uint8 seq_vendor; + + int device_state; + + GIP_RumbleState rumble_state; + Uint64 rumble_time; + bool rumble_pending; + Uint8 left_impulse_level; + Uint8 right_impulse_level; + Uint8 left_vibration_level; + Uint8 right_vibration_level; + + Uint8 last_input[64]; + + Uint8 last_modifiers; + bool capslock; + SDL_Keycode last_key; + Uint32 altcode; + int altcode_digit; + + GIP_AttachmentType attachment_type; + GIP_EliteButtonFormat xbe_format; + Uint32 features; + Uint32 quirks; + Uint8 share_button_idx; + Uint8 paddle_idx; + + Uint8 extra_button_idx; + int extra_buttons; + int extra_axes; +} GIP_Attachment; + +typedef struct GIP_Device +{ + SDL_HIDAPI_Device *device; + + Uint64 hello_deadline; + bool got_hello; + bool reset_for_metadata; + int timeout; + + GIP_Attachment *attachments[MAX_ATTACHMENTS]; +} GIP_Device; + +typedef struct GIP_HelloDevice +{ + Uint64 device_id; + Uint16 vendor_id; + Uint16 product_id; + Uint16 firmware_major_version; + Uint16 firmware_minor_version; + Uint16 firmware_build_version; + Uint16 firmware_revision; + Uint8 hardware_major_version; + Uint8 hardware_minor_version; + Uint8 rf_proto_major_version; + Uint8 rf_proto_minor_version; + Uint8 security_major_version; + Uint8 security_minor_version; + Uint8 gip_major_version; + Uint8 gip_minor_version; +} GIP_HelloDevice; + +typedef struct GIP_Status +{ + int power_level; + int charge; + int battery_type; + int battery_level; +} GIP_Status; + +typedef struct GIP_StatusEvent +{ + Uint16 event_type; + Uint32 fault_tag; + Uint32 fault_address; +} GIP_StatusEvent; + +typedef struct GIP_ExtendedStatus +{ + GIP_Status base; + bool device_active; + + int num_events; + GIP_StatusEvent events[5]; +} GIP_ExtendedStatus; + +typedef struct GIP_DirectMotor +{ + Uint8 motor_bitmap; + Uint8 left_impulse_level; + Uint8 right_impulse_level; + Uint8 left_vibration_level; + Uint8 right_vibration_level; + Uint8 duration; + Uint8 delay; + Uint8 repeat; +} GIP_DirectMotor; + +typedef struct GIP_InitialReportsRequest +{ + Uint8 type; + Uint8 data[2]; +} GIP_InitialReportsRequest; + +static bool GIP_SetMetadataDefaults(GIP_Attachment *attachment); + +static int GIP_DecodeLength(Uint64 *length, const Uint8 *bytes, int num_bytes) +{ + *length = 0; + int offset; + + for (offset = 0; offset < num_bytes; offset++) { + Uint8 byte = bytes[offset]; + *length |= (byte & 0x7full) << (offset * 7); + if (!(byte & 0x80)) { + offset++; + break; + } + } + return offset; +} + +static int GIP_EncodeLength(Uint64 length, Uint8 *bytes, int num_bytes) +{ + int offset; + + for (offset = 0; offset < num_bytes; offset++) { + Uint8 byte = length & 0x7f; + length >>= 7; + if (length) { + byte |= 0x80; + } + bytes[offset] = byte; + if (!length) { + offset++; + break; + } + } + return offset; +} + +static bool GIP_SupportsSystemMessage(GIP_Attachment *attachment, Uint8 command, bool upstream) +{ + if (upstream) { + return attachment->metadata.device.in_system_messages[command >> 5] & (1u << command); + } else { + return attachment->metadata.device.out_system_messages[command >> 5] & (1u << command); + } +} + +static bool GIP_SupportsVendorMessage(GIP_Attachment *attachment, Uint8 command, bool upstream) +{ + size_t i; + for (i = 0; i < attachment->metadata.num_messages; i++) { + GIP_MessageMetadata *metadata = &attachment->metadata.message_metadata[i]; + if (metadata->type != command) { + continue; + } + if (metadata->flags & GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE) { + return true; + } + if (upstream) { + return metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM; + } else { + return metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM; + } + } + return false; +} + +static Uint8 GIP_SequenceNext(GIP_Attachment *attachment, Uint8 command, bool system) +{ + Uint8 seq; + + if (system) { + switch (command) { + case GIP_CMD_SECURITY: + seq = attachment->seq_security++; + if (!seq) { + seq = attachment->seq_security++; + } + break; + case GIP_CMD_EXTENDED: + seq = attachment->seq_extended++; + if (!seq) { + seq = attachment->seq_extended++; + } + break; + case GIP_AUDIO_DATA: + seq = attachment->seq_audio++; + if (!seq) { + seq = attachment->seq_audio++; + } + break; + default: + seq = attachment->seq_system++; + if (!seq) { + seq = attachment->seq_system++; + } + break; + } + } else { + if (command == GIP_CMD_DIRECT_MOTOR) { + // The motor sequence number is optional and always works with 0 + return 0; + } + + seq = attachment->seq_vendor++; + if (!seq) { + seq = attachment->seq_vendor++; + } + } + return seq; +} + +static void GIP_HandleQuirks(GIP_Attachment *attachment) +{ + size_t i, j; + for (i = 0; quirks[i].vendor_id; i++) { + if (quirks[i].vendor_id != attachment->device->device->vendor_id) { + continue; + } + if (quirks[i].product_id != attachment->device->device->product_id) { + continue; + } + if (quirks[i].attachment_index != attachment->attachment_index) { + continue; + } + attachment->features |= quirks[i].added_features; + attachment->features &= ~quirks[i].filtered_features; + attachment->quirks = quirks[i].quirks; + attachment->attachment_type = quirks[i].device_type; + + for (j = 0; j < 8; ++j) { + attachment->metadata.device.in_system_messages[j] |= quirks[i].extra_in_system[j]; + attachment->metadata.device.out_system_messages[j] |= quirks[i].extra_out_system[j]; + } + + attachment->extra_buttons = quirks[i].extra_buttons; + attachment->extra_axes = quirks[i].extra_axes; + break; + } +} + +static bool GIP_SendRawMessage( + GIP_Device *device, + Uint8 message_type, + Uint8 flags, + Uint8 seq, + const Uint8 *bytes, + int num_bytes, + bool async, + SDL_HIDAPI_RumbleSentCallback callback, + void *userdata) +{ + Uint8 buffer[2054] = { message_type, flags, seq }; + int offset = 3; + + if (num_bytes < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Invalid message length %d", num_bytes); + return false; + } + + if (num_bytes > GIP_DataClassMtu[message_type >> GIP_DATA_CLASS_SHIFT]) { + SDL_LogError(SDL_LOG_CATEGORY_INPUT, + "Attempted to send a message that requires fragmenting, which is not yet supported."); + return false; + } + + offset += GIP_EncodeLength(num_bytes, &buffer[offset], sizeof(buffer) - offset); + + if (num_bytes > 0) { + SDL_memcpy(&buffer[offset], bytes, num_bytes); + } + num_bytes += offset; +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("GIP sending message: size = %d", buffer, num_bytes); +#endif + + if (async) { + if (!SDL_HIDAPI_LockRumble()) { + return false; + } + + return SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device->device, buffer, num_bytes, callback, userdata) == num_bytes; + } else { + return SDL_hid_write(device->device->dev, buffer, num_bytes) == num_bytes; + } +} + +static bool GIP_SendSystemMessage( + GIP_Attachment *attachment, + Uint8 message_type, + Uint8 flags, + const Uint8 *bytes, + int num_bytes) +{ + return GIP_SendRawMessage(attachment->device, + message_type, + GIP_FLAG_SYSTEM | attachment->attachment_index | flags, + GIP_SequenceNext(attachment, message_type, true), + bytes, + num_bytes, + false, + NULL, + NULL); +} + +static bool GIP_SendVendorMessage( + GIP_Attachment *attachment, + Uint8 message_type, + Uint8 flags, + const Uint8 *bytes, + int num_bytes) +{ + return GIP_SendRawMessage(attachment->device, + message_type, + flags, + GIP_SequenceNext(attachment, message_type, false), + bytes, + num_bytes, + true, + NULL, + NULL); +} + +static bool GIP_AttachmentIsController(GIP_Attachment *attachment) +{ + return attachment->attachment_type != GIP_TYPE_CHATPAD && + attachment->attachment_type != GIP_TYPE_HEADSET; +} + +static void GIP_MetadataFree(GIP_Metadata *metadata) +{ + SDL_free(metadata->device.audio_formats); + if (metadata->device.preferred_types) { + int i; + for (i = 0; i < metadata->device.num_preferred_types; i++) { + SDL_free(metadata->device.preferred_types[i]); + } + SDL_free(metadata->device.preferred_types); + } + SDL_free(metadata->device.supported_interfaces); + SDL_free(metadata->device.hid_descriptor); + + SDL_free(metadata->message_metadata); + SDL_zerop(metadata); +} + +static bool GIP_ParseDeviceMetadata(GIP_Metadata *metadata, const Uint8 *bytes, int num_bytes, int *offset) +{ + GIP_DeviceMetadata *device = &metadata->device; + int buffer_offset; + int count; + int length; + int i; + + bytes = &bytes[*offset]; + num_bytes -= *offset; + if (num_bytes < 16) { + return false; + } + + length = bytes[0]; + length |= bytes[1] << 8; + if (num_bytes < length) { + return false; + } + + /* Skip supported firmware versions for now */ + + buffer_offset = bytes[4]; + buffer_offset |= bytes[5] << 8; + if (buffer_offset >= length) { + return false; + } + if (buffer_offset > 0) { + device->num_audio_formats = bytes[buffer_offset]; + if (buffer_offset + device->num_audio_formats + 1 > length) { + return false; + } + device->audio_formats = SDL_malloc(device->num_audio_formats); + SDL_memcpy(device->audio_formats, &bytes[buffer_offset + 1], device->num_audio_formats); + } + + buffer_offset = bytes[6]; + buffer_offset |= bytes[7] << 8; + if (buffer_offset >= length) { + return false; + } + if (buffer_offset > 0) { + count = bytes[buffer_offset]; + if (buffer_offset + count + 1 > length) { + return false; + } + + for (i = 0; i < count; i++) { + Uint8 message = bytes[buffer_offset + 1 + i]; +#ifdef DEBUG_XBOX_PROTOCOL + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "GIP: Supported upstream system message %02x", + message); +#endif + device->in_system_messages[message >> 5] |= 1u << (message & 0x1F); + } + } + + buffer_offset = bytes[8]; + buffer_offset |= bytes[9] << 8; + if (buffer_offset >= length) { + return false; + } + if (buffer_offset > 0) { + count = bytes[buffer_offset]; + if (buffer_offset + count + 1 > length) { + return false; + } + + for (i = 0; i < count; i++) { + Uint8 message = bytes[buffer_offset + 1 + i]; +#ifdef DEBUG_XBOX_PROTOCOL + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "GIP: Supported downstream system message %02x", + message); +#endif + device->out_system_messages[message >> 5] |= 1u << (message & 0x1F); + } + } + + buffer_offset = bytes[10]; + buffer_offset |= bytes[11] << 8; + if (buffer_offset >= length) { + return false; + } + if (buffer_offset > 0) { + device->num_preferred_types = bytes[buffer_offset]; + device->preferred_types = SDL_calloc(device->num_preferred_types, sizeof(char *)); + buffer_offset++; + for (i = 0; i < device->num_preferred_types; i++) { + if (buffer_offset + 2 >= length) { + return false; + } + + count = bytes[buffer_offset]; + count |= bytes[buffer_offset]; + buffer_offset += 2; + if (buffer_offset + count > length) { + return false; + } + + device->preferred_types[i] = SDL_calloc(count + 1, sizeof(char)); + SDL_memcpy(device->preferred_types[i], &bytes[buffer_offset], count); + buffer_offset += count; + } + } + + buffer_offset = bytes[12]; + buffer_offset |= bytes[13] << 8; + if (buffer_offset >= length) { + return false; + } + if (buffer_offset > 0) { + device->num_supported_interfaces = bytes[buffer_offset]; + if (buffer_offset + 1 + (Sint32) (device->num_supported_interfaces * sizeof(GUID)) > length) { + return false; + } + device->supported_interfaces = SDL_calloc(device->num_supported_interfaces, sizeof(GUID)); + SDL_memcpy(device->supported_interfaces, + &bytes[buffer_offset + 1], + sizeof(GUID) * device->num_supported_interfaces); + } + + if (metadata->version_major > 1 || metadata->version_minor >= 1) { + /* HID descriptor support added in metadata version 1.1 */ + buffer_offset = bytes[14]; + buffer_offset |= bytes[15] << 8; + if (buffer_offset >= length) { + return false; + } + if (buffer_offset > 0) { + device->hid_descriptor_size = bytes[buffer_offset]; + if (buffer_offset + 1 + device->hid_descriptor_size > length) { + return false; + } + device->hid_descriptor = SDL_malloc(device->hid_descriptor_size); + SDL_memcpy(device->hid_descriptor, &bytes[buffer_offset + 1], device->hid_descriptor_size); +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("GIP received HID descriptor: size = %d", device->hid_descriptor, device->hid_descriptor_size); +#endif + } + } + + *offset += length; + return true; +} + +static bool GIP_ParseMessageMetadata(GIP_MessageMetadata *metadata, const Uint8 *bytes, int num_bytes, int *offset) +{ + Uint16 length; + + bytes = &bytes[*offset]; + num_bytes -= *offset; + + if (num_bytes < 2) { + return false; + } + length = bytes[0]; + length |= bytes[1] << 8; + if (num_bytes < length) { + return false; + } + + if (length < 15) { + return false; + } + + metadata->type = bytes[2]; + metadata->length = bytes[3]; + metadata->length |= bytes[4] << 8; + metadata->data_type = bytes[5]; + metadata->data_type |= bytes[6] << 8; + metadata->flags = bytes[7]; + metadata->flags |= bytes[8] << 8; + metadata->flags |= bytes[9] << 16; + metadata->flags |= bytes[10] << 24; + metadata->period = bytes[11]; + metadata->period |= bytes[12] << 8; + metadata->persistence_timeout = bytes[13]; + metadata->persistence_timeout |= bytes[14] << 8; + +#ifdef DEBUG_XBOX_PROTOCOL + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "GIP: Supported vendor message type %02x of length %d, %s, %s, %s", + metadata->type, + metadata->length, + metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM ? + (metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM ? "bidirectional" : "upstream") : + metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM ? "downstream" : + metadata->flags & GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE ? "downstream request response" : + "unknown direction", + metadata->flags & GIP_MESSAGE_FLAG_SEQUENCED ? "sequenced" : "not sequenced", + metadata->flags & GIP_MESSAGE_FLAG_RELIABLE ? "reliable" : "unreliable"); +#endif + + *offset += length; + return true; +} + +static bool GIP_ParseMetadata(GIP_Metadata *metadata, const Uint8 *bytes, int num_bytes) +{ + int header_size; + int metadata_size; + int offset = 0; + int i; + + if (num_bytes < 16) { + return false; + } + +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("GIP received metadata: size = %d", bytes, num_bytes); +#endif + + header_size = bytes[0]; + header_size |= bytes[1] << 8; + if (num_bytes < header_size || header_size < 16) { + return false; + } + metadata->version_major = bytes[2]; + metadata->version_major |= bytes[3] << 8; + metadata->version_minor = bytes[4]; + metadata->version_minor |= bytes[5] << 8; + /* Middle bytes are reserved */ + metadata_size = bytes[14]; + metadata_size |= bytes[15] << 8; + + if (num_bytes < metadata_size || metadata_size < header_size) { + return false; + } + offset = header_size; + + if (!GIP_ParseDeviceMetadata(metadata, bytes, num_bytes, &offset)) { + goto err; + } + + if (offset >= num_bytes) { + goto err; + } + metadata->num_messages = bytes[offset]; + offset++; + if (metadata->num_messages > 0) { + metadata->message_metadata = SDL_calloc(metadata->num_messages, sizeof(*metadata->message_metadata)); + for (i = 0; i < metadata->num_messages; i++) { + if (!GIP_ParseMessageMetadata(&metadata->message_metadata[i], bytes, num_bytes, &offset)) { + goto err; + } + } + } + + return true; + +err: + GIP_MetadataFree(metadata); + return false; +} + +static bool GIP_Acknowledge( + GIP_Device *device, + const GIP_Header *header, + Uint32 fragment_offset, + Uint16 bytes_remaining) +{ + Uint8 buffer[] = { + GIP_CONTROL_CODE_ACK, + header->message_type, + header->flags & GIP_FLAG_SYSTEM, + (Uint8) fragment_offset, + (Uint8) (fragment_offset >> 8), + (Uint8) (fragment_offset >> 16), + fragment_offset >> 24, + (Uint8) bytes_remaining, + bytes_remaining >> 8, + }; + + return GIP_SendRawMessage(device, + GIP_CMD_PROTO_CONTROL, + GIP_FLAG_SYSTEM | (header->flags & GIP_FLAG_ATTACHMENT_MASK), + header->sequence_id, + buffer, + sizeof(buffer), + false, + NULL, + NULL); +} + +static bool GIP_FragmentFailed(GIP_Attachment *attachment, const GIP_Header *header) +{ + attachment->fragment_retries++; + if (attachment->fragment_retries > 8) { + if (attachment->fragment_data) { + SDL_free(attachment->fragment_data); + attachment->fragment_data = NULL; + } + attachment->fragment_message = 0; + } + return GIP_Acknowledge(attachment->device, + header, + attachment->fragment_offset, + (Uint16) (attachment->total_length - attachment->fragment_offset)); +} + +static bool GIP_EnableEliteButtons(GIP_Attachment *attachment) { + if (attachment->device->device->vendor_id == USB_VENDOR_MICROSOFT) { + if (attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1) { + attachment->xbe_format = GIP_BTN_FMT_XBE1; + } else if (attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) { + if (attachment->firmware_major_version == 4) { + attachment->xbe_format = GIP_BTN_FMT_XBE2_4; + } else if (attachment->firmware_major_version == 5) { + /* + * The exact range for this being necessary is unknown, but it + * starts at 5.11 and at either 5.16 or 5.17. This approach + * still works on 5.21, even if it's not necessary, so having + * a loose upper limit is fine. + */ + if (attachment->firmware_minor_version >= 11 && + attachment->firmware_minor_version < 17) + { + attachment->xbe_format = GIP_BTN_FMT_XBE2_RAW; + } else { + attachment->xbe_format = GIP_BTN_FMT_XBE2_5; + } + } + } + } + if (attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) { + /* + * The meaning of this packet is unknown and not documented, but it's + * needed for the Elite 2 controller to send raw reports + */ + static const Uint8 enable_raw_report[] = { 7, 0 }; + + return GIP_SendVendorMessage(attachment, + GIP_SL_ELITE_CONFIG, + 0, + enable_raw_report, + sizeof(enable_raw_report)); + } + + return true; +} + +static bool GIP_SendGuideButtonLED(GIP_Attachment *attachment, Uint8 pattern, Uint8 intensity) +{ + Uint8 buffer[] = { + GIP_LED_GUIDE, + pattern, + intensity, + }; + + if (!GIP_SupportsSystemMessage(attachment, GIP_CMD_LED, false)) { + return true; + } + return GIP_SendSystemMessage(attachment, GIP_CMD_LED, 0, buffer, sizeof(buffer)); +} + +static bool GIP_SendQueryFirmware(GIP_Attachment *attachment, Uint8 slot) +{ + /* The "slot" variable might not be correct; the packet format is still unclear */ + Uint8 buffer[] = { 0x1, slot, 0, 0, 0 }; + + return GIP_SendSystemMessage(attachment, GIP_CMD_FIRMWARE, 0, buffer, sizeof(buffer)); +} + +static bool GIP_SendSetDeviceState(GIP_Attachment *attachment, Uint8 state) +{ + Uint8 buffer[] = { state }; + return GIP_SendSystemMessage(attachment, + GIP_CMD_SET_DEVICE_STATE, + attachment->attachment_index, + buffer, + sizeof(buffer)); +} + +static bool GIP_SendInitSequence(GIP_Attachment *attachment) +{ + if (attachment->features & GIP_FEATURE_EXTENDED_SET_DEVICE_STATE) { + /* + * The meaning of this packet is unknown and not documented, but it's + * needed for the Elite 2 controller to start up on older firmwares + */ + static const Uint8 set_device_state[] = { GIP_STATE_UNK6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x55, 0x53, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; + + if (!GIP_SendSystemMessage(attachment, + GIP_CMD_SET_DEVICE_STATE, + 0, + set_device_state, + sizeof(set_device_state))) + { + return false; + } + } + if (!GIP_EnableEliteButtons(attachment)) { + return false; + } + if (!GIP_SendSetDeviceState(attachment, GIP_STATE_START)) { + return false; + } + attachment->device_state = GIP_STATE_START; + + if (!GIP_SendGuideButtonLED(attachment, GIP_LED_GUIDE_ON, 20)) { + return false; + } + + if (GIP_SupportsSystemMessage(attachment, GIP_CMD_SECURITY, false) && + !(attachment->features & GIP_FEATURE_SECURITY_OPT_OUT)) + { + /* TODO: Implement Security command property */ + Uint8 buffer[] = { 0x1, 0x0 }; + GIP_SendSystemMessage(attachment, GIP_CMD_SECURITY, 0, buffer, sizeof(buffer)); + } + + if (GIP_SupportsVendorMessage(attachment, GIP_CMD_INITIAL_REPORTS_REQUEST, false)) { + GIP_InitialReportsRequest request = { 0 }; + GIP_SendVendorMessage(attachment, GIP_CMD_INITIAL_REPORTS_REQUEST, 0, (const Uint8 *)&request, sizeof(request)); + } + + if (GIP_SupportsVendorMessage(attachment, GIP_CMD_DEVICE_CAPABILITIES, false)) { + GIP_SendVendorMessage(attachment, GIP_CMD_DEVICE_CAPABILITIES, 0, NULL, 0); + } + + if ((!attachment->attachment_index || GIP_AttachmentIsController(attachment)) && !attachment->joystick) { + return HIDAPI_JoystickConnected(attachment->device->device, &attachment->joystick); + } + if (attachment->attachment_type == GIP_TYPE_CHATPAD && !attachment->keyboard) { + attachment->keyboard = (SDL_KeyboardID)(uintptr_t) attachment; + SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad"); + } + return true; +} + +static bool GIP_EnsureMetadata(GIP_Attachment *attachment) +{ + switch (attachment->got_metadata) { + case GIP_METADATA_GOT: + case GIP_METADATA_FAKED: + return true; + case GIP_METADATA_NONE: + if (attachment->device->got_hello) { + attachment->device->timeout = GIP_ACME_TIMEOUT; + attachment->got_metadata = GIP_METADATA_PENDING; + attachment->metadata_next = SDL_GetTicks() + 500; + attachment->metadata_retries = 0; + return GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0); + } else { + return GIP_SetMetadataDefaults(attachment); + } + default: + return true; + } +} + +static bool GIP_SetMetadataDefaults(GIP_Attachment *attachment) +{ + if (attachment->attachment_index == 0) { + /* Some decent default settings */ + attachment->features |= GIP_FEATURE_MOTOR_CONTROL; + attachment->attachment_type = GIP_TYPE_GAMEPAD; + attachment->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_GUIDE_BUTTON); + + if (SDL_IsJoystickXboxSeriesX(attachment->device->device->vendor_id, attachment->device->device->product_id)) { + attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP; + } + } + + GIP_HandleQuirks(attachment); + + if (GIP_SupportsSystemMessage(attachment, GIP_CMD_FIRMWARE, false)) { + GIP_SendQueryFirmware(attachment, 2); + } + + attachment->got_metadata = GIP_METADATA_FAKED; + attachment->device->hello_deadline = 0; + if (!attachment->joystick) { + return HIDAPI_JoystickConnected(attachment->device->device, &attachment->joystick); + } + return true; +} + +static bool GIP_HandleCommandProtocolControl( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Protocol Control message"); + return false; +} + +static bool GIP_HandleCommandHelloDevice( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + GIP_HelloDevice message = {0}; + + if (num_bytes != 28) { + return false; + } + + message.device_id = (Uint64) bytes[0]; + message.device_id |= (Uint64) bytes[1] << 8; + message.device_id |= (Uint64) bytes[2] << 16; + message.device_id |= (Uint64) bytes[3] << 24; + message.device_id |= (Uint64) bytes[4] << 32; + message.device_id |= (Uint64) bytes[5] << 40; + message.device_id |= (Uint64) bytes[6] << 48; + message.device_id |= (Uint64) bytes[7] << 56; + + message.vendor_id = bytes[8]; + message.vendor_id |= bytes[9] << 8; + + message.product_id = bytes[10]; + message.product_id |= bytes[11] << 8; + + message.firmware_major_version = bytes[12]; + message.firmware_major_version |= bytes[13] << 8; + + message.firmware_minor_version = bytes[14]; + message.firmware_minor_version |= bytes[15] << 8; + + message.firmware_build_version = bytes[16]; + message.firmware_build_version |= bytes[17] << 8; + + message.firmware_revision = bytes[18]; + message.firmware_revision |= bytes[19] << 8; + + message.hardware_major_version = bytes[20]; + message.hardware_minor_version = bytes[21]; + + message.rf_proto_major_version = bytes[22]; + message.rf_proto_minor_version = bytes[23]; + + message.security_major_version = bytes[24]; + message.security_minor_version = bytes[25]; + + message.gip_major_version = bytes[26]; + message.gip_minor_version = bytes[27]; + + SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, + "GIP: Device hello from %" SDL_PRIx64 " (%04x:%04x)", + message.device_id, message.vendor_id, message.product_id); + SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, + "GIP: Firmware version %d.%d.%d rev %d", + message.firmware_major_version, + message.firmware_minor_version, + message.firmware_build_version, + message.firmware_revision); + + /* + * The GIP spec specifies that the host should reject the device if any of these are wrong. + * I don't know if Windows or an Xbox do, however, so let's just log warnings instead. + */ + if (message.rf_proto_major_version != 1 && message.rf_proto_minor_version != 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Invalid RF protocol version %d.%d, expected 1.0", + message.rf_proto_major_version, message.rf_proto_minor_version); + } + + if (message.security_major_version != 1 && message.security_minor_version != 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Invalid security protocol version %d.%d, expected 1.0", + message.security_major_version, message.security_minor_version); + } + + if (message.gip_major_version != 1 && message.gip_minor_version != 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Invalid GIP version %d.%d, expected 1.0", + message.gip_major_version, message.gip_minor_version); + } + + if (header->flags & GIP_FLAG_ATTACHMENT_MASK) { + return GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0); + } else { + attachment->firmware_major_version = message.firmware_major_version; + attachment->firmware_minor_version = message.firmware_minor_version; + + if (attachment->attachment_index == 0) { + attachment->device->hello_deadline = 0; + attachment->device->got_hello = true; + } + if (attachment->got_metadata == GIP_METADATA_FAKED) { + attachment->got_metadata = GIP_METADATA_NONE; + } + GIP_EnsureMetadata(attachment); + } + return true; +} + +static bool GIP_HandleCommandStatusDevice( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + GIP_ExtendedStatus status; + SDL_Joystick *joystick = NULL; + SDL_PowerState power_state; + int power_percent = 0; + int i; + + if (num_bytes < 1) { + return false; + } + SDL_zero(status); + status.base.battery_level = bytes[0] & 3; + status.base.battery_type = (bytes[0] >> 2) & 3; + status.base.charge = (bytes[0] >> 4) & 3; + status.base.power_level = (bytes[0] >> 6) & 3; + + if (attachment->joystick) { + joystick = SDL_GetJoystickFromID(attachment->joystick); + } + if (joystick) { + switch (status.base.battery_level) { + case GIP_BATTERY_CRITICAL: + power_percent = 1; + break; + case GIP_BATTERY_LOW: + power_percent = 25; + break; + case GIP_BATTERY_MEDIUM: + power_percent = 50; + break; + case GIP_BATTERY_FULL: + power_percent = 100; + break; + } + switch (status.base.charge) { + case GIP_CHARGING: + if (status.base.battery_level == GIP_BATTERY_FULL) { + power_state = SDL_POWERSTATE_CHARGED; + } else { + power_state = SDL_POWERSTATE_CHARGING; + } + break; + case GIP_NOT_CHARGING: + power_state = SDL_POWERSTATE_ON_BATTERY; + break; + case GIP_CHARGE_ERROR: + default: + power_state = SDL_POWERSTATE_UNKNOWN; + break; + } + + switch (status.base.battery_type) { + case GIP_BATTERY_ABSENT: + power_state = SDL_POWERSTATE_NO_BATTERY; + break; + case GIP_BATTERY_STANDARD: + case GIP_BATTERY_RECHARGEABLE: + break; + default: + power_state = SDL_POWERSTATE_UNKNOWN; + break; + } + + SDL_SendJoystickPowerInfo(joystick, power_state, power_percent); + } + + if (num_bytes >= 4) { + status.device_active = bytes[1] & 1; + if (bytes[1] & 2) { + /* Events present */ + if (num_bytes < 5) { + return false; + } + status.num_events = bytes[4]; + if (status.num_events > 5) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Device reported too many events, %d > 5", + status.num_events); + return false; + } + if (5 + status.num_events * 10 > num_bytes) { + return false; + } + for (i = 0; i < status.num_events; i++) { + status.events[i].event_type = bytes[i * 10 + 5]; + status.events[i].event_type |= bytes[i * 10 + 6] << 8; + status.events[i].fault_tag = bytes[i * 10 + 7]; + status.events[i].fault_tag |= bytes[i * 10 + 8] << 8; + status.events[i].fault_tag |= bytes[i * 10 + 9] << 16; + status.events[i].fault_tag |= bytes[i * 10 + 10] << 24; + status.events[i].fault_tag = bytes[i * 10 + 11]; + status.events[i].fault_tag |= bytes[i * 10 + 12] << 8; + status.events[i].fault_tag |= bytes[i * 10 + 13] << 16; + status.events[i].fault_tag |= bytes[i * 10 + 14] << 24; + } + } + } + + GIP_EnsureMetadata(attachment); + return true; +} + +static bool GIP_HandleCommandMetadataRespose( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + GIP_Metadata metadata = {0}; + const GUID *expected_guid = NULL; + bool found_expected_guid; + bool found_controller_guid = false; + int i; + + if (!GIP_ParseMetadata(&metadata, bytes, num_bytes)) { + return false; + } + + if (attachment->got_metadata == GIP_METADATA_GOT) { + GIP_MetadataFree(&attachment->metadata); + } + attachment->metadata = metadata; + attachment->got_metadata = GIP_METADATA_GOT; + attachment->features = 0; + + attachment->attachment_type = GIP_TYPE_UNKNOWN; +#ifdef DEBUG_XBOX_PROTOCOL + for (i = 0; i < metadata.device.num_preferred_types; i++) { + const char *type = metadata.device.preferred_types[i]; + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Device preferred type: %s", type); + } +#endif + for (i = 0; i < metadata.device.num_preferred_types; i++) { + const char *type = metadata.device.preferred_types[i]; + if (SDL_strcmp(type, "Windows.Xbox.Input.Gamepad") == 0) { + attachment->attachment_type = GIP_TYPE_GAMEPAD; + expected_guid = &GUID_IGamepad; + break; + } + if (SDL_strcmp(type, "Microsoft.Xbox.Input.ArcadeStick") == 0) { + attachment->attachment_type = GIP_TYPE_ARCADE_STICK; + expected_guid = &GUID_ArcadeStick; + break; + } + if (SDL_strcmp(type, "Windows.Xbox.Input.ArcadeStick") == 0) { + attachment->attachment_type = GIP_TYPE_ARCADE_STICK; + expected_guid = &GUID_ArcadeStick; + break; + } + if (SDL_strcmp(type, "Microsoft.Xbox.Input.FlightStick") == 0) { + attachment->attachment_type = GIP_TYPE_FLIGHT_STICK; + expected_guid = &GUID_FlightStick; + break; + } + if (SDL_strcmp(type, "Windows.Xbox.Input.FlightStick") == 0) { + attachment->attachment_type = GIP_TYPE_FLIGHT_STICK; + expected_guid = &GUID_FlightStick; + break; + } + if (SDL_strcmp(type, "Microsoft.Xbox.Input.Wheel") == 0) { + attachment->attachment_type = GIP_TYPE_WHEEL; + expected_guid = &GUID_Wheel; + break; + } + if (SDL_strcmp(type, "Windows.Xbox.Input.Wheel") == 0) { + attachment->attachment_type = GIP_TYPE_WHEEL; + expected_guid = &GUID_Wheel; + break; + } + if (SDL_strcmp(type, "Windows.Xbox.Input.NavigationController") == 0) { + attachment->attachment_type = GIP_TYPE_NAVIGATION_CONTROLLER; + expected_guid = &GUID_NavigationController; + break; + } + if (SDL_strcmp(type, "Windows.Xbox.Input.Chatpad") == 0) { + attachment->attachment_type = GIP_TYPE_CHATPAD; + break; + } + if (SDL_strcmp(type, "Windows.Xbox.Input.Headset") == 0) { + attachment->attachment_type = GIP_TYPE_HEADSET; + expected_guid = &GUID_IHeadset; + break; + } + } + + found_expected_guid = !expected_guid; + for (i = 0; i < metadata.device.num_supported_interfaces; i++) { + const GUID* guid = &metadata.device.supported_interfaces[i]; +#ifdef DEBUG_XBOX_PROTOCOL + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "GIP: Supported interface: %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + guid->a, guid->b, guid->c, guid->d[0], guid->d[1], + guid->d[2], guid->d[3], guid->d[4], guid->d[5], guid->d[6], guid->d[7]); +#endif + if (expected_guid && SDL_memcmp(expected_guid, guid, sizeof(GUID)) == 0) { + found_expected_guid = true; + } + if (SDL_memcmp(&GUID_IController, guid, sizeof(GUID)) == 0) { + found_controller_guid = true; + continue; + } + if (SDL_memcmp(&GUID_IDevAuthPCOptOut, guid, sizeof(GUID)) == 0) { + attachment->features |= GIP_FEATURE_SECURITY_OPT_OUT; + continue; + } + if (SDL_memcmp(&GUID_IConsoleFunctionMap_InputReport, guid, sizeof(GUID)) == 0) { + attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP; + continue; + } + if (SDL_memcmp(&GUID_IConsoleFunctionMap_OverflowInputReport, guid, sizeof(GUID)) == 0) { + attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW; + continue; + } + if (SDL_memcmp(&GUID_IEliteButtons, guid, sizeof(GUID)) == 0) { + attachment->features |= GIP_FEATURE_ELITE_BUTTONS; + continue; + } + if (SDL_memcmp(&GUID_DynamicLatencyInput, guid, sizeof(GUID)) == 0) { + attachment->features |= GIP_FEATURE_DYNAMIC_LATENCY_INPUT; + continue; + } + } + + for (i = 0; i < metadata.num_messages; i++) { + GIP_MessageMetadata *message = &metadata.message_metadata[i]; + if (message->type == GIP_CMD_DIRECT_MOTOR && message->length >= 9 && + (message->flags & GIP_MESSAGE_FLAG_DOWNSTREAM)) { + attachment->features |= GIP_FEATURE_MOTOR_CONTROL; + } + } + + if (!found_expected_guid || (GIP_AttachmentIsController(attachment) && !found_controller_guid)) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "GIP: Controller was missing expected GUID. This controller probably won't work on an actual Xbox."); + } + + if ((attachment->features & GIP_FEATURE_GUIDE_COLOR) && + !GIP_SupportsVendorMessage(attachment, GIP_CMD_GUIDE_COLOR, false)) + { + attachment->features &= ~GIP_FEATURE_GUIDE_COLOR; + } + + GIP_HandleQuirks(attachment); + + return GIP_SendInitSequence(attachment); +} + +static bool GIP_HandleCommandSecurity( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Security message"); + return false; +} + +static bool GIP_HandleCommandGuideButtonStatus( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + Uint64 timestamp = SDL_GetTicksNS(); + SDL_Joystick *joystick = NULL; + + if (attachment->device->device->num_joysticks < 1) { + return true; + } + + joystick = SDL_GetJoystickFromID(attachment->joystick); + if (!joystick) { + return false; + } + if (bytes[1] == VK_LWIN) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (bytes[0] & 0x03) != 0); + } + + return true; +} + +static bool GIP_HandleCommandAudioControl( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Audio Control message"); + return false; +} + +static bool GIP_HandleCommandFirmware( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + if (num_bytes < 1) { + return false; + } + if (bytes[0] == 1) { + Uint16 major, minor, build, rev; + + if (num_bytes < 14) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short firmware message"); + + return false; + } + major = bytes[6]; + major |= bytes[7] << 8; + minor = bytes[8]; + minor |= bytes[9] << 8; + build = bytes[10]; + build |= bytes[11] << 8; + rev = bytes[12]; + rev |= bytes[13] << 8; + + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Firmware version: %d.%d.%d rev %d", major, minor, build, rev); + + attachment->firmware_major_version = major; + attachment->firmware_minor_version = minor; + + if (attachment->device->device->vendor_id == USB_VENDOR_MICROSOFT && + attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) + { + return GIP_EnableEliteButtons(attachment); + } + return true; + } else { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Firmware message"); + + return false; + } +} + +static bool GIP_HandleCommandRawReport( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + Uint64 timestamp = SDL_GetTicksNS(); + SDL_Joystick *joystick = NULL; + + if (attachment->device->device->num_joysticks < 1) { + return true; + } + + joystick = SDL_GetJoystickFromID(attachment->joystick); + if (!joystick) { + return true; + } + + if (num_bytes < 17) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short raw report"); + return false; + } + + if ((attachment->features & GIP_FEATURE_ELITE_BUTTONS) && attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) { + if (bytes[15] & 3) { + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx, + 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 1, + 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 2, + 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 3, + 0); + } else { + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x01) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 1, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x02) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 2, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x04) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 3, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x08) != 0); + } + } + return true; +} + +static bool GIP_HandleCommandHidReport( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + Uint64 timestamp = SDL_GetTicksNS(); + // SDL doesn't have HID descriptor parsing, so we have to hardcode for the Chatpad descriptor instead. + // I don't know of any other devices that emit HID reports, so this should be safe. + if (attachment->attachment_type != GIP_TYPE_CHATPAD || !attachment->keyboard || num_bytes != 8) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented HID Report message"); + return false; + } + + Uint8 modifiers = bytes[0]; + Uint8 changed_modifiers = modifiers ^ attachment->last_modifiers; + if (changed_modifiers & 0x02) { + if (modifiers & 0x02) { + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_LSHIFT, true); + } else { + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_LSHIFT, false); + } + } + // The chatpad has several non-ASCII characters that it sends as Alt codes + if (changed_modifiers & 0x04) { + if (modifiers & 0x04) { + attachment->altcode_digit = 0; + attachment->altcode = 0; + } else { + if (attachment->altcode_digit == 4) { + char utf8[4] = {0}; + // Some Alt codes don't match their Unicode codepoint for some reason + switch (attachment->altcode) { + case 128: + SDL_UCS4ToUTF8(0x20AC, utf8); + break; + case 138: + SDL_UCS4ToUTF8(0x0160, utf8); + break; + case 140: + SDL_UCS4ToUTF8(0x0152, utf8); + break; + case 154: + SDL_UCS4ToUTF8(0x0161, utf8); + break; + case 156: + SDL_UCS4ToUTF8(0x0153, utf8); + break; + default: + SDL_UCS4ToUTF8(attachment->altcode, utf8); + break; + } + SDL_SendKeyboardText(utf8); + } + attachment->altcode_digit = -1; + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_NUMLOCKCLEAR, true); + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_NUMLOCKCLEAR, false); + } + } + + if (!bytes[2] && attachment->last_key) { + if (attachment->last_key == SDL_SCANCODE_CAPSLOCK) { + attachment->capslock = !attachment->capslock; + } + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, attachment->last_key, false); + if (!(attachment->last_modifiers & 0xfd)) { + SDL_Keycode keycode = SDL_GetKeymapKeycode(NULL, + attachment->last_key, + ((attachment->last_modifiers & 0x02) || attachment->capslock) ? SDL_KMOD_SHIFT : 0); + if (keycode && keycode < 0x80) { + char text[2] = { (char)keycode }; + SDL_SendKeyboardText(text); + } + } + attachment->last_key = 0; + } else { + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, bytes[2], true); + attachment->last_key = bytes[2]; + + if ((modifiers & 0x04) && attachment->altcode_digit >= 0) { + int digit = bytes[2] - SDL_SCANCODE_KP_1 + 1; + if (digit < 1 || digit > 10) { + attachment->altcode_digit = -1; + } else { + attachment->altcode_digit++; + attachment->altcode *= 10; + if (digit < 10) { + attachment->altcode += digit; + } + } + } + } + + attachment->last_modifiers = modifiers; + return true; +} + +static bool GIP_HandleCommandExtended( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + char serial[33] = {0}; + + if (num_bytes < 2) { + return false; + } + + switch (bytes[0]) { + case GIP_EXTCMD_GET_SERIAL_NUMBER: + if (bytes[1] != GIP_EXTENDED_STATUS_OK) { + return true; + } + if (header->flags & GIP_FLAG_ATTACHMENT_MASK) { + return true; + } + SDL_memcpy(serial, &bytes[2], SDL_min(sizeof(serial) - 1, num_bytes - 2)); + HIDAPI_SetDeviceSerial(attachment->device->device, serial); + break; + default: + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Extended message type %02x", bytes[0]); + return false; + } + + return true; +} + +static void GIP_HandleNavigationReport( + GIP_Attachment *attachment, + SDL_Joystick *joystick, + Uint64 timestamp, + const Uint8 *bytes, + int num_bytes) +{ + if (attachment->last_input[0] != bytes[0]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((bytes[0] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((bytes[0] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((bytes[0] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((bytes[0] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((bytes[0] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((bytes[0] & 0x80) != 0)); + } + + if (attachment->last_input[1] != bytes[1]) { + Uint8 hat = 0; + + if (bytes[1] & 0x01) { + hat |= SDL_HAT_UP; + } + if (bytes[1] & 0x02) { + hat |= SDL_HAT_DOWN; + } + if (bytes[1] & 0x04) { + hat |= SDL_HAT_LEFT; + } + if (bytes[1] & 0x08) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + if (attachment->attachment_type == GIP_TYPE_ARCADE_STICK) { + /* Previous */ + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((bytes[1] & 0x10) != 0)); + /* Next */ + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((bytes[1] & 0x20) != 0)); + } else { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((bytes[1] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((bytes[1] & 0x20) != 0)); + } + } +} + +static void GIP_HandleGamepadReport( + GIP_Attachment *attachment, + SDL_Joystick *joystick, + Uint64 timestamp, + const Uint8 *bytes, + int num_bytes) +{ + Sint16 axis; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((bytes[1] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((bytes[1] & 0x80) != 0)); + + axis = bytes[2]; + axis |= bytes[3] << 8; + axis = SDL_clamp(axis, 0, 1023); + axis = (axis - 512) * 64; + if (axis == 32704) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + axis = bytes[4]; + axis |= bytes[5] << 8; + axis = SDL_clamp(axis, 0, 1023); + axis = (axis - 512) * 64; + if (axis == 32704) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + axis = bytes[6]; + axis |= bytes[7] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = bytes[8]; + axis |= bytes[9] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); + axis = bytes[10]; + axis |= bytes[11] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = bytes[12]; + axis |= bytes[13] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); +} + +static void GIP_HandleArcadeStickReport( + GIP_Attachment *attachment, + SDL_Joystick *joystick, + Uint64 timestamp, + const Uint8 *bytes, + int num_bytes) +{ + Sint16 axis; + axis = bytes[2]; + axis |= bytes[3] << 8; + axis = SDL_clamp(axis, 0, 1023); + axis = (axis - 512) * 64; + if (axis == 32704) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + axis = bytes[4]; + axis |= bytes[5] << 8; + axis = SDL_clamp(axis, 0, 1023); + axis = (axis - 512) * 64; + if (axis == 32704) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + if (num_bytes >= 19) { + /* Extra button 6 */ + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (bytes[18] & 0x40) ? 32767 : -32768); + /* Extra button 7 */ + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (bytes[18] & 0x80) ? 32767 : -32768); + } +} + +static void GIP_HandleFlightStickReport( + GIP_Attachment *attachment, + SDL_Joystick *joystick, + Uint64 timestamp, + const Uint8 *bytes, + int num_bytes) +{ + Sint16 axis; + int i; + + if (num_bytes < 19) { + return; + } + + if (attachment->last_input[2] != bytes[2]) { + /* Fire 1 and 2 */ + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((bytes[2] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((bytes[2] & 0x02) != 0)); + } + for (i = 0; i < attachment->extra_buttons;) { + if (attachment->last_input[i / 8 + 3] != bytes[i / 8 + 3]) { + for (; i < attachment->extra_buttons; i++) { + SDL_SendJoystickButton(timestamp, + joystick, + (Uint8) (attachment->extra_button_idx + i), + ((bytes[i / 8 + 3] & (1u << i)) != 0)); + } + } else { + i += 8; + } + } + + /* Roll, pitch and yaw are signed. Throttle and any extra axes are unsigned. All values are full-range. */ + axis = bytes[11]; + axis |= bytes[12] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + + axis = bytes[13]; + axis |= bytes[14] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + + axis = bytes[15]; + axis |= bytes[16] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + + /* There are no more signed values, so skip RIGHTY */ + + axis = (bytes[18] << 8) - 0x8000; + axis |= bytes[17]; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + for (i = 0; i < attachment->extra_axes; i++) { + if (20 + i * 2 >= num_bytes) { + return; + } + axis = (bytes[20 + i * 2] << 8) - 0x8000; + axis |= bytes[19 + i * 2]; + SDL_SendJoystickAxis(timestamp, joystick, (Uint8) (SDL_GAMEPAD_AXIS_RIGHT_TRIGGER + i), axis); + } +} + +static bool GIP_HandleLLInputReport( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + Uint64 timestamp = SDL_GetTicksNS(); + SDL_Joystick *joystick = NULL; + + if (attachment->device->device->num_joysticks < 1) { + GIP_EnsureMetadata(attachment); + if (attachment->got_metadata != GIP_METADATA_GOT && attachment->got_metadata != GIP_METADATA_FAKED) { + return true; + } + } + + joystick = SDL_GetJoystickFromID(attachment->joystick); + if (!joystick) { + return false; + } + + if (attachment->device_state != GIP_STATE_START) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding early input report"); + attachment->device_state = GIP_STATE_START; + return true; + } + + if (num_bytes < 14) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short input report"); + return false; + } + + GIP_HandleNavigationReport(attachment, joystick, timestamp, bytes, num_bytes); + + switch (attachment->attachment_type) { + case GIP_TYPE_GAMEPAD: + default: + GIP_HandleGamepadReport(attachment, joystick, timestamp, bytes, num_bytes); + break; + case GIP_TYPE_ARCADE_STICK: + GIP_HandleArcadeStickReport(attachment, joystick, timestamp, bytes, num_bytes); + break; + case GIP_TYPE_FLIGHT_STICK: + GIP_HandleFlightStickReport(attachment, joystick, timestamp, bytes, num_bytes); + break; + } + + if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) { + bool clear = false; + if (attachment->xbe_format == GIP_BTN_FMT_XBE1 && + num_bytes > GIP_BTN_OFFSET_XBE1 && + attachment->last_input[GIP_BTN_OFFSET_XBE1] != bytes[GIP_BTN_OFFSET_XBE1] && + (bytes[GIP_BTN_OFFSET_XBE1] & 0x10)) + { + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx, + (bytes[GIP_BTN_OFFSET_XBE1] & 0x02) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 1, + (bytes[GIP_BTN_OFFSET_XBE1] & 0x08) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 2, + (bytes[GIP_BTN_OFFSET_XBE1] & 0x01) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 3, + (bytes[GIP_BTN_OFFSET_XBE1] & 0x04) != 0); + } else if ((attachment->xbe_format == GIP_BTN_FMT_XBE2_4 || + attachment->xbe_format == GIP_BTN_FMT_XBE2_5) && + num_bytes > GIP_BTN_OFFSET_XBE2) + { + int profile_offset = attachment->xbe_format == GIP_BTN_FMT_XBE2_4 ? 15 : 20; + if (attachment->last_input[GIP_BTN_OFFSET_XBE2] != bytes[GIP_BTN_OFFSET_XBE2] || + attachment->last_input[profile_offset] != bytes[profile_offset]) + { + if (bytes[profile_offset] & 3) { + clear = true; + } else { + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x01) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 1, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x02) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 2, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x04) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 3, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x08) != 0); + } + } + } else { + clear = true; + } + if (clear) { + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx, + 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 1, + 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 2, + 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 3, + 0); + } + } + + if ((attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) && num_bytes >= 32) { + int function_map_offset = -1; + if (attachment->features & GIP_FEATURE_DYNAMIC_LATENCY_INPUT) { + /* The dynamic latency input bytes are after the console function map */ + if (num_bytes >= 40) { + function_map_offset = num_bytes - 26; + } + } else { + function_map_offset = num_bytes - 18; + } + if (function_map_offset >= 14) { + if (attachment->last_input[function_map_offset] != bytes[function_map_offset]) { + SDL_SendJoystickButton(timestamp, + joystick, + attachment->share_button_idx, + (bytes[function_map_offset] & 0x01) != 0); + } + } + } + + SDL_memcpy(attachment->last_input, bytes, SDL_min(num_bytes, sizeof(attachment->last_input))); + + return true; +} + +static bool GIP_HandleLLStaticConfiguration( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Static Configuration message"); + return false; +} + +static bool GIP_HandleLLButtonInfoReport( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Button Info Report message"); + return false; +} + +static bool GIP_HandleLLOverflowInputReport( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Overflow Input Report message"); + return false; +} + +static bool GIP_HandleAudioData( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Audio Data message"); + return false; +} + +static bool GIP_HandleSystemMessage( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + if (attachment->attachment_index > 0 && attachment->attachment_type == GIP_TYPE_UNKNOWN) { + // XXX If we reattach to a controller after it's been initialized, it might have + // attachments we don't know about. Try to figure out what this one is. + if (header->message_type == GIP_CMD_HID_REPORT && num_bytes == 8) { + if (!attachment->keyboard) { + attachment->keyboard = (SDL_KeyboardID)(uintptr_t) attachment; + SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad"); + } + attachment->attachment_type = GIP_TYPE_CHATPAD; + attachment->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_HID_REPORT); + } + } + if (!GIP_SupportsSystemMessage(attachment, header->message_type, true)) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received claimed-unsupported system message type %02x", + header->message_type); + return false; + } + switch (header->message_type) { + case GIP_CMD_PROTO_CONTROL: + return GIP_HandleCommandProtocolControl(attachment, header, bytes, num_bytes); + case GIP_CMD_HELLO_DEVICE: + return GIP_HandleCommandHelloDevice(attachment, header, bytes, num_bytes); + case GIP_CMD_STATUS_DEVICE: + return GIP_HandleCommandStatusDevice(attachment, header, bytes, num_bytes); + case GIP_CMD_METADATA: + return GIP_HandleCommandMetadataRespose(attachment, header, bytes, num_bytes); + case GIP_CMD_SECURITY: + return GIP_HandleCommandSecurity(attachment, header, bytes, num_bytes); + case GIP_CMD_GUIDE_BUTTON: + return GIP_HandleCommandGuideButtonStatus(attachment, header, bytes, num_bytes); + case GIP_CMD_AUDIO_CONTROL: + return GIP_HandleCommandAudioControl(attachment, header, bytes, num_bytes); + case GIP_CMD_FIRMWARE: + return GIP_HandleCommandFirmware(attachment, header, bytes, num_bytes); + case GIP_CMD_HID_REPORT: + return GIP_HandleCommandHidReport(attachment, header, bytes, num_bytes); + case GIP_CMD_EXTENDED: + return GIP_HandleCommandExtended(attachment, header, bytes, num_bytes); + case GIP_AUDIO_DATA: + return GIP_HandleAudioData(attachment, header, bytes, num_bytes); + default: + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received unknown system message type %02x", + header->message_type); + return false; + } +} + +static GIP_Attachment *GIP_EnsureAttachment(GIP_Device *device, Uint8 attachment_index) +{ + GIP_Attachment *attachment = device->attachments[attachment_index]; + if (!attachment) { + attachment = SDL_calloc(1, sizeof(*attachment)); + attachment->attachment_index = attachment_index; + if (attachment_index > 0) { + attachment->attachment_type = GIP_TYPE_UNKNOWN; + } + attachment->device = device; + attachment->metadata.device.in_system_messages[0] = GIP_DEFAULT_IN_SYSTEM_MESSAGES; + attachment->metadata.device.out_system_messages[0] = GIP_DEFAULT_OUT_SYSTEM_MESSAGES; + device->attachments[attachment_index] = attachment; + } + return attachment; +} + +static bool GIP_HandleMessage( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + if (header->flags & GIP_FLAG_SYSTEM) { + return GIP_HandleSystemMessage(attachment, header, bytes, num_bytes); + } else { + switch (header->message_type) { + case GIP_CMD_RAW_REPORT: + if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) { + return GIP_HandleCommandRawReport(attachment, header, bytes, num_bytes); + } + break; + case GIP_LL_INPUT_REPORT: + return GIP_HandleLLInputReport(attachment, header, bytes, num_bytes); + case GIP_LL_STATIC_CONFIGURATION: + return GIP_HandleLLStaticConfiguration(attachment, header, bytes, num_bytes); + case GIP_LL_BUTTON_INFO_REPORT: + return GIP_HandleLLButtonInfoReport(attachment, header, bytes, num_bytes); + case GIP_LL_OVERFLOW_INPUT_REPORT: + return GIP_HandleLLOverflowInputReport(attachment, header, bytes, num_bytes); + } + } + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received unknown vendor message type %02x", + header->message_type); + return false; +} + +static void GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_bytes) +{ + GIP_Header header; + int offset = 3; + bool ok = true; + Uint64 fragment_offset = 0; + Uint16 bytes_remaining = 0; + bool is_fragment; + Uint8 attachment_index; + GIP_Attachment *attachment; + + if (num_bytes < 5) { + return; + } + + header.message_type = bytes[0]; + header.flags = bytes[1]; + header.sequence_id = bytes[2]; + offset += GIP_DecodeLength(&header.length, &bytes[offset], num_bytes - offset); + + is_fragment = header.flags & GIP_FLAG_FRAGMENT; + attachment_index = header.flags & GIP_FLAG_ATTACHMENT_MASK; + attachment = GIP_EnsureAttachment(device, attachment_index); + +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("GIP received message: size = %d", bytes, num_bytes); +#endif + + /* Handle coalescing fragmented messages */ + if (is_fragment) { + if (header.flags & GIP_FLAG_INIT_FRAG) { + Uint64 total_length; + if (attachment->fragment_message) { + /* + * Reset fragment buffer if we get a new initial + * fragment before finishing the last message. + * TODO: Is this the correct behavior? + */ + if (attachment->fragment_data) { + SDL_free(attachment->fragment_data); + attachment->fragment_data = NULL; + } + } + offset += GIP_DecodeLength(&total_length, &bytes[offset], num_bytes - offset); + if (total_length > MAX_MESSAGE_LENGTH) { + return; + } + attachment->total_length = (Uint16) total_length; + attachment->fragment_message = header.message_type; + if (header.length > num_bytes - offset) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received fragment that claims to be %" SDL_PRIu64 " bytes, expected %i", + header.length, num_bytes - offset); + return; + } + if (header.length > total_length) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received too long fragment, %" SDL_PRIu64 " bytes, exceeds %d", + header.length, attachment->total_length); + return; + } + attachment->fragment_data = SDL_malloc(attachment->total_length); + SDL_memcpy(attachment->fragment_data, &bytes[offset], (size_t) header.length); + fragment_offset = header.length; + attachment->fragment_offset = (Uint32) fragment_offset; + bytes_remaining = (Uint16) (attachment->total_length - fragment_offset); + } else { + if (header.message_type != attachment->fragment_message) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received out of sequence message type %02x, expected %02x", + header.message_type, attachment->fragment_message); + GIP_FragmentFailed(attachment, &header); + return; + } + + offset += GIP_DecodeLength(&fragment_offset, &bytes[offset], num_bytes - offset); + if (fragment_offset != attachment->fragment_offset) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received out of sequence fragment, (claimed %" SDL_PRIu64 ", expected %d)", + fragment_offset, attachment->fragment_offset); + GIP_Acknowledge(device, + &header, + attachment->fragment_offset, + (Uint16) (attachment->total_length - attachment->fragment_offset)); + return; + } else if (fragment_offset + header.length > attachment->total_length) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received too long fragment, %" SDL_PRIu64 " exceeds %d", + fragment_offset + header.length, attachment->total_length); + GIP_FragmentFailed(attachment, &header); + return; + } + + bytes_remaining = attachment->total_length - (Uint16) (fragment_offset + header.length); + if (header.length != 0) { + SDL_memcpy(&attachment->fragment_data[fragment_offset], &bytes[offset], (size_t) header.length); + } else { + ok = GIP_HandleMessage(attachment, &header, attachment->fragment_data, attachment->total_length); + if (attachment->fragment_data) { + SDL_free(attachment->fragment_data); + attachment->fragment_data = NULL; + } + attachment->fragment_message = 0; + } + fragment_offset += header.length; + attachment->fragment_offset = (Uint16) fragment_offset; + } + attachment->fragment_timer = SDL_GetTicks(); + } else if (header.length + offset > num_bytes) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received message with erroneous length (claimed %" SDL_PRIu64 ", actual %d), discarding", + header.length + offset, num_bytes); + return; + } else { + num_bytes -= offset; + bytes += offset; + fragment_offset = header.length; + ok = GIP_HandleMessage(attachment, &header, bytes, num_bytes); + } + + if (ok && (header.flags & GIP_FLAG_ACME)) { + GIP_Acknowledge(device, &header, (Uint32) fragment_offset, bytes_remaining); + } +} + +static void HIDAPI_DriverGIP_RumbleSent(void *userdata) +{ + GIP_Attachment *ctx = (GIP_Attachment *)userdata; + ctx->rumble_time = SDL_GetTicks(); +} + +static bool HIDAPI_DriverGIP_UpdateRumble(GIP_Attachment *attachment) +{ + GIP_DirectMotor motor; + + if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL)) { + return true; + } + + if (attachment->rumble_state == GIP_RUMBLE_STATE_QUEUED && attachment->rumble_time) { + attachment->rumble_state = GIP_RUMBLE_STATE_BUSY; + } + + if (attachment->rumble_state == GIP_RUMBLE_STATE_BUSY) { + const int RUMBLE_BUSY_TIME_MS = 10; + if (SDL_GetTicks() >= (attachment->rumble_time + RUMBLE_BUSY_TIME_MS)) { + attachment->rumble_time = 0; + attachment->rumble_state = GIP_RUMBLE_STATE_IDLE; + } + } + + if (!attachment->rumble_pending) { + return true; + } + + if (attachment->rumble_state != GIP_RUMBLE_STATE_IDLE) { + return true; + } + + // We're no longer pending, even if we fail to send the rumble below + attachment->rumble_pending = false; + + motor.motor_bitmap = GIP_MOTOR_ALL; + motor.left_impulse_level = attachment->left_impulse_level; + motor.right_impulse_level = attachment->right_impulse_level; + motor.left_vibration_level = attachment->left_vibration_level; + motor.right_vibration_level = attachment->right_vibration_level; + motor.duration = SDL_RUMBLE_RESEND_MS / 10 + 5; // Add a 50ms leniency, just in case + motor.delay = 0; + motor.repeat = 0; + + Uint8 message[9] = {0}; + SDL_memcpy(&message[1], &motor, sizeof(motor)); + if (!GIP_SendRawMessage(attachment->device, + GIP_CMD_DIRECT_MOTOR, + attachment->attachment_index, + GIP_SequenceNext(attachment, GIP_CMD_DIRECT_MOTOR, false), + message, + sizeof(message), + true, + HIDAPI_DriverGIP_RumbleSent, + attachment)) + { + return SDL_SetError("Couldn't send rumble packet"); + } + + attachment->rumble_state = GIP_RUMBLE_STATE_QUEUED; + + return true; +} + +static void HIDAPI_DriverGIP_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP, callback, userdata); + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, callback, userdata); +} + +static void HIDAPI_DriverGIP_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP, callback, userdata); + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, callback, userdata); +} + +static bool HIDAPI_DriverGIP_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GIP, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)))); +} + +static bool HIDAPI_DriverGIP_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + // Xbox One controllers speak HID over bluetooth instead of GIP + if (device && device->is_bluetooth) { + return false; + } +#if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI) + if (!SDL_IsJoystickBluetoothXboxOne(vendor_id, product_id)) { + // On macOS we get a shortened version of the real report and + // you can't write output reports for wired controllers, so + // we'll just use the GCController support instead. + return false; + } +#endif + return (type == SDL_GAMEPAD_TYPE_XBOXONE); +} + +static bool HIDAPI_DriverGIP_InitDevice(SDL_HIDAPI_Device *device) +{ + GIP_Device *ctx; + GIP_Attachment *attachment; + + ctx = (GIP_Device *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + ctx->reset_for_metadata = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, false); + + attachment = GIP_EnsureAttachment(ctx, 0); + GIP_HandleQuirks(attachment); + + if (attachment->quirks & GIP_QUIRK_NO_HELLO) { + ctx->got_hello = true; + GIP_EnsureMetadata(attachment); + } else { + ctx->hello_deadline = SDL_GetTicks() + GIP_HELLO_TIMEOUT; + } + + device->context = ctx; + device->type = SDL_GAMEPAD_TYPE_XBOXONE; + + return true; +} + +static int HIDAPI_DriverGIP_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverGIP_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static GIP_Attachment * HIDAPI_DriverGIP_FindAttachment(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + GIP_Device *ctx = (GIP_Device *)device->context; + int i; + + SDL_AssertJoysticksLocked(); + + for (i = 0; i < MAX_ATTACHMENTS; i++) { + if (ctx->attachments[i] && ctx->attachments[i]->joystick == joystick->instance_id) { + return ctx->attachments[i]; + } + } + return NULL; +} + +static bool HIDAPI_DriverGIP_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); + if (!attachment) { + return SDL_SetError("Invalid joystick"); + } + + SDL_AssertJoysticksLocked(); + + attachment->left_impulse_level = 0; + attachment->right_impulse_level = 0; + attachment->left_vibration_level = 0; + attachment->right_vibration_level = 0; + attachment->rumble_state = GIP_RUMBLE_STATE_IDLE; + attachment->rumble_time = 0; + attachment->rumble_pending = false; + SDL_zeroa(attachment->last_input); + + // Initialize the joystick capabilities + joystick->nbuttons = 11; + GIP_EnableEliteButtons(attachment); + if (attachment->xbe_format != GIP_BTN_FMT_UNKNOWN || + (device->vendor_id == USB_VENDOR_MICROSOFT && + device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2)) + { + attachment->paddle_idx = (Uint8) joystick->nbuttons; + joystick->nbuttons += 4; + } + if (attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) { + attachment->share_button_idx = (Uint8) joystick->nbuttons; + joystick->nbuttons++; + } + if (attachment->extra_buttons > 0) { + attachment->extra_button_idx = (Uint8) joystick->nbuttons; + joystick->nbuttons += attachment->extra_buttons; + } + + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + if (attachment->attachment_type == GIP_TYPE_FLIGHT_STICK) { + /* Flight sticks have at least 4 axes, but only 3 are signed values, so we leave RIGHTY unused */ + joystick->naxes += attachment->extra_axes - 1; + } + + joystick->nhats = 1; + + return true; +} + +static bool HIDAPI_DriverGIP_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); + if (!attachment) { + return SDL_SetError("Invalid joystick"); + } + + if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL)) { + return SDL_Unsupported(); + } + + // Magnitude is 1..100 so scale the 16-bit input here + attachment->left_vibration_level = (Uint8)(low_frequency_rumble / 655); + attachment->right_vibration_level = (Uint8)(high_frequency_rumble / 655); + attachment->rumble_pending = true; + + return HIDAPI_DriverGIP_UpdateRumble(attachment); +} + +static bool HIDAPI_DriverGIP_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); + if (!attachment) { + return SDL_SetError("Invalid joystick"); + } + + if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL) || (attachment->quirks & GIP_QUIRK_NO_IMPULSE_VIBRATION)) { + return SDL_Unsupported(); + } + + // Magnitude is 1..100 so scale the 16-bit input here + attachment->left_impulse_level = (Uint8)(left_rumble / 655); + attachment->right_impulse_level = (Uint8)(right_rumble / 655); + attachment->rumble_pending = true; + + return HIDAPI_DriverGIP_UpdateRumble(attachment); +} + +static Uint32 HIDAPI_DriverGIP_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); + Uint32 result = 0; + if (!attachment) { + return 0; + } + + if (attachment->features & GIP_FEATURE_MOTOR_CONTROL) { + result |= SDL_JOYSTICK_CAP_RUMBLE; + if (!(attachment->quirks & GIP_QUIRK_NO_IMPULSE_VIBRATION)) { + result |= SDL_JOYSTICK_CAP_TRIGGER_RUMBLE; + } + } + + if (attachment->features & GIP_FEATURE_GUIDE_COLOR) { + result |= SDL_JOYSTICK_CAP_RGB_LED; + } + + return result; +} + +static bool HIDAPI_DriverGIP_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); + Uint8 buffer[] = { 0x00, 0x00, 0x00, 0x00, 0x00 }; + + if (!attachment) { + return SDL_SetError("Invalid joystick"); + } + + if (!(attachment->features & GIP_FEATURE_GUIDE_COLOR)) { + return SDL_Unsupported(); + } + + buffer[1] = 0x00; // Whiteness? Sets white intensity when RGB is 0, seems additive + buffer[2] = red; + buffer[3] = green; + buffer[4] = blue; + + if (!GIP_SendVendorMessage(attachment, GIP_CMD_GUIDE_COLOR, 0, buffer, sizeof(buffer))) { + return SDL_SetError("Couldn't send LED packet"); + } + return true; +} + +static bool HIDAPI_DriverGIP_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + + +static bool HIDAPI_DriverGIP_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverGIP_UpdateDevice(SDL_HIDAPI_Device *device) +{ + GIP_Device *ctx = (GIP_Device *)device->context; + Uint8 bytes[USB_PACKET_LENGTH]; + int i; + int num_bytes; + bool perform_reset = false; + Uint64 timestamp; + + while ((num_bytes = SDL_hid_read_timeout(device->dev, bytes, sizeof(bytes), ctx->timeout)) > 0) { + ctx->timeout = 0; + GIP_ReceivePacket(ctx, bytes, num_bytes); + } + + timestamp = SDL_GetTicks(); + if (ctx->hello_deadline && timestamp >= ctx->hello_deadline) { + ctx->hello_deadline = 0; + perform_reset = true; + } + for (i = 0; i < MAX_ATTACHMENTS; i++) { + GIP_Attachment *attachment = ctx->attachments[i]; + if (!attachment) { + continue; + } + if (attachment->fragment_message && timestamp >= attachment->fragment_timer + 1000) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Reliable message transfer failed"); + attachment->fragment_message = 0; + } + if (!perform_reset && + attachment->got_metadata == GIP_METADATA_PENDING && + timestamp >= attachment->metadata_next && + attachment->fragment_message != GIP_CMD_METADATA) + { + if (attachment->metadata_retries < 3) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Retrying metadata request"); + attachment->metadata_retries++; + attachment->metadata_next = timestamp + 500; + GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0); + } else { + perform_reset = true; + } + } + if (perform_reset) { + if (ctx->reset_for_metadata) { + GIP_SendSetDeviceState(attachment, GIP_STATE_RESET); + } else { + GIP_SetMetadataDefaults(attachment); + GIP_SendInitSequence(attachment); + } + perform_reset = false; + } + HIDAPI_DriverGIP_UpdateRumble(attachment); + } + + if (num_bytes < 0 && device->num_joysticks > 0) { + // Read error, device is disconnected + for (i = 0; i < MAX_ATTACHMENTS; i++) { + GIP_Attachment *attachment = ctx->attachments[i]; + if (attachment) { + HIDAPI_JoystickDisconnected(device, attachment->joystick); + } + } + } + return (num_bytes >= 0); +} +static void HIDAPI_DriverGIP_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +static void HIDAPI_DriverGIP_FreeDevice(SDL_HIDAPI_Device *device) +{ + GIP_Device *context = (GIP_Device *)device->context; + int i; + + for (i = 0; i < MAX_ATTACHMENTS; i++) { + GIP_Attachment *attachment = context->attachments[i]; + if (!attachment) { + continue; + } + if (attachment->fragment_data) { + SDL_free(attachment->fragment_data); + attachment->fragment_data = NULL; + } + if (attachment->keyboard) { + SDL_RemoveKeyboard(attachment->keyboard); + } + GIP_MetadataFree(&attachment->metadata); + SDL_free(attachment); + context->attachments[i] = NULL; + } +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGIP = { + SDL_HINT_JOYSTICK_HIDAPI_GIP, + true, + HIDAPI_DriverGIP_RegisterHints, + HIDAPI_DriverGIP_UnregisterHints, + HIDAPI_DriverGIP_IsEnabled, + HIDAPI_DriverGIP_IsSupportedDevice, + HIDAPI_DriverGIP_InitDevice, + HIDAPI_DriverGIP_GetDevicePlayerIndex, + HIDAPI_DriverGIP_SetDevicePlayerIndex, + HIDAPI_DriverGIP_UpdateDevice, + HIDAPI_DriverGIP_OpenJoystick, + HIDAPI_DriverGIP_RumbleJoystick, + HIDAPI_DriverGIP_RumbleJoystickTriggers, + HIDAPI_DriverGIP_GetJoystickCapabilities, + HIDAPI_DriverGIP_SetJoystickLED, + HIDAPI_DriverGIP_SendJoystickEffect, + HIDAPI_DriverGIP_SetJoystickSensorsEnabled, + HIDAPI_DriverGIP_CloseJoystick, + HIDAPI_DriverGIP_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_GIP + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_lg4ff.c new file mode 100644 index 0000000..fdf5b11 --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -0,0 +1,1011 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Simon Wood + Copyright (C) 2025 Michal Malý + Copyright (C) 2025 Katharine Chui + + 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_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL3/SDL_events.h" +#include "SDL_hidapijoystick_c.h" + +#ifdef SDL_JOYSTICK_HIDAPI_LG4FF + +#define USB_VENDOR_ID_LOGITECH 0x046d +#define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f +#define USB_DEVICE_ID_LOGITECH_G27_WHEEL 0xc29b +#define USB_DEVICE_ID_LOGITECH_G25_WHEEL 0xc299 +#define USB_DEVICE_ID_LOGITECH_DFGT_WHEEL 0xc29a +#define USB_DEVICE_ID_LOGITECH_DFP_WHEEL 0xc298 +#define USB_DEVICE_ID_LOGITECH_WHEEL 0xc294 + +static Uint32 supported_device_ids[] = { + USB_DEVICE_ID_LOGITECH_G29_WHEEL, + USB_DEVICE_ID_LOGITECH_G27_WHEEL, + USB_DEVICE_ID_LOGITECH_G25_WHEEL, + USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, + USB_DEVICE_ID_LOGITECH_DFP_WHEEL, + USB_DEVICE_ID_LOGITECH_WHEEL +}; + +// keep the same order as the supported_ids array +static const char *supported_device_names[] = { + "Logitech G29", + "Logitech G27", + "Logitech G25", + "Logitech Driving Force GT", + "Logitech Driving Force Pro", + "Driving Force EX" +}; + +static const char *HIDAPI_DriverLg4ff_GetDeviceName(Uint32 device_id) +{ + for (int i = 0;i < (sizeof supported_device_ids) / sizeof(Uint32);i++) { + if (supported_device_ids[i] == device_id) { + return supported_device_names[i]; + } + } + SDL_assert(0); + return ""; +} + +static int HIDAPI_DriverLg4ff_GetNumberOfButtons(Uint32 device_id) +{ + switch (device_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + return 25; + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + return 23; + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + return 19; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + return 21; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + return 14; + case USB_DEVICE_ID_LOGITECH_WHEEL: + return 13; + default: + SDL_assert(0); + return 0; + } +} + +typedef struct +{ + Uint8 last_report_buf[32]; + bool initialized; + bool is_ffex; + Uint16 range; +} SDL_DriverLg4ff_Context; + +static void HIDAPI_DriverLg4ff_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, callback, userdata); +} + +static void HIDAPI_DriverLg4ff_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, callback, userdata); +} + +static bool HIDAPI_DriverLg4ff_IsEnabled(void) +{ + #if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) + /* + * hid.dll simply cannot send 7 bytes reports unlike other platforms + * it enforces full length repots of 17 from the device's descriptor, which does not work on the device + * this breaks ffb and led control, so we disable this by default + */ + bool hint_default = false; + #else + bool hint_default = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT); + #endif + bool enabled = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, hint_default); + + return enabled; +} + +/* + Wheel id information by: + Michal Malý + Simon Wood + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static Uint16 HIDAPI_DriverLg4ff_IdentifyWheel(Uint16 device_id, Uint16 release_number) +{ + #define is_device(ret, m, r) { \ + if ((release_number & m) == r) { \ + return ret; \ + } \ + } + #define is_dfp { \ + is_device(USB_DEVICE_ID_LOGITECH_DFP_WHEEL, 0xf000, 0x1000); \ + } + #define is_dfgt { \ + is_device(USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, 0xff00, 0x1300); \ + } + #define is_g25 { \ + is_device(USB_DEVICE_ID_LOGITECH_G25_WHEEL, 0xff00, 0x1200); \ + } + #define is_g27 { \ + is_device(USB_DEVICE_ID_LOGITECH_G27_WHEEL, 0xfff0, 0x1230); \ + } + #define is_g29 { \ + is_device(USB_DEVICE_ID_LOGITECH_G29_WHEEL, 0xfff8, 0x1350); \ + is_device(USB_DEVICE_ID_LOGITECH_G29_WHEEL, 0xff00, 0x8900); \ + } + switch(device_id){ + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + case USB_DEVICE_ID_LOGITECH_WHEEL: + is_g29; + is_g27; + is_g25; + is_dfgt; + is_dfp; + break; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + is_g29; + is_dfgt; + break; + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + is_g29; + is_g27; + is_g25; + break; + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + is_g29; + is_g27; + break; + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + is_g29; + break; + } + return 0; + #undef is_device + #undef is_dfp + #undef is_dfgt + #undef is_g25 + #undef is_g27 + #undef is_g29 +} + +static int SDL_HIDAPI_DriverLg4ff_GetEnvInt(const char *env_name, int min, int max, int def) +{ + const char *env = SDL_getenv(env_name); + int value = 0; + if(env == NULL) { + return def; + } + value = SDL_atoi(env); + if (value < min) { + value = min; + } + if (value > max) { + value = max; + } + return value; +} + +/* + Commands by: + Michal Malý + Simon Wood + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static bool HIDAPI_DriverLg4ff_SwitchMode(SDL_HIDAPI_Device *device, Uint16 target_product_id){ + int ret = 0; + + switch(target_product_id){ + case USB_DEVICE_ID_LOGITECH_G29_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x09, 0x05, 0x01, 0x01, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_G27_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_G25_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + default:{ + SDL_assert(0); + } + } + if(ret == -1){ + return false; + } + return true; +} + +static bool HIDAPI_DriverLg4ff_IsSupportedDevice( + SDL_HIDAPI_Device *device, + const char *name, + SDL_GamepadType type, + Uint16 vendor_id, + Uint16 product_id, + Uint16 version, + int interface_number, + int interface_class, + int interface_subclass, + int interface_protocol) +{ + int i; + if (vendor_id != USB_VENDOR_ID_LOGITECH) { + return false; + } + for (i = 0;i < SDL_arraysize(supported_device_ids);i++) { + if (supported_device_ids[i] == product_id) { + break; + } + } + if (i == SDL_arraysize(supported_device_ids)) { + return false; + } + Uint16 real_id = HIDAPI_DriverLg4ff_IdentifyWheel(product_id, version); + if (real_id == product_id || real_id == 0) { + // either it is already in native mode, or we don't know what the native mode is + return true; + } + // a supported native mode is found, send mode change command, then still state that we support the device + if (device && SDL_HIDAPI_DriverLg4ff_GetEnvInt("SDL_HIDAPI_LG4FF_NO_MODE_SWITCH", 0, 1, 0) == 0) { + HIDAPI_DriverLg4ff_SwitchMode(device, real_id); + } + return true; +} + +/* + *Ported* + Original functions by: + Michal Malý + lg4ff_set_range_g25 lg4ff_set_range_dfp + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static bool HIDAPI_DriverLg4ff_SetRange(SDL_HIDAPI_Device *device, int range) +{ + Uint8 cmd[7] = {0}; + int ret = 0; + SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; + + if (range < 40) { + range = 40; + } + if (range > 900) { + range = 900; + } + + ctx->range = (Uint16)range; + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:{ + cmd[0] = 0xf8; + cmd[1] = 0x81; + cmd[2] = range & 0x00ff; + cmd[3] = (range & 0xff00) >> 8; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if (ret == -1) { + return false; + } + break; + } + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:{ + int start_left, start_right, full_range; + + /* Prepare "coarse" limit command */ + cmd[0] = 0xf8; + cmd[1] = 0x00; /* Set later */ + cmd[2] = 0x00; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + + if (range > 200) { + cmd[1] = 0x03; + full_range = 900; + } else { + cmd[1] = 0x02; + full_range = 200; + } + ret = SDL_hid_write(device->dev, cmd, 7); + if(ret == -1){ + return false; + } + + /* Prepare "fine" limit command */ + cmd[0] = 0x81; + cmd[1] = 0x0b; + cmd[2] = 0x00; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + + if (range != 200 && range != 900) { + /* Construct fine limit command */ + start_left = (((full_range - range + 1) * 2047) / full_range); + start_right = 0xfff - start_left; + + cmd[2] = (Uint8)(start_left >> 4); + cmd[3] = (Uint8)(start_right >> 4); + cmd[4] = 0xff; + cmd[5] = (start_right & 0xe) << 4 | (start_left & 0xe); + cmd[6] = 0xff; + } + + ret = SDL_hid_write(device->dev, cmd, 7); + if (ret == -1) { + return false; + } + break; + } + case USB_DEVICE_ID_LOGITECH_WHEEL: + // no range setting for ffex/dfex + break; + default: + SDL_assert(0); + } + + return true; +} + +/* + *Ported* + Original functions by: + Simon Wood + Michal Malý + lg4ff_set_autocenter_default lg4ff_set_autocenter_ffex + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static bool HIDAPI_DriverLg4ff_SetAutoCenter(SDL_HIDAPI_Device *device, int magnitude) +{ + SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; + Uint8 cmd[7] = {0}; + int ret; + + if (magnitude < 0) { + magnitude = 0; + } + if (magnitude > 65535) { + magnitude = 65535; + } + + if (ctx->is_ffex) { + magnitude = magnitude * 90 / 65535; + + cmd[0] = 0xfe; + cmd[1] = 0x03; + cmd[2] = (Uint8)((Uint16)magnitude >> 14); + cmd[3] = (Uint8)((Uint16)magnitude >> 14); + cmd[4] = (Uint8)magnitude; + + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if(ret == -1){ + return false; + } + } else { + Uint32 expand_a; + Uint32 expand_b; + // first disable + cmd[0] = 0xf5; + + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if (ret == -1) { + return false; + } + + if (magnitude == 0) { + return true; + } + + // set strength + + if (magnitude <= 0xaaaa) { + expand_a = 0x0c * magnitude; + expand_b = 0x80 * magnitude; + } else { + expand_a = (0x0c * 0xaaaa) + 0x06 * (magnitude - 0xaaaa); + expand_b = (0x80 * 0xaaaa) + 0xff * (magnitude - 0xaaaa); + } + // TODO do not adjust for MOMO wheels, when support is added + expand_a = expand_a >> 1; + + SDL_zeroa(cmd); + cmd[0] = 0xfe; + cmd[1] = 0x0d; + cmd[2] = (Uint8)(expand_a / 0xaaaa); + cmd[3] = (Uint8)(expand_a / 0xaaaa); + cmd[4] = (Uint8)(expand_b / 0xaaaa); + + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if (ret == -1) { + return false; + } + + // enable + SDL_zeroa(cmd); + cmd[0] = 0x14; + + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if (ret == -1) { + return false; + } + } + return true; +} + +/* + ffex identification method by: + Simon Wood + Michal Malý + lg4ff_init + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static bool HIDAPI_DriverLg4ff_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverLg4ff_Context *ctx; + + ctx = (SDL_DriverLg4ff_Context *)SDL_malloc(sizeof(SDL_DriverLg4ff_Context)); + if (ctx == NULL) { + SDL_OutOfMemory(); + return false; + } + SDL_zerop(ctx); + + device->context = ctx; + device->joystick_type = SDL_JOYSTICK_TYPE_WHEEL; + + HIDAPI_SetDeviceName(device, HIDAPI_DriverLg4ff_GetDeviceName(device->product_id)); + + if (SDL_hid_set_nonblocking(device->dev, 1) != 0) { + return false; + } + + if (!HIDAPI_DriverLg4ff_SetAutoCenter(device, 0)) { + return false; + } + + if (device->product_id == USB_DEVICE_ID_LOGITECH_WHEEL && + (device->version >> 8) == 0x21 && + (device->version & 0xff) == 0x00) { + ctx->is_ffex = true; + } else { + ctx->is_ffex = false; + } + + ctx->range = 900; + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverLg4ff_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverLg4ff_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + + +static bool HIDAPI_DriverLg4ff_GetBit(const Uint8 *buf, int bit_num, size_t buf_len) +{ + int byte_offset = bit_num / 8; + int local_bit = bit_num % 8; + Uint8 mask = 1 << local_bit; + if ((size_t)byte_offset >= buf_len) { + SDL_assert(0); + } + return (buf[byte_offset] & mask) ? true : false; +} + +/* + *Ported* + Original functions by: + Michal Malý + lg4ff_adjust_dfp_x_axis + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static Uint16 lg4ff_adjust_dfp_x_axis(Uint16 value, Uint16 range) +{ + Uint16 max_range; + Sint32 new_value; + + if (range == 900) + return value; + else if (range == 200) + return value; + else if (range < 200) + max_range = 200; + else + max_range = 900; + + new_value = 8192 + ((value - 8192) * max_range / range); + if (new_value < 0) + return 0; + else if (new_value > 16383) + return 16383; + else + return (Uint16)new_value; +} + +static bool HIDAPI_DriverLg4ff_HandleState(SDL_HIDAPI_Device *device, + SDL_Joystick *joystick, + Uint8 *report_buf, + size_t report_size) +{ + SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; + Uint8 hat = 0; + Uint8 last_hat = 0; + int num_buttons = HIDAPI_DriverLg4ff_GetNumberOfButtons(device->product_id); + int bit_offset = 0; + Uint64 timestamp = SDL_GetTicksNS(); + + bool state_changed = false; + + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + hat = report_buf[0] & 0x0f; + last_hat = ctx->last_report_buf[0] & 0x0f; + break; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + hat = report_buf[3] >> 4; + last_hat = ctx->last_report_buf[3] >> 4; + break; + case USB_DEVICE_ID_LOGITECH_WHEEL: + hat = report_buf[2] & 0x0F; + last_hat = ctx->last_report_buf[2] & 0x0F; + break; + default: + SDL_assert(0); + } + + if (hat != last_hat) { + Uint8 sdl_hat = 0; + state_changed = true; + switch (hat) { + case 0: + sdl_hat = SDL_HAT_UP; + break; + case 1: + sdl_hat = SDL_HAT_RIGHTUP; + break; + case 2: + sdl_hat = SDL_HAT_RIGHT; + break; + case 3: + sdl_hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + sdl_hat = SDL_HAT_DOWN; + break; + case 5: + sdl_hat = SDL_HAT_LEFTDOWN; + break; + case 6: + sdl_hat = SDL_HAT_LEFT; + break; + case 7: + sdl_hat = SDL_HAT_LEFTUP; + break; + case 8: + sdl_hat = SDL_HAT_CENTERED; + break; + // do not assert out, in case hardware can report weird hat values + } + SDL_SendJoystickHat(timestamp, joystick, 0, sdl_hat); + } + + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + bit_offset = 4; + break; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + bit_offset = 14; + break; + case USB_DEVICE_ID_LOGITECH_WHEEL: + bit_offset = 0; + break; + default: + SDL_assert(0); + } + + if (device->product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL) { + // ref https://github.com/sonik-br/lgff_wheel_adapter/blob/d97f7823154818e1b3edff6d51498a122c302728/pico_lgff_wheel_adapter/reports.h#L265-L310 + // shifter_r is outside of the main button bit field for this particular wheel + num_buttons--; + + bool button_on = HIDAPI_DriverLg4ff_GetBit(report_buf, 80, report_size); + bool button_was_on = HIDAPI_DriverLg4ff_GetBit(ctx->last_report_buf, 80, report_size); + if (button_on != button_was_on) { + state_changed = true; + SDL_SendJoystickButton(timestamp, joystick, (Uint8)(SDL_GAMEPAD_BUTTON_SOUTH + num_buttons), button_on); + } + } + + for (int i = 0;i < num_buttons;i++) { + int bit_num = bit_offset + i; + bool button_on = HIDAPI_DriverLg4ff_GetBit(report_buf, bit_num, report_size); + bool button_was_on = HIDAPI_DriverLg4ff_GetBit(ctx->last_report_buf, bit_num, report_size); + if(button_on != button_was_on){ + state_changed = true; + SDL_SendJoystickButton(timestamp, joystick, (Uint8)(SDL_GAMEPAD_BUTTON_SOUTH + i), button_on); + } + } + + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL:{ + Uint16 x = *(Uint16 *)&report_buf[4]; + Uint16 last_x = *(Uint16 *)&ctx->last_report_buf[4]; + if (x != last_x) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, x - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[6] * 257 - 32768); + } + if (report_buf[7] != ctx->last_report_buf[7]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, report_buf[7] * 257 - 32768); + } + if (report_buf[8] != ctx->last_report_buf[8]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[8] * 257 - 32768); + } + break; + } + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL:{ + Uint16 x = report_buf[4] << 6; + Uint16 last_x = ctx->last_report_buf[4] << 6; + x = x | report_buf[3] >> 2; + last_x = last_x | ctx->last_report_buf[3] >> 2; + if (x != last_x) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, x * 4 - 32768); + } + if (report_buf[5] != ctx->last_report_buf[5]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[5] * 257 - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, report_buf[6] * 257 - 32768); + } + if (report_buf[7] != ctx->last_report_buf[7]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[7] * 257 - 32768); + } + break; + } + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:{ + Uint16 x = report_buf[4]; + Uint16 last_x = ctx->last_report_buf[4]; + x = x | (report_buf[5] & 0x3F) << 8; + last_x = last_x | (ctx->last_report_buf[5] & 0x3F) << 8; + if (x != last_x) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, x * 4 - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[6] * 257 - 32768); + } + if (report_buf[7] != ctx->last_report_buf[7]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[7] * 257 - 32768); + } + break; + } + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:{ + Uint16 x = report_buf[0]; + Uint16 last_x = ctx->last_report_buf[0]; + x = x | (report_buf[1] & 0x3F) << 8; + last_x = last_x | (ctx->last_report_buf[1] & 0x3F) << 8; + if (x != last_x) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, lg4ff_adjust_dfp_x_axis(x, ctx->range) * 4 - 32768); + } + if (report_buf[5] != ctx->last_report_buf[5]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[5] * 257 - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[6] * 257 - 32768); + } + break; + } + case USB_DEVICE_ID_LOGITECH_WHEEL:{ + if (report_buf[3] != ctx->last_report_buf[3]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, report_buf[3] * 257 - 32768); + } + if (report_buf[4] != ctx->last_report_buf[4]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[4] * 257 - 32768); + } + if (report_buf[5] != ctx->last_report_buf[5]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[5] * 257 - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, report_buf[7] * 257 - 32768); + } + break; + } + default: + SDL_assert(0); + } + + SDL_memcpy(ctx->last_report_buf, report_buf, report_size); + return state_changed; +} + +static bool HIDAPI_DriverLg4ff_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_Joystick *joystick = NULL; + int r; + Uint8 report_buf[32] = {0}; + size_t report_size = 0; + SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + if (joystick == NULL) { + return false; + } + } else { + return false; + } + + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + report_size = 12; + break; + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + report_size = 11; + break; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + report_size = 8; + break; + case USB_DEVICE_ID_LOGITECH_WHEEL: + report_size = 27; + break; + default: + SDL_assert(0); + } + + do { + r = SDL_hid_read(device->dev, report_buf, report_size); + if (r < 0) { + /* Failed to read from controller */ + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + return false; + } else if ((size_t)r == report_size) { + bool state_changed = HIDAPI_DriverLg4ff_HandleState(device, joystick, report_buf, report_size); + if(state_changed && !ctx->initialized) { + ctx->initialized = true; + HIDAPI_DriverLg4ff_SetRange(device, SDL_HIDAPI_DriverLg4ff_GetEnvInt("SDL_HIDAPI_LG4FF_RANGE", 40, 900, 900)); + HIDAPI_DriverLg4ff_SetAutoCenter(device, 0); + } + } + } while (r > 0); + + return true; +} + +static bool HIDAPI_DriverLg4ff_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_AssertJoysticksLocked(); + + // Initialize the joystick capabilities + joystick->nhats = 1; + joystick->nbuttons = HIDAPI_DriverLg4ff_GetNumberOfButtons(device->product_id); + switch(device->product_id){ + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_WHEEL: + joystick->naxes = 4; + break; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + joystick->naxes = 3; + break; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + joystick->naxes = 3; + break; + default: + SDL_assert(0); + } + + return true; +} + +static bool HIDAPI_DriverLg4ff_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverLg4ff_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverLg4ff_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + switch(device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + return SDL_JOYSTICK_CAP_MONO_LED; + default: + return 0; + } +} + +/* + Commands by: + Michal Malý + Simon Wood + lg4ff_led_set_brightness lg4ff_set_leds + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static bool HIDAPI_DriverLg4ff_SendLedCommand(SDL_HIDAPI_Device *device, Uint8 state) +{ + Uint8 cmd[7]; + Uint8 led_state = 0; + + switch (state) { + case 0: + led_state = 0; + break; + case 1: + led_state = 1; + break; + case 2: + led_state = 3; + break; + case 3: + led_state = 7; + break; + case 4: + led_state = 15; + break; + case 5: + led_state = 31; + break; + default: + SDL_assert(0); + } + + cmd[0] = 0xf8; + cmd[1] = 0x12; + cmd[2] = led_state; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + + return SDL_hid_write(device->dev, cmd, sizeof(cmd)) == sizeof(cmd); +} + +static bool HIDAPI_DriverLg4ff_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + int max_led = red; + + // only g27/g29, and g923 when supported is added + if (device->product_id != USB_DEVICE_ID_LOGITECH_G29_WHEEL && + device->product_id != USB_DEVICE_ID_LOGITECH_G27_WHEEL) { + return SDL_Unsupported(); + } + + if (green > max_led) { + max_led = green; + } + if (blue > max_led) { + max_led = blue; + } + + return HIDAPI_DriverLg4ff_SendLedCommand(device, (Uint8)((5 * max_led) / 255)); +} + +static bool HIDAPI_DriverLg4ff_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + // allow programs to send raw commands + return SDL_hid_write(device->dev, data, size) == size; +} + +static bool HIDAPI_DriverLg4ff_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + // On steam deck, sensors are enabled by default. Nothing to do here. + return SDL_Unsupported(); +} + +static void HIDAPI_DriverLg4ff_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + // remember to stop effects on haptics close, when implemented + HIDAPI_DriverLg4ff_SetJoystickLED(device, joystick, 0, 0, 0); +} + +static void HIDAPI_DriverLg4ff_FreeDevice(SDL_HIDAPI_Device *device) +{ + // device context is freed in SDL_hidapijoystick.c +} + + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLg4ff = { + SDL_HINT_JOYSTICK_HIDAPI_LG4FF, + true, + HIDAPI_DriverLg4ff_RegisterHints, + HIDAPI_DriverLg4ff_UnregisterHints, + HIDAPI_DriverLg4ff_IsEnabled, + HIDAPI_DriverLg4ff_IsSupportedDevice, + HIDAPI_DriverLg4ff_InitDevice, + HIDAPI_DriverLg4ff_GetDevicePlayerIndex, + HIDAPI_DriverLg4ff_SetDevicePlayerIndex, + HIDAPI_DriverLg4ff_UpdateDevice, + HIDAPI_DriverLg4ff_OpenJoystick, + HIDAPI_DriverLg4ff_RumbleJoystick, + HIDAPI_DriverLg4ff_RumbleJoystickTriggers, + HIDAPI_DriverLg4ff_GetJoystickCapabilities, + HIDAPI_DriverLg4ff_SetJoystickLED, + HIDAPI_DriverLg4ff_SendJoystickEffect, + HIDAPI_DriverLg4ff_SetSensorsEnabled, + HIDAPI_DriverLg4ff_CloseJoystick, + HIDAPI_DriverLg4ff_FreeDevice, +}; + + +#endif /* SDL_JOYSTICK_HIDAPI_LG4FF */ + +#endif /* SDL_JOYSTICK_HIDAPI */ diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_luna.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_luna.c index 7c889a6..b560f78 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_luna.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_luna.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_nintendo.h b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_nintendo.h index 0a5836f..dc7622f 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_nintendo.h +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_nintendo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps3.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps3.c index 6c82647..ede49cb 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps3.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps3.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -32,8 +32,6 @@ // Define this if you want to log all packets from the controller // #define DEBUG_PS3_PROTOCOL -#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) - typedef enum { k_EPS3ReportIdState = 1, diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps4.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps4.c index d4332df..3f57a6d 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps4.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -44,12 +44,6 @@ #define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500 -#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) -#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \ - (((Uint32)(B)) << 8) | \ - (((Uint32)(C)) << 16) | \ - (((Uint32)(D)) << 24)) - enum { SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD = 11 diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps5.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps5.c index 4249578..c790388 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps5.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps5.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -43,12 +43,6 @@ #define ACCEL_RES_PER_G 8192.0f #define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500 -#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) -#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \ - (((Uint32)(B)) << 8) | \ - (((Uint32)(C)) << 16) | \ - (((Uint32)(D)) << 24)) - enum { SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD = 11, @@ -230,7 +224,7 @@ typedef struct { SDL_HIDAPI_Device *device; SDL_Joystick *joystick; - bool is_nacon_dongle; + bool is_dongle; bool use_alternate_report; bool sensors_supported; bool lightbar_supported; @@ -251,6 +245,7 @@ typedef struct Uint64 last_packet; int player_index; bool player_lights; + bool enhanced_rumble; Uint8 rumble_left; Uint8 rumble_right; bool color_set; @@ -432,6 +427,14 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) } } + if (device->vendor_id == USB_VENDOR_SONY) { + if (device->product_id == USB_PRODUCT_SONY_DS5_EDGE || + ctx->firmware_version == 0 || // Assume that it's updated firmware over Bluetooth + ctx->firmware_version >= 0x0224) { + ctx->enhanced_rumble = true; + } + } + // Get the device capabilities if (device->vendor_id == USB_VENDOR_SONY) { ctx->sensors_supported = true; @@ -506,8 +509,10 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) ctx->touchpad_supported = true; ctx->use_alternate_report = true; } else if (device->vendor_id == USB_VENDOR_RAZER && - device->product_id == USB_PRODUCT_RAZER_KITSUNE) { - // The Razer Kitsune doesn't respond to the detection protocol, but has a touchpad + (device->product_id == USB_PRODUCT_RAZER_KITSUNE || + device->product_id == USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRED || + device->product_id == USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRELESS)) { + // The Razer Kitsune and Raiju don't respond to the detection protocol, but have a touchpad joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK; ctx->touchpad_supported = true; ctx->use_alternate_report = true; @@ -515,9 +520,13 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) } ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported || ctx->playerled_supported); - if (device->vendor_id == USB_VENDOR_NACON_ALT && - device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS) { - ctx->is_nacon_dongle = true; + if ((device->vendor_id == USB_VENDOR_NACON_ALT && + device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS) || + (device->vendor_id == USB_VENDOR_RAZER && + (device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS || + device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRELESS || + device->product_id == USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRELESS))) { + ctx->is_dongle = true; } device->joystick_type = joystick_type; @@ -531,7 +540,7 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) } HIDAPI_SetDeviceSerial(device, serial); - if (ctx->is_nacon_dongle) { + if (ctx->is_dongle) { // We don't know if this is connected yet, wait for reports return true; } @@ -684,17 +693,17 @@ static bool HIDAPI_DriverPS5_UpdateEffects(SDL_DriverPS5_Context *ctx, int effec if (ctx->vibration_supported) { if (ctx->rumble_left || ctx->rumble_right) { - if (ctx->firmware_version < 0x0224) { + if (ctx->enhanced_rumble) { + effects.ucEnableBits3 |= 0x04; // Enable improved rumble emulation on 2.24 firmware and newer + + effects.ucRumbleLeft = ctx->rumble_left; + effects.ucRumbleRight = ctx->rumble_right; + } else { effects.ucEnableBits1 |= 0x01; // Enable rumble emulation // Shift to reduce effective rumble strength to match Xbox controllers effects.ucRumbleLeft = ctx->rumble_left >> 1; effects.ucRumbleRight = ctx->rumble_right >> 1; - } else { - effects.ucEnableBits3 |= 0x04; // Enable improved rumble emulation on 2.24 firmware and newer - - effects.ucRumbleLeft = ctx->rumble_left; - effects.ucRumbleRight = ctx->rumble_right; } effects.ucEnableBits1 |= 0x02; // Disable audio haptics } else { @@ -814,7 +823,7 @@ 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 update_rate = 1000.0f; @@ -955,6 +964,10 @@ static bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystic joystick->nhats = 1; joystick->firmware_version = ctx->firmware_version; + if (ctx->is_dongle) { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; + } + SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, SDL_PS5EnhancedReportsChanged, ctx); SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, @@ -1038,6 +1051,9 @@ static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *c if (!ctx->enhanced_mode) { if (application_usage) { HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx); + + // Wait briefly before sending additional effects + SDL_Delay(10); } if (!ctx->enhanced_mode) { @@ -1050,10 +1066,11 @@ static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *c if (ctx->device->is_bluetooth) { data[0] = k_EPS5ReportIdBluetoothEffects; - data[1] = 0x02; // Magic value + data[1] = 0x00; // Tag and sequence + data[2] = 0x10; // Magic value report_size = 78; - offset = 2; + offset = 3; } else { data[0] = k_EPS5ReportIdUsbEffects; @@ -1452,15 +1469,16 @@ static bool HIDAPI_DriverPS5_IsPacketValid(SDL_DriverPS5_Context *ctx, Uint8 *da { switch (data[0]) { case k_EPS5ReportIdState: - if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS5StatePacketAlt_t))) { + if (ctx->is_dongle && size >= (1 + sizeof(PS5StatePacketAlt_t))) { // The report timestamp doesn't change when the controller isn't connected PS5StatePacketAlt_t *packet = (PS5StatePacketAlt_t *)&data[1]; if (SDL_memcmp(packet->rgucPacketSequence, ctx->last_state.state.rgucPacketSequence, sizeof(packet->rgucPacketSequence)) == 0) { return false; } - if (ctx->last_state.alt_state.rgucAccelX[0] == 0 && ctx->last_state.alt_state.rgucAccelX[1] == 0 && - ctx->last_state.alt_state.rgucAccelY[0] == 0 && ctx->last_state.alt_state.rgucAccelY[1] == 0 && - ctx->last_state.alt_state.rgucAccelZ[0] == 0 && ctx->last_state.alt_state.rgucAccelZ[1] == 0) { + if (ctx->last_state.state.rgucPacketSequence[0] == 0 && + ctx->last_state.state.rgucPacketSequence[1] == 0 && + ctx->last_state.state.rgucPacketSequence[2] == 0 && + ctx->last_state.state.rgucPacketSequence[3] == 0) { // We don't have any state to compare yet, go ahead and copy it SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS5StatePacketAlt_t)); return false; @@ -1559,7 +1577,7 @@ static bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device) } } - if (ctx->is_nacon_dongle) { + if (ctx->is_dongle) { if (packet_count == 0) { if (device->num_joysticks > 0) { // Check to see if it looks like the device disconnected diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.c index 5fd93dc..ef3fede 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.h b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.h index ede061e..bcd4a79 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.h +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -28,7 +28,7 @@ #ifdef SDL_THREAD_SAFETY_ANALYSIS extern SDL_Mutex *SDL_HIDAPI_rumble_lock; #endif -bool SDL_HIDAPI_LockRumble(void) SDL_TRY_ACQUIRE(0, SDL_HIDAPI_rumble_lock); +bool SDL_HIDAPI_LockRumble(void) SDL_TRY_ACQUIRE(true, SDL_HIDAPI_rumble_lock); bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size); int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size) SDL_RELEASE(SDL_HIDAPI_rumble_lock); typedef void (*SDL_HIDAPI_RumbleSentCallback)(void *userdata); diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_shield.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_shield.c index 10dcea3..2b3df21 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_shield.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_shield.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -29,7 +29,9 @@ #ifdef SDL_JOYSTICK_HIDAPI_SHIELD // Define this if you want to log all packets from the controller -// #define DEBUG_SHIELD_PROTOCOL +#if 0 +#define DEBUG_SHIELD_PROTOCOL +#endif #define CMD_BATTERY_STATE 0x07 #define CMD_RUMBLE 0x39 @@ -138,6 +140,11 @@ static void HIDAPI_DriverShield_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, static bool HIDAPI_DriverShield_SendCommand(SDL_HIDAPI_Device *device, Uint8 cmd, const void *data, int size) { +#ifdef SDL_PLATFORM_MACOS + // We hang for several seconds when trying to send output reports on macOS + return SDL_Unsupported(); + +#else SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context; ShieldCommandReport_t cmd_pkt; @@ -166,6 +173,8 @@ static bool HIDAPI_DriverShield_SendCommand(SDL_HIDAPI_Device *device, Uint8 cmd } return true; + +#endif // MACOS } static bool HIDAPI_DriverShield_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) @@ -289,6 +298,7 @@ static bool HIDAPI_DriverShield_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *dev static void HIDAPI_DriverShield_HandleStatePacketV103(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size) { Uint64 timestamp = SDL_GetTicksNS(); + Sint16 axis; if (ctx->last_state[3] != data[3]) { Uint8 hat; @@ -346,14 +356,20 @@ static void HIDAPI_DriverShield_HandleStatePacketV103(SDL_Joystick *joystick, SD SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x80) != 0)); } - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_Swap16LE(*(Sint16 *)&data[4]) - 0x8000); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_Swap16LE(*(Sint16 *)&data[6]) - 0x8000); + axis = LOAD16(data[4], data[5]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = LOAD16(data[6], data[7]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_Swap16LE(*(Sint16 *)&data[8]) - 0x8000); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_Swap16LE(*(Sint16 *)&data[10]) - 0x8000); + axis = LOAD16(data[8], data[9]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = LOAD16(data[10], data[11]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[12]) - 0x8000); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[14]) - 0x8000); + axis = LOAD16(data[12], data[13]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = LOAD16(data[14], data[15]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); } @@ -379,6 +395,7 @@ static void HIDAPI_DriverShield_HandleTouchPacketV103(SDL_Joystick *joystick, SD static void HIDAPI_DriverShield_HandleStatePacketV104(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size) { Uint64 timestamp = SDL_GetTicksNS(); + Sint16 axis; if (size < 23) { return; @@ -434,14 +451,20 @@ static void HIDAPI_DriverShield_HandleStatePacketV104(SDL_Joystick *joystick, SD SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[4] & 0x01) != 0)); } - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_Swap16LE(*(Sint16 *)&data[9]) - 0x8000); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_Swap16LE(*(Sint16 *)&data[11]) - 0x8000); + axis = LOAD16(data[9], data[10]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = LOAD16(data[11], data[12]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_Swap16LE(*(Sint16 *)&data[13]) - 0x8000); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_Swap16LE(*(Sint16 *)&data[15]) - 0x8000); + axis = LOAD16(data[13], data[14]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = LOAD16(data[15], data[16]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[19]) - 0x8000); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[21]) - 0x8000); + axis = LOAD16(data[19], data[20]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = LOAD16(data[21], data[22]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); if (ctx->last_state[17] != data[17]) { //SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_SHARE, ((data[17] & 0x01) != 0)); @@ -482,13 +505,13 @@ static bool HIDAPI_DriverShield_UpdateDevice(SDL_HIDAPI_Device *device) #ifdef DEBUG_SHIELD_PROTOCOL HIDAPI_DumpPacket("NVIDIA SHIELD packet: size = %d", data, size); #endif + if (!joystick) { + continue; + } // Byte 0 is HID report ID switch (data[0]) { case k_ShieldReportIdControllerState: - if (!joystick) { - break; - } if (size == 16) { HIDAPI_DriverShield_HandleStatePacketV103(joystick, ctx, data, size); } else { @@ -496,9 +519,6 @@ static bool HIDAPI_DriverShield_UpdateDevice(SDL_HIDAPI_Device *device) } break; case k_ShieldReportIdControllerTouch: - if (!joystick) { - break; - } HIDAPI_DriverShield_HandleTouchPacketV103(joystick, ctx, data, size); break; case k_ShieldReportIdCommandResponse: diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_sinput.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_sinput.c new file mode 100644 index 0000000..f84dfaa --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_sinput.c @@ -0,0 +1,1131 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Mitchell Cairns + + 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_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../SDL_sysjoystick.h" + +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" +#include "SDL_hidapi_sinput.h" + +#ifdef SDL_JOYSTICK_HIDAPI_SINPUT + +/*****************************************************************************************************/ +// This protocol is documented at: +// https://docs.handheldlegend.com/s/sinput +/*****************************************************************************************************/ + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_SINPUT_PROTOCOL +#endif + +#if 0 +#define DEBUG_SINPUT_INIT +#endif + +#define SINPUT_DEVICE_REPORT_SIZE 64 // Size of input reports (And CMD Input reports) +#define SINPUT_DEVICE_REPORT_COMMAND_SIZE 48 // Size of command OUTPUT reports + +#define SINPUT_DEVICE_REPORT_ID_JOYSTICK_INPUT 0x01 +#define SINPUT_DEVICE_REPORT_ID_INPUT_CMDDAT 0x02 +#define SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT 0x03 + +#define SINPUT_DEVICE_COMMAND_HAPTIC 0x01 +#define SINPUT_DEVICE_COMMAND_FEATURES 0x02 +#define SINPUT_DEVICE_COMMAND_PLAYERLED 0x03 +#define SINPUT_DEVICE_COMMAND_JOYSTICKRGB 0x04 + +#define SINPUT_HAPTIC_TYPE_PRECISE 0x01 +#define SINPUT_HAPTIC_TYPE_ERMSIMULATION 0x02 + +#define SINPUT_DEFAULT_GYRO_SENS 2000 +#define SINPUT_DEFAULT_ACCEL_SENS 8 + +#define SINPUT_REPORT_IDX_BUTTONS_0 3 +#define SINPUT_REPORT_IDX_BUTTONS_1 4 +#define SINPUT_REPORT_IDX_BUTTONS_2 5 +#define SINPUT_REPORT_IDX_BUTTONS_3 6 +#define SINPUT_REPORT_IDX_LEFT_X 7 +#define SINPUT_REPORT_IDX_LEFT_Y 9 +#define SINPUT_REPORT_IDX_RIGHT_X 11 +#define SINPUT_REPORT_IDX_RIGHT_Y 13 +#define SINPUT_REPORT_IDX_LEFT_TRIGGER 15 +#define SINPUT_REPORT_IDX_RIGHT_TRIGGER 17 +#define SINPUT_REPORT_IDX_IMU_TIMESTAMP 19 +#define SINPUT_REPORT_IDX_IMU_ACCEL_X 23 +#define SINPUT_REPORT_IDX_IMU_ACCEL_Y 25 +#define SINPUT_REPORT_IDX_IMU_ACCEL_Z 27 +#define SINPUT_REPORT_IDX_IMU_GYRO_X 29 +#define SINPUT_REPORT_IDX_IMU_GYRO_Y 31 +#define SINPUT_REPORT_IDX_IMU_GYRO_Z 33 +#define SINPUT_REPORT_IDX_TOUCH1_X 35 +#define SINPUT_REPORT_IDX_TOUCH1_Y 37 +#define SINPUT_REPORT_IDX_TOUCH1_P 39 +#define SINPUT_REPORT_IDX_TOUCH2_X 41 +#define SINPUT_REPORT_IDX_TOUCH2_Y 43 +#define SINPUT_REPORT_IDX_TOUCH2_P 45 + +#define SINPUT_BUTTON_IDX_EAST 0 +#define SINPUT_BUTTON_IDX_SOUTH 1 +#define SINPUT_BUTTON_IDX_NORTH 2 +#define SINPUT_BUTTON_IDX_WEST 3 +#define SINPUT_BUTTON_IDX_DPAD_UP 4 +#define SINPUT_BUTTON_IDX_DPAD_DOWN 5 +#define SINPUT_BUTTON_IDX_DPAD_LEFT 6 +#define SINPUT_BUTTON_IDX_DPAD_RIGHT 7 +#define SINPUT_BUTTON_IDX_LEFT_STICK 8 +#define SINPUT_BUTTON_IDX_RIGHT_STICK 9 +#define SINPUT_BUTTON_IDX_LEFT_BUMPER 10 +#define SINPUT_BUTTON_IDX_RIGHT_BUMPER 11 +#define SINPUT_BUTTON_IDX_LEFT_TRIGGER 12 +#define SINPUT_BUTTON_IDX_RIGHT_TRIGGER 13 +#define SINPUT_BUTTON_IDX_LEFT_PADDLE1 14 +#define SINPUT_BUTTON_IDX_RIGHT_PADDLE1 15 +#define SINPUT_BUTTON_IDX_START 16 +#define SINPUT_BUTTON_IDX_BACK 17 +#define SINPUT_BUTTON_IDX_GUIDE 18 +#define SINPUT_BUTTON_IDX_CAPTURE 19 +#define SINPUT_BUTTON_IDX_LEFT_PADDLE2 20 +#define SINPUT_BUTTON_IDX_RIGHT_PADDLE2 21 +#define SINPUT_BUTTON_IDX_TOUCHPAD1 22 +#define SINPUT_BUTTON_IDX_TOUCHPAD2 23 +#define SINPUT_BUTTON_IDX_POWER 24 +#define SINPUT_BUTTON_IDX_MISC4 25 +#define SINPUT_BUTTON_IDX_MISC5 26 +#define SINPUT_BUTTON_IDX_MISC6 27 +#define SINPUT_BUTTON_IDX_MISC7 28 +#define SINPUT_BUTTON_IDX_MISC8 29 +#define SINPUT_BUTTON_IDX_MISC9 30 +#define SINPUT_BUTTON_IDX_MISC10 31 + +#define SINPUT_BUTTONMASK_EAST 0x01 +#define SINPUT_BUTTONMASK_SOUTH 0x02 +#define SINPUT_BUTTONMASK_NORTH 0x04 +#define SINPUT_BUTTONMASK_WEST 0x08 +#define SINPUT_BUTTONMASK_DPAD_UP 0x10 +#define SINPUT_BUTTONMASK_DPAD_DOWN 0x20 +#define SINPUT_BUTTONMASK_DPAD_LEFT 0x40 +#define SINPUT_BUTTONMASK_DPAD_RIGHT 0x80 +#define SINPUT_BUTTONMASK_LEFT_STICK 0x01 +#define SINPUT_BUTTONMASK_RIGHT_STICK 0x02 +#define SINPUT_BUTTONMASK_LEFT_BUMPER 0x04 +#define SINPUT_BUTTONMASK_RIGHT_BUMPER 0x08 +#define SINPUT_BUTTONMASK_LEFT_TRIGGER 0x10 +#define SINPUT_BUTTONMASK_RIGHT_TRIGGER 0x20 +#define SINPUT_BUTTONMASK_LEFT_PADDLE1 0x40 +#define SINPUT_BUTTONMASK_RIGHT_PADDLE1 0x80 +#define SINPUT_BUTTONMASK_START 0x01 +#define SINPUT_BUTTONMASK_BACK 0x02 +#define SINPUT_BUTTONMASK_GUIDE 0x04 +#define SINPUT_BUTTONMASK_CAPTURE 0x08 +#define SINPUT_BUTTONMASK_LEFT_PADDLE2 0x10 +#define SINPUT_BUTTONMASK_RIGHT_PADDLE2 0x20 +#define SINPUT_BUTTONMASK_TOUCHPAD1 0x40 +#define SINPUT_BUTTONMASK_TOUCHPAD2 0x80 +#define SINPUT_BUTTONMASK_POWER 0x01 +#define SINPUT_BUTTONMASK_MISC4 0x02 +#define SINPUT_BUTTONMASK_MISC5 0x04 +#define SINPUT_BUTTONMASK_MISC6 0x08 +#define SINPUT_BUTTONMASK_MISC7 0x10 +#define SINPUT_BUTTONMASK_MISC8 0x20 +#define SINPUT_BUTTONMASK_MISC9 0x40 +#define SINPUT_BUTTONMASK_MISC10 0x80 + +#define SINPUT_REPORT_IDX_COMMAND_RESPONSE_ID 1 +#define SINPUT_REPORT_IDX_COMMAND_RESPONSE_BULK 2 + +#define SINPUT_REPORT_IDX_PLUG_STATUS 1 +#define SINPUT_REPORT_IDX_CHARGE_LEVEL 2 + +#define SINPUT_MAX_ALLOWED_TOUCHPADS 2 + +#ifndef EXTRACTSINT16 +#define EXTRACTSINT16(data, idx) ((Sint16)((data)[(idx)] | ((data)[(idx) + 1] << 8))) +#endif + +#ifndef EXTRACTUINT16 +#define EXTRACTUINT16(data, idx) ((Uint16)((data)[(idx)] | ((data)[(idx) + 1] << 8))) +#endif + +#ifndef EXTRACTUINT32 +#define EXTRACTUINT32(data, idx) ((Uint32)((data)[(idx)] | ((data)[(idx) + 1] << 8) | ((data)[(idx) + 2] << 16) | ((data)[(idx) + 3] << 24))) +#endif + +typedef struct +{ + uint8_t type; + + union { + // Frequency Amplitude pairs + struct { + struct { + uint16_t frequency_1; + uint16_t amplitude_1; + uint16_t frequency_2; + uint16_t amplitude_2; + } left; + + struct { + uint16_t frequency_1; + uint16_t amplitude_1; + uint16_t frequency_2; + uint16_t amplitude_2; + } right; + + } type_1; + + // Basic ERM simulation model + struct { + struct { + uint8_t amplitude; + bool brake; + } left; + + struct { + uint8_t amplitude; + bool brake; + } right; + + } type_2; + }; +} SINPUT_HAPTIC_S; + +typedef struct +{ + SDL_HIDAPI_Device *device; + Uint16 protocol_version; + Uint16 usb_device_version; + bool sensors_enabled; + + Uint8 player_idx; + + bool player_leds_supported; + bool joystick_rgb_supported; + bool rumble_supported; + bool accelerometer_supported; + bool gyroscope_supported; + bool left_analog_stick_supported; + bool right_analog_stick_supported; + bool left_analog_trigger_supported; + bool right_analog_trigger_supported; + bool dpad_supported; + bool touchpad_supported; + bool is_handheld; + + Uint8 touchpad_count; // 2 touchpads maximum + Uint8 touchpad_finger_count; // 2 fingers for one touchpad, or 1 per touchpad (2 max) + + Uint16 polling_rate_us; + Uint8 sub_product; // Subtype of the device, 0 in most cases + + Uint16 accelRange; // Example would be 2,4,8,16 +/- (g-force) + Uint16 gyroRange; // Example would be 1000,2000,4000 +/- (degrees per second) + + float accelScale; // Scale factor for accelerometer values + float gyroScale; // Scale factor for gyroscope values + Uint8 last_state[USB_PACKET_LENGTH]; + + Uint8 axes_count; + Uint8 buttons_count; + Uint8 usage_masks[4]; + + Uint32 last_imu_timestamp_us; + + Uint64 imu_timestamp_ns; // Nanoseconds. We accumulate with received deltas +} SDL_DriverSInput_Context; + +// Converts raw int16_t gyro scale setting +static inline float CalculateGyroScale(uint16_t dps_range) +{ + return SDL_PI_F / 180.0f / (32768.0f / (float)dps_range); +} + +// Converts raw int16_t accel scale setting +static inline float CalculateAccelScale(uint16_t g_range) +{ + return SDL_STANDARD_GRAVITY / (32768.0f / (float)g_range); +} + +// This function uses base-n encoding to encode features into the version GUID bytes +// that properly represents the supported device features +// This also sets the driver context button mask correctly based on the features +static void DeviceDynamicEncodingSetup(SDL_HIDAPI_Device *device) +{ + SDL_DriverSInput_Context *ctx = device->context; + + // A new button mask is generated to provide + // SDL with a mapping string that is sane. In case of + // an unconventional gamepad setup, the closest sane + // mapping is provided to the driver. + Uint8 mask[4] = { 0 }; + + // For all gamepads, there is a minimum SInput expectation + // to have dpad, abxy, and start buttons + + // ABXY + D-Pad + mask[0] = 0xFF; + ctx->dpad_supported = true; + + // Start button + mask[2] |= SINPUT_BUTTONMASK_START; + + // Bumpers + bool left_bumper = (ctx->usage_masks[1] & SINPUT_BUTTONMASK_LEFT_BUMPER) != 0; + bool right_bumper = (ctx->usage_masks[1] & SINPUT_BUTTONMASK_RIGHT_BUMPER) != 0; + + int bumperStyle = SINPUT_BUMPERSTYLE_NONE; + if (left_bumper && right_bumper) { + bumperStyle = SINPUT_BUMPERSTYLE_TWO; + mask[1] |= (SINPUT_BUTTONMASK_LEFT_BUMPER | SINPUT_BUTTONMASK_RIGHT_BUMPER); + } else if (left_bumper || right_bumper) { + bumperStyle = SINPUT_BUMPERSTYLE_ONE; + + if (left_bumper) { + mask[1] |= SINPUT_BUTTONMASK_LEFT_BUMPER; + } else if (right_bumper) { + mask[1] |= SINPUT_BUTTONMASK_RIGHT_BUMPER; + } + } + + // Trigger bits live in mask[1] + bool digital_triggers = (ctx->usage_masks[1] & (SINPUT_BUTTONMASK_LEFT_TRIGGER | SINPUT_BUTTONMASK_RIGHT_TRIGGER)) != 0; + + bool analog_triggers = ctx->left_analog_trigger_supported || ctx->right_analog_trigger_supported; + + // Touchpads + bool t1 = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_TOUCHPAD1) != 0; + bool t2 = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_TOUCHPAD2) != 0; + + int analogStyle = SINPUT_ANALOGSTYLE_NONE; + if (ctx->left_analog_stick_supported && ctx->right_analog_stick_supported) { + analogStyle = SINPUT_ANALOGSTYLE_LEFTRIGHT; + mask[1] |= (SINPUT_BUTTONMASK_LEFT_STICK | SINPUT_BUTTONMASK_RIGHT_STICK); + } else if (ctx->left_analog_stick_supported) { + analogStyle = SINPUT_ANALOGSTYLE_LEFTONLY; + mask[1] |= SINPUT_BUTTONMASK_LEFT_STICK; + } else if (ctx->right_analog_stick_supported) { + analogStyle = SINPUT_ANALOGSTYLE_RIGHTONLY; + mask[1] |= SINPUT_BUTTONMASK_RIGHT_STICK; + } + + int triggerStyle = SINPUT_TRIGGERSTYLE_NONE; + + if (analog_triggers && digital_triggers) { + // When we have both analog triggers and digital triggers + // this is interpreted as having dual-stage triggers + triggerStyle = SINPUT_TRIGGERSTYLE_DUALSTAGE; + mask[1] |= (SINPUT_BUTTONMASK_LEFT_TRIGGER | SINPUT_BUTTONMASK_RIGHT_TRIGGER); + } else if (analog_triggers) { + triggerStyle = SINPUT_TRIGGERSTYLE_ANALOG; + } else if (digital_triggers) { + triggerStyle = SINPUT_TRIGGERSTYLE_DIGITAL; + mask[1] |= (SINPUT_BUTTONMASK_LEFT_TRIGGER | SINPUT_BUTTONMASK_RIGHT_TRIGGER); + } + + // Paddle bits may touch mask[1] and mask[2] + bool pg1 = (ctx->usage_masks[1] & (SINPUT_BUTTONMASK_LEFT_PADDLE1 | SINPUT_BUTTONMASK_RIGHT_PADDLE1)) != 0; + bool pg2 = (ctx->usage_masks[2] & (SINPUT_BUTTONMASK_LEFT_PADDLE2 | SINPUT_BUTTONMASK_RIGHT_PADDLE2)) != 0; + + int paddleStyle = SINPUT_PADDLESTYLE_NONE; + if (pg1 && pg2) { + paddleStyle = SINPUT_PADDLESTYLE_FOUR; + mask[1] |= (SINPUT_BUTTONMASK_LEFT_PADDLE1 | SINPUT_BUTTONMASK_RIGHT_PADDLE1); + mask[2] |= (SINPUT_BUTTONMASK_LEFT_PADDLE2 | SINPUT_BUTTONMASK_RIGHT_PADDLE2); + } else if (pg1) { + paddleStyle = SINPUT_PADDLESTYLE_TWO; + mask[1] |= (SINPUT_BUTTONMASK_LEFT_PADDLE1 | SINPUT_BUTTONMASK_RIGHT_PADDLE1); + } + + + // Meta Buttons (Back, Guide, Share) + bool back = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_BACK) != 0; + bool guide = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_GUIDE) != 0; + bool share = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_CAPTURE) != 0; + + int metaStyle = SINPUT_METASTYLE_NONE; + if (share) { + metaStyle = SINPUT_METASTYLE_BACKGUIDESHARE; + mask[2] |= (SINPUT_BUTTONMASK_BACK | SINPUT_BUTTONMASK_GUIDE | SINPUT_BUTTONMASK_CAPTURE); + } else if (guide) { + metaStyle = SINPUT_METASTYLE_BACKGUIDE; + mask[2] |= (SINPUT_BUTTONMASK_BACK | SINPUT_BUTTONMASK_GUIDE); + } else if (back) { + metaStyle = SINPUT_METASTYLE_BACK; + mask[2] |= (SINPUT_BUTTONMASK_BACK); + } + + int touchStyle = SINPUT_TOUCHSTYLE_NONE; + if (t1 && t2) { + touchStyle = SINPUT_TOUCHSTYLE_DOUBLE; + mask[2] |= (SINPUT_BUTTONMASK_TOUCHPAD1 | SINPUT_BUTTONMASK_TOUCHPAD2); + } else if (t1) { + touchStyle = SINPUT_TOUCHSTYLE_SINGLE; + mask[2] |= SINPUT_BUTTONMASK_TOUCHPAD1; + } + + // Misc Buttons + int miscStyle = SINPUT_MISCSTYLE_NONE; + Uint8 extra_misc = ctx->usage_masks[3] & 0x0F; + switch (extra_misc) { + case 0x0F: + miscStyle = SINPUT_MISCSTYLE_4; + mask[3] = 0x0F; + break; + case 0x07: + miscStyle = SINPUT_MISCSTYLE_3; + mask[3] = 0x07; + break; + case 0x03: + miscStyle = SINPUT_MISCSTYLE_2; + mask[3] = 0x03; + break; + case 0x01: + miscStyle = SINPUT_MISCSTYLE_1; + mask[3] = 0x01; + break; + default: + miscStyle = SINPUT_MISCSTYLE_NONE; + mask[3] = 0x00; + break; + } + + int version = analogStyle; + version = (version * (int)SINPUT_BUMPERSTYLE_MAX) + bumperStyle; + version = (version * (int)SINPUT_TRIGGERSTYLE_MAX) + triggerStyle; + version = (version * (int)SINPUT_PADDLESTYLE_MAX) + paddleStyle; + version = (version * (int)SINPUT_METASTYLE_MAX) + metaStyle; + version = (version * (int)SINPUT_TOUCHSTYLE_MAX) + touchStyle; + version = (version * (int)SINPUT_MISCSTYLE_MAX) + miscStyle; + + // Overwrite our button usage masks + // with our sanitized masks + ctx->usage_masks[0] = mask[0]; + ctx->usage_masks[1] = mask[1]; + ctx->usage_masks[2] = mask[2]; + ctx->usage_masks[3] = mask[3]; + + version = SDL_clamp(version, 0, UINT16_MAX); + + // Overwrite 'Version' field of the GUID data + device->guid.data[12] = (Uint8)(version & 0xFF); + device->guid.data[13] = (Uint8)(version >> 8); +} + + +static void ProcessSDLFeaturesResponse(SDL_HIDAPI_Device *device, Uint8 *data) +{ + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + + // Obtain protocol version + ctx->protocol_version = EXTRACTUINT16(data, 0); + + // Bitfields are not portable, so we unpack them into a struct value + ctx->rumble_supported = (data[2] & 0x01) != 0; + ctx->player_leds_supported = (data[2] & 0x02) != 0; + ctx->accelerometer_supported = (data[2] & 0x04) != 0; + ctx->gyroscope_supported = (data[2] & 0x08) != 0; + + ctx->left_analog_stick_supported = (data[2] & 0x10) != 0; + ctx->right_analog_stick_supported = (data[2] & 0x20) != 0; + ctx->left_analog_trigger_supported = (data[2] & 0x40) != 0; + ctx->right_analog_trigger_supported = (data[2] & 0x80) != 0; + + ctx->touchpad_supported = (data[3] & 0x01) != 0; + ctx->joystick_rgb_supported = (data[3] & 0x02) != 0; + + ctx->is_handheld = (data[3] & 0x04) != 0; + + // The gamepad type represents a style of gamepad that most closely + // resembles the gamepad in question (Button style, button layout) + SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN; + type = (SDL_GamepadType)SDL_clamp(data[4], SDL_GAMEPAD_TYPE_UNKNOWN, SDL_GAMEPAD_TYPE_COUNT); + device->type = type; + + // The 3 MSB represent a button layout style SDL_GamepadFaceStyle + // The 5 LSB represent a device sub-type + device->guid.data[15] = data[5]; + + ctx->sub_product = (data[5] & 0x1F); + +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("SInput Face Style: %d", (data[5] & 0xE0) >> 5); + SDL_Log("SInput Sub-product: %d", (data[5] & 0x1F)); +#endif + + ctx->polling_rate_us = EXTRACTUINT16(data, 6); + +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("SInput polling interval (microseconds): %d", ctx->polling_rate_us); +#endif + + ctx->accelRange = EXTRACTUINT16(data, 8); + ctx->gyroRange = EXTRACTUINT16(data, 10); + + ctx->usage_masks[0] = data[12]; + ctx->usage_masks[1] = data[13]; + ctx->usage_masks[2] = data[14]; + ctx->usage_masks[3] = data[15]; + + // Get and validate touchpad parameters + ctx->touchpad_count = data[16]; + ctx->touchpad_finger_count = data[17]; + + // Get device Serial - MAC address + char serial[18]; + (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", + data[18], data[19], data[20], data[21], data[22], data[23]); + +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("Serial num: %s", serial); +#endif + HIDAPI_SetDeviceSerial(device, serial); + +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("Accelerometer Range: %d", ctx->accelRange); +#endif + +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("Gyro Range: %d", ctx->gyroRange); +#endif + + ctx->accelScale = CalculateAccelScale(ctx->accelRange); + ctx->gyroScale = CalculateGyroScale(ctx->gyroRange); + + Uint8 axes = 0; + if (ctx->left_analog_stick_supported) { + axes += 2; + } + + if (ctx->right_analog_stick_supported) { + axes += 2; + } + + if (ctx->left_analog_trigger_supported || ctx->right_analog_trigger_supported) { + // Always add both analog trigger axes if one is present + axes += 2; + } + + ctx->axes_count = axes; + + DeviceDynamicEncodingSetup(device); + + // Derive button count from mask + for (Uint8 byte = 0; byte < 4; ++byte) { + for (Uint8 bit = 0; bit < 8; ++bit) { + if ((ctx->usage_masks[byte] & (1 << bit)) != 0) { + ++ctx->buttons_count; + } + } + } + + // Convert DPAD to hat + const int DPAD_MASK = (1 << SINPUT_BUTTON_IDX_DPAD_UP) | + (1 << SINPUT_BUTTON_IDX_DPAD_DOWN) | + (1 << SINPUT_BUTTON_IDX_DPAD_LEFT) | + (1 << SINPUT_BUTTON_IDX_DPAD_RIGHT); + if ((ctx->usage_masks[0] & DPAD_MASK) == DPAD_MASK) { + ctx->dpad_supported = true; + ctx->usage_masks[0] &= ~DPAD_MASK; + ctx->buttons_count -= 4; + } + +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("Buttons count: %d", ctx->buttons_count); +#endif +} + +static bool RetrieveSDLFeatures(SDL_HIDAPI_Device *device) +{ + int written = 0; + + // Attempt to send the SDL features get command. + for (int attempt = 0; attempt < 8; ++attempt) { + const Uint8 featuresGetCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_FEATURES }; + // This write will occasionally return -1, so ignore failure here and try again + written = SDL_hid_write(device->dev, featuresGetCommand, sizeof(featuresGetCommand)); + + if (written == SINPUT_DEVICE_REPORT_COMMAND_SIZE) { + break; + } + } + + if (written < SINPUT_DEVICE_REPORT_COMMAND_SIZE) { + SDL_SetError("SInput device SDL Features GET command could not write"); + return false; + } + + int read = 0; + + // Read the reply + for (int i = 0; i < 100; ++i) { + SDL_Delay(1); + + Uint8 data[USB_PACKET_LENGTH]; + read = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0); + if (read < 0) { + SDL_SetError("SInput device SDL Features GET command could not read"); + return false; + } + if (read == 0) { + continue; + } + +#ifdef DEBUG_SINPUT_PROTOCOL + HIDAPI_DumpPacket("SInput packet: size = %d", data, read); +#endif + + if ((read == USB_PACKET_LENGTH) && (data[0] == SINPUT_DEVICE_REPORT_ID_INPUT_CMDDAT) && (data[1] == SINPUT_DEVICE_COMMAND_FEATURES)) { + ProcessSDLFeaturesResponse(device, &(data[SINPUT_REPORT_IDX_COMMAND_RESPONSE_BULK])); +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("Received SInput SDL Features command response"); +#endif + return true; + } + } + + return false; +} + +// Type 2 haptics are for more traditional rumble such as +// ERM motors or simulated ERM motors +static inline void HapticsType2Pack(SINPUT_HAPTIC_S *in, Uint8 *out) +{ + // Type of haptics + out[0] = 2; + + out[1] = in->type_2.left.amplitude; + out[2] = in->type_2.left.brake; + + out[3] = in->type_2.right.amplitude; + out[4] = in->type_2.right.brake; +} + +static void HIDAPI_DriverSInput_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, callback, userdata); +} + +static void HIDAPI_DriverSInput_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, callback, userdata); +} + +static bool HIDAPI_DriverSInput_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverSInput_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + return SDL_IsJoystickSInputController(vendor_id, product_id); +} + +static bool HIDAPI_DriverSInput_InitDevice(SDL_HIDAPI_Device *device) +{ +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("SInput device Init"); +#endif + + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + + ctx->device = device; + device->context = ctx; + + if (!RetrieveSDLFeatures(device)) { + return false; + } + + // Store the USB Device Version because we will overwrite this data + ctx->usb_device_version = device->version; + + switch (device->product_id) { + case USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE: + HIDAPI_SetDeviceName(device, "HHL GC Ultimate"); + break; + case USB_PRODUCT_HANDHELDLEGEND_PROGCC: + HIDAPI_SetDeviceName(device, "HHL ProGCC"); + break; + case USB_PRODUCT_VOIDGAMING_PS4FIREBIRD: + HIDAPI_SetDeviceName(device, "Void Gaming PS4 FireBird"); + break; + case USB_PRODUCT_BONZIRICHANNEL_FIREBIRD: + HIDAPI_SetDeviceName(device, "Bonziri FireBird"); + break; + case USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC: + default: + // Use the USB product name + break; + } + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverSInput_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverSInput_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + + if (ctx->player_leds_supported) { + player_index = SDL_clamp(player_index + 1, 0, 255); + Uint8 player_num = (Uint8)player_index; + + ctx->player_idx = player_num; + + // Set player number, finalizing the setup + Uint8 playerLedCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_PLAYERLED, ctx->player_idx }; + int playerNumBytesWritten = SDL_hid_write(device->dev, playerLedCommand, SINPUT_DEVICE_REPORT_COMMAND_SIZE); + + if (playerNumBytesWritten < 0) { + SDL_SetError("SInput device player led command could not write"); + } + } +} + +#ifndef DEG2RAD +#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) +#endif + + +static bool HIDAPI_DriverSInput_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("SInput device Open"); +#endif + + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + joystick->nbuttons = ctx->buttons_count; + + SDL_zeroa(ctx->last_state); + + joystick->naxes = ctx->axes_count; + + if (ctx->dpad_supported) { + joystick->nhats = 1; + } + + if (ctx->gyroscope_supported) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 1000000.0f / ctx->polling_rate_us); + } + + if (ctx->accelerometer_supported) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 1000000.0f / ctx->polling_rate_us); + } + + if (ctx->touchpad_supported) { + // If touchpad is supported, minimum 1, max is capped + ctx->touchpad_count = SDL_clamp(ctx->touchpad_count, 1, SINPUT_MAX_ALLOWED_TOUCHPADS); + + if (ctx->touchpad_count > 1) { + // Support two separate touchpads with 1 finger each + // or support one touchpad with 2 fingers max + ctx->touchpad_finger_count = 1; + } + + if (ctx->touchpad_count > 0) { + SDL_PrivateJoystickAddTouchpad(joystick, ctx->touchpad_finger_count); + } + + if (ctx->touchpad_count > 1) { + SDL_PrivateJoystickAddTouchpad(joystick, ctx->touchpad_finger_count); + } + } + + return true; +} + +static bool HIDAPI_DriverSInput_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + + if (ctx->rumble_supported) { + SINPUT_HAPTIC_S hapticData = { 0 }; + Uint8 hapticReport[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_HAPTIC }; + + // Low Frequency = Left + // High Frequency = Right + hapticData.type_2.left.amplitude = (Uint8) (low_frequency_rumble >> 8); + hapticData.type_2.right.amplitude = (Uint8)(high_frequency_rumble >> 8); + + HapticsType2Pack(&hapticData, &(hapticReport[2])); + + SDL_HIDAPI_SendRumble(device, hapticReport, SINPUT_DEVICE_REPORT_COMMAND_SIZE); + + return true; + } + + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSInput_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverSInput_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + + Uint32 caps = 0; + if (ctx->rumble_supported) { + caps |= SDL_JOYSTICK_CAP_RUMBLE; + } + + if (ctx->player_leds_supported) { + caps |= SDL_JOYSTICK_CAP_PLAYER_LED; + } + + if (ctx->joystick_rgb_supported) { + caps |= SDL_JOYSTICK_CAP_RGB_LED; + } + + return caps; +} + +static bool HIDAPI_DriverSInput_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + + if (ctx->joystick_rgb_supported) { + Uint8 joystickRGBCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_JOYSTICKRGB, red, green, blue }; + int joystickRGBBytesWritten = SDL_hid_write(device->dev, joystickRGBCommand, SINPUT_DEVICE_REPORT_COMMAND_SIZE); + + if (joystickRGBBytesWritten < 0) { + SDL_SetError("SInput device joystick rgb command could not write"); + return false; + } + + return true; + } + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSInput_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSInput_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + + if (ctx->accelerometer_supported || ctx->gyroscope_supported) { + ctx->sensors_enabled = enabled; + return true; + } + return SDL_Unsupported(); +} + +static void HIDAPI_DriverSInput_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverSInput_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis = 0; + Sint16 accel = 0; + Sint16 gyro = 0; + Uint64 timestamp = SDL_GetTicksNS(); + float imu_values[3] = { 0 }; + Uint8 output_idx = 0; + + // Process digital buttons according to the supplied + // button mask to create a contiguous button input set + for (Uint8 processes = 0; processes < 4; ++processes) { + + Uint8 button_idx = SINPUT_REPORT_IDX_BUTTONS_0 + processes; + + for (Uint8 buttons = 0; buttons < 8; ++buttons) { + + // If a button is enabled by our usage mask + const Uint8 mask = (0x01 << buttons); + if ((ctx->usage_masks[processes] & mask) != 0) { + + bool down = (data[button_idx] & mask) != 0; + + if ( (output_idx < SDL_GAMEPAD_BUTTON_COUNT) && (ctx->last_state[button_idx] != data[button_idx]) ) { + SDL_SendJoystickButton(timestamp, joystick, output_idx, down); + } + + ++output_idx; + } + } + } + + if (ctx->dpad_supported) { + Uint8 hat = SDL_HAT_CENTERED; + + if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_UP)) { + hat |= SDL_HAT_UP; + } + if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_DOWN)) { + hat |= SDL_HAT_DOWN; + } + if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_LEFT)) { + hat |= SDL_HAT_LEFT; + } + if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_RIGHT)) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + // Analog inputs map to a signed Sint16 range of -32768 to 32767 from the device. + // Use an axis index because not all gamepads will have the same axis inputs. + Uint8 axis_idx = 0; + + // Left Analog Stick + axis = 0; // Reset axis value for joystick + if (ctx->left_analog_stick_supported) { + axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_X); + SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis); + ++axis_idx; + + axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_Y); + SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis); + ++axis_idx; + } + + // Right Analog Stick + axis = 0; // Reset axis value for joystick + if (ctx->right_analog_stick_supported) { + axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_X); + SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis); + ++axis_idx; + + axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_Y); + SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis); + ++axis_idx; + } + + // Left Analog Trigger + axis = SDL_MIN_SINT16; // Reset axis value for trigger + if (ctx->left_analog_trigger_supported) { + axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_TRIGGER); + SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis); + ++axis_idx; + } + + // Right Analog Trigger + axis = SDL_MIN_SINT16; // Reset axis value for trigger + if (ctx->right_analog_trigger_supported) { + axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_TRIGGER); + SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis); + } + + // Battery/Power state handling + if (ctx->last_state[SINPUT_REPORT_IDX_PLUG_STATUS] != data[SINPUT_REPORT_IDX_PLUG_STATUS] || + ctx->last_state[SINPUT_REPORT_IDX_CHARGE_LEVEL] != data[SINPUT_REPORT_IDX_CHARGE_LEVEL]) { + + SDL_PowerState state = SDL_POWERSTATE_UNKNOWN; + Uint8 status = data[SINPUT_REPORT_IDX_PLUG_STATUS]; + int percent = data[SINPUT_REPORT_IDX_CHARGE_LEVEL]; + + percent = SDL_clamp(percent, 0, 100); // Ensure percent is within valid range + + switch (status) { + case 1: + state = SDL_POWERSTATE_NO_BATTERY; + percent = 0; + break; + case 2: + state = SDL_POWERSTATE_CHARGING; + break; + case 3: + state = SDL_POWERSTATE_CHARGED; + percent = 100; + break; + case 4: + state = SDL_POWERSTATE_ON_BATTERY; + break; + default: + break; + } + + if (state != SDL_POWERSTATE_UNKNOWN) { + SDL_SendJoystickPowerInfo(joystick, state, percent); + } + } + + // Extract the IMU timestamp delta (in microseconds) + Uint32 imu_timestamp_us = EXTRACTUINT32(data, SINPUT_REPORT_IDX_IMU_TIMESTAMP); + Uint32 imu_time_delta_us = 0; + + // Check if we should process IMU data and if sensors are enabled + if (ctx->sensors_enabled) { + + if (imu_timestamp_us >= ctx->last_imu_timestamp_us) { + imu_time_delta_us = (imu_timestamp_us - ctx->last_imu_timestamp_us); + } else { + // Handle rollover case + imu_time_delta_us = (UINT32_MAX - ctx->last_imu_timestamp_us) + imu_timestamp_us + 1; + } + + // Convert delta to nanoseconds and update running timestamp + ctx->imu_timestamp_ns += (Uint64)imu_time_delta_us * 1000; + + // Update last timestamp + ctx->last_imu_timestamp_us = imu_timestamp_us; + + // Process Gyroscope + if (ctx->gyroscope_supported) { + + gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_Y); + imu_values[2] = -(float)gyro * ctx->gyroScale; // Y-axis rotation + + gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_Z); + imu_values[1] = (float)gyro * ctx->gyroScale; // Z-axis rotation + + gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_X); + imu_values[0] = -(float)gyro * ctx->gyroScale; // X-axis rotation + + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->imu_timestamp_ns, imu_values, 3); + } + + // Process Accelerometer + if (ctx->accelerometer_supported) { + + accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_Y); + imu_values[2] = -(float)accel * ctx->accelScale; // Y-axis acceleration + + accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_Z); + imu_values[1] = (float)accel * ctx->accelScale; // Z-axis acceleration + + accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_X); + imu_values[0] = -(float)accel * ctx->accelScale; // X-axis acceleration + + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->imu_timestamp_ns, imu_values, 3); + } + } + + // Check if we should process touchpad + if (ctx->touchpad_supported && ctx->touchpad_count > 0) { + Uint8 touchpad = 0; + Uint8 finger = 0; + + Sint16 touch1X = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH1_X); + Sint16 touch1Y = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH1_Y); + Uint16 touch1P = EXTRACTUINT16(data, SINPUT_REPORT_IDX_TOUCH1_P); + + Sint16 touch2X = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH2_X); + Sint16 touch2Y = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH2_Y); + Uint16 touch2P = EXTRACTUINT16(data, SINPUT_REPORT_IDX_TOUCH2_P); + + SDL_SendJoystickTouchpad(timestamp, joystick, touchpad, finger, + touch1P > 0, + touch1X / 65536.0f + 0.5f, + touch1Y / 65536.0f + 0.5f, + touch1P / 32768.0f); + + if (ctx->touchpad_count > 1) { + ++touchpad; + } else if (ctx->touchpad_finger_count > 1) { + ++finger; + } + + if ((touchpad > 0) || (finger > 0)) { + SDL_SendJoystickTouchpad(timestamp, joystick, touchpad, finger, + touch2P > 0, + touch2X / 65536.0f + 0.5f, + touch2Y / 65536.0f + 0.5f, + touch2P / 32768.0f); + } + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverSInput_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_SINPUT_PROTOCOL + HIDAPI_DumpPacket("SInput packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + if (data[0] == SINPUT_DEVICE_REPORT_ID_JOYSTICK_INPUT) { + HIDAPI_DriverSInput_HandleStatePacket(joystick, ctx, data, size); + } + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverSInput_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +static void HIDAPI_DriverSInput_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSInput = { + SDL_HINT_JOYSTICK_HIDAPI_SINPUT, + true, + HIDAPI_DriverSInput_RegisterHints, + HIDAPI_DriverSInput_UnregisterHints, + HIDAPI_DriverSInput_IsEnabled, + HIDAPI_DriverSInput_IsSupportedDevice, + HIDAPI_DriverSInput_InitDevice, + HIDAPI_DriverSInput_GetDevicePlayerIndex, + HIDAPI_DriverSInput_SetDevicePlayerIndex, + HIDAPI_DriverSInput_UpdateDevice, + HIDAPI_DriverSInput_OpenJoystick, + HIDAPI_DriverSInput_RumbleJoystick, + HIDAPI_DriverSInput_RumbleJoystickTriggers, + HIDAPI_DriverSInput_GetJoystickCapabilities, + HIDAPI_DriverSInput_SetJoystickLED, + HIDAPI_DriverSInput_SendJoystickEffect, + HIDAPI_DriverSInput_SetJoystickSensorsEnabled, + HIDAPI_DriverSInput_CloseJoystick, + HIDAPI_DriverSInput_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_SINPUT + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_sinput.h b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_sinput.h new file mode 100644 index 0000000..8bcc70d --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_sinput.h @@ -0,0 +1,92 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Mitchell Cairns + + 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. +*/ + +typedef enum +{ + SINPUT_ANALOGSTYLE_NONE, + SINPUT_ANALOGSTYLE_LEFTONLY, + SINPUT_ANALOGSTYLE_RIGHTONLY, + SINPUT_ANALOGSTYLE_LEFTRIGHT, + SINPUT_ANALOGSTYLE_MAX, +} SInput_AnalogStyleType; + +typedef enum +{ + SINPUT_BUMPERSTYLE_NONE, + SINPUT_BUMPERSTYLE_ONE, + SINPUT_BUMPERSTYLE_TWO, + SINPUT_BUMPERSTYLE_MAX, +} SInput_BumperStyleType; + +typedef enum +{ + SINPUT_TRIGGERSTYLE_NONE, + SINPUT_TRIGGERSTYLE_ANALOG, + SINPUT_TRIGGERSTYLE_DIGITAL, + SINPUT_TRIGGERSTYLE_DUALSTAGE, + SINPUT_TRIGGERSTYLE_MAX, +} SInput_TriggerStyleType; + +typedef enum +{ + SINPUT_PADDLESTYLE_NONE, + SINPUT_PADDLESTYLE_TWO, + SINPUT_PADDLESTYLE_FOUR, + SINPUT_PADDLESTYLE_MAX, +} SInput_PaddleStyleType; + +typedef enum +{ + SINPUT_METASTYLE_NONE, + SINPUT_METASTYLE_BACK, + SINPUT_METASTYLE_BACKGUIDE, + SINPUT_METASTYLE_BACKGUIDESHARE, + SINPUT_METASTYLE_MAX, +} SInput_MetaStyleType; + +typedef enum +{ + SINPUT_TOUCHSTYLE_NONE, + SINPUT_TOUCHSTYLE_SINGLE, + SINPUT_TOUCHSTYLE_DOUBLE, + SINPUT_TOUCHSTYLE_MAX, +} SInput_TouchStyleType; + +typedef enum +{ + SINPUT_MISCSTYLE_NONE, + SINPUT_MISCSTYLE_1, + SINPUT_MISCSTYLE_2, + SINPUT_MISCSTYLE_3, + SINPUT_MISCSTYLE_4, + SINPUT_MISCSTYLE_MAX, +} SInput_MiscStyleType; + +typedef struct +{ + Uint16 analog_style; + Uint16 bumper_style; + Uint16 trigger_style; + Uint16 paddle_style; + Uint16 meta_style; + Uint16 touch_style; + Uint16 misc_style; +} SDL_SInputStyles_t; diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_stadia.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_stadia.c index 487bf41..5c5e749 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_stadia.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_stadia.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam.c index b48d353..0ebb723 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -221,7 +221,7 @@ static void hexdump(const uint8_t *ptr, int len) static void ResetSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler) { - SDL_memset(pAssembler->uBuffer, 0, sizeof(pAssembler->uBuffer)); + SDL_zeroa(pAssembler->uBuffer); pAssembler->nExpectedSegmentNumber = 0; } @@ -320,7 +320,7 @@ static int SetFeatureReport(SDL_HIDAPI_Device *dev, const unsigned char uBuffer[ nActualDataLen -= nBytesInPacket; // Construct packet - SDL_memset(uPacketBuffer, 0, sizeof(uPacketBuffer)); + SDL_zeroa(uPacketBuffer); uPacketBuffer[0] = BLE_REPORT_NUMBER; uPacketBuffer[1] = GetSegmentHeader(nSegmentNumber, nActualDataLen == 0); SDL_memcpy(&uPacketBuffer[2], pBufferPtr, nBytesInPacket); @@ -370,7 +370,7 @@ static int GetFeatureReport(SDL_HIDAPI_Device *dev, unsigned char uBuffer[65]) #endif while (nRetries < BLE_MAX_READ_RETRIES) { - SDL_memset(uSegmentBuffer, 0, sizeof(uSegmentBuffer)); + SDL_zeroa(uSegmentBuffer); uSegmentBuffer[0] = BLE_REPORT_NUMBER; nRet = SDL_hid_get_feature_report(dev->dev, uSegmentBuffer, ucBytesToRead); @@ -457,6 +457,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew DPRINTF("ResetSteamController hid=%p\n", dev); + SDL_zero(buf); buf[0] = 0; buf[1] = ID_GET_ATTRIBUTES_VALUES; res = SetFeatureReport(dev, buf, 2); @@ -510,6 +511,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew } // Clear digital button mappings + SDL_zero(buf); buf[0] = 0; buf[1] = ID_CLEAR_DIGITAL_MAPPINGS; res = SetFeatureReport(dev, buf, 2); @@ -521,7 +523,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew } // Reset the default settings - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_LOAD_DEFAULT_SETTINGS; buf[2] = 0; res = SetFeatureReport(dev, buf, 3); @@ -539,7 +541,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew buf[3 + nSettings * 3 + 2] = ((uint16_t)VALUE) >> 8; \ ++nSettings; - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_SET_SETTINGS_VALUES; ADD_SETTING(SETTING_WIRELESS_PACKET_VERSION, 2); ADD_SETTING(SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE); @@ -567,7 +569,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew bool bMappingsCleared = false; int iRetry; for (iRetry = 0; iRetry < 2; ++iRetry) { - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_GET_DIGITAL_MAPPINGS; buf[2] = 1; // one byte - requesting from index 0 buf[3] = 0; @@ -596,7 +598,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew } // Set our new mappings - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_SET_DIGITAL_MAPPINGS; buf[2] = 6; // 2 settings x 3 bytes buf[3] = IO_DIGITAL_BUTTON_RIGHT_TRIGGER; @@ -634,7 +636,7 @@ static int ReadSteamController(SDL_hid_device *dev, uint8_t *pData, int nDataSiz static void SetPairingState(SDL_HIDAPI_Device *dev, bool bEnablePairing) { unsigned char buf[65]; - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_ENABLE_PAIRING; buf[2] = 2; // 2 payload bytes: bool + timeout buf[3] = bEnablePairing ? 1 : 0; @@ -648,7 +650,7 @@ static void SetPairingState(SDL_HIDAPI_Device *dev, bool bEnablePairing) static void CommitPairing(SDL_HIDAPI_Device *dev) { unsigned char buf[65]; - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_DONGLE_COMMIT_DEVICE; SetFeatureReport(dev, buf, 2); } @@ -663,18 +665,18 @@ static void CloseSteamController(SDL_HIDAPI_Device *dev) int nSettings = 0; // Reset digital button mappings - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_SET_DEFAULT_DIGITAL_MAPPINGS; SetFeatureReport(dev, buf, 2); // Reset the default settings - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_LOAD_DEFAULT_SETTINGS; buf[2] = 0; SetFeatureReport(dev, buf, 3); // Reset mouse mode for lizard mode - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_SET_SETTINGS_VALUES; ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE); buf[2] = (unsigned char)(nSettings * 3); @@ -706,13 +708,6 @@ static void RotatePad(int *pX, int *pY, float flAngleInRad) *pX = (int)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY); *pY = (int)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY); } -static void RotatePadShort(short *pX, short *pY, float flAngleInRad) -{ - int origX = *pX, origY = *pY; - - *pX = (short)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY); - *pY = (short)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY); -} //--------------------------------------------------------------------------- // Format the first part of the state packet @@ -836,9 +831,16 @@ static void FormatStatePacketUntilGyro(SteamControllerStateInternal_t *pState, V //--------------------------------------------------------------------------- static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState) { - const float flRotationAngle = 0.261799f; + int nLeftPadX; + int nLeftPadY; + int nRightPadX; + int nRightPadY; + int nPadOffset; uint32_t ucOptionDataMask; + // 15 degrees in rad + const float flRotationAngle = 0.261799f; + pState->unPacketNum++; ucOptionDataMask = (*pData++ & 0xF0); ucOptionDataMask |= (uint32_t)(*pData++) << 8; @@ -867,7 +869,6 @@ static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, S } if (ucOptionDataMask & k_EBLELeftTrackpadChunk) { int nLength = sizeof(pState->sLeftPadX) + sizeof(pState->sLeftPadY); - int nPadOffset; SDL_memcpy(&pState->sLeftPadX, pData, nLength); if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) { nPadOffset = 1000; @@ -875,14 +876,15 @@ static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, S nPadOffset = 0; } - RotatePadShort(&pState->sLeftPadX, &pState->sLeftPadY, -flRotationAngle); - pState->sLeftPadX = (short)clamp(pState->sLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); - pState->sLeftPadY = (short)clamp(pState->sLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + nLeftPadX = pState->sLeftPadX; + nLeftPadY = pState->sLeftPadY; + RotatePad(&nLeftPadX, &nLeftPadY, -flRotationAngle); + pState->sLeftPadX = (short)clamp(nLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + pState->sLeftPadY = (short)clamp(nLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); pData += nLength; } if (ucOptionDataMask & k_EBLERightTrackpadChunk) { int nLength = sizeof(pState->sRightPadX) + sizeof(pState->sRightPadY); - int nPadOffset = 0; SDL_memcpy(&pState->sRightPadX, pData, nLength); @@ -892,9 +894,11 @@ static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, S nPadOffset = 0; } - RotatePadShort(&pState->sRightPadX, &pState->sRightPadY, flRotationAngle); - pState->sRightPadX = (short)clamp(pState->sRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); - pState->sRightPadY = (short)clamp(pState->sRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + nRightPadX = pState->sRightPadX; + nRightPadY = pState->sRightPadY; + RotatePad(&nRightPadX, &nRightPadY, flRotationAngle); + pState->sRightPadX = (short)clamp(nRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + pState->sRightPadY = (short)clamp(nRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); pData += nLength; } if (ucOptionDataMask & k_EBLEIMUAccelChunk) { @@ -1038,6 +1042,11 @@ static bool HIDAPI_DriverSteam_IsSupportedDevice(SDL_HIDAPI_Device *device, cons return false; } + if (!device) { + // Might be supported by this driver, enumerate and find out + return true; + } + if (device->is_bluetooth) { return true; } @@ -1133,6 +1142,7 @@ static bool HIDAPI_DriverSteam_InitDevice(SDL_HIDAPI_Device *device) unsigned char buf[65]; int res; + SDL_zero(buf); buf[0] = 0; buf[1] = ID_DONGLE_GET_WIRELESS_STATE; res = SetFeatureReport(device, buf, 2); @@ -1194,7 +1204,7 @@ static bool SetHomeLED(SDL_HIDAPI_Device *device, Uint8 value) unsigned char buf[65]; int nSettings = 0; - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_SET_SETTINGS_VALUES; ADD_SETTING(SETTING_LED_USER_BRIGHTNESS, value); buf[2] = (unsigned char)(nSettings * 3); @@ -1305,7 +1315,7 @@ static bool HIDAPI_DriverSteam_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_ unsigned char buf[65]; int nSettings = 0; - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_SET_SETTINGS_VALUES; if (enabled) { ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO); diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam_hori.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam_hori.c index 0cd192d..8d445c9 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam_hori.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam_hori.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -32,8 +32,6 @@ /* Define this if you want to log all packets from the controller */ /*#define DEBUG_HORI_PROTOCOL*/ -#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) - enum { SDL_GAMEPAD_BUTTON_HORI_QAM = 11, @@ -51,6 +49,8 @@ typedef struct Uint8 last_state[USB_PACKET_LENGTH]; Uint64 sensor_ticks; Uint32 last_tick; + Uint64 simulated_sensor_step_ns; + Uint64 simulated_sensor_time_stamp; bool wireless; bool serial_needs_init; } SDL_DriverSteamHori_Context; @@ -114,21 +114,25 @@ static bool HIDAPI_DriverSteamHori_OpenJoystick(SDL_HIDAPI_Device *device, SDL_J /* Initialize the joystick capabilities */ joystick->nbuttons = SDL_GAMEPAD_NUM_HORI_BUTTONS; joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; - joystick->nhats = 1; + joystick->nhats = 1; - ctx->wireless = device->product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT; + ctx->wireless = device->product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT; - if (ctx->wireless && device->serial) { - joystick->serial = SDL_strdup(device->serial); - ctx->serial_needs_init = false; - } else if (!ctx->wireless) { - // Need to actual read from the device to init the serial - HIDAPI_DriverSteamHori_UpdateDevice(device); - } - - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); + if (ctx->wireless && device->serial) { + joystick->serial = SDL_strdup(device->serial); + ctx->serial_needs_init = false; + } else if (!ctx->wireless) { + // Need to actual read from the device to init the serial + HIDAPI_DriverSteamHori_UpdateDevice(device); + } + const float sensorupdaterate = ctx->wireless ? 120.0f : 250.0f; + + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, sensorupdaterate); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, sensorupdaterate); + + const Uint64 sensorupdatestep_ms = ctx->wireless ? 8333 : 4000; // Equivalent to 120hz / 250hz respectively + ctx->simulated_sensor_step_ns = SDL_US_TO_NS(sensorupdatestep_ms); return true; } @@ -275,12 +279,13 @@ static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FL, ((data[7] & 0x80) != 0)); } - if (!ctx->wireless && ctx->serial_needs_init) { + if (!ctx->wireless && ctx->serial_needs_init) { char serial[18]; - (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", - data[38], data[39], data[40], data[41], data[42], data[43]); + (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", + data[38], data[39], data[40], data[41], data[42], data[43]); - joystick->serial = SDL_strdup(serial); + SDL_AssertJoysticksLocked(); + joystick->serial = SDL_strdup(serial); ctx->serial_needs_init = false; } @@ -312,7 +317,11 @@ static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL ctx->sensor_ticks += delta; /* Sensor timestamp is in 1us units, but there seems to be some issues with the values reported from the device */ - sensor_timestamp = timestamp; // if the values were good we woudl call SDL_US_TO_NS(ctx->sensor_ticks); + // sensor_timestamp = timestamp; // if the values were good we would call SDL_US_TO_NS(ctx->sensor_ticks); + + /* New approach - simulate a fixed rate of 250hz (from observation). This reduces stutter from dropped/racing bluetooth packets.*/ + ctx->simulated_sensor_time_stamp += ctx->simulated_sensor_step_ns; + sensor_timestamp = ctx->simulated_sensor_time_stamp; const float accelScale = SDL_STANDARD_GRAVITY * 8 / 32768.0f; const float gyroScale = DEG2RAD(2048); @@ -320,7 +329,6 @@ static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL imu_data[1] = RemapValClamped(-1.0f * LOAD16(data[12], data[13]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); imu_data[2] = RemapValClamped(-1.0f * LOAD16(data[14], data[15]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); imu_data[0] = RemapValClamped(-1.0f * LOAD16(data[16], data[17]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); - SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, imu_data, 3); @@ -334,8 +342,9 @@ static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL if (ctx->last_state[24] != data[24]) { bool bCharging = (data[24] & 0x10) != 0; int percent = (data[24] & 0xF) * 10; - SDL_PowerState state; - if (bCharging) { + SDL_PowerState state; + + if (bCharging) { state = SDL_POWERSTATE_CHARGING; } else if (ctx->wireless) { state = SDL_POWERSTATE_ON_BATTERY; diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam_triton.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam_triton.c new file mode 100644 index 0000000..453becd --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam_triton.c @@ -0,0 +1,540 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2023 Max Maisel + + 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_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" + +#ifdef SDL_JOYSTICK_HIDAPI_STEAM_TRITON + +/*****************************************************************************************************/ + +#include "steam/controller_constants.h" +#include "steam/controller_structs.h" + +// Always 1kHz according to USB descriptor, but actually about 4 ms. +#define TRITON_SENSOR_UPDATE_INTERVAL_US 4032 + +enum +{ + SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM = 11, + SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1, + SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1, + SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2, + SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2, + SDL_GAMEPAD_NUM_TRITON_BUTTONS, +}; + +typedef enum +{ + + TRITON_LBUTTON_A = 0x00000001, + TRITON_LBUTTON_B = 0x00000002, + TRITON_LBUTTON_X = 0x00000004, + TRITON_LBUTTON_Y = 0x00000008, + + TRITON_HBUTTON_QAM = 0x00000010, + TRITON_LBUTTON_R3 = 0x00000020, + TRITON_LBUTTON_VIEW = 0x00000040, + TRITON_HBUTTON_R4 = 0x00000080, + + TRITON_LBUTTON_R5 = 0x00000100, + TRITON_LBUTTON_R = 0x00000200, + TRITON_LBUTTON_DPAD_DOWN = 0x00000400, + TRITON_LBUTTON_DPAD_RIGHT = 0x00000800, + + TRITON_LBUTTON_DPAD_LEFT = 0x00001000, + TRITON_LBUTTON_DPAD_UP = 0x00002000, + TRITON_LBUTTON_MENU = 0x00004000, + TRITON_LBUTTON_L3 = 0x00008000, + + TRITON_LBUTTON_STEAM = 0x00010000, + TRITON_HBUTTON_L4 = 0x00020000, + TRITON_LBUTTON_L5 = 0x00040000, + TRITON_LBUTTON_L = 0x00080000, + + /* + STEAM_RIGHTSTICK_FINGERDOWN_MASK, // Right Stick Touch 0x00100000 + STEAM_RIGHTPAD_FINGERDOWN_MASK, // Right Pad Touch 0x00200000 + STEAM_BUTTON_RIGHTPAD_CLICKED_MASK, // Right Pressure Click 0x00400000 + STEAM_RIGHT_TRIGGER_MASK, // Right Trigger Click 0x00800000 + + STEAM_LEFTSTICK_FINGERDOWN_MASK, // Left Stick Touch 0x01000000 + STEAM_LEFTPAD_FINGERDOWN_MASK, // Left Pad Touch 0x02000000 + STEAM_BUTTON_LEFTPAD_CLICKED_MASK, // Left Pressure Click 0x04000000 + STEAM_LEFT_TRIGGER_MASK, // Left Trigger Click 0x08000000 + STEAM_RIGHT_AUX_MASK, // Right Pinky Touch 0x10000000 + STEAM_LEFT_AUX_MASK, // Left Pinky Touch 0x20000000 + */ +} TritonButtons; + +typedef struct +{ + bool connected; + bool report_sensors; + Uint32 last_sensor_tick; + Uint64 sensor_timestamp_ns; + Uint64 last_button_state; + Uint64 last_lizard_update; +} SDL_DriverSteamTriton_Context; + +static bool IsProteusDongle(Uint16 product_id) +{ + return (product_id == USB_PRODUCT_VALVE_STEAM_PROTEUS_DONGLE || + product_id == USB_PRODUCT_VALVE_STEAM_NEREID_DONGLE); +} + +static bool DisableSteamTritonLizardMode(SDL_hid_device *dev) +{ + int rc; + Uint8 buffer[HID_FEATURE_REPORT_BYTES] = { 1 }; + FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1); + + msg->header.type = ID_SET_SETTINGS_VALUES; + msg->header.length = 1 * sizeof(ControllerSetting); + msg->payload.setSettingsValues.settings[0].settingNum = SETTING_LIZARD_MODE; + msg->payload.setSettingsValues.settings[0].settingValue = LIZARD_MODE_OFF; + + rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) { + return false; + } + + return true; +} + +static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device, + SDL_Joystick *joystick, + TritonMTUFull_t *pTritonReport) +{ + float values[3]; + SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context; + Uint64 timestamp = SDL_GetTicksNS(); + + if (pTritonReport->uButtons != ctx->last_button_state) { + Uint8 hat = 0; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, + ((pTritonReport->uButtons & TRITON_LBUTTON_A) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, + ((pTritonReport->uButtons & TRITON_LBUTTON_B) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, + ((pTritonReport->uButtons & TRITON_LBUTTON_X) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, + ((pTritonReport->uButtons & TRITON_LBUTTON_Y) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + ((pTritonReport->uButtons & TRITON_LBUTTON_L) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + ((pTritonReport->uButtons & TRITON_LBUTTON_R) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, + ((pTritonReport->uButtons & TRITON_LBUTTON_MENU) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, + ((pTritonReport->uButtons & TRITON_LBUTTON_VIEW) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, + ((pTritonReport->uButtons & TRITON_LBUTTON_STEAM) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM, + ((pTritonReport->uButtons & TRITON_HBUTTON_QAM) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, + ((pTritonReport->uButtons & TRITON_LBUTTON_L3) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, + ((pTritonReport->uButtons & TRITON_LBUTTON_R3) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1, + ((pTritonReport->uButtons & TRITON_HBUTTON_R4) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1, + ((pTritonReport->uButtons & TRITON_HBUTTON_L4) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2, + ((pTritonReport->uButtons & TRITON_LBUTTON_R5) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2, + ((pTritonReport->uButtons & TRITON_LBUTTON_L5) != 0)); + + if (pTritonReport->uButtons & TRITON_LBUTTON_DPAD_UP) { + hat |= SDL_HAT_UP; + } + if (pTritonReport->uButtons & TRITON_LBUTTON_DPAD_DOWN) { + hat |= SDL_HAT_DOWN; + } + if (pTritonReport->uButtons & TRITON_LBUTTON_DPAD_LEFT) { + hat |= SDL_HAT_LEFT; + } + if (pTritonReport->uButtons & TRITON_LBUTTON_DPAD_RIGHT) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + ctx->last_button_state = pTritonReport->uButtons; + } + + // RKRK There're button bits for this if you so choose. + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, + (int)pTritonReport->sTriggerLeft * 2 - 32768); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, + (int)pTritonReport->sTriggerRight * 2 - 32768); + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, + pTritonReport->sLeftStickX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, + -pTritonReport->sLeftStickY); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, + pTritonReport->sRightStickX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, + -pTritonReport->sRightStickY); + + if (ctx->report_sensors && pTritonReport->imu.uTimestamp != ctx->last_sensor_tick) { + Uint32 delta_us = (pTritonReport->imu.uTimestamp - ctx->last_sensor_tick); + + ctx->sensor_timestamp_ns += SDL_US_TO_NS(delta_us); + + values[0] = (pTritonReport->imu.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + values[1] = (pTritonReport->imu.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + values[2] = (-pTritonReport->imu.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_ns, values, 3); + + values[0] = (pTritonReport->imu.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + values[1] = (pTritonReport->imu.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + values[2] = (-pTritonReport->imu.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_ns, values, 3); + + ctx->last_sensor_tick = pTritonReport->imu.uTimestamp; + } +} + +static void HIDAPI_DriverSteamTriton_HandleBatteryStatus(SDL_HIDAPI_Device *device, + SDL_Joystick *joystick, + TritonBatteryStatus_t *pTritonBatteryStatus) +{ + SDL_PowerState state; + + switch (pTritonBatteryStatus->ucChargeState) { + case k_EChargeStateDischarging: + state = SDL_POWERSTATE_ON_BATTERY; + break; + case k_EChargeStateCharging: + state = SDL_POWERSTATE_CHARGING; + break; + case k_EChargeStateChargingDone: + state = SDL_POWERSTATE_CHARGED; + break; + default: + // Error state? + state = SDL_POWERSTATE_UNKNOWN; + break; + } + SDL_SendJoystickPowerInfo(joystick, state, pTritonBatteryStatus->ucBatteryLevel); +} + +static bool HIDAPI_DriverSteamTriton_SetControllerConnected(SDL_HIDAPI_Device *device, bool connected) +{ + SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context; + + if (ctx->connected != connected) { + ctx->connected = connected; + + if (connected) { + SDL_JoystickID joystickID; + if (!HIDAPI_JoystickConnected(device, &joystickID)) { + return false; + } + } else { + if (device->num_joysticks > 0) { + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + } + } + return true; +} + +static void HIDAPI_DriverSteamTriton_HandleWirelessStatus(SDL_HIDAPI_Device *device, + TritonWirelessStatus_t *pTritonWirelessStatus) +{ + switch (pTritonWirelessStatus->state) { + case k_ETritonWirelessStateConnect: + HIDAPI_DriverSteamTriton_SetControllerConnected(device, true); + break; + case k_ETritonWirelessStateDisconnect: + HIDAPI_DriverSteamTriton_SetControllerConnected(device, false); + break; + default: + break; + } +} + +/*****************************************************************************************************/ + +static void HIDAPI_DriverSteamTriton_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata); +} + +static void HIDAPI_DriverSteamTriton_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata); +} + +static bool HIDAPI_DriverSteamTriton_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverSteamTriton_IsSupportedDevice( + SDL_HIDAPI_Device *device, + const char *name, + SDL_GamepadType type, + Uint16 vendor_id, + Uint16 product_id, + Uint16 version, + int interface_number, + int interface_class, + int interface_subclass, + int interface_protocol) +{ + + if (IsProteusDongle(product_id)) { + if (interface_number >= 2 && interface_number <= 5) { + // The set of controller interfaces for Proteus & Nereid...currently + return true; + } + } else if (SDL_IsJoystickSteamTriton(vendor_id, product_id)) { + return true; + } + return false; +} + +static bool HIDAPI_DriverSteamTriton_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSteamTriton_Context *ctx; + + ctx = (SDL_DriverSteamTriton_Context *)SDL_calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return false; + } + + device->context = ctx; + + HIDAPI_SetDeviceName(device, "Steam Controller"); + + if (IsProteusDongle(device->product_id)) { + return true; + } + + // Wired controller, connected! + return HIDAPI_DriverSteamTriton_SetControllerConnected(device, true); +} + +static int HIDAPI_DriverSteamTriton_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverSteamTriton_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static bool HIDAPI_DriverSteamTriton_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context; + SDL_Joystick *joystick = NULL; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } + + if (ctx->connected && joystick) { + Uint64 now = SDL_GetTicks(); + if (!ctx->last_lizard_update || (now - ctx->last_lizard_update) >= 3000) { + DisableSteamTritonLizardMode(device->dev); + ctx->last_lizard_update = now; + } + } + + for (;;) { + uint8_t data[64]; + int r = SDL_hid_read(device->dev, data, sizeof(data)); + + if (r == 0) { + return true; + } + if (r < 0) { + // Failed to read from controller + HIDAPI_DriverSteamTriton_SetControllerConnected(device, false); + return false; + } + + switch (data[0]) { + case ID_TRITON_CONTROLLER_STATE: + case ID_TRITON_CONTROLLER_STATE_BLE: + if (!joystick) { + HIDAPI_DriverSteamTriton_SetControllerConnected(device, true); + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } + } + if (joystick && r >= (1 + sizeof(TritonMTUFull_t))) { + TritonMTUFull_t *pTritonReport = (TritonMTUFull_t *)&data[1]; + HIDAPI_DriverSteamTriton_HandleState(device, joystick, pTritonReport); + } + break; + case ID_TRITON_BATTERY_STATUS: + if (joystick && r >= (1 + sizeof(TritonBatteryStatus_t))) { + TritonBatteryStatus_t *pTritonBatteryStatus = (TritonBatteryStatus_t *)&data[1]; + HIDAPI_DriverSteamTriton_HandleBatteryStatus(device, joystick, pTritonBatteryStatus); + } + break; + case ID_TRITON_WIRELESS_STATUS_X: + case ID_TRITON_WIRELESS_STATUS: + if (r >= (1 + sizeof(TritonWirelessStatus_t))) { + TritonWirelessStatus_t *pTritonWirelessStatus = (TritonWirelessStatus_t *)&data[1]; + HIDAPI_DriverSteamTriton_HandleWirelessStatus(device, pTritonWirelessStatus); + } + break; + default: + break; + } + } +} + +static bool HIDAPI_DriverSteamTriton_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + float update_rate_in_hz = 1000000.0f / TRITON_SENSOR_UPDATE_INTERVAL_US; + + SDL_AssertJoysticksLocked(); + + // Initialize the joystick capabilities + joystick->nbuttons = SDL_GAMEPAD_NUM_TRITON_BUTTONS; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz); + + return true; +} + +static bool HIDAPI_DriverSteamTriton_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + int rc; + + //RKRK Not sure about size. Probably 64+1 is OK for ORs + Uint8 buffer[HID_RUMBLE_OUTPUT_REPORT_BYTES]; + OutputReportMsg *msg = (OutputReportMsg *)(buffer); + + msg->report_id = ID_OUT_REPORT_HAPTIC_RUMBLE; + msg->payload.hapticRumble.type = 0; + msg->payload.hapticRumble.intensity = 0; + msg->payload.hapticRumble.left.speed = low_frequency_rumble; + msg->payload.hapticRumble.left.gain = 0; + msg->payload.hapticRumble.right.speed = high_frequency_rumble; + msg->payload.hapticRumble.right.gain = 0; + + + rc = SDL_hid_write(device->dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) { + return false; + } + return true; +} + +static bool HIDAPI_DriverSteamTriton_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverSteamTriton_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + return SDL_JOYSTICK_CAP_RUMBLE; +} + +static bool HIDAPI_DriverSteamTriton_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSteamTriton_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSteamTriton_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context; + int rc; + Uint8 buffer[HID_FEATURE_REPORT_BYTES] = { 1 }; + FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1); + + msg->header.type = ID_SET_SETTINGS_VALUES; + msg->header.length = 1 * sizeof(ControllerSetting); + msg->payload.setSettingsValues.settings[0].settingNum = SETTING_IMU_MODE; + if (enabled) { + msg->payload.setSettingsValues.settings[0].settingValue = (SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO); + } else { + msg->payload.setSettingsValues.settings[0].settingValue = SETTING_GYRO_MODE_OFF; + } + + rc = SDL_hid_send_feature_report(device->dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) { + return false; + } + + ctx->report_sensors = enabled; + + return true; +} + +static void HIDAPI_DriverSteamTriton_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + // Lizard mode id automatically re-enabled by watchdog. Nothing to do here. +} + +static void HIDAPI_DriverSteamTriton_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamTriton = { + SDL_HINT_JOYSTICK_HIDAPI_STEAM, + true, + HIDAPI_DriverSteamTriton_RegisterHints, + HIDAPI_DriverSteamTriton_UnregisterHints, + HIDAPI_DriverSteamTriton_IsEnabled, + HIDAPI_DriverSteamTriton_IsSupportedDevice, + HIDAPI_DriverSteamTriton_InitDevice, + HIDAPI_DriverSteamTriton_GetDevicePlayerIndex, + HIDAPI_DriverSteamTriton_SetDevicePlayerIndex, + HIDAPI_DriverSteamTriton_UpdateDevice, + HIDAPI_DriverSteamTriton_OpenJoystick, + HIDAPI_DriverSteamTriton_RumbleJoystick, + HIDAPI_DriverSteamTriton_RumbleJoystickTriggers, + HIDAPI_DriverSteamTriton_GetJoystickCapabilities, + HIDAPI_DriverSteamTriton_SetJoystickLED, + HIDAPI_DriverSteamTriton_SendJoystickEffect, + HIDAPI_DriverSteamTriton_SetSensorsEnabled, + HIDAPI_DriverSteamTriton_CloseJoystick, + HIDAPI_DriverSteamTriton_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_STEAM_TRITON + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steamdeck.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steamdeck.c index 75a13cc..018ce00 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steamdeck.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steamdeck.c @@ -74,7 +74,7 @@ typedef enum typedef struct { Uint32 update_rate_us; - Uint32 sensor_timestamp_us; + Uint64 sensor_timestamp_ns; Uint64 last_button_state; Uint8 watchdog_counter; } SDL_DriverSteamDeck_Context; @@ -222,17 +222,29 @@ static void HIDAPI_DriverSteamDeck_HandleState(SDL_HIDAPI_Device *device, SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, -pInReport->payload.deckState.sRightStickY); - ctx->sensor_timestamp_us += ctx->update_rate_us; + ctx->sensor_timestamp_ns += SDL_US_TO_NS(ctx->update_rate_us); values[0] = (pInReport->payload.deckState.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); values[1] = (pInReport->payload.deckState.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); values[2] = (-pInReport->payload.deckState.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); - SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_us, values, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_ns, values, 3); values[0] = (pInReport->payload.deckState.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; values[1] = (pInReport->payload.deckState.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; values[2] = (-pInReport->payload.deckState.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; - SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_us, values, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_ns, values, 3); + + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, + pInReport->payload.deckState.sPressurePadLeft > 0, + pInReport->payload.deckState.sLeftPadX / 65536.0f + 0.5f, + pInReport->payload.deckState.sLeftPadY / 65536.0f + 0.5f, + pInReport->payload.deckState.sPressurePadLeft / 32768.0f); + + SDL_SendJoystickTouchpad(timestamp, joystick, 1, 0, + pInReport->payload.deckState.sPressurePadRight > 0, + pInReport->payload.deckState.sRightPadX / 65536.0f + 0.5f, + pInReport->payload.deckState.sRightPadY / 65536.0f + 0.5f, + pInReport->payload.deckState.sPressurePadRight / 32768.0f); } /*****************************************************************************************************/ @@ -331,7 +343,7 @@ static bool HIDAPI_DriverSteamDeck_UpdateDevice(SDL_HIDAPI_Device *device) return false; } - SDL_memset(data, 0, sizeof(data)); + SDL_zeroa(data); do { r = SDL_hid_read(device->dev, data, sizeof(data)); @@ -366,6 +378,9 @@ static bool HIDAPI_DriverSteamDeck_OpenJoystick(SDL_HIDAPI_Device *device, SDL_J SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz); SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz); + SDL_PrivateJoystickAddTouchpad(joystick, 1); + SDL_PrivateJoystickAddTouchpad(joystick, 1); + return true; } diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c index 8f9b1bf..514e158 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -34,7 +34,9 @@ #ifdef SDL_JOYSTICK_HIDAPI_SWITCH // Define this if you want to log all packets from the controller -// #define DEBUG_SWITCH_PROTOCOL +#if 0 +#define DEBUG_SWITCH_PROTOCOL +#endif // Define this to get log output for rumble logic // #define DEBUG_RUMBLE @@ -159,20 +161,21 @@ typedef struct Uint8 ucVibrationCode; } SwitchControllerStatePacket_t; +typedef struct +{ + Sint16 sAccelX; + Sint16 sAccelY; + Sint16 sAccelZ; + + Sint16 sGyroX; + Sint16 sGyroY; + Sint16 sGyroZ; +} SwitchControllerIMUState_t; + typedef struct { SwitchControllerStatePacket_t controllerState; - - struct - { - Sint16 sAccelX; - Sint16 sAccelY; - Sint16 sAccelZ; - - Sint16 sGyroX; - Sint16 sGyroY; - Sint16 sGyroZ; - } imuState[3]; + SwitchControllerIMUState_t imuState[3]; } SwitchStatePacket_t; typedef struct @@ -313,6 +316,8 @@ typedef struct Uint64 m_ulIMUUpdateIntervalNS; Uint64 m_ulTimestampNS; bool m_bVerticalMode; + SDL_PowerState m_ePowerState; + int m_nPowerPercent; SwitchInputOnlyControllerStatePacket_t m_lastInputOnlyState; SwitchSimpleStatePacket_t m_lastSimpleState; @@ -388,7 +393,7 @@ static int WriteOutput(SDL_DriverSwitch_Context *ctx, const Uint8 *data, int siz #endif // SWITCH_SYNCHRONOUS_WRITES } -static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs expectedID) +static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs expectedID, const Uint8 *pBuf, Uint8 ucLen) { // Average response time for messages is ~30ms Uint64 endTicks = SDL_GetTicks() + 100; @@ -398,9 +403,17 @@ static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Conte if (nRead > 0) { if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) { SwitchSubcommandInputPacket_t *reply = (SwitchSubcommandInputPacket_t *)&ctx->m_rgucReadBuffer[1]; - if (reply->ucSubcommandID == expectedID && (reply->ucSubcommandAck & 0x80)) { - return reply; + if (reply->ucSubcommandID != expectedID || !(reply->ucSubcommandAck & 0x80)) { + continue; } + if (reply->ucSubcommandID == k_eSwitchSubcommandIDs_SPIFlashRead) { + SDL_assert(ucLen == sizeof(reply->spiReadData.opData)); + if (SDL_memcmp(&reply->spiReadData.opData, pBuf, ucLen) != 0) { + // This was a reply for another SPI read command + continue; + } + } + return reply; } } else { SDL_Delay(1); @@ -437,7 +450,7 @@ static bool ReadProprietaryReply(SDL_DriverSwitch_Context *ctx, ESwitchProprieta static void ConstructSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, const Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandOutputPacket_t *outPacket) { - SDL_memset(outPacket, 0, sizeof(*outPacket)); + SDL_zerop(outPacket); outPacket->commonData.ucPacketType = k_eSwitchOutputReportIDs_RumbleAndSubcommand; outPacket->commonData.ucPacketNumber = ctx->m_nCommandNumber; @@ -487,7 +500,7 @@ static bool WriteSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs continue; } - reply = ReadSubcommandReply(ctx, ucCommandID); + reply = ReadSubcommandReply(ctx, ucCommandID, pBuf, ucLen); } if (ppReply) { @@ -557,15 +570,33 @@ static Uint16 EncodeRumbleLowAmplitude(Uint16 amplitude) return lfa[100][1]; } -static void SetNeutralRumble(SwitchRumbleData_t *pRumble) +static void SetNeutralRumble(SDL_HIDAPI_Device *device, SwitchRumbleData_t *pRumble) { - pRumble->rgucData[0] = 0x00; - pRumble->rgucData[1] = 0x01; - pRumble->rgucData[2] = 0x40; - pRumble->rgucData[3] = 0x40; + bool bStandardNeutralValue; + if (device->vendor_id == USB_VENDOR_NINTENDO && + device->product_id == USB_PRODUCT_NINTENDO_N64_CONTROLLER) { + // The 8BitDo 64 Bluetooth Controller rumbles at startup with the standard neutral value, + // so we'll use a 0 amplitude value instead. + bStandardNeutralValue = false; + } else { + // The KingKong2 PRO Controller doesn't initialize correctly with a 0 amplitude value + // over Bluetooth, so we'll use the standard value in all other cases. + bStandardNeutralValue = true; + } + if (bStandardNeutralValue) { + pRumble->rgucData[0] = 0x00; + pRumble->rgucData[1] = 0x01; + pRumble->rgucData[2] = 0x40; + pRumble->rgucData[3] = 0x40; + } else { + pRumble->rgucData[0] = 0x00; + pRumble->rgucData[1] = 0x00; + pRumble->rgucData[2] = 0x01; + pRumble->rgucData[3] = 0x40; + } } -static void EncodeRumble(SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 ucHighFreqAmp, Uint8 ucLowFreq, Uint16 usLowFreqAmp) +static void EncodeRumble(SDL_HIDAPI_Device *device, SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 ucHighFreqAmp, Uint8 ucLowFreq, Uint16 usLowFreqAmp) { if (ucHighFreqAmp > 0 || usLowFreqAmp > 0) { // High-band frequency and low-band amplitude are actually nine-bits each so they @@ -582,7 +613,7 @@ static void EncodeRumble(SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 u ucHighFreqAmp, ((usLowFreqAmp >> 8) & 0x80), usLowFreqAmp & 0xFF); #endif } else { - SetNeutralRumble(pRumble); + SetNeutralRumble(device, pRumble); } } @@ -747,9 +778,10 @@ static void UpdateSlotLED(SDL_DriverSwitch_Context *ctx) { if (!ctx->m_bInputOnly) { Uint8 led_data = 0; + const Uint8 player_pattern[] = { 0x1, 0x3, 0x7, 0xf, 0x9, 0x5, 0xd, 0x6 }; if (ctx->m_bPlayerLights && ctx->m_nPlayerIndex >= 0) { - led_data = (1 << (ctx->m_nPlayerIndex % 4)); + led_data = player_pattern[ctx->m_nPlayerIndex % 8]; } WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetPlayerLights, &led_data, sizeof(led_data), NULL); } @@ -957,7 +989,7 @@ static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx) if (user_reply && user_reply->stickUserCalibration.rgucRightMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucRightMagic[1] == 0xA1) { userParamsReadSuccessCount += 1; pRightStickCal = user_reply->stickUserCalibration.rgucRightCalibration; - } + } // Only read the factory calibration info if we failed to receive the correct magic bytes if (userParamsReadSuccessCount < 2) { @@ -1386,7 +1418,15 @@ static bool HIDAPI_DriverSwitch_IsSupportedDevice(SDL_HIDAPI_Device *device, con return false; } - return (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO); + if (type != SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO) { + return false; + } + + // The Nintendo Switch 2 Pro uses another driver + if (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH2_PRO) { + return false; + } + return true; } static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) @@ -1395,7 +1435,7 @@ static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) if (ctx->m_bInputOnly) { if (SDL_IsJoystickGameCube(device->vendor_id, device->product_id)) { - device->type = SDL_GAMEPAD_TYPE_STANDARD; + device->type = SDL_GAMEPAD_TYPE_GAMECUBE; } } else { char serial[18]; @@ -1507,8 +1547,8 @@ static bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device) ctx->m_bInputOnly = SDL_IsJoystickNintendoSwitchProInputOnly(device->vendor_id, device->product_id); if (!ctx->m_bInputOnly) { // Initialize rumble data, important for reading device info on the MOBAPAD M073 - SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); - SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); + SetNeutralRumble(device, &ctx->m_RumblePacket.rumbleData[0]); + SetNeutralRumble(device, &ctx->m_RumblePacket.rumbleData[1]); BReadDeviceInfo(ctx); } @@ -1562,8 +1602,8 @@ static bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys ctx->m_nCurrentInputMode = ctx->m_nInitialInputMode; // Initialize rumble data - SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); - SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); + SetNeutralRumble(device, &ctx->m_RumblePacket.rumbleData[0]); + SetNeutralRumble(device, &ctx->m_RumblePacket.rumbleData[1]); if (!device->is_bluetooth) { if (!BTrySetupUSB(ctx)) { @@ -1583,7 +1623,8 @@ static bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_NESRight && ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SNES && ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_N64 && - ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SEGA_Genesis) { + ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SEGA_Genesis && + !(device->vendor_id == USB_VENDOR_PDP && device->product_id == USB_PRODUCT_PDP_REALMZ_WIRELESS)) { if (LoadIMUCalibration(ctx)) { ctx->m_bSensorsSupported = true; } @@ -1660,11 +1701,11 @@ static bool HIDAPI_DriverSwitch_ActuallyRumbleJoystick(SDL_DriverSwitch_Context const Uint16 k_usLowFreqAmp = EncodeRumbleLowAmplitude(low_frequency_rumble); if (low_frequency_rumble || high_frequency_rumble) { - EncodeRumble(&ctx->m_RumblePacket.rumbleData[0], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp); - EncodeRumble(&ctx->m_RumblePacket.rumbleData[1], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp); + EncodeRumble(ctx->device, &ctx->m_RumblePacket.rumbleData[0], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp); + EncodeRumble(ctx->device, &ctx->m_RumblePacket.rumbleData[1], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp); } else { - SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); - SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); + SetNeutralRumble(ctx->device, &ctx->m_RumblePacket.rumbleData[0]); + SetNeutralRumble(ctx->device, &ctx->m_RumblePacket.rumbleData[1]); } ctx->m_bRumbleActive = (low_frequency_rumble || high_frequency_rumble); @@ -1771,16 +1812,29 @@ static Uint32 HIDAPI_DriverSwitch_GetJoystickCapabilities(SDL_HIDAPI_Device *dev if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_ProController && !ctx->m_bInputOnly) { // Doesn't have an RGB LED, so don't return SDL_JOYSTICK_CAP_RGB_LED here result |= SDL_JOYSTICK_CAP_RUMBLE; + // But has the HOME LED, so treat it like a mono LED + result |= SDL_JOYSTICK_CAP_MONO_LED; } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft || ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { result |= SDL_JOYSTICK_CAP_RUMBLE; + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + result |= SDL_JOYSTICK_CAP_MONO_LED; // Right JoyCon also have the HOME LED + } } return result; } static bool HIDAPI_DriverSwitch_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) { - return SDL_Unsupported(); + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; + + if (!(ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_ProController && !ctx->m_bInputOnly) && + ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_JoyConRight) { + return SDL_Unsupported(); + } + + int value = (int)((SDL_max(red, SDL_max(green, blue)) / 255.0f) * 100.0f); // The colors are received between 0-255 and we need them to be 0-100 + return SetHomeLED(ctx, (Uint8)value); } static bool HIDAPI_DriverSwitch_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) @@ -2572,26 +2626,39 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C * LSB of the battery nibble is used to report charging. * The battery level is reported from 0(empty)-8(full) */ - SDL_PowerState state; int charging = (packet->controllerState.ucBatteryAndConnection & 0x10); int level = (packet->controllerState.ucBatteryAndConnection & 0xE0) >> 4; - int percent = (int)SDL_roundf((level / 8.0f) * 100.0f); - if (charging) { if (level == 8) { - state = SDL_POWERSTATE_CHARGED; + ctx->m_ePowerState = SDL_POWERSTATE_CHARGED; } else { - state = SDL_POWERSTATE_CHARGING; + ctx->m_ePowerState = SDL_POWERSTATE_CHARGING; } } else { - state = SDL_POWERSTATE_ON_BATTERY; + ctx->m_ePowerState = SDL_POWERSTATE_ON_BATTERY; + } + ctx->m_nPowerPercent = (int)SDL_roundf((level / 8.0f) * 100.0f); + + if (!ctx->device->parent) { + SDL_PowerState state = ctx->m_ePowerState; + int percent = ctx->m_nPowerPercent; + SDL_SendJoystickPowerInfo(joystick, state, percent); + } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + SDL_DriverSwitch_Context *other = (SDL_DriverSwitch_Context *)ctx->device->parent->children[0]->context; + SDL_PowerState state = (SDL_PowerState)SDL_min(ctx->m_ePowerState, other->m_ePowerState); + int percent = SDL_min(ctx->m_nPowerPercent, other->m_nPowerPercent); + SDL_SendJoystickPowerInfo(joystick, state, percent); } - SDL_SendJoystickPowerInfo(joystick, state, percent); if (ctx->m_bReportSensors) { - bool bHasSensorData = (packet->imuState[0].sAccelZ != 0 || - packet->imuState[0].sAccelY != 0 || - packet->imuState[0].sAccelX != 0); + // Need to copy the imuState to an aligned variable + SwitchControllerIMUState_t imuState[3]; + SDL_COMPILE_TIME_ASSERT(imuState_size, sizeof(imuState) == sizeof(packet->imuState)); + SDL_memcpy(imuState, packet->imuState, sizeof(packet->imuState)); + + bool bHasSensorData = (imuState[0].sAccelZ != 0 || + imuState[0].sAccelY != 0 || + imuState[0].sAccelX != 0); if (bHasSensorData) { const Uint32 IMU_UPDATE_RATE_SAMPLE_FREQUENCY = 1000; Uint64 sensor_timestamp[3]; @@ -2620,37 +2687,37 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C if (!ctx->device->parent || ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[0], &packet->imuState[2].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[0], &packet->imuState[2].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[0], &imuState[2].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[0], &imuState[2].sAccelX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[1], &packet->imuState[1].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[1], &packet->imuState[1].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[1], &imuState[1].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[1], &imuState[1].sAccelX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[2], &packet->imuState[0].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[2], &packet->imuState[0].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[2], &imuState[0].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[2], &imuState[0].sAccelX); } if (ctx->device->parent && ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[0], &packet->imuState[2].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[0], &packet->imuState[2].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[0], &imuState[2].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[0], &imuState[2].sAccelX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[1], &packet->imuState[1].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[1], &packet->imuState[1].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[1], &imuState[1].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[1], &imuState[1].sAccelX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[2], &packet->imuState[0].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[2], &packet->imuState[0].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[2], &imuState[0].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[2], &imuState[0].sAccelX); } if (ctx->device->parent && ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[0], &packet->imuState[2].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[0], &packet->imuState[2].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[0], &imuState[2].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[0], &imuState[2].sAccelX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[1], &packet->imuState[1].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[1], &packet->imuState[1].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[1], &imuState[1].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[1], &imuState[1].sAccelX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[2], &packet->imuState[0].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[2], &packet->imuState[0].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[2], &imuState[0].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[2], &imuState[0].sAccelX); } } else if (ctx->m_bHasSensorData) { diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch2.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch2.c new file mode 100644 index 0000000..7a43156 --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch2.c @@ -0,0 +1,1339 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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. +*/ +/* This driver supports the Nintendo Switch Pro controller. + Code and logic contributed by Valve Corporation under the SDL zlib license. +*/ +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../../misc/SDL_libusb.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_SWITCH2 + +#define RUMBLE_INTERVAL 12 +#define RUMBLE_MAX 29000 + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_SWITCH2_PROTOCOL +#endif + +enum +{ + SDL_GAMEPAD_BUTTON_SWITCH2_PRO_SHARE = 11, + SDL_GAMEPAD_BUTTON_SWITCH2_PRO_C, + SDL_GAMEPAD_BUTTON_SWITCH2_PRO_RIGHT_PADDLE, + SDL_GAMEPAD_BUTTON_SWITCH2_PRO_LEFT_PADDLE, + SDL_GAMEPAD_NUM_SWITCH2_PRO_BUTTONS +}; + +enum +{ + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_SHARE = 11, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_C, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_RIGHT_PADDLE1, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_LEFT_PADDLE1, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_RIGHT_PADDLE2, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_LEFT_PADDLE2, + SDL_GAMEPAD_NUM_SWITCH2_JOYCON_BUTTONS +}; + +enum +{ + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_GUIDE = 4, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_START, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_LEFT_SHOULDER, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_RIGHT_SHOULDER, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_SHARE, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_C, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_LEFT_TRIGGER, // Full trigger pull click + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_RIGHT_TRIGGER, // Full trigger pull click + SDL_GAMEPAD_NUM_SWITCH2_GAMECUBE_BUTTONS +}; + +typedef struct +{ + Uint16 neutral; + Uint16 max; + Uint16 min; +} Switch2_AxisCalibration; + +typedef struct +{ + Switch2_AxisCalibration x; + Switch2_AxisCalibration y; +} Switch2_StickCalibration; + +typedef struct +{ + SDL_HIDAPI_Device *device; + SDL_Joystick *joystick; + + SDL_LibUSBContext *libusb; + libusb_device_handle *device_handle; + bool interface_claimed; + Uint8 interface_number; + Uint8 out_endpoint; + Uint8 in_endpoint; + + Uint64 rumble_timestamp; + Uint32 rumble_seq; + Uint16 rumble_hi_amp; + Uint16 rumble_hi_freq; + Uint16 rumble_lo_amp; + Uint16 rumble_lo_freq; + Uint32 rumble_error; + bool rumble_updated; + + Switch2_StickCalibration left_stick; + Switch2_StickCalibration right_stick; + Uint8 left_trigger_zero; + Uint8 right_trigger_zero; + + float gyro_bias_x; + float gyro_bias_y; + float gyro_bias_z; + float accel_bias_x; + float accel_bias_y; + float accel_bias_z; + bool sensors_enabled; + bool sensors_ready; + int sample_count; + Uint64 first_sensor_timestamp; + Uint64 sensor_ts_coeff; + float gyro_coeff; + + bool player_lights; + int player_index; + + bool vertical_mode; + Uint8 last_state[USB_PACKET_LENGTH]; +} SDL_DriverSwitch2_Context; + +static void ParseStickCalibration(Switch2_StickCalibration *stick_data, const Uint8 *data) +{ + stick_data->x.neutral = data[0]; + stick_data->x.neutral |= (data[1] & 0x0F) << 8; + + stick_data->y.neutral = data[1] >> 4; + stick_data->y.neutral |= data[2] << 4; + + stick_data->x.max = data[3]; + stick_data->x.max |= (data[4] & 0x0F) << 8; + + stick_data->y.max = data[4] >> 4; + stick_data->y.max |= data[5] << 4; + + stick_data->x.min = data[6]; + stick_data->x.min |= (data[7] & 0x0F) << 8; + + stick_data->y.min = data[7] >> 4; + stick_data->y.min |= data[8] << 4; +} + +static int SendBulkData(SDL_DriverSwitch2_Context *ctx, const Uint8 *data, unsigned size) +{ + int transferred; + int res = ctx->libusb->bulk_transfer(ctx->device_handle, + ctx->out_endpoint, + (Uint8 *)data, + size, + &transferred, + 1000); + if (res < 0) { + return res; + } + return transferred; +} + +static int RecvBulkData(SDL_DriverSwitch2_Context *ctx, Uint8 *data, unsigned size) +{ + int transferred; + int total_transferred = 0; + int res; + + while (size > 0) { + unsigned current_read = size; + if (current_read > 64) { + current_read = 64; + } + res = ctx->libusb->bulk_transfer(ctx->device_handle, + ctx->in_endpoint, + data, + current_read, + &transferred, + 100); + if (res < 0) { + return res; + } + total_transferred += transferred; + size -= transferred; + data += current_read; + if ((unsigned) transferred < current_read) { + break; + } + } + + return total_transferred; +} + +static void MapJoystickAxis(Uint64 timestamp, SDL_Joystick *joystick, Uint8 axis, const Switch2_AxisCalibration *calib, float value, bool invert) +{ + Sint16 mapped_value; + if (calib && calib->neutral && calib->min && calib->max) { + value -= calib->neutral; + if (value < 0) { + value /= calib->min; + } else { + value /= calib->max; + } + mapped_value = (Sint16) SDL_clamp(value * SDL_MAX_SINT16, SDL_MIN_SINT16, SDL_MAX_SINT16); + } else { + mapped_value = (Sint16) HIDAPI_RemapVal(value, 0, 4096, SDL_MIN_SINT16, SDL_MAX_SINT16); + } + if (invert) { + mapped_value = ~mapped_value; + } + SDL_SendJoystickAxis(timestamp, joystick, axis, mapped_value); +} + +static void MapTriggerAxis(Uint64 timestamp, SDL_Joystick *joystick, Uint8 axis, Uint8 max, float value) +{ + Sint16 mapped_value = (Sint16) HIDAPI_RemapVal( + SDL_clamp((value - max) / (232.f - max), 0, 1), + 0, 1, + SDL_MIN_SINT16, SDL_MAX_SINT16 + ); + SDL_SendJoystickAxis(timestamp, joystick, axis, mapped_value); +} + +static bool UpdateSlotLED(SDL_DriverSwitch2_Context *ctx) +{ + Uint8 set_led_data[] = { + 0x09, 0x91, 0x00, 0x07, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + Uint8 reply[8] = {0}; + const Uint8 player_pattern[] = { 0x1, 0x3, 0x7, 0xf, 0x9, 0x5, 0xd, 0x6 }; + + if (ctx->player_lights && ctx->player_index >= 0) { + set_led_data[8] = player_pattern[ctx->player_index % 8]; + } + int res = SendBulkData(ctx, set_led_data, sizeof(set_led_data)); + if (res < 0) { + return SDL_SetError("Couldn't set LED data: %d\n", res); + } + return (RecvBulkData(ctx, reply, sizeof(reply)) > 0); +} + +static int ReadFlashBlock(SDL_DriverSwitch2_Context *ctx, Uint32 address, Uint8 *out) +{ + Uint8 flash_read_command[] = { + 0x02, 0x91, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + Uint8 buffer[0x50] = {0}; + int res; + + flash_read_command[12] = (Uint8)address; + flash_read_command[13] = (Uint8)(address >> 8); + flash_read_command[14] = (Uint8)(address >> 16); + flash_read_command[15] = (Uint8)(address >> 24); + + res = SendBulkData(ctx, flash_read_command, sizeof(flash_read_command)); + if (res < 0) { + return res; + } + + res = RecvBulkData(ctx, buffer, sizeof(buffer)); + if (res < 0) { + return res; + } + + SDL_memcpy(out, &buffer[0x10], 0x40); + return 0; +} + +static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)userdata; + bool player_lights = SDL_GetStringBoolean(hint, true); + + if (player_lights != ctx->player_lights) { + ctx->player_lights = player_lights; + + UpdateSlotLED(ctx); + HIDAPI_UpdateDeviceProperties(ctx->device); + } +} + +static void HIDAPI_DriverSwitch2_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, callback, userdata); +} + +static void HIDAPI_DriverSwitch2_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, callback, userdata); +} + +static bool HIDAPI_DriverSwitch2_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverSwitch2_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (vendor_id == USB_VENDOR_NINTENDO) { + switch (product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER: + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT: + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT: + case USB_PRODUCT_NINTENDO_SWITCH2_PRO: + return true; + } + } + + return false; +} + +static bool HIDAPI_DriverSwitch2_InitBluetooth(SDL_HIDAPI_Device *device) +{ + // FIXME: Need to add Bluetooth support + return SDL_SetError("Nintendo Switch2 controllers not supported over Bluetooth"); +} + +static bool FindBulkEndpoints(SDL_LibUSBContext *libusb, libusb_device_handle *handle, Uint8 *bInterfaceNumber, Uint8 *out_endpoint, Uint8 *in_endpoint) +{ + struct libusb_config_descriptor *config; + int found = 0; + + if (libusb->get_config_descriptor(libusb->get_device(handle), 0, &config) != 0) { + return false; + } + + for (int i = 0; i < config->bNumInterfaces; i++) { + const struct libusb_interface *iface = &config->interface[i]; + for (int j = 0; j < iface->num_altsetting; j++) { + const struct libusb_interface_descriptor *altsetting = &iface->altsetting[j]; + if (altsetting->bInterfaceNumber == 1) { + for (int k = 0; k < altsetting->bNumEndpoints; k++) { + const struct libusb_endpoint_descriptor *ep = &altsetting->endpoint[k]; + if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK) { + *bInterfaceNumber = altsetting->bInterfaceNumber; + if ((ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT) { + *out_endpoint = ep->bEndpointAddress; + found |= 1; + } + if ((ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) { + *in_endpoint = ep->bEndpointAddress; + found |= 2; + } + if (found == 3) { + libusb->free_config_descriptor(config); + return true; + } + } + } + } + } + } + libusb->free_config_descriptor(config); + return false; +} + +static bool HIDAPI_DriverSwitch2_InitUSB(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + if (!SDL_InitLibUSB(&ctx->libusb)) { + return false; + } + + ctx->device_handle = (libusb_device_handle *)SDL_GetPointerProperty(SDL_hid_get_properties(device->dev), SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER, NULL); + if (!ctx->device_handle) { + return SDL_SetError("Couldn't get libusb device handle"); + } + + if (!FindBulkEndpoints(ctx->libusb, ctx->device_handle, &ctx->interface_number, &ctx->out_endpoint, &ctx->in_endpoint)) { + return SDL_SetError("Couldn't find bulk endpoints"); + } + + ctx->libusb->set_auto_detach_kernel_driver(ctx->device_handle, true); + int res = ctx->libusb->claim_interface(ctx->device_handle, ctx->interface_number); + if (res < 0) { + return SDL_SetError("Couldn't claim interface %d: %d\n", ctx->interface_number, res); + } + ctx->interface_claimed = true; + + const Uint8 *init_sequence[] = { + (Uint8[]) { // Unknown purpose + 0x7, 0x91, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, + }, + (Uint8[]) { // Set feature output bit mask + 0x0c, 0x91, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00 + }, + (Uint8[]) { // Unknown purpose + 0x11, 0x91, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, + }, + (Uint8[]) { // Set rumble data? + 0x0a, 0x91, 0x00, 0x08, 0x00, 0x14, 0x00, 0x00, + 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x35, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }, + (Uint8[]) { // Enable feature output bits + 0x0c, 0x91, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00 + }, + (Uint8[]) { // Unknown purpose + 0x01, 0x91, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, + }, + (Uint8[]) { // Enable rumble + 0x01, 0x91, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, + }, + (Uint8[]) { // Enable grip buttons on charging grip + 0x8, 0x91, 0x0, 0x2, 0x0, 0x4, 0x0, 0x0, 0x01, 0x0, 0x0, 0x0, + }, + (Uint8[]) { // Set report format + 0x03, 0x91, 0x00, 0x0a, 0x00, 0x04, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00 + }, + (Uint8[]) { // Start output + 0x03, 0x91, 0x00, 0x0d, 0x00, 0x08, 0x00, 0x00, + 0x01, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }, + NULL, // Sentinel + }; + + unsigned char calibration_data[0x40] = {0}; + + res = ReadFlashBlock(ctx, 0x13000, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read serial number: %d", res); + } else { + char serial[0x11] = {0}; + SDL_strlcpy(serial, (char*)&calibration_data[2], sizeof(serial)); + HIDAPI_SetDeviceSerial(device, serial); + } + + res = ReadFlashBlock(ctx, 0x13040, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read factory calibration data: %d", res); + } else { + ctx->gyro_bias_x = *(float*)&calibration_data[4]; + ctx->gyro_bias_y = *(float*)&calibration_data[8]; + ctx->gyro_bias_z = *(float*)&calibration_data[12]; + } + + res = ReadFlashBlock(ctx, 0x13080, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read factory calibration data: %d", res); + } else { + ParseStickCalibration(&ctx->left_stick, &calibration_data[0x28]); + } + + res = ReadFlashBlock(ctx, 0x130C0, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read factory calibration data: %d", res); + } else { + ParseStickCalibration(&ctx->right_stick, &calibration_data[0x28]); + } + + res = ReadFlashBlock(ctx, 0x13100, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read factory calibration data: %d", res); + } else { + ctx->accel_bias_x = *(float*)&calibration_data[12]; + ctx->accel_bias_y = *(float*)&calibration_data[16]; + ctx->accel_bias_z = *(float*)&calibration_data[20]; + } + + if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER) { + res = ReadFlashBlock(ctx, 0x13140, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read factory calibration data: %d", res); + } else { + ctx->left_trigger_zero = calibration_data[0]; + ctx->right_trigger_zero = calibration_data[1]; + } + } + + res = ReadFlashBlock(ctx, 0x1FC040, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read user calibration data: %d", res); + } else if (calibration_data[0] == 0xb2 && calibration_data[1] == 0xa1) { + ParseStickCalibration(&ctx->left_stick, &calibration_data[2]); + } + + res = ReadFlashBlock(ctx, 0x1FC080, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read user calibration data: %d", res); + } else if (calibration_data[0] == 0xb2 && calibration_data[1] == 0xa1) { + ParseStickCalibration(&ctx->right_stick, &calibration_data[2]); + } + + for (int i = 0; init_sequence[i]; i++) { + res = SendBulkData(ctx, init_sequence[i], init_sequence[i][5] + 8); + if (res < 0) { + return SDL_SetError("Couldn't send initialization data: %d\n", res); + } + RecvBulkData(ctx, calibration_data, 0x40); + } + + return true; +} + +static bool HIDAPI_DriverSwitch2_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch2_Context *ctx; + + ctx = (SDL_DriverSwitch2_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + device->context = ctx; + + if (device->is_bluetooth) { + if (!HIDAPI_DriverSwitch2_InitBluetooth(device)) { + return false; + } + } else { + if (!HIDAPI_DriverSwitch2_InitUSB(device)) { + return false; + } + } + + ctx->sensor_ts_coeff = 10000; + ctx->gyro_coeff = 34.8f; + + // Sometimes the device handle isn't available during enumeration so we don't get the device name, so set it explicitly + switch (device->product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER: + HIDAPI_SetDeviceName(device, "Nintendo GameCube Controller"); + break; + case USB_PRODUCT_NINTENDO_SWITCH2_PRO: + HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller"); + break; + default: + break; + } + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverSwitch2_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverSwitch2_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + if (!ctx->joystick) { + return; + } + + ctx->player_index = player_index; + + UpdateSlotLED(ctx); +} + +static bool HIDAPI_DriverSwitch2_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + ctx->joystick = joystick; + + // Initialize player index (needed for setting LEDs) + ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); + ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, true); + UpdateSlotLED(ctx); + + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + + // Initialize the joystick capabilities + if (!ctx->device->parent) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); + } + switch (device->product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER: + joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH2_GAMECUBE_BUTTONS; + break; + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT: + if (ctx->device->parent) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO_L, 250.0f); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_L, 250.0f); + } + joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH2_JOYCON_BUTTONS; + break; + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT: + if (ctx->device->parent) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO_R, 250.0f); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_R, 250.0f); + } + joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH2_JOYCON_BUTTONS; + break; + case USB_PRODUCT_NINTENDO_SWITCH2_PRO: + joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH2_PRO_BUTTONS; + break; + default: + // FIXME: How many buttons does this have? + break; + } + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + ctx->rumble_hi_freq = 0x187; + ctx->rumble_lo_freq = 0x112; + + // Set up for vertical mode + ctx->vertical_mode = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false); + + return true; +} + +static bool HIDAPI_DriverSwitch2_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + if (low_frequency_rumble != ctx->rumble_lo_amp || high_frequency_rumble != ctx->rumble_hi_amp) { + ctx->rumble_lo_amp = low_frequency_rumble; + ctx->rumble_hi_amp = high_frequency_rumble; + ctx->rumble_updated = true; + } + + return true; +} + +static bool HIDAPI_DriverSwitch2_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverSwitch2_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + Uint32 result = SDL_JOYSTICK_CAP_RUMBLE; + + if (ctx->player_lights) { + result |= SDL_JOYSTICK_CAP_PLAYER_LED; + } + return result; +} + +static bool HIDAPI_DriverSwitch2_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSwitch2_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSwitch2_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + if (ctx->sensors_ready) { + Uint8 data[] = { + 0x0c, 0x91, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00 + }; + unsigned char reply[12] = {0}; + + if (enabled) { + data[8] |= 4; + } + int res = SendBulkData(ctx, data, sizeof(data)); + if (res < 0) { + return SDL_SetError("Couldn't set sensors enabled: %d\n", res); + } + RecvBulkData(ctx, reply, sizeof(reply)); + } + ctx->sensors_enabled = true; + return true; +} + +static void HandleGameCubeState(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + + if (data[5] != ctx->last_state[5]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[5] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_RIGHT_TRIGGER, ((data[5] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_RIGHT_SHOULDER, ((data[5] & 0x80) != 0)); + } + + if (data[6] != ctx->last_state[6]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_START, ((data[6] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_GUIDE, ((data[6] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_SHARE, ((data[6] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_C, ((data[6] & 0x40) != 0)); + } + + if (data[7] != ctx->last_state[7]) { + Uint8 hat = 0; + + if (data[7] & 0x01) { + hat |= SDL_HAT_DOWN; + } + if (data[7] & 0x02) { + hat |= SDL_HAT_UP; + } + if (data[7] & 0x04) { + hat |= SDL_HAT_RIGHT; + } + if (data[7] & 0x08) { + hat |= SDL_HAT_LEFT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_LEFT_TRIGGER, ((data[7] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_LEFT_SHOULDER, ((data[7] & 0x80) != 0)); + } + + MapTriggerAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFT_TRIGGER, + ctx->left_trigger_zero, + data[61] + ); + MapTriggerAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, + ctx->right_trigger_zero, + data[62] + ); + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.x, + (float) (data[11] | ((data[12] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.y, + (float) ((data[12] >> 4) | (data[13] << 4)), + true + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTX, + &ctx->right_stick.x, + (float) (data[14] | ((data[15] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTY, + &ctx->right_stick.y, + (float)((data[15] >> 4) | (data[16] << 4)), + true + ); +} + +static void HandleCombinedControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + // FIXME: When we find out what the SL and SR buttons are, map them to paddles + + if (data[6] != ctx->last_state[6]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[6] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[6] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_SHARE, ((data[6] & 0x20) != 0)); + } + + if (data[7] != ctx->last_state[7]) { + Uint8 hat = 0; + + if (data[7] & 0x01) { + hat |= SDL_HAT_DOWN; + } + if (data[7] & 0x02) { + hat |= SDL_HAT_UP; + } + if (data[7] & 0x04) { + hat |= SDL_HAT_RIGHT; + } + if (data[7] & 0x08) { + hat |= SDL_HAT_LEFT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[7] & 0x40) != 0)); + } + + if (data[8] != ctx->last_state[8]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_LEFT_PADDLE1, ((data[8] & 0x02) != 0)); + } + + Sint16 axis = (data[7] & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.x, + (float) (data[11] | ((data[12] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.y, + (float) ((data[12] >> 4) | (data[13] << 4)), + true + ); +} + +static void HandleMiniControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + // FIXME: When we find out what the SL and SR buttons are, map them to shoulder buttons + + if (data[6] != ctx->last_state[6]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[6] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[6] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_SHARE, ((data[6] & 0x10) != 0)); + } + + if (data[7] != ctx->last_state[7]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[7] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[7] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[7] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[7] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_LEFT_PADDLE1, ((data[7] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_LEFT_PADDLE2, ((data[7] & 0x80) != 0)); + } + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.y, + (float) ((data[12] >> 4) | (data[13] << 4)), + true + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.x, + (float) (data[11] | ((data[12] & 0x0F) << 8)), + true + ); +} + +static void HandleCombinedControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + // FIXME: When we find out what the SL and SR buttons are, map them to paddles + + if (data[5] != ctx->last_state[5]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[5] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[5] & 0x40) != 0)); + } + + if (data[6] != ctx->last_state[6]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[6] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[6] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_C, ((data[6] & 0x40) != 0)); + } + + if (data[8] != ctx->last_state[8]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_RIGHT_PADDLE1, ((data[8] & 0x01) != 0)); + } + + Sint16 axis = (data[5] & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTX, + &ctx->left_stick.x, + (float) (data[14] | ((data[15] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTY, + &ctx->left_stick.y, + (float)((data[15] >> 4) | (data[16] << 4)), + true + ); +} + +static void HandleMiniControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + // FIXME: When we find out what the SL and SR buttons are, map them to shoulder buttons + + if (data[5] != ctx->last_state[5]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[5] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_RIGHT_PADDLE1, ((data[5] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_RIGHT_PADDLE2, ((data[5] & 0x80) != 0)); + } + + if (data[6] != ctx->last_state[6]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[6] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[6] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_C, ((data[6] & 0x40) != 0)); + } + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.y, + (float)((data[15] >> 4) | (data[16] << 4)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.x, + (float) (data[14] | ((data[15] & 0x0F) << 8)), + false + ); +} + +static void HandleSwitchProState(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + + if (data[5] != ctx->last_state[5]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[5] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[5] & 0x40) != 0)); + } + + if (data[6] != ctx->last_state[6]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[6] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[6] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[6] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[6] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_PRO_SHARE, ((data[6] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_PRO_C, ((data[6] & 0x40) != 0)); + } + + if (data[7] != ctx->last_state[7]) { + Uint8 hat = 0; + + if (data[7] & 0x01) { + hat |= SDL_HAT_DOWN; + } + if (data[7] & 0x02) { + hat |= SDL_HAT_UP; + } + if (data[7] & 0x04) { + hat |= SDL_HAT_RIGHT; + } + if (data[7] & 0x08) { + hat |= SDL_HAT_LEFT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[7] & 0x40) != 0)); + } + + if (data[8] != ctx->last_state[8]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_PRO_RIGHT_PADDLE, ((data[8] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_PRO_LEFT_PADDLE, ((data[8] & 0x02) != 0)); + } + + axis = (data[5] & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + axis = (data[7] & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.x, + (float) (data[11] | ((data[12] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.y, + (float) ((data[12] >> 4) | (data[13] << 4)), + true + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTX, + &ctx->right_stick.x, + (float) (data[14] | ((data[15] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTY, + &ctx->right_stick.y, + (float)((data[15] >> 4) | (data[16] << 4)), + true + ); +} + +static void EncodeHDRumble(Uint16 high_freq, Uint16 high_amp, Uint16 low_freq, Uint16 low_amp, Uint8 rumble_data[5]) +{ + rumble_data[0] = (Uint8)(high_freq & 0xFF); + rumble_data[1] = (Uint8)(((high_amp >> 4) & 0xfc) | ((high_freq >> 8) & 0x03)); + rumble_data[2] = (Uint8)((high_amp >> 12) | (low_freq << 4)); + rumble_data[3] = (Uint8)((low_amp & 0xc0) | ((low_freq >> 4) & 0x3f)); + rumble_data[4] = (Uint8)(low_amp >> 8); +} + +static bool UpdateRumble(SDL_DriverSwitch2_Context *ctx) +{ + if (!ctx->rumble_updated && !ctx->rumble_lo_amp && !ctx->rumble_hi_amp) { + return true; + } + + Uint64 timestamp = SDL_GetTicks(); + Uint64 interval = RUMBLE_INTERVAL; + + if (timestamp < ctx->rumble_timestamp) { + return true; + } + + if (!SDL_HIDAPI_LockRumble()) { + return false; + } + + unsigned char rumble_data[64] = {0}; + if (ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER) { + Uint16 rumble_max = SDL_max(ctx->rumble_lo_amp, ctx->rumble_hi_amp); + rumble_data[0x00] = 0x3; + rumble_data[1] = 0x50 | (ctx->rumble_seq & 0xf); + if (rumble_max == 0) { + rumble_data[2] = 2; + ctx->rumble_error = 0; + } else { + if (ctx->rumble_error < rumble_max) { + rumble_data[2] = 1; + ctx->rumble_error += UINT16_MAX - rumble_max; + } else { + rumble_data[2] = 0; + ctx->rumble_error -= rumble_max; + } + } + } else { + // Rumble can get so strong that it might be dangerous to the controller... + // This is a game controller, not a massage device, so let's clamp it somewhat + Uint16 low_amp = (Uint16)((int)ctx->rumble_lo_amp * RUMBLE_MAX / UINT16_MAX); + Uint16 high_amp = (Uint16)((int)ctx->rumble_hi_amp * RUMBLE_MAX / UINT16_MAX); + rumble_data[0x01] = 0x50 | (ctx->rumble_seq & 0xf); + EncodeHDRumble(ctx->rumble_hi_freq, high_amp, ctx->rumble_lo_freq, low_amp, &rumble_data[0x02]); + switch (ctx->device->product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT: + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT: + if (ctx->device->parent) { + // FIXME: This shouldn't be necessary, but the rumble thread appears to back up if we don't do this + interval *= 2; + } + rumble_data[0] = 0x1; + break; + case USB_PRODUCT_NINTENDO_SWITCH2_PRO: + rumble_data[0] = 0x2; + SDL_memcpy(&rumble_data[0x11], &rumble_data[0x01], 6); + break; + } + } + ctx->rumble_seq++; + ctx->rumble_updated = false; + if (!ctx->rumble_lo_amp && !ctx->rumble_hi_amp) { + ctx->rumble_timestamp = 0; + } else { + if (!ctx->rumble_timestamp) { + ctx->rumble_timestamp = timestamp; + } + ctx->rumble_timestamp += interval; + } + + if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, rumble_data, sizeof(rumble_data)) != sizeof(rumble_data)) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; +} + +static void HIDAPI_DriverSwitch2_HandleStatePacket(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + Uint64 timestamp = SDL_GetTicksNS(); + + if (size < 64) { + // We don't know how to handle this report + return; + } + + switch (device->product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER: + HandleGameCubeState(timestamp, joystick, ctx, data, size); + break; + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT: + if (device->parent || ctx->vertical_mode) { + HandleCombinedControllerStateL(timestamp, joystick, ctx, data, size); + } else { + HandleMiniControllerStateL(timestamp, joystick, ctx, data, size); + } + break; + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT: + if (device->parent || ctx->vertical_mode) { + HandleCombinedControllerStateR(timestamp, joystick, ctx, data, size); + } else { + HandleMiniControllerStateR(timestamp, joystick, ctx, data, size); + } + break; + case USB_PRODUCT_NINTENDO_SWITCH2_PRO: + HandleSwitchProState(timestamp, joystick, ctx, data, size); + break; + default: + // FIXME: Need state handling implementation + break; + } + + Uint64 sensor_timestamp = (Uint32) (data[0x2b] | (data[0x2c] << 8U) | (data[0x2d] << 16U) | (data[0x2e] << 24U)); + if (sensor_timestamp && !ctx->sensors_ready) { + ctx->sample_count++; + if (ctx->sample_count >= 5 && !ctx->first_sensor_timestamp) { + ctx->first_sensor_timestamp = sensor_timestamp; + ctx->sample_count = 0; + } else if (ctx->sample_count == 100) { + // Calculate timestamp coefficient + // Timestamp are normally microseconds but sometimes it's something else for no apparent reason + Uint64 coeff = 1000 * (sensor_timestamp - ctx->first_sensor_timestamp) / (ctx->sample_count * 4); + if ((coeff + 100000) / 200000 == 5) { + // Within 10% of 1000 + ctx->sensor_ts_coeff = 10000; + ctx->gyro_coeff = 34.8f; + ctx->sensors_ready = true; + } else if (coeff != 0) { + ctx->sensor_ts_coeff = 10000000000 / coeff; + ctx->gyro_coeff = 40.0f; + ctx->sensors_ready = true; + } else { + // Didn't get a valid reading, try again + ctx->first_sensor_timestamp = 0; + ctx->sample_count = 0; + } + + if (ctx->sensors_ready && !ctx->sensors_enabled) { + Uint8 set_features[] = { + 0x0c, 0x91, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00 + }; + unsigned char reply[12] = {0}; + + SendBulkData(ctx, set_features, sizeof(set_features)); + RecvBulkData(ctx, reply, sizeof(reply)); + } + } + } + if (ctx->sensors_enabled && sensor_timestamp && ctx->sensors_ready) { + sensor_timestamp = sensor_timestamp * ctx->sensor_ts_coeff / 10; + float accel_data[3]; + float gyro_data[3]; + const float g = 9.80665f; + const float accel_scale = g * 8.f / INT16_MAX; + + accel_data[0] = (Sint16)(data[0x31] | (data[0x32] << 8)) * accel_scale; + accel_data[1] = (Sint16)(data[0x35] | (data[0x36] << 8)) * accel_scale; + accel_data[2] = (Sint16)(data[0x33] | (data[0x34] << 8)) * -accel_scale; + + gyro_data[0] = (Sint16)(data[0x37] | (data[0x38] << 8)) * ctx->gyro_coeff / INT16_MAX - ctx->gyro_bias_x; + gyro_data[1] = (Sint16)(data[0x3b] | (data[0x3c] << 8)) * ctx->gyro_coeff / INT16_MAX - ctx->gyro_bias_z; + gyro_data[2] = (Sint16)(data[0x39] | (data[0x3a] << 8)) * -ctx->gyro_coeff / INT16_MAX + ctx->gyro_bias_y; + + switch (ctx->device->product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT: + if (ctx->device->parent) { + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO_L, sensor_timestamp, gyro_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL_L, sensor_timestamp, accel_data, 3); + } else { + float tmp = -accel_data[0]; + accel_data[0] = accel_data[2]; + accel_data[2] = tmp; + + tmp = -gyro_data[0]; + gyro_data[0] = gyro_data[2]; + gyro_data[2] = tmp; + + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, gyro_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, accel_data, 3); + } + break; + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT: + if (ctx->device->parent) { + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, gyro_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, accel_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO_R, sensor_timestamp, gyro_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL_R, sensor_timestamp, accel_data, 3); + } else { + float tmp = accel_data[0]; + accel_data[0] = -accel_data[2]; + accel_data[2] = tmp; + + tmp = gyro_data[0]; + gyro_data[0] = -gyro_data[2]; + gyro_data[2] = tmp; + + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, gyro_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, accel_data, 3); + } + break; + default: + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, gyro_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, accel_data, 3); + break; + } + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverSwitch2_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_SWITCH2_PROTOCOL + if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT) { + HIDAPI_DumpPacket("Nintendo Joy-Con(L) packet: size = %d", data, size); + } else if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT) { + HIDAPI_DumpPacket("Nintendo Joy-Con(R) packet: size = %d", data, size); + } else { + HIDAPI_DumpPacket("Nintendo Switch2 packet: size = %d", data, size); + } +#endif + if (!joystick) { + continue; + } + + HIDAPI_DriverSwitch2_HandleStatePacket(device, joystick, ctx, data, size); + + UpdateRumble(ctx); + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverSwitch2_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + + ctx->joystick = NULL; +} + +static void HIDAPI_DriverSwitch2_FreeDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + if (ctx) { + if (ctx->interface_claimed) { + ctx->libusb->release_interface(ctx->device_handle, ctx->interface_number); + ctx->interface_claimed = false; + } + if (ctx->libusb) { + SDL_QuitLibUSB(); + ctx->libusb = NULL; + } + } +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch2 = { + SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, + true, + HIDAPI_DriverSwitch2_RegisterHints, + HIDAPI_DriverSwitch2_UnregisterHints, + HIDAPI_DriverSwitch2_IsEnabled, + HIDAPI_DriverSwitch2_IsSupportedDevice, + HIDAPI_DriverSwitch2_InitDevice, + HIDAPI_DriverSwitch2_GetDevicePlayerIndex, + HIDAPI_DriverSwitch2_SetDevicePlayerIndex, + HIDAPI_DriverSwitch2_UpdateDevice, + HIDAPI_DriverSwitch2_OpenJoystick, + HIDAPI_DriverSwitch2_RumbleJoystick, + HIDAPI_DriverSwitch2_RumbleJoystickTriggers, + HIDAPI_DriverSwitch2_GetJoystickCapabilities, + HIDAPI_DriverSwitch2_SetJoystickLED, + HIDAPI_DriverSwitch2_SendJoystickEffect, + HIDAPI_DriverSwitch2_SetJoystickSensorsEnabled, + HIDAPI_DriverSwitch2_CloseJoystick, + HIDAPI_DriverSwitch2_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_SWITCH2 + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_wii.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_wii.c index fb3e164..ac2d7b9 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_wii.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_wii.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -512,6 +512,8 @@ static void UpdatePowerLevelWiiU(SDL_Joystick *joystick, Uint8 extensionBatteryB bool pluggedIn = !(extensionBatteryByte & 0x04); Uint8 batteryLevel = extensionBatteryByte >> 4; + SDL_AssertJoysticksLocked(); + if (pluggedIn) { joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED; } else { diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360.c index 49be08a..e140cef 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360w.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360w.c index bf63707..57206bb 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360w.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360w.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xboxone.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xboxone.c index 342eabd..896423f 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xboxone.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xboxone.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -26,6 +26,7 @@ #include "../SDL_sysjoystick.h" #include "SDL_hidapijoystick_c.h" #include "SDL_hidapi_rumble.h" +#include "SDL_report_descriptor.h" #ifdef SDL_JOYSTICK_HIDAPI_XBOXONE @@ -33,7 +34,9 @@ // #define DEBUG_JOYSTICK // Define this if you want to log all packets from the controller -// #define DEBUG_XBOX_PROTOCOL +#if 0 +#define DEBUG_XBOX_PROTOCOL +#endif #if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) #define XBOX_ONE_DRIVER_ACTIVE 1 @@ -132,6 +135,8 @@ typedef struct bool has_unmapped_state; bool has_trigger_rumble; bool has_share_button; + bool has_separate_back_button; + bool has_separate_guide_button; Uint8 last_paddle_state; Uint8 low_frequency_rumble; Uint8 high_frequency_rumble; @@ -140,6 +145,8 @@ typedef struct SDL_XboxOneRumbleState rumble_state; Uint64 rumble_time; bool rumble_pending; + SDL_ReportDescriptor *descriptor; + Uint32 last_buttons; Uint8 last_state[USB_PACKET_LENGTH]; Uint8 *chunk_buffer; Uint32 chunk_length; @@ -266,8 +273,14 @@ static bool SendSerialRequest(SDL_DriverXboxOne_Context *ctx) static bool ControllerSendsAnnouncement(Uint16 vendor_id, Uint16 product_id) { - if (vendor_id == USB_VENDOR_PDP && product_id == 0x0246) { - // The PDP Rock Candy (PID 0x0246) doesn't send the announce packet on Linux for some reason + // The PDP Rock Candy (PID 0x0246) and PowerA Fusion Pro 4 (PID 0x400b) + // don't send the announce packet on Linux for some reason. + // + // Just to be safe and cover future products, we'll always send the startup + // protocol sequence for PDP and PowerA controllers + if (vendor_id == USB_VENDOR_PDP || + vendor_id == USB_VENDOR_POWERA || + vendor_id == USB_VENDOR_POWERA_ALT) { return false; } return true; @@ -350,6 +363,10 @@ static bool HIDAPI_DriverXboxOne_IsEnabled(void) static bool HIDAPI_DriverXboxOne_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) { + static const int LIBUSB_CLASS_VENDOR_SPEC = 0xFF; + static const int XBONE_IFACE_SUBCLASS = 71; + static const int XBONE_IFACE_PROTOCOL = 208; + #if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI) if (!SDL_IsJoystickBluetoothXboxOne(vendor_id, product_id)) { // On macOS we get a shortened version of the real report and @@ -358,6 +375,13 @@ static bool HIDAPI_DriverXboxOne_IsSupportedDevice(SDL_HIDAPI_Device *device, co return false; } #endif + if (interface_class && + (interface_class != LIBUSB_CLASS_VENDOR_SPEC || + interface_subclass != XBONE_IFACE_SUBCLASS || + interface_protocol != XBONE_IFACE_PROTOCOL)) { + // This isn't the Xbox gamepad interface + return false; + } return (type == SDL_GAMEPAD_TYPE_XBOXONE); } @@ -373,6 +397,56 @@ static bool HIDAPI_DriverXboxOne_InitDevice(SDL_HIDAPI_Device *device) device->context = ctx; + Uint8 descriptor[1024]; + int descriptor_len = SDL_hid_get_report_descriptor(device->dev, descriptor, sizeof(descriptor)); + if (descriptor_len > 0) { + HIDAPI_DumpPacket("Xbox One report descriptor: size = %d", descriptor, descriptor_len); + + ctx->descriptor = SDL_ParseReportDescriptor(descriptor, descriptor_len); + if (ctx->descriptor) { + // Collapse the buttons into a single field read + int field_count = ctx->descriptor->field_count; + DescriptorInputField *fields = ctx->descriptor->fields; + int button_count = 0; + for (int i = 0; i < field_count; ++i) { + DescriptorInputField *field = &fields[i]; + if (field->usage == MAKE_USAGE(USB_USAGEPAGE_BUTTON, 1)) { + Uint32 expected_usage = field->usage; + int expected_offset = field->bit_offset; + for (int j = i; j < field_count; ++j) { + DescriptorInputField *other = &fields[j]; + if (other->usage != expected_usage || + other->bit_offset != expected_offset) { + break; + } + + ++button_count; + ++expected_usage; + ++expected_offset; + } + field->bit_size = button_count; + + int next_field = i + button_count; + int fields_left = (field_count - next_field); + SDL_memmove(&fields[i+1], &fields[next_field], (fields_left * sizeof(fields[0]))); + ctx->descriptor->field_count -= (button_count - 1); + break; + } + } + if (!SDL_DescriptorHasUsage(ctx->descriptor, USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_X) || + !SDL_DescriptorHasUsage(ctx->descriptor, USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_Y) || + (button_count != 12 && button_count != 15)) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Xbox report descriptor missing expected usages, ignoring"); + SDL_DestroyDescriptor(ctx->descriptor); + ctx->descriptor = NULL; + } + } else { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't parse Xbox report descriptor: %s", SDL_GetError()); + } + } else { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Xbox report descriptor not available"); + } + ctx->vendor_id = device->vendor_id; ctx->product_id = device->product_id; ctx->start_time = SDL_GetTicks(); @@ -581,6 +655,310 @@ static bool HIDAPI_DriverXboxOne_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *de return SDL_Unsupported(); } +static void HIDAPI_DriverXboxOne_HandleBatteryState(SDL_Joystick *joystick, Uint32 flags) +{ + bool on_usb = (((flags & 0x0C) >> 2) == 0); + SDL_PowerState state; + int percent = 0; + + // Mapped percentage value from: + // https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_getbatterystate + switch (flags & 0x03) { + case 0: + percent = 10; + break; + case 1: + percent = 40; + break; + case 2: + percent = 70; + break; + case 3: + percent = 100; + break; + } + if (on_usb) { + state = SDL_POWERSTATE_CHARGING; + } else { + state = SDL_POWERSTATE_ON_BATTERY; + } + SDL_SendJoystickPowerInfo(joystick, state, percent); +} + +static void HandleDescriptorAxis(Uint64 timestamp, SDL_Joystick *joystick, SDL_GamepadAxis axis, Uint32 value) +{ + Sint16 axis_value = (Sint16)((int)value - 0x8000); + SDL_SendJoystickAxis(timestamp, joystick, axis, axis_value); +} + +static void HandleDescriptorTrigger(Uint64 timestamp, SDL_Joystick *joystick, SDL_GamepadAxis axis, Uint32 value) +{ + Sint16 axis_value = (Sint16)(((int)value * 64) - 32768); + if (axis_value == 32704) { + axis_value = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, axis, axis_value); +} + +static bool HIDAPI_DriverXboxOne_HandleDescriptorReport(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) +{ + const SDL_ReportDescriptor *descriptor = ctx->descriptor; + Uint64 timestamp = SDL_GetTicksNS(); + + // Skip the report ID + const Uint8 report_id = *data; + ++data; + --size; + + for (int i = 0; i < descriptor->field_count; ++i) { + DescriptorInputField *field = &descriptor->fields[i]; + if (field->report_id != report_id) { + continue; + } + + Uint32 value; + if (!SDL_ReadReportData(data, size, field->bit_offset, field->bit_size, &value)) { + continue; + } + + switch (field->usage) { + case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_X): + { + HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, value); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_Y): + { + HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, value); + break; + } + // Some controllers use Z+RZ for the right thumbstick and BRAKE and ACCEL for the left and right triggers + // and others use RX+RY for the right thumbstick and Z and RZ for the left and right triggers + case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_Z): + { + if (field->bit_size == 16) { + HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, value); + } else if (field->bit_size == 10) { + HandleDescriptorTrigger(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, value); + } + break; + } + case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_RX): + { + HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, value); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_RY): + { + HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, value); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_RZ): + { + if (field->bit_size == 16) { + HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, value); + } else if (field->bit_size == 10) { + HandleDescriptorTrigger(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, value); + } + break; + } + case MAKE_USAGE(USB_USAGEPAGE_SIMULATION, USB_USAGE_SIMULATION_BRAKE): + { + HandleDescriptorTrigger(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, value); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_SIMULATION, USB_USAGE_SIMULATION_ACCELERATOR): + { + HandleDescriptorTrigger(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, value); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_HAT): + { + Uint8 hat; + + switch (value) { + case 1: + hat = SDL_HAT_UP; + break; + case 2: + hat = SDL_HAT_RIGHTUP; + break; + case 3: + hat = SDL_HAT_RIGHT; + break; + case 4: + hat = SDL_HAT_RIGHTDOWN; + break; + case 5: + hat = SDL_HAT_DOWN; + break; + case 6: + hat = SDL_HAT_LEFTDOWN; + break; + case 7: + hat = SDL_HAT_LEFT; + break; + case 8: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 1): + { + static const SDL_GamepadButton button_map_12[] = { + // 0x0001 + SDL_GAMEPAD_BUTTON_SOUTH, + // 0x0002 + SDL_GAMEPAD_BUTTON_EAST, + // 0x0004 + SDL_GAMEPAD_BUTTON_WEST, + // 0x0008 + SDL_GAMEPAD_BUTTON_NORTH, + // 0x0010 + SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + // 0x0020 + SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + // 0x0040 + SDL_GAMEPAD_BUTTON_BACK, + // 0x0080 + SDL_GAMEPAD_BUTTON_START, + // 0x0100 + SDL_GAMEPAD_BUTTON_LEFT_STICK, + // 0x0200 + SDL_GAMEPAD_BUTTON_RIGHT_STICK, + // 0x0400 + SDL_GAMEPAD_BUTTON_GUIDE, + // 0x0800 + SDL_GAMEPAD_BUTTON_INVALID, + }; + static const SDL_GamepadButton button_map_15[] = { + // 0x0001 + SDL_GAMEPAD_BUTTON_SOUTH, + // 0x0002 + SDL_GAMEPAD_BUTTON_EAST, + // 0x0004 + SDL_GAMEPAD_BUTTON_INVALID, + // 0x0008 + SDL_GAMEPAD_BUTTON_WEST, + // 0x0010 + SDL_GAMEPAD_BUTTON_NORTH, + // 0x0020 + SDL_GAMEPAD_BUTTON_INVALID, + // 0x0040 + SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + // 0x0080 + SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + // 0x0100 + SDL_GAMEPAD_BUTTON_INVALID, + // 0x0200 + SDL_GAMEPAD_BUTTON_INVALID, + // 0x0400 + SDL_GAMEPAD_BUTTON_BACK, + // 0x0800 + SDL_GAMEPAD_BUTTON_START, + // 0x1000 + SDL_GAMEPAD_BUTTON_GUIDE, + // 0x2000 + SDL_GAMEPAD_BUTTON_LEFT_STICK, + // 0x4000 + SDL_GAMEPAD_BUTTON_RIGHT_STICK, + }; + + if (value == ctx->last_buttons) { + break; + } + ctx->last_buttons = value; + + const SDL_GamepadButton *button_map; + if (field->bit_size == 12) { + button_map = button_map_12; + } else if (field->bit_size == 15) { + button_map = button_map_15; + } else { + // Should never happen + break; + } + for (int button_index = 0; button_index < field->bit_size; ++button_index, value >>= 1) { + SDL_GamepadButton button = button_map[button_index]; + if (button == SDL_GAMEPAD_BUTTON_INVALID) { + continue; + } + if (button == SDL_GAMEPAD_BUTTON_BACK && ctx->has_separate_back_button) { + continue; + } + if (button == SDL_GAMEPAD_BUTTON_GUIDE && ctx->has_separate_guide_button) { + continue; + } + + bool pressed = ((value & 1) != 0); + SDL_SendJoystickButton(timestamp, joystick, button, pressed); + } + break; + } + case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_AC_BACK): + { + bool pressed = (value != 0); + if (pressed) { + ctx->has_separate_back_button = true; + } + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, pressed); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_AC_HOME): + { + bool pressed = (value != 0); + if (pressed) { + ctx->has_separate_guide_button = true; + } + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, pressed); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_RECORD): + { + if (ctx->has_share_button) { + bool pressed = (value != 0); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, pressed); + } + break; + } + case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_ORDER_MOVIE): + { + // This value is the currently selected profile + ctx->has_unmapped_state = (value == 0); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_ASSIGN_SELECTION): + { + if (ctx->has_paddles) { + if (!ctx->has_unmapped_state) { + value = 0; + } + + Uint8 button = (Uint8)(SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON + ctx->has_share_button); // Next available button + SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x1) != 0)); + SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x2) != 0)); + SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x4) != 0)); + SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x8) != 0)); + } + break; + } + case MAKE_USAGE(USB_USAGEPAGE_DEVICE_CONTROLS, USB_USAGE_DEVICE_CONTROLS_BATTERY_STRENGTH): + { + HIDAPI_DriverXboxOne_HandleBatteryState(joystick, value); + break; + } + default: + break; + } + } + return true; +} + /* * The Xbox One Elite controller with 5.13+ firmware sends the unmapped state in a separate packet. * We can use this to send the paddle state when they aren't mapped @@ -810,7 +1188,7 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D } } - axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[2])) * 64) - 32768; + axis = ((int)LOAD16(data[2], data[3]) * 64) - 32768; if (axis == 32704) { axis = 32767; } @@ -819,7 +1197,7 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D } SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); - axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[4])) * 64) - 32768; + axis = ((int)LOAD16(data[4], data[5]) * 64) - 32768; if (axis == -32768 && size == 26 && (data[18] & 0x40)) { axis = 32767; } @@ -828,13 +1206,13 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D } SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); - axis = SDL_Swap16LE(*(Sint16 *)(&data[6])); + axis = LOAD16(data[6], data[7]); SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); - axis = SDL_Swap16LE(*(Sint16 *)(&data[8])); + axis = LOAD16(data[8], data[9]); SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); - axis = SDL_Swap16LE(*(Sint16 *)(&data[10])); + axis = LOAD16(data[10], data[11]); SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); - axis = SDL_Swap16LE(*(Sint16 *)(&data[12])); + axis = LOAD16(data[12], data[13]); SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); @@ -1030,25 +1408,25 @@ static void HIDAPI_DriverXboxOneBluetooth_HandleStatePacket(SDL_Joystick *joysti SDL_SendJoystickHat(timestamp, joystick, 0, hat); } - axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[9])) * 64) - 32768; + axis = ((int)LOAD16(data[9], data[10]) * 64) - 32768; if (axis == 32704) { axis = 32767; } SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); - axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[11])) * 64) - 32768; + axis = ((int)LOAD16(data[11], data[12]) * 64) - 32768; if (axis == 32704) { axis = 32767; } SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); - axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[1])) - 0x8000; + axis = (int)(Uint16)LOAD16(data[1], data[2]) - 0x8000; SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); - axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[3])) - 0x8000; + axis = (int)(Uint16)LOAD16(data[3], data[4]) - 0x8000; SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); - axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[5])) - 0x8000; + axis = (int)(Uint16)LOAD16(data[5], data[6]) - 0x8000; SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); - axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[7])) - 0x8000; + axis = (int)(Uint16)LOAD16(data[7], data[8]) - 0x8000; SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); @@ -1064,33 +1442,7 @@ static void HIDAPI_DriverXboxOneBluetooth_HandleGuidePacket(SDL_Joystick *joysti static void HIDAPI_DriverXboxOneBluetooth_HandleBatteryPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) { - Uint8 flags = data[1]; - bool on_usb = (((flags & 0x0C) >> 2) == 0); - SDL_PowerState state; - int percent = 0; - - // Mapped percentage value from: - // https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_getbatterystate - switch (flags & 0x03) { - case 0: - percent = 10; - break; - case 1: - percent = 40; - break; - case 2: - percent = 70; - break; - case 3: - percent = 100; - break; - } - if (on_usb) { - state = SDL_POWERSTATE_CHARGING; - } else { - state = SDL_POWERSTATE_ON_BATTERY; - } - SDL_SendJoystickPowerInfo(joystick, state, percent); + HIDAPI_DriverXboxOne_HandleBatteryState(joystick, data[1]); } static void HIDAPI_DriverXboxOne_HandleSerialIDPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) @@ -1586,7 +1938,12 @@ static bool HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device) #ifdef DEBUG_XBOX_PROTOCOL HIDAPI_DumpPacket("Xbox One packet: size = %d", data, size); #endif - if (device->is_bluetooth) { + if (ctx->descriptor) { + if (!joystick) { + break; + } + HIDAPI_DriverXboxOne_HandleDescriptorReport(joystick, ctx, data, size); + } else if (device->is_bluetooth) { switch (data[0]) { case 0x01: if (!joystick) { @@ -1645,6 +2002,8 @@ static void HIDAPI_DriverXboxOne_FreeDevice(SDL_HIDAPI_Device *device) { SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; + SDL_DestroyDescriptor(ctx->descriptor); + HIDAPI_GIP_DestroyChunkBuffer(ctx); } diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_zuiki.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_zuiki.c new file mode 100644 index 0000000..18f4162 --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_zuiki.c @@ -0,0 +1,297 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + Copyright (C) 2025 Zuiki Inc. + + 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_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_ZUIKI + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_ZUIKI_PROTOCOL +#endif + +typedef struct +{ + Uint8 last_state[USB_PACKET_LENGTH]; +} SDL_DriverZUIKI_Context; + +static void HIDAPI_DriverZUIKI_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_ZUIKI, callback, userdata); +} + +static void HIDAPI_DriverZUIKI_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_ZUIKI, callback, userdata); +} + +static bool HIDAPI_DriverZUIKI_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_ZUIKI, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverZUIKI_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (vendor_id == USB_VENDOR_ZUIKI) { + switch (product_id) { + case USB_PRODUCT_ZUIKI_MASCON_PRO: + return true; + default: + break; + } + } + return false; +} + +static bool HIDAPI_DriverZUIKI_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverZUIKI_Context *ctx = (SDL_DriverZUIKI_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + device->context = ctx; + + if (device->product_id == USB_PRODUCT_ZUIKI_MASCON_PRO) { + HIDAPI_SetDeviceName(device, "ZUIKI MASCON PRO"); + } + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverZUIKI_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverZUIKI_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +#ifndef DEG2RAD +#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) +#endif + +static bool HIDAPI_DriverZUIKI_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverZUIKI_Context *ctx = (SDL_DriverZUIKI_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + SDL_zeroa(ctx->last_state); + + joystick->nbuttons = 11; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + return true; +} + +static bool HIDAPI_DriverZUIKI_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + Uint8 rumble_packet[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + rumble_packet[4] = low_frequency_rumble >> 8; + rumble_packet[5] = high_frequency_rumble >> 8; + if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; +} + +static bool HIDAPI_DriverZUIKI_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverZUIKI_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + Uint32 caps = 0; + caps |= SDL_JOYSTICK_CAP_RUMBLE; + return caps; +} + +static bool HIDAPI_DriverZUIKI_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverZUIKI_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + if (SDL_HIDAPI_SendRumble(device, data, size) != size) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; +} + +static bool HIDAPI_DriverZUIKI_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void HIDAPI_DriverZUIKI_HandleOldStatePacket(SDL_Joystick *joystick, SDL_DriverZUIKI_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[2] != data[2]) { + Uint8 hat; + + switch (data[2]) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + if (ctx->last_state[0] != data[0]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x20) != 0)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (data[0] & 0x40) ? SDL_MAX_SINT16 : SDL_MIN_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (data[0] & 0x80) ? SDL_MAX_SINT16 : SDL_MIN_SINT16); + } + + if (ctx->last_state[1] != data[1]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, ((data[1] & 0x20) != 0)); + /* todo for switch C key */ + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + axis = READ_STICK_AXIS(3); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(4); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(5); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(6); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverZUIKI_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverZUIKI_Context *ctx = (SDL_DriverZUIKI_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_ZUIKI_PROTOCOL + HIDAPI_DumpPacket("ZUIKI packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + if (size == 8) { + HIDAPI_DriverZUIKI_HandleOldStatePacket(joystick, ctx, data, size); + } + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverZUIKI_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +static void HIDAPI_DriverZUIKI_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverZUIKI = { + SDL_HINT_JOYSTICK_HIDAPI_ZUIKI, + true, + HIDAPI_DriverZUIKI_RegisterHints, + HIDAPI_DriverZUIKI_UnregisterHints, + HIDAPI_DriverZUIKI_IsEnabled, + HIDAPI_DriverZUIKI_IsSupportedDevice, + HIDAPI_DriverZUIKI_InitDevice, + HIDAPI_DriverZUIKI_GetDevicePlayerIndex, + HIDAPI_DriverZUIKI_SetDevicePlayerIndex, + HIDAPI_DriverZUIKI_UpdateDevice, + HIDAPI_DriverZUIKI_OpenJoystick, + HIDAPI_DriverZUIKI_RumbleJoystick, + HIDAPI_DriverZUIKI_RumbleJoystickTriggers, + HIDAPI_DriverZUIKI_GetJoystickCapabilities, + HIDAPI_DriverZUIKI_SetJoystickLED, + HIDAPI_DriverZUIKI_SendJoystickEffect, + HIDAPI_DriverZUIKI_SetJoystickSensorsEnabled, + HIDAPI_DriverZUIKI_CloseJoystick, + HIDAPI_DriverZUIKI_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_ZUIKI + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c index c7607ae..4ae31e2 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -70,11 +70,17 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = { #ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK &SDL_HIDAPI_DriverSteamDeck, #endif +#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK + &SDL_HIDAPI_DriverSteamTriton, +#endif #ifdef SDL_JOYSTICK_HIDAPI_SWITCH &SDL_HIDAPI_DriverNintendoClassic, &SDL_HIDAPI_DriverJoyCons, &SDL_HIDAPI_DriverSwitch, #endif +#ifdef SDL_JOYSTICK_HIDAPI_SWITCH2 + &SDL_HIDAPI_DriverSwitch2, +#endif #ifdef SDL_JOYSTICK_HIDAPI_WII &SDL_HIDAPI_DriverWii, #endif @@ -82,9 +88,27 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = { &SDL_HIDAPI_DriverXbox360, &SDL_HIDAPI_DriverXbox360W, #endif +#ifdef SDL_JOYSTICK_HIDAPI_GIP + &SDL_HIDAPI_DriverGIP, +#endif #ifdef SDL_JOYSTICK_HIDAPI_XBOXONE &SDL_HIDAPI_DriverXboxOne, #endif +#ifdef SDL_JOYSTICK_HIDAPI_LG4FF + &SDL_HIDAPI_DriverLg4ff, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_8BITDO + &SDL_HIDAPI_Driver8BitDo, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_FLYDIGI + &SDL_HIDAPI_DriverFlydigi, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_SINPUT + &SDL_HIDAPI_DriverSInput, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_ZUIKI + &SDL_HIDAPI_DriverZUIKI, +#endif }; static int SDL_HIDAPI_numdrivers = 0; static SDL_AtomicInt SDL_HIDAPI_updating_devices; @@ -133,7 +157,7 @@ void HIDAPI_DumpPacket(const char *prefix, const Uint8 *data, int size) current_len += SDL_snprintf(&buffer[current_len], length - current_len, " 0x%.2x", data[i]); } SDL_strlcat(buffer, "\n", length); - SDL_Log("%s", buffer); + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "%s", buffer); SDL_free(buffer); } @@ -288,9 +312,13 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U 0x1532, // Razer 0x20d6, // PowerA 0x24c6, // PowerA + 0x294b, // Snakebyte 0x2dc8, // 8BitDo 0x2e24, // Hyperkin + 0x2e95, // SCUF + 0x3285, // Nacon 0x3537, // GameSir + 0x366c, // ByoWave }; int i; @@ -338,7 +366,7 @@ static SDL_HIDAPI_DeviceDriver *HIDAPI_GetDeviceDriver(SDL_HIDAPI_Device *device return NULL; } - if (device->vendor_id != USB_VENDOR_VALVE) { + if (device->vendor_id != USB_VENDOR_VALVE && device->vendor_id != USB_VENDOR_FLYDIGI_V1 && device->vendor_id != USB_VENDOR_FLYDIGI_V2) { if (device->usage_page && device->usage_page != USAGE_PAGE_GENERIC_DESKTOP) { return NULL; } @@ -430,7 +458,9 @@ static void HIDAPI_SetupDeviceDriver(SDL_HIDAPI_Device *device, bool *removed) S if (device->driver) { bool enabled; - if (device->vendor_id == USB_VENDOR_NINTENDO && device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) { + if (device->vendor_id == USB_VENDOR_NINTENDO && + (device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR || + device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR)) { enabled = SDL_HIDAPI_combine_joycons; } else { enabled = device->driver->enabled; @@ -1046,7 +1076,11 @@ static bool HIDAPI_CreateCombinedJoyCons(void) SDL_zero(info); info.path = "nintendo_joycons_combined"; info.vendor_id = USB_VENDOR_NINTENDO; - info.product_id = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; + if (joycons[0]->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT) { + info.product_id = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; + } else { + info.product_id = USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR; + } info.interface_number = -1; info.usage_page = USB_USAGEPAGE_GENERIC_DESKTOP; info.usage = USB_USAGE_GENERIC_GAMEPAD; @@ -1274,6 +1308,16 @@ bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, */ SDL_LockJoysticks(); for (device = SDL_HIDAPI_devices; device; device = device->next) { + // The HIDAPI functionality will be available when the FlyDigi Space Station app has + // enabled third party controller mapping, so the driver needs to be active to watch + // for that change. Since this is dynamic and we don't have a way to re-trigger device + // changes when that happens, we'll pretend the driver isn't available so the XInput + // interface will always show up (but won't have any input when the controller is in + // enhanced mode) + if (device->vendor_id == USB_VENDOR_FLYDIGI_V2) { + continue; + } + if (device->driver && HIDAPI_IsEquivalentToDevice(vendor_id, product_id, device)) { result = true; diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h index 9cd9f40..6129441 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -35,11 +35,21 @@ #define SDL_JOYSTICK_HIDAPI_STEAM #define SDL_JOYSTICK_HIDAPI_STEAMDECK #define SDL_JOYSTICK_HIDAPI_SWITCH +#ifdef HAVE_LIBUSB +#define SDL_JOYSTICK_HIDAPI_SWITCH2 +#endif #define SDL_JOYSTICK_HIDAPI_WII #define SDL_JOYSTICK_HIDAPI_XBOX360 #define SDL_JOYSTICK_HIDAPI_XBOXONE #define SDL_JOYSTICK_HIDAPI_SHIELD #define SDL_JOYSTICK_HIDAPI_STEAM_HORI +#define SDL_JOYSTICK_HIDAPI_STEAM_TRITON +#define SDL_JOYSTICK_HIDAPI_LG4FF +#define SDL_JOYSTICK_HIDAPI_8BITDO +#define SDL_JOYSTICK_HIDAPI_FLYDIGI +#define SDL_JOYSTICK_HIDAPI_GIP +#define SDL_JOYSTICK_HIDAPI_SINPUT +#define SDL_JOYSTICK_HIDAPI_ZUIKI // Joystick capability definitions #define SDL_JOYSTICK_CAP_MONO_LED 0x00000001 @@ -139,6 +149,7 @@ typedef struct SDL_HIDAPI_DeviceDriver // HIDAPI device support extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGIP; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverJoyCons; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverNintendoClassic; @@ -152,11 +163,24 @@ extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch2; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamTriton; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLg4ff; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_Driver8BitDo; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverFlydigi; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSInput; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverZUIKI; + +#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) +#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \ + (((Uint32)(B)) << 8) | \ + (((Uint32)(C)) << 16) | \ + (((Uint32)(D)) << 24)) // Return true if a HID device is present and supported as a joystick of the given type extern bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type); diff --git a/libs/SDL3/src/joystick/hidapi/SDL_report_descriptor.c b/libs/SDL3/src/joystick/hidapi/SDL_report_descriptor.c new file mode 100644 index 0000000..f515583 --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_report_descriptor.c @@ -0,0 +1,616 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_report_descriptor.h" + +// This is a very simple (and non-compliant!) report descriptor parser +// used to quickly parse Xbox Bluetooth reports + +typedef enum +{ + DescriptorItemTypeMain = 0, + DescriptorItemTypeGlobal = 1, + DescriptorItemTypeLocal = 2, + DescriptorItemTypeReserved = 3, +} ItemType; + +typedef enum +{ + MainTagInput = 0x8, + MainTagOutput = 0x9, + MainTagFeature = 0xb, + MainTagCollection = 0xa, + MainTagEndCollection = 0xc, +} MainTag; + +typedef enum +{ + MainFlagConstant = 0x0001, + MainFlagVariable = 0x0002, + MainFlagRelative = 0x0004, + MainFlagWrap = 0x0008, + MainFlagNonLinear = 0x0010, + MainFlagNoPreferred = 0x0020, + MainFlagNullState = 0x0040, + MainFlagVolatile = 0x0080, + MainFlagBufferedBytes = 0x0100, +} MainFlag; + +typedef enum +{ + GlobalTagUsagePage = 0x0, + GlobalTagLogicalMinimum = 0x1, + GlobalTagLogicalMaximum = 0x2, + GlobalTagPhysicalMinimum = 0x3, + GlobalTagPhysicalMaximum = 0x4, + GlobalTagUnitExponent = 0x5, + GlobalTagUnit = 0x6, + GlobalTagReportSize = 0x7, + GlobalTagReportID = 0x8, + GlobalTagReportCount = 0x9, + GlobalTagPush = 0xa, + GlobalTagPop = 0xb, +} GlobalTag; + +typedef enum +{ + LocalTagUsage = 0x0, + LocalTagUsageMinimum = 0x1, + LocalTagUsageMaximum = 0x2, + LocalTagDesignatorIndex = 0x3, + LocalTagDesignatorMinimum = 0x4, + LocalTagDesignatorMaximum = 0x5, + LocalTagStringIndex = 0x7, + LocalTagStringMinimum = 0x8, + LocalTagStringMaximum = 0x9, + LocalTagDelimiter = 0xa, +} LocalTag; + +typedef struct +{ + Uint32 usage_page; + Uint32 report_size; + Uint32 report_count; + Uint32 report_id; +} DescriptorGlobalState; + +typedef struct +{ + Uint32 usage_minimum; + Uint32 usage_maximum; + int usage_maxcount; + int usage_count; + Uint32 *usages; +} DescriptorLocalState; + +typedef struct +{ + int collection_depth; + DescriptorGlobalState global; + DescriptorLocalState local; + int field_maxcount; + int field_count; + int field_offset; + DescriptorInputField *fields; +} DescriptorContext; + +static void DebugDescriptor(DescriptorContext *ctx, const char *fmt, ...) +{ +#ifdef DEBUG_DESCRIPTOR + va_list ap; + va_start(ap, fmt); + char *message = NULL; + SDL_vasprintf(&message, fmt, ap); + va_end(ap); + if (ctx->collection_depth > 0) { + size_t len = 4 * ctx->collection_depth + SDL_strlen(message) + 1; + char *output = (char *)SDL_malloc(len); + if (output) { + SDL_memset(output, ' ', 4 * ctx->collection_depth); + output[4 * ctx->collection_depth] = '\0'; + SDL_strlcat(output, message, len); + SDL_free(message); + message = output; + } + } + SDL_Log("%s", message); + SDL_free(message); +#endif // DEBUG_DESCRIPTOR +} + +static void DebugMainTag(DescriptorContext *ctx, const char *tag, Uint32 flags) +{ +#ifdef DEBUG_DESCRIPTOR + char message[1024] = { 0 }; + + SDL_strlcat(message, tag, sizeof(message)); + SDL_strlcat(message, "(", sizeof(message)); + if (flags & MainFlagConstant) { + SDL_strlcat(message, " Constant", sizeof(message)); + } else { + SDL_strlcat(message, " Data", sizeof(message)); + } + if (flags & MainFlagVariable) { + SDL_strlcat(message, " Variable", sizeof(message)); + } else { + SDL_strlcat(message, " Array", sizeof(message)); + } + if (flags & MainFlagRelative) { + SDL_strlcat(message, " Relative", sizeof(message)); + } else { + SDL_strlcat(message, " Absolute", sizeof(message)); + } + if (flags & MainFlagWrap) { + SDL_strlcat(message, " Wrap", sizeof(message)); + } else { + SDL_strlcat(message, " No Wrap", sizeof(message)); + } + if (flags & MainFlagNonLinear) { + SDL_strlcat(message, " Non Linear", sizeof(message)); + } else { + SDL_strlcat(message, " Linear", sizeof(message)); + } + if (flags & MainFlagNoPreferred) { + SDL_strlcat(message, " No Preferred", sizeof(message)); + } else { + SDL_strlcat(message, " Preferred State", sizeof(message)); + } + if (flags & MainFlagNullState) { + SDL_strlcat(message, " Null State", sizeof(message)); + } else { + SDL_strlcat(message, " No Null Position", sizeof(message)); + } + if (flags & MainFlagVolatile) { + SDL_strlcat(message, " Volatile", sizeof(message)); + } else { + SDL_strlcat(message, " Non Volatile", sizeof(message)); + } + if (flags & MainFlagBufferedBytes) { + SDL_strlcat(message, " Buffered Bytes", sizeof(message)); + } else { + SDL_strlcat(message, " Bit Field", sizeof(message)); + } + SDL_strlcat(message, " )", sizeof(message)); + + DebugDescriptor(ctx, "%s", message); + +#endif // DEBUG_DESCRIPTOR +} + +static Uint32 ReadValue(const Uint8 *data, int size) +{ + Uint32 value = 0; + + int shift = 0; + while (size--) { + value |= ((Uint32)(*data++)) << shift; + shift += 8; + } + return value; +} + +static void ResetLocalState(DescriptorContext *ctx) +{ + ctx->local.usage_minimum = 0; + ctx->local.usage_maximum = 0; + ctx->local.usage_count = 0; +} + +static bool AddUsage(DescriptorContext *ctx, Uint32 usage) +{ + if (ctx->local.usage_count == ctx->local.usage_maxcount) { + int usage_maxcount = ctx->local.usage_maxcount + 4; + Uint32 *usages = (Uint32 *)SDL_realloc(ctx->local.usages, usage_maxcount * sizeof(*usages)); + if (!usages) { + return false; + } + ctx->local.usages = usages; + ctx->local.usage_maxcount = usage_maxcount; + } + + if (usage <= 0xFFFF) { + usage |= (ctx->global.usage_page << 16); + } + ctx->local.usages[ctx->local.usage_count++] = usage; + return true; +} + +static bool AddInputField(DescriptorContext *ctx, Uint32 usage, int bit_size) +{ + if (ctx->field_count == ctx->field_maxcount) { + int field_maxcount = ctx->field_maxcount + 4; + DescriptorInputField *fields = (DescriptorInputField *)SDL_realloc(ctx->fields, field_maxcount * sizeof(*fields)); + if (!fields) { + return false; + } + ctx->fields = fields; + ctx->field_maxcount = field_maxcount; + } + + DescriptorInputField *field = &ctx->fields[ctx->field_count++]; + field->report_id = (Uint8)ctx->global.report_id; + field->usage = usage; + field->bit_offset = ctx->field_offset; + field->bit_size = bit_size; + + DebugDescriptor(ctx, "Adding report %d field 0x%.8x size %d bits at bit offset %d", field->report_id, field->usage, field->bit_size, field->bit_offset); + return true; +} + +static bool AddInputFields(DescriptorContext *ctx) +{ + Uint32 usage = 0; + + if (ctx->global.report_count == 0 || ctx->global.report_size == 0) { + return true; + } + + if (ctx->local.usage_count == 0 && + ctx->local.usage_minimum > 0 && + ctx->local.usage_maximum >= ctx->local.usage_minimum) { + for (usage = ctx->local.usage_minimum; usage <= ctx->local.usage_maximum; ++usage) { + if (!AddUsage(ctx, usage)) { + return false; + } + } + } + + int usage_index = 0; + for (Uint32 i = 0; i < ctx->global.report_count; ++i) { + if (usage_index < ctx->local.usage_count) { + usage = ctx->local.usages[usage_index]; + if (usage_index < (ctx->local.usage_count - 1)) { + ++usage_index; + } + } + + int size = (int)ctx->global.report_size; + if (usage > 0) { + if (!AddInputField(ctx, usage, size)) { + return false; + } + } + ctx->field_offset += size; + } + return true; +} + +static bool ParseMainItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data) +{ + Uint32 flags; + + switch (tag) { + case MainTagInput: + flags = ReadValue(data, size); + DebugMainTag(ctx, "MainTagInput", flags); + AddInputFields(ctx); + break; + case MainTagOutput: + flags = ReadValue(data, size); + DebugMainTag(ctx, "MainTagOutput", flags); + break; + case MainTagFeature: + flags = ReadValue(data, size); + DebugMainTag(ctx, "MainTagFeature", flags); + break; + case MainTagCollection: + DebugDescriptor(ctx, "MainTagCollection"); + switch (*data) { + case 0x00: + DebugDescriptor(ctx, "Physical"); + break; + case 0x01: + DebugDescriptor(ctx, "Application"); + break; + case 0x02: + DebugDescriptor(ctx, "Logical"); + break; + case 0x03: + DebugDescriptor(ctx, "Report"); + break; + case 0x04: + DebugDescriptor(ctx, "Named Array"); + break; + case 0x05: + DebugDescriptor(ctx, "Usage Switch"); + break; + case 0x06: + DebugDescriptor(ctx, "Usage Modifier"); + break; + default: + break; + } + ++ctx->collection_depth; + break; + case MainTagEndCollection: + if (ctx->collection_depth > 0) { + --ctx->collection_depth; + } + DebugDescriptor(ctx, "MainTagEndCollection"); + break; + default: + DebugDescriptor(ctx, "Unknown main tag: %d", tag); + break; + } + + ResetLocalState(ctx); + + return true; +} + +static bool ParseGlobalItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data) +{ + Uint32 value; + + switch (tag) { + case GlobalTagUsagePage: + ctx->global.usage_page = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagUsagePage: 0x%.4x", ctx->global.usage_page); + break; + case GlobalTagLogicalMinimum: + value = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagLogicalMinimum: %u", value); + break; + case GlobalTagLogicalMaximum: + value = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagLogicalMaximum: %u", value); + break; + case GlobalTagPhysicalMinimum: + value = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagPhysicalMinimum: %u", value); + break; + case GlobalTagPhysicalMaximum: + value = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagPhysicalMaximum: %u", value); + break; + case GlobalTagUnitExponent: + DebugDescriptor(ctx, "GlobalTagUnitExponent"); + break; + case GlobalTagUnit: + DebugDescriptor(ctx, "GlobalTagUnit"); + break; + case GlobalTagReportSize: + ctx->global.report_size = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagReportSize: %u", ctx->global.report_size); + break; + case GlobalTagReportID: + ctx->global.report_id = ReadValue(data, size); + ctx->field_offset = 0; + DebugDescriptor(ctx, "GlobalTagReportID: %u", ctx->global.report_id); + break; + case GlobalTagReportCount: + ctx->global.report_count = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagReportCount: %u", ctx->global.report_count); + break; + case GlobalTagPush: + DebugDescriptor(ctx, "GlobalTagPush"); + break; + case GlobalTagPop: + DebugDescriptor(ctx, "GlobalTagPop"); + break; + default: + DebugDescriptor(ctx, "Unknown global tag"); + break; + } + return true; +} + +static bool ParseLocalItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data) +{ + Uint32 value; + + switch (tag) { + case LocalTagUsage: + value = ReadValue(data, size); + AddUsage(ctx, value); + DebugDescriptor(ctx, "LocalTagUsage: 0x%.4x", value); + break; + case LocalTagUsageMinimum: + ctx->local.usage_minimum = ReadValue(data, size); + DebugDescriptor(ctx, "LocalTagUsageMinimum: 0x%.4x", ctx->local.usage_minimum); + break; + case LocalTagUsageMaximum: + ctx->local.usage_maximum = ReadValue(data, size); + DebugDescriptor(ctx, "LocalTagUsageMaximum: 0x%.4x", ctx->local.usage_maximum); + break; + case LocalTagDesignatorIndex: + DebugDescriptor(ctx, "LocalTagDesignatorIndex"); + break; + case LocalTagDesignatorMinimum: + DebugDescriptor(ctx, "LocalTagDesignatorMinimum"); + break; + case LocalTagDesignatorMaximum: + DebugDescriptor(ctx, "LocalTagDesignatorMaximum"); + break; + case LocalTagStringIndex: + DebugDescriptor(ctx, "LocalTagStringIndex"); + break; + case LocalTagStringMinimum: + DebugDescriptor(ctx, "LocalTagStringMinimum"); + break; + case LocalTagStringMaximum: + DebugDescriptor(ctx, "LocalTagStringMaximum"); + break; + case LocalTagDelimiter: + DebugDescriptor(ctx, "LocalTagDelimiter"); + break; + default: + DebugDescriptor(ctx, "Unknown local tag"); + break; + } + return true; +} + +static bool ParseDescriptor(DescriptorContext *ctx, const Uint8 *descriptor, int descriptor_size) +{ + SDL_zerop(ctx); + + for (const Uint8 *here = descriptor; here < descriptor + descriptor_size; ) { + static const int sizes[4] = { 0, 1, 2, 4 }; + Uint8 data = *here++; + int size = sizes[(data & 0x3)]; + int type = ((data >> 2) & 0x3); + int tag = (data >> 4); + + if ((here + size) > (descriptor + descriptor_size)) { + return SDL_SetError("Invalid descriptor"); + } + +#ifdef DEBUG_DESCRIPTOR + SDL_Log("Data: 0x%.2x, size: %d, type: %d, tag: %d", data, size, type, tag); +#endif + switch (type) { + case DescriptorItemTypeMain: + if (!ParseMainItem(ctx, tag, size, here)) { + return false; + } + break; + case DescriptorItemTypeGlobal: + if (!ParseGlobalItem(ctx, tag, size, here)) { + return false; + } + break; + case DescriptorItemTypeLocal: + if (!ParseLocalItem(ctx, tag, size, here)) { + return false; + } + break; + case DescriptorItemTypeReserved: + // Long items are currently unsupported + return SDL_Unsupported(); + } + + here += size; + } + return true; +} + +static void CleanupContext(DescriptorContext *ctx) +{ + SDL_free(ctx->local.usages); + SDL_free(ctx->fields); +} + +SDL_ReportDescriptor *SDL_ParseReportDescriptor(const Uint8 *descriptor, int descriptor_size) +{ + SDL_ReportDescriptor *result = NULL; + + DescriptorContext ctx; + if (ParseDescriptor(&ctx, descriptor, descriptor_size)) { + result = (SDL_ReportDescriptor *)SDL_malloc(sizeof(*result)); + if (result) { + result->field_count = ctx.field_count; + result->fields = ctx.fields; + ctx.fields = NULL; + } + } + CleanupContext(&ctx); + + return result; +} + +bool SDL_DescriptorHasUsage(SDL_ReportDescriptor *descriptor, Uint16 usage_page, Uint16 usage) +{ + if (!descriptor) { + return false; + } + + Uint32 full_usage = (((Uint32)usage_page << 16) | usage); + for (int i = 0; i < descriptor->field_count; ++i) { + if (descriptor->fields[i].usage == full_usage) { + return true; + } + } + return false; +} + +void SDL_DestroyDescriptor(SDL_ReportDescriptor *descriptor) +{ + if (descriptor) { + SDL_free(descriptor->fields); + SDL_free(descriptor); + } +} + +bool SDL_ReadReportData(const Uint8 *data, int size, int bit_offset, int bit_size, Uint32 *value) +{ + int offset = (bit_offset / 8); + if (offset >= size) { + *value = 0; + return SDL_SetError("Out of bounds reading report data"); + } + + *value = ReadValue(data + offset, (bit_size + 7) / 8); + + int shift = (bit_offset % 8); + if (shift > 0) { + *value >>= shift; + } + + switch (bit_size) { + case 1: + *value &= 0x1; + break; + case 4: + *value &= 0xf; + break; + case 10: + *value &= 0x3ff; + break; + case 15: + *value &= 0x7fff; + break; + default: + SDL_assert((bit_size % 8) == 0); + break; + } + return true; +} + +#ifdef TEST_MAIN + +#include + +int main(int argc, char *argv[]) +{ + const char *file = argv[1]; + if (argc < 2) { + SDL_Log("Usage: %s file", argv[0]); + return 1; + } + + size_t descriptor_size = 0; + Uint8 *descriptor = SDL_LoadFile(argv[1], &descriptor_size); + if (!descriptor) { + SDL_Log("Couldn't load %s: %s", argv[1], SDL_GetError()); + return 2; + } + + DescriptorContext ctx; + if (!ParseDescriptor(&ctx, descriptor, descriptor_size)) { + SDL_Log("Couldn't parse %s: %s", argv[1], SDL_GetError()); + return 3; + } + return 0; +} + +#endif // TEST_MAIN diff --git a/libs/SDL3/src/joystick/hidapi/SDL_report_descriptor.h b/libs/SDL3/src/joystick/hidapi/SDL_report_descriptor.h new file mode 100644 index 0000000..3e21f8b --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_report_descriptor.h @@ -0,0 +1,40 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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 +{ + Uint8 report_id; + Uint32 usage; + int bit_offset; + int bit_size; +} DescriptorInputField; + +typedef struct +{ + int field_count; + DescriptorInputField *fields; +} SDL_ReportDescriptor; + +extern SDL_ReportDescriptor *SDL_ParseReportDescriptor(const Uint8 *descriptor, int descriptor_size); +extern bool SDL_DescriptorHasUsage(SDL_ReportDescriptor *descriptor, Uint16 usage_page, Uint16 usage); +extern void SDL_DestroyDescriptor(SDL_ReportDescriptor *descriptor); +extern bool SDL_ReadReportData(const Uint8 *data, int size, int bit_offset, int bit_size, Uint32 *value); diff --git a/libs/SDL3/src/joystick/hidapi/steam/controller_constants.h b/libs/SDL3/src/joystick/hidapi/steam/controller_constants.h index 78af016..b60021e 100644 --- a/libs/SDL3/src/joystick/hidapi/steam/controller_constants.h +++ b/libs/SDL3/src/joystick/hidapi/steam/controller_constants.h @@ -353,7 +353,7 @@ enum GamepadButtons // Mode adjust enum ModeAdjustModes { - MODE_ADJUST_SENSITITY=1, + MODE_ADJUST_SENSITIVITY=1, MODE_ADJUST_LEFT_PAD_SECONDARY_MODE, MODE_ADJUST_RIGHT_PAD_SECONDARY_MODE, MODE_ADJUST_COUNT @@ -364,8 +364,8 @@ typedef enum { ATTRIB_UNIQUE_ID, ATTRIB_PRODUCT_ID, - ATTRIB_PRODUCT_REVISON, // deprecated - ATTRIB_CAPABILITIES = ATTRIB_PRODUCT_REVISON, // intentional aliasing + ATTRIB_PRODUCT_REVISION, // deprecated + ATTRIB_CAPABILITIES = ATTRIB_PRODUCT_REVISION, // intentional aliasing ATTRIB_FIRMWARE_VERSION, // deprecated ATTRIB_FIRMWARE_BUILD_TIME, ATTRIB_RADIO_FIRMWARE_BUILD_TIME, @@ -411,6 +411,12 @@ typedef enum { TRACKPAD_NUM_MODES } TrackpadDPadMode; +typedef enum +{ + LIZARD_MODE_OFF, + LIZARD_MODE_ON, +} LizardModeState_t; + // Read-write controller settings (only add to this enum and never change the order) typedef enum { @@ -423,7 +429,7 @@ typedef enum SETTING_USB_DEBUG_MODE, SETTING_LEFT_TRACKPAD_MODE, SETTING_RIGHT_TRACKPAD_MODE, - SETTING_MOUSE_POINTER_ENABLED, + SETTING_LIZARD_MODE, // 10 SETTING_DPAD_DEADZONE, diff --git a/libs/SDL3/src/joystick/hidapi/steam/controller_structs.h b/libs/SDL3/src/joystick/hidapi/steam/controller_structs.h index ea2a352..2657346 100644 --- a/libs/SDL3/src/joystick/hidapi/steam/controller_structs.h +++ b/libs/SDL3/src/joystick/hidapi/steam/controller_structs.h @@ -112,7 +112,7 @@ typedef struct { int16_t dur_ms; // Duration of tone / rumble (if applicable) (neg = infinite) uint16_t noise_intensity; - uint16_t lfo_freq; // Drives both tone and rumble geneators + uint16_t lfo_freq; // Drives both tone and rumble generators uint8_t lfo_depth; // percentage, typically 100 uint8_t rand_tone_gain; // Randomize each LFO cycle's gain uint8_t script_id; // Used w/ dBgain for scripted haptics @@ -154,6 +154,100 @@ typedef struct } FeatureReportMsg; +// Triton and derivatives utilize output reports for haptic commands. This is a +// snapshot from Nov 2024 -- things may change. + +// Triton Output Report Lengths #defs include +1 for the OR ID + +// Output Report Haptic Messages for Triton +typedef struct +{ + uint8_t type; + uint16_t intensity; + struct + { + uint16_t speed; + int8_t gain; + } left, right; +} MsgHapticRumble; +#define HID_RUMBLE_OUTPUT_REPORT_BYTES 10 + + +typedef struct +{ + uint8_t side; + uint16_t on_us; + uint16_t off_us; + uint16_t repeat_count; + uint16_t gain_db; +} MsgHapticPulse; +#define HID_HAPTIC_PULSE_OUTPUT_REPORT_BYTES 10 + +typedef struct +{ + uint8_t side; + uint8_t command; + int8_t gain_db; +} MsgHapticCommand; +#define HID_HAPTIC_COMMAND_REPORT_BYTES 4 + +typedef struct +{ + uint8_t side; + int8_t gain_db; + uint16_t frequency; + uint16_t duration_ms; + uint16_t lfo_freq; + uint8_t lfo_depth; +} MsgHapticLfoTone; +#define HID_HAPTIC_LFO_TONE_REPORT_BYTES 10 + +typedef struct +{ + uint8_t side; + int8_t gain_db; + uint16_t duration_ms; + struct + { + uint16_t frequency; + } start, end; +} MsgHapticLogSweep; +#define HID_HAPTIC_LOG_SWEEP_REPORT_BYTES 9 + +typedef struct m +{ + uint8_t side; + uint8_t script_id; + int8_t gain_db; +} MsgHapticScript; +#define HID_HAPTIC_SCRIPT_REPORT_BYTES 4 + +typedef enum +{ + ID_OUT_REPORT_HAPTIC_RUMBLE = 0x80, + ID_OUT_REPORT_HAPTIC_PULSE = 0x81, + ID_OUT_REPORT_HAPTIC_COMMAND = 0x82, + ID_OUT_REPORT_HAPTIC_LFO_TONE = 0x83, + ID_OUT_REPORT_HAPTIC_LOG_SWEEP = 0x85, + ID_OUT_REPORT_HAPTIC_SCRIPT = 0x86, +} ValveTritonOutReportMessageIDs; + +typedef struct +{ + uint8_t report_id; + union + { + MsgHapticRumble hapticRumble; + MsgHapticPulse hapticPulse; + MsgHapticCommand hapticCommand; + MsgHapticLfoTone hapticLfoTone; + MsgHapticLogSweep hapticLogSweep; + MsgHapticScript hapticScript; + } payload; + +} OutputReportMsg; + + // Roll this version forward anytime that you are breaking compatibility of existing // message types within ValveInReport_t or the header itself. Hopefully this should // be super rare and instead you should just add new message payloads to the union, @@ -261,7 +355,7 @@ typedef struct short sRightPadX; short sRightPadY; - //This mimcs how the dongle reconstitutes HID packets, there will be 0-4 shorts depending on gyro mode + //This mimics how the dongle reconstitutes HID packets, there will be 0-4 shorts depending on gyro mode unsigned char ucGyroDataType; //TODO could maybe find some unused bits in the button field for this info (is only 2bits) short sGyro[4]; @@ -413,51 +507,129 @@ typedef struct unsigned short sPressurePadRight; } SteamDeckStatePacket_t; + typedef struct { - ValveInReportHeader_t header; - - union - { - ValveControllerStatePacket_t controllerState; - ValveControllerBLEStatePacket_t controllerBLEState; - ValveControllerDebugPacket_t debugState; - ValveControllerTrackpadImage_t padImage; - ValveControllerRawTrackpadImage_t rawPadImage; - SteamControllerWirelessEvent_t wirelessEvent; - SteamControllerStatusEvent_t statusEvent; - SteamDeckStatePacket_t deckState; - } payload; - + ValveInReportHeader_t header; + + union + { + ValveControllerStatePacket_t controllerState; + ValveControllerBLEStatePacket_t controllerBLEState; + ValveControllerDebugPacket_t debugState; + ValveControllerTrackpadImage_t padImage; + ValveControllerRawTrackpadImage_t rawPadImage; + SteamControllerWirelessEvent_t wirelessEvent; + SteamControllerStatusEvent_t statusEvent; + SteamDeckStatePacket_t deckState; + } payload; + } ValveInReport_t; - -// Enumeration for BLE packet protocol enum EBLEPacketReportNums { - // Skipping past 2-3 because they are escape characters in Uart protocol - k_EBLEReportState = 4, - k_EBLEReportStatus = 5, + k_EBLEReportState = 4, + k_EBLEReportStatus = 5, }; - - // Enumeration of data chunks in BLE state packets enum EBLEOptionDataChunksBitmask { - // First byte upper nibble - k_EBLEButtonChunk1 = 0x10, - k_EBLEButtonChunk2 = 0x20, - k_EBLEButtonChunk3 = 0x40, - k_EBLELeftJoystickChunk = 0x80, + // First byte upper nibble + k_EBLEButtonChunk1 = 0x10, + k_EBLEButtonChunk2 = 0x20, + k_EBLEButtonChunk3 = 0x40, + k_EBLELeftJoystickChunk = 0x80, - // Second full byte - k_EBLELeftTrackpadChunk = 0x100, - k_EBLERightTrackpadChunk = 0x200, - k_EBLEIMUAccelChunk = 0x400, - k_EBLEIMUGyroChunk = 0x800, - k_EBLEIMUQuatChunk = 0x1000, + // Second full byte + k_EBLELeftTrackpadChunk = 0x100, + k_EBLERightTrackpadChunk = 0x200, + k_EBLEIMUAccelChunk = 0x400, + k_EBLEIMUGyroChunk = 0x800, + k_EBLEIMUQuatChunk = 0x1000, }; +// Triton and derivatives do not use the ValveInReport_t structure + +enum ETritonReportIDTypes +{ + ID_TRITON_CONTROLLER_STATE = 0x42, + ID_TRITON_BATTERY_STATUS = 0x43, + ID_TRITON_CONTROLLER_STATE_BLE = 0x45, + ID_TRITON_WIRELESS_STATUS_X = 0x46, + ID_TRITON_WIRELESS_STATUS = 0x79, +}; + +enum ETritonWirelessState +{ + k_ETritonWirelessStateDisconnect = 1, + k_ETritonWirelessStateConnect = 2, +}; + +typedef struct +{ + uint32_t uTimestamp; + short sAccelX; + short sAccelY; + short sAccelZ; + + short sGyroX; + short sGyroY; + short sGyroZ; + + short sGyroQuatW; + short sGyroQuatX; + short sGyroQuatY; + short sGyroQuatZ; +} TritonMTUIMU_t; + +typedef struct +{ + uint8_t cSeq_num; + uint32_t uButtons; + short sTriggerLeft; + short sTriggerRight; + + short sLeftStickX; + short sLeftStickY; + short sRightStickX; + short sRightStickY; + + short sLeftPadX; + short sLeftPadY; + unsigned short ucPressureLeft; + + short sRightPadX; + short sRightPadY; + unsigned short ucPressureRight; + TritonMTUIMU_t imu; +} TritonMTUFull_t; + +enum EChargeState +{ + k_EChargeStateReset, + k_EChargeStateDischarging, + k_EChargeStateCharging, + k_EChargeStateSrcValidate, + k_EChargeStateChargingDone, +}; + +typedef struct +{ + unsigned char ucChargeState; // EChargeState + unsigned char ucBatteryLevel; + unsigned short sBatteryVoltage; + unsigned short sSystemVoltage; + unsigned short sInputVoltage; + unsigned short sCurrent; + unsigned short sInputCurrent; + unsigned short sTemperature; +} TritonBatteryStatus_t; + +typedef struct +{ + unsigned char state; +} TritonWirelessStatus_t; + #pragma pack() #endif // _CONTROLLER_STRUCTS diff --git a/libs/SDL3/src/joystick/linux/SDL_sysjoystick.c b/libs/SDL3/src/joystick/linux/SDL_sysjoystick.c index ea73821..4a924f4 100644 --- a/libs/SDL3/src/joystick/linux/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/linux/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -75,6 +75,18 @@ #ifndef BTN_DPAD_RIGHT #define BTN_DPAD_RIGHT 0x223 #endif +#ifndef BTN_GRIPL +#define BTN_GRIPL 0x224 +#endif +#ifndef BTN_GRIPR +#define BTN_GRIPR 0x225 +#endif +#ifndef BTN_GRIPL2 +#define BTN_GRIPL2 0x226 +#endif +#ifndef BTN_GRIPR2 +#define BTN_GRIPR2 0x227 +#endif #ifndef BTN_TRIGGER_HAPPY #define BTN_TRIGGER_HAPPY 0x2c0 @@ -150,7 +162,10 @@ typedef struct SDL_joylist_item { SDL_JoystickID device_instance; char *path; // "/dev/input/event2" or whatever + Uint16 vendor; + Uint16 product; char *name; // "SideWinder 3D Pro" or whatever + char *driver; // "xpad" or whatever SDL_GUID guid; dev_t devnum; int steam_virtual_gamepad_slot; @@ -274,19 +289,20 @@ static bool GuessIsSensor(int fd) return false; } -static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_GUID *guid) +static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_GUID *guid, char **driver_return) { struct input_id inpid; - char *name; + char *name = NULL; + char *driver = NULL; char product_string[128]; int class = 0; SDL_zero(inpid); #ifdef SDL_USE_LIBUDEV // Opening input devices can generate synchronous device I/O, so avoid it if we can - if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) && + if (SDL_UDEV_GetProductInfo(path, &inpid, &class, &driver) && !(class & SDL_UDEV_DEVICE_JOYSTICK)) { - return false; + goto error; } #endif @@ -294,34 +310,33 @@ static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *ve *fd = open(path, O_RDONLY | O_CLOEXEC, 0); } if (!fd || *fd < 0) { - return false; + goto error; } if (ioctl(*fd, JSIOCGNAME(sizeof(product_string)), product_string) <= 0) { // When udev enumeration or classification, we only got joysticks here, so no need to test if (enumeration_method != ENUMERATION_LIBUDEV && !class && !GuessIsJoystick(*fd)) { - return false; + goto error; } // Could have vendor and product already from udev, but should agree with evdev if (ioctl(*fd, EVIOCGID, &inpid) < 0) { - return false; + goto error; } if (ioctl(*fd, EVIOCGNAME(sizeof(product_string)), product_string) < 0) { - return false; + goto error; } } name = SDL_CreateJoystickName(inpid.vendor, inpid.product, NULL, product_string); if (!name) { - return false; + goto error; } if (!IsVirtualJoystick(inpid.vendor, inpid.product, inpid.version, name) && SDL_JoystickHandledByAnotherDriver(&SDL_LINUX_JoystickDriver, inpid.vendor, inpid.product, inpid.version, name)) { - SDL_free(name); - return false; + goto error; } FixupDeviceInfoForMapping(*fd, &inpid); @@ -331,14 +346,19 @@ static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *ve #endif if (SDL_ShouldIgnoreJoystick(inpid.vendor, inpid.product, inpid.version, name)) { - SDL_free(name); - return false; + goto error; } *name_return = name; + *driver_return = driver; *vendor_return = inpid.vendor; *product_return = inpid.product; *guid = SDL_CreateJoystickGUID(inpid.bustype, inpid.vendor, inpid.product, inpid.version, NULL, product_string, 0, 0); return true; + +error: + SDL_free(driver); + SDL_free(name); + return false; } static bool IsSensor(const char *path, int *fd) @@ -349,7 +369,7 @@ static bool IsSensor(const char *path, int *fd) SDL_zero(inpid); #ifdef SDL_USE_LIBUDEV // Opening input devices can generate synchronous device I/O, so avoid it if we can - if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) && + if (SDL_UDEV_GetProductInfo(path, &inpid, &class, NULL) && !(class & SDL_UDEV_DEVICE_ACCELEROMETER)) { return false; } @@ -422,6 +442,7 @@ static void FreeJoylistItem(SDL_joylist_item *item) SDL_free(item->mapping); SDL_free(item->path); SDL_free(item->name); + SDL_free(item->driver); SDL_free(item); } @@ -436,6 +457,7 @@ static void MaybeAddDevice(const char *path) struct stat sb; int fd = -1; char *name = NULL; + char *driver = NULL; Uint16 vendor, product; SDL_GUID guid; SDL_joylist_item *item; @@ -473,7 +495,7 @@ static void MaybeAddDevice(const char *path) SDL_Log("Checking %s", path); #endif - if (IsJoystick(path, &fd, &name, &vendor, &product, &guid)) { + if (IsJoystick(path, &fd, &name, &vendor, &product, &guid, &driver)) { #ifdef DEBUG_INPUT_EVENTS SDL_Log("found joystick: %s", path); #endif @@ -486,8 +508,11 @@ static void MaybeAddDevice(const char *path) item->devnum = sb.st_rdev; item->steam_virtual_gamepad_slot = -1; item->path = SDL_strdup(path); + item->vendor = vendor; + item->product = product; item->name = name; item->guid = guid; + item->driver = driver; if (vendor == USB_VENDOR_VALVE && product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { @@ -861,7 +886,7 @@ static void LINUX_ScanSteamVirtualGamepads(void) // Opening input devices can generate synchronous device I/O, so avoid it if we can class = 0; SDL_zero(inpid); - if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) && + if (SDL_UDEV_GetProductInfo(path, &inpid, &class, NULL) && (inpid.vendor != USB_VENDOR_VALVE || inpid.product != USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD)) { free(entries[i]); // This should NOT be SDL_free() continue; @@ -948,7 +973,7 @@ static void LINUX_JoystickDetect(void) { #ifdef SDL_USE_LIBUDEV if (enumeration_method == ENUMERATION_LIBUDEV) { - SDL_UDEV_Poll(); + // Polling will happen in the main event loop } else #endif #ifdef HAVE_INOTIFY @@ -1136,6 +1161,8 @@ static SDL_JoystickID LINUX_JoystickGetDeviceInstanceID(int device_index) static bool allocate_balldata(SDL_Joystick *joystick) { + SDL_AssertJoysticksLocked(); + joystick->hwdata->balls = (struct hwdata_ball *)SDL_calloc(joystick->nballs, sizeof(struct hwdata_ball)); if (joystick->hwdata->balls == NULL) { @@ -1512,7 +1539,7 @@ static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item) return NULL; } - SDL_memset(uniq_item, 0, sizeof(uniq_item)); + SDL_zeroa(uniq_item); fd_item = open(item->path, O_RDONLY | O_CLOEXEC, 0); if (fd_item < 0) { return NULL; @@ -1534,7 +1561,7 @@ static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item) continue; } - SDL_memset(uniq_sensor, 0, sizeof(uniq_sensor)); + SDL_zeroa(uniq_sensor); fd_sensor = open(item_sensor->path, O_RDONLY | O_CLOEXEC, 0); if (fd_sensor < 0) { continue; @@ -1587,6 +1614,10 @@ static bool LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index) item_sensor->hwdata = joystick->hwdata; } +#ifdef SDL_USE_LIBUDEV + joystick->serial = SDL_UDEV_GetProductSerial(item->path); +#endif + // mark joystick as fresh and ready joystick->hwdata->fresh = true; @@ -1747,6 +1778,8 @@ static void HandleHat(Uint64 timestamp, SDL_Joystick *stick, int hatidx, int axi static void HandleBall(SDL_Joystick *stick, Uint8 ball, int axis, int value) { + SDL_AssertJoysticksLocked(); + stick->hwdata->balls[ball].axis[axis] += value; } @@ -1844,6 +1877,20 @@ static void PollAllValues(Uint64 timestamp, SDL_Joystick *joystick) // Joyballs are relative input, so there's no poll state. Events only! } +static void CorrectSensorData(struct joystick_hwdata *hwdata, float *values, float *data) +{ + if (hwdata->item->vendor == USB_VENDOR_NINTENDO) { + // The Nintendo driver uses a different axis order than SDL + data[0] = -values[1]; + data[1] = values[2]; + data[2] = -values[0]; + } else { + data[0] = values[0]; + data[1] = values[1]; + data[2] = values[2]; + } +} + static void PollAllSensors(Uint64 timestamp, SDL_Joystick *joystick) { struct input_absinfo absinfo; @@ -1854,27 +1901,31 @@ static void PollAllSensors(Uint64 timestamp, SDL_Joystick *joystick) SDL_assert(joystick->hwdata->fd_sensor >= 0); if (joystick->hwdata->has_gyro) { - float data[3] = {0.0f, 0.0f, 0.0f}; + float values[3] = {0.0f, 0.0f, 0.0f}; for (i = 0; i < 3; i++) { if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_RX + i), &absinfo) >= 0) { - data[i] = absinfo.value * (SDL_PI_F / 180.f) / joystick->hwdata->gyro_scale[i]; + values[i] = absinfo.value * (SDL_PI_F / 180.f) / joystick->hwdata->gyro_scale[i]; #ifdef DEBUG_INPUT_EVENTS SDL_Log("Joystick : Re-read Gyro (axis %d) val= %f", i, data[i]); #endif } } + float data[3]; + CorrectSensorData(joystick->hwdata, values, data); SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, SDL_US_TO_NS(joystick->hwdata->sensor_tick), data, 3); } if (joystick->hwdata->has_accelerometer) { - float data[3] = {0.0f, 0.0f, 0.0f}; + float values[3] = {0.0f, 0.0f, 0.0f}; for (i = 0; i < 3; i++) { if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_X + i), &absinfo) >= 0) { - data[i] = absinfo.value * SDL_STANDARD_GRAVITY / joystick->hwdata->accelerometer_scale[i]; + values[i] = absinfo.value * SDL_STANDARD_GRAVITY / joystick->hwdata->accelerometer_scale[i]; #ifdef DEBUG_INPUT_EVENTS SDL_Log("Joystick : Re-read Accelerometer (axis %d) val= %f", i, data[i]); #endif } } + float data[3]; + CorrectSensorData(joystick->hwdata, values, data); SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, SDL_US_TO_NS(joystick->hwdata->sensor_tick), data, 3); } } @@ -2058,12 +2109,15 @@ static void HandleInputEvents(SDL_Joystick *joystick) PollAllSensors(SDL_GetTicksNS(), joystick); // try to sync up to current state now } else { Uint64 timestamp = SDL_EVDEV_GetEventTimestamp(event); + float data[3]; + CorrectSensorData(joystick->hwdata, joystick->hwdata->gyro_data, data); SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, SDL_US_TO_NS(joystick->hwdata->sensor_tick), - joystick->hwdata->gyro_data, 3); + data, 3); + CorrectSensorData(joystick->hwdata, joystick->hwdata->accel_data, data); SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, SDL_US_TO_NS(joystick->hwdata->sensor_tick), - joystick->hwdata->accel_data, 3); + data, 3); } break; default: @@ -2244,6 +2298,12 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping MAPPED_DPAD_LEFT = 0x4, MAPPED_DPAD_RIGHT = 0x8, MAPPED_DPAD_ALL = 0xF, + + MAPPED_LEFT_PADDLE1 = 0x1, + MAPPED_RIGHT_PADDLE1 = 0x2, + MAPPED_LEFT_PADDLE2 = 0x4, + MAPPED_RIGHT_PADDLE2 = 0x8, + MAPPED_PADDLE_ALL = 0xF, }; unsigned int mapped; bool result = false; @@ -2609,6 +2669,27 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping SDL_Log("Mapped DPUP+DOWN to axis %d (ABS_HAT0Y)", out->dpup.target); SDL_Log("Mapped DPLEFT+RIGHT to axis %d (ABS_HAT0X)", out->dpleft.target); #endif + } else if (item->driver && SDL_strcmp(item->driver, "xpad") == 0) { + // xpad will sometimes map the D-Pad as BTN_TRIGGER_HAPPY1 - BTN_TRIGGER_HAPPY4 + if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY1] && + joystick->hwdata->has_key[BTN_TRIGGER_HAPPY2] && + joystick->hwdata->has_key[BTN_TRIGGER_HAPPY3] && + joystick->hwdata->has_key[BTN_TRIGGER_HAPPY4]) { + out->dpleft.kind = EMappingKind_Button; + out->dpright.kind = EMappingKind_Button; + out->dpup.kind = EMappingKind_Button; + out->dpdown.kind = EMappingKind_Button; + out->dpleft.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY1]; + out->dpright.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY2]; + out->dpup.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY3]; + out->dpdown.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY4]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped DPLEFT to button %d (BTN_TRIGGER_HAPPY1)", out->dpleft.target); + SDL_Log("Mapped DPRIGHT to button %d (BTN_TRIGGER_HAPPY2)", out->dpright.target); + SDL_Log("Mapped DPUP to button %d (BTN_TRIGGER_HAPPY3)", out->dpup.target); + SDL_Log("Mapped DPDOWN to button %d (BTN_TRIGGER_HAPPY4)", out->dpdown.target); +#endif + } } } @@ -2653,8 +2734,44 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping #endif } - if (SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) { + mapped = 0; + + if (joystick->hwdata->has_key[BTN_GRIPR]) { + out->right_paddle1.kind = EMappingKind_Button; + out->right_paddle1.target = joystick->hwdata->key_map[BTN_GRIPR]; + mapped |= MAPPED_RIGHT_PADDLE1; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHT_PADDLE1 to button %d (BTN_GRIPR)", out->right_paddle1.target); +#endif + } + if (joystick->hwdata->has_key[BTN_GRIPL]) { + out->left_paddle1.kind = EMappingKind_Button; + out->left_paddle1.target = joystick->hwdata->key_map[BTN_GRIPL]; + mapped |= MAPPED_LEFT_PADDLE1; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFT_PADDLE1 to button %d (BTN_GRIPL)", out->left_paddle1.target); +#endif + } + if (joystick->hwdata->has_key[BTN_GRIPR2]) { + out->right_paddle2.kind = EMappingKind_Button; + out->right_paddle2.target = joystick->hwdata->key_map[BTN_GRIPR2]; + mapped |= MAPPED_RIGHT_PADDLE2; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHT_PADDLE2 to button %d (BTN_GRIPR)", out->right_paddle2.target); +#endif + } + if (joystick->hwdata->has_key[BTN_GRIPL2]) { + out->left_paddle2.kind = EMappingKind_Button; + out->left_paddle2.target = joystick->hwdata->key_map[BTN_GRIPL2]; + mapped |= MAPPED_LEFT_PADDLE2; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_GRIPL2)", out->left_paddle2.target); +#endif + } + + if (mapped != MAPPED_PADDLE_ALL && SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) { // The Xbox Elite controllers have the paddles as BTN_TRIGGER_HAPPY5 - BTN_TRIGGER_HAPPY8 + // in older drivers if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY5] && joystick->hwdata->has_key[BTN_TRIGGER_HAPPY6] && joystick->hwdata->has_key[BTN_TRIGGER_HAPPY7] && @@ -2667,6 +2784,7 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping out->right_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY6]; out->left_paddle2.kind = EMappingKind_Button; out->left_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY8]; + mapped = MAPPED_PADDLE_ALL; #ifdef DEBUG_GAMEPAD_MAPPING SDL_Log("Mapped RIGHT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY5)", out->right_paddle1.target); SDL_Log("Mapped LEFT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY7)", out->left_paddle1.target); @@ -2674,15 +2792,15 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY8)", out->left_paddle2.target); #endif } + } - // The Xbox Series X controllers have the Share button as KEY_RECORD - if (joystick->hwdata->has_key[KEY_RECORD]) { - out->misc1.kind = EMappingKind_Button; - out->misc1.target = joystick->hwdata->key_map[KEY_RECORD]; + // Xbox Series controllers have the Share button as KEY_RECORD + if (joystick->hwdata->has_key[KEY_RECORD]) { + out->misc1.kind = EMappingKind_Button; + out->misc1.target = joystick->hwdata->key_map[KEY_RECORD]; #ifdef DEBUG_GAMEPAD_MAPPING - SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target); + SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target); #endif - } } // Cache the mapping for later diff --git a/libs/SDL3/src/joystick/linux/SDL_sysjoystick_c.h b/libs/SDL3/src/joystick/linux/SDL_sysjoystick_c.h index ae5384f..d149948 100644 --- a/libs/SDL3/src/joystick/linux/SDL_sysjoystick_c.h +++ b/libs/SDL3/src/joystick/linux/SDL_sysjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/n3ds/SDL_sysjoystick.c b/libs/SDL3/src/joystick/n3ds/SDL_sysjoystick.c index 8396ac5..fda19dd 100644 --- a/libs/SDL3/src/joystick/n3ds/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/n3ds/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/ps2/SDL_sysjoystick.c b/libs/SDL3/src/joystick/ps2/SDL_sysjoystick.c index b938b1f..e8ac2bb 100644 --- a/libs/SDL3/src/joystick/ps2/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/ps2/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -191,6 +191,66 @@ static SDL_JoystickID PS2_JoystickGetDeviceInstanceID(int device_index) return device_index + 1; } +static void PS2_WaitPadReady(int port, int slot) +{ + int state = padGetState(port, slot); + while ((state != PAD_STATE_STABLE) && (state != PAD_STATE_FINDCTP1)) { + SDL_Delay(1); + state = padGetState(port, slot); + } +} + +static void PS2_InitializePad(int port, int slot) +{ + int modes; + int i; + char actAlign[6]; + + PS2_WaitPadReady(port, slot); + + // How many different modes can this device operate in? + modes = padInfoMode(port, slot, PAD_MODETABLE, -1); + + // Verify that the controller has a DUAL SHOCK mode + for (i = 0; i < modes; i++) { + if (padInfoMode(port, slot, PAD_MODETABLE, i) == PAD_TYPE_DUALSHOCK) { + break; + } + } + if (i >= modes) { + // This is no Dual Shock controller + return; + } + + // If ExId != 0x0 => This controller has actuator engines + // This check should always pass if the Dual Shock test above passed + if (!padInfoMode(port, slot, PAD_MODECUREXID, 0)) { + // This is no Dual Shock controller?? + return; + } + + // When using MMODE_LOCK, user cant change mode with Select button + padSetMainMode(port, slot, PAD_MMODE_DUALSHOCK, PAD_MMODE_LOCK); + + PS2_WaitPadReady(port, slot); + padEnterPressMode(port, slot); + + PS2_WaitPadReady(port, slot); + if (padInfoAct(port, slot, -1, 0)) { + actAlign[0] = 0; // Enable small engine + actAlign[1] = 1; // Enable big engine + actAlign[2] = 0xff; + actAlign[3] = 0xff; + actAlign[4] = 0xff; + actAlign[5] = 0xff; + + PS2_WaitPadReady(port, slot); + padSetActAlign(port, slot, actAlign); + } + + PS2_WaitPadReady(port, slot); +} + /* Function to open a joystick for use. The joystick to open is specified by the device index. This should fill the nbuttons and naxes fields of the joystick structure. @@ -208,6 +268,8 @@ static bool PS2_JoystickOpen(SDL_Joystick *joystick, int device_index) return false; } } + PS2_InitializePad(info->port, info->slot); + joystick->nbuttons = PS2_BUTTONS; joystick->naxes = PS2_TOTAL_AXIS; joystick->nhats = 0; diff --git a/libs/SDL3/src/joystick/psp/SDL_sysjoystick.c b/libs/SDL3/src/joystick/psp/SDL_sysjoystick.c index 8a0154c..feeebe6 100644 --- a/libs/SDL3/src/joystick/psp/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/psp/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/usb_ids.h b/libs/SDL3/src/joystick/usb_ids.h index 4c85b6d..29c9fc3 100644 --- a/libs/SDL3/src/joystick/usb_ids.h +++ b/libs/SDL3/src/joystick/usb_ids.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -32,6 +32,8 @@ #define USB_VENDOR_BACKBONE 0x358a #define USB_VENDOR_GAMESIR 0x3537 #define USB_VENDOR_DRAGONRISE 0x0079 +#define USB_VENDOR_FLYDIGI_V1 0x04b4 +#define USB_VENDOR_FLYDIGI_V2 0x37d7 #define USB_VENDOR_GOOGLE 0x18d1 #define USB_VENDOR_HORI 0x0f0d #define USB_VENDOR_HP 0x03f0 @@ -58,25 +60,36 @@ #define USB_VENDOR_SWITCH 0x2563 #define USB_VENDOR_VALVE 0x28de #define USB_VENDOR_ZEROPLUS 0x0c12 +#define USB_VENDOR_RASPBERRYPI 0x2e8a // Commercial hardware from various companies are registered under this VID +#define USB_VENDOR_ZUIKI 0x33dd -#define USB_PRODUCT_8BITDO_XBOX_CONTROLLER1 0x2002 // Ultimate Wired Controller for Xbox -#define USB_PRODUCT_8BITDO_XBOX_CONTROLLER2 0x3106 // Ultimate Wireless / Pro 2 Wired Controller +#define USB_PRODUCT_8BITDO_SF30_PRO 0x6000 // B + START +#define USB_PRODUCT_8BITDO_SF30_PRO_BT 0x6100 // B + START +#define USB_PRODUCT_8BITDO_SN30_PRO 0x6001 // B + START +#define USB_PRODUCT_8BITDO_SN30_PRO_BT 0x6101 // B + START +#define USB_PRODUCT_8BITDO_PRO_2 0x6003 // mode switch to D +#define USB_PRODUCT_8BITDO_PRO_2_BT 0x6006 // mode switch to D +#define USB_PRODUCT_8BITDO_PRO_3 0x6009 // mode switch to D +#define USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS 0x6012 // mode switch to BT #define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419 #define USB_PRODUCT_ASTRO_C40_XBOX360 0x0024 #define USB_PRODUCT_BACKBONE_ONE_IOS 0x0103 #define USB_PRODUCT_BACKBONE_ONE_IOS_PS5 0x0104 -#define USB_PRODUCT_GAMESIR_G7 0x1001 +#define USB_PRODUCT_BDA_XB1_CLASSIC 0x581a +#define USB_PRODUCT_BDA_XB1_FIGHTPAD 0x791a +#define USB_PRODUCT_BDA_XB1_SPECTRA_PRO 0x592a #define USB_PRODUCT_GOOGLE_STADIA_CONTROLLER 0x9400 #define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 0x1843 -#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 0x1846 -#define USB_PRODUCT_HORI_FIGHTING_COMMANDER_OCTA_SERIES_X 0x0150 -#define USB_PRODUCT_HORI_HORIPAD_PRO_SERIES_X 0x014f +#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 0x1844 +#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3 0x1846 +#define USB_PRODUCT_FLYDIGI_V1_GAMEPAD 0x2412 +#define USB_PRODUCT_FLYDIGI_V2_APEX 0x2501 +#define USB_PRODUCT_FLYDIGI_V2_VADER 0x2401 #define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS4 0x011c #define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS5 0x0184 #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 @@ -94,15 +107,25 @@ #define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR 0x2008 // Used by joycond #define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT 0x2007 #define USB_PRODUCT_NINTENDO_SWITCH_PRO 0x2009 +#define USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER 0x2073 +#define USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT 0x2067 +#define USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR 0x2068 +#define USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT 0x2066 +#define USB_PRODUCT_NINTENDO_SWITCH2_PRO 0x2069 #define USB_PRODUCT_NINTENDO_WII_REMOTE 0x0306 #define USB_PRODUCT_NINTENDO_WII_REMOTE2 0x0330 #define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103 0x7210 #define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104 0x7214 +#define USB_PRODUCT_PDP_ROCK_CANDY 0x0246 +#define USB_PRODUCT_PDP_REALMZ_WIRELESS 0x018c +#define USB_PRODUCT_POWERA_MINI 0x541a #define USB_PRODUCT_RAZER_ATROX 0x0a00 #define USB_PRODUCT_RAZER_KITSUNE 0x1012 #define USB_PRODUCT_RAZER_PANTHERA 0x0401 #define USB_PRODUCT_RAZER_PANTHERA_EVO 0x1008 #define USB_PRODUCT_RAZER_RAIJU 0x1000 +#define USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRED 0x1024 +#define USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRELESS 0x1026 #define USB_PRODUCT_RAZER_TOURNAMENT_EDITION_USB 0x1007 #define USB_PRODUCT_RAZER_TOURNAMENT_EDITION_BLUETOOTH 0x100a #define USB_PRODUCT_RAZER_ULTIMATE_EDITION_USB 0x1004 @@ -114,7 +137,6 @@ #define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_XBOX_WIRED 0x1010 #define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_XBOX_WIRELESS 0x1011 #define USB_PRODUCT_RAZER_WOLVERINE_V3_PRO 0x0a3f -#define USB_PRODUCT_ROG_RAIKIRI 0x1a38 #define USB_PRODUCT_SAITEK_CYBORG_V3 0xf622 #define USB_PRODUCT_SHANWAN_DS3 0x0523 #define USB_PRODUCT_SONY_DS3 0x0268 @@ -124,12 +146,13 @@ #define USB_PRODUCT_SONY_DS4_STRIKEPAD 0x05c5 #define USB_PRODUCT_SONY_DS5 0x0ce6 #define USB_PRODUCT_SONY_DS5_EDGE 0x0df2 +#define USB_PRODUCT_STEALTH_ULTRA_WIRED 0x7073 #define USB_PRODUCT_SWITCH_RETROBIT_CONTROLLER 0x0575 #define USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_PS4 0xd00e -#define USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_SERIES_X 0xd012 -#define USB_PRODUCT_TURTLE_BEACH_SERIES_X_REACT_R 0x7013 -#define USB_PRODUCT_TURTLE_BEACH_SERIES_X_RECON 0x7009 +#define USB_PRODUCT_THRUSTMASTER_T_FLIGHT_HOTAS_ONE 0xb68c #define USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE 0x1142 +#define USB_PRODUCT_VALVE_STEAM_PROTEUS_DONGLE 0x1304 +#define USB_PRODUCT_VALVE_STEAM_NEREID_DONGLE 0x1305 #define USB_PRODUCT_VICTRIX_FS_PRO 0x0203 #define USB_PRODUCT_VICTRIX_FS_PRO_V2 0x0207 #define USB_PRODUCT_XBOX360_XUSB_CONTROLLER 0x02a1 // XUSB driver software PID @@ -150,23 +173,22 @@ #define USB_PRODUCT_XBOX_ONE_S_REV2_BLE 0x0b20 #define USB_PRODUCT_XBOX_SERIES_X 0x0b12 #define USB_PRODUCT_XBOX_SERIES_X_BLE 0x0b13 -#define USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX 0x08b6 -#define USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX_RGB 0x07a0 -#define USB_PRODUCT_XBOX_SERIES_X_PDP_AFTERGLOW 0x02da -#define USB_PRODUCT_XBOX_SERIES_X_PDP_BLUE 0x02d9 -#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO2 0x4001 -#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO4 0x400b -#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_USB 0x4014 -#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_DONGLE 0x4016 -#define USB_PRODUCT_XBOX_SERIES_X_POWERA_MOGA_XP_ULTRA 0x890b -#define USB_PRODUCT_XBOX_SERIES_X_POWERA_SPECTRA 0x4002 -#define USB_PRODUCT_XBOX_SERIES_X_VICTRIX_GAMBIT 0x02d6 #define USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER 0x02ff // XBOXGIP driver software PID #define USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD 0x11ff +#define USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC 0x10c6 +#define USB_PRODUCT_HANDHELDLEGEND_PROGCC 0x10df +#define USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE 0x10dd +#define USB_PRODUCT_BONZIRICHANNEL_FIREBIRD 0x10e0 +#define USB_PRODUCT_ZUIKI_MASCON_PRO 0x0006 +#define USB_PRODUCT_VOIDGAMING_PS4FIREBIRD 0x10e5 // USB usage pages #define USB_USAGEPAGE_GENERIC_DESKTOP 0x0001 +#define USB_USAGEPAGE_SIMULATION 0x0002 +#define USB_USAGEPAGE_DEVICE_CONTROLS 0x0006 #define USB_USAGEPAGE_BUTTON 0x0009 +#define USB_USAGEPAGE_CONSUMER 0x000C +#define USB_USAGEPAGE_VENDOR_FLYDIGI 0xFFA0 // USB usages for USAGE_PAGE_GENERIC_DESKTOP #define USB_USAGE_GENERIC_POINTER 0x0001 @@ -187,6 +209,22 @@ #define USB_USAGE_GENERIC_WHEEL 0x0038 #define USB_USAGE_GENERIC_HAT 0x0039 +// USB usages for USB_USAGEPAGE_SIMULATION +#define USB_USAGE_SIMULATION_ACCELERATOR 0x00C4 +#define USB_USAGE_SIMULATION_BRAKE 0x00C5 + +// USB usages for USB_USAGEPAGE_DEVICE_CONTROLS +#define USB_USAGE_DEVICE_CONTROLS_BATTERY_STRENGTH 0x0020 + +// USB usages for USB_USAGEPAGE_CONSUMER +#define USB_USAGE_CONSUMER_ASSIGN_SELECTION 0x0081 +#define USB_USAGE_CONSUMER_ORDER_MOVIE 0x0085 +#define USB_USAGE_CONSUMER_RECORD 0x00B2 +#define USB_USAGE_CONSUMER_AC_HOME 0x0223 +#define USB_USAGE_CONSUMER_AC_BACK 0x0224 + +#define MAKE_USAGE(PAGE, USAGE) (((Uint32)PAGE) << 16 | USAGE) + /* Bluetooth SIG assigned Company Identifiers https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers/ */ #define BLUETOOTH_VENDOR_AMAZON 0x0171 diff --git a/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick.c b/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick.c index 6925662..2655dbd 100644 --- a/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick.c +++ b/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -138,11 +138,11 @@ SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *des SDL_AssertJoysticksLocked(); - if (!desc) { + CHECK_PARAM(!desc) { SDL_InvalidParamError("desc"); return 0; } - if (desc->version < sizeof(*desc)) { + CHECK_PARAM(desc->version < sizeof(*desc)) { // Update this to handle older versions of this interface SDL_SetError("Invalid desc, should be initialized with SDL_INIT_INTERFACE()"); return 0; @@ -471,7 +471,7 @@ bool SDL_SendJoystickVirtualSensorDataInner(SDL_Joystick *joystick, SDL_SensorTy return false; } hwdata->sensor_events = sensor_events; - hwdata->max_sensor_events = hwdata->max_sensor_events; + hwdata->max_sensor_events = new_max_sensor_events; } VirtualSensorEvent *event = &hwdata->sensor_events[hwdata->num_sensor_events++]; diff --git a/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick_c.h b/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick_c.h index 14fe59e..f5ea107 100644 --- a/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick_c.h +++ b/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/vita/SDL_sysjoystick.c b/libs/SDL3/src/joystick/vita/SDL_sysjoystick.c index 6a69411..59f553f 100644 --- a/libs/SDL3/src/joystick/vita/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/vita/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c b/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c index 5b4b6fc..62ffbb4 100644 --- a/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c +++ b/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -45,6 +45,7 @@ extern HWND SDL_HelperWindow; // local variables static bool coinitialized = false; static LPDIRECTINPUT8 dinput = NULL; +static bool has_broken_EZFRD64DLL = false; // Taken from Wine - Thanks! static DIOBJECTDATAFORMAT dfDIJoystick2[] = { @@ -289,7 +290,7 @@ static bool QueryDeviceName(LPDIRECTINPUTDEVICE8 device, Uint16 vendor_id, Uint1 } *manufacturer_string = NULL; - *product_string = WIN_StringToUTF8(dipstr.wsz); + *product_string = WIN_StringToUTF8W(dipstr.wsz); return true; } @@ -437,6 +438,29 @@ bool SDL_DINPUT_JoystickInit(void) dinput = NULL; return SetDIerror("IDirectInput::Initialize", result); } + +#ifdef _WIN64 + if (SDL_GetHintBoolean("SDL_JOYSTICK_CHECK_EZFRD64", true)) { + // The 64-bit version of EZFRD64.DLL crashes after being loaded, + // which happens implicitly when querying the device capabilities, + // so make sure we don't do that if there's a possibility of crashing + static const char *directories[] = { + "C:/Windows/USB_Vibration", + "C:/Windows/USB Vibration" + }; + for (int i = 0; i < SDL_arraysize(directories) && !has_broken_EZFRD64DLL; ++i) { + int count = 0; + char **files = SDL_GlobDirectory(directories[i], "*/EZFRD64.DLL", SDL_GLOB_CASEINSENSITIVE, &count); + if (count > 0) { + has_broken_EZFRD64DLL = true; + } + SDL_free(files); + } + if (has_broken_EZFRD64DLL) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Broken EZFRD64.DLL detected, disabling DirectInput force feedback"); + } + } +#endif return true; } @@ -778,12 +802,14 @@ bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joysti return SetDIerror("IDirectInputDevice8::SetDataFormat", result); } - // Get device capabilities - result = - IDirectInputDevice8_GetCapabilities(joystick->hwdata->InputDevice, - &joystick->hwdata->Capabilities); - if (FAILED(result)) { - return SetDIerror("IDirectInputDevice8::GetCapabilities", result); + if (!has_broken_EZFRD64DLL) { + // Get device capabilities to see if we are force feedback capable + result = + IDirectInputDevice8_GetCapabilities(joystick->hwdata->InputDevice, + &joystick->hwdata->Capabilities); + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::GetCapabilities", result); + } } // Force capable? diff --git a/libs/SDL3/src/joystick/windows/SDL_dinputjoystick_c.h b/libs/SDL3/src/joystick/windows/SDL_dinputjoystick_c.h index 0643ce1..6f89f72 100644 --- a/libs/SDL3/src/joystick/windows/SDL_dinputjoystick_c.h +++ b/libs/SDL3/src/joystick/windows/SDL_dinputjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c b/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c index 8590d9a..c8cd9cd 100644 --- a/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c +++ b/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 2025 Sam Lantinga + Copyright (C) 2026 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 @@ -923,12 +923,8 @@ static void RAWINPUT_AddDevice(HANDLE hDevice) device->name = SDL_CreateJoystickName(device->vendor_id, device->product_id, manufacturer_string, product_string); device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, device->vendor_id, device->product_id, device->version, manufacturer_string, product_string, 'r', 0); - if (manufacturer_string) { - SDL_free(manufacturer_string); - } - if (product_string) { - SDL_free(product_string); - } + SDL_free(manufacturer_string); + SDL_free(product_string); } device->path = SDL_strdup(dev_name); @@ -963,12 +959,8 @@ err: CloseHandle(hFile); } if (device) { - if (device->name) { - SDL_free(device->name); - } - if (device->path) { - SDL_free(device->path); - } + SDL_free(device->name); + SDL_free(device->path); SDL_free(device); } #undef CHECK diff --git a/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick_c.h b/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick_c.h index b67544b..dc6e5ff 100644 --- a/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick_c.h +++ b/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -25,8 +25,8 @@ extern bool RAWINPUT_IsEnabled(void); // Registers for input events -extern int RAWINPUT_RegisterNotifications(HWND hWnd); -extern int RAWINPUT_UnregisterNotifications(void); +extern bool RAWINPUT_RegisterNotifications(HWND hWnd); +extern bool RAWINPUT_UnregisterNotifications(void); // Returns 0 if message was handled extern LRESULT CALLBACK RAWINPUT_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); diff --git a/libs/SDL3/src/joystick/windows/SDL_windows_gaming_input.c b/libs/SDL3/src/joystick/windows/SDL_windows_gaming_input.c index dbc5658..4608bba 100644 --- a/libs/SDL3/src/joystick/windows/SDL_windows_gaming_input.c +++ b/libs/SDL3/src/joystick/windows/SDL_windows_gaming_input.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -69,6 +69,7 @@ typedef PCWSTR(WINAPI *WindowsGetStringRawBuffer_t)(HSTRING string, UINT32 *leng static struct { + bool ro_initialized; CoIncrementMTAUsage_t CoIncrementMTAUsage; RoGetActivationFactory_t RoGetActivationFactory; WindowsCreateStringReference_t WindowsCreateStringReference; @@ -585,13 +586,14 @@ static bool WGI_JoystickInit(void) { HRESULT hr; - if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, true)) { + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, false)) { return true; } if (FAILED(WIN_RoInitialize())) { return SDL_SetError("RoInitialize() failed"); } + wgi.ro_initialized = true; #define RESOLVE(x) wgi.x = (x##_t)WIN_LoadComBaseFunction(#x); if (!wgi.x) return WIN_SetError("GetProcAddress failed for " #x); RESOLVE(CoIncrementMTAUsage); @@ -971,9 +973,7 @@ static void WGI_JoystickQuit(void) while (wgi.controller_count > 0) { IEventHandler_CRawGameControllerVtbl_InvokeRemoved(&controller_removed.iface, NULL, wgi.controllers[wgi.controller_count - 1].controller); } - if (wgi.controllers) { - SDL_free(wgi.controllers); - } + SDL_free(wgi.controllers); if (wgi.arcade_stick_statics) { __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics_Release(wgi.arcade_stick_statics); @@ -1002,7 +1002,9 @@ static void WGI_JoystickQuit(void) __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_Release(wgi.controller_statics); } - WIN_RoUninitialize(); + if (wgi.ro_initialized) { + WIN_RoUninitialize(); + } SDL_zero(wgi); } diff --git a/libs/SDL3/src/joystick/windows/SDL_windowsjoystick.c b/libs/SDL3/src/joystick/windows/SDL_windowsjoystick.c index e7fbfcb..a7ed293 100644 --- a/libs/SDL3/src/joystick/windows/SDL_windowsjoystick.c +++ b/libs/SDL3/src/joystick/windows/SDL_windowsjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -416,11 +416,15 @@ void WINDOWS_JoystickDetect(void) while (pCurList) { JoyStick_DeviceData *pListNext = NULL; - if (!pCurList->bXInputDevice) { #ifdef SDL_HAPTIC_DINPUT +#ifdef SDL_JOYSTICK_XINPUT + if (!pCurList->bXInputDevice) { SDL_DINPUT_HapticMaybeRemoveDevice(&pCurList->dxdevice); -#endif } +#else + SDL_DINPUT_HapticMaybeRemoveDevice(&pCurList->dxdevice); +#endif +#endif SDL_PrivateJoystickRemoved(pCurList->nInstanceID); @@ -432,11 +436,15 @@ void WINDOWS_JoystickDetect(void) for (pCurList = SYS_Joystick; pCurList; pCurList = pCurList->pNext) { if (pCurList->send_add_event) { - if (!pCurList->bXInputDevice) { #ifdef SDL_HAPTIC_DINPUT +#ifdef SDL_JOYSTICK_XINPUT + if (!pCurList->bXInputDevice) { SDL_DINPUT_HapticMaybeAddDevice(&pCurList->dxdevice); -#endif } +#else + SDL_DINPUT_HapticMaybeAddDevice(&pCurList->dxdevice); +#endif +#endif SDL_PrivateJoystickAdded(pCurList->nInstanceID); @@ -489,16 +497,18 @@ static int WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) device = device->pNext; } +#ifdef SDL_JOYSTICK_XINPUT if (device->bXInputDevice) { // The slot for XInput devices can change as controllers are seated return SDL_XINPUT_GetSteamVirtualGamepadSlot(device->XInputUserId); - } else { - return device->steam_virtual_gamepad_slot; } +#endif + return device->steam_virtual_gamepad_slot; } static int WINDOWS_JoystickGetDevicePlayerIndex(int device_index) { +#ifdef SDL_JOYSTICK_XINPUT JoyStick_DeviceData *device = SYS_Joystick; int index; @@ -507,6 +517,9 @@ static int WINDOWS_JoystickGetDevicePlayerIndex(int device_index) } return device->bXInputDevice ? (int)device->XInputUserId : -1; +#else + return -1; +#endif } static void WINDOWS_JoystickSetDevicePlayerIndex(int device_index, int player_index) @@ -560,20 +573,22 @@ static bool WINDOWS_JoystickOpen(SDL_Joystick *joystick, int device_index) } joystick->hwdata->guid = device->guid; +#ifdef SDL_JOYSTICK_XINPUT if (device->bXInputDevice) { return SDL_XINPUT_JoystickOpen(joystick, device); - } else { - return SDL_DINPUT_JoystickOpen(joystick, device); } +#endif + return SDL_DINPUT_JoystickOpen(joystick, device); } static bool WINDOWS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) { +#ifdef SDL_JOYSTICK_XINPUT if (joystick->hwdata->bXInputDevice) { return SDL_XINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble); - } else { - return SDL_DINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble); } +#endif + return SDL_DINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble); } static bool WINDOWS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) @@ -602,21 +617,27 @@ static void WINDOWS_JoystickUpdate(SDL_Joystick *joystick) return; } +#ifdef SDL_JOYSTICK_XINPUT if (joystick->hwdata->bXInputDevice) { SDL_XINPUT_JoystickUpdate(joystick); - } else { - SDL_DINPUT_JoystickUpdate(joystick); + return; } +#endif + SDL_DINPUT_JoystickUpdate(joystick); } // Function to close a joystick after use static void WINDOWS_JoystickClose(SDL_Joystick *joystick) { +#ifdef SDL_JOYSTICK_XINPUT if (joystick->hwdata->bXInputDevice) { SDL_XINPUT_JoystickClose(joystick); } else { SDL_DINPUT_JoystickClose(joystick); } +#else + SDL_DINPUT_JoystickClose(joystick); +#endif SDL_free(joystick->hwdata); } diff --git a/libs/SDL3/src/joystick/windows/SDL_windowsjoystick_c.h b/libs/SDL3/src/joystick/windows/SDL_windowsjoystick_c.h index 16b9184..d0ce9fd 100644 --- a/libs/SDL3/src/joystick/windows/SDL_windowsjoystick_c.h +++ b/libs/SDL3/src/joystick/windows/SDL_windowsjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -37,9 +37,11 @@ typedef struct JoyStick_DeviceData char *joystickname; Uint8 send_add_event; SDL_JoystickID nInstanceID; +#ifdef SDL_JOYSTICK_XINPUT bool bXInputDevice; BYTE SubType; Uint8 XInputUserId; +#endif DIDEVICEINSTANCE dxdevice; char path[MAX_PATH]; int steam_virtual_gamepad_slot; @@ -85,10 +87,12 @@ struct joystick_hwdata LPDIRECTINPUTEFFECT ffeffect_ref; #endif +#ifdef SDL_JOYSTICK_XINPUT bool bXInputDevice; // true if this device supports using the xinput API rather than DirectInput bool bXInputHaptic; // Supports force feedback via XInput. Uint8 userid; // XInput userid index for this joystick DWORD dwPacketNumber; +#endif }; #ifdef SDL_JOYSTICK_DINPUT diff --git a/libs/SDL3/src/joystick/windows/SDL_xinputjoystick.c b/libs/SDL3/src/joystick/windows/SDL_xinputjoystick.c index 9f6ce10..8f26bcd 100644 --- a/libs/SDL3/src/joystick/windows/SDL_xinputjoystick.c +++ b/libs/SDL3/src/joystick/windows/SDL_xinputjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/joystick/windows/SDL_xinputjoystick_c.h b/libs/SDL3/src/joystick/windows/SDL_xinputjoystick_c.h index 305b090..9ac1224 100644 --- a/libs/SDL3/src/joystick/windows/SDL_xinputjoystick_c.h +++ b/libs/SDL3/src/joystick/windows/SDL_xinputjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/libm/e_exp.c b/libs/SDL3/src/libm/e_exp.c index f39bb5c..413e360 100644 --- a/libs/SDL3/src/libm/e_exp.c +++ b/libs/SDL3/src/libm/e_exp.c @@ -27,7 +27,7 @@ * the interval [0,0.34658]: * Write * R(r**2) = r*(exp(r)+1)/(exp(r)-1) = 2 + r*r/6 - r**4/360 + ... - * We use a special Reme algorithm on [0,0.34658] to generate + * We use a special Remez algorithm on [0,0.34658] to generate * a polynomial of degree 5 to approximate R. The maximum error * of this polynomial approximation is bounded by 2**-59. In * other words, @@ -76,10 +76,6 @@ #include "math_libm.h" #include "math_private.h" -#ifdef __WATCOMC__ /* Watcom defines huge=__huge */ -#undef huge -#endif - static const double one = 1.0, halF[2] = {0.5,-0.5,}, diff --git a/libs/SDL3/src/libm/e_log.c b/libs/SDL3/src/libm/e_log.c index f935fa7..e6eaf3d 100644 --- a/libs/SDL3/src/libm/e_log.c +++ b/libs/SDL3/src/libm/e_log.c @@ -16,7 +16,7 @@ #endif /* __ieee754_log(x) - * Return the logrithm of x + * Return the logarithm of x * * Method : * 1. Argument Reduction: find k and f such that @@ -27,7 +27,7 @@ * Let s = f/(2+f) ; based on log(1+f) = log(1+s) - log(1-s) * = 2s + 2/3 s**3 + 2/5 s**5 + ....., * = 2s + s*R - * We use a special Reme algorithm on [0,0.1716] to generate + * We use a special Remez algorithm on [0,0.1716] to generate * a polynomial of degree 14 to approximate R The maximum error * of this polynomial approximation is bounded by 2**-58.45. In * other words, diff --git a/libs/SDL3/src/libm/e_pow.c b/libs/SDL3/src/libm/e_pow.c index d1a141e..dc393e1 100644 --- a/libs/SDL3/src/libm/e_pow.c +++ b/libs/SDL3/src/libm/e_pow.c @@ -64,10 +64,6 @@ #pragma warning ( disable : 4756 ) #endif -#ifdef __WATCOMC__ /* Watcom defines huge=__huge */ -#undef huge -#endif - static const double bp[] = {1.0, 1.5,}, dp_h[] = { 0.0, 5.84962487220764160156e-01,}, /* 0x3FE2B803, 0x40000000 */ diff --git a/libs/SDL3/src/libm/e_sqrt.c b/libs/SDL3/src/libm/e_sqrt.c index 8ac58c6..f0add57 100644 --- a/libs/SDL3/src/libm/e_sqrt.c +++ b/libs/SDL3/src/libm/e_sqrt.c @@ -331,7 +331,7 @@ B. sqrt(x) by Reciproot Iteration Let x0 and x1 be the leading and the trailing 32-bit words of a floating point number x (in IEEE double format) respectively - (see section A). By performing shifs and subtracts on x0 and y0, + (see section A). By performing shifts and subtracts on x0 and y0, we obtain a 7.8-bit approximation of 1/sqrt(x) as follows. k := 0x5fe80000 - (x0>>1); diff --git a/libs/SDL3/src/libm/k_rem_pio2.c b/libs/SDL3/src/libm/k_rem_pio2.c index 3dd5b2b..29dd9f3 100644 --- a/libs/SDL3/src/libm/k_rem_pio2.c +++ b/libs/SDL3/src/libm/k_rem_pio2.c @@ -48,7 +48,7 @@ * 64-bit precision 2 * 113-bit precision 3 * The actual value is the sum of them. Thus for 113-bit - * precison, one may have to do something like: + * precision, one may have to do something like: * * long double t,w,r_head, r_tail; * t = (long double)y[2] + (long double)y[1]; diff --git a/libs/SDL3/src/libm/math_libm.h b/libs/SDL3/src/libm/math_libm.h index b7b1614..43f2e5b 100644 --- a/libs/SDL3/src/libm/math_libm.h +++ b/libs/SDL3/src/libm/math_libm.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/libm/s_atan.c b/libs/SDL3/src/libm/s_atan.c index ce429d2..c8a0d01 100644 --- a/libs/SDL3/src/libm/s_atan.c +++ b/libs/SDL3/src/libm/s_atan.c @@ -61,10 +61,6 @@ static const double aT[] = { 1.62858201153657823623e-02, /* 0x3F90AD3A, 0xE322DA11 */ }; -#ifdef __WATCOMC__ /* Watcom defines huge=__huge */ -#undef huge -#endif - static const double one = 1.0, huge = 1.0e300; diff --git a/libs/SDL3/src/libm/s_floor.c b/libs/SDL3/src/libm/s_floor.c index 4809af1..a3f4f2f 100644 --- a/libs/SDL3/src/libm/s_floor.c +++ b/libs/SDL3/src/libm/s_floor.c @@ -25,10 +25,6 @@ #include "math_libm.h" #include "math_private.h" -#ifdef __WATCOMC__ /* Watcom defines huge=__huge */ -#undef huge -#endif - static const double huge = 1.0e300; double floor(double x) diff --git a/libs/SDL3/src/libm/s_scalbn.c b/libs/SDL3/src/libm/s_scalbn.c index b3a0604..799e7fc 100644 --- a/libs/SDL3/src/libm/s_scalbn.c +++ b/libs/SDL3/src/libm/s_scalbn.c @@ -21,10 +21,6 @@ #include "math_private.h" #include -#ifdef __WATCOMC__ /* Watcom defines huge=__huge */ -#undef huge -#endif - static const double two54 = 1.80143985094819840000e+16, /* 0x43500000, 0x00000000 */ twom54 = 5.55111512312578270212e-17, /* 0x3C900000, 0x00000000 */ diff --git a/libs/SDL3/src/loadso/dlopen/SDL_sysloadso.c b/libs/SDL3/src/loadso/dlopen/SDL_sysloadso.c index c84d1a1..7d223ce 100644 --- a/libs/SDL3/src/loadso/dlopen/SDL_sysloadso.c +++ b/libs/SDL3/src/loadso/dlopen/SDL_sysloadso.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/loadso/dummy/SDL_sysloadso.c b/libs/SDL3/src/loadso/dummy/SDL_sysloadso.c index 733386b..ff7db42 100644 --- a/libs/SDL3/src/loadso/dummy/SDL_sysloadso.c +++ b/libs/SDL3/src/loadso/dummy/SDL_sysloadso.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/loadso/windows/SDL_sysloadso.c b/libs/SDL3/src/loadso/windows/SDL_sysloadso.c index 89e4145..d2c2666 100644 --- a/libs/SDL3/src/loadso/windows/SDL_sysloadso.c +++ b/libs/SDL3/src/loadso/windows/SDL_sysloadso.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -29,7 +29,7 @@ SDL_SharedObject *SDL_LoadObject(const char *sofile) { - if (!sofile) { + CHECK_PARAM(!sofile) { SDL_InvalidParamError("sofile"); return NULL; } diff --git a/libs/SDL3/src/locale/SDL_locale.c b/libs/SDL3/src/locale/SDL_locale.c index 09e011b..0cdd101 100644 --- a/libs/SDL3/src/locale/SDL_locale.c +++ b/libs/SDL3/src/locale/SDL_locale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/locale/SDL_syslocale.h b/libs/SDL3/src/locale/SDL_syslocale.h index 40b805d..69ac9fd 100644 --- a/libs/SDL3/src/locale/SDL_syslocale.h +++ b/libs/SDL3/src/locale/SDL_syslocale.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/locale/android/SDL_syslocale.c b/libs/SDL3/src/locale/android/SDL_syslocale.c index da13ee4..f121643 100644 --- a/libs/SDL3/src/locale/android/SDL_syslocale.c +++ b/libs/SDL3/src/locale/android/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/locale/dummy/SDL_syslocale.c b/libs/SDL3/src/locale/dummy/SDL_syslocale.c index f6cf9d7..1bd7702 100644 --- a/libs/SDL3/src/locale/dummy/SDL_syslocale.c +++ b/libs/SDL3/src/locale/dummy/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/locale/emscripten/SDL_syslocale.c b/libs/SDL3/src/locale/emscripten/SDL_syslocale.c index 6983628..0dd86a3 100644 --- a/libs/SDL3/src/locale/emscripten/SDL_syslocale.c +++ b/libs/SDL3/src/locale/emscripten/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -27,7 +27,7 @@ bool SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) { /* *INDENT-OFF* */ // clang-format off - EM_ASM({ + MAIN_THREAD_EM_ASM({ var buf = $0; var buflen = $1; var list = undefined; diff --git a/libs/SDL3/src/locale/haiku/SDL_syslocale.cc b/libs/SDL3/src/locale/haiku/SDL_syslocale.cc index 045b751..6014dc9 100644 --- a/libs/SDL3/src/locale/haiku/SDL_syslocale.cc +++ b/libs/SDL3/src/locale/haiku/SDL_syslocale.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/locale/macos/SDL_syslocale.m b/libs/SDL3/src/locale/macos/SDL_syslocale.m index 6ea1761..aee33d8 100644 --- a/libs/SDL3/src/locale/macos/SDL_syslocale.m +++ b/libs/SDL3/src/locale/macos/SDL_syslocale.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/locale/n3ds/SDL_syslocale.c b/libs/SDL3/src/locale/n3ds/SDL_syslocale.c index 5d0bbd3..52e3742 100644 --- a/libs/SDL3/src/locale/n3ds/SDL_syslocale.c +++ b/libs/SDL3/src/locale/n3ds/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -35,10 +35,12 @@ bool SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) static const char AVAILABLE_LOCALES[][6] = { "ja_JP", "en_US", "fr_FR", "de_DE", "it_IT", "es_ES", "zh_CN", "ko_KR", "nl_NL", "pt_PT", "ru_RU", "zh_TW" }; - u8 current_locale = GetLocaleIndex(); - if (current_locale != BAD_LOCALE) { - SDL_strlcpy(buf, AVAILABLE_LOCALES[current_locale], buflen); + const u8 current_locale = GetLocaleIndex(); + if ((current_locale == BAD_LOCALE) || (current_locale >= SDL_arraysize(AVAILABLE_LOCALES))) { + return SDL_SetError("Could not obtain system locale"); } + + SDL_strlcpy(buf, AVAILABLE_LOCALES[current_locale], buflen); return true; } diff --git a/libs/SDL3/src/locale/ngage/SDL_syslocale.cpp b/libs/SDL3/src/locale/ngage/SDL_syslocale.cpp new file mode 100644 index 0000000..d94da01 --- /dev/null +++ b/libs/SDL3/src/locale/ngage/SDL_syslocale.cpp @@ -0,0 +1,307 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_syslocale.h" +#include "SDL_internal.h" + +#include +#include +#include +#include + +bool SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) +{ + TLanguage language = User::Language(); + const char *locale; + + switch (language) { + case ELangFrench: + case ELangSwissFrench: + locale = "fr_CH"; + break; + case ELangBelgianFrench: + locale = "fr_BE"; + break; + case ELangInternationalFrench: + locale = "fr_FR"; + break; + case ELangGerman: + case ELangSwissGerman: + case ELangAustrian: + locale = "de_DE"; + break; + case ELangSpanish: + case ELangInternationalSpanish: + case ELangLatinAmericanSpanish: + locale = "es_ES"; + break; + case ELangItalian: + case ELangSwissItalian: + locale = "it_IT"; + break; + case ELangSwedish: + case ELangFinlandSwedish: + locale = "sv_SE"; + break; + case ELangDanish: + locale = "da_DK"; + break; + case ELangNorwegian: + case ELangNorwegianNynorsk: + locale = "no_NO"; + break; + case ELangFinnish: + locale = "fi_FI"; + break; + case ELangPortuguese: + locale = "pt_PT"; + break; + case ELangBrazilianPortuguese: + locale = "pt_BR"; + break; + case ELangTurkish: + case ELangCyprusTurkish: + locale = "tr_TR"; + break; + case ELangIcelandic: + locale = "is_IS"; + break; + case ELangRussian: + locale = "ru_RU"; + break; + case ELangHungarian: + locale = "hu_HU"; + break; + case ELangDutch: + locale = "nl_NL"; + break; + case ELangBelgianFlemish: + locale = "nl_BE"; + break; + case ELangAustralian: + case ELangNewZealand: + locale = "en_AU"; + break; + case ELangCzech: + locale = "cs_CZ"; + break; + case ELangSlovak: + locale = "sk_SK"; + break; + case ELangPolish: + locale = "pl_PL"; + break; + case ELangSlovenian: + locale = "sl_SI"; + break; + case ELangTaiwanChinese: + locale = "zh_TW"; + break; + case ELangHongKongChinese: + locale = "zh_HK"; + break; + case ELangPrcChinese: + locale = "zh_CN"; + break; + case ELangJapanese: + locale = "ja_JP"; + break; + case ELangThai: + locale = "th_TH"; + break; + case ELangAfrikaans: + locale = "af_ZA"; + break; + case ELangAlbanian: + locale = "sq_AL"; + break; + case ELangAmharic: + locale = "am_ET"; + break; + case ELangArabic: + locale = "ar_SA"; + break; + case ELangArmenian: + locale = "hy_AM"; + break; + case ELangAzerbaijani: + locale = "az_AZ"; + break; + case ELangBelarussian: + locale = "be_BY"; + break; + case ELangBengali: + locale = "bn_IN"; + break; + case ELangBulgarian: + locale = "bg_BG"; + break; + case ELangBurmese: + locale = "my_MM"; + break; + case ELangCatalan: + locale = "ca_ES"; + break; + case ELangCroatian: + locale = "hr_HR"; + break; + case ELangEstonian: + locale = "et_EE"; + break; + case ELangFarsi: + locale = "fa_IR"; + break; + case ELangCanadianFrench: + locale = "fr_CA"; + break; + case ELangScotsGaelic: + locale = "gd_GB"; + break; + case ELangGeorgian: + locale = "ka_GE"; + break; + case ELangGreek: + case ELangCyprusGreek: + locale = "el_GR"; + break; + case ELangGujarati: + locale = "gu_IN"; + break; + case ELangHebrew: + locale = "he_IL"; + break; + case ELangHindi: + locale = "hi_IN"; + break; + case ELangIndonesian: + locale = "id_ID"; + break; + case ELangIrish: + locale = "ga_IE"; + break; + case ELangKannada: + locale = "kn_IN"; + break; + case ELangKazakh: + locale = "kk_KZ"; + break; + case ELangKhmer: + locale = "km_KH"; + break; + case ELangKorean: + locale = "ko_KR"; + break; + case ELangLao: + locale = "lo_LA"; + break; + case ELangLatvian: + locale = "lv_LV"; + break; + case ELangLithuanian: + locale = "lt_LT"; + break; + case ELangMacedonian: + locale = "mk_MK"; + break; + case ELangMalay: + locale = "ms_MY"; + break; + case ELangMalayalam: + locale = "ml_IN"; + break; + case ELangMarathi: + locale = "mr_IN"; + break; + case ELangMoldavian: + locale = "ro_MD"; + break; + case ELangMongolian: + locale = "mn_MN"; + break; + case ELangPunjabi: + locale = "pa_IN"; + break; + case ELangRomanian: + locale = "ro_RO"; + break; + case ELangSerbian: + locale = "sr_RS"; + break; + case ELangSinhalese: + locale = "si_LK"; + break; + case ELangSomali: + locale = "so_SO"; + break; + case ELangSwahili: + locale = "sw_KE"; + break; + case ELangTajik: + locale = "tg_TJ"; + break; + case ELangTamil: + locale = "ta_IN"; + break; + case ELangTelugu: + locale = "te_IN"; + break; + case ELangTibetan: + locale = "bo_CN"; + break; + case ELangTigrinya: + locale = "ti_ET"; + break; + case ELangTurkmen: + locale = "tk_TM"; + break; + case ELangUkrainian: + locale = "uk_UA"; + break; + case ELangUrdu: + locale = "ur_PK"; + break; + case ELangUzbek: + locale = "uz_UZ"; + break; + case ELangVietnamese: + locale = "vi_VN"; + break; + case ELangWelsh: + locale = "cy_GB"; + break; + case ELangZulu: + locale = "zu_ZA"; + break; + case ELangEnglish: + locale = "en_GB"; + break; + case ELangAmerican: + case ELangCanadianEnglish: + case ELangInternationalEnglish: + case ELangSouthAfricanEnglish: + default: + locale = "en_US"; + break; + } + + SDL_strlcpy(buf, locale, buflen); + + return true; +} diff --git a/libs/SDL3/src/locale/psp/SDL_syslocale.c b/libs/SDL3/src/locale/psp/SDL_syslocale.c index 4b5e1b9..b6ec4f7 100644 --- a/libs/SDL3/src/locale/psp/SDL_syslocale.c +++ b/libs/SDL3/src/locale/psp/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/locale/unix/SDL_syslocale.c b/libs/SDL3/src/locale/unix/SDL_syslocale.c index 98c68c3..7c698d3 100644 --- a/libs/SDL3/src/locale/unix/SDL_syslocale.c +++ b/libs/SDL3/src/locale/unix/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/locale/vita/SDL_syslocale.c b/libs/SDL3/src/locale/vita/SDL_syslocale.c index 76a6f6a..edb1d38 100644 --- a/libs/SDL3/src/locale/vita/SDL_syslocale.c +++ b/libs/SDL3/src/locale/vita/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/locale/windows/SDL_syslocale.c b/libs/SDL3/src/locale/windows/SDL_syslocale.c index 7f69c56..0141271 100644 --- a/libs/SDL3/src/locale/windows/SDL_syslocale.c +++ b/libs/SDL3/src/locale/windows/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -23,7 +23,7 @@ #include "../../core/windows/SDL_windows.h" #include "../SDL_syslocale.h" -typedef BOOL(WINAPI *pfnGetUserPreferredUILanguages)(DWORD, PULONG, WCHAR *, PULONG); +typedef BOOL (WINAPI *pfnGetUserPreferredUILanguages)(DWORD, PULONG, WCHAR *, PULONG); #ifndef MUI_LANGUAGE_NAME #define MUI_LANGUAGE_NAME 0x8 #endif diff --git a/libs/SDL3/src/main/SDL_main_callbacks.c b/libs/SDL3/src/main/SDL_main_callbacks.c index 9f3d9c1..757baef 100644 --- a/libs/SDL3/src/main/SDL_main_callbacks.c +++ b/libs/SDL3/src/main/SDL_main_callbacks.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -94,7 +94,7 @@ bool SDL_HasMainCallbacks(void) return false; } -SDL_AppResult SDL_InitMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +SDL_AppResult SDL_InitMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) { SDL_main_iteration_callback = appiter; SDL_main_event_callback = appevent; @@ -147,3 +147,19 @@ void SDL_QuitMainCallbacks(SDL_AppResult result) SDL_Quit(); } +static void SDL_CheckDefaultArgcArgv(int *argc, char ***argv) +{ + if (!*argv) { + static char dummyargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; + static char *argvdummy[2] = { dummyargv0, NULL }; + *argc = 1; + *argv = argvdummy; + } +} + +int SDL_CallMainFunction(int argc, char *argv[], SDL_main_func mainFunction) +{ + SDL_CheckDefaultArgcArgv(&argc, &argv); + SDL_SetMainReady(); + return mainFunction(argc, argv); +} diff --git a/libs/SDL3/src/main/SDL_main_callbacks.h b/libs/SDL3/src/main/SDL_main_callbacks.h index 1fd3725..3a8c30a 100644 --- a/libs/SDL3/src/main/SDL_main_callbacks.h +++ b/libs/SDL3/src/main/SDL_main_callbacks.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -27,6 +27,7 @@ SDL_AppResult SDL_InitMainCallbacks(int argc, char *argv[], SDL_AppInit_func app SDL_AppResult SDL_IterateMainCallbacks(bool pump_events); void SDL_QuitMainCallbacks(SDL_AppResult result); +// Check args and call the main function +extern int SDL_CallMainFunction(int argc, char *argv[], SDL_main_func mainFunction); + #endif // SDL_main_callbacks_h_ - - diff --git a/libs/SDL3/src/main/SDL_runapp.c b/libs/SDL3/src/main/SDL_runapp.c index ddda4a6..c0ccd8f 100644 --- a/libs/SDL3/src/main/SDL_runapp.c +++ b/libs/SDL3/src/main/SDL_runapp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -19,25 +19,22 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "SDL_internal.h" +#include "SDL_main_callbacks.h" -/* Most platforms that use/need SDL_main have their own SDL_RunApp() implementation. - * If not, you can special case it here by appending || defined(__YOUR_PLATFORM__) */ -#if ( !defined(SDL_MAIN_NEEDED) && !defined(SDL_MAIN_AVAILABLE) ) || defined(SDL_PLATFORM_ANDROID) +// Add your platform here if you define a custom SDL_RunApp() implementation +#if !defined(SDL_PLATFORM_WIN32) && \ + !defined(SDL_PLATFORM_GDK) && \ + !defined(SDL_PLATFORM_IOS) && \ + !defined(SDL_PLATFORM_TVOS) && \ + !defined(SDL_PLATFORM_EMSCRIPTEN) && \ + !defined(SDL_PLATFORM_PSP) && \ + !defined(SDL_PLATFORM_PS2) && \ + !defined(SDL_PLATFORM_3DS) -int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved) +int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { (void)reserved; - - if(!argv) - { - // make sure argv isn't NULL, in case some user code doesn't like that - static char dummyargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; - static char* argvdummy[2] = { dummyargv0, NULL }; - argc = 1; - argv = argvdummy; - } - - return mainFunction(argc, argv); + return SDL_CallMainFunction(argc, argv, mainFunction); } #endif diff --git a/libs/SDL3/src/main/emscripten/SDL_sysmain_callbacks.c b/libs/SDL3/src/main/emscripten/SDL_sysmain_callbacks.c index d3b2f95..fca3488 100644 --- a/libs/SDL3/src/main/emscripten/SDL_sysmain_callbacks.c +++ b/libs/SDL3/src/main/emscripten/SDL_sysmain_callbacks.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -24,9 +24,61 @@ #include +// For Emscripten, we let you use SDL_HINT_MAIN_CALLBACK_RATE, because it might be useful to drop it super-low for +// things like loopwave that don't really do much but wait on the audio device, but be warned that browser timers +// are super-unreliable in modern times, so you likely won't hit your desired callback rate with good precision. +// Almost all apps should leave this alone, so we can use requestAnimationFrame, which is intended to run reliably +// at the refresh rate of the user's display. +static Uint32 callback_rate_increment = 0; +static bool iterate_after_waitevent = false; +static bool callback_rate_changed = false; +static void SDLCALL MainCallbackRateHintChanged(void *userdata, const char *name, const char *oldValue, const char *newValue) +{ + callback_rate_changed = true; + iterate_after_waitevent = newValue && (SDL_strcmp(newValue, "waitevent") == 0); + if (iterate_after_waitevent) { + callback_rate_increment = 0; + } else { + const double callback_rate = newValue ? SDL_atof(newValue) : 0.0; + if (callback_rate > 0.0) { + callback_rate_increment = (Uint32) SDL_NS_TO_MS((double) SDL_NS_PER_SECOND / callback_rate); + } else { + callback_rate_increment = 0; + } + } +} + +// just tell us when any new event is pushed on the queue, so we can check a flag for "waitevent" mode. +static bool saw_new_event = false; +static bool SDLCALL EmscriptenMainCallbackEventWatcher(void *userdata, SDL_Event *event) +{ + saw_new_event = true; + return true; +} + static void EmscriptenInternalMainloop(void) { - const SDL_AppResult rc = SDL_IterateMainCallbacks(true); + // callback rate changed? Update emscripten's mainloop iteration speed. + if (callback_rate_changed) { + callback_rate_changed = false; + if (callback_rate_increment == 0) { + emscripten_set_main_loop_timing(EM_TIMING_RAF, 1); + } else { + emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, callback_rate_increment); + } + } + + if (iterate_after_waitevent) { + SDL_PumpEvents(); + if (!saw_new_event) { + // do nothing yet. Note that we're still going to iterate here because we can't block, + // but we can stop the app's iteration from progressing until there's an event. + return; + } + saw_new_event = false; + } + + const SDL_AppResult rc = SDL_IterateMainCallbacks(!iterate_after_waitevent); if (rc != SDL_APP_CONTINUE) { SDL_QuitMainCallbacks(rc); emscripten_cancel_main_loop(); // kill" the mainloop, so it stops calling back into it. @@ -34,11 +86,20 @@ static void EmscriptenInternalMainloop(void) } } -int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +int SDL_EnterAppMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) { - const SDL_AppResult rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit); + SDL_AppResult rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit); if (rc == SDL_APP_CONTINUE) { - emscripten_set_main_loop(EmscriptenInternalMainloop, 0, 0); // run at refresh rate, don't throw an exception since we do an orderly return. + if (!SDL_AddEventWatch(EmscriptenMainCallbackEventWatcher, NULL)) { + rc = SDL_APP_FAILURE; + } else { + SDL_AddHintCallback(SDL_HINT_MAIN_CALLBACK_RATE, MainCallbackRateHintChanged, NULL); + callback_rate_changed = false; + emscripten_set_main_loop(EmscriptenInternalMainloop, 0, 0); // don't throw an exception since we do an orderly return. + if (callback_rate_increment > 0.0) { + emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, callback_rate_increment); + } + } } else { SDL_QuitMainCallbacks(rc); } diff --git a/libs/SDL3/src/main/emscripten/SDL_sysmain_runapp.c b/libs/SDL3/src/main/emscripten/SDL_sysmain_runapp.c index 20dd6eb..0564240 100644 --- a/libs/SDL3/src/main/emscripten/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/emscripten/SDL_sysmain_runapp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -22,11 +22,13 @@ #ifdef SDL_PLATFORM_EMSCRIPTEN +#include "../SDL_main_callbacks.h" + #include EM_JS_DEPS(sdlrunapp, "$dynCall,$stringToNewUTF8"); -int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved) +int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { (void)reserved; @@ -44,13 +46,13 @@ int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserv //console.log("Setting SDL env var '" + key + "' to '" + value + "' ..."); dynCall('iiii', $0, [ckey, cvalue, 1]); } - _free(ckey); // these must use free(), not SDL_free()! - _free(cvalue); + _Emscripten_force_free(ckey); // these must use free(), not SDL_free()! + _Emscripten_force_free(cvalue); } } }, SDL_setenv_unsafe); - return mainFunction(argc, argv); + return SDL_CallMainFunction(argc, argv, mainFunction); } #endif diff --git a/libs/SDL3/src/main/gdk/SDL_sysmain_runapp.cpp b/libs/SDL3/src/main/gdk/SDL_sysmain_runapp.cpp index d7bdea0..b485891 100644 --- a/libs/SDL3/src/main/gdk/SDL_sysmain_runapp.cpp +++ b/libs/SDL3/src/main/gdk/SDL_sysmain_runapp.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -24,66 +24,28 @@ extern "C" { #include "../../core/gdk/SDL_gdk.h" #include "../../core/windows/SDL_windows.h" #include "../../events/SDL_events_c.h" +#include "../SDL_main_callbacks.h" } #include #include #include // CommandLineToArgvW() #include -// Pop up an out of memory message, returns to Windows -static BOOL OutOfMemory(void) -{ - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Out of memory - aborting", NULL); - return FALSE; -} - -/* Gets the arguments with GetCommandLine, converts them to argc and argv - and calls SDL_main */ extern "C" -int SDL_RunApp(int, char**, SDL_main_func mainFunction, void *reserved) +int SDL_RunApp(int argc, char **argv, SDL_main_func mainFunction, void *reserved) { - LPWSTR *argvw; - char **argv; - int i, argc, result; - HRESULT hr; + (void)reserved; + + void *heap_allocated = NULL; + const char *args_error = WIN_CheckDefaultArgcArgv(&argc, &argv, &heap_allocated); + if (args_error) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", args_error, NULL); + return -1; + } + + int result = -1; XTaskQueueHandle taskQueue; - - argvw = CommandLineToArgvW(GetCommandLineW(), &argc); - if (argvw == NULL) { - return OutOfMemory(); - } - - /* Note that we need to be careful about how we allocate/free memory here. - * If the application calls SDL_SetMemoryFunctions(), we can't rely on - * SDL_free() to use the same allocator after SDL_main() returns. - */ - - // Parse it into argv and argc - argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv)); - if (argv == NULL) { - return OutOfMemory(); - } - for (i = 0; i < argc; ++i) { - const int utf8size = WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, NULL, 0, NULL, NULL); - if (!utf8size) { // uhoh? - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL); - return -1; - } - - argv[i] = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, utf8size); // this size includes the null-terminator character. - if (!argv[i]) { - return OutOfMemory(); - } - - if (WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, argv[i], utf8size, NULL, NULL) == 0) { // failed? uhoh! - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL); - return -1; - } - } - argv[i] = NULL; - LocalFree(argvw); - - hr = XGameRuntimeInitialize(); + HRESULT hr = XGameRuntimeInitialize(); if (SUCCEEDED(hr) && SDL_GetGDKTaskQueue(&taskQueue)) { Uint32 titleid = 0; @@ -104,14 +66,12 @@ int SDL_RunApp(int, char**, SDL_main_func mainFunction, void *reserved) SDL_SetError("[GDK] Unable to get titleid. Will not call XblInitialize. Check MicrosoftGame.config!"); } - SDL_SetMainReady(); - if (!GDK_RegisterChangeNotifications()) { return -1; } // Run the application main() code - result = mainFunction(argc, argv); + result = SDL_CallMainFunction(argc, argv, mainFunction); GDK_UnregisterChangeNotifications(); @@ -134,14 +94,11 @@ int SDL_RunApp(int, char**, SDL_main_func mainFunction, void *reserved) #else SDL_assert_always(0 && "[GDK] Could not initialize - aborting"); #endif - result = -1; } - // Free argv, to avoid memory leak - for (i = 0; i < argc; ++i) { - HeapFree(GetProcessHeap(), 0, argv[i]); + if (heap_allocated) { + HeapFree(GetProcessHeap(), 0, heap_allocated); } - HeapFree(GetProcessHeap(), 0, argv); return result; } diff --git a/libs/SDL3/src/main/generic/SDL_sysmain_callbacks.c b/libs/SDL3/src/main/generic/SDL_sysmain_callbacks.c index 716489f..77aa2f5 100644 --- a/libs/SDL3/src/main/generic/SDL_sysmain_callbacks.c +++ b/libs/SDL3/src/main/generic/SDL_sysmain_callbacks.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -25,7 +25,7 @@ #ifndef SDL_PLATFORM_IOS -static int callback_rate_increment = 0; +static Uint64 callback_rate_increment = 0; static bool iterate_after_waitevent = false; static void SDLCALL MainCallbackRateHintChanged(void *userdata, const char *name, const char *oldValue, const char *newValue) @@ -34,9 +34,9 @@ static void SDLCALL MainCallbackRateHintChanged(void *userdata, const char *name if (iterate_after_waitevent) { callback_rate_increment = 0; } else { - const int callback_rate = newValue ? SDL_atoi(newValue) : 0; - if (callback_rate > 0) { - callback_rate_increment = ((Uint64) 1000000000) / ((Uint64) callback_rate); + const double callback_rate = newValue ? SDL_atof(newValue) : 0.0; + if (callback_rate > 0.0) { + callback_rate_increment = (Uint64) ((double) SDL_NS_PER_SECOND / callback_rate); } else { callback_rate_increment = 0; } @@ -51,10 +51,10 @@ static SDL_AppResult GenericIterateMainCallbacks(void) return SDL_IterateMainCallbacks(!iterate_after_waitevent); } -int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +int SDL_EnterAppMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) { SDL_AppResult rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit); - if (rc == 0) { + if (rc == SDL_APP_CONTINUE) { SDL_AddHintCallback(SDL_HINT_MAIN_CALLBACK_RATE, MainCallbackRateHintChanged, NULL); Uint64 next_iteration = callback_rate_increment ? (SDL_GetTicksNS() + callback_rate_increment) : 0; diff --git a/libs/SDL3/src/main/ios/SDL_sysmain_callbacks.m b/libs/SDL3/src/main/ios/SDL_sysmain_callbacks.m index becbda2..544f0d6 100644 --- a/libs/SDL3/src/main/ios/SDL_sysmain_callbacks.m +++ b/libs/SDL3/src/main/ios/SDL_sysmain_callbacks.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -43,6 +43,17 @@ static SDLIosMainCallbacksDisplayLink *globalDisplayLink; { if ((self = [super init])) { self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(appIteration:)]; + // Enable high refresh rates on iOS + // To enable this on phones, you should add the following line to Info.plist: + // CADisableMinimumFrameDurationOnPhone + // If main callbacks are used then this CADisplayLink will affect framerate, not one in SDL_uikitviewcontroller. + if (@available(iOS 15.0, tvOS 15.0, *)) { + const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(SDL_GetPrimaryDisplay()); + if (mode && mode->refresh_rate > 60.0f) { + int frame_rate = (int)mode->refresh_rate; + self.displayLink.preferredFrameRateRange = CAFrameRateRangeMake((frame_rate * 2) / 3, frame_rate, frame_rate); + } + } [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; } return self; @@ -64,7 +75,7 @@ static SDLIosMainCallbacksDisplayLink *globalDisplayLink; // SDL_RunApp will land in UIApplicationMain, which calls SDL_main from postFinishLaunch, which calls this. // When we return from here, we're living in the RunLoop, and a CADisplayLink is firing regularly for us. -int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +int SDL_EnterAppMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) { SDL_AppResult rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit); if (rc == SDL_APP_CONTINUE) { diff --git a/libs/SDL3/src/main/n3ds/SDL_sysmain_runapp.c b/libs/SDL3/src/main/n3ds/SDL_sysmain_runapp.c index 74ead99..a6fed9b 100644 --- a/libs/SDL3/src/main/n3ds/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/n3ds/SDL_sysmain_runapp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -23,17 +23,19 @@ #ifdef SDL_PLATFORM_3DS +#include "../SDL_main_callbacks.h" + #include <3ds.h> -int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved) +int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { int result; + // init osSetSpeedupEnable(true); romfsInit(); - SDL_SetMainReady(); - result = mainFunction(argc, argv); + result = SDL_CallMainFunction(argc, argv, mainFunction); // quit romfsExit(); diff --git a/libs/SDL3/src/main/ngage/SDL_sysmain_callbacks.c b/libs/SDL3/src/main/ngage/SDL_sysmain_callbacks.c new file mode 100644 index 0000000..be17256 --- /dev/null +++ b/libs/SDL3/src/main/ngage/SDL_sysmain_callbacks.c @@ -0,0 +1,31 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_PLATFORM_NGAGE + +int SDL_EnterAppMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +{ + // Intentionally does nothing; Callbacks are called using the RunL() method. + return 0; +} + +#endif // SDL_PLATFORM_NGAGE diff --git a/libs/SDL3/src/main/ngage/SDL_sysmain_main.cpp b/libs/SDL3/src/main/ngage/SDL_sysmain_main.cpp new file mode 100644 index 0000000..fc3ce05 --- /dev/null +++ b/libs/SDL3/src/main/ngage/SDL_sysmain_main.cpp @@ -0,0 +1,199 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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. +*/ +#ifdef __cplusplus +extern "C" { +#endif + +#include "SDL_internal.h" + +extern SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]); +extern SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event); +extern SDL_AppResult SDL_AppIterate(void *appstate); +extern void SDL_AppQuit(void *appstate, SDL_AppResult result); + +#ifdef __cplusplus +} +#endif + +#include +#include +#include +#include + +#include "SDL_sysmain_main.hpp" +#include "../../audio/ngage/SDL_ngageaudio.hpp" +#include "../../render/ngage/SDL_render_ngage_c.hpp" + +CRenderer *gRenderer = 0; + +GLDEF_C TInt E32Main() +{ + // Get args and environment. + int argc = 1; + char *argv[] = { "game", NULL }; + char **envp = NULL; + + // Create lvalue variables for __crt0 arguments. + char **argv_lvalue = argv; + char **envp_lvalue = envp; + + CTrapCleanup *cleanup = CTrapCleanup::New(); + if (!cleanup) + { + return KErrNoMemory; + } + + TRAPD(err, + { + CActiveScheduler *scheduler = new (ELeave) CActiveScheduler(); + CleanupStack::PushL(scheduler); + CActiveScheduler::Install(scheduler); + + TInt posixErr = SpawnPosixServerThread(); + if (posixErr != KErrNone) + { + SDL_Log("Error: Failed to spawn POSIX server thread: %d", posixErr); + User::Leave(posixErr); + } + + __crt0(argc, argv_lvalue, envp_lvalue); + + // Increase heap size. + RHeap *newHeap = User::ChunkHeap(NULL, 7500000, 7500000, KMinHeapGrowBy); + if (!newHeap) + { + SDL_Log("Error: Failed to create new heap"); + User::Leave(KErrNoMemory); + } + CleanupStack::PushL(newHeap); + + RHeap *oldHeap = User::SwitchHeap(newHeap); + + TInt targetLatency = 225; + InitAudio(&targetLatency); + + // Wait until audio is ready. + while (!AudioIsReady()) + { + User::After(100000); // 100ms. + } + + // Create and start the rendering backend. + gRenderer = CRenderer::NewL(); + CleanupStack::PushL(gRenderer); + + // Create and start the SDL main runner. + CSDLmain *mainApp = CSDLmain::NewL(); + CleanupStack::PushL(mainApp); + mainApp->Start(); + + // Start the active scheduler to handle events. + CActiveScheduler::Start(); + + CleanupStack::PopAndDestroy(gRenderer); + CleanupStack::PopAndDestroy(mainApp); + + User::SwitchHeap(oldHeap); + + CleanupStack::PopAndDestroy(newHeap); + CleanupStack::PopAndDestroy(scheduler); + }); + + if (err != KErrNone) + { + SDL_Log("Error: %d", err); + } + + return err; +} + +CSDLmain *CSDLmain::NewL() +{ + CSDLmain *self = new (ELeave) CSDLmain(); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; +} + +CSDLmain::CSDLmain() : CActive(EPriorityLow) {} + +void CSDLmain::ConstructL() +{ + CActiveScheduler::Add(this); +} + +CSDLmain::~CSDLmain() +{ + Cancel(); +} + +void CSDLmain::Start() +{ + SetActive(); + TRequestStatus *status = &iStatus; + User::RequestComplete(status, KErrNone); +} + +void CSDLmain::DoCancel() {} + +static bool callbacks_initialized = false; + +void CSDLmain::RunL() +{ + if (callbacks_initialized) + { + SDL_Event event; + + iResult = SDL_AppIterate(NULL); + if (iResult != SDL_APP_CONTINUE) + { + DeinitAudio(); + SDL_AppQuit(NULL, iResult); + SDL_Quit(); + CActiveScheduler::Stop(); + return; + } + + SDL_PumpEvents(); + if (SDL_PollEvent(&event)) + { + iResult = SDL_AppEvent(NULL, &event); + if (iResult != SDL_APP_CONTINUE) + { + DeinitAudio(); + SDL_AppQuit(NULL, iResult); + SDL_Quit(); + CActiveScheduler::Stop(); + return; + } + } + + Start(); + } + else + { + SDL_SetMainReady(); + SDL_AppInit(NULL, 0, NULL); + callbacks_initialized = true; + Start(); + } +} diff --git a/libs/SDL3/src/main/ngage/SDL_sysmain_main.hpp b/libs/SDL3/src/main/ngage/SDL_sysmain_main.hpp new file mode 100644 index 0000000..018c3e1 --- /dev/null +++ b/libs/SDL3/src/main/ngage/SDL_sysmain_main.hpp @@ -0,0 +1,46 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_sysmain_main_hpp_ +#define SDL_sysmain_main_hpp_ + +#include + +class CSDLmain : public CActive +{ +public: + static CSDLmain *NewL(); + ~CSDLmain(); + + void Start(); + +protected: + void DoCancel() ; + void RunL(); + +private: + CSDLmain(); + void ConstructL(); + SDL_AppResult iResult; +}; + +#endif // SDL_sysmain_main_hpp_ diff --git a/libs/SDL3/src/main/ps2/SDL_sysmain_runapp.c b/libs/SDL3/src/main/ps2/SDL_sysmain_runapp.c index 23443c5..889ccca 100644 --- a/libs/SDL3/src/main/ps2/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/ps2/SDL_sysmain_runapp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -25,6 +25,8 @@ // SDL_RunApp() code for PS2 based on SDL_ps2_main.c, fjtrujy@gmail.com +#include "../SDL_main_callbacks.h" + #include #include #include @@ -64,21 +66,19 @@ static void deinit_drivers(void) deinit_ps2_filesystem_driver(); } -int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved) +int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { - int res; + int result; (void)reserved; prepare_IOP(); init_drivers(); - SDL_SetMainReady(); - - res = mainFunction(argc, argv); + result = SDL_CallMainFunction(argc, argv, mainFunction); deinit_drivers(); - return res; + return result; } #endif // SDL_PLATFORM_PS2 diff --git a/libs/SDL3/src/main/psp/SDL_sysmain_runapp.c b/libs/SDL3/src/main/psp/SDL_sysmain_runapp.c index e89f5ff..08279f4 100644 --- a/libs/SDL3/src/main/psp/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/psp/SDL_sysmain_runapp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -28,6 +28,7 @@ #include #include #include "../../events/SDL_events_c.h" +#include "../SDL_main_callbacks.h" /* If application's main() is redefined as SDL_main, and libSDL_main is linked, then this file will create the standard exit callback, @@ -69,14 +70,13 @@ int sdl_psp_setup_callbacks(void) return thid; } -int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved) +int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { (void)reserved; + sdl_psp_setup_callbacks(); - SDL_SetMainReady(); - - return mainFunction(argc, argv); + return SDL_CallMainFunction(argc, argv, mainFunction); } #endif // SDL_PLATFORM_PSP diff --git a/libs/SDL3/src/main/windows/SDL_sysmain_runapp.c b/libs/SDL3/src/main/windows/SDL_sysmain_runapp.c index d8c3fac..ff50f3d 100644 --- a/libs/SDL3/src/main/windows/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/windows/SDL_sysmain_runapp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -23,76 +23,26 @@ #ifdef SDL_PLATFORM_WIN32 #include "../../core/windows/SDL_windows.h" +#include "../SDL_main_callbacks.h" /* Win32-specific SDL_RunApp(), which does most of the SDL_main work, based on SDL_windows_main.c, placed in the public domain by Sam Lantinga 4/13/98 */ -#include // CommandLineToArgvW() - -// Pop up an out of memory message, returns to Windows -static int OutOfMemory(void) +int MINGW32_FORCEALIGN SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void *reserved) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Out of memory - aborting", NULL); - return -1; -} + (void)reserved; -int MINGW32_FORCEALIGN SDL_RunApp(int _argc, char* _argv[], SDL_main_func mainFunction, void * reserved) -{ - /* Gets the arguments with GetCommandLine, converts them to argc and argv - and calls SDL_main */ - - LPWSTR *argvw; - char **argv; - int i, argc, result; - - (void)_argc; (void)_argv; (void)reserved; - - argvw = CommandLineToArgvW(GetCommandLineW(), &argc); - if (!argvw) { - return OutOfMemory(); - } - - /* Note that we need to be careful about how we allocate/free memory here. - * If the application calls SDL_SetMemoryFunctions(), we can't rely on - * SDL_free() to use the same allocator after SDL_main() returns. - */ - - // Parse it into argv and argc - argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv)); - if (!argv) { - return OutOfMemory(); - } - for (i = 0; i < argc; ++i) { - const int utf8size = WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, NULL, 0, NULL, NULL); - if (!utf8size) { // uhoh? - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL); - return -1; - } - - argv[i] = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, utf8size); // this size includes the null-terminator character. - if (!argv[i]) { - return OutOfMemory(); - } - - if (WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, argv[i], utf8size, NULL, NULL) == 0) { // failed? uhoh! - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL); - return -1; + int result = -1; + void *heap_allocated = NULL; + const char *args_error = WIN_CheckDefaultArgcArgv(&argc, &argv, &heap_allocated); + if (args_error) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", args_error, NULL); + } else { + result = SDL_CallMainFunction(argc, argv, mainFunction); + if (heap_allocated) { + HeapFree(GetProcessHeap(), 0, heap_allocated); } } - argv[i] = NULL; - LocalFree(argvw); - - SDL_SetMainReady(); - - // Run the application main() code - result = mainFunction(argc, argv); - - // Free argv, to avoid memory leak - for (i = 0; i < argc; ++i) { - HeapFree(GetProcessHeap(), 0, argv[i]); - } - HeapFree(GetProcessHeap(), 0, argv); - return result; } diff --git a/libs/SDL3/src/misc/SDL_libusb.c b/libs/SDL3/src/misc/SDL_libusb.c new file mode 100644 index 0000000..82fd95b --- /dev/null +++ b/libs/SDL3/src/misc/SDL_libusb.c @@ -0,0 +1,119 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_libusb.h" + +#ifdef HAVE_LIBUSB + +#ifdef SDL_LIBUSB_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "libusb", + "Support for joysticks through libusb", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_LIBUSB_DYNAMIC +) +#endif + +static SDL_AtomicInt SDL_libusb_refcount; +static bool SDL_libusb_loaded; +static SDL_SharedObject *SDL_libusb_handle; +static SDL_LibUSBContext SDL_libusb_context; + +bool SDL_InitLibUSB(SDL_LibUSBContext **ctx) +{ + if (SDL_AtomicIncRef(&SDL_libusb_refcount) == 0) { +#ifdef SDL_LIBUSB_DYNAMIC + SDL_libusb_handle = SDL_LoadObject(SDL_LIBUSB_DYNAMIC); + if (SDL_libusb_handle) +#endif + { + SDL_libusb_loaded = true; +#ifdef SDL_LIBUSB_DYNAMIC +#define LOAD_LIBUSB_SYMBOL(type, func) \ + if ((SDL_libusb_context.func = (type)SDL_LoadFunction(SDL_libusb_handle, "libusb_" #func)) == NULL) { \ + SDL_libusb_loaded = false; \ + } +#else +#define LOAD_LIBUSB_SYMBOL(type, func) \ + SDL_libusb_context.func = libusb_##func; +#endif + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context **), init) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_context *), exit) + LOAD_LIBUSB_SYMBOL(ssize_t (LIBUSB_CALL *)(libusb_context *, libusb_device ***), get_device_list) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_device **, int), free_device_list) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, struct libusb_device_descriptor *), get_device_descriptor) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, struct libusb_config_descriptor **), get_active_config_descriptor) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, uint8_t, struct libusb_config_descriptor **), get_config_descriptor) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_config_descriptor *), free_config_descriptor) + LOAD_LIBUSB_SYMBOL(uint8_t (LIBUSB_CALL *)(libusb_device *), get_bus_number) +#ifdef SDL_PLATFORM_FREEBSD + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *dev, uint8_t *port_numbers, uint8_t port_numbers_len), get_port_numbers) +#else + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len), get_port_numbers) +#endif + LOAD_LIBUSB_SYMBOL(uint8_t (LIBUSB_CALL *)(libusb_device *), get_device_address) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, libusb_device_handle **), open) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_device_handle *), close) + LOAD_LIBUSB_SYMBOL(libusb_device * (LIBUSB_CALL *)(libusb_device_handle *dev_handle), get_device) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), claim_interface) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), release_interface) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), kernel_driver_active) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), detach_kernel_driver) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), attach_kernel_driver) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), set_auto_detach_kernel_driver) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int, int), set_interface_alt_setting) + LOAD_LIBUSB_SYMBOL(struct libusb_transfer * (LIBUSB_CALL *)(int), alloc_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(struct libusb_transfer *), submit_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(struct libusb_transfer *), cancel_transfer) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_transfer *), free_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, uint8_t, uint8_t, uint16_t, uint16_t, unsigned char *, uint16_t, unsigned int), control_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), interrupt_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), bulk_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *), handle_events) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *, int *), handle_events_completed) + LOAD_LIBUSB_SYMBOL(const char * (LIBUSB_CALL *)(int), error_name) +#undef LOAD_LIBUSB_SYMBOL + } + } + + if (SDL_libusb_loaded) { + *ctx = &SDL_libusb_context; + return true; + } else { + SDL_QuitLibUSB(); + *ctx = NULL; + return false; + } +} + +void SDL_QuitLibUSB(void) +{ + if (SDL_AtomicDecRef(&SDL_libusb_refcount)) { + if (SDL_libusb_handle) { + SDL_UnloadObject(SDL_libusb_handle); + SDL_libusb_handle = NULL; + } + SDL_libusb_loaded = false; + } +} + +#endif // HAVE_LIBUSB diff --git a/libs/SDL3/src/misc/SDL_libusb.h b/libs/SDL3/src/misc/SDL_libusb.h new file mode 100644 index 0000000..9cce444 --- /dev/null +++ b/libs/SDL3/src/misc/SDL_libusb.h @@ -0,0 +1,102 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_LIBUSB +// libusb HIDAPI Implementation + +// Include this now, for our dynamically-loaded libusb context +#include + +typedef struct SDL_LibUSBContext +{ +/* *INDENT-OFF* */ // clang-format off + int (LIBUSB_CALL *init)(libusb_context **ctx); + void (LIBUSB_CALL *exit)(libusb_context *ctx); + ssize_t (LIBUSB_CALL *get_device_list)(libusb_context *ctx, libusb_device ***list); + void (LIBUSB_CALL *free_device_list)(libusb_device **list, int unref_devices); + int (LIBUSB_CALL *get_device_descriptor)(libusb_device *dev, struct libusb_device_descriptor *desc); + int (LIBUSB_CALL *get_active_config_descriptor)(libusb_device *dev, struct libusb_config_descriptor **config); + int (LIBUSB_CALL *get_config_descriptor)( + libusb_device *dev, + uint8_t config_index, + struct libusb_config_descriptor **config + ); + void (LIBUSB_CALL *free_config_descriptor)(struct libusb_config_descriptor *config); + uint8_t (LIBUSB_CALL *get_bus_number)(libusb_device *dev); +#ifdef SDL_PLATFORM_FREEBSD + int (LIBUSB_CALL *get_port_numbers)(libusb_device *dev, uint8_t *port_numbers, uint8_t port_numbers_len); +#else + int (LIBUSB_CALL *get_port_numbers)(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len); +#endif + uint8_t (LIBUSB_CALL *get_device_address)(libusb_device *dev); + int (LIBUSB_CALL *open)(libusb_device *dev, libusb_device_handle **dev_handle); + void (LIBUSB_CALL *close)(libusb_device_handle *dev_handle); + libusb_device *(LIBUSB_CALL *get_device)(libusb_device_handle *dev_handle); + int (LIBUSB_CALL *claim_interface)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *release_interface)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *kernel_driver_active)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *detach_kernel_driver)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *attach_kernel_driver)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *set_auto_detach_kernel_driver)(libusb_device_handle *dev_handle, int enable); + int (LIBUSB_CALL *set_interface_alt_setting)(libusb_device_handle *dev, int interface_number, int alternate_setting); + struct libusb_transfer * (LIBUSB_CALL *alloc_transfer)(int iso_packets); + int (LIBUSB_CALL *submit_transfer)(struct libusb_transfer *transfer); + int (LIBUSB_CALL *cancel_transfer)(struct libusb_transfer *transfer); + void (LIBUSB_CALL *free_transfer)(struct libusb_transfer *transfer); + int (LIBUSB_CALL *control_transfer)( + libusb_device_handle *dev_handle, + uint8_t request_type, + uint8_t bRequest, + uint16_t wValue, + uint16_t wIndex, + unsigned char *data, + uint16_t wLength, + unsigned int timeout + ); + int (LIBUSB_CALL *interrupt_transfer)( + libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *actual_length, + unsigned int timeout + ); + int (LIBUSB_CALL *bulk_transfer)( + libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *transferred, + unsigned int timeout + ); + int (LIBUSB_CALL *handle_events)(libusb_context *ctx); + int (LIBUSB_CALL *handle_events_completed)(libusb_context *ctx, int *completed); + const char * (LIBUSB_CALL *error_name)(int errcode); +/* *INDENT-ON* */ // clang-format on + +} SDL_LibUSBContext; + +extern bool SDL_InitLibUSB(SDL_LibUSBContext **ctx); +extern void SDL_QuitLibUSB(void); + +#endif // HAVE_LIBUSB diff --git a/libs/SDL3/src/misc/SDL_sysurl.h b/libs/SDL3/src/misc/SDL_sysurl.h index b5ee700..103221c 100644 --- a/libs/SDL3/src/misc/SDL_sysurl.h +++ b/libs/SDL3/src/misc/SDL_sysurl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/misc/SDL_url.c b/libs/SDL3/src/misc/SDL_url.c index b3163ab..7ec2883 100644 --- a/libs/SDL3/src/misc/SDL_url.c +++ b/libs/SDL3/src/misc/SDL_url.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -24,7 +24,7 @@ bool SDL_OpenURL(const char *url) { - if (!url) { + CHECK_PARAM(!url) { return SDL_InvalidParamError("url"); } return SDL_SYS_OpenURL(url); diff --git a/libs/SDL3/src/misc/android/SDL_sysurl.c b/libs/SDL3/src/misc/android/SDL_sysurl.c index d79f1e0..8ed17a4 100644 --- a/libs/SDL3/src/misc/android/SDL_sysurl.c +++ b/libs/SDL3/src/misc/android/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/misc/dummy/SDL_sysurl.c b/libs/SDL3/src/misc/dummy/SDL_sysurl.c index 64dcfa9..1828e3e 100644 --- a/libs/SDL3/src/misc/dummy/SDL_sysurl.c +++ b/libs/SDL3/src/misc/dummy/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/misc/emscripten/SDL_sysurl.c b/libs/SDL3/src/misc/emscripten/SDL_sysurl.c index 881de01..7dd4262 100644 --- a/libs/SDL3/src/misc/emscripten/SDL_sysurl.c +++ b/libs/SDL3/src/misc/emscripten/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/misc/haiku/SDL_sysurl.cc b/libs/SDL3/src/misc/haiku/SDL_sysurl.cc index 1ce5955..0372dd9 100644 --- a/libs/SDL3/src/misc/haiku/SDL_sysurl.cc +++ b/libs/SDL3/src/misc/haiku/SDL_sysurl.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/misc/ios/SDL_sysurl.m b/libs/SDL3/src/misc/ios/SDL_sysurl.m index 1221eda..a778be9 100644 --- a/libs/SDL3/src/misc/ios/SDL_sysurl.m +++ b/libs/SDL3/src/misc/ios/SDL_sysurl.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/misc/macos/SDL_sysurl.m b/libs/SDL3/src/misc/macos/SDL_sysurl.m index d41696a..06dd6f0 100644 --- a/libs/SDL3/src/misc/macos/SDL_sysurl.m +++ b/libs/SDL3/src/misc/macos/SDL_sysurl.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/misc/riscos/SDL_sysurl.c b/libs/SDL3/src/misc/riscos/SDL_sysurl.c index f5a92b2..7e80f3b 100644 --- a/libs/SDL3/src/misc/riscos/SDL_sysurl.c +++ b/libs/SDL3/src/misc/riscos/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/misc/unix/SDL_sysurl.c b/libs/SDL3/src/misc/unix/SDL_sysurl.c index e72894b..8745576 100644 --- a/libs/SDL3/src/misc/unix/SDL_sysurl.c +++ b/libs/SDL3/src/misc/unix/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/misc/vita/SDL_sysurl.c b/libs/SDL3/src/misc/vita/SDL_sysurl.c index fb901c3..3f4fb22 100644 --- a/libs/SDL3/src/misc/vita/SDL_sysurl.c +++ b/libs/SDL3/src/misc/vita/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/misc/windows/SDL_sysurl.c b/libs/SDL3/src/misc/windows/SDL_sysurl.c index 8e7bf0b..252768b 100644 --- a/libs/SDL3/src/misc/windows/SDL_sysurl.c +++ b/libs/SDL3/src/misc/windows/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/power/SDL_power.c b/libs/SDL3/src/power/SDL_power.c index 5dde3b1..8308890 100644 --- a/libs/SDL3/src/power/SDL_power.c +++ b/libs/SDL3/src/power/SDL_power.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -84,7 +84,7 @@ static SDL_GetPowerInfo_Impl implementations[] = { SDL_PowerState SDL_GetPowerInfo(int *seconds, int *percent) { #ifndef SDL_POWER_DISABLED - const int total = sizeof(implementations) / sizeof(implementations[0]); + const int total = SDL_arraysize(implementations); SDL_PowerState result = SDL_POWERSTATE_UNKNOWN; int i; #endif diff --git a/libs/SDL3/src/power/SDL_syspower.h b/libs/SDL3/src/power/SDL_syspower.h index 5f8fa53..6ef3e26 100644 --- a/libs/SDL3/src/power/SDL_syspower.h +++ b/libs/SDL3/src/power/SDL_syspower.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/power/android/SDL_syspower.c b/libs/SDL3/src/power/android/SDL_syspower.c index 7f8d3f9..0e602f8 100644 --- a/libs/SDL3/src/power/android/SDL_syspower.c +++ b/libs/SDL3/src/power/android/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/power/emscripten/SDL_syspower.c b/libs/SDL3/src/power/emscripten/SDL_syspower.c index 14928de..c3975be 100644 --- a/libs/SDL3/src/power/emscripten/SDL_syspower.c +++ b/libs/SDL3/src/power/emscripten/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/power/haiku/SDL_syspower.c b/libs/SDL3/src/power/haiku/SDL_syspower.c index c334fa6..5b24024 100644 --- a/libs/SDL3/src/power/haiku/SDL_syspower.c +++ b/libs/SDL3/src/power/haiku/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -56,7 +56,7 @@ bool SDL_GetPowerInfo_Haiku(SDL_PowerState *state, int *seconds, int *percent) return false; // maybe some other method will work? } - SDL_memset(regs, '\0', sizeof(regs)); + SDL_zeroa(regs); regs[0] = APM_FUNC_OFFSET + APM_FUNC_GET_POWER_STATUS; regs[1] = APM_DEVICE_ALL; rc = ioctl(fd, APM_BIOS_CALL, regs); diff --git a/libs/SDL3/src/power/linux/SDL_syspower.c b/libs/SDL3/src/power/linux/SDL_syspower.c index 428be4a..88e7b48 100644 --- a/libs/SDL3/src/power/linux/SDL_syspower.c +++ b/libs/SDL3/src/power/linux/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -536,17 +536,17 @@ static void check_upower_device(DBusConnection *conn, const char *path, SDL_Powe Sint64 si64 = 0; double d = 0.0; - if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Type", DBUS_TYPE_UINT32, &ui32)) { + if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Type", DBUS_TYPE_UINT32, &ui32)) { return; // Don't know _what_ we're looking at. Give up on it. } else if (ui32 != 2) { // 2==Battery return; // we don't care about UPS and such. - } else if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "PowerSupply", DBUS_TYPE_BOOLEAN, &ui32)) { + } else if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "PowerSupply", DBUS_TYPE_BOOLEAN, &ui32)) { return; } else if (!ui32) { return; // we don't care about random devices with batteries, like wireless controllers, etc } - if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "IsPresent", DBUS_TYPE_BOOLEAN, &ui32)) { + if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "IsPresent", DBUS_TYPE_BOOLEAN, &ui32)) { return; } if (!ui32) { @@ -555,9 +555,11 @@ static void check_upower_device(DBusConnection *conn, const char *path, SDL_Powe /* Get updated information on the battery status * This can occasionally fail, and we'll just return slightly stale data in that case */ - SDL_DBus_CallMethodOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Refresh", DBUS_TYPE_INVALID, DBUS_TYPE_INVALID); + SDL_DBus_CallMethodOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Refresh", + DBUS_TYPE_INVALID, + DBUS_TYPE_INVALID); - if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "State", DBUS_TYPE_UINT32, &ui32)) { + if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "State", DBUS_TYPE_UINT32, &ui32)) { st = SDL_POWERSTATE_UNKNOWN; // uh oh } else if (ui32 == 1) { // 1 == charging st = SDL_POWERSTATE_CHARGING; @@ -578,14 +580,14 @@ static void check_upower_device(DBusConnection *conn, const char *path, SDL_Powe } } - if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Percentage", DBUS_TYPE_DOUBLE, &d)) { + if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Percentage", DBUS_TYPE_DOUBLE, &d)) { pct = -1; // some old/cheap batteries don't set this property. } else { pct = (int)d; pct = (pct > 100) ? 100 : pct; // clamp between 0%, 100% } - if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "TimeToEmpty", DBUS_TYPE_INT64, &si64)) { + if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "TimeToEmpty", DBUS_TYPE_INT64, &si64)) { secs = -1; } else { secs = (int)si64; @@ -620,25 +622,41 @@ bool SDL_GetPowerInfo_Linux_org_freedesktop_upower(SDL_PowerState *state, int *s #ifdef SDL_USE_LIBDBUS SDL_DBusContext *dbus = SDL_DBus_GetContext(); + DBusMessage *reply = NULL; char **paths = NULL; + char *path = NULL; int i, numpaths = 0; - if (!dbus || !SDL_DBus_CallMethodOnConnection(dbus->system_conn, UPOWER_DBUS_NODE, UPOWER_DBUS_PATH, UPOWER_DBUS_INTERFACE, "EnumerateDevices", - DBUS_TYPE_INVALID, - DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &numpaths, DBUS_TYPE_INVALID)) { + if (!dbus) { return false; // try a different approach than UPower. } - result = true; // Clearly we can use this interface. - *state = SDL_POWERSTATE_NO_BATTERY; // assume we're just plugged in. - *seconds = -1; - *percent = -1; + if (SDL_DBus_CallMethodOnConnection(dbus->system_conn, &reply, UPOWER_DBUS_NODE, UPOWER_DBUS_PATH, UPOWER_DBUS_INTERFACE, "GetDisplayDevice", + DBUS_TYPE_INVALID, + DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { + result = true; // Clearly we can use this interface. + *state = SDL_POWERSTATE_NO_BATTERY; // assume we're just plugged in. + *seconds = -1; + *percent = -1; - for (i = 0; i < numpaths; i++) { - check_upower_device(dbus->system_conn, paths[i], state, seconds, percent); + check_upower_device(dbus->system_conn, path, state, seconds, percent); + SDL_DBus_FreeReply(&reply); + + } else if (SDL_DBus_CallMethodOnConnection(dbus->system_conn, NULL, UPOWER_DBUS_NODE, UPOWER_DBUS_PATH, UPOWER_DBUS_INTERFACE, "EnumerateDevices", + DBUS_TYPE_INVALID, + DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &numpaths, DBUS_TYPE_INVALID)) { + result = true; // Clearly we can use this interface. + *state = SDL_POWERSTATE_NO_BATTERY; // assume we're just plugged in. + *seconds = -1; + *percent = -1; + + for (i = 0; i < numpaths; i++) { + check_upower_device(dbus->system_conn, paths[i], state, seconds, percent); + } + + dbus->free_string_array(paths); } - dbus->free_string_array(paths); #endif // SDL_USE_LIBDBUS return result; diff --git a/libs/SDL3/src/power/macos/SDL_syspower.c b/libs/SDL3/src/power/macos/SDL_syspower.c index 4764da9..77b2d59 100644 --- a/libs/SDL3/src/power/macos/SDL_syspower.c +++ b/libs/SDL3/src/power/macos/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/power/n3ds/SDL_syspower.c b/libs/SDL3/src/power/n3ds/SDL_syspower.c index 822398a..65d9b02 100644 --- a/libs/SDL3/src/power/n3ds/SDL_syspower.c +++ b/libs/SDL3/src/power/n3ds/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/power/psp/SDL_syspower.c b/libs/SDL3/src/power/psp/SDL_syspower.c index 231411f..a252a93 100644 --- a/libs/SDL3/src/power/psp/SDL_syspower.c +++ b/libs/SDL3/src/power/psp/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/power/uikit/SDL_syspower.h b/libs/SDL3/src/power/uikit/SDL_syspower.h index 41fc43b..94a6f76 100644 --- a/libs/SDL3/src/power/uikit/SDL_syspower.h +++ b/libs/SDL3/src/power/uikit/SDL_syspower.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/power/uikit/SDL_syspower.m b/libs/SDL3/src/power/uikit/SDL_syspower.m index 561f2de..443dbfc 100644 --- a/libs/SDL3/src/power/uikit/SDL_syspower.m +++ b/libs/SDL3/src/power/uikit/SDL_syspower.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/power/vita/SDL_syspower.c b/libs/SDL3/src/power/vita/SDL_syspower.c index 5dc8f5e..722603f 100644 --- a/libs/SDL3/src/power/vita/SDL_syspower.c +++ b/libs/SDL3/src/power/vita/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/power/windows/SDL_syspower.c b/libs/SDL3/src/power/windows/SDL_syspower.c index ab334f3..af0fb99 100644 --- a/libs/SDL3/src/power/windows/SDL_syspower.c +++ b/libs/SDL3/src/power/windows/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/process/SDL_process.c b/libs/SDL3/src/process/SDL_process.c index 3ccf5d5..a1adabd 100644 --- a/libs/SDL3/src/process/SDL_process.c +++ b/libs/SDL3/src/process/SDL_process.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -25,7 +25,7 @@ SDL_Process *SDL_CreateProcess(const char * const *args, bool pipe_stdio) { - if (!args || !args[0] || !args[0][0]) { + CHECK_PARAM(!args || !args[0] || !args[0][0]) { SDL_InvalidParamError("args"); return NULL; } @@ -45,10 +45,18 @@ SDL_Process *SDL_CreateProcess(const char * const *args, bool pipe_stdio) SDL_Process *SDL_CreateProcessWithProperties(SDL_PropertiesID props) { const char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); - if (!args || !args[0] || !args[0][0]) { +#if defined(SDL_PLATFORM_WINDOWS) + const char *cmdline = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, NULL); + CHECK_PARAM((!args || !args[0] || !args[0][0]) && (!cmdline || !cmdline[0])) { + SDL_SetError("Either SDL_PROP_PROCESS_CREATE_ARGS_POINTER or SDL_PROP_PROCESS_CREATE_CMDLINE_STRING must be valid"); + return NULL; + } +#else + CHECK_PARAM(!args || !args[0] || !args[0][0]) { SDL_InvalidParamError("SDL_PROP_PROCESS_CREATE_ARGS_POINTER"); return NULL; } +#endif SDL_Process *process = (SDL_Process *)SDL_calloc(1, sizeof(*process)); if (!process) { @@ -73,7 +81,7 @@ SDL_Process *SDL_CreateProcessWithProperties(SDL_PropertiesID props) SDL_PropertiesID SDL_GetProcessProperties(SDL_Process *process) { - if (!process) { + CHECK_PARAM(!process) { return SDL_InvalidParamError("process"); } return process->props; @@ -90,7 +98,7 @@ void *SDL_ReadProcess(SDL_Process *process, size_t *datasize, int *exitcode) *exitcode = -1; } - if (!process) { + CHECK_PARAM(!process) { SDL_InvalidParamError("process"); return NULL; } @@ -110,7 +118,7 @@ void *SDL_ReadProcess(SDL_Process *process, size_t *datasize, int *exitcode) SDL_IOStream *SDL_GetProcessInput(SDL_Process *process) { - if (!process) { + CHECK_PARAM(!process) { SDL_InvalidParamError("process"); return NULL; } @@ -126,7 +134,7 @@ SDL_IOStream *SDL_GetProcessInput(SDL_Process *process) SDL_IOStream *SDL_GetProcessOutput(SDL_Process *process) { - if (!process) { + CHECK_PARAM(!process) { SDL_InvalidParamError("process"); return NULL; } @@ -142,7 +150,7 @@ SDL_IOStream *SDL_GetProcessOutput(SDL_Process *process) bool SDL_KillProcess(SDL_Process *process, bool force) { - if (!process) { + CHECK_PARAM(!process) { return SDL_InvalidParamError("process"); } @@ -155,7 +163,7 @@ bool SDL_KillProcess(SDL_Process *process, bool force) bool SDL_WaitProcess(SDL_Process *process, bool block, int *exitcode) { - if (!process) { + CHECK_PARAM(!process) { return SDL_InvalidParamError("process"); } diff --git a/libs/SDL3/src/process/SDL_sysprocess.h b/libs/SDL3/src/process/SDL_sysprocess.h index 839ef9d..0bf7150 100644 --- a/libs/SDL3/src/process/SDL_sysprocess.h +++ b/libs/SDL3/src/process/SDL_sysprocess.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -20,6 +20,9 @@ */ #include "SDL_internal.h" +#ifndef SDL_sysprocess_h_ +#define SDL_sysprocess_h_ + typedef struct SDL_ProcessData SDL_ProcessData; struct SDL_Process @@ -35,3 +38,5 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID bool SDL_SYS_KillProcess(SDL_Process *process, bool force); bool SDL_SYS_WaitProcess(SDL_Process *process, bool block, int *exitcode); void SDL_SYS_DestroyProcess(SDL_Process *process); + +#endif // SDL_sysprocess_h_ diff --git a/libs/SDL3/src/process/dummy/SDL_dummyprocess.c b/libs/SDL3/src/process/dummy/SDL_dummyprocess.c index ba69e4a..98064cc 100644 --- a/libs/SDL3/src/process/dummy/SDL_dummyprocess.c +++ b/libs/SDL3/src/process/dummy/SDL_dummyprocess.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/process/posix/SDL_posixprocess.c b/libs/SDL3/src/process/posix/SDL_posixprocess.c index 98b9f75..2d061f9 100644 --- a/libs/SDL3/src/process/posix/SDL_posixprocess.c +++ b/libs/SDL3/src/process/posix/SDL_posixprocess.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -37,6 +37,12 @@ #include "../../io/SDL_iostream_c.h" +#if defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP) && \ + !defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR) +#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR +#define posix_spawn_file_actions_addchdir posix_spawn_file_actions_addchdir_np +#endif + #define READ_END 0 #define WRITE_END 1 @@ -156,6 +162,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); SDL_Environment *env = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, SDL_GetEnvironment()); char **envp = NULL; + const char *working_directory = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING, NULL); SDL_ProcessIO stdin_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL); SDL_ProcessIO stdout_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_INHERITED); SDL_ProcessIO stderr_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_INHERITED); @@ -192,6 +199,30 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID goto posix_spawn_fail_attr; } + if (working_directory) { +#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR +#ifdef SDL_PLATFORM_APPLE + if (__builtin_available(macOS 10.15, *)) { + if (posix_spawn_file_actions_addchdir_np(&fa, working_directory) != 0) { + SDL_SetError("posix_spawn_file_actions_addchdir failed: %s", strerror(errno)); + goto posix_spawn_fail_all; + } + } else { + SDL_SetError("Setting the working directory is only supported on macOS 10.15 and newer"); + goto posix_spawn_fail_all; + } +#else + if (posix_spawn_file_actions_addchdir(&fa, working_directory) != 0) { + SDL_SetError("posix_spawn_file_actions_addchdir failed: %s", strerror(errno)); + goto posix_spawn_fail_all; + } +#endif // SDL_PLATFORM_APPLE +#else + SDL_SetError("Setting the working directory is not supported"); + goto posix_spawn_fail_all; +#endif + } + // Background processes don't have access to the terminal if (process->background) { if (stdin_option == SDL_PROCESS_STDIO_INHERITED) { diff --git a/libs/SDL3/src/process/windows/SDL_windowsprocess.c b/libs/SDL3/src/process/windows/SDL_windowsprocess.c index 221d36d..5c4bde8 100644 --- a/libs/SDL3/src/process/windows/SDL_windowsprocess.c +++ b/libs/SDL3/src/process/windows/SDL_windowsprocess.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -106,9 +106,12 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) len = 0; for (i = 0; args[i]; i++) { const char *a = args[i]; + bool quotes = *a == '\0' || SDL_strpbrk(a, " \r\n\t\v") != NULL; - /* two double quotes to surround an argument with */ - len += 2; + if (quotes) { + /* surround the argument with double quote if it is empty or contains whitespaces */ + len += 2; + } for (; *a; a++) { switch (*a) { @@ -116,8 +119,8 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) len += 2; break; case '\\': - /* only escape backslashes that precede a double quote */ - len += (a[1] == '"' || a[1] == '\0') ? 2 : 1; + /* only escape backslashes that precede a double quote (including the enclosing double quote) */ + len += (a[1] == '"' || (quotes && a[1] == '\0')) ? 2 : 1; break; case ' ': case '^': @@ -149,8 +152,11 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) i_out = 0; for (i = 0; args[i]; i++) { const char *a = args[i]; + bool quotes = *a == '\0' || SDL_strpbrk(a, " \r\n\t\v") != NULL; - result[i_out++] = '"'; + if (quotes) { + result[i_out++] = '"'; + } for (; *a; a++) { switch (*a) { case '"': @@ -163,7 +169,7 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) break; case '\\': result[i_out++] = *a; - if (a[1] == '"' || a[1] == '\0') { + if (a[1] == '"' || (quotes && a[1] == '\0')) { result[i_out++] = *a; } break; @@ -188,7 +194,9 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) break; } } - result[i_out++] = '"'; + if (quotes) { + result[i_out++] = '"'; + } result[i_out++] = ' '; } SDL_assert(i_out == len); @@ -237,8 +245,10 @@ static bool join_env(char **env, LPWSTR *env_out) bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID props) { const char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); + const char *cmdline = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, NULL); SDL_Environment *env = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, SDL_GetEnvironment()); char **envp = NULL; + const char *working_directory = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING, NULL); SDL_ProcessIO stdin_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL); SDL_ProcessIO stdout_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_INHERITED); SDL_ProcessIO stderr_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_INHERITED); @@ -246,6 +256,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID !SDL_HasProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER); LPWSTR createprocess_cmdline = NULL; LPWSTR createprocess_env = NULL; + LPWSTR createprocess_cwd = NULL; STARTUPINFOW startup_info; DWORD creation_flags; SECURITY_ATTRIBUTES security_attributes; @@ -284,7 +295,12 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID security_attributes.bInheritHandle = TRUE; security_attributes.lpSecurityDescriptor = NULL; - if (!join_arguments(args, &createprocess_cmdline)) { + if (cmdline) { + createprocess_cmdline = WIN_UTF8ToString(cmdline); + if (!createprocess_cmdline) { + goto done; + } + } else if (!join_arguments(args, &createprocess_cmdline)) { goto done; } @@ -292,6 +308,13 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID goto done; } + if (working_directory) { + createprocess_cwd = WIN_UTF8ToStringW(working_directory); + if (!createprocess_cwd) { + goto done; + } + } + // Background processes don't have access to the terminal // This isn't necessary on Windows, but we keep the same behavior as the POSIX implementation. if (process->background) { @@ -428,7 +451,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID } } - if (!CreateProcessW(NULL, createprocess_cmdline, NULL, NULL, TRUE, creation_flags, createprocess_env, NULL, &startup_info, &data->process_information)) { + if (!CreateProcessW(NULL, createprocess_cmdline, NULL, NULL, TRUE, creation_flags, createprocess_env, createprocess_cwd, &startup_info, &data->process_information)) { WIN_SetError("CreateProcess"); goto done; } @@ -480,6 +503,7 @@ done: } SDL_free(createprocess_cmdline); SDL_free(createprocess_env); + SDL_free(createprocess_cwd); SDL_free(envp); if (!result) { @@ -496,8 +520,31 @@ done: return result; } +static BOOL CALLBACK terminate_app(HWND hwnd, LPARAM lparam) +{ + DWORD current_proc_id = 0, *term_info = (DWORD *) lparam; + GetWindowThreadProcessId(hwnd, ¤t_proc_id); + if (current_proc_id == term_info[0] && PostMessage(hwnd, WM_CLOSE, 0, 0)) { + term_info[1]++; + } + return TRUE; +} + bool SDL_SYS_KillProcess(SDL_Process *process, bool force) { + if (!force) { + // term_info[0] is the process ID, term_info[1] is number of successful tries + DWORD term_info[2]; + term_info[0] = process->internal->process_information.dwProcessId; + term_info[1] = 0; + EnumWindows(terminate_app, (LPARAM) &term_info); + if (term_info[1] || PostThreadMessage(process->internal->process_information.dwThreadId, WM_CLOSE, 0, 0)) { + return true; + } + if (GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, term_info[0])) { + return true; + } + } if (!TerminateProcess(process->internal->process_information.hProcess, 1)) { return WIN_SetError("TerminateProcess failed"); } diff --git a/libs/SDL3/src/render/SDL_d3dmath.c b/libs/SDL3/src/render/SDL_d3dmath.c deleted file mode 100644 index 6a1ab59..0000000 --- a/libs/SDL3/src/render/SDL_d3dmath.c +++ /dev/null @@ -1,135 +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" - -#if defined(SDL_VIDEO_RENDER_D3D) || \ - defined(SDL_VIDEO_RENDER_D3D11) || \ - defined(SDL_VIDEO_RENDER_D3D12) || \ - defined(SDL_VIDEO_RENDER_VULKAN) - -#include "SDL_d3dmath.h" - -// Direct3D matrix math functions - -Float4X4 MatrixIdentity(void) -{ - Float4X4 m; - SDL_zero(m); - m.v._11 = 1.0f; - m.v._22 = 1.0f; - m.v._33 = 1.0f; - m.v._44 = 1.0f; - return m; -} - -Float4X4 MatrixMultiply(Float4X4 M1, Float4X4 M2) -{ - Float4X4 m; - m.v._11 = M1.v._11 * M2.v._11 + M1.v._12 * M2.v._21 + M1.v._13 * M2.v._31 + M1.v._14 * M2.v._41; - m.v._12 = M1.v._11 * M2.v._12 + M1.v._12 * M2.v._22 + M1.v._13 * M2.v._32 + M1.v._14 * M2.v._42; - m.v._13 = M1.v._11 * M2.v._13 + M1.v._12 * M2.v._23 + M1.v._13 * M2.v._33 + M1.v._14 * M2.v._43; - m.v._14 = M1.v._11 * M2.v._14 + M1.v._12 * M2.v._24 + M1.v._13 * M2.v._34 + M1.v._14 * M2.v._44; - m.v._21 = M1.v._21 * M2.v._11 + M1.v._22 * M2.v._21 + M1.v._23 * M2.v._31 + M1.v._24 * M2.v._41; - m.v._22 = M1.v._21 * M2.v._12 + M1.v._22 * M2.v._22 + M1.v._23 * M2.v._32 + M1.v._24 * M2.v._42; - m.v._23 = M1.v._21 * M2.v._13 + M1.v._22 * M2.v._23 + M1.v._23 * M2.v._33 + M1.v._24 * M2.v._43; - m.v._24 = M1.v._21 * M2.v._14 + M1.v._22 * M2.v._24 + M1.v._23 * M2.v._34 + M1.v._24 * M2.v._44; - m.v._31 = M1.v._31 * M2.v._11 + M1.v._32 * M2.v._21 + M1.v._33 * M2.v._31 + M1.v._34 * M2.v._41; - m.v._32 = M1.v._31 * M2.v._12 + M1.v._32 * M2.v._22 + M1.v._33 * M2.v._32 + M1.v._34 * M2.v._42; - m.v._33 = M1.v._31 * M2.v._13 + M1.v._32 * M2.v._23 + M1.v._33 * M2.v._33 + M1.v._34 * M2.v._43; - m.v._34 = M1.v._31 * M2.v._14 + M1.v._32 * M2.v._24 + M1.v._33 * M2.v._34 + M1.v._34 * M2.v._44; - m.v._41 = M1.v._41 * M2.v._11 + M1.v._42 * M2.v._21 + M1.v._43 * M2.v._31 + M1.v._44 * M2.v._41; - m.v._42 = M1.v._41 * M2.v._12 + M1.v._42 * M2.v._22 + M1.v._43 * M2.v._32 + M1.v._44 * M2.v._42; - m.v._43 = M1.v._41 * M2.v._13 + M1.v._42 * M2.v._23 + M1.v._43 * M2.v._33 + M1.v._44 * M2.v._43; - m.v._44 = M1.v._41 * M2.v._14 + M1.v._42 * M2.v._24 + M1.v._43 * M2.v._34 + M1.v._44 * M2.v._44; - return m; -} - -Float4X4 MatrixScaling(float x, float y, float z) -{ - Float4X4 m; - SDL_zero(m); - m.v._11 = x; - m.v._22 = y; - m.v._33 = z; - m.v._44 = 1.0f; - return m; -} - -Float4X4 MatrixTranslation(float x, float y, float z) -{ - Float4X4 m; - SDL_zero(m); - m.v._11 = 1.0f; - m.v._22 = 1.0f; - m.v._33 = 1.0f; - m.v._44 = 1.0f; - m.v._41 = x; - m.v._42 = y; - m.v._43 = z; - return m; -} - -Float4X4 MatrixRotationX(float r) -{ - float sinR = SDL_sinf(r); - float cosR = SDL_cosf(r); - Float4X4 m; - SDL_zero(m); - m.v._11 = 1.0f; - m.v._22 = cosR; - m.v._23 = sinR; - m.v._32 = -sinR; - m.v._33 = cosR; - m.v._44 = 1.0f; - return m; -} - -Float4X4 MatrixRotationY(float r) -{ - float sinR = SDL_sinf(r); - float cosR = SDL_cosf(r); - Float4X4 m; - SDL_zero(m); - m.v._11 = cosR; - m.v._13 = -sinR; - m.v._22 = 1.0f; - m.v._31 = sinR; - m.v._33 = cosR; - m.v._44 = 1.0f; - return m; -} - -Float4X4 MatrixRotationZ(float r) -{ - float sinR = SDL_sinf(r); - float cosR = SDL_cosf(r); - Float4X4 m; - SDL_zero(m); - m.v._11 = cosR; - m.v._12 = sinR; - m.v._21 = -sinR; - m.v._22 = cosR; - m.v._33 = 1.0f; - m.v._44 = 1.0f; - return m; -} - -#endif // SDL_VIDEO_RENDER_D3D || SDL_VIDEO_RENDER_D3D11 || SDL_VIDEO_RENDER_D3D12 || SDL_VIDEO_RENDER_VULKAN diff --git a/libs/SDL3/src/render/SDL_d3dmath.h b/libs/SDL3/src/render/SDL_d3dmath.h index 84cff9e..7b99869 100644 --- a/libs/SDL3/src/render/SDL_d3dmath.h +++ b/libs/SDL3/src/render/SDL_d3dmath.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -69,13 +69,108 @@ typedef struct }; } Float4X4; -extern Float4X4 MatrixIdentity(void); -extern Float4X4 MatrixMultiply(Float4X4 M1, Float4X4 M2); -extern Float4X4 MatrixScaling(float x, float y, float z); -extern Float4X4 MatrixTranslation(float x, float y, float z); -extern Float4X4 MatrixRotationX(float r); -extern Float4X4 MatrixRotationY(float r); -extern Float4X4 MatrixRotationZ(float r); +static inline Float4X4 MatrixIdentity(void) +{ + Float4X4 m; + SDL_zero(m); + m.v._11 = 1.0f; + m.v._22 = 1.0f; + m.v._33 = 1.0f; + m.v._44 = 1.0f; + return m; +} + +static inline Float4X4 MatrixMultiply(Float4X4 M1, Float4X4 M2) +{ + Float4X4 m; + m.v._11 = M1.v._11 * M2.v._11 + M1.v._12 * M2.v._21 + M1.v._13 * M2.v._31 + M1.v._14 * M2.v._41; + m.v._12 = M1.v._11 * M2.v._12 + M1.v._12 * M2.v._22 + M1.v._13 * M2.v._32 + M1.v._14 * M2.v._42; + m.v._13 = M1.v._11 * M2.v._13 + M1.v._12 * M2.v._23 + M1.v._13 * M2.v._33 + M1.v._14 * M2.v._43; + m.v._14 = M1.v._11 * M2.v._14 + M1.v._12 * M2.v._24 + M1.v._13 * M2.v._34 + M1.v._14 * M2.v._44; + m.v._21 = M1.v._21 * M2.v._11 + M1.v._22 * M2.v._21 + M1.v._23 * M2.v._31 + M1.v._24 * M2.v._41; + m.v._22 = M1.v._21 * M2.v._12 + M1.v._22 * M2.v._22 + M1.v._23 * M2.v._32 + M1.v._24 * M2.v._42; + m.v._23 = M1.v._21 * M2.v._13 + M1.v._22 * M2.v._23 + M1.v._23 * M2.v._33 + M1.v._24 * M2.v._43; + m.v._24 = M1.v._21 * M2.v._14 + M1.v._22 * M2.v._24 + M1.v._23 * M2.v._34 + M1.v._24 * M2.v._44; + m.v._31 = M1.v._31 * M2.v._11 + M1.v._32 * M2.v._21 + M1.v._33 * M2.v._31 + M1.v._34 * M2.v._41; + m.v._32 = M1.v._31 * M2.v._12 + M1.v._32 * M2.v._22 + M1.v._33 * M2.v._32 + M1.v._34 * M2.v._42; + m.v._33 = M1.v._31 * M2.v._13 + M1.v._32 * M2.v._23 + M1.v._33 * M2.v._33 + M1.v._34 * M2.v._43; + m.v._34 = M1.v._31 * M2.v._14 + M1.v._32 * M2.v._24 + M1.v._33 * M2.v._34 + M1.v._34 * M2.v._44; + m.v._41 = M1.v._41 * M2.v._11 + M1.v._42 * M2.v._21 + M1.v._43 * M2.v._31 + M1.v._44 * M2.v._41; + m.v._42 = M1.v._41 * M2.v._12 + M1.v._42 * M2.v._22 + M1.v._43 * M2.v._32 + M1.v._44 * M2.v._42; + m.v._43 = M1.v._41 * M2.v._13 + M1.v._42 * M2.v._23 + M1.v._43 * M2.v._33 + M1.v._44 * M2.v._43; + m.v._44 = M1.v._41 * M2.v._14 + M1.v._42 * M2.v._24 + M1.v._43 * M2.v._34 + M1.v._44 * M2.v._44; + return m; +} + +static inline Float4X4 MatrixScaling(float x, float y, float z) +{ + Float4X4 m; + SDL_zero(m); + m.v._11 = x; + m.v._22 = y; + m.v._33 = z; + m.v._44 = 1.0f; + return m; +} + +static inline Float4X4 MatrixTranslation(float x, float y, float z) +{ + Float4X4 m; + SDL_zero(m); + m.v._11 = 1.0f; + m.v._22 = 1.0f; + m.v._33 = 1.0f; + m.v._44 = 1.0f; + m.v._41 = x; + m.v._42 = y; + m.v._43 = z; + return m; +} + +static inline Float4X4 MatrixRotationX(float r) +{ + float sinR = SDL_sinf(r); + float cosR = SDL_cosf(r); + Float4X4 m; + SDL_zero(m); + m.v._11 = 1.0f; + m.v._22 = cosR; + m.v._23 = sinR; + m.v._32 = -sinR; + m.v._33 = cosR; + m.v._44 = 1.0f; + return m; +} + +static inline Float4X4 MatrixRotationY(float r) +{ + float sinR = SDL_sinf(r); + float cosR = SDL_cosf(r); + Float4X4 m; + SDL_zero(m); + m.v._11 = cosR; + m.v._13 = -sinR; + m.v._22 = 1.0f; + m.v._31 = sinR; + m.v._33 = cosR; + m.v._44 = 1.0f; + return m; +} + +static inline Float4X4 MatrixRotationZ(float r) +{ + float sinR = SDL_sinf(r); + float cosR = SDL_cosf(r); + Float4X4 m; + SDL_zero(m); + m.v._11 = cosR; + m.v._12 = sinR; + m.v._21 = -sinR; + m.v._22 = cosR; + m.v._33 = 1.0f; + m.v._44 = 1.0f; + return m; +} // Ends C function definitions when using C++ #ifdef __cplusplus diff --git a/libs/SDL3/src/render/SDL_render.c b/libs/SDL3/src/render/SDL_render.c index cd3a929..3d1c95f 100644 --- a/libs/SDL3/src/render/SDL_render.c +++ b/libs/SDL3/src/render/SDL_render.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -50,22 +50,22 @@ this should probably be removed at some point in the future. --ryan. */ #define SDL_PROP_TEXTURE_PARENT_POINTER "SDL.internal.texture.parent" #define CHECK_RENDERER_MAGIC_BUT_NOT_DESTROYED_FLAG(renderer, result) \ - if (!SDL_ObjectValid(renderer, SDL_OBJECT_TYPE_RENDERER)) { \ + CHECK_PARAM(!SDL_ObjectValid(renderer, SDL_OBJECT_TYPE_RENDERER)) { \ SDL_InvalidParamError("renderer"); \ return result; \ } -#define CHECK_RENDERER_MAGIC(renderer, result) \ - CHECK_RENDERER_MAGIC_BUT_NOT_DESTROYED_FLAG(renderer, result); \ - if ((renderer)->destroyed) { \ +#define CHECK_RENDERER_MAGIC(renderer, result) \ + CHECK_RENDERER_MAGIC_BUT_NOT_DESTROYED_FLAG(renderer, result); \ + CHECK_PARAM(renderer->destroyed) { \ SDL_SetError("Renderer's window has been destroyed, can't use further"); \ - return result; \ + return result; \ } -#define CHECK_TEXTURE_MAGIC(texture, result) \ - if (!SDL_ObjectValid(texture, SDL_OBJECT_TYPE_TEXTURE)) { \ - SDL_InvalidParamError("texture"); \ - return result; \ +#define CHECK_TEXTURE_MAGIC(texture, result) \ + CHECK_PARAM(!SDL_ObjectValid(texture, SDL_OBJECT_TYPE_TEXTURE)) { \ + SDL_InvalidParamError("texture"); \ + return result; \ } // Predefined blend modes @@ -120,6 +120,9 @@ static const SDL_RenderDriver *render_drivers[] = { #ifdef SDL_VIDEO_RENDER_METAL &METAL_RenderDriver, #endif +#ifdef SDL_VIDEO_RENDER_NGAGE + &NGAGE_RenderDriver, +#endif #ifdef SDL_VIDEO_RENDER_OGL &GL_RenderDriver, #endif @@ -344,6 +347,25 @@ static bool FlushRenderCommandsIfTextureNeeded(SDL_Texture *texture) return true; } +static bool FlushRenderCommandsIfPaletteNeeded(SDL_Renderer *renderer, SDL_TexturePalette *palette) +{ + if (palette->last_command_generation == renderer->render_command_generation) { + // the current command queue depends on this palette, flush the queue now before it changes + return FlushRenderCommands(renderer); + } + return true; +} + +static bool FlushRenderCommandsIfGPURenderStateNeeded(SDL_GPURenderState *state) +{ + SDL_Renderer *renderer = state->renderer; + if (state->last_command_generation == renderer->render_command_generation) { + // the current command queue depends on this state, flush the queue now before it changes + return FlushRenderCommands(renderer); + } + return true; +} + bool SDL_FlushRenderer(SDL_Renderer *renderer) { if (!FlushRenderCommands(renderer)) { @@ -574,7 +596,12 @@ static SDL_RenderCommand *PrepQueueCmdDraw(SDL_Renderer *renderer, const SDL_Ren if (texture) { cmd->data.draw.texture_scale_mode = texture->scaleMode; } - cmd->data.draw.texture_address_mode = SDL_TEXTURE_ADDRESS_CLAMP; + cmd->data.draw.texture_address_mode_u = SDL_TEXTURE_ADDRESS_CLAMP; + cmd->data.draw.texture_address_mode_v = SDL_TEXTURE_ADDRESS_CLAMP; + cmd->data.draw.gpu_render_state = renderer->gpu_render_state; + if (renderer->gpu_render_state) { + renderer->gpu_render_state->last_command_generation = renderer->render_command_generation; + } } } return cmd; @@ -679,6 +706,60 @@ static bool QueueCmdFillRects(SDL_Renderer *renderer, const SDL_FRect *rects, co return result; } +static bool UpdateTexturePalette(SDL_Texture *texture) +{ + SDL_Renderer *renderer = texture->renderer; + SDL_Palette *public = texture->public_palette; + + if (!SDL_ISPIXELFORMAT_INDEXED(texture->format)) { + return true; + } + + if (!public) { + return SDL_SetError("Texture doesn't have a palette"); + } + + if (texture->native) { + // Keep the native texture in sync with palette updates + if (texture->palette_version == public->version) { + return true; + } + + if (!FlushRenderCommandsIfTextureNeeded(texture->native)) { + return false; + } + + SDL_Surface *surface; + bool result = SDL_LockTextureToSurface(texture->native, NULL, &surface); + if (result) { + result = SDL_BlitSurface(texture->palette_surface, NULL, surface, NULL); + SDL_UnlockTexture(texture->native); + } + if (!result) { + return false; + } + texture->palette_version = public->version; + return true; + } + + SDL_TexturePalette *palette = texture->palette; + if (palette->version != public->version) { + // Keep the native palette in sync with palette updates + if (!FlushRenderCommandsIfPaletteNeeded(renderer, palette)) { + return false; + } + + if (!renderer->UpdatePalette(renderer, palette, public->ncolors, public->colors)) { + return false; + } + + palette->version = public->version; + } + + palette->last_command_generation = renderer->render_command_generation; + return true; +} + static bool QueueCmdCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FRect *dstrect) { SDL_RenderCommand *cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_COPY, texture); @@ -713,13 +794,15 @@ static bool QueueCmdGeometry(SDL_Renderer *renderer, SDL_Texture *texture, const float *uv, int uv_stride, int num_vertices, const void *indices, int num_indices, int size_indices, - float scale_x, float scale_y, SDL_TextureAddressMode texture_address_mode) + float scale_x, float scale_y, + SDL_TextureAddressMode texture_address_mode_u, SDL_TextureAddressMode texture_address_mode_v) { SDL_RenderCommand *cmd; bool result = false; cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_GEOMETRY, texture); if (cmd) { - cmd->data.draw.texture_address_mode = texture_address_mode; + cmd->data.draw.texture_address_mode_u = texture_address_mode_u; + cmd->data.draw.texture_address_mode_v = texture_address_mode_v; result = renderer->QueueGeometry(renderer, cmd, texture, xy, xy_stride, color, color_stride, uv, uv_stride, @@ -812,7 +895,7 @@ int SDL_GetNumRenderDrivers(void) const char *SDL_GetRenderDriver(int index) { #ifndef SDL_RENDER_DISABLED - if (index < 0 || index >= SDL_GetNumRenderDrivers()) { + CHECK_PARAM(index < 0 || index >= SDL_GetNumRenderDrivers()) { SDL_InvalidParamError("index"); return NULL; } @@ -865,17 +948,16 @@ static bool SDL_RendererEventWatch(void *userdata, SDL_Event *event) bool SDL_CreateWindowAndRenderer(const char *title, int width, int height, SDL_WindowFlags window_flags, SDL_Window **window, SDL_Renderer **renderer) { - bool hidden = (window_flags & SDL_WINDOW_HIDDEN) != 0; - - if (!window) { + CHECK_PARAM(!window) { return SDL_InvalidParamError("window"); } - if (!renderer) { + CHECK_PARAM(!renderer) { return SDL_InvalidParamError("renderer"); } // Hide the window so if the renderer recreates it, we don't get a visual flash on screen + bool hidden = (window_flags & SDL_WINDOW_HIDDEN) != 0; window_flags |= SDL_WINDOW_HIDDEN; *window = SDL_CreateWindow(title, width, height, window_flags); if (!*window) { @@ -933,7 +1015,7 @@ static SDL_RenderLineMethod SDL_GetRenderLineMethod(void) static void SDL_CalculateSimulatedVSyncInterval(SDL_Renderer *renderer, SDL_Window *window) { - SDL_DisplayID displayID = SDL_GetDisplayForWindow(window); + SDL_DisplayID displayID = window ? SDL_GetDisplayForWindow(window) : 0; const SDL_DisplayMode *mode; int refresh_num, refresh_den; @@ -965,6 +1047,27 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) const char *hint; SDL_PropertiesID new_props; + // The GPU renderer is the only one that can be created without a window or surface + CHECK_PARAM(!window && !surface && (!driver_name || SDL_strcmp(driver_name, SDL_GPU_RENDERER) != 0)) { + SDL_InvalidParamError("window"); + return NULL; + } + + CHECK_PARAM(window && surface) { + SDL_SetError("A renderer can't target both a window and surface"); + return NULL; + } + + CHECK_PARAM(window && SDL_WindowHasSurface(window)) { + SDL_SetError("Surface already associated with window"); + return NULL; + } + + CHECK_PARAM(window && SDL_GetRenderer(window)) { + SDL_SetError("Renderer already associated with window"); + return NULL; + } + #ifdef SDL_PLATFORM_ANDROID if (!Android_WaitActiveAndLockActivity()) { return NULL; @@ -978,21 +1081,6 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) SDL_SetObjectValid(renderer, SDL_OBJECT_TYPE_RENDERER, true); - if ((!window && !surface) || (window && surface)) { - SDL_InvalidParamError("window"); - goto error; - } - - if (window && SDL_WindowHasSurface(window)) { - SDL_SetError("Surface already associated with window"); - goto error; - } - - if (window && SDL_GetRenderer(window)) { - SDL_SetError("Renderer already associated with window"); - goto error; - } - hint = SDL_GetHint(SDL_HINT_RENDER_VSYNC); if (hint && *hint) { SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, SDL_GetHintBoolean(SDL_HINT_RENDER_VSYNC, true)); @@ -1008,6 +1096,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) goto error; } } else { + char *driver_error = NULL; bool rc = false; if (!driver_name) { driver_name = SDL_GetHint(SDL_HINT_RENDER_DRIVER); @@ -1022,10 +1111,20 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) for (int i = 0; render_drivers[i]; i++) { const SDL_RenderDriver *driver = render_drivers[i]; if ((driver_attempt_len == SDL_strlen(driver->name)) && (SDL_strncasecmp(driver->name, driver_attempt, driver_attempt_len) == 0)) { + if (driver_error) { + // Free any previous driver error + SDL_free(driver_error); + driver_error = NULL; + } + rc = driver->CreateRenderer(renderer, window, props); if (rc) { break; } + driver_error = SDL_strdup(SDL_GetError()); + SDL_LogWarn(SDL_LOG_CATEGORY_RENDER, "Couldn't create renderer %s: %s\n", driver->name, driver_error); + SDL_DestroyRendererWithoutFreeing(renderer); + SDL_zerop(renderer); // make sure we don't leave function pointers from a previous CreateRenderer() in this struct. } } @@ -1043,12 +1142,20 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) } } - if (!rc) { + if (rc) { + SDL_DebugLogBackend("render", renderer->name); + SDL_free(driver_error); + } else { if (driver_name) { - SDL_SetError("%s not available", driver_name); + if (driver_error) { + SDL_SetError("%s", driver_error); + } else { + SDL_SetError("%s not available", driver_name); + } } else { SDL_SetError("Couldn't find matching render driver"); } + SDL_free(driver_error); goto error; } } @@ -1076,6 +1183,11 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) UpdatePixelClipRect(renderer, &renderer->main_view); UpdateMainViewDimensions(renderer); + renderer->palettes = SDL_CreateHashTable(0, false, SDL_HashPointer, SDL_KeyMatchPointer, SDL_DestroyHashValue, NULL); + if (!renderer->palettes) { + goto error; + } + // new textures start at zero, so we start at 1 so first render doesn't flush by accident. renderer->render_command_generation = 1; @@ -1086,6 +1198,8 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) renderer->line_method = SDL_GetRenderLineMethod(); } + renderer->scale_mode = SDL_SCALEMODE_LINEAR; + renderer->SDR_white_point = 1.0f; renderer->HDR_headroom = 1.0f; renderer->desired_color_scale = 1.0f; @@ -1110,9 +1224,10 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) SDL_SetPointerProperty(new_props, SDL_PROP_RENDERER_SURFACE_POINTER, surface); } SDL_SetNumberProperty(new_props, SDL_PROP_RENDERER_OUTPUT_COLORSPACE_NUMBER, renderer->output_colorspace); - UpdateHDRProperties(renderer); + SDL_SetBooleanProperty(new_props, SDL_PROP_RENDERER_TEXTURE_WRAPPING_BOOLEAN, !renderer->npot_texture_wrap_unsupported); if (window) { + UpdateHDRProperties(renderer); SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_RENDERER_POINTER, renderer); SDL_AddWindowRenderer(window, renderer); } @@ -1168,12 +1283,38 @@ SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, const char *name) return renderer; } +SDL_Renderer *SDL_CreateGPURenderer(SDL_GPUDevice *device, SDL_Window *window) +{ + SDL_Renderer *renderer; + + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_GPU_DEVICE_POINTER, device); + SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, window); + SDL_SetStringProperty(props, SDL_PROP_RENDERER_CREATE_NAME_STRING, SDL_GPU_RENDERER); + + renderer = SDL_CreateRendererWithProperties(props); + SDL_DestroyProperties(props); + return renderer; +} + +SDL_GPUDevice *SDL_GetGPURendererDevice(SDL_Renderer *renderer) +{ + CHECK_RENDERER_MAGIC(renderer, NULL); + + SDL_GPUDevice *device = SDL_GetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_GPU_DEVICE_POINTER, NULL); + if (!device) { + SDL_SetError("Renderer isn't a GPU renderer"); + return NULL; + } + return device; +} + SDL_Renderer *SDL_CreateSoftwareRenderer(SDL_Surface *surface) { #ifdef SDL_VIDEO_RENDER_SW SDL_Renderer *renderer; - if (!surface) { + CHECK_PARAM(!surface) { SDL_InvalidParamError("surface"); return NULL; } @@ -1233,8 +1374,8 @@ bool SDL_GetRenderOutputSize(SDL_Renderer *renderer, int *w, int *h) } else if (renderer->window) { return SDL_GetWindowSizeInPixels(renderer->window, w, h); } else { - SDL_assert(!"This should never happen"); - return SDL_SetError("Renderer doesn't support querying output size"); + // We don't have any output size, this might be an offscreen-only renderer + return true; } } @@ -1327,11 +1468,13 @@ static SDL_PixelFormat GetClosestSupportedFormat(SDL_Renderer *renderer, SDL_Pix } } else { bool hasAlpha = SDL_ISPIXELFORMAT_ALPHA(format); + bool isIndexed = SDL_ISPIXELFORMAT_INDEXED(format); // We just want to match the first format that has the same channels for (i = 0; i < renderer->num_texture_formats; ++i) { if (!SDL_ISPIXELFORMAT_FOURCC(renderer->texture_formats[i]) && - SDL_ISPIXELFORMAT_ALPHA(renderer->texture_formats[i]) == hasAlpha) { + SDL_ISPIXELFORMAT_ALPHA(renderer->texture_formats[i]) == hasAlpha && + SDL_ISPIXELFORMAT_INDEXED(renderer->texture_formats[i]) == isIndexed) { return renderer->texture_formats[i]; } } @@ -1346,6 +1489,7 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert SDL_TextureAccess access = (SDL_TextureAccess)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STATIC); int w = (int)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, 0); int h = (int)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, 0); + SDL_Palette *palette = (SDL_Palette *)SDL_GetPointerProperty(props, SDL_PROP_TEXTURE_CREATE_PALETTE_POINTER, NULL); SDL_Colorspace default_colorspace; bool texture_is_fourcc_and_target; @@ -1354,22 +1498,21 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert if (!format) { format = renderer->texture_formats[0]; } - if (SDL_BYTESPERPIXEL(format) == 0) { + + CHECK_PARAM(SDL_BYTESPERPIXEL(format) == 0) { SDL_SetError("Invalid texture format"); return NULL; } - if (SDL_ISPIXELFORMAT_INDEXED(format)) { - if (!IsSupportedFormat(renderer, format)) { - SDL_SetError("Palettized textures are not supported"); - return NULL; - } + CHECK_PARAM(SDL_ISPIXELFORMAT_INDEXED(format) && access == SDL_TEXTUREACCESS_TARGET) { + SDL_SetError("Palettized textures can't be render targets"); + return NULL; } - if (w <= 0 || h <= 0) { + CHECK_PARAM(w <= 0 || h <= 0) { SDL_SetError("Texture dimensions can't be 0"); return NULL; } int max_texture_size = (int)SDL_GetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 0); - if (max_texture_size && (w > max_texture_size || h > max_texture_size)) { + CHECK_PARAM(max_texture_size && (w > max_texture_size || h > max_texture_size)) { SDL_SetError("Texture dimensions are limited to %dx%d", max_texture_size, max_texture_size); return NULL; } @@ -1392,7 +1535,7 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert texture->color.b = 1.0f; texture->color.a = 1.0f; texture->blendMode = SDL_ISPIXELFORMAT_ALPHA(format) ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE; - texture->scaleMode = SDL_SCALEMODE_LINEAR; + texture->scaleMode = renderer->scale_mode; texture->view.pixel_w = w; texture->view.pixel_h = h; texture->view.viewport.w = -1; @@ -1438,14 +1581,20 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, SDL_COLORSPACE_JPEG); } else { default_colorspace = SDL_GetDefaultColorspaceForFormat(closest_format); - if (SDL_COLORSPACETYPE(texture->colorspace) == SDL_COLORSPACETYPE(default_colorspace)) { + if (SDL_COLORSPACETYPE(texture->colorspace) == SDL_COLORSPACETYPE(default_colorspace) && + SDL_COLORSPACETRANSFER(texture->colorspace) == SDL_COLORSPACETRANSFER(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); + if (SDL_ISPIXELFORMAT_INDEXED(texture->format)) { + // We're going to be uploading pixels frequently as the palette changes + SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING); + } else { + SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, texture->access); + } SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, texture->w); SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, texture->h); @@ -1471,6 +1620,8 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert texture->next = texture->native; renderer->textures = texture; + SDL_SetTextureScaleMode(texture->native, texture->scaleMode); + if (texture->format == SDL_PIXELFORMAT_MJPG) { // We have a custom decode + upload path for this } else if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) { @@ -1483,6 +1634,12 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert SDL_DestroyTexture(texture); return NULL; } + } else if (SDL_ISPIXELFORMAT_INDEXED(texture->format)) { + texture->palette_surface = SDL_CreateSurface(w, h, texture->format); + if (!texture->palette_surface) { + SDL_DestroyTexture(texture); + return NULL; + } } else if (access == SDL_TEXTUREACCESS_STREAMING) { // The pitch is 4 byte aligned texture->pitch = (((w * SDL_BYTESPERPIXEL(format)) + 3) & ~3); @@ -1494,6 +1651,10 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert } } + if (SDL_ISPIXELFORMAT_INDEXED(texture->format) && palette) { + SDL_SetTexturePalette(texture, palette); + } + // Now set the properties for the new texture props = SDL_GetTextureProperties(texture); SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_COLORSPACE_NUMBER, texture->colorspace); @@ -1523,50 +1684,10 @@ SDL_Texture *SDL_CreateTexture(SDL_Renderer *renderer, SDL_PixelFormat format, S static bool SDL_UpdateTextureFromSurface(SDL_Texture *texture, SDL_Rect *rect, SDL_Surface *surface) { - SDL_TextureAccess access; bool direct_update; - SDL_PixelFormat tex_format; - SDL_PropertiesID surface_props; - SDL_PropertiesID tex_props; - SDL_Colorspace surface_colorspace = SDL_COLORSPACE_UNKNOWN; - SDL_Colorspace texture_colorspace = SDL_COLORSPACE_UNKNOWN; - if (texture == NULL || surface == NULL) { - return false; - } - - tex_props = SDL_GetTextureProperties(texture); - if (!tex_props) { - return false; - } - - surface_props = SDL_GetSurfaceProperties(surface); - if (!surface_props) { - return false; - } - - tex_format = (SDL_PixelFormat)SDL_GetNumberProperty(tex_props, SDL_PROP_TEXTURE_FORMAT_NUMBER, 0); - access = (SDL_TextureAccess)SDL_GetNumberProperty(tex_props, SDL_PROP_TEXTURE_ACCESS_NUMBER, 0); - - if (access != SDL_TEXTUREACCESS_STATIC && access != SDL_TEXTUREACCESS_STREAMING) { - return false; - } - - 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(tex_format)) { - texture_colorspace = SDL_COLORSPACE_SRGB_LINEAR; - } else if (SDL_ISPIXELFORMAT_10BIT(tex_format)) { - texture_colorspace = SDL_COLORSPACE_HDR10; - } else { - texture_colorspace = SDL_COLORSPACE_SRGB; - } - } - - if (tex_format == surface->format && texture_colorspace == surface_colorspace) { + if (surface->format == texture->format && + SDL_GetSurfaceColorspace(surface) == texture->colorspace) { if (SDL_ISPIXELFORMAT_ALPHA(surface->format) && SDL_SurfaceHasColorKey(surface)) { /* Surface and Renderer formats are identical. * Intermediate conversion is needed to convert color key to alpha (SDL_ConvertColorkeyToAlpha()). */ @@ -1582,17 +1703,16 @@ static bool SDL_UpdateTextureFromSurface(SDL_Texture *texture, SDL_Rect *rect, S if (direct_update) { if (SDL_MUSTLOCK(surface)) { - SDL_LockSurface(surface); - SDL_UpdateTexture(texture, rect, surface->pixels, surface->pitch); - SDL_UnlockSurface(surface); + if (SDL_LockSurface(surface)) { + SDL_UpdateTexture(texture, rect, surface->pixels, surface->pitch); + SDL_UnlockSurface(surface); + } } else { SDL_UpdateTexture(texture, rect, surface->pixels, surface->pitch); } } else { - SDL_Surface *temp = NULL; - // Set up a destination surface for the texture update - temp = SDL_ConvertSurfaceAndColorspace(surface, tex_format, NULL, texture_colorspace, surface_props); + SDL_Surface *temp = SDL_ConvertSurfaceAndColorspace(surface, texture->format, texture->public_palette, texture->colorspace, SDL_GetSurfaceProperties(surface)); if (temp) { SDL_UpdateTexture(texture, NULL, temp->pixels, temp->pitch); SDL_DestroySurface(temp); @@ -1601,6 +1721,21 @@ static bool SDL_UpdateTextureFromSurface(SDL_Texture *texture, SDL_Rect *rect, S } } + if (texture->format == surface->format && surface->palette) { + // Copy the palette to the new texture + SDL_Palette *existing = surface->palette; + SDL_Palette *palette = SDL_CreatePalette(existing->ncolors); + if (palette && + SDL_SetPaletteColors(palette, existing->colors, 0, existing->ncolors) && + SDL_SetTexturePalette(texture, palette)) { + // The texture has a reference to the palette now + SDL_DestroyPalette(palette); + } else { + SDL_DestroyPalette(palette); + return false; + } + } + { Uint8 r, g, b, a; SDL_BlendMode blendMode; @@ -1625,10 +1760,8 @@ static bool SDL_UpdateTextureFromSurface(SDL_Texture *texture, SDL_Rect *rect, S SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *surface) { - bool needAlpha; int i; SDL_PixelFormat format = SDL_PIXELFORMAT_UNKNOWN; - SDL_Palette *palette; SDL_Texture *texture; SDL_PropertiesID props; SDL_Colorspace surface_colorspace = SDL_COLORSPACE_UNKNOWN; @@ -1636,28 +1769,11 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s CHECK_RENDERER_MAGIC(renderer, NULL); - if (!SDL_SurfaceValid(surface)) { + CHECK_PARAM(!SDL_SurfaceValid(surface)) { SDL_InvalidParamError("SDL_CreateTextureFromSurface(): surface"); return NULL; } - // See what the best texture format is - if (SDL_ISPIXELFORMAT_ALPHA(surface->format) || SDL_SurfaceHasColorKey(surface)) { - needAlpha = true; - } else { - needAlpha = false; - } - - // If Palette contains alpha values, promotes to alpha format - palette = SDL_GetSurfacePalette(surface); - if (palette) { - bool is_opaque, has_alpha_channel; - SDL_DetectPalette(palette, &is_opaque, &has_alpha_channel); - if (!is_opaque) { - needAlpha = true; - } - } - // 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)) { @@ -1710,9 +1826,31 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s // Fallback, choose a valid pixel format if (format == SDL_PIXELFORMAT_UNKNOWN) { format = renderer->texture_formats[0]; + + // See what the best texture format is + bool needAlpha; + if (SDL_ISPIXELFORMAT_ALPHA(surface->format) || SDL_SurfaceHasColorKey(surface)) { + needAlpha = true; + } else { + needAlpha = false; + } + + // If palette contains alpha values, promotes to alpha format + if (surface->palette) { + bool is_opaque, has_alpha_channel; + SDL_DetectPalette(surface->palette, &is_opaque, &has_alpha_channel); + if (!is_opaque) { + needAlpha = true; + } + } + + // Indexed formats don't support the transparency needed for color-keyed surfaces + bool preferIndexed = SDL_ISPIXELFORMAT_INDEXED(surface->format) && !needAlpha; + for (i = 0; i < renderer->num_texture_formats; ++i) { if (!SDL_ISPIXELFORMAT_FOURCC(renderer->texture_formats[i]) && - SDL_ISPIXELFORMAT_ALPHA(renderer->texture_formats[i]) == needAlpha) { + SDL_ISPIXELFORMAT_ALPHA(renderer->texture_formats[i]) == needAlpha && + SDL_ISPIXELFORMAT_INDEXED(renderer->texture_formats[i]) == preferIndexed) { format = renderer->texture_formats[i]; break; } @@ -1796,6 +1934,85 @@ bool SDL_GetTextureSize(SDL_Texture *texture, float *w, float *h) return true; } +bool SDL_SetTexturePalette(SDL_Texture *texture, SDL_Palette *palette) +{ + CHECK_TEXTURE_MAGIC(texture, false); + + CHECK_PARAM(!SDL_ISPIXELFORMAT_INDEXED(texture->format)) { + return SDL_SetError("Texture isn't palettized format"); + } + + CHECK_PARAM(palette && palette->ncolors > (1 << SDL_BITSPERPIXEL(texture->format))) { + return SDL_SetError("Palette doesn't match surface format"); + } + + if (palette != texture->public_palette) { + SDL_Renderer *renderer = texture->renderer; + + if (texture->public_palette) { + SDL_DestroyPalette(texture->public_palette); + + if (!texture->native) { + // Clean up the texture palette + --texture->palette->refcount; + if (texture->palette->refcount == 0) { + FlushRenderCommandsIfPaletteNeeded(renderer, texture->palette); + renderer->DestroyPalette(renderer, texture->palette); + SDL_RemoveFromHashTable(renderer->palettes, texture->public_palette); + } + texture->palette = NULL; + } + } + + texture->public_palette = palette; + texture->palette_version = 0; + + if (texture->public_palette) { + ++texture->public_palette->refcount; + + if (!texture->native) { + if (SDL_FindInHashTable(renderer->palettes, palette, (const void **)&texture->palette)) { + ++texture->palette->refcount; + } else { + SDL_TexturePalette *texture_palette = (SDL_TexturePalette *)SDL_calloc(1, sizeof(*texture_palette)); + if (!texture_palette) { + SDL_SetTexturePalette(texture, NULL); + return false; + } + if (!renderer->CreatePalette(renderer, texture_palette)) { + renderer->DestroyPalette(renderer, texture_palette); + SDL_SetTexturePalette(texture, NULL); + return false; + } + texture->palette = texture_palette; + texture->palette->refcount = 1; + + if (!SDL_InsertIntoHashTable(renderer->palettes, palette, texture->palette, false)) { + SDL_SetTexturePalette(texture, NULL); + return false; + } + } + } + + if (!texture->native && renderer->ChangeTexturePalette) { + renderer->ChangeTexturePalette(renderer, texture); + } + } + + if (texture->palette_surface) { + SDL_SetSurfacePalette(texture->palette_surface, palette); + } + } + return true; +} + +SDL_Palette *SDL_GetTexturePalette(SDL_Texture *texture) +{ + CHECK_TEXTURE_MAGIC(texture, NULL); + + return texture->public_palette; +} + bool SDL_SetTextureColorMod(SDL_Texture *texture, Uint8 r, Uint8 g, Uint8 b) { const float fR = (float)r / 255.0f; @@ -1932,7 +2149,7 @@ bool SDL_SetTextureBlendMode(SDL_Texture *texture, SDL_BlendMode blendMode) CHECK_TEXTURE_MAGIC(texture, false); - if (blendMode == SDL_BLENDMODE_INVALID) { + CHECK_PARAM(blendMode == SDL_BLENDMODE_INVALID) { return SDL_InvalidParamError("blendMode"); } @@ -1965,8 +2182,12 @@ bool SDL_SetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode scaleMode) { CHECK_TEXTURE_MAGIC(texture, false); - if (scaleMode != SDL_SCALEMODE_NEAREST && - scaleMode != SDL_SCALEMODE_LINEAR) { + switch (scaleMode) { + case SDL_SCALEMODE_NEAREST: + case SDL_SCALEMODE_PIXELART: + case SDL_SCALEMODE_LINEAR: + break; + default: return SDL_InvalidParamError("scaleMode"); } @@ -1981,7 +2202,7 @@ bool SDL_SetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode scaleMode) bool SDL_GetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode *scaleMode) { if (scaleMode) { - *scaleMode = SDL_SCALEMODE_LINEAR; + *scaleMode = SDL_SCALEMODE_INVALID; } CHECK_TEXTURE_MAGIC(texture, false); @@ -1998,6 +2219,7 @@ static bool SDL_UpdateTextureYUV(SDL_Texture *texture, const SDL_Rect *rect, { SDL_Texture *native = texture->native; SDL_Rect full_rect; + bool result = true; if (!SDL_SW_UpdateYUVTexture(texture->yuv, rect, pixels, pitch)) { return false; @@ -2017,8 +2239,7 @@ static bool SDL_UpdateTextureYUV(SDL_Texture *texture, const SDL_Rect *rect, if (!SDL_LockTexture(native, rect, &native_pixels, &native_pitch)) { return false; } - SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, - rect->w, rect->h, native_pixels, native_pitch); + result = SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, native_pixels, native_pitch); SDL_UnlockTexture(native); } else { // Use a temporary buffer for updating @@ -2029,25 +2250,38 @@ static bool SDL_UpdateTextureYUV(SDL_Texture *texture, const SDL_Rect *rect, if (!temp_pixels) { return false; } - SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, - rect->w, rect->h, temp_pixels, temp_pitch); - SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch); + result = SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, temp_pixels, temp_pitch); + if (result) { + SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch); + } SDL_free(temp_pixels); } } - return true; + return result; } #endif // SDL_HAVE_YUV -static bool SDL_UpdateTextureNative(SDL_Texture *texture, const SDL_Rect *rect, - const void *pixels, int pitch) +static bool SDL_UpdateTexturePaletteSurface(SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch) +{ + SDL_Surface *surface = texture->palette_surface; + + const Uint8 *src = (const Uint8 *)pixels; + Uint8 *dst = ((Uint8 *)surface->pixels) + rect->y * surface->pitch + (rect->x * SDL_BITSPERPIXEL(texture->format)) / 8; + int w = ((rect->w * SDL_BITSPERPIXEL(texture->format)) + 7) / 8; + int h = rect->h; + while (h--) { + SDL_memcpy(dst, src, w); + src += pitch; + dst += surface->pitch; + } + texture->palette_version = 0; + return true; +} + +static bool SDL_UpdateTextureNative(SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch) { SDL_Texture *native = texture->native; - if (!rect->w || !rect->h) { - return true; // nothing to do. - } - if (texture->access == SDL_TEXTUREACCESS_STREAMING) { // We can lock the texture and copy to it void *native_pixels = NULL; @@ -2085,10 +2319,10 @@ bool SDL_UpdateTexture(SDL_Texture *texture, const SDL_Rect *rect, const void *p CHECK_TEXTURE_MAGIC(texture, false); - if (!pixels) { + CHECK_PARAM(!pixels) { return SDL_InvalidParamError("pixels"); } - if (!pitch) { + CHECK_PARAM(!pitch) { return SDL_InvalidParamError("pitch"); } @@ -2108,6 +2342,8 @@ bool SDL_UpdateTexture(SDL_Texture *texture, const SDL_Rect *rect, const void *p } else if (texture->yuv) { return SDL_UpdateTextureYUV(texture, &real_rect, pixels, pitch); #endif + } else if (texture->palette_surface) { + return SDL_UpdateTexturePaletteSurface(texture, &real_rect, pixels, pitch); } else if (texture->native) { return SDL_UpdateTextureNative(texture, &real_rect, pixels, pitch); } else { @@ -2127,6 +2363,7 @@ static bool SDL_UpdateTextureYUVPlanar(SDL_Texture *texture, const SDL_Rect *rec { SDL_Texture *native = texture->native; SDL_Rect full_rect; + bool result = true; if (!SDL_SW_UpdateYUVTexturePlanar(texture->yuv, rect, Yplane, Ypitch, Uplane, Upitch, Vplane, Vpitch)) { return false; @@ -2150,8 +2387,7 @@ static bool SDL_UpdateTextureYUVPlanar(SDL_Texture *texture, const SDL_Rect *rec if (!SDL_LockTexture(native, rect, &native_pixels, &native_pitch)) { return false; } - SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, - rect->w, rect->h, native_pixels, native_pitch); + result = SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, native_pixels, native_pitch); SDL_UnlockTexture(native); } else { // Use a temporary buffer for updating @@ -2162,13 +2398,14 @@ static bool SDL_UpdateTextureYUVPlanar(SDL_Texture *texture, const SDL_Rect *rec if (!temp_pixels) { return false; } - SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, - rect->w, rect->h, temp_pixels, temp_pitch); - SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch); + result = SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, temp_pixels, temp_pitch); + if (result) { + SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch); + } SDL_free(temp_pixels); } } - return true; + return result; } static bool SDL_UpdateTextureNVPlanar(SDL_Texture *texture, const SDL_Rect *rect, @@ -2177,6 +2414,7 @@ static bool SDL_UpdateTextureNVPlanar(SDL_Texture *texture, const SDL_Rect *rect { SDL_Texture *native = texture->native; SDL_Rect full_rect; + bool result = true; if (!SDL_SW_UpdateNVTexturePlanar(texture->yuv, rect, Yplane, Ypitch, UVplane, UVpitch)) { return false; @@ -2200,8 +2438,7 @@ static bool SDL_UpdateTextureNVPlanar(SDL_Texture *texture, const SDL_Rect *rect if (!SDL_LockTexture(native, rect, &native_pixels, &native_pitch)) { return false; } - SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, - rect->w, rect->h, native_pixels, native_pitch); + result = SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, native_pixels, native_pitch); SDL_UnlockTexture(native); } else { // Use a temporary buffer for updating @@ -2212,13 +2449,14 @@ static bool SDL_UpdateTextureNVPlanar(SDL_Texture *texture, const SDL_Rect *rect if (!temp_pixels) { return false; } - SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, - rect->w, rect->h, temp_pixels, temp_pitch); - SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch); + result = SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, temp_pixels, temp_pitch); + if (result) { + SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch); + } SDL_free(temp_pixels); } } - return true; + return result; } #endif // SDL_HAVE_YUV @@ -2234,28 +2472,28 @@ bool SDL_UpdateYUVTexture(SDL_Texture *texture, const SDL_Rect *rect, CHECK_TEXTURE_MAGIC(texture, false); - if (!Yplane) { + CHECK_PARAM(!Yplane) { return SDL_InvalidParamError("Yplane"); } - if (!Ypitch) { + CHECK_PARAM(!Ypitch) { return SDL_InvalidParamError("Ypitch"); } - if (!Uplane) { + CHECK_PARAM(!Uplane) { return SDL_InvalidParamError("Uplane"); } - if (!Upitch) { + CHECK_PARAM(!Upitch) { return SDL_InvalidParamError("Upitch"); } - if (!Vplane) { + CHECK_PARAM(!Vplane) { return SDL_InvalidParamError("Vplane"); } - if (!Vpitch) { + CHECK_PARAM(!Vpitch) { return SDL_InvalidParamError("Vpitch"); } - if (texture->format != SDL_PIXELFORMAT_YV12 && - texture->format != SDL_PIXELFORMAT_IYUV) { - return SDL_SetError("Texture format must by YV12 or IYUV"); + CHECK_PARAM(texture->format != SDL_PIXELFORMAT_YV12 && + texture->format != SDL_PIXELFORMAT_IYUV) { + return SDL_SetError("Texture format must be YV12 or IYUV"); } real_rect.x = 0; @@ -2300,22 +2538,23 @@ bool SDL_UpdateNVTexture(SDL_Texture *texture, const SDL_Rect *rect, CHECK_TEXTURE_MAGIC(texture, false); - if (!Yplane) { + CHECK_PARAM(!Yplane) { return SDL_InvalidParamError("Yplane"); } - if (!Ypitch) { + CHECK_PARAM(!Ypitch) { return SDL_InvalidParamError("Ypitch"); } - if (!UVplane) { + CHECK_PARAM(!UVplane) { return SDL_InvalidParamError("UVplane"); } - if (!UVpitch) { + CHECK_PARAM(!UVpitch) { return SDL_InvalidParamError("UVpitch"); } - if (texture->format != SDL_PIXELFORMAT_NV12 && - texture->format != SDL_PIXELFORMAT_NV21) { - return SDL_SetError("Texture format must by NV12 or NV21"); + CHECK_PARAM(texture->format != SDL_PIXELFORMAT_NV12 && + texture->format != SDL_PIXELFORMAT_NV21 && + texture->format != SDL_PIXELFORMAT_P010) { + return SDL_SetError("Texture format must be NV12, NV21, or P010"); } real_rect.x = 0; @@ -2358,8 +2597,16 @@ static bool SDL_LockTextureYUV(SDL_Texture *texture, const SDL_Rect *rect, } #endif // SDL_HAVE_YUV -static bool SDL_LockTextureNative(SDL_Texture *texture, const SDL_Rect *rect, - void **pixels, int *pitch) +static bool SDL_LockTexturePaletteSurface(SDL_Texture *texture, const SDL_Rect *rect, void **pixels, int *pitch) +{ + SDL_Surface *surface = texture->palette_surface; + + *pixels = ((Uint8 *)surface->pixels) + rect->y * surface->pitch + (rect->x * SDL_BITSPERPIXEL(texture->format)) / 8; + *pitch = surface->pitch; + return true; +} + +static bool SDL_LockTextureNative(SDL_Texture *texture, const SDL_Rect *rect, void **pixels, int *pitch) { texture->locked_rect = *rect; *pixels = (void *)((Uint8 *)texture->pixels + @@ -2375,7 +2622,7 @@ bool SDL_LockTexture(SDL_Texture *texture, const SDL_Rect *rect, void **pixels, CHECK_TEXTURE_MAGIC(texture, false); - if (texture->access != SDL_TEXTUREACCESS_STREAMING) { + CHECK_PARAM(texture->access != SDL_TEXTUREACCESS_STREAMING) { return SDL_SetError("SDL_LockTexture(): texture must be streaming"); } @@ -2395,7 +2642,9 @@ bool SDL_LockTexture(SDL_Texture *texture, const SDL_Rect *rect, void **pixels, return SDL_LockTextureYUV(texture, rect, pixels, pitch); } else #endif - if (texture->native) { + if (texture->palette_surface) { + return SDL_LockTexturePaletteSurface(texture, rect, pixels, pitch); + } else if (texture->native) { // Calls a real SDL_LockTexture/SDL_UnlockTexture on unlock, flushing then. return SDL_LockTextureNative(texture, rect, pixels, pitch); } else { @@ -2413,8 +2662,10 @@ bool SDL_LockTextureToSurface(SDL_Texture *texture, const SDL_Rect *rect, SDL_Su void *pixels = NULL; int pitch = 0; // fix static analysis - if (!texture || !surface) { - return false; + CHECK_TEXTURE_MAGIC(texture, false); + + CHECK_PARAM(!surface) { + return SDL_InvalidParamError("surface"); } real_rect.x = 0; @@ -2434,6 +2685,9 @@ bool SDL_LockTextureToSurface(SDL_Texture *texture, const SDL_Rect *rect, SDL_Su SDL_UnlockTexture(texture); return false; } + if (texture->public_palette) { + SDL_SetSurfacePalette(texture->locked_surface, texture->public_palette); + } *surface = texture->locked_surface; return true; @@ -2461,6 +2715,11 @@ static void SDL_UnlockTextureYUV(SDL_Texture *texture) } #endif // SDL_HAVE_YUV +static void SDL_UnlockTexturePaletteSurface(SDL_Texture *texture) +{ + texture->palette_version = 0; +} + static void SDL_UnlockTextureNative(SDL_Texture *texture) { SDL_Texture *native = texture->native; @@ -2488,33 +2747,42 @@ void SDL_UnlockTexture(SDL_Texture *texture) if (texture->access != SDL_TEXTUREACCESS_STREAMING) { return; } + #ifdef SDL_HAVE_YUV if (texture->yuv) { SDL_UnlockTextureYUV(texture); } else #endif - if (texture->native) { + if (texture->palette_surface) { + SDL_UnlockTexturePaletteSurface(texture); + } else if (texture->native) { SDL_UnlockTextureNative(texture); } else { SDL_Renderer *renderer = texture->renderer; renderer->UnlockTexture(renderer, texture); } - SDL_DestroySurface(texture->locked_surface); - texture->locked_surface = NULL; + if (texture->locked_surface) { + SDL_DestroySurface(texture->locked_surface); + texture->locked_surface = NULL; + } } bool SDL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) { + CHECK_RENDERER_MAGIC(renderer, false); + // texture == NULL is valid and means reset the target to the window if (texture) { CHECK_TEXTURE_MAGIC(texture, false); - if (renderer != texture->renderer) { + + CHECK_PARAM(renderer != texture->renderer) { return SDL_SetError("Texture was not created with this renderer"); } - if (texture->access != SDL_TEXTUREACCESS_TARGET) { + CHECK_PARAM(texture->access != SDL_TEXTUREACCESS_TARGET) { return SDL_SetError("Texture not created with SDL_TEXTUREACCESS_TARGET"); } + if (texture->native) { // Always render to the native texture texture = texture->native; @@ -2559,6 +2827,7 @@ bool SDL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) SDL_Texture *SDL_GetRenderTarget(SDL_Renderer *renderer) { CHECK_RENDERER_MAGIC(renderer, NULL); + if (!renderer->target) { return NULL; } @@ -2573,11 +2842,12 @@ static void UpdateLogicalPresentation(SDL_Renderer *renderer) const float logical_h = view->logical_h; int iwidth, iheight; - if (!is_main_view && renderer->target) { + if (is_main_view) { + SDL_GetRenderOutputSize(renderer, &iwidth, &iheight); + } else { + SDL_assert(renderer->target != NULL); iwidth = (int)renderer->target->w; iheight = (int)renderer->target->h; - } else { - SDL_GetRenderOutputSize(renderer, &iwidth, &iheight); } view->logical_src_rect.x = 0.0f; @@ -2694,9 +2964,14 @@ bool SDL_SetRenderLogicalPresentation(SDL_Renderer *renderer, int w, int h, SDL_ CHECK_RENDERER_MAGIC(renderer, false); SDL_RenderViewState *view = renderer->view; + if (mode == SDL_LOGICAL_PRESENTATION_DISABLED) { + view->logical_w = 0; + view->logical_h = 0; + } else { + view->logical_w = w; + view->logical_h = h; + } view->logical_presentation_mode = mode; - view->logical_w = w; - view->logical_h = h; UpdateLogicalPresentation(renderer); @@ -2737,94 +3012,6 @@ bool SDL_GetRenderLogicalPresentationRect(SDL_Renderer *renderer, SDL_FRect *rec return true; } -static void SDL_RenderLogicalBorders(SDL_Renderer *renderer, const SDL_FRect *dst) -{ - const SDL_RenderViewState *view = renderer->view; - - if (dst->x > 0.0f || dst->y > 0.0f) { - SDL_BlendMode saved_blend_mode = renderer->blendMode; - SDL_FColor saved_color = renderer->color; - - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); - SDL_SetRenderDrawColorFloat(renderer, 0.0f, 0.0f, 0.0f, 1.0f); - - if (dst->x > 0.0f) { - SDL_FRect rect; - - rect.x = 0.0f; - rect.y = 0.0f; - rect.w = dst->x; - rect.h = (float)view->pixel_h; - SDL_RenderFillRect(renderer, &rect); - - rect.x = dst->x + dst->w; - rect.w = (float)view->pixel_w - rect.x; - SDL_RenderFillRect(renderer, &rect); - } - - if (dst->y > 0.0f) { - SDL_FRect rect; - - rect.x = 0.0f; - rect.y = 0.0f; - rect.w = (float)view->pixel_w; - rect.h = dst->y; - SDL_RenderFillRect(renderer, &rect); - - rect.y = dst->y + dst->h; - rect.h = (float)view->pixel_h - rect.y; - SDL_RenderFillRect(renderer, &rect); - } - - SDL_SetRenderDrawBlendMode(renderer, saved_blend_mode); - SDL_SetRenderDrawColorFloat(renderer, saved_color.r, saved_color.g, saved_color.b, saved_color.a); - } -} - -static void SDL_RenderLogicalPresentation(SDL_Renderer *renderer) -{ - 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. - 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) { - SDL_copyp(&orig_cliprect, &view->clip_rect); - } - - // trample some state. - SDL_SetRenderLogicalPresentation(renderer, logical_w, logical_h, SDL_LOGICAL_PRESENTATION_DISABLED); - SDL_SetRenderViewport(renderer, NULL); - if (clipping_enabled) { - SDL_SetRenderClipRect(renderer, NULL); - } - SDL_SetRenderScale(renderer, 1.0f, 1.0f); - - // draw the borders. - SDL_RenderLogicalBorders(renderer, &logical_dst_rect); - - // now set everything back. - view->logical_presentation_mode = mode; - SDL_SetRenderViewport(renderer, &orig_viewport); - if (clipping_enabled) { - SDL_SetRenderClipRect(renderer, &orig_cliprect); - } - SDL_SetRenderScale(renderer, scale_x, scale_y); - - SDL_SetRenderLogicalPresentation(renderer, logical_w, logical_h, mode); - } -} - 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 @@ -3302,11 +3489,7 @@ bool SDL_SetRenderDrawBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode) { CHECK_RENDERER_MAGIC(renderer, false); - if (blendMode == SDL_BLENDMODE_INVALID) { - return SDL_InvalidParamError("blendMode"); - } - - if (blendMode == SDL_BLENDMODE_INVALID) { + CHECK_PARAM(blendMode == SDL_BLENDMODE_INVALID) { return SDL_InvalidParamError("blendMode"); } @@ -3386,9 +3569,10 @@ bool SDL_RenderPoints(SDL_Renderer *renderer, const SDL_FPoint *points, int coun CHECK_RENDERER_MAGIC(renderer, false); - if (!points) { + CHECK_PARAM(!points) { return SDL_InvalidParamError("SDL_RenderPoints(): points"); } + if (count < 1) { return true; } @@ -3592,9 +3776,10 @@ bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count CHECK_RENDERER_MAGIC(renderer, false); - if (!points) { + CHECK_PARAM(!points) { return SDL_InvalidParamError("SDL_RenderLines(): points"); } + if (count < 2) { return true; } @@ -3722,7 +3907,7 @@ bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count result = QueueCmdGeometry(renderer, NULL, xy, xy_stride, &renderer->color, 0 /* color_stride */, NULL, 0, num_vertices, indices, num_indices, size_indices, - 1.0f, 1.0f, SDL_TEXTURE_ADDRESS_CLAMP); + 1.0f, 1.0f, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP); } SDL_small_free(xy, isstack1); @@ -3771,9 +3956,10 @@ bool SDL_RenderRects(SDL_Renderer *renderer, const SDL_FRect *rects, int count) CHECK_RENDERER_MAGIC(renderer, false); - if (!rects) { + CHECK_PARAM(!rects) { return SDL_InvalidParamError("SDL_RenderRects(): rects"); } + if (count < 1) { return true; } @@ -3816,9 +4002,10 @@ bool SDL_RenderFillRects(SDL_Renderer *renderer, const SDL_FRect *rects, int cou CHECK_RENDERER_MAGIC(renderer, false); - if (!rects) { + CHECK_PARAM(!rects) { return SDL_InvalidParamError("SDL_RenderFillRects(): rects"); } + if (count < 1) { return true; } @@ -3903,7 +4090,7 @@ static bool SDL_RenderTextureInternal(SDL_Renderer *renderer, SDL_Texture *textu result = QueueCmdGeometry(renderer, texture, xy, xy_stride, &texture->color, 0 /* color_stride */, uv, uv_stride, num_vertices, indices, num_indices, size_indices, - scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP); + scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP); } else { const SDL_FRect rect = { dstrect->x * scale_x, dstrect->y * scale_y, dstrect->w * scale_x, dstrect->h * scale_y }; result = QueueCmdCopy(renderer, texture, srcrect, &rect); @@ -3916,7 +4103,7 @@ bool SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_F CHECK_RENDERER_MAGIC(renderer, false); CHECK_TEXTURE_MAGIC(texture, false); - if (renderer != texture->renderer) { + CHECK_PARAM(renderer != texture->renderer) { return SDL_SetError("Texture was not created with this renderer"); } @@ -3944,6 +4131,10 @@ bool SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_F dstrect = &full_dstrect; } + if (!UpdateTexturePalette(texture)) { + return false; + } + if (texture->native) { texture = texture->native; } @@ -3963,7 +4154,7 @@ bool SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture, CHECK_RENDERER_MAGIC(renderer, false); CHECK_TEXTURE_MAGIC(texture, false); - if (renderer != texture->renderer) { + CHECK_PARAM(renderer != texture->renderer) { return SDL_SetError("Texture was not created with this renderer"); } if (!renderer->QueueCopyEx && !renderer->QueueGeometry) { @@ -3989,6 +4180,10 @@ bool SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture, GetRenderViewportSize(renderer, &real_dstrect); + if (!UpdateTexturePalette(texture)) { + return false; + } + if (texture->native) { texture = texture->native; } @@ -4065,7 +4260,7 @@ bool SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture, &texture->color, 0 /* color_stride */, uv, uv_stride, num_vertices, indices, num_indices, size_indices, - scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP + scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP ); } return result; @@ -4086,7 +4281,7 @@ bool SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture, CHECK_RENDERER_MAGIC(renderer, false); CHECK_TEXTURE_MAGIC(texture, false); - if (renderer != texture->renderer) { + CHECK_PARAM(renderer != texture->renderer) { return SDL_SetError("Texture was not created with this renderer"); } if (!renderer->QueueCopyEx && !renderer->QueueGeometry) { @@ -4117,6 +4312,10 @@ bool SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture, dstrect = &full_dstrect; } + if (!UpdateTexturePalette(texture)) { + return false; + } + if (texture->native) { texture = texture->native; } @@ -4215,7 +4414,7 @@ bool SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture, result = QueueCmdGeometry(renderer, texture, xy, xy_stride, &texture->color, 0 /* color_stride */, uv, uv_stride, num_vertices, indices, num_indices, size_indices, - scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP); + scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP); } else { result = QueueCmdCopyEx(renderer, texture, &real_srcrect, dstrect, angle, &real_center, flip, scale_x, scale_y); } @@ -4267,7 +4466,8 @@ static bool SDL_RenderTextureTiled_Wrap(SDL_Renderer *renderer, SDL_Texture *tex return QueueCmdGeometry(renderer, texture, xy, xy_stride, &texture->color, 0 /* color_stride */, uv, uv_stride, num_vertices, indices, num_indices, size_indices, - view->current_scale.x, view->current_scale.y, SDL_TEXTURE_ADDRESS_WRAP); + view->current_scale.x, view->current_scale.y, + SDL_TEXTURE_ADDRESS_WRAP, 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) @@ -4329,6 +4529,11 @@ static bool SDL_RenderTextureTiled_Iterate(SDL_Renderer *renderer, SDL_Texture * return true; } +static bool IsNPOT(int x) +{ + return (x <= 0) || ((x & (x - 1)) != 0); +} + bool SDL_RenderTextureTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float scale, const SDL_FRect *dstrect) { SDL_FRect real_srcrect; @@ -4336,11 +4541,11 @@ bool SDL_RenderTextureTiled(SDL_Renderer *renderer, SDL_Texture *texture, const CHECK_RENDERER_MAGIC(renderer, false); CHECK_TEXTURE_MAGIC(texture, false); - if (renderer != texture->renderer) { + CHECK_PARAM(renderer != texture->renderer) { return SDL_SetError("Texture was not created with this renderer"); } - if (scale <= 0.0f) { + CHECK_PARAM(scale <= 0.0f) { return SDL_InvalidParamError("scale"); } @@ -4367,17 +4572,28 @@ bool SDL_RenderTextureTiled(SDL_Renderer *renderer, SDL_Texture *texture, const dstrect = &full_dstrect; } + if (!UpdateTexturePalette(texture)) { + return false; + } + if (texture->native) { texture = texture->native; } texture->last_command_generation = renderer->render_command_generation; + bool do_wrapping = !renderer->software && + (!srcrect || + (real_srcrect.x == 0.0f && real_srcrect.y == 0.0f && + real_srcrect.w == (float)texture->w && real_srcrect.h == (float)texture->h)); + if (do_wrapping && renderer->npot_texture_wrap_unsupported) { + if (IsNPOT(texture->w) || IsNPOT(texture->h)) { + do_wrapping = false; + } + } + // See if we can use geometry with repeating texture coordinates - if (!renderer->software && - (!srcrect || - (real_srcrect.x == 0.0f && real_srcrect.y == 0.0f && - real_srcrect.w == (float)texture->w && real_srcrect.h == (float)texture->h))) { + if (do_wrapping) { return SDL_RenderTextureTiled_Wrap(renderer, texture, &real_srcrect, scale, dstrect); } else { return SDL_RenderTextureTiled_Iterate(renderer, texture, &real_srcrect, scale, dstrect); @@ -4396,7 +4612,7 @@ bool SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const CHECK_RENDERER_MAGIC(renderer, false); CHECK_TEXTURE_MAGIC(texture, false); - if (renderer != texture->renderer) { + CHECK_PARAM(renderer != texture->renderer) { return SDL_SetError("Texture was not created with this renderer"); } @@ -4521,6 +4737,143 @@ bool SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const return true; } +bool SDL_RenderTexture9GridTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect, float tileScale) +{ + SDL_FRect full_src, full_dst; + SDL_FRect curr_src, curr_dst; + float dst_left_width; + float dst_right_width; + float dst_top_height; + float dst_bottom_height; + + CHECK_RENDERER_MAGIC(renderer, false); + CHECK_TEXTURE_MAGIC(texture, false); + + CHECK_PARAM(renderer != texture->renderer) { + return SDL_SetError("Texture was not created with this renderer"); + } + + if (!srcrect) { + full_src.x = 0; + full_src.y = 0; + full_src.w = (float)texture->w; + full_src.h = (float)texture->h; + srcrect = &full_src; + } + + if (!dstrect) { + GetRenderViewportSize(renderer, &full_dst); + dstrect = &full_dst; + } + + if (scale <= 0.0f || scale == 1.0f) { + dst_left_width = SDL_ceilf(left_width); + dst_right_width = SDL_ceilf(right_width); + dst_top_height = SDL_ceilf(top_height); + dst_bottom_height = SDL_ceilf(bottom_height); + } else { + dst_left_width = SDL_ceilf(left_width * scale); + dst_right_width = SDL_ceilf(right_width * scale); + dst_top_height = SDL_ceilf(top_height * scale); + dst_bottom_height = SDL_ceilf(bottom_height * scale); + } + + // Center + curr_src.x = srcrect->x + left_width; + curr_src.y = srcrect->y + top_height; + curr_src.w = srcrect->w - left_width - right_width; + curr_src.h = srcrect->h - top_height - bottom_height; + curr_dst.x = dstrect->x + dst_left_width; + curr_dst.y = dstrect->y + dst_top_height; + curr_dst.w = dstrect->w - dst_left_width - dst_right_width; + curr_dst.h = dstrect->h - dst_top_height - dst_bottom_height; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + // Upper-left corner + curr_src.x = srcrect->x; + curr_src.y = srcrect->y; + curr_src.w = left_width; + curr_src.h = top_height; + curr_dst.x = dstrect->x; + curr_dst.y = dstrect->y; + curr_dst.w = dst_left_width; + curr_dst.h = dst_top_height; + if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) { + return false; + } + + // Upper-right corner + curr_src.x = srcrect->x + srcrect->w - right_width; + curr_src.w = right_width; + curr_dst.x = dstrect->x + dstrect->w - dst_right_width; + curr_dst.w = dst_right_width; + if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) { + return false; + } + + // Lower-right corner + curr_src.y = srcrect->y + srcrect->h - bottom_height; + curr_src.h = bottom_height; + curr_dst.y = dstrect->y + dstrect->h - dst_bottom_height; + curr_dst.h = dst_bottom_height; + if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) { + return false; + } + + // Lower-left corner + curr_src.x = srcrect->x; + curr_src.w = left_width; + curr_dst.x = dstrect->x; + curr_dst.w = dst_left_width; + if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) { + return false; + } + + // Left + curr_src.y = srcrect->y + top_height; + curr_src.h = srcrect->h - top_height - bottom_height; + curr_dst.y = dstrect->y + dst_top_height; + curr_dst.h = dstrect->h - dst_top_height - dst_bottom_height; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + // Right + curr_src.x = srcrect->x + srcrect->w - right_width; + curr_src.w = right_width; + curr_dst.x = dstrect->x + dstrect->w - dst_right_width; + curr_dst.w = dst_right_width; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + // Top + curr_src.x = srcrect->x + left_width; + curr_src.y = srcrect->y; + curr_src.w = srcrect->w - left_width - right_width; + curr_src.h = top_height; + curr_dst.x = dstrect->x + dst_left_width; + curr_dst.y = dstrect->y; + curr_dst.w = dstrect->w - dst_left_width - dst_right_width; + curr_dst.h = dst_top_height; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + // Bottom + curr_src.y = srcrect->y + srcrect->h - bottom_height; + curr_src.h = bottom_height; + curr_dst.y = dstrect->y + dstrect->h - dst_bottom_height; + curr_dst.h = dst_bottom_height; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + return true; +} + bool SDL_RenderGeometry(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Vertex *vertices, int num_vertices, @@ -4674,7 +5027,7 @@ static bool SDLCALL SDL_SW_RenderGeometryRaw(SDL_Renderer *renderer, continue; } - /* Two triangles forming a quadialateral, + /* Two triangles forming a quadrilateral, * prev and current triangles must have exactly 2 common vertices */ { int cnt = 0, j = 3; @@ -4803,6 +5156,22 @@ static bool SDLCALL SDL_SW_RenderGeometryRaw(SDL_Renderer *renderer, } } + // Check if UVs within range + if (is_quad) { + const float *uv0_ = (const float *)((const char *)uv + A * color_stride); + const float *uv1_ = (const float *)((const char *)uv + B * color_stride); + const float *uv2_ = (const float *)((const char *)uv + C * color_stride); + const float *uv3_ = (const float *)((const char *)uv + C2 * color_stride); + if (uv0_[0] >= 0.0f && uv0_[0] <= 1.0f && + uv1_[0] >= 0.0f && uv1_[0] <= 1.0f && + uv2_[0] >= 0.0f && uv2_[0] <= 1.0f && + uv3_[0] >= 0.0f && uv3_[0] <= 1.0f) { + // ok + } else { + is_quad = 0; + } + } + // Start rendering rect if (is_quad) { SDL_FRect s; @@ -4877,7 +5246,7 @@ static bool SDLCALL SDL_SW_RenderGeometryRaw(SDL_Renderer *renderer, result = QueueCmdGeometry(renderer, texture, xy, xy_stride, color, color_stride, uv, uv_stride, num_vertices, prev, 3, 4, - scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP); + scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP); if (!result) { goto end; } @@ -4897,7 +5266,7 @@ static bool SDLCALL SDL_SW_RenderGeometryRaw(SDL_Renderer *renderer, result = QueueCmdGeometry(renderer, texture, xy, xy_stride, color, color_stride, uv, uv_stride, num_vertices, prev, 3, 4, - scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP); + scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP); if (!result) { goto end; } @@ -4922,46 +5291,50 @@ bool SDL_RenderGeometryRaw(SDL_Renderer *renderer, { int i; int count = indices ? num_indices : num_vertices; - SDL_TextureAddressMode texture_address_mode; + SDL_TextureAddressMode texture_address_mode_u = SDL_TEXTURE_ADDRESS_CLAMP; + SDL_TextureAddressMode texture_address_mode_v = SDL_TEXTURE_ADDRESS_CLAMP; CHECK_RENDERER_MAGIC(renderer, false); - if (!renderer->QueueGeometry) { - return SDL_Unsupported(); - } - if (texture) { CHECK_TEXTURE_MAGIC(texture, false); - if (renderer != texture->renderer) { + CHECK_PARAM(renderer != texture->renderer) { return SDL_SetError("Texture was not created with this renderer"); } } - if (!xy) { + CHECK_PARAM(!xy) { return SDL_InvalidParamError("xy"); } - if (!color) { + CHECK_PARAM(!color) { return SDL_InvalidParamError("color"); } - if (texture && !uv) { + CHECK_PARAM(texture && !uv) { return SDL_InvalidParamError("uv"); } - if (count % 3 != 0) { + (void)count; // In case parameter checking is disabled + CHECK_PARAM(count % 3 != 0) { return SDL_InvalidParamError(indices ? "num_indices" : "num_vertices"); } if (indices) { - if (size_indices != 1 && size_indices != 2 && size_indices != 4) { + CHECK_PARAM(size_indices != 1 && size_indices != 2 && size_indices != 4) { return SDL_InvalidParamError("size_indices"); } - } else { + } + + if (!indices) { size_indices = 0; } + if (!renderer->QueueGeometry) { + return SDL_Unsupported(); + } + #if DONT_DRAW_WHILE_HIDDEN // Don't draw while we're hidden if (renderer->hidden) { @@ -4973,20 +5346,54 @@ bool SDL_RenderGeometryRaw(SDL_Renderer *renderer, return true; } - if (texture && texture->native) { - texture = texture->native; - } + if (texture) { + if (!UpdateTexturePalette(texture)) { + return false; + } - texture_address_mode = renderer->texture_address_mode; - if (texture_address_mode == SDL_TEXTURE_ADDRESS_AUTO && texture) { - texture_address_mode = SDL_TEXTURE_ADDRESS_CLAMP; - for (i = 0; i < num_vertices; ++i) { - const float *uv_ = (const float *)((const char *)uv + i * uv_stride); - float u = uv_[0]; - float v = uv_[1]; - if (u < 0.0f || v < 0.0f || u > 1.0f || v > 1.0f) { - texture_address_mode = SDL_TEXTURE_ADDRESS_WRAP; - break; + if (texture->native) { + texture = texture->native; + } + + if (renderer->npot_texture_wrap_unsupported && IsNPOT(texture->w)) { + texture_address_mode_u = SDL_TEXTURE_ADDRESS_CLAMP; + } else { + texture_address_mode_u = renderer->texture_address_mode_u; + } + if (renderer->npot_texture_wrap_unsupported && IsNPOT(texture->h)) { + texture_address_mode_v = SDL_TEXTURE_ADDRESS_CLAMP; + } else { + texture_address_mode_v = renderer->texture_address_mode_v; + } + + if (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO || + texture_address_mode_v == SDL_TEXTURE_ADDRESS_AUTO) { + for (i = 0; i < num_vertices; ++i) { + const float *uv_ = (const float *)((const char *)uv + i * uv_stride); + float u = uv_[0]; + float v = uv_[1]; + if (u < 0.0f || u > 1.0f) { + if (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO) { + texture_address_mode_u = SDL_TEXTURE_ADDRESS_WRAP; + if (texture_address_mode_v != SDL_TEXTURE_ADDRESS_AUTO) { + break; + } + } + } + if (v < 0.0f || v > 1.0f) { + if (texture_address_mode_v == SDL_TEXTURE_ADDRESS_AUTO) { + texture_address_mode_v = SDL_TEXTURE_ADDRESS_WRAP; + if (texture_address_mode_u != SDL_TEXTURE_ADDRESS_AUTO) { + break; + } + } + } + } + if (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO) { + texture_address_mode_u = SDL_TEXTURE_ADDRESS_CLAMP; + } + if (texture_address_mode_v == SDL_TEXTURE_ADDRESS_AUTO) { + texture_address_mode_v = SDL_TEXTURE_ADDRESS_CLAMP; } } } @@ -5013,7 +5420,9 @@ bool SDL_RenderGeometryRaw(SDL_Renderer *renderer, // For the software renderer, try to reinterpret triangles as SDL_Rect #ifdef SDL_VIDEO_RENDER_SW - if (renderer->software && texture_address_mode == SDL_TEXTURE_ADDRESS_CLAMP) { + if (renderer->software && + texture_address_mode_u == SDL_TEXTURE_ADDRESS_CLAMP && + texture_address_mode_v == SDL_TEXTURE_ADDRESS_CLAMP) { return SDL_SW_RenderGeometryRaw(renderer, texture, xy, xy_stride, color, color_stride, uv, uv_stride, num_vertices, indices, num_indices, size_indices); @@ -5025,7 +5434,36 @@ bool SDL_RenderGeometryRaw(SDL_Renderer *renderer, xy, xy_stride, color, color_stride, uv, uv_stride, num_vertices, indices, num_indices, size_indices, view->current_scale.x, view->current_scale.y, - texture_address_mode); + texture_address_mode_u, texture_address_mode_v); +} + +bool SDL_SetRenderTextureAddressMode(SDL_Renderer *renderer, SDL_TextureAddressMode u_mode, SDL_TextureAddressMode v_mode) +{ + CHECK_RENDERER_MAGIC(renderer, false); + + renderer->texture_address_mode_u = u_mode; + renderer->texture_address_mode_v = v_mode; + return true; +} + +bool SDL_GetRenderTextureAddressMode(SDL_Renderer *renderer, SDL_TextureAddressMode *u_mode, SDL_TextureAddressMode *v_mode) +{ + if (u_mode) { + *u_mode = SDL_TEXTURE_ADDRESS_INVALID; + } + if (v_mode) { + *v_mode = SDL_TEXTURE_ADDRESS_INVALID; + } + + CHECK_RENDERER_MAGIC(renderer, false); + + if (u_mode) { + *u_mode = renderer->texture_address_mode_u; + } + if (v_mode) { + *v_mode = renderer->texture_address_mode_v; + } + return true; } SDL_Surface *SDL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect) @@ -5135,13 +5573,14 @@ bool SDL_RenderPresent(SDL_Renderer *renderer) CHECK_RENDERER_MAGIC(renderer, false); - SDL_Texture *target = renderer->target; - if (target) { - SDL_SetRenderTarget(renderer, NULL); + CHECK_PARAM(renderer->target) { + if (!renderer->window && SDL_strcmp(renderer->name, SDL_GPU_RENDERER) == 0) { + // We're an offscreen renderer, we must submit the command queue + } else { + return SDL_SetError("You can't present on a render target"); + } } - SDL_RenderLogicalPresentation(renderer); - if (renderer->transparent_window) { SDL_RenderApplyWindowShape(renderer); } @@ -5158,10 +5597,6 @@ bool SDL_RenderPresent(SDL_Renderer *renderer) presented = false; } - if (target) { - SDL_SetRenderTarget(renderer, target); - } - if (renderer->simulate_vsync || (!presented && renderer->wanted_vsync)) { SDL_SimulateRenderVSync(renderer); @@ -5173,6 +5608,10 @@ static void SDL_DestroyTextureInternal(SDL_Texture *texture, bool is_destroying) { SDL_Renderer *renderer; + if (texture->public_palette) { + SDL_SetTexturePalette(texture, NULL); + } + SDL_DestroyProperties(texture->props); renderer = texture->renderer; @@ -5209,8 +5648,14 @@ static void SDL_DestroyTextureInternal(SDL_Texture *texture, bool is_destroying) renderer->DestroyTexture(renderer, texture); - SDL_DestroySurface(texture->locked_surface); - texture->locked_surface = NULL; + if (texture->palette_surface) { + SDL_DestroySurface(texture->palette_surface); + texture->palette_surface = NULL; + } + if (texture->locked_surface) { + SDL_DestroySurface(texture->locked_surface); + texture->locked_surface = NULL; + } SDL_free(texture); } @@ -5284,6 +5729,13 @@ void SDL_DestroyRendererWithoutFreeing(SDL_Renderer *renderer) SDL_assert(tex != renderer->textures); // satisfy static analysis. } + // Free palette cache, which should be empty now + if (renderer->palettes) { + SDL_assert(SDL_HashTableEmpty(renderer->palettes)); + SDL_DestroyHashTable(renderer->palettes); + renderer->palettes = NULL; + } + // Clean up renderer-specific resources if (renderer->DestroyRenderer) { renderer->DestroyRenderer(renderer); @@ -5577,7 +6029,7 @@ static bool CreateDebugTextAtlas(SDL_Renderer *renderer) // Convert temp surface into texture SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, atlas); if (texture) { - SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); + SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_PIXELART); renderer->debug_char_texture_atlas = texture; } SDL_DestroySurface(atlas); @@ -5667,3 +6119,159 @@ bool SDL_RenderDebugTextFormat(SDL_Renderer *renderer, float x, float y, SDL_PRI SDL_free(str); return retval; } + +bool SDL_SetDefaultTextureScaleMode(SDL_Renderer *renderer, SDL_ScaleMode scale_mode) +{ + CHECK_RENDERER_MAGIC(renderer, false); + + renderer->scale_mode = scale_mode; + + return true; +} + +bool SDL_GetDefaultTextureScaleMode(SDL_Renderer *renderer, SDL_ScaleMode *scale_mode) +{ + if (scale_mode) { + *scale_mode = SDL_SCALEMODE_LINEAR; + } + + CHECK_RENDERER_MAGIC(renderer, false); + + if (scale_mode) { + *scale_mode = renderer->scale_mode; + } + return true; +} + +SDL_GPURenderState *SDL_CreateGPURenderState(SDL_Renderer *renderer, const SDL_GPURenderStateCreateInfo *createinfo) +{ + CHECK_RENDERER_MAGIC(renderer, NULL); + + CHECK_PARAM(!createinfo) { + SDL_InvalidParamError("createinfo"); + return NULL; + } + + CHECK_PARAM(!createinfo->fragment_shader) { + SDL_SetError("A fragment_shader is required"); + return NULL; + } + + SDL_GPUDevice *device = (SDL_GPUDevice *)SDL_GetPointerProperty(renderer->props, SDL_PROP_RENDERER_GPU_DEVICE_POINTER, NULL); + if (!device) { + SDL_SetError("Renderer isn't associated with a GPU device"); + return NULL; + } + + SDL_GPURenderState *state = (SDL_GPURenderState *)SDL_calloc(1, sizeof(*state)); + if (!state) { + return NULL; + } + + state->renderer = renderer; + state->fragment_shader = createinfo->fragment_shader; + + if (createinfo->num_sampler_bindings > 0) { + state->sampler_bindings = (SDL_GPUTextureSamplerBinding *)SDL_calloc(createinfo->num_sampler_bindings, sizeof(*state->sampler_bindings)); + if (!state->sampler_bindings) { + SDL_DestroyGPURenderState(state); + return NULL; + } + SDL_memcpy(state->sampler_bindings, createinfo->sampler_bindings, createinfo->num_sampler_bindings * sizeof(*state->sampler_bindings)); + state->num_sampler_bindings = createinfo->num_sampler_bindings; + } + + if (createinfo->num_storage_textures > 0) { + state->storage_textures = (SDL_GPUTexture **)SDL_calloc(createinfo->num_storage_textures, sizeof(*state->storage_textures)); + if (!state->storage_textures) { + SDL_DestroyGPURenderState(state); + return NULL; + } + SDL_memcpy(state->storage_textures, createinfo->storage_textures, createinfo->num_storage_textures * sizeof(*state->storage_textures)); + state->num_storage_textures = createinfo->num_storage_textures; + } + + if (createinfo->num_storage_buffers > 0) { + state->storage_buffers = (SDL_GPUBuffer **)SDL_calloc(createinfo->num_storage_buffers, sizeof(*state->storage_buffers)); + if (!state->storage_buffers) { + SDL_DestroyGPURenderState(state); + return NULL; + } + SDL_memcpy(state->storage_buffers, createinfo->storage_buffers, createinfo->num_storage_buffers * sizeof(*state->storage_buffers)); + state->num_storage_buffers = createinfo->num_storage_buffers; + } + + return state; +} + +bool SDL_SetGPURenderStateFragmentUniforms(SDL_GPURenderState *state, Uint32 slot_index, const void *data, Uint32 length) +{ + if (!state) { + return SDL_InvalidParamError("state"); + } + + if (!FlushRenderCommandsIfGPURenderStateNeeded(state)) { + return false; + } + + for (int i = 0; i < state->num_uniform_buffers; i++) { + SDL_GPURenderStateUniformBuffer *buffer = &state->uniform_buffers[i]; + if (buffer->slot_index == slot_index) { + void *new_data = SDL_realloc(buffer->data, length); + if (!new_data) { + return false; + } + SDL_memcpy(new_data, data, length); + buffer->data = new_data; + buffer->length = length; + return true; + } + } + + SDL_GPURenderStateUniformBuffer *buffers = (SDL_GPURenderStateUniformBuffer *)SDL_realloc(state->uniform_buffers, (state->num_uniform_buffers + 1) * sizeof(*state->uniform_buffers)); + if (!buffers) { + return false; + } + + SDL_GPURenderStateUniformBuffer *buffer = &buffers[state->num_uniform_buffers]; + buffer->slot_index = slot_index; + buffer->length = length; + buffer->data = SDL_malloc(length); + if (!buffer->data) { + SDL_free(buffers); + return false; + } + SDL_memcpy(buffer->data, data, length); + + state->uniform_buffers = buffers; + ++state->num_uniform_buffers; + return true; +} + +bool SDL_SetGPURenderState(SDL_Renderer *renderer, SDL_GPURenderState *state) +{ + CHECK_RENDERER_MAGIC(renderer, false); + + renderer->gpu_render_state = state; + return true; +} + +void SDL_DestroyGPURenderState(SDL_GPURenderState *state) +{ + if (!state) { + return; + } + + FlushRenderCommandsIfGPURenderStateNeeded(state); + + if (state->num_uniform_buffers > 0) { + for (int i = 0; i < state->num_uniform_buffers; i++) { + SDL_free(state->uniform_buffers[i].data); + } + SDL_free(state->uniform_buffers); + } + SDL_free(state->sampler_bindings); + SDL_free(state->storage_textures); + SDL_free(state->storage_buffers); + SDL_free(state); +} diff --git a/libs/SDL3/src/render/SDL_render_debug_font.h b/libs/SDL3/src/render/SDL_render_debug_font.h index ca296e8..1996338 100644 --- a/libs/SDL3/src/render/SDL_render_debug_font.h +++ b/libs/SDL3/src/render/SDL_render_debug_font.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/render/SDL_render_unsupported.c b/libs/SDL3/src/render/SDL_render_unsupported.c index 5369fb4..f01e52a 100644 --- a/libs/SDL3/src/render/SDL_render_unsupported.c +++ b/libs/SDL3/src/render/SDL_render_unsupported.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/render/SDL_sysrender.h b/libs/SDL3/src/render/SDL_sysrender.h index bbafefd..a1bb44a 100644 --- a/libs/SDL3/src/render/SDL_sysrender.h +++ b/libs/SDL3/src/render/SDL_sysrender.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -32,14 +32,6 @@ extern "C" { #endif -typedef enum SDL_TextureAddressMode -{ - SDL_TEXTURE_ADDRESS_INVALID = -1, - SDL_TEXTURE_ADDRESS_AUTO, - SDL_TEXTURE_ADDRESS_CLAMP, - SDL_TEXTURE_ADDRESS_WRAP, -} SDL_TextureAddressMode; - /** * A rectangle, with the origin at the upper left (double precision). */ @@ -78,6 +70,15 @@ typedef struct SDL_RenderViewState SDL_FPoint current_scale; // this is just `scale * logical_scale`, precalculated, since we use it a lot. } SDL_RenderViewState; +// Define the SDL texture palette structure +typedef struct SDL_TexturePalette +{ + int refcount; + Uint32 version; + Uint32 last_command_generation; // last command queue generation this palette was in. + void *internal; // Driver specific palette representation +} SDL_TexturePalette; + // Define the SDL texture structure struct SDL_Texture { @@ -89,6 +90,7 @@ struct SDL_Texture int refcount; /**< Application reference count, used when freeing texture */ // Private API definition + SDL_Renderer *renderer; SDL_Colorspace colorspace; // The colorspace of the texture float SDR_white_point; // The SDR white point for this content float HDR_headroom; // The HDR headroom needed by this content @@ -97,8 +99,10 @@ struct SDL_Texture SDL_ScaleMode scaleMode; // The texture scale mode SDL_FColor color; // Texture modulation values SDL_RenderViewState view; // Target texture view state - - SDL_Renderer *renderer; + SDL_Palette *public_palette; + SDL_TexturePalette *palette; + Uint32 palette_version; + SDL_Surface *palette_surface; // Support for formats not supported directly by the renderer SDL_Texture *native; @@ -118,6 +122,36 @@ struct SDL_Texture SDL_Texture *next; }; +// Define the GPU render state structure +typedef struct SDL_GPURenderStateUniformBuffer +{ + Uint32 slot_index; + void *data; + Uint32 length; +} SDL_GPURenderStateUniformBuffer; + +// Define the GPU render state structure +struct SDL_GPURenderState +{ + SDL_Renderer *renderer; + + Uint32 last_command_generation; // last command queue generation this state was in. + + SDL_GPUShader *fragment_shader; + + int num_sampler_bindings; + SDL_GPUTextureSamplerBinding *sampler_bindings; + + int num_storage_textures; + SDL_GPUTexture **storage_textures; + + int num_storage_buffers; + SDL_GPUBuffer **storage_buffers; + + int num_uniform_buffers; + SDL_GPURenderStateUniformBuffer *uniform_buffers; +}; + typedef enum { SDL_RENDERCMD_NO_OP, @@ -157,7 +191,9 @@ typedef struct SDL_RenderCommand SDL_BlendMode blend; SDL_Texture *texture; SDL_ScaleMode texture_scale_mode; - SDL_TextureAddressMode texture_address_mode; + SDL_TextureAddressMode texture_address_mode_u; + SDL_TextureAddressMode texture_address_mode_v; + SDL_GPURenderState *gpu_render_state; } draw; struct { @@ -209,6 +245,10 @@ struct SDL_Renderer void (*InvalidateCachedState)(SDL_Renderer *renderer); bool (*RunCommandQueue)(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize); + bool (*CreatePalette)(SDL_Renderer *renderer, SDL_TexturePalette *palette); + bool (*UpdatePalette)(SDL_Renderer *renderer, SDL_TexturePalette *palette, int ncolors, SDL_Color *colors); + void (*DestroyPalette)(SDL_Renderer *renderer, SDL_TexturePalette *palette); + bool (*ChangeTexturePalette)(SDL_Renderer *renderer, SDL_Texture *texture); bool (*UpdateTexture)(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch); @@ -245,6 +285,7 @@ struct SDL_Renderer SDL_PixelFormat *texture_formats; int num_texture_formats; bool software; + bool npot_texture_wrap_unsupported; // The window associated with the renderer SDL_Window *window; @@ -265,11 +306,17 @@ struct SDL_Renderer // The method of drawing lines SDL_RenderLineMethod line_method; + // Default scale mode for textures created with this renderer + SDL_ScaleMode scale_mode; + // The list of textures SDL_Texture *textures; SDL_Texture *target; SDL_Mutex *target_mutex; + // The list of palettes + SDL_HashTable *palettes; + SDL_Colorspace output_colorspace; float SDR_white_point; float HDR_headroom; @@ -278,7 +325,9 @@ struct SDL_Renderer float color_scale; SDL_FColor color; /**< Color for drawing operations values */ SDL_BlendMode blendMode; /**< The drawing blend mode */ - SDL_TextureAddressMode texture_address_mode; + SDL_TextureAddressMode texture_address_mode_u; + SDL_TextureAddressMode texture_address_mode_v; + SDL_GPURenderState *gpu_render_state; SDL_RenderCommand *render_commands; SDL_RenderCommand *render_commands_tail; @@ -328,6 +377,7 @@ extern SDL_RenderDriver D3D12_RenderDriver; extern SDL_RenderDriver GL_RenderDriver; extern SDL_RenderDriver GLES2_RenderDriver; extern SDL_RenderDriver METAL_RenderDriver; +extern SDL_RenderDriver NGAGE_RenderDriver; extern SDL_RenderDriver VULKAN_RenderDriver; extern SDL_RenderDriver PS2_RenderDriver; extern SDL_RenderDriver PSP_RenderDriver; @@ -338,6 +388,12 @@ extern SDL_RenderDriver GPU_RenderDriver; // Clean up any renderers at shutdown extern void SDL_QuitRender(void); +#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)) +#define RENDER_SAMPLER_COUNT (((1 << 0) | (1 << 1) | (1 << 2)) + 1) + // 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/SDL_yuv_sw.c b/libs/SDL3/src/render/SDL_yuv_sw.c index abe9e16..a837870 100644 --- a/libs/SDL3/src/render/SDL_yuv_sw.c +++ b/libs/SDL3/src/render/SDL_yuv_sw.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/render/SDL_yuv_sw_c.h b/libs/SDL3/src/render/SDL_yuv_sw_c.h index 807c44c..12c7920 100644 --- a/libs/SDL3/src/render/SDL_yuv_sw_c.h +++ b/libs/SDL3/src/render/SDL_yuv_sw_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette.h b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette.h new file mode 100644 index 0000000..201c337 --- /dev/null +++ b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette.h @@ -0,0 +1,272 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// Parameters: +// +// sampler2D image; +// sampler1D palette; +// float4 texel_size; +// float texture_type; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// texture_type c0 1 +// texel_size c1 1 +// image s0 1 +// palette s1 1 +// + + ps_2_0 + def c2, -1, 255, 0.5, 0.00390625 + def c3, -2, 0, 0, 0 + def c4, 1, 0, 0, 1 + dcl v0 + dcl t0.xy + dcl_2d s0 + dcl_2d s1 + mov r0.xz, c2 + mad r1.x, t0.x, c1.z, r0.z + mad r1.y, t0.y, c1.w, r0.z + frc r0.yz, r1.zxyw + add r1.xy, -r0.yzxw, r1 + add r1.zw, r1.wzyx, -c2.z + add r1.xy, r1, c2.z + mul r1.xy, r1, c1 + mul r2.xy, r1.wzyx, c1 + mov r3.x, r2.x + mov r3.y, r1.y + mov r4.y, r2.y + mov r4.x, r1.x + texld r3, r3, s0 + texld r2, r2, s0 + texld r1, r1, s0 + texld r4, r4, s0 + texld r5, t0, s0 + mad r0.w, r3.x, c2.y, c2.z + mul r3.xy, r0.w, c2.w + mad r0.w, r2.x, c2.y, c2.z + mul r2.xy, r0.w, c2.w + mad r0.w, r1.x, c2.y, c2.z + mul r1.xy, r0.w, c2.w + mad r0.w, r4.x, c2.y, c2.z + mul r4.xy, r0.w, c2.w + mad r0.w, r5.x, c2.y, c2.z + mul r5.xy, r0.w, c2.w + texld r3, r3, s1 + texld r2, r2, s1 + texld r1, r1, s1 + texld r4, r4, s1 + texld r5, r5, s1 + lrp r6, r0.z, r3, r2 + lrp r2, r0.z, r1, r4 + lrp r1, r0.y, r2, r6 + mov r2.x, c0.x + add r0.y, r2.x, c3.x + mul r0.y, r0.y, r0.y + cmp r1, -r0.y, r1, c4 + add r0.x, r0.x, c0.x + mul r0.x, r0.x, r0.x + cmp r0, -r0.x, r5, r1 + mul r0, r0, v0 + mov oC0, r0 + +// approximately 45 instruction slots used (10 texture, 35 arithmetic) +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 67, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 223, 0, + 0, 0, 0, 2, 255, 255, + 4, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 216, 0, 0, 0, 108, 0, + 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 116, 0, + 0, 0, 0, 0, 0, 0, + 132, 0, 0, 0, 3, 0, + 1, 0, 1, 0, 0, 0, + 140, 0, 0, 0, 0, 0, + 0, 0, 156, 0, 0, 0, + 2, 0, 1, 0, 1, 0, + 0, 0, 168, 0, 0, 0, + 0, 0, 0, 0, 184, 0, + 0, 0, 2, 0, 0, 0, + 1, 0, 0, 0, 200, 0, + 0, 0, 0, 0, 0, 0, + 105, 109, 97, 103, 101, 0, + 171, 171, 4, 0, 12, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 97, 108, 101, 116, 116, + 101, 0, 4, 0, 11, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 116, 101, 120, 101, 108, 95, + 115, 105, 122, 101, 0, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 116, 101, + 120, 116, 117, 114, 101, 95, + 116, 121, 112, 101, 0, 171, + 171, 171, 0, 0, 3, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 115, 95, 50, 95, 48, + 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, + 49, 48, 46, 49, 0, 171, + 81, 0, 0, 5, 2, 0, + 15, 160, 0, 0, 128, 191, + 0, 0, 127, 67, 0, 0, + 0, 63, 0, 0, 128, 59, + 81, 0, 0, 5, 3, 0, + 15, 160, 0, 0, 0, 192, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 81, 0, 0, 5, 4, 0, + 15, 160, 0, 0, 128, 63, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 128, 63, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 144, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 3, 176, + 31, 0, 0, 2, 0, 0, + 0, 144, 0, 8, 15, 160, + 31, 0, 0, 2, 0, 0, + 0, 144, 1, 8, 15, 160, + 1, 0, 0, 2, 0, 0, + 5, 128, 2, 0, 228, 160, + 4, 0, 0, 4, 1, 0, + 1, 128, 0, 0, 0, 176, + 1, 0, 170, 160, 0, 0, + 170, 128, 4, 0, 0, 4, + 1, 0, 2, 128, 0, 0, + 85, 176, 1, 0, 255, 160, + 0, 0, 170, 128, 19, 0, + 0, 2, 0, 0, 6, 128, + 1, 0, 210, 128, 2, 0, + 0, 3, 1, 0, 3, 128, + 0, 0, 201, 129, 1, 0, + 228, 128, 2, 0, 0, 3, + 1, 0, 12, 128, 1, 0, + 27, 128, 2, 0, 170, 161, + 2, 0, 0, 3, 1, 0, + 3, 128, 1, 0, 228, 128, + 2, 0, 170, 160, 5, 0, + 0, 3, 1, 0, 3, 128, + 1, 0, 228, 128, 1, 0, + 228, 160, 5, 0, 0, 3, + 2, 0, 3, 128, 1, 0, + 27, 128, 1, 0, 228, 160, + 1, 0, 0, 2, 3, 0, + 1, 128, 2, 0, 0, 128, + 1, 0, 0, 2, 3, 0, + 2, 128, 1, 0, 85, 128, + 1, 0, 0, 2, 4, 0, + 2, 128, 2, 0, 85, 128, + 1, 0, 0, 2, 4, 0, + 1, 128, 1, 0, 0, 128, + 66, 0, 0, 3, 3, 0, + 15, 128, 3, 0, 228, 128, + 0, 8, 228, 160, 66, 0, + 0, 3, 2, 0, 15, 128, + 2, 0, 228, 128, 0, 8, + 228, 160, 66, 0, 0, 3, + 1, 0, 15, 128, 1, 0, + 228, 128, 0, 8, 228, 160, + 66, 0, 0, 3, 4, 0, + 15, 128, 4, 0, 228, 128, + 0, 8, 228, 160, 66, 0, + 0, 3, 5, 0, 15, 128, + 0, 0, 228, 176, 0, 8, + 228, 160, 4, 0, 0, 4, + 0, 0, 8, 128, 3, 0, + 0, 128, 2, 0, 85, 160, + 2, 0, 170, 160, 5, 0, + 0, 3, 3, 0, 3, 128, + 0, 0, 255, 128, 2, 0, + 255, 160, 4, 0, 0, 4, + 0, 0, 8, 128, 2, 0, + 0, 128, 2, 0, 85, 160, + 2, 0, 170, 160, 5, 0, + 0, 3, 2, 0, 3, 128, + 0, 0, 255, 128, 2, 0, + 255, 160, 4, 0, 0, 4, + 0, 0, 8, 128, 1, 0, + 0, 128, 2, 0, 85, 160, + 2, 0, 170, 160, 5, 0, + 0, 3, 1, 0, 3, 128, + 0, 0, 255, 128, 2, 0, + 255, 160, 4, 0, 0, 4, + 0, 0, 8, 128, 4, 0, + 0, 128, 2, 0, 85, 160, + 2, 0, 170, 160, 5, 0, + 0, 3, 4, 0, 3, 128, + 0, 0, 255, 128, 2, 0, + 255, 160, 4, 0, 0, 4, + 0, 0, 8, 128, 5, 0, + 0, 128, 2, 0, 85, 160, + 2, 0, 170, 160, 5, 0, + 0, 3, 5, 0, 3, 128, + 0, 0, 255, 128, 2, 0, + 255, 160, 66, 0, 0, 3, + 3, 0, 15, 128, 3, 0, + 228, 128, 1, 8, 228, 160, + 66, 0, 0, 3, 2, 0, + 15, 128, 2, 0, 228, 128, + 1, 8, 228, 160, 66, 0, + 0, 3, 1, 0, 15, 128, + 1, 0, 228, 128, 1, 8, + 228, 160, 66, 0, 0, 3, + 4, 0, 15, 128, 4, 0, + 228, 128, 1, 8, 228, 160, + 66, 0, 0, 3, 5, 0, + 15, 128, 5, 0, 228, 128, + 1, 8, 228, 160, 18, 0, + 0, 4, 6, 0, 15, 128, + 0, 0, 170, 128, 3, 0, + 228, 128, 2, 0, 228, 128, + 18, 0, 0, 4, 2, 0, + 15, 128, 0, 0, 170, 128, + 1, 0, 228, 128, 4, 0, + 228, 128, 18, 0, 0, 4, + 1, 0, 15, 128, 0, 0, + 85, 128, 2, 0, 228, 128, + 6, 0, 228, 128, 1, 0, + 0, 2, 2, 0, 1, 128, + 0, 0, 0, 160, 2, 0, + 0, 3, 0, 0, 2, 128, + 2, 0, 0, 128, 3, 0, + 0, 160, 5, 0, 0, 3, + 0, 0, 2, 128, 0, 0, + 85, 128, 0, 0, 85, 128, + 88, 0, 0, 4, 1, 0, + 15, 128, 0, 0, 85, 129, + 1, 0, 228, 128, 4, 0, + 228, 160, 2, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 0, 128, 0, 0, 0, 160, + 5, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 0, 0, 0, 128, 88, 0, + 0, 4, 0, 0, 15, 128, + 0, 0, 0, 129, 5, 0, + 228, 128, 1, 0, 228, 128, + 5, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 128, + 0, 0, 228, 144, 1, 0, + 0, 2, 0, 8, 15, 128, + 0, 0, 228, 128, 255, 255, + 0, 0 +}; diff --git a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette.hlsli b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette.hlsli new file mode 100644 index 0000000..5bd5a58 --- /dev/null +++ b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette.hlsli @@ -0,0 +1,49 @@ + +cbuffer Constants +{ + float4 texel_size; +}; + +uniform sampler2D image; +uniform sampler1D palette; + +struct PixelShaderInput +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +static const float TEXTURETYPE_NONE = 0; +static const float TEXTURETYPE_PALETTE_NEAREST = 1; +static const float TEXTURETYPE_PALETTE_LINEAR = 2; + +float4 SamplePaletteNearest(float2 uv) +{ + float index = tex2D(image, uv).r * 255; + return tex1D(palette, (index + 0.5) / 256); +} + +// Implementation with thanks from bgolus: +// https://discussions.unity.com/t/how-to-make-data-shader-support-bilinear-trilinear/598639/8 +float4 SamplePaletteLinear(float2 uv) +{ + // scale & offset uvs to integer values at texel centers + float2 uv_texels = uv * texel_size.zw + 0.5; + + // get uvs for the center of the 4 surrounding texels by flooring + float4 uv_min_max = float4((floor(uv_texels) - 0.5) * texel_size.xy, (floor(uv_texels) + 0.5) * texel_size.xy); + + // blend factor + float2 uv_frac = frac(uv_texels); + + // sample all 4 texels + float4 texelA = SamplePaletteNearest(uv_min_max.xy); + float4 texelB = SamplePaletteNearest(uv_min_max.xw); + float4 texelC = SamplePaletteNearest(uv_min_max.zy); + float4 texelD = SamplePaletteNearest(uv_min_max.zw); + + // bilinear interpolation + return lerp(lerp(texelA, texelB, uv_frac.y), lerp(texelC, texelD, uv_frac.y), uv_frac.x); +} + diff --git a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Linear.h b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Linear.h new file mode 100644 index 0000000..da73f86 --- /dev/null +++ b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Linear.h @@ -0,0 +1,209 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// Parameters: +// +// sampler2D image; +// sampler1D palette; +// float4 texel_size; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// texel_size c0 1 +// image s0 1 +// palette s1 1 +// + + ps_2_0 + def c1, 0.5, -0.5, 255, 0.00390625 + dcl t0.xy + dcl v0 + dcl_2d s0 + dcl_2d s1 + mov r0.w, c1.x + mad r0.x, t0.x, c0.z, r0.w + mad r0.y, t0.y, c0.w, r0.w + frc r0.zw, r0.wzyx + add r0.xy, -r0.wzyx, r0 + add r1.xy, r0, c1.y + add r0.xy, r0, c1.x + mul r0.xy, r0, c0 + mul r1.xy, r1, c0 + mov r2.x, r1.x + mov r2.y, r0.y + mov r3.y, r1.y + mov r3.x, r0.x + texld r2, r2, s0 + texld r1, r1, s0 + texld r4, r0, s0 + texld r3, r3, s0 + mad r0.x, r2.x, c1.z, c1.x + mul r0.xy, r0.x, c1.w + mad r1.x, r1.x, c1.z, c1.x + mul r1.xy, r1.x, c1.w + mad r1.z, r4.x, c1.z, c1.x + mul r2.xy, r1.z, c1.w + mad r1.z, r3.x, c1.z, c1.x + mul r3.xy, r1.z, c1.w + texld r4, r0, s1 + texld r1, r1, s1 + texld r2, r2, s1 + texld r3, r3, s1 + lrp r5, r0.z, r4, r1 + lrp r1, r0.z, r2, r3 + lrp r2, r0.w, r1, r5 + mul r0, r2, v0 + mov oC0, r0 + +// approximately 34 instruction slots used (8 texture, 26 arithmetic) +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 54, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 171, 0, + 0, 0, 0, 2, 255, 255, + 3, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 164, 0, 0, 0, 88, 0, + 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 96, 0, + 0, 0, 0, 0, 0, 0, + 112, 0, 0, 0, 3, 0, + 1, 0, 1, 0, 0, 0, + 120, 0, 0, 0, 0, 0, + 0, 0, 136, 0, 0, 0, + 2, 0, 0, 0, 1, 0, + 0, 0, 148, 0, 0, 0, + 0, 0, 0, 0, 105, 109, + 97, 103, 101, 0, 171, 171, + 4, 0, 12, 0, 1, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 112, 97, + 108, 101, 116, 116, 101, 0, + 4, 0, 11, 0, 1, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 116, 101, + 120, 101, 108, 95, 115, 105, + 122, 101, 0, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 112, 115, 95, 50, + 95, 48, 0, 77, 105, 99, + 114, 111, 115, 111, 102, 116, + 32, 40, 82, 41, 32, 72, + 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, + 111, 109, 112, 105, 108, 101, + 114, 32, 49, 48, 46, 49, + 0, 171, 81, 0, 0, 5, + 1, 0, 15, 160, 0, 0, + 0, 63, 0, 0, 0, 191, + 0, 0, 127, 67, 0, 0, + 128, 59, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 3, 176, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 0, 0, 0, 144, 0, 8, + 15, 160, 31, 0, 0, 2, + 0, 0, 0, 144, 1, 8, + 15, 160, 1, 0, 0, 2, + 0, 0, 8, 128, 1, 0, + 0, 160, 4, 0, 0, 4, + 0, 0, 1, 128, 0, 0, + 0, 176, 0, 0, 170, 160, + 0, 0, 255, 128, 4, 0, + 0, 4, 0, 0, 2, 128, + 0, 0, 85, 176, 0, 0, + 255, 160, 0, 0, 255, 128, + 19, 0, 0, 2, 0, 0, + 12, 128, 0, 0, 27, 128, + 2, 0, 0, 3, 0, 0, + 3, 128, 0, 0, 27, 129, + 0, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 3, 128, + 0, 0, 228, 128, 1, 0, + 85, 160, 2, 0, 0, 3, + 0, 0, 3, 128, 0, 0, + 228, 128, 1, 0, 0, 160, + 5, 0, 0, 3, 0, 0, + 3, 128, 0, 0, 228, 128, + 0, 0, 228, 160, 5, 0, + 0, 3, 1, 0, 3, 128, + 1, 0, 228, 128, 0, 0, + 228, 160, 1, 0, 0, 2, + 2, 0, 1, 128, 1, 0, + 0, 128, 1, 0, 0, 2, + 2, 0, 2, 128, 0, 0, + 85, 128, 1, 0, 0, 2, + 3, 0, 2, 128, 1, 0, + 85, 128, 1, 0, 0, 2, + 3, 0, 1, 128, 0, 0, + 0, 128, 66, 0, 0, 3, + 2, 0, 15, 128, 2, 0, + 228, 128, 0, 8, 228, 160, + 66, 0, 0, 3, 1, 0, + 15, 128, 1, 0, 228, 128, + 0, 8, 228, 160, 66, 0, + 0, 3, 4, 0, 15, 128, + 0, 0, 228, 128, 0, 8, + 228, 160, 66, 0, 0, 3, + 3, 0, 15, 128, 3, 0, + 228, 128, 0, 8, 228, 160, + 4, 0, 0, 4, 0, 0, + 1, 128, 2, 0, 0, 128, + 1, 0, 170, 160, 1, 0, + 0, 160, 5, 0, 0, 3, + 0, 0, 3, 128, 0, 0, + 0, 128, 1, 0, 255, 160, + 4, 0, 0, 4, 1, 0, + 1, 128, 1, 0, 0, 128, + 1, 0, 170, 160, 1, 0, + 0, 160, 5, 0, 0, 3, + 1, 0, 3, 128, 1, 0, + 0, 128, 1, 0, 255, 160, + 4, 0, 0, 4, 1, 0, + 4, 128, 4, 0, 0, 128, + 1, 0, 170, 160, 1, 0, + 0, 160, 5, 0, 0, 3, + 2, 0, 3, 128, 1, 0, + 170, 128, 1, 0, 255, 160, + 4, 0, 0, 4, 1, 0, + 4, 128, 3, 0, 0, 128, + 1, 0, 170, 160, 1, 0, + 0, 160, 5, 0, 0, 3, + 3, 0, 3, 128, 1, 0, + 170, 128, 1, 0, 255, 160, + 66, 0, 0, 3, 4, 0, + 15, 128, 0, 0, 228, 128, + 1, 8, 228, 160, 66, 0, + 0, 3, 1, 0, 15, 128, + 1, 0, 228, 128, 1, 8, + 228, 160, 66, 0, 0, 3, + 2, 0, 15, 128, 2, 0, + 228, 128, 1, 8, 228, 160, + 66, 0, 0, 3, 3, 0, + 15, 128, 3, 0, 228, 128, + 1, 8, 228, 160, 18, 0, + 0, 4, 5, 0, 15, 128, + 0, 0, 170, 128, 4, 0, + 228, 128, 1, 0, 228, 128, + 18, 0, 0, 4, 1, 0, + 15, 128, 0, 0, 170, 128, + 2, 0, 228, 128, 3, 0, + 228, 128, 18, 0, 0, 4, + 2, 0, 15, 128, 0, 0, + 255, 128, 1, 0, 228, 128, + 5, 0, 228, 128, 5, 0, + 0, 3, 0, 0, 15, 128, + 2, 0, 228, 128, 0, 0, + 228, 144, 1, 0, 0, 2, + 0, 8, 15, 128, 0, 0, + 228, 128, 255, 255, 0, 0 +}; diff --git a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Linear.hlsl b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Linear.hlsl new file mode 100644 index 0000000..63aeaa0 --- /dev/null +++ b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Linear.hlsl @@ -0,0 +1,7 @@ + +#include "D3D9_PixelShader_Palette.hlsli" + +float4 main(PixelShaderInput input) : SV_TARGET +{ + return SamplePaletteLinear(input.tex) * input.color; +} diff --git a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Nearest.h b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Nearest.h new file mode 100644 index 0000000..6a36bb7 --- /dev/null +++ b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Nearest.h @@ -0,0 +1,95 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// Parameters: +// +// sampler2D image; +// sampler1D palette; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// image s0 1 +// palette s1 1 +// + + ps_2_0 + def c0, 255, 0.5, 0.00390625, 0 + dcl t0.xy + dcl v0 + dcl_2d s0 + dcl_2d s1 + texld r0, t0, s0 + mad r0.x, r0.x, c0.x, c0.y + mul r0.xy, r0.x, c0.z + texld r0, r0, s1 + mul r0, r0, v0 + mov oC0, r0 + +// approximately 6 instruction slots used (2 texture, 4 arithmetic) +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 42, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 123, 0, + 0, 0, 0, 2, 255, 255, + 2, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 116, 0, 0, 0, 68, 0, + 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 76, 0, + 0, 0, 0, 0, 0, 0, + 92, 0, 0, 0, 3, 0, + 1, 0, 1, 0, 0, 0, + 100, 0, 0, 0, 0, 0, + 0, 0, 105, 109, 97, 103, + 101, 0, 171, 171, 4, 0, + 12, 0, 1, 0, 1, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 112, 97, 108, 101, + 116, 116, 101, 0, 4, 0, + 11, 0, 1, 0, 1, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 112, 115, 95, 50, + 95, 48, 0, 77, 105, 99, + 114, 111, 115, 111, 102, 116, + 32, 40, 82, 41, 32, 72, + 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, + 111, 109, 112, 105, 108, 101, + 114, 32, 49, 48, 46, 49, + 0, 171, 81, 0, 0, 5, + 0, 0, 15, 160, 0, 0, + 127, 67, 0, 0, 0, 63, + 0, 0, 128, 59, 0, 0, + 0, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 3, 176, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 0, 0, 0, 144, 0, 8, + 15, 160, 31, 0, 0, 2, + 0, 0, 0, 144, 1, 8, + 15, 160, 66, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 176, 0, 8, 228, 160, + 4, 0, 0, 4, 0, 0, + 1, 128, 0, 0, 0, 128, + 0, 0, 0, 160, 0, 0, + 85, 160, 5, 0, 0, 3, + 0, 0, 3, 128, 0, 0, + 0, 128, 0, 0, 170, 160, + 66, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 128, + 1, 8, 228, 160, 5, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 228, 128, 0, 0, + 228, 144, 1, 0, 0, 2, + 0, 8, 15, 128, 0, 0, + 228, 128, 255, 255, 0, 0 +}; diff --git a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Nearest.hlsl b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Nearest.hlsl new file mode 100644 index 0000000..6131f7b --- /dev/null +++ b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Nearest.hlsl @@ -0,0 +1,7 @@ + +#include "D3D9_PixelShader_Palette.hlsli" + +float4 main(PixelShaderInput input) : SV_TARGET +{ + return SamplePaletteNearest(input.tex) * input.color; +} diff --git a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_YUV.h b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_YUV.h index e6d9130..57a0a77 100644 --- a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_YUV.h +++ b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_YUV.h @@ -51,114 +51,114 @@ const BYTE g_ps20_main[] = { - 0, 2, 255, 255, 254, 255, - 97, 0, 67, 84, 65, 66, - 28, 0, 0, 0, 87, 1, - 0, 0, 0, 2, 255, 255, - 7, 0, 0, 0, 28, 0, - 0, 0, 0, 1, 0, 0, - 80, 1, 0, 0, 168, 0, - 0, 0, 2, 0, 3, 0, - 1, 0, 0, 0, 176, 0, - 0, 0, 0, 0, 0, 0, - 192, 0, 0, 0, 2, 0, - 2, 0, 1, 0, 0, 0, - 176, 0, 0, 0, 0, 0, - 0, 0, 199, 0, 0, 0, - 2, 0, 1, 0, 1, 0, - 0, 0, 176, 0, 0, 0, - 0, 0, 0, 0, 206, 0, - 0, 0, 2, 0, 0, 0, - 1, 0, 0, 0, 176, 0, - 0, 0, 0, 0, 0, 0, - 214, 0, 0, 0, 3, 0, - 1, 0, 1, 0, 0, 0, - 240, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, - 3, 0, 2, 0, 1, 0, - 0, 0, 24, 1, 0, 0, - 0, 0, 0, 0, 40, 1, - 0, 0, 3, 0, 0, 0, - 1, 0, 0, 0, 64, 1, - 0, 0, 0, 0, 0, 0, - 66, 99, 111, 101, 102, 102, - 0, 171, 1, 0, 3, 0, - 1, 0, 4, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 71, 99, 111, 101, 102, 102, - 0, 82, 99, 111, 101, 102, - 102, 0, 89, 111, 102, 102, - 115, 101, 116, 0, 116, 104, - 101, 83, 97, 109, 112, 108, - 101, 114, 43, 116, 104, 101, - 84, 101, 120, 116, 117, 114, - 101, 85, 0, 171, 171, 171, - 4, 0, 7, 0, 1, 0, - 4, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 116, 104, - 101, 83, 97, 109, 112, 108, - 101, 114, 43, 116, 104, 101, - 84, 101, 120, 116, 117, 114, - 101, 86, 0, 171, 4, 0, - 7, 0, 1, 0, 4, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 116, 104, 101, 83, - 97, 109, 112, 108, 101, 114, - 43, 116, 104, 101, 84, 101, - 120, 116, 117, 114, 101, 89, - 0, 171, 4, 0, 7, 0, - 1, 0, 4, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 112, 115, 95, 50, 95, 48, - 0, 77, 105, 99, 114, 111, - 115, 111, 102, 116, 32, 40, - 82, 41, 32, 72, 76, 83, - 76, 32, 83, 104, 97, 100, - 101, 114, 32, 67, 111, 109, - 112, 105, 108, 101, 114, 32, - 49, 48, 46, 49, 0, 171, - 81, 0, 0, 5, 4, 0, - 15, 160, 0, 0, 128, 63, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 31, 0, 0, 2, 0, 0, - 0, 128, 0, 0, 3, 176, - 31, 0, 0, 2, 0, 0, - 0, 128, 0, 0, 15, 144, - 31, 0, 0, 2, 0, 0, - 0, 144, 0, 8, 15, 160, - 31, 0, 0, 2, 0, 0, - 0, 144, 1, 8, 15, 160, - 31, 0, 0, 2, 0, 0, - 0, 144, 2, 8, 15, 160, - 66, 0, 0, 3, 0, 0, - 15, 128, 0, 0, 228, 176, - 0, 8, 228, 160, 66, 0, - 0, 3, 1, 0, 15, 128, - 0, 0, 228, 176, 1, 8, - 228, 160, 66, 0, 0, 3, - 2, 0, 15, 128, 0, 0, - 228, 176, 2, 8, 228, 160, - 1, 0, 0, 2, 0, 0, - 2, 128, 1, 0, 0, 128, - 1, 0, 0, 2, 0, 0, - 4, 128, 2, 0, 0, 128, - 2, 0, 0, 3, 0, 0, - 7, 128, 0, 0, 228, 128, - 0, 0, 228, 160, 8, 0, - 0, 3, 1, 0, 1, 128, - 0, 0, 228, 128, 1, 0, - 228, 160, 8, 0, 0, 3, - 1, 0, 2, 128, 0, 0, - 228, 128, 2, 0, 228, 160, - 8, 0, 0, 3, 1, 0, - 4, 128, 0, 0, 228, 128, - 3, 0, 228, 160, 1, 0, - 0, 2, 1, 0, 8, 128, - 4, 0, 0, 160, 5, 0, - 0, 3, 0, 0, 15, 128, - 1, 0, 228, 128, 0, 0, - 228, 144, 1, 0, 0, 2, - 0, 8, 15, 128, 0, 0, + 0, 2, 255, 255, 254, 255, + 97, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 87, 1, + 0, 0, 0, 2, 255, 255, + 7, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 80, 1, 0, 0, 168, 0, + 0, 0, 2, 0, 3, 0, + 1, 0, 0, 0, 176, 0, + 0, 0, 0, 0, 0, 0, + 192, 0, 0, 0, 2, 0, + 2, 0, 1, 0, 0, 0, + 176, 0, 0, 0, 0, 0, + 0, 0, 199, 0, 0, 0, + 2, 0, 1, 0, 1, 0, + 0, 0, 176, 0, 0, 0, + 0, 0, 0, 0, 206, 0, + 0, 0, 2, 0, 0, 0, + 1, 0, 0, 0, 176, 0, + 0, 0, 0, 0, 0, 0, + 214, 0, 0, 0, 3, 0, + 1, 0, 1, 0, 0, 0, + 240, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, + 3, 0, 2, 0, 1, 0, + 0, 0, 24, 1, 0, 0, + 0, 0, 0, 0, 40, 1, + 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 64, 1, + 0, 0, 0, 0, 0, 0, + 66, 99, 111, 101, 102, 102, + 0, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 71, 99, 111, 101, 102, 102, + 0, 82, 99, 111, 101, 102, + 102, 0, 89, 111, 102, 102, + 115, 101, 116, 0, 116, 104, + 101, 83, 97, 109, 112, 108, + 101, 114, 43, 116, 104, 101, + 84, 101, 120, 116, 117, 114, + 101, 85, 0, 171, 171, 171, + 4, 0, 7, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 116, 104, + 101, 83, 97, 109, 112, 108, + 101, 114, 43, 116, 104, 101, + 84, 101, 120, 116, 117, 114, + 101, 86, 0, 171, 4, 0, + 7, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 116, 104, 101, 83, + 97, 109, 112, 108, 101, 114, + 43, 116, 104, 101, 84, 101, + 120, 116, 117, 114, 101, 89, + 0, 171, 4, 0, 7, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 115, 95, 50, 95, 48, + 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, + 49, 48, 46, 49, 0, 171, + 81, 0, 0, 5, 4, 0, + 15, 160, 0, 0, 128, 63, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 3, 176, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 144, + 31, 0, 0, 2, 0, 0, + 0, 144, 0, 8, 15, 160, + 31, 0, 0, 2, 0, 0, + 0, 144, 1, 8, 15, 160, + 31, 0, 0, 2, 0, 0, + 0, 144, 2, 8, 15, 160, + 66, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 176, + 0, 8, 228, 160, 66, 0, + 0, 3, 1, 0, 15, 128, + 0, 0, 228, 176, 1, 8, + 228, 160, 66, 0, 0, 3, + 2, 0, 15, 128, 0, 0, + 228, 176, 2, 8, 228, 160, + 1, 0, 0, 2, 0, 0, + 2, 128, 1, 0, 0, 128, + 1, 0, 0, 2, 0, 0, + 4, 128, 2, 0, 0, 128, + 2, 0, 0, 3, 0, 0, + 7, 128, 0, 0, 228, 128, + 0, 0, 228, 160, 8, 0, + 0, 3, 1, 0, 1, 128, + 0, 0, 228, 128, 1, 0, + 228, 160, 8, 0, 0, 3, + 1, 0, 2, 128, 0, 0, + 228, 128, 2, 0, 228, 160, + 8, 0, 0, 3, 1, 0, + 4, 128, 0, 0, 228, 128, + 3, 0, 228, 160, 1, 0, + 0, 2, 1, 0, 8, 128, + 4, 0, 0, 160, 5, 0, + 0, 3, 0, 0, 15, 128, + 1, 0, 228, 128, 0, 0, + 228, 144, 1, 0, 0, 2, + 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0 }; diff --git a/libs/SDL3/src/render/direct3d/SDL_render_d3d.c b/libs/SDL3/src/render/direct3d/SDL_render_d3d.c index 1d395f1..3a7b8af 100644 --- a/libs/SDL3/src/render/direct3d/SDL_render_d3d.c +++ b/libs/SDL3/src/render/direct3d/SDL_render_d3d.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -46,8 +46,29 @@ typedef struct bool cliprect_dirty; D3D9_Shader shader; const float *shader_params; + bool texture_state_dirty; } D3D_DrawStateCache; +typedef struct +{ + bool dirty; + int w, h; + DWORD usage; + Uint32 format; + D3DFORMAT d3dfmt; + IDirect3DTexture9 *texture; + IDirect3DTexture9 *staging; +} D3D_TextureRep; + +struct D3D_PaletteData +{ + D3D_TextureRep texture; + + struct D3D_PaletteData *prev; + struct D3D_PaletteData *next; +}; +typedef struct D3D_PaletteData D3D_PaletteData; + // Direct3D renderer implementation typedef struct @@ -61,36 +82,26 @@ typedef struct bool beginScene; bool enableSeparateAlphaBlend; SDL_ScaleMode scaleMode[3]; - SDL_TextureAddressMode addressMode[3]; + SDL_TextureAddressMode addressModeU[3]; + SDL_TextureAddressMode addressModeV[3]; IDirect3DSurface9 *defaultRenderTarget; IDirect3DSurface9 *currentRenderTarget; void *d3dxDLL; -#ifdef SDL_HAVE_YUV LPDIRECT3DPIXELSHADER9 shaders[NUM_SHADERS]; -#endif LPDIRECT3DVERTEXBUFFER9 vertexBuffers[8]; size_t vertexBufferSize[8]; int currentVertexBuffer; bool reportedVboProblem; D3D_DrawStateCache drawstate; + D3D_PaletteData *palettes; } D3D_RenderData; -typedef struct -{ - bool dirty; - int w, h; - DWORD usage; - Uint32 format; - D3DFORMAT d3dfmt; - IDirect3DTexture9 *texture; - IDirect3DTexture9 *staging; -} D3D_TextureRep; - typedef struct { D3D_TextureRep texture; - D3D9_Shader shader; + UINT shader_params_length; const float *shader_params; + float palette_shader_params[4]; #ifdef SDL_HAVE_YUV // YV12 texture support @@ -197,6 +208,7 @@ static D3DFORMAT PixelFormatToD3DFMT(Uint32 format) return D3DFMT_X8R8G8B8; case SDL_PIXELFORMAT_ARGB8888: return D3DFMT_A8R8G8B8; + case SDL_PIXELFORMAT_INDEX8: case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: case SDL_PIXELFORMAT_NV12: @@ -278,8 +290,11 @@ static void D3D_InitRenderState(D3D_RenderData *data) } // Reset our current address mode - for (int i = 0; i < SDL_arraysize(data->addressMode); ++i) { - data->addressMode[i] = SDL_TEXTURE_ADDRESS_INVALID; + for (int i = 0; i < SDL_arraysize(data->addressModeU); ++i) { + data->addressModeU[i] = SDL_TEXTURE_ADDRESS_INVALID; + } + for (int i = 0; i < SDL_arraysize(data->addressModeV); ++i) { + data->addressModeV[i] = SDL_TEXTURE_ADDRESS_INVALID; } // Start the render with beginScene @@ -526,6 +541,99 @@ static void D3D_DestroyTextureRep(D3D_TextureRep *texture) } } +static bool D3D_CreatePalette(SDL_Renderer *renderer, SDL_TexturePalette *palette) +{ + D3D_RenderData *data = (D3D_RenderData *)renderer->internal; + D3D_PaletteData *palettedata = (D3D_PaletteData *)SDL_calloc(1, sizeof(*palettedata)); + if (!palettedata) { + return false; + } + palette->internal = palettedata; + + if (!D3D_CreateTextureRep(data->device, &palettedata->texture, 0, SDL_PIXELFORMAT_ARGB8888, D3DFMT_A8R8G8B8, 256, 1)) { + SDL_free(palettedata); + return false; + } + + // Keep a reference to the palette so we can restore the texture if we lose the D3D device + if (data->palettes) { + palettedata->next = data->palettes; + data->palettes->prev = palettedata; + } + data->palettes = palettedata; + return true; +} + +static bool D3D_UpdatePalette(SDL_Renderer *renderer, SDL_TexturePalette *palette, int ncolors, SDL_Color *colors) +{ + D3D_RenderData *data = (D3D_RenderData *)renderer->internal; + D3D_PaletteData *palettedata = (D3D_PaletteData *)palette->internal; + bool retval; + + Uint32 *entries = SDL_stack_alloc(Uint32, ncolors); + if (!entries) { + return false; + } + for (int i = 0; i < ncolors; ++i) { + entries[i] = (colors[i].a << 24) | (colors[i].r << 16) | (colors[i].g << 8) | colors[i].b; + } + retval = D3D_UpdateTextureRep(data->device, &palettedata->texture, 0, 0, ncolors, 1, entries, ncolors * sizeof(*entries)); + SDL_stack_free(entries); + return retval; +} + +static void D3D_DestroyPalette(SDL_Renderer *renderer, SDL_TexturePalette *palette) +{ + D3D_RenderData *data = (D3D_RenderData *)renderer->internal; + D3D_PaletteData *palettedata = (D3D_PaletteData *)palette->internal; + + if (palettedata) { + D3D_DestroyTextureRep(&palettedata->texture); + + if (data->palettes == palettedata) { + data->palettes = palettedata->next; + } else if (palettedata->prev) { + palettedata->prev->next = palettedata->next; + } + if (palettedata->next) { + palettedata->next->prev = palettedata->prev; + } + SDL_free(palettedata); + } +} + +static bool D3D_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, + const SDL_Rect *rect, const void *pixels, int pitch) +{ + D3D_RenderData *data = (D3D_RenderData *)renderer->internal; + D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal; + + if (!texturedata) { + return SDL_SetError("Texture is not currently available"); + } + + if (!D3D_UpdateTextureRep(data->device, &texturedata->texture, rect->x, rect->y, rect->w, rect->h, pixels, pitch)) { + return false; + } +#ifdef SDL_HAVE_YUV + if (texturedata->yuv) { + // Skip to the correct offset into the next texture + pixels = (const void *)((const Uint8 *)pixels + rect->h * pitch); + + if (!D3D_UpdateTextureRep(data->device, texture->format == SDL_PIXELFORMAT_YV12 ? &texturedata->vtexture : &texturedata->utexture, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, pixels, (pitch + 1) / 2)) { + return false; + } + + // Skip to the correct offset into the next texture + pixels = (const void *)((const Uint8 *)pixels + ((rect->h + 1) / 2) * ((pitch + 1) / 2)); + if (!D3D_UpdateTextureRep(data->device, texture->format == SDL_PIXELFORMAT_YV12 ? &texturedata->utexture : &texturedata->vtexture, rect->x / 2, (rect->y + 1) / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, pixels, (pitch + 1) / 2)) { + return false; + } + } +#endif + return true; +} + static bool D3D_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { D3D_RenderData *data = (D3D_RenderData *)renderer->internal; @@ -548,6 +656,14 @@ static bool D3D_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ if (!D3D_CreateTextureRep(data->device, &texturedata->texture, usage, texture->format, PixelFormatToD3DFMT(texture->format), texture->w, texture->h)) { return false; } + if (texture->format == SDL_PIXELFORMAT_INDEX8) { + texturedata->shader_params_length = 1; // The palette shader takes 1 float4 parameters + texturedata->shader_params = texturedata->palette_shader_params; + texturedata->palette_shader_params[0] = 1.0f / texture->w; + texturedata->palette_shader_params[1] = 1.0f / texture->h; + texturedata->palette_shader_params[2] = texture->w; + texturedata->palette_shader_params[3] = texture->h; + } #ifdef SDL_HAVE_YUV if (texture->format == SDL_PIXELFORMAT_YV12 || texture->format == SDL_PIXELFORMAT_IYUV) { @@ -561,7 +677,7 @@ static bool D3D_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ return false; } - texturedata->shader = SHADER_YUV; + texturedata->shader_params_length = 4; // The YUV shader takes 4 float4 parameters texturedata->shader_params = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8); if (texturedata->shader_params == NULL) { return SDL_SetError("Unsupported YUV colorspace"); @@ -597,38 +713,6 @@ static bool D3D_RecreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) return true; } -static bool D3D_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, - const SDL_Rect *rect, const void *pixels, int pitch) -{ - D3D_RenderData *data = (D3D_RenderData *)renderer->internal; - D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal; - - if (!texturedata) { - return SDL_SetError("Texture is not currently available"); - } - - if (!D3D_UpdateTextureRep(data->device, &texturedata->texture, rect->x, rect->y, rect->w, rect->h, pixels, pitch)) { - return false; - } -#ifdef SDL_HAVE_YUV - if (texturedata->yuv) { - // Skip to the correct offset into the next texture - pixels = (const void *)((const Uint8 *)pixels + rect->h * pitch); - - if (!D3D_UpdateTextureRep(data->device, texture->format == SDL_PIXELFORMAT_YV12 ? &texturedata->vtexture : &texturedata->utexture, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, pixels, (pitch + 1) / 2)) { - return false; - } - - // Skip to the correct offset into the next texture - pixels = (const void *)((const Uint8 *)pixels + ((rect->h + 1) / 2) * ((pitch + 1) / 2)); - if (!D3D_UpdateTextureRep(data->device, texture->format == SDL_PIXELFORMAT_YV12 ? &texturedata->utexture : &texturedata->vtexture, rect->x / 2, (rect->y + 1) / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, pixels, (pitch + 1) / 2)) { - return false; - } - } -#endif - return true; -} - #ifdef SDL_HAVE_YUV static bool D3D_UpdateTextureYUV(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, @@ -732,6 +816,7 @@ static void D3D_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture) data->drawstate.texture = NULL; data->drawstate.shader = SHADER_NONE; data->drawstate.shader_params = NULL; + data->drawstate.texture_state_dirty = false; IDirect3DDevice9_SetPixelShader(data->device, NULL); IDirect3DDevice9_SetTexture(data->device, 0, NULL); } @@ -921,6 +1006,7 @@ static void UpdateTextureScaleMode(D3D_RenderData *data, SDL_ScaleMode scaleMode { if (scaleMode != data->scaleMode[index]) { switch (scaleMode) { + case SDL_SCALEMODE_PIXELART: case SDL_SCALEMODE_NEAREST: IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_MINFILTER, D3DTEXF_POINT); IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_MAGFILTER, D3DTEXF_POINT); @@ -936,26 +1022,32 @@ static void UpdateTextureScaleMode(D3D_RenderData *data, SDL_ScaleMode scaleMode } } -static void UpdateTextureAddressMode(D3D_RenderData *data, SDL_TextureAddressMode addressMode, unsigned index) +static DWORD TranslateAddressMode(SDL_TextureAddressMode addressMode) { - if (addressMode != data->addressMode[index]) { - switch (addressMode) { - case SDL_TEXTURE_ADDRESS_CLAMP: - IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP); - IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP); - break; - case SDL_TEXTURE_ADDRESS_WRAP: - IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP); - IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP); - break; - default: - break; - } - data->addressMode[index] = addressMode; + switch (addressMode) { + case SDL_TEXTURE_ADDRESS_CLAMP: + return D3DTADDRESS_CLAMP; + case SDL_TEXTURE_ADDRESS_WRAP: + return D3DTADDRESS_WRAP; + default: + SDL_assert(!"Unknown texture address mode"); + return D3DTADDRESS_CLAMP; } } -static bool SetupTextureState(D3D_RenderData *data, SDL_Texture *texture, D3D9_Shader *shader, const float **shader_params) +static void UpdateTextureAddressMode(D3D_RenderData *data, SDL_TextureAddressMode addressModeU, SDL_TextureAddressMode addressModeV, unsigned index) +{ + if (addressModeU != data->addressModeU[index]) { + IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSU, TranslateAddressMode(addressModeU)); + data->addressModeU[index] = addressModeU; + } + if (addressModeV != data->addressModeV[index]) { + IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSV, TranslateAddressMode(addressModeV)); + data->addressModeV[index] = addressModeV; + } +} + +static bool SetupTextureState(D3D_RenderData *data, SDL_Texture *texture, SDL_ScaleMode scale_mode, D3D9_Shader *shader, const float **shader_params) { D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal; @@ -963,12 +1055,28 @@ static bool SetupTextureState(D3D_RenderData *data, SDL_Texture *texture, D3D9_S return SDL_SetError("Texture is not currently available"); } - *shader = texturedata->shader; + if (texture->format == SDL_PIXELFORMAT_INDEX8) { + if (scale_mode == SDL_SCALEMODE_LINEAR) { + *shader = SHADER_PALETTE_LINEAR; + } else { + *shader = SHADER_PALETTE_NEAREST; + } +#ifdef SDL_HAVE_YUV + } else if (texturedata->yuv) { + *shader = SHADER_YUV; +#endif // SDL_HAVE_YUV + } *shader_params = texturedata->shader_params; if (!BindTextureRep(data->device, &texturedata->texture, 0)) { return false; } + if (texture->palette) { + D3D_PaletteData *palette = (D3D_PaletteData *)texture->palette->internal; + if (!BindTextureRep(data->device, &palette->texture, 1)) { + return false; + } + } #ifdef SDL_HAVE_YUV if (texturedata->yuv) { if (!BindTextureRep(data->device, &texturedata->utexture, 1)) { @@ -987,11 +1095,9 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd) SDL_Texture *texture = cmd->data.draw.texture; const SDL_BlendMode blend = cmd->data.draw.blend; - if (texture != data->drawstate.texture) { -#ifdef SDL_HAVE_YUV + if (texture != data->drawstate.texture || data->drawstate.texture_state_dirty) { D3D_TextureData *oldtexturedata = data->drawstate.texture ? (D3D_TextureData *)data->drawstate.texture->internal : NULL; D3D_TextureData *newtexturedata = texture ? (D3D_TextureData *)texture->internal : NULL; -#endif D3D9_Shader shader = SHADER_NONE; const float *shader_params = NULL; @@ -999,18 +1105,21 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd) if (!texture) { IDirect3DDevice9_SetTexture(data->device, 0, NULL); } + if ((!newtexturedata || !texture->palette) && + ((oldtexturedata && data->drawstate.texture->palette) || data->drawstate.texture_state_dirty)) { + IDirect3DDevice9_SetTexture(data->device, 1, NULL); + } #ifdef SDL_HAVE_YUV - if ((!newtexturedata || !newtexturedata->yuv) && (oldtexturedata && oldtexturedata->yuv)) { + if ((!newtexturedata || !newtexturedata->yuv) && ((oldtexturedata && oldtexturedata->yuv) || data->drawstate.texture_state_dirty)) { IDirect3DDevice9_SetTexture(data->device, 1, NULL); IDirect3DDevice9_SetTexture(data->device, 2, NULL); } #endif - if (texture && !SetupTextureState(data, texture, &shader, &shader_params)) { + if (texture && !SetupTextureState(data, texture, cmd->data.draw.texture_scale_mode, &shader, &shader_params)) { return false; } -#ifdef SDL_HAVE_YUV - if (shader != data->drawstate.shader) { + if (shader != data->drawstate.shader || data->drawstate.texture_state_dirty) { const HRESULT result = IDirect3DDevice9_SetPixelShader(data->device, data->shaders[shader]); if (FAILED(result)) { return D3D_SetError("IDirect3DDevice9_SetPixelShader()", result); @@ -1018,7 +1127,7 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd) data->drawstate.shader = shader; } - if (shader_params != data->drawstate.shader_params) { + if (shader_params != data->drawstate.shader_params || data->drawstate.texture_state_dirty) { if (shader_params) { const UINT shader_params_length = 4; // The YUV shader takes 4 float4 parameters const HRESULT result = IDirect3DDevice9_SetPixelShaderConstantF(data->device, 0, shader_params, shader_params_length); @@ -1028,13 +1137,17 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd) } data->drawstate.shader_params = shader_params; } -#endif // SDL_HAVE_YUV data->drawstate.texture = texture; + data->drawstate.texture_state_dirty = false; } else if (texture) { D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal; if (texturedata) { UpdateDirtyTexture(data->device, &texturedata->texture); + if (texture->palette) { + D3D_PaletteData *palettedata = (D3D_PaletteData *)texture->palette->internal; + UpdateDirtyTexture(data->device, &palettedata->texture); + } #ifdef SDL_HAVE_YUV if (texturedata->yuv) { UpdateDirtyTexture(data->device, &texturedata->utexture); @@ -1046,15 +1159,15 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd) if (texture) { UpdateTextureScaleMode(data, cmd->data.draw.texture_scale_mode, 0); - UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode, 0); + UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v, 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); + UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v, 1); + UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v, 2); } #endif // SDL_HAVE_YUV } @@ -1140,6 +1253,7 @@ static void D3D_InvalidateCachedState(SDL_Renderer *renderer) data->drawstate.texture = NULL; data->drawstate.shader = SHADER_NONE; data->drawstate.shader_params = NULL; + data->drawstate.texture_state_dirty = true; } static bool D3D_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) @@ -1203,10 +1317,7 @@ static bool D3D_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, switch (cmd->command) { case SDL_RENDERCMD_SETDRAWCOLOR: { - /* currently this is sent with each vertex, but if we move to - shaders, we can put this in a uniform here and reduce vertex - buffer bandwidth */ - break; + break; // this isn't currently used in this render backend. } case SDL_RENDERCMD_SETVIEWPORT: @@ -1267,43 +1378,74 @@ static bool D3D_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, break; } - case SDL_RENDERCMD_DRAW_POINTS: - { - const size_t count = cmd->data.draw.count; - const size_t first = cmd->data.draw.first; - SetDrawState(data, cmd); - if (vbo) { - IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)(first / sizeof(Vertex)), (UINT)count); - } else { - const Vertex *verts = (Vertex *)(((Uint8 *)vertices) + first); - IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, (UINT)count, verts, sizeof(Vertex)); - } - break; - } - case SDL_RENDERCMD_DRAW_LINES: { - const size_t count = cmd->data.draw.count; + size_t count = cmd->data.draw.count; const size_t first = cmd->data.draw.first; + const size_t start = first / sizeof(Vertex); const Vertex *verts = (Vertex *)(((Uint8 *)vertices) + first); - /* DirectX 9 has the same line rasterization semantics as GDI, - so we need to close the endpoint of the line with a second draw call. - NOLINTNEXTLINE(clang-analyzer-core.NullDereference): FIXME: Can verts truly not be NULL ? */ - const bool close_endpoint = ((count == 2) || (verts[0].x != verts[count - 1].x) || (verts[0].y != verts[count - 1].y)); - SetDrawState(data, cmd); - if (vbo) { - IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_LINESTRIP, (UINT)(first / sizeof(Vertex)), (UINT)(count - 1)); - if (close_endpoint) { - IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)((first / sizeof(Vertex)) + (count - 1)), 1); + // Add the final point in the line + size_t line_start = 0; + size_t line_end = line_start + count - 1; + if (count == 2 || verts[line_start].x != verts[line_end].x || verts[line_start].y != verts[line_end].y) { + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)(start + line_end), 1); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, 1, &verts[line_end], sizeof(Vertex)); + } + } + + if (count > 2) { + // joined lines cannot be grouped + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_LINESTRIP, (UINT)start, (UINT)(count - 1)); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_LINESTRIP, (UINT)(count - 1), verts, sizeof(Vertex)); } } else { - IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_LINESTRIP, (UINT)(count - 1), verts, sizeof(Vertex)); - if (close_endpoint) { - IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, 1, &verts[count - 1], sizeof(Vertex)); + // let's group non joined lines + SDL_RenderCommand *finalcmd = cmd; + SDL_RenderCommand *nextcmd; + SDL_BlendMode thisblend = cmd->data.draw.blend; + + for (nextcmd = cmd->next; nextcmd; nextcmd = nextcmd->next) { + const SDL_RenderCommandType nextcmdtype = nextcmd->command; + if (nextcmdtype != SDL_RENDERCMD_DRAW_LINES) { + if (nextcmdtype == SDL_RENDERCMD_SETDRAWCOLOR) { + // The vertex data has the draw color built in, ignore this + continue; + } + break; // can't go any further on this draw call, different render command up next. + } else if (nextcmd->data.draw.count != 2) { + break; // can't go any further on this draw call, those are joined lines + } else if (nextcmd->data.draw.blend != thisblend) { + break; // can't go any further on this draw call, different blendmode copy up next. + } else { + finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command. + + // Add the final point in the line + line_start = count; + line_end = line_start + nextcmd->data.draw.count - 1; + if (verts[line_start].x != verts[line_end].x || verts[line_start].y != verts[line_end].y) { + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)(start + line_end), 1); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, 1, &verts[line_end], sizeof(Vertex)); + } + } + count += nextcmd->data.draw.count; + } } + + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_LINELIST, (UINT)start, (UINT)(count - 1)); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_LINELIST, (UINT)(count - 1), verts, sizeof(Vertex)); + } + cmd = finalcmd; // skip any copy commands we just combined in here. } break; } @@ -1317,17 +1459,59 @@ static bool D3D_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, case SDL_RENDERCMD_COPY_EX: // unused break; + case SDL_RENDERCMD_DRAW_POINTS: case SDL_RENDERCMD_GEOMETRY: { - const size_t count = cmd->data.draw.count; + /* as long as we have the same copy command in a row, with the + 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_u = cmd->data.draw.texture_address_mode_u; + SDL_TextureAddressMode thisaddressmode_v = cmd->data.draw.texture_address_mode_v; + const SDL_RenderCommandType thiscmdtype = cmd->command; + SDL_RenderCommand *finalcmd = cmd; + SDL_RenderCommand *nextcmd; + size_t count = cmd->data.draw.count; const size_t first = cmd->data.draw.first; - SetDrawState(data, cmd); - if (vbo) { - IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_TRIANGLELIST, (UINT)(first / sizeof(Vertex)), (UINT)count / 3); - } else { - const Vertex *verts = (Vertex *)(((Uint8 *)vertices) + first); - IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_TRIANGLELIST, (UINT)count / 3, verts, sizeof(Vertex)); + const size_t start = first / sizeof(Vertex); + const Vertex *verts = (Vertex *)(((Uint8 *)vertices) + first); + for (nextcmd = cmd->next; nextcmd; nextcmd = nextcmd->next) { + const SDL_RenderCommandType nextcmdtype = nextcmd->command; + if (nextcmdtype != thiscmdtype) { + if (nextcmdtype == SDL_RENDERCMD_SETDRAWCOLOR) { + // The vertex data has the draw color built in, ignore this + continue; + } + 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.texture_scale_mode != thisscalemode || + nextcmd->data.draw.texture_address_mode_u != thisaddressmode_u || + nextcmd->data.draw.texture_address_mode_v != thisaddressmode_v || + 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. + count += nextcmd->data.draw.count; + } } + + SetDrawState(data, cmd); + + if (thiscmdtype == SDL_RENDERCMD_GEOMETRY) { + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_TRIANGLELIST, (UINT)start, (UINT)count / 3); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_TRIANGLELIST, (UINT)count / 3, verts, sizeof(Vertex)); + } + } else { + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)start, (UINT)count); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, (UINT)count, verts, sizeof(Vertex)); + } + } + cmd = finalcmd; // skip any copy commands we just combined in here. break; } @@ -1432,10 +1616,14 @@ static void D3D_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture) renderdata->drawstate.texture = NULL; renderdata->drawstate.shader = SHADER_NONE; renderdata->drawstate.shader_params = NULL; + renderdata->drawstate.texture_state_dirty = false; IDirect3DDevice9_SetPixelShader(renderdata->device, NULL); IDirect3DDevice9_SetTexture(renderdata->device, 0, NULL); + if (texture->palette) { + IDirect3DDevice9_SetTexture(renderdata->device, 1, NULL); + } #ifdef SDL_HAVE_YUV - if (data->yuv) { + if (data && data->yuv) { IDirect3DDevice9_SetTexture(renderdata->device, 1, NULL); IDirect3DDevice9_SetTexture(renderdata->device, 2, NULL); } @@ -1463,6 +1651,9 @@ static void D3D_DestroyRenderer(SDL_Renderer *renderer) if (data) { int i; + // Make sure the palettes have been freed + SDL_assert(!data->palettes); + // Release the render target if (data->defaultRenderTarget) { IDirect3DSurface9_Release(data->defaultRenderTarget); @@ -1472,14 +1663,12 @@ static void D3D_DestroyRenderer(SDL_Renderer *renderer) IDirect3DSurface9_Release(data->currentRenderTarget); data->currentRenderTarget = NULL; } -#ifdef SDL_HAVE_YUV for (i = 0; i < SDL_arraysize(data->shaders); ++i) { if (data->shaders[i]) { IDirect3DPixelShader9_Release(data->shaders[i]); data->shaders[i] = NULL; } } -#endif // Release all vertex buffers for (i = 0; i < SDL_arraysize(data->vertexBuffers); ++i) { if (data->vertexBuffers[i]) { @@ -1505,6 +1694,7 @@ static bool D3D_Reset(SDL_Renderer *renderer) const Float4X4 d3dmatrix = MatrixIdentity(); HRESULT result; SDL_Texture *texture; + D3D_PaletteData *palette; int i; // Cancel any scene that we've started @@ -1532,6 +1722,11 @@ static bool D3D_Reset(SDL_Renderer *renderer) } } + // Release all palettes + for (palette = data->palettes; palette; palette = palette->next) { + D3D_RecreateTextureRep(data->device, &palette->texture); + } + // Release all vertex buffers for (i = 0; i < SDL_arraysize(data->vertexBuffers); ++i) { if (data->vertexBuffers[i]) { @@ -1656,6 +1851,9 @@ static bool D3D_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P renderer->WindowEvent = D3D_WindowEvent; renderer->SupportsBlendMode = D3D_SupportsBlendMode; + renderer->CreatePalette = D3D_CreatePalette; + renderer->UpdatePalette = D3D_UpdatePalette; + renderer->DestroyPalette = D3D_DestroyPalette; renderer->CreateTexture = D3D_CreateTexture; renderer->UpdateTexture = D3D_UpdateTexture; #ifdef SDL_HAVE_YUV @@ -1720,6 +1918,13 @@ static bool D3D_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P } else { device_flags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING; } + if (caps.TextureCaps & D3DPTEXTURECAPS_POW2) { + if (caps.TextureCaps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL) { + renderer->npot_texture_wrap_unsupported = true; + } else { + return SDL_SetError("Non-power-of-two textures are not supported"); + } + } if (SDL_GetHintBoolean(SDL_HINT_RENDER_DIRECT3D_THREADSAFE, false)) { device_flags |= D3DCREATE_MULTITHREADED; @@ -1760,19 +1965,21 @@ static bool D3D_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P // Set up parameters for rendering D3D_InitRenderState(data); + for (int i = SHADER_NONE + 1; i < SDL_arraysize(data->shaders); ++i) { + result = D3D9_CreatePixelShader(data->device, (D3D9_Shader)i, &data->shaders[i]); + if (FAILED(result)) { + D3D_SetError("CreatePixelShader()", result); + } + } + if (caps.MaxSimultaneousTextures >= 2 && + data->shaders[SHADER_PALETTE_NEAREST] && + data->shaders[SHADER_PALETTE_LINEAR]) { + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_INDEX8); + } #ifdef SDL_HAVE_YUV - if (caps.MaxSimultaneousTextures >= 3) { - int i; - for (i = SHADER_NONE + 1; i < SDL_arraysize(data->shaders); ++i) { - result = D3D9_CreatePixelShader(data->device, (D3D9_Shader)i, &data->shaders[i]); - if (FAILED(result)) { - D3D_SetError("CreatePixelShader()", result); - } - } - if (data->shaders[SHADER_YUV]) { - SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_YV12); - SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_IYUV); - } + if (caps.MaxSimultaneousTextures >= 3 && data->shaders[SHADER_YUV]) { + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_YV12); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_IYUV); } #endif diff --git a/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.c b/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.c index 9b1b4b5..d2611ed 100644 --- a/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.c +++ b/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -30,12 +30,22 @@ // The shaders here were compiled with compile_shaders.bat +#define g_ps20_main D3D9_PixelShader_Palette_Nearest +#include "D3D9_PixelShader_Palette_Nearest.h" +#undef g_ps20_main + +#define g_ps20_main D3D9_PixelShader_Palette_Linear +#include "D3D9_PixelShader_Palette_Linear.h" +#undef g_ps20_main + #define g_ps20_main D3D9_PixelShader_YUV #include "D3D9_PixelShader_YUV.h" #undef g_ps20_main static const BYTE *D3D9_shaders[] = { NULL, + D3D9_PixelShader_Palette_Nearest, + D3D9_PixelShader_Palette_Linear, D3D9_PixelShader_YUV }; SDL_COMPILE_TIME_ASSERT(D3D9_shaders, SDL_arraysize(D3D9_shaders) == NUM_SHADERS); diff --git a/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.h b/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.h index 90ef7a9..c6fba5c 100644 --- a/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.h +++ b/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -25,6 +25,8 @@ typedef enum { SHADER_NONE, + SHADER_PALETTE_NEAREST, + SHADER_PALETTE_LINEAR, SHADER_YUV, NUM_SHADERS } D3D9_Shader; diff --git a/libs/SDL3/src/render/direct3d/compile_shaders.bat b/libs/SDL3/src/render/direct3d/compile_shaders.bat index 58a559d..39d856d 100644 --- a/libs/SDL3/src/render/direct3d/compile_shaders.bat +++ b/libs/SDL3/src/render/direct3d/compile_shaders.bat @@ -1 +1,3 @@ +fxc /T ps_2_0 /Fh D3D9_PixelShader_Palette_Nearest.h D3D9_PixelShader_Palette_Nearest.hlsl +fxc /T ps_2_0 /Fh D3D9_PixelShader_Palette_Linear.h D3D9_PixelShader_Palette_Linear.hlsl fxc /T ps_2_0 /Fh D3D9_PixelShader_YUV.h D3D9_PixelShader_YUV.hlsl diff --git a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Advanced.h b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Advanced.h index a1297c9..1697e57 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Advanced.h +++ b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Advanced.h @@ -3,7 +3,7 @@ // Generated by Microsoft (R) HLSL Shader Compiler 10.1 // // -// Buffer Definitions: +// Buffer Definitions: // // cbuffer Constants // { @@ -12,14 +12,15 @@ // float texture_type; // Offset: 4 Size: 4 // float input_type; // Offset: 8 Size: 4 // float color_scale; // Offset: 12 Size: 4 -// float tonemap_method; // Offset: 16 Size: 4 -// float tonemap_factor1; // Offset: 20 Size: 4 -// float tonemap_factor2; // Offset: 24 Size: 4 -// float sdr_white_point; // Offset: 28 Size: 4 -// float4 Yoffset; // Offset: 32 Size: 16 -// float4 Rcoeff; // Offset: 48 Size: 16 -// float4 Gcoeff; // Offset: 64 Size: 16 -// float4 Bcoeff; // Offset: 80 Size: 16 +// float4 texel_size; // Offset: 16 Size: 16 +// float tonemap_method; // Offset: 32 Size: 4 +// float tonemap_factor1; // Offset: 36 Size: 4 +// float tonemap_factor2; // Offset: 40 Size: 4 +// float sdr_white_point; // Offset: 44 Size: 4 +// float4 Yoffset; // Offset: 48 Size: 16 +// float4 Rcoeff; // Offset: 64 Size: 16 +// float4 Gcoeff; // Offset: 80 Size: 16 +// float4 Bcoeff; // Offset: 96 Size: 16 // // } // @@ -28,11 +29,12 @@ // // Name Type Format Dim HLSL Bind Count // ------------------------------ ---------- ------- ----------- -------------- ------ -// sampler0 sampler NA NA s0 1 -// texture0 texture float4 2d t0 1 -// texture1 texture float4 2d t1 1 -// texture2 texture float4 2d t2 1 -// Constants cbuffer NA NA cb0 1 +// sampler0 sampler NA NA s0 1 +// sampler1 sampler NA NA s1 1 +// texture0 texture float4 2d t0 1 +// texture1 texture float4 2d t1 1 +// texture2 texture float4 2d t2 1 +// Constants cbuffer NA NA cb0 1 // // // @@ -40,8 +42,8 @@ // // Name Index Mask Register SysValue Format Used // -------------------- ----- ------ -------- -------- ------- ------ -// SV_POSITION 0 xyzw 0 POS float -// TEXCOORD 0 xy 1 NONE float xy +// SV_POSITION 0 xyzw 0 POS float +// TEXCOORD 0 xy 1 NONE float xy // COLOR 0 xyzw 2 NONE float xyzw // // @@ -53,58 +55,179 @@ // ps_5_0 dcl_globalFlags refactoringAllowed -dcl_constantbuffer CB0[6], immediateIndexed +dcl_constantbuffer CB0[7], immediateIndexed dcl_sampler s0, mode_default +dcl_sampler s1, mode_default dcl_resource_texture2d (float,float,float,float) t0 dcl_resource_texture2d (float,float,float,float) t1 dcl_resource_texture2d (float,float,float,float) t2 dcl_input_ps linear v1.xy dcl_input_ps linear v2.xyzw dcl_output o0.xyzw -dcl_temps 7 +dcl_temps 8 eq r0.xyzw, cb0[0].yzzz, l(0.000000, 3.000000, 2.000000, 1.000000) if_nz r0.x mov r1.xyzw, l(1.000000,1.000000,1.000000,1.000000) -else +else eq r0.x, cb0[0].y, l(1.000000) if_nz r0.x sample_indexable(texture2d)(float,float,float,float) r1.xyzw, v1.xyxx, t0.xyzw, s0 - else + else eq r0.x, cb0[0].y, l(2.000000) if_nz r0.x - sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 - sample_indexable(texture2d)(float,float,float,float) r2.yz, v1.xyxx, t1.zxyw, s0 - add r2.xyz, r2.xyzx, cb0[2].xyzx - dp3 r1.x, r2.xyzx, cb0[3].xyzx - dp3 r1.y, r2.xyzx, cb0[4].xyzx - dp3 r1.z, r2.xyzx, cb0[5].xyzx - else + deriv_rtx_coarse r2.xy, v1.xyxx + deriv_rty_coarse r2.zw, v1.xxxy + add r3.xy, |r2.zwzz|, |r2.xyxx| + mul r3.xy, r3.xyxx, cb0[1].zwzz + max r3.xy, r3.xyxx, l(0.000010, 0.000010, 0.000000, 0.000000) + min r3.xy, r3.xyxx, l(1.000000, 1.000000, 0.000000, 0.000000) + mul r3.zw, r3.xxxy, l(0.000000, 0.000000, 0.500000, 0.500000) + mad r3.zw, v1.xxxy, cb0[1].zzzw, -r3.zzzw + add r3.xy, -r3.xyxx, l(1.000000, 1.000000, 0.000000, 0.000000) + frc r4.xy, r3.zwzz + add r4.zw, -r3.xxxy, l(0.000000, 0.000000, 1.000000, 1.000000) + add r3.xy, -r3.xyxx, r4.xyxx + div r4.xy, l(1.000000, 1.000000, 1.000000, 1.000000), r4.zwzz + mul_sat r3.xy, r3.xyxx, r4.xyxx + mad r4.xy, r3.xyxx, l(-2.000000, -2.000000, 0.000000, 0.000000), l(3.000000, 3.000000, 0.000000, 0.000000) + mul r3.xy, r3.xyxx, r3.xyxx + round_ni r3.zw, r3.zzzw + mad r3.xy, r4.xyxx, r3.xyxx, r3.zwzz + add r3.xy, r3.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) + mul r3.xy, r3.xyxx, cb0[1].xyxx + sample_d_indexable(texture2d)(float,float,float,float) r1.xyzw, r3.xyxx, t0.xyzw, s0, r2.xyxx, r2.zwzz + else eq r0.x, cb0[0].y, l(3.000000) if_nz r0.x - sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 - sample_indexable(texture2d)(float,float,float,float) r2.yz, v1.xyxx, t1.zyxw, s0 - add r2.xyz, r2.xyzx, cb0[2].xyzx - dp3 r1.x, r2.xyzx, cb0[3].xyzx - dp3 r1.y, r2.xyzx, cb0[4].xyzx - dp3 r1.z, r2.xyzx, cb0[5].xyzx - else + sample_indexable(texture2d)(float,float,float,float) r0.x, v1.xyxx, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r2.x, r0.x, l(0.003906) + mov r2.y, l(0.500000) + sample_indexable(texture2d)(float,float,float,float) r1.xyzw, r2.xyxx, t1.xyzw, s1 + else eq r0.x, cb0[0].y, l(4.000000) if_nz r0.x - sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 - sample_indexable(texture2d)(float,float,float,float) r2.y, v1.xyxx, t1.yxzw, s0 - sample_indexable(texture2d)(float,float,float,float) r2.z, v1.xyxx, t2.yzxw, s0 - add r2.xyz, r2.xyzx, cb0[2].xyzx - dp3 r1.x, r2.xyzx, cb0[3].xyzx - dp3 r1.y, r2.xyzx, cb0[4].xyzx - dp3 r1.z, r2.xyzx, cb0[5].xyzx - else - mov r1.xyz, l(1.000000,0,0,0) - endif - endif - endif - mov r1.w, l(1.000000) - endif -endif + mad r2.xy, v1.xyxx, cb0[1].zwzz, l(0.500000, 0.500000, 0.000000, 0.000000) + round_ni r3.xyzw, r2.xyxy + add r3.xyzw, r3.xyzw, l(-0.500000, -0.500000, 0.500000, 0.500000) + mul r3.xyzw, r3.xyzw, cb0[1].xyxy + frc r2.xy, r2.xyxx + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.xyxx, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r4.x, r0.x, l(0.003906) + mov r4.yw, l(0,0.500000,0,0.500000) + sample_indexable(texture2d)(float,float,float,float) r5.xyzw, r4.xyxx, t1.xyzw, s1 + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.xwxx, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r4.z, r0.x, l(0.003906) + sample_indexable(texture2d)(float,float,float,float) r4.xyzw, r4.zwzz, t1.xyzw, s1 + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.zyzz, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r6.x, r0.x, l(0.003906) + mov r6.yw, l(0,0.500000,0,0.500000) + sample_indexable(texture2d)(float,float,float,float) r7.xyzw, r6.xyxx, t1.xyzw, s1 + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.zwzz, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r6.z, r0.x, l(0.003906) + sample_indexable(texture2d)(float,float,float,float) r3.xyzw, r6.zwzz, t1.xyzw, s1 + add r4.xyzw, -r5.xyzw, r4.xyzw + mad r4.xyzw, r2.yyyy, r4.xyzw, r5.xyzw + add r3.xyzw, -r7.xyzw, r3.xyzw + mad r3.xyzw, r2.yyyy, r3.xyzw, r7.xyzw + add r3.xyzw, -r4.xyzw, r3.xyzw + mad r1.xyzw, r2.xxxx, r3.xyzw, r4.xyzw + else + eq r0.x, cb0[0].y, l(5.000000) + if_nz r0.x + deriv_rtx_coarse r2.xy, v1.xyxx + deriv_rty_coarse r2.zw, v1.xxxy + add r2.xy, |r2.zwzz|, |r2.xyxx| + mul r2.xy, r2.xyxx, cb0[1].zwzz + max r2.xy, r2.xyxx, l(0.000010, 0.000010, 0.000000, 0.000000) + min r2.xy, r2.xyxx, l(1.000000, 1.000000, 0.000000, 0.000000) + mul r2.zw, r2.xxxy, l(0.000000, 0.000000, 0.500000, 0.500000) + mad r2.zw, v1.xxxy, cb0[1].zzzw, -r2.zzzw + add r2.xy, -r2.xyxx, l(1.000000, 1.000000, 0.000000, 0.000000) + frc r3.xy, r2.zwzz + add r3.zw, -r2.xxxy, l(0.000000, 0.000000, 1.000000, 1.000000) + add r2.xy, -r2.xyxx, r3.xyxx + div r3.xy, l(1.000000, 1.000000, 1.000000, 1.000000), r3.zwzz + mul_sat r2.xy, r2.xyxx, r3.xyxx + mad r3.xy, r2.xyxx, l(-2.000000, -2.000000, 0.000000, 0.000000), l(3.000000, 3.000000, 0.000000, 0.000000) + mul r2.xy, r2.xyxx, r2.xyxx + round_ni r2.zw, r2.zzzw + mad r2.xy, r3.xyxx, r2.xyxx, r2.zwzz + add r2.xy, r2.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) + mul r2.xy, r2.xyxx, cb0[1].xyxx + mad r2.xy, r2.xyxx, cb0[1].zwzz, l(0.500000, 0.500000, 0.000000, 0.000000) + round_ni r3.xyzw, r2.xyxy + add r3.xyzw, r3.xyzw, l(-0.500000, -0.500000, 0.500000, 0.500000) + mul r3.xyzw, r3.xyzw, cb0[1].xyxy + frc r2.xy, r2.xyxx + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.xyxx, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r4.x, r0.x, l(0.003906) + mov r4.yw, l(0,0.500000,0,0.500000) + sample_indexable(texture2d)(float,float,float,float) r5.xyzw, r4.xyxx, t1.xyzw, s1 + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.xwxx, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r4.z, r0.x, l(0.003906) + sample_indexable(texture2d)(float,float,float,float) r4.xyzw, r4.zwzz, t1.xyzw, s1 + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.zyzz, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r6.x, r0.x, l(0.003906) + mov r6.yw, l(0,0.500000,0,0.500000) + sample_indexable(texture2d)(float,float,float,float) r7.xyzw, r6.xyxx, t1.xyzw, s1 + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.zwzz, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r6.z, r0.x, l(0.003906) + sample_indexable(texture2d)(float,float,float,float) r3.xyzw, r6.zwzz, t1.xyzw, s1 + add r4.xyzw, -r5.xyzw, r4.xyzw + mad r4.xyzw, r2.yyyy, r4.xyzw, r5.xyzw + add r3.xyzw, -r7.xyzw, r3.xyzw + mad r3.xyzw, r2.yyyy, r3.xyzw, r7.xyzw + add r3.xyzw, -r4.xyzw, r3.xyzw + mad r1.xyzw, r2.xxxx, r3.xyzw, r4.xyzw + else + eq r0.x, cb0[0].y, l(6.000000) + if_nz r0.x + sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 + sample_indexable(texture2d)(float,float,float,float) r2.yz, v1.xyxx, t1.zxyw, s0 + add r2.xyz, r2.xyzx, cb0[3].xyzx + dp3 r1.x, r2.xyzx, cb0[4].xyzx + dp3 r1.y, r2.xyzx, cb0[5].xyzx + dp3 r1.z, r2.xyzx, cb0[6].xyzx + else + eq r0.x, cb0[0].y, l(7.000000) + if_nz r0.x + sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 + sample_indexable(texture2d)(float,float,float,float) r2.yz, v1.xyxx, t1.zyxw, s0 + add r2.xyz, r2.xyzx, cb0[3].xyzx + dp3 r1.x, r2.xyzx, cb0[4].xyzx + dp3 r1.y, r2.xyzx, cb0[5].xyzx + dp3 r1.z, r2.xyzx, cb0[6].xyzx + else + eq r0.x, cb0[0].y, l(8.000000) + if_nz r0.x + sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 + sample_indexable(texture2d)(float,float,float,float) r2.y, v1.xyxx, t1.yxzw, s0 + sample_indexable(texture2d)(float,float,float,float) r2.z, v1.xyxx, t2.yzxw, s0 + add r2.xyz, r2.xyzx, cb0[3].xyzx + dp3 r1.x, r2.xyzx, cb0[4].xyzx + dp3 r1.y, r2.xyzx, cb0[5].xyzx + dp3 r1.z, r2.xyzx, cb0[6].xyzx + else + mov r1.xyz, l(1.000000,0,1.000000,0) + endif + endif + endif + mov r1.w, l(1.000000) + endif + endif + endif + endif + endif +endif log r2.xyz, |r1.xyzx| mul r2.xyz, r2.xyzx, l(0.012683, 0.012683, 0.012683, 0.000000) exp r2.xyz, r2.xyzx @@ -116,11 +239,11 @@ log r2.xyz, |r2.xyzx| mul r2.xyz, r2.xyzx, l(6.277395, 6.277395, 6.277395, 0.000000) exp r2.xyz, r2.xyzx mul r2.xyz, r2.xyzx, l(10000.000000, 10000.000000, 10000.000000, 0.000000) -div r2.xyz, r2.xyzx, cb0[1].wwww +div r2.xyz, r2.xyzx, cb0[2].wwww movc r2.xyz, r0.yyyy, r2.xyzx, r1.xyzx -ne r0.x, cb0[1].x, l(0.000000) -mul r3.xyz, r2.xyzx, cb0[1].yyyy -eq r4.xy, cb0[1].xxxx, l(1.000000, 2.000000, 0.000000, 0.000000) +ne r0.x, cb0[2].x, l(0.000000) +mul r3.xyz, r2.xyzx, cb0[2].yyyy +eq r4.xy, cb0[2].xxxx, l(1.000000, 2.000000, 0.000000, 0.000000) dp3 r5.x, l(0.627404, 0.329283, 0.043313, 0.000000), r2.xyzx dp3 r5.y, l(0.069097, 0.919541, 0.011362, 0.000000), r2.xyzx dp3 r5.z, l(0.016391, 0.088013, 0.895595, 0.000000), r2.xyzx @@ -128,7 +251,7 @@ movc r5.xyz, r0.zzzz, r5.xyzx, r2.xyzx max r2.w, r5.z, r5.y max r2.w, r2.w, r5.x lt r3.w, l(0.000000), r2.w -mad r4.zw, cb0[1].yyyz, r2.wwww, l(0.000000, 0.000000, 1.000000, 1.000000) +mad r4.zw, cb0[2].yyyz, r2.wwww, l(0.000000, 0.000000, 1.000000, 1.000000) div r2.w, r4.z, r4.w mul r6.xyz, r2.wwww, r5.xyzx movc r5.xyz, r3.wwww, r6.xyzx, r5.xyzx @@ -150,9 +273,9 @@ if_nz r0.w mul r5.xyz, r5.xyzx, l(2.400000, 2.400000, 2.400000, 0.000000) exp r5.xyz, r5.xyzx movc r2.xyz, r3.xyzx, r4.xyzx, r5.xyzx - endif + endif mul r1.xyz, r2.xyzx, cb0[0].wwww -else +else if_nz r0.z mul r1.xyz, r2.xyzx, cb0[0].wwww ne r0.x, l(0.000000, 0.000000, 0.000000, 0.000000), cb0[0].x @@ -164,8 +287,8 @@ else exp r4.xyz, r4.xyzx mad r4.xyz, r4.xyzx, l(1.055000, 1.055000, 1.055000, 0.000000), l(-0.055000, -0.055000, -0.055000, 0.000000) movc_sat r1.xyz, r0.xzwx, r3.xyzx, r4.xyzx - endif - else + endif + else if_nz r0.y dp3 r0.x, l(1.660496, -0.587656, -0.072840, 0.000000), r2.xyzx dp3 r0.y, l(-0.124547, 1.132895, -0.008348, 0.000000), r2.xyzx @@ -180,881 +303,1550 @@ else exp r4.xyz, r4.xyzx mad r4.xyz, r4.xyzx, l(1.055000, 1.055000, 1.055000, 0.000000), l(-0.055000, -0.055000, -0.055000, 0.000000) movc_sat r1.xyz, r0.xyzx, r3.xyzx, r4.xyzx - endif - else + endif + else mul r1.xyz, r2.xyzx, cb0[0].wwww - endif - endif -endif + endif + endif +endif mul o0.xyzw, r1.xyzw, v2.xyzw -ret -// Approximately 126 instruction slots used +ret +// Approximately 246 instruction slots used #endif const BYTE g_main[] = { - 68, 88, 66, 67, 154, 200, - 28, 25, 30, 140, 0, 183, - 251, 32, 6, 185, 210, 29, - 251, 110, 1, 0, 0, 0, - 64, 20, 0, 0, 5, 0, - 0, 0, 52, 0, 0, 0, - 80, 4, 0, 0, 196, 4, - 0, 0, 248, 4, 0, 0, - 164, 19, 0, 0, 82, 68, - 69, 70, 20, 4, 0, 0, - 1, 0, 0, 0, 12, 1, - 0, 0, 5, 0, 0, 0, - 60, 0, 0, 0, 0, 5, - 255, 255, 0, 1, 0, 0, - 233, 3, 0, 0, 82, 68, - 49, 49, 60, 0, 0, 0, - 24, 0, 0, 0, 32, 0, - 0, 0, 40, 0, 0, 0, - 36, 0, 0, 0, 12, 0, - 0, 0, 0, 0, 0, 0, - 220, 0, 0, 0, 3, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 1, 0, - 0, 0, 229, 0, 0, 0, - 2, 0, 0, 0, 5, 0, - 0, 0, 4, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 1, 0, 0, 0, - 13, 0, 0, 0, 238, 0, - 0, 0, 2, 0, 0, 0, - 5, 0, 0, 0, 4, 0, - 0, 0, 255, 255, 255, 255, - 1, 0, 0, 0, 1, 0, - 0, 0, 13, 0, 0, 0, - 247, 0, 0, 0, 2, 0, - 0, 0, 5, 0, 0, 0, - 4, 0, 0, 0, 255, 255, - 255, 255, 2, 0, 0, 0, - 1, 0, 0, 0, 13, 0, - 0, 0, 0, 1, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 115, 97, - 109, 112, 108, 101, 114, 48, - 0, 116, 101, 120, 116, 117, - 114, 101, 48, 0, 116, 101, - 120, 116, 117, 114, 101, 49, - 0, 116, 101, 120, 116, 117, - 114, 101, 50, 0, 67, 111, - 110, 115, 116, 97, 110, 116, - 115, 0, 171, 171, 0, 1, - 0, 0, 12, 0, 0, 0, - 36, 1, 0, 0, 96, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 4, 3, - 0, 0, 0, 0, 0, 0, - 4, 0, 0, 0, 2, 0, - 0, 0, 24, 3, 0, 0, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 60, 3, 0, 0, - 4, 0, 0, 0, 4, 0, - 0, 0, 2, 0, 0, 0, - 24, 3, 0, 0, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 73, 3, 0, 0, 8, 0, - 0, 0, 4, 0, 0, 0, - 2, 0, 0, 0, 24, 3, - 0, 0, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 84, 3, - 0, 0, 12, 0, 0, 0, - 4, 0, 0, 0, 2, 0, - 0, 0, 24, 3, 0, 0, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 96, 3, 0, 0, - 16, 0, 0, 0, 4, 0, - 0, 0, 2, 0, 0, 0, - 24, 3, 0, 0, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 111, 3, 0, 0, 20, 0, - 0, 0, 4, 0, 0, 0, - 2, 0, 0, 0, 24, 3, - 0, 0, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 127, 3, - 0, 0, 24, 0, 0, 0, - 4, 0, 0, 0, 2, 0, - 0, 0, 24, 3, 0, 0, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 143, 3, 0, 0, - 28, 0, 0, 0, 4, 0, - 0, 0, 2, 0, 0, 0, - 24, 3, 0, 0, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 159, 3, 0, 0, 32, 0, - 0, 0, 16, 0, 0, 0, - 2, 0, 0, 0, 176, 3, - 0, 0, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 212, 3, - 0, 0, 48, 0, 0, 0, - 16, 0, 0, 0, 2, 0, - 0, 0, 176, 3, 0, 0, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 219, 3, 0, 0, - 64, 0, 0, 0, 16, 0, - 0, 0, 2, 0, 0, 0, - 176, 3, 0, 0, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 226, 3, 0, 0, 80, 0, - 0, 0, 16, 0, 0, 0, - 2, 0, 0, 0, 176, 3, - 0, 0, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 115, 99, - 82, 71, 66, 95, 111, 117, - 116, 112, 117, 116, 0, 102, - 108, 111, 97, 116, 0, 171, - 0, 0, 3, 0, 1, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 17, 3, 0, 0, - 116, 101, 120, 116, 117, 114, - 101, 95, 116, 121, 112, 101, - 0, 105, 110, 112, 117, 116, - 95, 116, 121, 112, 101, 0, - 99, 111, 108, 111, 114, 95, - 115, 99, 97, 108, 101, 0, - 116, 111, 110, 101, 109, 97, - 112, 95, 109, 101, 116, 104, - 111, 100, 0, 116, 111, 110, - 101, 109, 97, 112, 95, 102, - 97, 99, 116, 111, 114, 49, - 0, 116, 111, 110, 101, 109, - 97, 112, 95, 102, 97, 99, - 116, 111, 114, 50, 0, 115, - 100, 114, 95, 119, 104, 105, - 116, 101, 95, 112, 111, 105, - 110, 116, 0, 89, 111, 102, - 102, 115, 101, 116, 0, 102, - 108, 111, 97, 116, 52, 0, - 171, 171, 1, 0, 3, 0, - 1, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 167, 3, - 0, 0, 82, 99, 111, 101, - 102, 102, 0, 71, 99, 111, - 101, 102, 102, 0, 66, 99, - 111, 101, 102, 102, 0, 77, - 105, 99, 114, 111, 115, 111, - 102, 116, 32, 40, 82, 41, - 32, 72, 76, 83, 76, 32, - 83, 104, 97, 100, 101, 114, - 32, 67, 111, 109, 112, 105, - 108, 101, 114, 32, 49, 48, - 46, 49, 0, 171, 171, 171, - 73, 83, 71, 78, 108, 0, - 0, 0, 3, 0, 0, 0, - 8, 0, 0, 0, 80, 0, - 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 3, 0, - 0, 0, 0, 0, 0, 0, - 15, 0, 0, 0, 92, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 3, 0, - 0, 0, 1, 0, 0, 0, - 3, 3, 0, 0, 101, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 3, 0, - 0, 0, 2, 0, 0, 0, - 15, 15, 0, 0, 83, 86, - 95, 80, 79, 83, 73, 84, - 73, 79, 78, 0, 84, 69, - 88, 67, 79, 79, 82, 68, - 0, 67, 79, 76, 79, 82, - 0, 171, 79, 83, 71, 78, - 44, 0, 0, 0, 1, 0, - 0, 0, 8, 0, 0, 0, - 32, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 0, 0, - 0, 0, 15, 0, 0, 0, - 83, 86, 95, 84, 65, 82, - 71, 69, 84, 0, 171, 171, - 83, 72, 69, 88, 164, 14, - 0, 0, 80, 0, 0, 0, - 169, 3, 0, 0, 106, 8, - 0, 1, 89, 0, 0, 4, - 70, 142, 32, 0, 0, 0, - 0, 0, 6, 0, 0, 0, - 90, 0, 0, 3, 0, 96, - 16, 0, 0, 0, 0, 0, - 88, 24, 0, 4, 0, 112, - 16, 0, 0, 0, 0, 0, - 85, 85, 0, 0, 88, 24, - 0, 4, 0, 112, 16, 0, - 1, 0, 0, 0, 85, 85, - 0, 0, 88, 24, 0, 4, - 0, 112, 16, 0, 2, 0, - 0, 0, 85, 85, 0, 0, - 98, 16, 0, 3, 50, 16, - 16, 0, 1, 0, 0, 0, - 98, 16, 0, 3, 242, 16, - 16, 0, 2, 0, 0, 0, - 101, 0, 0, 3, 242, 32, - 16, 0, 0, 0, 0, 0, - 104, 0, 0, 2, 7, 0, - 0, 0, 24, 0, 0, 11, - 242, 0, 16, 0, 0, 0, - 0, 0, 150, 138, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 2, 64, 0, 0, - 0, 0, 0, 0, 0, 0, - 64, 64, 0, 0, 0, 64, - 0, 0, 128, 63, 31, 0, - 4, 3, 10, 0, 16, 0, - 0, 0, 0, 0, 54, 0, - 0, 8, 242, 0, 16, 0, - 1, 0, 0, 0, 2, 64, - 0, 0, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, - 128, 63, 0, 0, 128, 63, - 18, 0, 0, 1, 24, 0, - 0, 8, 18, 0, 16, 0, - 0, 0, 0, 0, 26, 128, - 32, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 64, - 0, 0, 0, 0, 128, 63, - 31, 0, 4, 3, 10, 0, - 16, 0, 0, 0, 0, 0, - 69, 0, 0, 139, 194, 0, - 0, 128, 67, 85, 21, 0, - 242, 0, 16, 0, 1, 0, - 0, 0, 70, 16, 16, 0, - 1, 0, 0, 0, 70, 126, - 16, 0, 0, 0, 0, 0, - 0, 96, 16, 0, 0, 0, - 0, 0, 18, 0, 0, 1, - 24, 0, 0, 8, 18, 0, - 16, 0, 0, 0, 0, 0, - 26, 128, 32, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 64, 0, 0, 0, 0, - 0, 64, 31, 0, 4, 3, - 10, 0, 16, 0, 0, 0, - 0, 0, 69, 0, 0, 139, - 194, 0, 0, 128, 67, 85, - 21, 0, 18, 0, 16, 0, - 2, 0, 0, 0, 70, 16, - 16, 0, 1, 0, 0, 0, - 70, 126, 16, 0, 0, 0, - 0, 0, 0, 96, 16, 0, - 0, 0, 0, 0, 69, 0, - 0, 139, 194, 0, 0, 128, - 67, 85, 21, 0, 98, 0, - 16, 0, 2, 0, 0, 0, - 70, 16, 16, 0, 1, 0, - 0, 0, 38, 125, 16, 0, - 1, 0, 0, 0, 0, 96, - 16, 0, 0, 0, 0, 0, - 0, 0, 0, 8, 114, 0, - 16, 0, 2, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 70, 130, 32, 0, - 0, 0, 0, 0, 2, 0, - 0, 0, 16, 0, 0, 8, - 18, 0, 16, 0, 1, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 70, 130, - 32, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 16, 0, - 0, 8, 34, 0, 16, 0, - 1, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 70, 130, 32, 0, 0, 0, - 0, 0, 4, 0, 0, 0, - 16, 0, 0, 8, 66, 0, - 16, 0, 1, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 70, 130, 32, 0, - 0, 0, 0, 0, 5, 0, - 0, 0, 18, 0, 0, 1, - 24, 0, 0, 8, 18, 0, - 16, 0, 0, 0, 0, 0, - 26, 128, 32, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 64, 0, 0, 0, 0, - 64, 64, 31, 0, 4, 3, - 10, 0, 16, 0, 0, 0, - 0, 0, 69, 0, 0, 139, - 194, 0, 0, 128, 67, 85, - 21, 0, 18, 0, 16, 0, - 2, 0, 0, 0, 70, 16, - 16, 0, 1, 0, 0, 0, - 70, 126, 16, 0, 0, 0, - 0, 0, 0, 96, 16, 0, - 0, 0, 0, 0, 69, 0, - 0, 139, 194, 0, 0, 128, - 67, 85, 21, 0, 98, 0, - 16, 0, 2, 0, 0, 0, - 70, 16, 16, 0, 1, 0, - 0, 0, 102, 124, 16, 0, - 1, 0, 0, 0, 0, 96, - 16, 0, 0, 0, 0, 0, - 0, 0, 0, 8, 114, 0, - 16, 0, 2, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 70, 130, 32, 0, - 0, 0, 0, 0, 2, 0, - 0, 0, 16, 0, 0, 8, - 18, 0, 16, 0, 1, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 70, 130, - 32, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 16, 0, - 0, 8, 34, 0, 16, 0, - 1, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 70, 130, 32, 0, 0, 0, - 0, 0, 4, 0, 0, 0, - 16, 0, 0, 8, 66, 0, - 16, 0, 1, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 70, 130, 32, 0, - 0, 0, 0, 0, 5, 0, - 0, 0, 18, 0, 0, 1, - 24, 0, 0, 8, 18, 0, - 16, 0, 0, 0, 0, 0, - 26, 128, 32, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 64, 0, 0, 0, 0, - 128, 64, 31, 0, 4, 3, - 10, 0, 16, 0, 0, 0, - 0, 0, 69, 0, 0, 139, - 194, 0, 0, 128, 67, 85, - 21, 0, 18, 0, 16, 0, - 2, 0, 0, 0, 70, 16, - 16, 0, 1, 0, 0, 0, - 70, 126, 16, 0, 0, 0, - 0, 0, 0, 96, 16, 0, - 0, 0, 0, 0, 69, 0, - 0, 139, 194, 0, 0, 128, - 67, 85, 21, 0, 34, 0, - 16, 0, 2, 0, 0, 0, - 70, 16, 16, 0, 1, 0, - 0, 0, 22, 126, 16, 0, - 1, 0, 0, 0, 0, 96, - 16, 0, 0, 0, 0, 0, - 69, 0, 0, 139, 194, 0, - 0, 128, 67, 85, 21, 0, - 66, 0, 16, 0, 2, 0, - 0, 0, 70, 16, 16, 0, - 1, 0, 0, 0, 150, 124, - 16, 0, 2, 0, 0, 0, - 0, 96, 16, 0, 0, 0, - 0, 0, 0, 0, 0, 8, - 114, 0, 16, 0, 2, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 70, 130, - 32, 0, 0, 0, 0, 0, - 2, 0, 0, 0, 16, 0, - 0, 8, 18, 0, 16, 0, - 1, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 70, 130, 32, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 16, 0, 0, 8, 34, 0, - 16, 0, 1, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 70, 130, 32, 0, - 0, 0, 0, 0, 4, 0, - 0, 0, 16, 0, 0, 8, - 66, 0, 16, 0, 1, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 70, 130, - 32, 0, 0, 0, 0, 0, - 5, 0, 0, 0, 18, 0, - 0, 1, 54, 0, 0, 8, - 114, 0, 16, 0, 1, 0, - 0, 0, 2, 64, 0, 0, - 0, 0, 128, 63, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 21, 0, - 0, 1, 21, 0, 0, 1, - 21, 0, 0, 1, 54, 0, - 0, 5, 130, 0, 16, 0, - 1, 0, 0, 0, 1, 64, - 0, 0, 0, 0, 128, 63, - 21, 0, 0, 1, 21, 0, - 0, 1, 47, 0, 0, 6, - 114, 0, 16, 0, 2, 0, - 0, 0, 70, 2, 16, 128, - 129, 0, 0, 0, 1, 0, - 0, 0, 56, 0, 0, 10, - 114, 0, 16, 0, 2, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 2, 64, - 0, 0, 172, 205, 79, 60, - 172, 205, 79, 60, 172, 205, - 79, 60, 0, 0, 0, 0, - 25, 0, 0, 5, 114, 0, - 16, 0, 2, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 0, 0, 0, 10, - 114, 0, 16, 0, 3, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 2, 64, - 0, 0, 0, 0, 86, 191, - 0, 0, 86, 191, 0, 0, - 86, 191, 0, 0, 0, 0, - 52, 0, 0, 10, 114, 0, - 16, 0, 3, 0, 0, 0, - 70, 2, 16, 0, 3, 0, - 0, 0, 2, 64, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 50, 0, - 0, 16, 114, 0, 16, 0, - 2, 0, 0, 0, 70, 2, - 16, 128, 65, 0, 0, 0, - 2, 0, 0, 0, 2, 64, - 0, 0, 0, 128, 149, 65, - 0, 128, 149, 65, 0, 128, - 149, 65, 0, 0, 0, 0, - 2, 64, 0, 0, 0, 208, - 150, 65, 0, 208, 150, 65, - 0, 208, 150, 65, 0, 0, - 0, 0, 14, 0, 0, 7, - 114, 0, 16, 0, 2, 0, - 0, 0, 70, 2, 16, 0, - 3, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 47, 0, 0, 6, 114, 0, - 16, 0, 2, 0, 0, 0, - 70, 2, 16, 128, 129, 0, - 0, 0, 2, 0, 0, 0, - 56, 0, 0, 10, 114, 0, - 16, 0, 2, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 2, 64, 0, 0, - 107, 224, 200, 64, 107, 224, - 200, 64, 107, 224, 200, 64, - 0, 0, 0, 0, 25, 0, - 0, 5, 114, 0, 16, 0, - 2, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 56, 0, 0, 10, 114, 0, - 16, 0, 2, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 2, 64, 0, 0, - 0, 64, 28, 70, 0, 64, - 28, 70, 0, 64, 28, 70, - 0, 0, 0, 0, 14, 0, - 0, 8, 114, 0, 16, 0, - 2, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 246, 143, 32, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 55, 0, 0, 9, 114, 0, - 16, 0, 2, 0, 0, 0, - 86, 5, 16, 0, 0, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 70, 2, - 16, 0, 1, 0, 0, 0, - 57, 0, 0, 8, 18, 0, - 16, 0, 0, 0, 0, 0, - 10, 128, 32, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 1, 64, 0, 0, 0, 0, - 0, 0, 56, 0, 0, 8, - 114, 0, 16, 0, 3, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 86, 133, - 32, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 24, 0, - 0, 11, 50, 0, 16, 0, - 4, 0, 0, 0, 6, 128, - 32, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 2, 64, - 0, 0, 0, 0, 128, 63, - 0, 0, 0, 64, 0, 0, - 0, 0, 0, 0, 0, 0, - 16, 0, 0, 10, 18, 0, - 16, 0, 5, 0, 0, 0, - 2, 64, 0, 0, 140, 157, - 32, 63, 200, 151, 168, 62, - 249, 104, 49, 61, 0, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 16, 0, - 0, 10, 34, 0, 16, 0, - 5, 0, 0, 0, 2, 64, - 0, 0, 186, 130, 141, 61, - 10, 103, 107, 63, 175, 39, - 58, 60, 0, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 16, 0, 0, 10, - 66, 0, 16, 0, 5, 0, - 0, 0, 2, 64, 0, 0, - 107, 70, 134, 60, 41, 64, - 180, 61, 183, 69, 101, 63, - 0, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 55, 0, 0, 9, 114, 0, - 16, 0, 5, 0, 0, 0, - 166, 10, 16, 0, 0, 0, - 0, 0, 70, 2, 16, 0, - 5, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 52, 0, 0, 7, 130, 0, - 16, 0, 2, 0, 0, 0, - 42, 0, 16, 0, 5, 0, - 0, 0, 26, 0, 16, 0, - 5, 0, 0, 0, 52, 0, - 0, 7, 130, 0, 16, 0, - 2, 0, 0, 0, 58, 0, - 16, 0, 2, 0, 0, 0, - 10, 0, 16, 0, 5, 0, - 0, 0, 49, 0, 0, 7, - 130, 0, 16, 0, 3, 0, - 0, 0, 1, 64, 0, 0, - 0, 0, 0, 0, 58, 0, - 16, 0, 2, 0, 0, 0, - 50, 0, 0, 13, 194, 0, - 16, 0, 4, 0, 0, 0, - 86, 137, 32, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 246, 15, 16, 0, 2, 0, - 0, 0, 2, 64, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 128, 63, - 0, 0, 128, 63, 14, 0, - 0, 7, 130, 0, 16, 0, - 2, 0, 0, 0, 42, 0, - 16, 0, 4, 0, 0, 0, - 58, 0, 16, 0, 4, 0, - 0, 0, 56, 0, 0, 7, - 114, 0, 16, 0, 6, 0, - 0, 0, 246, 15, 16, 0, - 2, 0, 0, 0, 70, 2, - 16, 0, 5, 0, 0, 0, - 55, 0, 0, 9, 114, 0, - 16, 0, 5, 0, 0, 0, - 246, 15, 16, 0, 3, 0, - 0, 0, 70, 2, 16, 0, - 6, 0, 0, 0, 70, 2, - 16, 0, 5, 0, 0, 0, - 16, 0, 0, 10, 18, 0, - 16, 0, 6, 0, 0, 0, - 2, 64, 0, 0, 34, 139, - 212, 63, 160, 112, 22, 191, - 35, 45, 149, 189, 0, 0, - 0, 0, 70, 2, 16, 0, - 5, 0, 0, 0, 16, 0, - 0, 10, 34, 0, 16, 0, - 6, 0, 0, 0, 2, 64, - 0, 0, 127, 18, 255, 189, - 180, 2, 145, 63, 13, 198, - 8, 188, 0, 0, 0, 0, - 70, 2, 16, 0, 5, 0, - 0, 0, 16, 0, 0, 10, - 66, 0, 16, 0, 6, 0, - 0, 0, 2, 64, 0, 0, - 179, 183, 148, 188, 205, 5, - 206, 189, 60, 51, 143, 63, - 0, 0, 0, 0, 70, 2, - 16, 0, 5, 0, 0, 0, - 55, 0, 0, 9, 114, 0, - 16, 0, 5, 0, 0, 0, - 166, 10, 16, 0, 0, 0, - 0, 0, 70, 2, 16, 0, - 6, 0, 0, 0, 70, 2, - 16, 0, 5, 0, 0, 0, - 55, 0, 0, 9, 226, 0, - 16, 0, 4, 0, 0, 0, - 86, 5, 16, 0, 4, 0, - 0, 0, 6, 9, 16, 0, - 5, 0, 0, 0, 6, 9, - 16, 0, 2, 0, 0, 0, - 55, 0, 0, 9, 114, 0, - 16, 0, 3, 0, 0, 0, - 6, 0, 16, 0, 4, 0, - 0, 0, 70, 2, 16, 0, - 3, 0, 0, 0, 150, 7, - 16, 0, 4, 0, 0, 0, - 55, 0, 0, 9, 114, 0, - 16, 0, 2, 0, 0, 0, - 6, 0, 16, 0, 0, 0, - 0, 0, 70, 2, 16, 0, - 3, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 31, 0, 4, 3, 58, 0, - 16, 0, 0, 0, 0, 0, - 57, 0, 0, 11, 18, 0, - 16, 0, 0, 0, 0, 0, - 2, 64, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 10, 128, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 31, 0, 4, 3, - 10, 0, 16, 0, 0, 0, - 0, 0, 29, 0, 0, 10, - 114, 0, 16, 0, 3, 0, - 0, 0, 2, 64, 0, 0, - 230, 174, 37, 61, 230, 174, - 37, 61, 230, 174, 37, 61, - 0, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 56, 0, 0, 10, 114, 0, - 16, 0, 4, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 2, 64, 0, 0, - 145, 131, 158, 61, 145, 131, - 158, 61, 145, 131, 158, 61, - 0, 0, 0, 0, 0, 0, - 0, 10, 114, 0, 16, 0, - 5, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 2, 64, 0, 0, 174, 71, - 97, 61, 174, 71, 97, 61, - 174, 71, 97, 61, 0, 0, - 0, 0, 56, 0, 0, 11, - 114, 0, 16, 0, 5, 0, - 0, 0, 70, 2, 16, 128, - 129, 0, 0, 0, 5, 0, - 0, 0, 2, 64, 0, 0, - 111, 167, 114, 63, 111, 167, - 114, 63, 111, 167, 114, 63, - 0, 0, 0, 0, 47, 0, - 0, 5, 114, 0, 16, 0, - 5, 0, 0, 0, 70, 2, - 16, 0, 5, 0, 0, 0, - 56, 0, 0, 10, 114, 0, - 16, 0, 5, 0, 0, 0, - 70, 2, 16, 0, 5, 0, - 0, 0, 2, 64, 0, 0, - 154, 153, 25, 64, 154, 153, - 25, 64, 154, 153, 25, 64, - 0, 0, 0, 0, 25, 0, - 0, 5, 114, 0, 16, 0, - 5, 0, 0, 0, 70, 2, - 16, 0, 5, 0, 0, 0, - 55, 0, 0, 9, 114, 0, - 16, 0, 2, 0, 0, 0, - 70, 2, 16, 0, 3, 0, - 0, 0, 70, 2, 16, 0, - 4, 0, 0, 0, 70, 2, - 16, 0, 5, 0, 0, 0, - 21, 0, 0, 1, 56, 0, - 0, 8, 114, 0, 16, 0, - 1, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 246, 143, 32, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 18, 0, 0, 1, 31, 0, - 4, 3, 42, 0, 16, 0, - 0, 0, 0, 0, 56, 0, - 0, 8, 114, 0, 16, 0, - 1, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 246, 143, 32, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 57, 0, 0, 11, 18, 0, - 16, 0, 0, 0, 0, 0, - 2, 64, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 10, 128, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 31, 0, 0, 3, - 10, 0, 16, 0, 0, 0, - 0, 0, 29, 0, 0, 10, - 210, 0, 16, 0, 0, 0, - 0, 0, 2, 64, 0, 0, - 28, 46, 77, 59, 0, 0, - 0, 0, 28, 46, 77, 59, - 28, 46, 77, 59, 6, 9, - 16, 0, 1, 0, 0, 0, - 56, 0, 0, 10, 114, 0, - 16, 0, 3, 0, 0, 0, - 70, 2, 16, 0, 1, 0, - 0, 0, 2, 64, 0, 0, - 82, 184, 78, 65, 82, 184, - 78, 65, 82, 184, 78, 65, - 0, 0, 0, 0, 47, 0, - 0, 6, 114, 0, 16, 0, - 4, 0, 0, 0, 70, 2, - 16, 128, 129, 0, 0, 0, - 1, 0, 0, 0, 56, 0, - 0, 10, 114, 0, 16, 0, - 4, 0, 0, 0, 70, 2, - 16, 0, 4, 0, 0, 0, - 2, 64, 0, 0, 85, 85, - 213, 62, 85, 85, 213, 62, - 85, 85, 213, 62, 0, 0, - 0, 0, 25, 0, 0, 5, - 114, 0, 16, 0, 4, 0, - 0, 0, 70, 2, 16, 0, - 4, 0, 0, 0, 50, 0, - 0, 15, 114, 0, 16, 0, - 4, 0, 0, 0, 70, 2, - 16, 0, 4, 0, 0, 0, - 2, 64, 0, 0, 61, 10, - 135, 63, 61, 10, 135, 63, - 61, 10, 135, 63, 0, 0, - 0, 0, 2, 64, 0, 0, - 174, 71, 97, 189, 174, 71, - 97, 189, 174, 71, 97, 189, - 0, 0, 0, 0, 55, 32, - 0, 9, 114, 0, 16, 0, - 1, 0, 0, 0, 134, 3, - 16, 0, 0, 0, 0, 0, - 70, 2, 16, 0, 3, 0, - 0, 0, 70, 2, 16, 0, - 4, 0, 0, 0, 21, 0, - 0, 1, 18, 0, 0, 1, - 31, 0, 4, 3, 26, 0, - 16, 0, 0, 0, 0, 0, - 16, 0, 0, 10, 18, 0, - 16, 0, 0, 0, 0, 0, - 2, 64, 0, 0, 34, 139, - 212, 63, 160, 112, 22, 191, - 35, 45, 149, 189, 0, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 16, 0, - 0, 10, 34, 0, 16, 0, - 0, 0, 0, 0, 2, 64, - 0, 0, 127, 18, 255, 189, - 180, 2, 145, 63, 13, 198, - 8, 188, 0, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 16, 0, 0, 10, - 66, 0, 16, 0, 0, 0, - 0, 0, 2, 64, 0, 0, - 179, 183, 148, 188, 205, 5, - 206, 189, 60, 51, 143, 63, - 0, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 56, 0, 0, 8, 114, 0, - 16, 0, 1, 0, 0, 0, - 70, 2, 16, 0, 0, 0, - 0, 0, 246, 143, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 57, 0, 0, 11, - 18, 0, 16, 0, 0, 0, - 0, 0, 2, 64, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 10, 128, - 32, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 31, 0, - 0, 3, 10, 0, 16, 0, - 0, 0, 0, 0, 29, 0, - 0, 10, 114, 0, 16, 0, - 0, 0, 0, 0, 2, 64, - 0, 0, 28, 46, 77, 59, - 28, 46, 77, 59, 28, 46, - 77, 59, 0, 0, 0, 0, - 70, 2, 16, 0, 1, 0, - 0, 0, 56, 0, 0, 10, - 114, 0, 16, 0, 3, 0, - 0, 0, 70, 2, 16, 0, - 1, 0, 0, 0, 2, 64, - 0, 0, 82, 184, 78, 65, - 82, 184, 78, 65, 82, 184, - 78, 65, 0, 0, 0, 0, - 47, 0, 0, 6, 114, 0, - 16, 0, 4, 0, 0, 0, - 70, 2, 16, 128, 129, 0, - 0, 0, 1, 0, 0, 0, - 56, 0, 0, 10, 114, 0, - 16, 0, 4, 0, 0, 0, - 70, 2, 16, 0, 4, 0, - 0, 0, 2, 64, 0, 0, - 85, 85, 213, 62, 85, 85, - 213, 62, 85, 85, 213, 62, - 0, 0, 0, 0, 25, 0, - 0, 5, 114, 0, 16, 0, - 4, 0, 0, 0, 70, 2, - 16, 0, 4, 0, 0, 0, - 50, 0, 0, 15, 114, 0, - 16, 0, 4, 0, 0, 0, - 70, 2, 16, 0, 4, 0, - 0, 0, 2, 64, 0, 0, - 61, 10, 135, 63, 61, 10, - 135, 63, 61, 10, 135, 63, - 0, 0, 0, 0, 2, 64, - 0, 0, 174, 71, 97, 189, - 174, 71, 97, 189, 174, 71, - 97, 189, 0, 0, 0, 0, - 55, 32, 0, 9, 114, 0, - 16, 0, 1, 0, 0, 0, - 70, 2, 16, 0, 0, 0, - 0, 0, 70, 2, 16, 0, - 3, 0, 0, 0, 70, 2, - 16, 0, 4, 0, 0, 0, - 21, 0, 0, 1, 18, 0, - 0, 1, 56, 0, 0, 8, - 114, 0, 16, 0, 1, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 246, 143, - 32, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 21, 0, - 0, 1, 21, 0, 0, 1, - 21, 0, 0, 1, 56, 0, - 0, 7, 242, 32, 16, 0, - 0, 0, 0, 0, 70, 14, - 16, 0, 1, 0, 0, 0, - 70, 30, 16, 0, 2, 0, - 0, 0, 62, 0, 0, 1, - 83, 84, 65, 84, 148, 0, - 0, 0, 126, 0, 0, 0, - 7, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 71, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 9, 0, 0, 0, 11, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 8, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 10, 0, - 0, 0, 3, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0 + 68, 88, 66, 67, 218, 34, + 85, 97, 34, 23, 186, 108, + 107, 10, 84, 63, 204, 132, + 47, 109, 1, 0, 0, 0, + 236, 35, 0, 0, 5, 0, + 0, 0, 52, 0, 0, 0, + 168, 4, 0, 0, 28, 5, + 0, 0, 80, 5, 0, 0, + 80, 35, 0, 0, 82, 68, + 69, 70, 108, 4, 0, 0, + 1, 0, 0, 0, 52, 1, + 0, 0, 6, 0, 0, 0, + 60, 0, 0, 0, 0, 5, + 255, 255, 0, 1, 0, 0, + 68, 4, 0, 0, 82, 68, + 49, 49, 60, 0, 0, 0, + 24, 0, 0, 0, 32, 0, + 0, 0, 40, 0, 0, 0, + 36, 0, 0, 0, 12, 0, + 0, 0, 0, 0, 0, 0, + 252, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 5, 1, 0, 0, + 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 14, 1, + 0, 0, 2, 0, 0, 0, + 5, 0, 0, 0, 4, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 1, 0, + 0, 0, 13, 0, 0, 0, + 23, 1, 0, 0, 2, 0, + 0, 0, 5, 0, 0, 0, + 4, 0, 0, 0, 255, 255, + 255, 255, 1, 0, 0, 0, + 1, 0, 0, 0, 13, 0, + 0, 0, 32, 1, 0, 0, + 2, 0, 0, 0, 5, 0, + 0, 0, 4, 0, 0, 0, + 255, 255, 255, 255, 2, 0, + 0, 0, 1, 0, 0, 0, + 13, 0, 0, 0, 41, 1, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, + 115, 97, 109, 112, 108, 101, + 114, 48, 0, 115, 97, 109, + 112, 108, 101, 114, 49, 0, + 116, 101, 120, 116, 117, 114, + 101, 48, 0, 116, 101, 120, + 116, 117, 114, 101, 49, 0, + 116, 101, 120, 116, 117, 114, + 101, 50, 0, 67, 111, 110, + 115, 116, 97, 110, 116, 115, + 0, 171, 41, 1, 0, 0, + 13, 0, 0, 0, 76, 1, + 0, 0, 112, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 84, 3, 0, 0, + 0, 0, 0, 0, 4, 0, + 0, 0, 2, 0, 0, 0, + 104, 3, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 140, 3, 0, 0, 4, 0, + 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 104, 3, + 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 153, 3, + 0, 0, 8, 0, 0, 0, + 4, 0, 0, 0, 2, 0, + 0, 0, 104, 3, 0, 0, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 164, 3, 0, 0, + 12, 0, 0, 0, 4, 0, + 0, 0, 2, 0, 0, 0, + 104, 3, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 176, 3, 0, 0, 16, 0, + 0, 0, 16, 0, 0, 0, + 2, 0, 0, 0, 196, 3, + 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 232, 3, + 0, 0, 32, 0, 0, 0, + 4, 0, 0, 0, 2, 0, + 0, 0, 104, 3, 0, 0, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 247, 3, 0, 0, + 36, 0, 0, 0, 4, 0, + 0, 0, 2, 0, 0, 0, + 104, 3, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 7, 4, 0, 0, 40, 0, + 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 104, 3, + 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 23, 4, + 0, 0, 44, 0, 0, 0, + 4, 0, 0, 0, 2, 0, + 0, 0, 104, 3, 0, 0, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 39, 4, 0, 0, + 48, 0, 0, 0, 16, 0, + 0, 0, 2, 0, 0, 0, + 196, 3, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 47, 4, 0, 0, 64, 0, + 0, 0, 16, 0, 0, 0, + 2, 0, 0, 0, 196, 3, + 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 54, 4, + 0, 0, 80, 0, 0, 0, + 16, 0, 0, 0, 2, 0, + 0, 0, 196, 3, 0, 0, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 61, 4, 0, 0, + 96, 0, 0, 0, 16, 0, + 0, 0, 2, 0, 0, 0, + 196, 3, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 115, 99, 82, 71, 66, 95, + 111, 117, 116, 112, 117, 116, + 0, 102, 108, 111, 97, 116, + 0, 171, 0, 0, 3, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 97, 3, + 0, 0, 116, 101, 120, 116, + 117, 114, 101, 95, 116, 121, + 112, 101, 0, 105, 110, 112, + 117, 116, 95, 116, 121, 112, + 101, 0, 99, 111, 108, 111, + 114, 95, 115, 99, 97, 108, + 101, 0, 116, 101, 120, 101, + 108, 95, 115, 105, 122, 101, + 0, 102, 108, 111, 97, 116, + 52, 0, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 187, 3, 0, 0, 116, 111, + 110, 101, 109, 97, 112, 95, + 109, 101, 116, 104, 111, 100, + 0, 116, 111, 110, 101, 109, + 97, 112, 95, 102, 97, 99, + 116, 111, 114, 49, 0, 116, + 111, 110, 101, 109, 97, 112, + 95, 102, 97, 99, 116, 111, + 114, 50, 0, 115, 100, 114, + 95, 119, 104, 105, 116, 101, + 95, 112, 111, 105, 110, 116, + 0, 89, 111, 102, 102, 115, + 101, 116, 0, 82, 99, 111, + 101, 102, 102, 0, 71, 99, + 111, 101, 102, 102, 0, 66, + 99, 111, 101, 102, 102, 0, + 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, + 41, 32, 72, 76, 83, 76, + 32, 83, 104, 97, 100, 101, + 114, 32, 67, 111, 109, 112, + 105, 108, 101, 114, 32, 49, + 48, 46, 49, 0, 73, 83, + 71, 78, 108, 0, 0, 0, + 3, 0, 0, 0, 8, 0, + 0, 0, 80, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 15, 0, + 0, 0, 92, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 3, 3, + 0, 0, 101, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 2, 0, 0, 0, 15, 15, + 0, 0, 83, 86, 95, 80, + 79, 83, 73, 84, 73, 79, + 78, 0, 84, 69, 88, 67, + 79, 79, 82, 68, 0, 67, + 79, 76, 79, 82, 0, 171, + 79, 83, 71, 78, 44, 0, + 0, 0, 1, 0, 0, 0, + 8, 0, 0, 0, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 83, 86, + 95, 84, 65, 82, 71, 69, + 84, 0, 171, 171, 83, 72, + 69, 88, 248, 29, 0, 0, + 80, 0, 0, 0, 126, 7, + 0, 0, 106, 8, 0, 1, + 89, 0, 0, 4, 70, 142, + 32, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 90, 0, + 0, 3, 0, 96, 16, 0, + 0, 0, 0, 0, 90, 0, + 0, 3, 0, 96, 16, 0, + 1, 0, 0, 0, 88, 24, + 0, 4, 0, 112, 16, 0, + 0, 0, 0, 0, 85, 85, + 0, 0, 88, 24, 0, 4, + 0, 112, 16, 0, 1, 0, + 0, 0, 85, 85, 0, 0, + 88, 24, 0, 4, 0, 112, + 16, 0, 2, 0, 0, 0, + 85, 85, 0, 0, 98, 16, + 0, 3, 50, 16, 16, 0, + 1, 0, 0, 0, 98, 16, + 0, 3, 242, 16, 16, 0, + 2, 0, 0, 0, 101, 0, + 0, 3, 242, 32, 16, 0, + 0, 0, 0, 0, 104, 0, + 0, 2, 8, 0, 0, 0, + 24, 0, 0, 11, 242, 0, + 16, 0, 0, 0, 0, 0, + 150, 138, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 64, 64, + 0, 0, 0, 64, 0, 0, + 128, 63, 31, 0, 4, 3, + 10, 0, 16, 0, 0, 0, + 0, 0, 54, 0, 0, 8, + 242, 0, 16, 0, 1, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 128, 63, + 0, 0, 128, 63, 18, 0, + 0, 1, 24, 0, 0, 8, + 18, 0, 16, 0, 0, 0, + 0, 0, 26, 128, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 128, 63, 31, 0, + 4, 3, 10, 0, 16, 0, + 0, 0, 0, 0, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 242, 0, + 16, 0, 1, 0, 0, 0, + 70, 16, 16, 0, 1, 0, + 0, 0, 70, 126, 16, 0, + 0, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, + 18, 0, 0, 1, 24, 0, + 0, 8, 18, 0, 16, 0, + 0, 0, 0, 0, 26, 128, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 0, 64, + 31, 0, 4, 3, 10, 0, + 16, 0, 0, 0, 0, 0, + 122, 0, 0, 5, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 16, 16, 0, 1, 0, + 0, 0, 124, 0, 0, 5, + 194, 0, 16, 0, 2, 0, + 0, 0, 6, 20, 16, 0, + 1, 0, 0, 0, 0, 0, + 0, 9, 50, 0, 16, 0, + 3, 0, 0, 0, 230, 10, + 16, 128, 129, 0, 0, 0, + 2, 0, 0, 0, 70, 0, + 16, 128, 129, 0, 0, 0, + 2, 0, 0, 0, 56, 0, + 0, 8, 50, 0, 16, 0, + 3, 0, 0, 0, 70, 0, + 16, 0, 3, 0, 0, 0, + 230, 138, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 52, 0, 0, 10, 50, 0, + 16, 0, 3, 0, 0, 0, + 70, 0, 16, 0, 3, 0, + 0, 0, 2, 64, 0, 0, + 172, 197, 39, 55, 172, 197, + 39, 55, 0, 0, 0, 0, + 0, 0, 0, 0, 51, 0, + 0, 10, 50, 0, 16, 0, + 3, 0, 0, 0, 70, 0, + 16, 0, 3, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 128, 63, 0, 0, 128, 63, + 0, 0, 0, 0, 0, 0, + 0, 0, 56, 0, 0, 10, + 194, 0, 16, 0, 3, 0, + 0, 0, 6, 4, 16, 0, + 3, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 63, 0, 0, 0, 63, + 50, 0, 0, 11, 194, 0, + 16, 0, 3, 0, 0, 0, + 6, 20, 16, 0, 1, 0, + 0, 0, 166, 142, 32, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 166, 14, 16, 128, + 65, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 11, + 50, 0, 16, 0, 3, 0, + 0, 0, 70, 0, 16, 128, + 65, 0, 0, 0, 3, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 26, 0, + 0, 5, 50, 0, 16, 0, + 4, 0, 0, 0, 230, 10, + 16, 0, 3, 0, 0, 0, + 0, 0, 0, 11, 194, 0, + 16, 0, 4, 0, 0, 0, + 6, 4, 16, 128, 65, 0, + 0, 0, 3, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 0, 8, + 50, 0, 16, 0, 3, 0, + 0, 0, 70, 0, 16, 128, + 65, 0, 0, 0, 3, 0, + 0, 0, 70, 0, 16, 0, + 4, 0, 0, 0, 14, 0, + 0, 10, 50, 0, 16, 0, + 4, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 128, 63, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 128, 63, + 230, 10, 16, 0, 4, 0, + 0, 0, 56, 32, 0, 7, + 50, 0, 16, 0, 3, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 70, 0, + 16, 0, 4, 0, 0, 0, + 50, 0, 0, 15, 50, 0, + 16, 0, 4, 0, 0, 0, + 70, 0, 16, 0, 3, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 192, 0, 0, + 0, 192, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 64, 64, + 0, 0, 64, 64, 0, 0, + 0, 0, 0, 0, 0, 0, + 56, 0, 0, 7, 50, 0, + 16, 0, 3, 0, 0, 0, + 70, 0, 16, 0, 3, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 65, 0, + 0, 5, 194, 0, 16, 0, + 3, 0, 0, 0, 166, 14, + 16, 0, 3, 0, 0, 0, + 50, 0, 0, 9, 50, 0, + 16, 0, 3, 0, 0, 0, + 70, 0, 16, 0, 4, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 230, 10, + 16, 0, 3, 0, 0, 0, + 0, 0, 0, 10, 50, 0, + 16, 0, 3, 0, 0, 0, + 70, 0, 16, 0, 3, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 63, 0, 0, + 0, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 56, 0, + 0, 8, 50, 0, 16, 0, + 3, 0, 0, 0, 70, 0, + 16, 0, 3, 0, 0, 0, + 70, 128, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 73, 0, 0, 143, 194, 0, + 0, 128, 67, 85, 21, 0, + 242, 0, 16, 0, 1, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 70, 0, 16, 0, + 2, 0, 0, 0, 230, 10, + 16, 0, 2, 0, 0, 0, + 18, 0, 0, 1, 24, 0, + 0, 8, 18, 0, 16, 0, + 0, 0, 0, 0, 26, 128, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 64, 64, + 31, 0, 4, 3, 10, 0, + 16, 0, 0, 0, 0, 0, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 18, 0, 16, 0, 0, 0, + 0, 0, 70, 16, 16, 0, + 1, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 50, 0, 0, 9, + 18, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 127, 67, + 1, 64, 0, 0, 0, 0, + 0, 63, 56, 0, 0, 7, + 18, 0, 16, 0, 2, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 59, + 54, 0, 0, 5, 34, 0, + 16, 0, 2, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 63, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 242, 0, 16, 0, + 1, 0, 0, 0, 70, 0, + 16, 0, 2, 0, 0, 0, + 70, 126, 16, 0, 1, 0, + 0, 0, 0, 96, 16, 0, + 1, 0, 0, 0, 18, 0, + 0, 1, 24, 0, 0, 8, + 18, 0, 16, 0, 0, 0, + 0, 0, 26, 128, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 128, 64, 31, 0, + 4, 3, 10, 0, 16, 0, + 0, 0, 0, 0, 50, 0, + 0, 13, 50, 0, 16, 0, + 2, 0, 0, 0, 70, 16, + 16, 0, 1, 0, 0, 0, + 230, 138, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 63, 0, 0, 0, 63, + 0, 0, 0, 0, 0, 0, + 0, 0, 65, 0, 0, 5, + 242, 0, 16, 0, 3, 0, + 0, 0, 70, 4, 16, 0, + 2, 0, 0, 0, 0, 0, + 0, 10, 242, 0, 16, 0, + 3, 0, 0, 0, 70, 14, + 16, 0, 3, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 191, 0, 0, 0, 191, + 0, 0, 0, 63, 0, 0, + 0, 63, 56, 0, 0, 8, + 242, 0, 16, 0, 3, 0, + 0, 0, 70, 14, 16, 0, + 3, 0, 0, 0, 70, 132, + 32, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 26, 0, + 0, 5, 50, 0, 16, 0, + 2, 0, 0, 0, 70, 0, + 16, 0, 2, 0, 0, 0, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 18, 0, 16, 0, 0, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 50, 0, 0, 9, + 18, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 127, 67, + 1, 64, 0, 0, 0, 0, + 0, 63, 56, 0, 0, 7, + 18, 0, 16, 0, 4, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 59, + 54, 0, 0, 8, 162, 0, + 16, 0, 4, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 63, + 0, 0, 0, 0, 0, 0, + 0, 63, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 242, 0, 16, 0, + 5, 0, 0, 0, 70, 0, + 16, 0, 4, 0, 0, 0, + 70, 126, 16, 0, 1, 0, + 0, 0, 0, 96, 16, 0, + 1, 0, 0, 0, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 18, 0, + 16, 0, 0, 0, 0, 0, + 198, 0, 16, 0, 3, 0, + 0, 0, 70, 126, 16, 0, + 0, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, + 50, 0, 0, 9, 18, 0, + 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 127, 67, 1, 64, + 0, 0, 0, 0, 0, 63, + 56, 0, 0, 7, 66, 0, + 16, 0, 4, 0, 0, 0, + 10, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 128, 59, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 242, 0, + 16, 0, 4, 0, 0, 0, + 230, 10, 16, 0, 4, 0, + 0, 0, 70, 126, 16, 0, + 1, 0, 0, 0, 0, 96, + 16, 0, 1, 0, 0, 0, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 18, 0, 16, 0, 0, 0, + 0, 0, 102, 10, 16, 0, + 3, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 50, 0, 0, 9, + 18, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 127, 67, + 1, 64, 0, 0, 0, 0, + 0, 63, 56, 0, 0, 7, + 18, 0, 16, 0, 6, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 59, + 54, 0, 0, 8, 162, 0, + 16, 0, 6, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 63, + 0, 0, 0, 0, 0, 0, + 0, 63, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 242, 0, 16, 0, + 7, 0, 0, 0, 70, 0, + 16, 0, 6, 0, 0, 0, + 70, 126, 16, 0, 1, 0, + 0, 0, 0, 96, 16, 0, + 1, 0, 0, 0, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 18, 0, + 16, 0, 0, 0, 0, 0, + 230, 10, 16, 0, 3, 0, + 0, 0, 70, 126, 16, 0, + 0, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, + 50, 0, 0, 9, 18, 0, + 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 127, 67, 1, 64, + 0, 0, 0, 0, 0, 63, + 56, 0, 0, 7, 66, 0, + 16, 0, 6, 0, 0, 0, + 10, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 128, 59, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 242, 0, + 16, 0, 3, 0, 0, 0, + 230, 10, 16, 0, 6, 0, + 0, 0, 70, 126, 16, 0, + 1, 0, 0, 0, 0, 96, + 16, 0, 1, 0, 0, 0, + 0, 0, 0, 8, 242, 0, + 16, 0, 4, 0, 0, 0, + 70, 14, 16, 128, 65, 0, + 0, 0, 5, 0, 0, 0, + 70, 14, 16, 0, 4, 0, + 0, 0, 50, 0, 0, 9, + 242, 0, 16, 0, 4, 0, + 0, 0, 86, 5, 16, 0, + 2, 0, 0, 0, 70, 14, + 16, 0, 4, 0, 0, 0, + 70, 14, 16, 0, 5, 0, + 0, 0, 0, 0, 0, 8, + 242, 0, 16, 0, 3, 0, + 0, 0, 70, 14, 16, 128, + 65, 0, 0, 0, 7, 0, + 0, 0, 70, 14, 16, 0, + 3, 0, 0, 0, 50, 0, + 0, 9, 242, 0, 16, 0, + 3, 0, 0, 0, 86, 5, + 16, 0, 2, 0, 0, 0, + 70, 14, 16, 0, 3, 0, + 0, 0, 70, 14, 16, 0, + 7, 0, 0, 0, 0, 0, + 0, 8, 242, 0, 16, 0, + 3, 0, 0, 0, 70, 14, + 16, 128, 65, 0, 0, 0, + 4, 0, 0, 0, 70, 14, + 16, 0, 3, 0, 0, 0, + 50, 0, 0, 9, 242, 0, + 16, 0, 1, 0, 0, 0, + 6, 0, 16, 0, 2, 0, + 0, 0, 70, 14, 16, 0, + 3, 0, 0, 0, 70, 14, + 16, 0, 4, 0, 0, 0, + 18, 0, 0, 1, 24, 0, + 0, 8, 18, 0, 16, 0, + 0, 0, 0, 0, 26, 128, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 160, 64, + 31, 0, 4, 3, 10, 0, + 16, 0, 0, 0, 0, 0, + 122, 0, 0, 5, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 16, 16, 0, 1, 0, + 0, 0, 124, 0, 0, 5, + 194, 0, 16, 0, 2, 0, + 0, 0, 6, 20, 16, 0, + 1, 0, 0, 0, 0, 0, + 0, 9, 50, 0, 16, 0, + 2, 0, 0, 0, 230, 10, + 16, 128, 129, 0, 0, 0, + 2, 0, 0, 0, 70, 0, + 16, 128, 129, 0, 0, 0, + 2, 0, 0, 0, 56, 0, + 0, 8, 50, 0, 16, 0, + 2, 0, 0, 0, 70, 0, + 16, 0, 2, 0, 0, 0, + 230, 138, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 52, 0, 0, 10, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 0, 16, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 172, 197, 39, 55, 172, 197, + 39, 55, 0, 0, 0, 0, + 0, 0, 0, 0, 51, 0, + 0, 10, 50, 0, 16, 0, + 2, 0, 0, 0, 70, 0, + 16, 0, 2, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 128, 63, 0, 0, 128, 63, + 0, 0, 0, 0, 0, 0, + 0, 0, 56, 0, 0, 10, + 194, 0, 16, 0, 2, 0, + 0, 0, 6, 4, 16, 0, + 2, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 63, 0, 0, 0, 63, + 50, 0, 0, 11, 194, 0, + 16, 0, 2, 0, 0, 0, + 6, 20, 16, 0, 1, 0, + 0, 0, 166, 142, 32, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 166, 14, 16, 128, + 65, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 11, + 50, 0, 16, 0, 2, 0, + 0, 0, 70, 0, 16, 128, + 65, 0, 0, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 26, 0, + 0, 5, 50, 0, 16, 0, + 3, 0, 0, 0, 230, 10, + 16, 0, 2, 0, 0, 0, + 0, 0, 0, 11, 194, 0, + 16, 0, 3, 0, 0, 0, + 6, 4, 16, 128, 65, 0, + 0, 0, 2, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 0, 8, + 50, 0, 16, 0, 2, 0, + 0, 0, 70, 0, 16, 128, + 65, 0, 0, 0, 2, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 14, 0, + 0, 10, 50, 0, 16, 0, + 3, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 128, 63, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 128, 63, + 230, 10, 16, 0, 3, 0, + 0, 0, 56, 32, 0, 7, + 50, 0, 16, 0, 2, 0, + 0, 0, 70, 0, 16, 0, + 2, 0, 0, 0, 70, 0, + 16, 0, 3, 0, 0, 0, + 50, 0, 0, 15, 50, 0, + 16, 0, 3, 0, 0, 0, + 70, 0, 16, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 192, 0, 0, + 0, 192, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 64, 64, + 0, 0, 64, 64, 0, 0, + 0, 0, 0, 0, 0, 0, + 56, 0, 0, 7, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 0, 16, 0, 2, 0, + 0, 0, 70, 0, 16, 0, + 2, 0, 0, 0, 65, 0, + 0, 5, 194, 0, 16, 0, + 2, 0, 0, 0, 166, 14, + 16, 0, 2, 0, 0, 0, + 50, 0, 0, 9, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 0, 16, 0, 3, 0, + 0, 0, 70, 0, 16, 0, + 2, 0, 0, 0, 230, 10, + 16, 0, 2, 0, 0, 0, + 0, 0, 0, 10, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 0, 16, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 63, 0, 0, + 0, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 56, 0, + 0, 8, 50, 0, 16, 0, + 2, 0, 0, 0, 70, 0, + 16, 0, 2, 0, 0, 0, + 70, 128, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 50, 0, 0, 13, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 0, 16, 0, 2, 0, + 0, 0, 230, 138, 32, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 63, 0, 0, + 0, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 65, 0, + 0, 5, 242, 0, 16, 0, + 3, 0, 0, 0, 70, 4, + 16, 0, 2, 0, 0, 0, + 0, 0, 0, 10, 242, 0, + 16, 0, 3, 0, 0, 0, + 70, 14, 16, 0, 3, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 191, 0, 0, + 0, 191, 0, 0, 0, 63, + 0, 0, 0, 63, 56, 0, + 0, 8, 242, 0, 16, 0, + 3, 0, 0, 0, 70, 14, + 16, 0, 3, 0, 0, 0, + 70, 132, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 26, 0, 0, 5, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 0, 16, 0, 2, 0, + 0, 0, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 18, 0, 16, 0, + 0, 0, 0, 0, 70, 0, + 16, 0, 3, 0, 0, 0, + 70, 126, 16, 0, 0, 0, + 0, 0, 0, 96, 16, 0, + 0, 0, 0, 0, 50, 0, + 0, 9, 18, 0, 16, 0, + 0, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 127, 67, 1, 64, 0, 0, + 0, 0, 0, 63, 56, 0, + 0, 7, 18, 0, 16, 0, + 4, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 128, 59, 54, 0, 0, 8, + 162, 0, 16, 0, 4, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 63, 0, 0, 0, 0, + 0, 0, 0, 63, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 242, 0, + 16, 0, 5, 0, 0, 0, + 70, 0, 16, 0, 4, 0, + 0, 0, 70, 126, 16, 0, + 1, 0, 0, 0, 0, 96, + 16, 0, 1, 0, 0, 0, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 18, 0, 16, 0, 0, 0, + 0, 0, 198, 0, 16, 0, + 3, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 50, 0, 0, 9, + 18, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 127, 67, + 1, 64, 0, 0, 0, 0, + 0, 63, 56, 0, 0, 7, + 66, 0, 16, 0, 4, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 59, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 242, 0, 16, 0, 4, 0, + 0, 0, 230, 10, 16, 0, + 4, 0, 0, 0, 70, 126, + 16, 0, 1, 0, 0, 0, + 0, 96, 16, 0, 1, 0, + 0, 0, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 18, 0, 16, 0, + 0, 0, 0, 0, 102, 10, + 16, 0, 3, 0, 0, 0, + 70, 126, 16, 0, 0, 0, + 0, 0, 0, 96, 16, 0, + 0, 0, 0, 0, 50, 0, + 0, 9, 18, 0, 16, 0, + 0, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 127, 67, 1, 64, 0, 0, + 0, 0, 0, 63, 56, 0, + 0, 7, 18, 0, 16, 0, + 6, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 128, 59, 54, 0, 0, 8, + 162, 0, 16, 0, 6, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 63, 0, 0, 0, 0, + 0, 0, 0, 63, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 242, 0, + 16, 0, 7, 0, 0, 0, + 70, 0, 16, 0, 6, 0, + 0, 0, 70, 126, 16, 0, + 1, 0, 0, 0, 0, 96, + 16, 0, 1, 0, 0, 0, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 18, 0, 16, 0, 0, 0, + 0, 0, 230, 10, 16, 0, + 3, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 50, 0, 0, 9, + 18, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 127, 67, + 1, 64, 0, 0, 0, 0, + 0, 63, 56, 0, 0, 7, + 66, 0, 16, 0, 6, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 59, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 242, 0, 16, 0, 3, 0, + 0, 0, 230, 10, 16, 0, + 6, 0, 0, 0, 70, 126, + 16, 0, 1, 0, 0, 0, + 0, 96, 16, 0, 1, 0, + 0, 0, 0, 0, 0, 8, + 242, 0, 16, 0, 4, 0, + 0, 0, 70, 14, 16, 128, + 65, 0, 0, 0, 5, 0, + 0, 0, 70, 14, 16, 0, + 4, 0, 0, 0, 50, 0, + 0, 9, 242, 0, 16, 0, + 4, 0, 0, 0, 86, 5, + 16, 0, 2, 0, 0, 0, + 70, 14, 16, 0, 4, 0, + 0, 0, 70, 14, 16, 0, + 5, 0, 0, 0, 0, 0, + 0, 8, 242, 0, 16, 0, + 3, 0, 0, 0, 70, 14, + 16, 128, 65, 0, 0, 0, + 7, 0, 0, 0, 70, 14, + 16, 0, 3, 0, 0, 0, + 50, 0, 0, 9, 242, 0, + 16, 0, 3, 0, 0, 0, + 86, 5, 16, 0, 2, 0, + 0, 0, 70, 14, 16, 0, + 3, 0, 0, 0, 70, 14, + 16, 0, 7, 0, 0, 0, + 0, 0, 0, 8, 242, 0, + 16, 0, 3, 0, 0, 0, + 70, 14, 16, 128, 65, 0, + 0, 0, 4, 0, 0, 0, + 70, 14, 16, 0, 3, 0, + 0, 0, 50, 0, 0, 9, + 242, 0, 16, 0, 1, 0, + 0, 0, 6, 0, 16, 0, + 2, 0, 0, 0, 70, 14, + 16, 0, 3, 0, 0, 0, + 70, 14, 16, 0, 4, 0, + 0, 0, 18, 0, 0, 1, + 24, 0, 0, 8, 18, 0, + 16, 0, 0, 0, 0, 0, + 26, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 192, 64, 31, 0, 4, 3, + 10, 0, 16, 0, 0, 0, + 0, 0, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 18, 0, 16, 0, + 2, 0, 0, 0, 70, 16, + 16, 0, 1, 0, 0, 0, + 70, 126, 16, 0, 0, 0, + 0, 0, 0, 96, 16, 0, + 0, 0, 0, 0, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 98, 0, + 16, 0, 2, 0, 0, 0, + 70, 16, 16, 0, 1, 0, + 0, 0, 38, 125, 16, 0, + 1, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, + 0, 0, 0, 8, 114, 0, + 16, 0, 2, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 70, 130, 32, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 16, 0, 0, 8, + 18, 0, 16, 0, 1, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 70, 130, + 32, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 16, 0, + 0, 8, 34, 0, 16, 0, + 1, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 70, 130, 32, 0, 0, 0, + 0, 0, 5, 0, 0, 0, + 16, 0, 0, 8, 66, 0, + 16, 0, 1, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 70, 130, 32, 0, + 0, 0, 0, 0, 6, 0, + 0, 0, 18, 0, 0, 1, + 24, 0, 0, 8, 18, 0, + 16, 0, 0, 0, 0, 0, + 26, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 224, 64, 31, 0, 4, 3, + 10, 0, 16, 0, 0, 0, + 0, 0, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 18, 0, 16, 0, + 2, 0, 0, 0, 70, 16, + 16, 0, 1, 0, 0, 0, + 70, 126, 16, 0, 0, 0, + 0, 0, 0, 96, 16, 0, + 0, 0, 0, 0, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 98, 0, + 16, 0, 2, 0, 0, 0, + 70, 16, 16, 0, 1, 0, + 0, 0, 102, 124, 16, 0, + 1, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, + 0, 0, 0, 8, 114, 0, + 16, 0, 2, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 70, 130, 32, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 16, 0, 0, 8, + 18, 0, 16, 0, 1, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 70, 130, + 32, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 16, 0, + 0, 8, 34, 0, 16, 0, + 1, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 70, 130, 32, 0, 0, 0, + 0, 0, 5, 0, 0, 0, + 16, 0, 0, 8, 66, 0, + 16, 0, 1, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 70, 130, 32, 0, + 0, 0, 0, 0, 6, 0, + 0, 0, 18, 0, 0, 1, + 24, 0, 0, 8, 18, 0, + 16, 0, 0, 0, 0, 0, + 26, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 65, 31, 0, 4, 3, + 10, 0, 16, 0, 0, 0, + 0, 0, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 18, 0, 16, 0, + 2, 0, 0, 0, 70, 16, + 16, 0, 1, 0, 0, 0, + 70, 126, 16, 0, 0, 0, + 0, 0, 0, 96, 16, 0, + 0, 0, 0, 0, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 34, 0, + 16, 0, 2, 0, 0, 0, + 70, 16, 16, 0, 1, 0, + 0, 0, 22, 126, 16, 0, + 1, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 66, 0, 16, 0, 2, 0, + 0, 0, 70, 16, 16, 0, + 1, 0, 0, 0, 150, 124, + 16, 0, 2, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 0, 0, 0, 8, + 114, 0, 16, 0, 2, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 70, 130, + 32, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 16, 0, + 0, 8, 18, 0, 16, 0, + 1, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 70, 130, 32, 0, 0, 0, + 0, 0, 4, 0, 0, 0, + 16, 0, 0, 8, 34, 0, + 16, 0, 1, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 70, 130, 32, 0, + 0, 0, 0, 0, 5, 0, + 0, 0, 16, 0, 0, 8, + 66, 0, 16, 0, 1, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 70, 130, + 32, 0, 0, 0, 0, 0, + 6, 0, 0, 0, 18, 0, + 0, 1, 54, 0, 0, 8, + 114, 0, 16, 0, 1, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 128, 63, 0, 0, + 0, 0, 0, 0, 128, 63, + 0, 0, 0, 0, 21, 0, + 0, 1, 21, 0, 0, 1, + 21, 0, 0, 1, 54, 0, + 0, 5, 130, 0, 16, 0, + 1, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 63, + 21, 0, 0, 1, 21, 0, + 0, 1, 21, 0, 0, 1, + 21, 0, 0, 1, 21, 0, + 0, 1, 21, 0, 0, 1, + 47, 0, 0, 6, 114, 0, + 16, 0, 2, 0, 0, 0, + 70, 2, 16, 128, 129, 0, + 0, 0, 1, 0, 0, 0, + 56, 0, 0, 10, 114, 0, + 16, 0, 2, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 172, 205, 79, 60, 172, 205, + 79, 60, 172, 205, 79, 60, + 0, 0, 0, 0, 25, 0, + 0, 5, 114, 0, 16, 0, + 2, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 0, 0, 0, 10, 114, 0, + 16, 0, 3, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 86, 191, 0, 0, + 86, 191, 0, 0, 86, 191, + 0, 0, 0, 0, 52, 0, + 0, 10, 114, 0, 16, 0, + 3, 0, 0, 0, 70, 2, + 16, 0, 3, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 50, 0, 0, 16, + 114, 0, 16, 0, 2, 0, + 0, 0, 70, 2, 16, 128, + 65, 0, 0, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 0, 128, 149, 65, 0, 128, + 149, 65, 0, 128, 149, 65, + 0, 0, 0, 0, 2, 64, + 0, 0, 0, 208, 150, 65, + 0, 208, 150, 65, 0, 208, + 150, 65, 0, 0, 0, 0, + 14, 0, 0, 7, 114, 0, + 16, 0, 2, 0, 0, 0, + 70, 2, 16, 0, 3, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 47, 0, + 0, 6, 114, 0, 16, 0, + 2, 0, 0, 0, 70, 2, + 16, 128, 129, 0, 0, 0, + 2, 0, 0, 0, 56, 0, + 0, 10, 114, 0, 16, 0, + 2, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 2, 64, 0, 0, 107, 224, + 200, 64, 107, 224, 200, 64, + 107, 224, 200, 64, 0, 0, + 0, 0, 25, 0, 0, 5, + 114, 0, 16, 0, 2, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 56, 0, + 0, 10, 114, 0, 16, 0, + 2, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 2, 64, 0, 0, 0, 64, + 28, 70, 0, 64, 28, 70, + 0, 64, 28, 70, 0, 0, + 0, 0, 14, 0, 0, 8, + 114, 0, 16, 0, 2, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 246, 143, + 32, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, + 2, 0, 0, 0, 86, 5, + 16, 0, 0, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 70, 2, 16, 0, + 1, 0, 0, 0, 57, 0, + 0, 8, 18, 0, 16, 0, + 0, 0, 0, 0, 10, 128, + 32, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 0, 0, + 56, 0, 0, 8, 114, 0, + 16, 0, 3, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 86, 133, 32, 0, + 0, 0, 0, 0, 2, 0, + 0, 0, 24, 0, 0, 11, + 50, 0, 16, 0, 4, 0, + 0, 0, 6, 128, 32, 0, + 0, 0, 0, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 128, 63, 0, 0, + 0, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 16, 0, + 0, 10, 18, 0, 16, 0, + 5, 0, 0, 0, 2, 64, + 0, 0, 140, 157, 32, 63, + 200, 151, 168, 62, 249, 104, + 49, 61, 0, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 16, 0, 0, 10, + 34, 0, 16, 0, 5, 0, + 0, 0, 2, 64, 0, 0, + 186, 130, 141, 61, 10, 103, + 107, 63, 175, 39, 58, 60, + 0, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 16, 0, 0, 10, 66, 0, + 16, 0, 5, 0, 0, 0, + 2, 64, 0, 0, 107, 70, + 134, 60, 41, 64, 180, 61, + 183, 69, 101, 63, 0, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, + 5, 0, 0, 0, 166, 10, + 16, 0, 0, 0, 0, 0, + 70, 2, 16, 0, 5, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 52, 0, + 0, 7, 130, 0, 16, 0, + 2, 0, 0, 0, 42, 0, + 16, 0, 5, 0, 0, 0, + 26, 0, 16, 0, 5, 0, + 0, 0, 52, 0, 0, 7, + 130, 0, 16, 0, 2, 0, + 0, 0, 58, 0, 16, 0, + 2, 0, 0, 0, 10, 0, + 16, 0, 5, 0, 0, 0, + 49, 0, 0, 7, 130, 0, + 16, 0, 3, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 58, 0, 16, 0, + 2, 0, 0, 0, 50, 0, + 0, 13, 194, 0, 16, 0, + 4, 0, 0, 0, 86, 137, + 32, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 246, 15, + 16, 0, 2, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 128, 63, 0, 0, + 128, 63, 14, 0, 0, 7, + 130, 0, 16, 0, 2, 0, + 0, 0, 42, 0, 16, 0, + 4, 0, 0, 0, 58, 0, + 16, 0, 4, 0, 0, 0, + 56, 0, 0, 7, 114, 0, + 16, 0, 6, 0, 0, 0, + 246, 15, 16, 0, 2, 0, + 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, + 5, 0, 0, 0, 246, 15, + 16, 0, 3, 0, 0, 0, + 70, 2, 16, 0, 6, 0, + 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 16, 0, + 0, 10, 18, 0, 16, 0, + 6, 0, 0, 0, 2, 64, + 0, 0, 34, 139, 212, 63, + 160, 112, 22, 191, 35, 45, + 149, 189, 0, 0, 0, 0, + 70, 2, 16, 0, 5, 0, + 0, 0, 16, 0, 0, 10, + 34, 0, 16, 0, 6, 0, + 0, 0, 2, 64, 0, 0, + 127, 18, 255, 189, 180, 2, + 145, 63, 13, 198, 8, 188, + 0, 0, 0, 0, 70, 2, + 16, 0, 5, 0, 0, 0, + 16, 0, 0, 10, 66, 0, + 16, 0, 6, 0, 0, 0, + 2, 64, 0, 0, 179, 183, + 148, 188, 205, 5, 206, 189, + 60, 51, 143, 63, 0, 0, + 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, + 5, 0, 0, 0, 166, 10, + 16, 0, 0, 0, 0, 0, + 70, 2, 16, 0, 6, 0, + 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 55, 0, + 0, 9, 226, 0, 16, 0, + 4, 0, 0, 0, 86, 5, + 16, 0, 4, 0, 0, 0, + 6, 9, 16, 0, 5, 0, + 0, 0, 6, 9, 16, 0, + 2, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, + 3, 0, 0, 0, 6, 0, + 16, 0, 4, 0, 0, 0, + 70, 2, 16, 0, 3, 0, + 0, 0, 150, 7, 16, 0, + 4, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, + 2, 0, 0, 0, 6, 0, + 16, 0, 0, 0, 0, 0, + 70, 2, 16, 0, 3, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 31, 0, + 4, 3, 58, 0, 16, 0, + 0, 0, 0, 0, 57, 0, + 0, 11, 18, 0, 16, 0, + 0, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 10, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 31, 0, 4, 3, 10, 0, + 16, 0, 0, 0, 0, 0, + 29, 0, 0, 10, 114, 0, + 16, 0, 3, 0, 0, 0, + 2, 64, 0, 0, 230, 174, + 37, 61, 230, 174, 37, 61, + 230, 174, 37, 61, 0, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 56, 0, + 0, 10, 114, 0, 16, 0, + 4, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 2, 64, 0, 0, 145, 131, + 158, 61, 145, 131, 158, 61, + 145, 131, 158, 61, 0, 0, + 0, 0, 0, 0, 0, 10, + 114, 0, 16, 0, 5, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 2, 64, + 0, 0, 174, 71, 97, 61, + 174, 71, 97, 61, 174, 71, + 97, 61, 0, 0, 0, 0, + 56, 0, 0, 11, 114, 0, + 16, 0, 5, 0, 0, 0, + 70, 2, 16, 128, 129, 0, + 0, 0, 5, 0, 0, 0, + 2, 64, 0, 0, 111, 167, + 114, 63, 111, 167, 114, 63, + 111, 167, 114, 63, 0, 0, + 0, 0, 47, 0, 0, 5, + 114, 0, 16, 0, 5, 0, + 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 56, 0, + 0, 10, 114, 0, 16, 0, + 5, 0, 0, 0, 70, 2, + 16, 0, 5, 0, 0, 0, + 2, 64, 0, 0, 154, 153, + 25, 64, 154, 153, 25, 64, + 154, 153, 25, 64, 0, 0, + 0, 0, 25, 0, 0, 5, + 114, 0, 16, 0, 5, 0, + 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, + 2, 0, 0, 0, 70, 2, + 16, 0, 3, 0, 0, 0, + 70, 2, 16, 0, 4, 0, + 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 21, 0, + 0, 1, 56, 0, 0, 8, + 114, 0, 16, 0, 1, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 246, 143, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 18, 0, + 0, 1, 31, 0, 4, 3, + 42, 0, 16, 0, 0, 0, + 0, 0, 56, 0, 0, 8, + 114, 0, 16, 0, 1, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 246, 143, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 57, 0, + 0, 11, 18, 0, 16, 0, + 0, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 10, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 31, 0, 0, 3, 10, 0, + 16, 0, 0, 0, 0, 0, + 29, 0, 0, 10, 210, 0, + 16, 0, 0, 0, 0, 0, + 2, 64, 0, 0, 28, 46, + 77, 59, 0, 0, 0, 0, + 28, 46, 77, 59, 28, 46, + 77, 59, 6, 9, 16, 0, + 1, 0, 0, 0, 56, 0, + 0, 10, 114, 0, 16, 0, + 3, 0, 0, 0, 70, 2, + 16, 0, 1, 0, 0, 0, + 2, 64, 0, 0, 82, 184, + 78, 65, 82, 184, 78, 65, + 82, 184, 78, 65, 0, 0, + 0, 0, 47, 0, 0, 6, + 114, 0, 16, 0, 4, 0, + 0, 0, 70, 2, 16, 128, + 129, 0, 0, 0, 1, 0, + 0, 0, 56, 0, 0, 10, + 114, 0, 16, 0, 4, 0, + 0, 0, 70, 2, 16, 0, + 4, 0, 0, 0, 2, 64, + 0, 0, 85, 85, 213, 62, + 85, 85, 213, 62, 85, 85, + 213, 62, 0, 0, 0, 0, + 25, 0, 0, 5, 114, 0, + 16, 0, 4, 0, 0, 0, + 70, 2, 16, 0, 4, 0, + 0, 0, 50, 0, 0, 15, + 114, 0, 16, 0, 4, 0, + 0, 0, 70, 2, 16, 0, + 4, 0, 0, 0, 2, 64, + 0, 0, 61, 10, 135, 63, + 61, 10, 135, 63, 61, 10, + 135, 63, 0, 0, 0, 0, + 2, 64, 0, 0, 174, 71, + 97, 189, 174, 71, 97, 189, + 174, 71, 97, 189, 0, 0, + 0, 0, 55, 32, 0, 9, + 114, 0, 16, 0, 1, 0, + 0, 0, 134, 3, 16, 0, + 0, 0, 0, 0, 70, 2, + 16, 0, 3, 0, 0, 0, + 70, 2, 16, 0, 4, 0, + 0, 0, 21, 0, 0, 1, + 18, 0, 0, 1, 31, 0, + 4, 3, 26, 0, 16, 0, + 0, 0, 0, 0, 16, 0, + 0, 10, 18, 0, 16, 0, + 0, 0, 0, 0, 2, 64, + 0, 0, 34, 139, 212, 63, + 160, 112, 22, 191, 35, 45, + 149, 189, 0, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 16, 0, 0, 10, + 34, 0, 16, 0, 0, 0, + 0, 0, 2, 64, 0, 0, + 127, 18, 255, 189, 180, 2, + 145, 63, 13, 198, 8, 188, + 0, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 16, 0, 0, 10, 66, 0, + 16, 0, 0, 0, 0, 0, + 2, 64, 0, 0, 179, 183, + 148, 188, 205, 5, 206, 189, + 60, 51, 143, 63, 0, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 56, 0, + 0, 8, 114, 0, 16, 0, + 1, 0, 0, 0, 70, 2, + 16, 0, 0, 0, 0, 0, + 246, 143, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 57, 0, 0, 11, 18, 0, + 16, 0, 0, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 10, 128, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 31, 0, 0, 3, + 10, 0, 16, 0, 0, 0, + 0, 0, 29, 0, 0, 10, + 114, 0, 16, 0, 0, 0, + 0, 0, 2, 64, 0, 0, + 28, 46, 77, 59, 28, 46, + 77, 59, 28, 46, 77, 59, + 0, 0, 0, 0, 70, 2, + 16, 0, 1, 0, 0, 0, + 56, 0, 0, 10, 114, 0, + 16, 0, 3, 0, 0, 0, + 70, 2, 16, 0, 1, 0, + 0, 0, 2, 64, 0, 0, + 82, 184, 78, 65, 82, 184, + 78, 65, 82, 184, 78, 65, + 0, 0, 0, 0, 47, 0, + 0, 6, 114, 0, 16, 0, + 4, 0, 0, 0, 70, 2, + 16, 128, 129, 0, 0, 0, + 1, 0, 0, 0, 56, 0, + 0, 10, 114, 0, 16, 0, + 4, 0, 0, 0, 70, 2, + 16, 0, 4, 0, 0, 0, + 2, 64, 0, 0, 85, 85, + 213, 62, 85, 85, 213, 62, + 85, 85, 213, 62, 0, 0, + 0, 0, 25, 0, 0, 5, + 114, 0, 16, 0, 4, 0, + 0, 0, 70, 2, 16, 0, + 4, 0, 0, 0, 50, 0, + 0, 15, 114, 0, 16, 0, + 4, 0, 0, 0, 70, 2, + 16, 0, 4, 0, 0, 0, + 2, 64, 0, 0, 61, 10, + 135, 63, 61, 10, 135, 63, + 61, 10, 135, 63, 0, 0, + 0, 0, 2, 64, 0, 0, + 174, 71, 97, 189, 174, 71, + 97, 189, 174, 71, 97, 189, + 0, 0, 0, 0, 55, 32, + 0, 9, 114, 0, 16, 0, + 1, 0, 0, 0, 70, 2, + 16, 0, 0, 0, 0, 0, + 70, 2, 16, 0, 3, 0, + 0, 0, 70, 2, 16, 0, + 4, 0, 0, 0, 21, 0, + 0, 1, 18, 0, 0, 1, + 56, 0, 0, 8, 114, 0, + 16, 0, 1, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 246, 143, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 21, 0, 0, 1, + 21, 0, 0, 1, 21, 0, + 0, 1, 56, 0, 0, 7, + 242, 32, 16, 0, 0, 0, + 0, 0, 70, 14, 16, 0, + 1, 0, 0, 0, 70, 30, + 16, 0, 2, 0, 0, 0, + 62, 0, 0, 1, 83, 84, + 65, 84, 148, 0, 0, 0, + 246, 0, 0, 0, 8, 0, + 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 155, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 13, 0, + 0, 0, 15, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 26, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 8, 0, + 0, 0, 10, 0, 0, 0, + 11, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0 }; diff --git a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Colors.h b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Colors.h index 6658e4d..b2e30ce 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Colors.h +++ b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Colors.h @@ -3,7 +3,7 @@ // Generated by Microsoft (R) HLSL Shader Compiler 10.1 // // -// Buffer Definitions: +// Buffer Definitions: // // cbuffer Constants // { @@ -12,14 +12,15 @@ // float texture_type; // Offset: 4 Size: 4 [unused] // float input_type; // Offset: 8 Size: 4 [unused] // float color_scale; // Offset: 12 Size: 4 -// float tonemap_method; // Offset: 16 Size: 4 [unused] -// float tonemap_factor1; // Offset: 20 Size: 4 [unused] -// float tonemap_factor2; // Offset: 24 Size: 4 [unused] -// float sdr_white_point; // Offset: 28 Size: 4 [unused] -// float4 Yoffset; // Offset: 32 Size: 16 [unused] -// float4 Rcoeff; // Offset: 48 Size: 16 [unused] -// float4 Gcoeff; // Offset: 64 Size: 16 [unused] -// float4 Bcoeff; // Offset: 80 Size: 16 [unused] +// float4 texel_size; // Offset: 16 Size: 16 [unused] +// float tonemap_method; // Offset: 32 Size: 4 [unused] +// float tonemap_factor1; // Offset: 36 Size: 4 [unused] +// float tonemap_factor2; // Offset: 40 Size: 4 [unused] +// float sdr_white_point; // Offset: 44 Size: 4 [unused] +// float4 Yoffset; // Offset: 48 Size: 16 [unused] +// float4 Rcoeff; // Offset: 64 Size: 16 [unused] +// float4 Gcoeff; // Offset: 80 Size: 16 [unused] +// float4 Bcoeff; // Offset: 96 Size: 16 [unused] // // } // @@ -28,7 +29,7 @@ // // Name Type Format Dim HLSL Bind Count // ------------------------------ ---------- ------- ----------- -------------- ------ -// Constants cbuffer NA NA cb0 1 +// Constants cbuffer NA NA cb0 1 // // // @@ -36,8 +37,8 @@ // // Name Index Mask Register SysValue Format Used // -------------------- ----- ------ -------- -------- ------- ------ -// SV_POSITION 0 xyzw 0 POS float -// TEXCOORD 0 xy 1 NONE float +// SV_POSITION 0 xyzw 0 POS float +// TEXCOORD 0 xy 1 NONE float // COLOR 0 xyzw 2 NONE float xyzw // // @@ -72,213 +73,218 @@ dcl_temps 1 mov r0.x, cb0[0].w mov r0.w, l(1.000000) mul o0.xyzw, r0.xxxw, v2.xyzw -ret +ret // Approximately 4 instruction slots used #endif const BYTE g_main[] = { - 68, 88, 66, 67, 78, 223, - 23, 23, 93, 184, 255, 26, - 153, 0, 220, 179, 25, 194, - 30, 249, 1, 0, 0, 0, - 192, 4, 0, 0, 6, 0, - 0, 0, 56, 0, 0, 0, - 172, 0, 0, 0, 56, 1, - 0, 0, 180, 1, 0, 0, - 24, 4, 0, 0, 140, 4, - 0, 0, 65, 111, 110, 57, - 108, 0, 0, 0, 108, 0, - 0, 0, 0, 2, 255, 255, - 60, 0, 0, 0, 48, 0, - 0, 0, 1, 0, 36, 0, - 0, 0, 48, 0, 0, 0, - 48, 0, 0, 0, 36, 0, - 0, 0, 48, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 2, - 255, 255, 31, 0, 0, 2, - 0, 0, 0, 128, 1, 0, - 15, 176, 5, 0, 0, 3, - 0, 0, 7, 128, 1, 0, - 228, 176, 0, 0, 255, 160, - 1, 0, 0, 2, 0, 0, - 8, 128, 1, 0, 255, 176, - 1, 0, 0, 2, 0, 8, - 15, 128, 0, 0, 228, 128, - 255, 255, 0, 0, 83, 72, - 68, 82, 132, 0, 0, 0, - 64, 0, 0, 0, 33, 0, - 0, 0, 89, 0, 0, 4, - 70, 142, 32, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 98, 16, 0, 3, 242, 16, - 16, 0, 2, 0, 0, 0, - 101, 0, 0, 3, 242, 32, - 16, 0, 0, 0, 0, 0, - 104, 0, 0, 2, 1, 0, - 0, 0, 54, 0, 0, 6, - 18, 0, 16, 0, 0, 0, - 0, 0, 58, 128, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 54, 0, 0, 5, - 130, 0, 16, 0, 0, 0, - 0, 0, 1, 64, 0, 0, - 0, 0, 128, 63, 56, 0, - 0, 7, 242, 32, 16, 0, - 0, 0, 0, 0, 6, 12, - 16, 0, 0, 0, 0, 0, - 70, 30, 16, 0, 2, 0, - 0, 0, 62, 0, 0, 1, - 83, 84, 65, 84, 116, 0, - 0, 0, 4, 0, 0, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 2, 0, 0, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 2, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 82, 68, - 69, 70, 92, 2, 0, 0, - 1, 0, 0, 0, 72, 0, - 0, 0, 1, 0, 0, 0, - 28, 0, 0, 0, 0, 4, - 255, 255, 0, 1, 0, 0, - 49, 2, 0, 0, 60, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, - 0, 0, 1, 0, 0, 0, - 67, 111, 110, 115, 116, 97, - 110, 116, 115, 0, 171, 171, - 60, 0, 0, 0, 12, 0, - 0, 0, 96, 0, 0, 0, - 96, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 128, 1, 0, 0, 0, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 160, 1, 0, 0, 4, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 173, 1, 0, 0, 8, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 184, 1, 0, 0, 12, 0, - 0, 0, 4, 0, 0, 0, - 2, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 196, 1, 0, 0, 16, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 211, 1, 0, 0, 20, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 227, 1, 0, 0, 24, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 243, 1, 0, 0, 28, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 3, 2, 0, 0, 32, 0, - 0, 0, 16, 0, 0, 0, - 0, 0, 0, 0, 12, 2, - 0, 0, 0, 0, 0, 0, - 28, 2, 0, 0, 48, 0, - 0, 0, 16, 0, 0, 0, - 0, 0, 0, 0, 12, 2, - 0, 0, 0, 0, 0, 0, - 35, 2, 0, 0, 64, 0, - 0, 0, 16, 0, 0, 0, - 0, 0, 0, 0, 12, 2, - 0, 0, 0, 0, 0, 0, - 42, 2, 0, 0, 80, 0, - 0, 0, 16, 0, 0, 0, - 0, 0, 0, 0, 12, 2, - 0, 0, 0, 0, 0, 0, - 115, 99, 82, 71, 66, 95, - 111, 117, 116, 112, 117, 116, - 0, 171, 171, 171, 0, 0, - 3, 0, 1, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 116, 101, 120, 116, - 117, 114, 101, 95, 116, 121, - 112, 101, 0, 105, 110, 112, - 117, 116, 95, 116, 121, 112, - 101, 0, 99, 111, 108, 111, - 114, 95, 115, 99, 97, 108, - 101, 0, 116, 111, 110, 101, - 109, 97, 112, 95, 109, 101, - 116, 104, 111, 100, 0, 116, - 111, 110, 101, 109, 97, 112, - 95, 102, 97, 99, 116, 111, - 114, 49, 0, 116, 111, 110, - 101, 109, 97, 112, 95, 102, - 97, 99, 116, 111, 114, 50, - 0, 115, 100, 114, 95, 119, - 104, 105, 116, 101, 95, 112, - 111, 105, 110, 116, 0, 89, - 111, 102, 102, 115, 101, 116, - 0, 171, 1, 0, 3, 0, - 1, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 82, 99, 111, 101, 102, 102, - 0, 71, 99, 111, 101, 102, - 102, 0, 66, 99, 111, 101, - 102, 102, 0, 77, 105, 99, - 114, 111, 115, 111, 102, 116, - 32, 40, 82, 41, 32, 72, - 76, 83, 76, 32, 83, 104, - 97, 100, 101, 114, 32, 67, - 111, 109, 112, 105, 108, 101, - 114, 32, 49, 48, 46, 49, - 0, 171, 171, 171, 73, 83, - 71, 78, 108, 0, 0, 0, - 3, 0, 0, 0, 8, 0, - 0, 0, 80, 0, 0, 0, - 0, 0, 0, 0, 1, 0, - 0, 0, 3, 0, 0, 0, - 0, 0, 0, 0, 15, 0, - 0, 0, 92, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 1, 0, 0, 0, 3, 0, - 0, 0, 101, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 2, 0, 0, 0, 15, 15, - 0, 0, 83, 86, 95, 80, - 79, 83, 73, 84, 73, 79, - 78, 0, 84, 69, 88, 67, - 79, 79, 82, 68, 0, 67, - 79, 76, 79, 82, 0, 171, - 79, 83, 71, 78, 44, 0, - 0, 0, 1, 0, 0, 0, - 8, 0, 0, 0, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 3, 0, - 0, 0, 0, 0, 0, 0, - 15, 0, 0, 0, 83, 86, - 95, 84, 65, 82, 71, 69, - 84, 0, 171, 171 + 68, 88, 66, 67, 131, 2, + 46, 215, 253, 13, 129, 98, + 132, 106, 250, 166, 217, 206, + 9, 154, 1, 0, 0, 0, + 224, 4, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, + 172, 0, 0, 0, 56, 1, + 0, 0, 180, 1, 0, 0, + 56, 4, 0, 0, 172, 4, + 0, 0, 65, 111, 110, 57, + 108, 0, 0, 0, 108, 0, + 0, 0, 0, 2, 255, 255, + 60, 0, 0, 0, 48, 0, + 0, 0, 1, 0, 36, 0, + 0, 0, 48, 0, 0, 0, + 48, 0, 0, 0, 36, 0, + 0, 0, 48, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 2, + 255, 255, 31, 0, 0, 2, + 0, 0, 0, 128, 1, 0, + 15, 176, 5, 0, 0, 3, + 0, 0, 7, 128, 1, 0, + 228, 176, 0, 0, 255, 160, + 1, 0, 0, 2, 0, 0, + 8, 128, 1, 0, 255, 176, + 1, 0, 0, 2, 0, 8, + 15, 128, 0, 0, 228, 128, + 255, 255, 0, 0, 83, 72, + 68, 82, 132, 0, 0, 0, + 64, 0, 0, 0, 33, 0, + 0, 0, 89, 0, 0, 4, + 70, 142, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 98, 16, 0, 3, 242, 16, + 16, 0, 2, 0, 0, 0, + 101, 0, 0, 3, 242, 32, + 16, 0, 0, 0, 0, 0, + 104, 0, 0, 2, 1, 0, + 0, 0, 54, 0, 0, 6, + 18, 0, 16, 0, 0, 0, + 0, 0, 58, 128, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 54, 0, 0, 5, + 130, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 128, 63, 56, 0, + 0, 7, 242, 32, 16, 0, + 0, 0, 0, 0, 6, 12, + 16, 0, 0, 0, 0, 0, + 70, 30, 16, 0, 2, 0, + 0, 0, 62, 0, 0, 1, + 83, 84, 65, 84, 116, 0, + 0, 0, 4, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 2, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 82, 68, + 69, 70, 124, 2, 0, 0, + 1, 0, 0, 0, 72, 0, + 0, 0, 1, 0, 0, 0, + 28, 0, 0, 0, 0, 4, + 255, 255, 0, 1, 0, 0, + 84, 2, 0, 0, 60, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, + 67, 111, 110, 115, 116, 97, + 110, 116, 115, 0, 171, 171, + 60, 0, 0, 0, 13, 0, + 0, 0, 96, 0, 0, 0, + 112, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 152, 1, 0, 0, 0, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 184, 1, 0, 0, 4, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 197, 1, 0, 0, 8, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 208, 1, 0, 0, 12, 0, + 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 220, 1, 0, 0, 16, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 232, 1, + 0, 0, 0, 0, 0, 0, + 248, 1, 0, 0, 32, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 7, 2, 0, 0, 36, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 23, 2, 0, 0, 40, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 39, 2, 0, 0, 44, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 55, 2, 0, 0, 48, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 232, 1, + 0, 0, 0, 0, 0, 0, + 63, 2, 0, 0, 64, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 232, 1, + 0, 0, 0, 0, 0, 0, + 70, 2, 0, 0, 80, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 232, 1, + 0, 0, 0, 0, 0, 0, + 77, 2, 0, 0, 96, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 232, 1, + 0, 0, 0, 0, 0, 0, + 115, 99, 82, 71, 66, 95, + 111, 117, 116, 112, 117, 116, + 0, 171, 171, 171, 0, 0, + 3, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 116, 101, 120, 116, + 117, 114, 101, 95, 116, 121, + 112, 101, 0, 105, 110, 112, + 117, 116, 95, 116, 121, 112, + 101, 0, 99, 111, 108, 111, + 114, 95, 115, 99, 97, 108, + 101, 0, 116, 101, 120, 101, + 108, 95, 115, 105, 122, 101, + 0, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 116, 111, 110, 101, 109, 97, + 112, 95, 109, 101, 116, 104, + 111, 100, 0, 116, 111, 110, + 101, 109, 97, 112, 95, 102, + 97, 99, 116, 111, 114, 49, + 0, 116, 111, 110, 101, 109, + 97, 112, 95, 102, 97, 99, + 116, 111, 114, 50, 0, 115, + 100, 114, 95, 119, 104, 105, + 116, 101, 95, 112, 111, 105, + 110, 116, 0, 89, 111, 102, + 102, 115, 101, 116, 0, 82, + 99, 111, 101, 102, 102, 0, + 71, 99, 111, 101, 102, 102, + 0, 66, 99, 111, 101, 102, + 102, 0, 77, 105, 99, 114, + 111, 115, 111, 102, 116, 32, + 40, 82, 41, 32, 72, 76, + 83, 76, 32, 83, 104, 97, + 100, 101, 114, 32, 67, 111, + 109, 112, 105, 108, 101, 114, + 32, 49, 48, 46, 49, 0, + 73, 83, 71, 78, 108, 0, + 0, 0, 3, 0, 0, 0, + 8, 0, 0, 0, 80, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 92, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 0, 0, + 3, 0, 0, 0, 101, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 2, 0, 0, 0, + 15, 15, 0, 0, 83, 86, + 95, 80, 79, 83, 73, 84, + 73, 79, 78, 0, 84, 69, + 88, 67, 79, 79, 82, 68, + 0, 67, 79, 76, 79, 82, + 0, 171, 79, 83, 71, 78, + 44, 0, 0, 0, 1, 0, + 0, 0, 8, 0, 0, 0, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, + 0, 0, 15, 0, 0, 0, + 83, 86, 95, 84, 65, 82, + 71, 69, 84, 0, 171, 171 }; diff --git a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Common.hlsli b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Common.hlsli index b940c0c..4a4cedd 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Common.hlsli +++ b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Common.hlsli @@ -3,6 +3,7 @@ Texture2D texture0 : register(t0); Texture2D texture1 : register(t1); Texture2D texture2 : register(t2); SamplerState sampler0 : register(s0); +SamplerState sampler1 : register(s1); struct PixelShaderInput { @@ -18,9 +19,13 @@ static const float TONEMAP_CHROME = 2; static const float TEXTURETYPE_NONE = 0; static const float TEXTURETYPE_RGB = 1; -static const float TEXTURETYPE_NV12 = 2; -static const float TEXTURETYPE_NV21 = 3; -static const float TEXTURETYPE_YUV = 4; +static const float TEXTURETYPE_RGB_PIXELART = 2; +static const float TEXTURETYPE_PALETTE_NEAREST = 3; +static const float TEXTURETYPE_PALETTE_LINEAR = 4; +static const float TEXTURETYPE_PALETTE_PIXELART = 5; +static const float TEXTURETYPE_NV12 = 6; +static const float TEXTURETYPE_NV21 = 7; +static const float TEXTURETYPE_YUV = 8; static const float INPUTTYPE_UNSPECIFIED = 0; static const float INPUTTYPE_SRGB = 1; @@ -33,6 +38,7 @@ cbuffer Constants : register(b0) float texture_type; float input_type; float color_scale; + float4 texel_size; float tonemap_method; float tonemap_factor1; @@ -114,6 +120,50 @@ float3 ApplyTonemap(float3 v) return v; } +float4 SamplePaletteNearest(float2 uv) +{ + float index = texture0.Sample(sampler0, uv).r * 255; + return texture1.Sample(sampler1, float2((index + 0.5) / 256, 0.5)); +} + +// Implementation with thanks from bgolus: +// https://discussions.unity.com/t/how-to-make-data-shader-support-bilinear-trilinear/598639/8 +float4 SamplePaletteLinear(float2 uv) +{ + // scale & offset uvs to integer values at texel centers + float2 uv_texels = uv * texel_size.zw + 0.5; + + // get uvs for the center of the 4 surrounding texels by flooring + float4 uv_min_max = float4((floor(uv_texels) - 0.5) * texel_size.xy, (floor(uv_texels) + 0.5) * texel_size.xy); + + // blend factor + float2 uv_frac = frac(uv_texels); + + // sample all 4 texels + float4 texelA = SamplePaletteNearest(uv_min_max.xy); + float4 texelB = SamplePaletteNearest(uv_min_max.xw); + float4 texelC = SamplePaletteNearest(uv_min_max.zy); + float4 texelD = SamplePaletteNearest(uv_min_max.zw); + + // bilinear interpolation + return lerp(lerp(texelA, texelB, uv_frac.y), lerp(texelC, texelD, uv_frac.y), uv_frac.x); +} + +float2 GetPixelArtUV(float2 uv) +{ + // box filter size in texel units + float2 boxSize = clamp(fwidth(uv) * texel_size.zw, 1e-5, 1); + + // scale uv by texture size to get texel coordinate + float2 tx = uv * texel_size.zw - 0.5 * boxSize; + + // compute offset for pixel-sized box filter + float2 txOffset = smoothstep(1 - boxSize, 1, frac(tx)); + + // compute bilinear sample uv coordinates + return (floor(tx) + 0.5 + txOffset) * texel_size.xy; +} + float4 GetInputColor(PixelShaderInput input) { float4 rgba; @@ -122,6 +172,16 @@ float4 GetInputColor(PixelShaderInput input) rgba = 1.0; } else if (texture_type == TEXTURETYPE_RGB) { rgba = texture0.Sample(sampler0, input.tex); + } else if (texture_type == TEXTURETYPE_RGB_PIXELART) { + float2 uv = GetPixelArtUV(input.tex); + rgba = texture0.SampleGrad(sampler0, uv, ddx(input.tex), ddy(input.tex)); + } else if (texture_type == TEXTURETYPE_PALETTE_NEAREST) { + rgba = SamplePaletteNearest(input.tex); + } else if (texture_type == TEXTURETYPE_PALETTE_LINEAR) { + rgba = SamplePaletteLinear(input.tex); + } else if (texture_type == TEXTURETYPE_PALETTE_PIXELART) { + float2 uv = GetPixelArtUV(input.tex); + rgba = SamplePaletteLinear(uv); } else if (texture_type == TEXTURETYPE_NV12) { float3 yuv; yuv.x = texture0.Sample(sampler0, input.tex).r; @@ -157,7 +217,7 @@ float4 GetInputColor(PixelShaderInput input) // Error! rgba.r = 1.0; rgba.g = 0.0; - rgba.b = 0.0; + rgba.b = 1.0; rgba.a = 1.0; } return rgba; diff --git a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.h b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.h index 996cac6..7e2c7c1 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.h +++ b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.h @@ -3,7 +3,7 @@ // Generated by Microsoft (R) HLSL Shader Compiler 10.1 // // -// Buffer Definitions: +// Buffer Definitions: // // cbuffer Constants // { @@ -12,14 +12,15 @@ // float texture_type; // Offset: 4 Size: 4 [unused] // float input_type; // Offset: 8 Size: 4 [unused] // float color_scale; // Offset: 12 Size: 4 -// float tonemap_method; // Offset: 16 Size: 4 [unused] -// float tonemap_factor1; // Offset: 20 Size: 4 [unused] -// float tonemap_factor2; // Offset: 24 Size: 4 [unused] -// float sdr_white_point; // Offset: 28 Size: 4 [unused] -// float4 Yoffset; // Offset: 32 Size: 16 [unused] -// float4 Rcoeff; // Offset: 48 Size: 16 [unused] -// float4 Gcoeff; // Offset: 64 Size: 16 [unused] -// float4 Bcoeff; // Offset: 80 Size: 16 [unused] +// float4 texel_size; // Offset: 16 Size: 16 [unused] +// float tonemap_method; // Offset: 32 Size: 4 [unused] +// float tonemap_factor1; // Offset: 36 Size: 4 [unused] +// float tonemap_factor2; // Offset: 40 Size: 4 [unused] +// float sdr_white_point; // Offset: 44 Size: 4 [unused] +// float4 Yoffset; // Offset: 48 Size: 16 [unused] +// float4 Rcoeff; // Offset: 64 Size: 16 [unused] +// float4 Gcoeff; // Offset: 80 Size: 16 [unused] +// float4 Bcoeff; // Offset: 96 Size: 16 [unused] // // } // @@ -28,9 +29,9 @@ // // Name Type Format Dim HLSL Bind Count // ------------------------------ ---------- ------- ----------- -------------- ------ -// theSampler sampler NA NA s0 1 -// theTexture texture float4 2d t0 1 -// Constants cbuffer NA NA cb0 1 +// sampler0 sampler NA NA s0 1 +// texture0 texture float4 2d t0 1 +// Constants cbuffer NA NA cb0 1 // // // @@ -38,8 +39,8 @@ // // Name Index Mask Register SysValue Format Used // -------------------- ----- ------ -------- -------- ------- ------ -// SV_POSITION 0 xyzw 0 POS float -// TEXCOORD 0 xy 1 NONE float xy +// SV_POSITION 0 xyzw 0 POS float +// TEXCOORD 0 xy 1 NONE float xy // COLOR 0 xyzw 2 NONE float xyzw // // @@ -61,7 +62,7 @@ // // Target Sampler Source Sampler Source Resource // -------------- --------------- ---------------- -// s0 s0 t0 +// s0 s0 t0 // // // Level9 shader bytecode: @@ -87,246 +88,250 @@ dcl_temps 1 sample r0.xyzw, v1.xyxx, t0.xyzw, s0 mul r0.xyz, r0.xyzx, cb0[0].wwww mul o0.xyzw, r0.xyzw, v2.xyzw -ret +ret // Approximately 4 instruction slots used #endif const BYTE g_main[] = { - 68, 88, 66, 67, 8, 152, - 224, 210, 182, 254, 37, 89, - 68, 213, 13, 174, 95, 42, - 2, 11, 1, 0, 0, 0, - 132, 5, 0, 0, 6, 0, - 0, 0, 56, 0, 0, 0, - 220, 0, 0, 0, 168, 1, - 0, 0, 36, 2, 0, 0, - 220, 4, 0, 0, 80, 5, - 0, 0, 65, 111, 110, 57, - 156, 0, 0, 0, 156, 0, - 0, 0, 0, 2, 255, 255, - 104, 0, 0, 0, 52, 0, - 0, 0, 1, 0, 40, 0, - 0, 0, 52, 0, 0, 0, - 52, 0, 1, 0, 36, 0, - 0, 0, 52, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 0, 2, 255, 255, - 31, 0, 0, 2, 0, 0, - 0, 128, 0, 0, 3, 176, - 31, 0, 0, 2, 0, 0, - 0, 128, 1, 0, 15, 176, - 31, 0, 0, 2, 0, 0, - 0, 144, 0, 8, 15, 160, - 66, 0, 0, 3, 0, 0, - 15, 128, 0, 0, 228, 176, - 0, 8, 228, 160, 5, 0, - 0, 3, 0, 0, 7, 128, - 0, 0, 228, 128, 0, 0, - 255, 160, 5, 0, 0, 3, - 0, 0, 15, 128, 0, 0, - 228, 128, 1, 0, 228, 176, - 1, 0, 0, 2, 0, 8, - 15, 128, 0, 0, 228, 128, - 255, 255, 0, 0, 83, 72, - 68, 82, 196, 0, 0, 0, - 64, 0, 0, 0, 49, 0, - 0, 0, 89, 0, 0, 4, - 70, 142, 32, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 90, 0, 0, 3, 0, 96, - 16, 0, 0, 0, 0, 0, - 88, 24, 0, 4, 0, 112, - 16, 0, 0, 0, 0, 0, - 85, 85, 0, 0, 98, 16, - 0, 3, 50, 16, 16, 0, - 1, 0, 0, 0, 98, 16, - 0, 3, 242, 16, 16, 0, - 2, 0, 0, 0, 101, 0, - 0, 3, 242, 32, 16, 0, - 0, 0, 0, 0, 104, 0, - 0, 2, 1, 0, 0, 0, - 69, 0, 0, 9, 242, 0, - 16, 0, 0, 0, 0, 0, - 70, 16, 16, 0, 1, 0, - 0, 0, 70, 126, 16, 0, - 0, 0, 0, 0, 0, 96, - 16, 0, 0, 0, 0, 0, - 56, 0, 0, 8, 114, 0, - 16, 0, 0, 0, 0, 0, - 70, 2, 16, 0, 0, 0, - 0, 0, 246, 143, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 56, 0, 0, 7, - 242, 32, 16, 0, 0, 0, - 0, 0, 70, 14, 16, 0, - 0, 0, 0, 0, 70, 30, - 16, 0, 2, 0, 0, 0, - 62, 0, 0, 1, 83, 84, - 65, 84, 116, 0, 0, 0, - 4, 0, 0, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 2, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 82, 68, 69, 70, - 176, 2, 0, 0, 1, 0, - 0, 0, 156, 0, 0, 0, - 3, 0, 0, 0, 28, 0, - 0, 0, 0, 4, 255, 255, - 0, 1, 0, 0, 133, 2, - 0, 0, 124, 0, 0, 0, - 3, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 135, 0, - 0, 0, 2, 0, 0, 0, - 5, 0, 0, 0, 4, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 1, 0, - 0, 0, 13, 0, 0, 0, - 146, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 1, 0, - 0, 0, 116, 104, 101, 83, - 97, 109, 112, 108, 101, 114, - 0, 116, 104, 101, 84, 101, - 120, 116, 117, 114, 101, 0, - 67, 111, 110, 115, 116, 97, - 110, 116, 115, 0, 146, 0, - 0, 0, 12, 0, 0, 0, - 180, 0, 0, 0, 96, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 212, 1, - 0, 0, 0, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 244, 1, - 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 1, 2, - 0, 0, 8, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 12, 2, - 0, 0, 12, 0, 0, 0, - 4, 0, 0, 0, 2, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 24, 2, - 0, 0, 16, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 39, 2, - 0, 0, 20, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 55, 2, - 0, 0, 24, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 71, 2, - 0, 0, 28, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 87, 2, - 0, 0, 32, 0, 0, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 96, 2, 0, 0, - 0, 0, 0, 0, 112, 2, - 0, 0, 48, 0, 0, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 96, 2, 0, 0, - 0, 0, 0, 0, 119, 2, - 0, 0, 64, 0, 0, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 96, 2, 0, 0, - 0, 0, 0, 0, 126, 2, - 0, 0, 80, 0, 0, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 96, 2, 0, 0, - 0, 0, 0, 0, 115, 99, - 82, 71, 66, 95, 111, 117, - 116, 112, 117, 116, 0, 171, - 171, 171, 0, 0, 3, 0, - 1, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 116, 101, 120, 116, 117, 114, - 101, 95, 116, 121, 112, 101, - 0, 105, 110, 112, 117, 116, - 95, 116, 121, 112, 101, 0, - 99, 111, 108, 111, 114, 95, - 115, 99, 97, 108, 101, 0, - 116, 111, 110, 101, 109, 97, - 112, 95, 109, 101, 116, 104, - 111, 100, 0, 116, 111, 110, - 101, 109, 97, 112, 95, 102, - 97, 99, 116, 111, 114, 49, - 0, 116, 111, 110, 101, 109, - 97, 112, 95, 102, 97, 99, - 116, 111, 114, 50, 0, 115, - 100, 114, 95, 119, 104, 105, - 116, 101, 95, 112, 111, 105, - 110, 116, 0, 89, 111, 102, - 102, 115, 101, 116, 0, 171, - 1, 0, 3, 0, 1, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 82, 99, - 111, 101, 102, 102, 0, 71, - 99, 111, 101, 102, 102, 0, - 66, 99, 111, 101, 102, 102, - 0, 77, 105, 99, 114, 111, - 115, 111, 102, 116, 32, 40, - 82, 41, 32, 72, 76, 83, - 76, 32, 83, 104, 97, 100, - 101, 114, 32, 67, 111, 109, - 112, 105, 108, 101, 114, 32, - 49, 48, 46, 49, 0, 171, - 171, 171, 73, 83, 71, 78, - 108, 0, 0, 0, 3, 0, - 0, 0, 8, 0, 0, 0, - 80, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 3, 0, 0, 0, 0, 0, - 0, 0, 15, 0, 0, 0, - 92, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 1, 0, - 0, 0, 3, 3, 0, 0, - 101, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 2, 0, - 0, 0, 15, 15, 0, 0, - 83, 86, 95, 80, 79, 83, - 73, 84, 73, 79, 78, 0, - 84, 69, 88, 67, 79, 79, - 82, 68, 0, 67, 79, 76, - 79, 82, 0, 171, 79, 83, - 71, 78, 44, 0, 0, 0, - 1, 0, 0, 0, 8, 0, - 0, 0, 32, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 0, 0, 0, 0, 15, 0, - 0, 0, 83, 86, 95, 84, - 65, 82, 71, 69, 84, 0, - 171, 171 + 68, 88, 66, 67, 98, 171, + 127, 123, 23, 170, 245, 47, + 35, 199, 24, 186, 200, 109, + 190, 201, 1, 0, 0, 0, + 160, 5, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, + 220, 0, 0, 0, 168, 1, + 0, 0, 36, 2, 0, 0, + 248, 4, 0, 0, 108, 5, + 0, 0, 65, 111, 110, 57, + 156, 0, 0, 0, 156, 0, + 0, 0, 0, 2, 255, 255, + 104, 0, 0, 0, 52, 0, + 0, 0, 1, 0, 40, 0, + 0, 0, 52, 0, 0, 0, + 52, 0, 1, 0, 36, 0, + 0, 0, 52, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 255, 255, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 3, 176, + 31, 0, 0, 2, 0, 0, + 0, 128, 1, 0, 15, 176, + 31, 0, 0, 2, 0, 0, + 0, 144, 0, 8, 15, 160, + 66, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 176, + 0, 8, 228, 160, 5, 0, + 0, 3, 0, 0, 7, 128, + 0, 0, 228, 128, 0, 0, + 255, 160, 5, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 128, 1, 0, 228, 176, + 1, 0, 0, 2, 0, 8, + 15, 128, 0, 0, 228, 128, + 255, 255, 0, 0, 83, 72, + 68, 82, 196, 0, 0, 0, + 64, 0, 0, 0, 49, 0, + 0, 0, 89, 0, 0, 4, + 70, 142, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 90, 0, 0, 3, 0, 96, + 16, 0, 0, 0, 0, 0, + 88, 24, 0, 4, 0, 112, + 16, 0, 0, 0, 0, 0, + 85, 85, 0, 0, 98, 16, + 0, 3, 50, 16, 16, 0, + 1, 0, 0, 0, 98, 16, + 0, 3, 242, 16, 16, 0, + 2, 0, 0, 0, 101, 0, + 0, 3, 242, 32, 16, 0, + 0, 0, 0, 0, 104, 0, + 0, 2, 1, 0, 0, 0, + 69, 0, 0, 9, 242, 0, + 16, 0, 0, 0, 0, 0, + 70, 16, 16, 0, 1, 0, + 0, 0, 70, 126, 16, 0, + 0, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, + 56, 0, 0, 8, 114, 0, + 16, 0, 0, 0, 0, 0, + 70, 2, 16, 0, 0, 0, + 0, 0, 246, 143, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 56, 0, 0, 7, + 242, 32, 16, 0, 0, 0, + 0, 0, 70, 14, 16, 0, + 0, 0, 0, 0, 70, 30, + 16, 0, 2, 0, 0, 0, + 62, 0, 0, 1, 83, 84, + 65, 84, 116, 0, 0, 0, + 4, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 82, 68, 69, 70, + 204, 2, 0, 0, 1, 0, + 0, 0, 152, 0, 0, 0, + 3, 0, 0, 0, 28, 0, + 0, 0, 0, 4, 255, 255, + 0, 1, 0, 0, 164, 2, + 0, 0, 124, 0, 0, 0, + 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 133, 0, + 0, 0, 2, 0, 0, 0, + 5, 0, 0, 0, 4, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 1, 0, + 0, 0, 13, 0, 0, 0, + 142, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 115, 97, 109, 112, + 108, 101, 114, 48, 0, 116, + 101, 120, 116, 117, 114, 101, + 48, 0, 67, 111, 110, 115, + 116, 97, 110, 116, 115, 0, + 142, 0, 0, 0, 13, 0, + 0, 0, 176, 0, 0, 0, + 112, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 232, 1, 0, 0, 0, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 8, 2, 0, 0, 4, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 21, 2, 0, 0, 8, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 32, 2, 0, 0, 12, 0, + 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 44, 2, 0, 0, 16, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 56, 2, + 0, 0, 0, 0, 0, 0, + 72, 2, 0, 0, 32, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 87, 2, 0, 0, 36, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 103, 2, 0, 0, 40, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 119, 2, 0, 0, 44, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 135, 2, 0, 0, 48, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 56, 2, + 0, 0, 0, 0, 0, 0, + 143, 2, 0, 0, 64, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 56, 2, + 0, 0, 0, 0, 0, 0, + 150, 2, 0, 0, 80, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 56, 2, + 0, 0, 0, 0, 0, 0, + 157, 2, 0, 0, 96, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 56, 2, + 0, 0, 0, 0, 0, 0, + 115, 99, 82, 71, 66, 95, + 111, 117, 116, 112, 117, 116, + 0, 171, 171, 171, 0, 0, + 3, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 116, 101, 120, 116, + 117, 114, 101, 95, 116, 121, + 112, 101, 0, 105, 110, 112, + 117, 116, 95, 116, 121, 112, + 101, 0, 99, 111, 108, 111, + 114, 95, 115, 99, 97, 108, + 101, 0, 116, 101, 120, 101, + 108, 95, 115, 105, 122, 101, + 0, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 116, 111, 110, 101, 109, 97, + 112, 95, 109, 101, 116, 104, + 111, 100, 0, 116, 111, 110, + 101, 109, 97, 112, 95, 102, + 97, 99, 116, 111, 114, 49, + 0, 116, 111, 110, 101, 109, + 97, 112, 95, 102, 97, 99, + 116, 111, 114, 50, 0, 115, + 100, 114, 95, 119, 104, 105, + 116, 101, 95, 112, 111, 105, + 110, 116, 0, 89, 111, 102, + 102, 115, 101, 116, 0, 82, + 99, 111, 101, 102, 102, 0, + 71, 99, 111, 101, 102, 102, + 0, 66, 99, 111, 101, 102, + 102, 0, 77, 105, 99, 114, + 111, 115, 111, 102, 116, 32, + 40, 82, 41, 32, 72, 76, + 83, 76, 32, 83, 104, 97, + 100, 101, 114, 32, 67, 111, + 109, 112, 105, 108, 101, 114, + 32, 49, 48, 46, 49, 0, + 73, 83, 71, 78, 108, 0, + 0, 0, 3, 0, 0, 0, + 8, 0, 0, 0, 80, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 92, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 0, 0, + 3, 3, 0, 0, 101, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 2, 0, 0, 0, + 15, 15, 0, 0, 83, 86, + 95, 80, 79, 83, 73, 84, + 73, 79, 78, 0, 84, 69, + 88, 67, 79, 79, 82, 68, + 0, 67, 79, 76, 79, 82, + 0, 171, 79, 83, 71, 78, + 44, 0, 0, 0, 1, 0, + 0, 0, 8, 0, 0, 0, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, + 0, 0, 15, 0, 0, 0, + 83, 86, 95, 84, 65, 82, + 71, 69, 84, 0, 171, 171 }; diff --git a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.hlsl b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.hlsl index f2ecf18..9031e0f 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.hlsl +++ b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.hlsl @@ -1,9 +1,7 @@ -Texture2D theTexture : register(t0); -SamplerState theSampler : register(s0); #include "D3D11_PixelShader_Common.hlsli" float4 main(PixelShaderInput input) : SV_TARGET { - return GetOutputColor(theTexture.Sample(theSampler, input.tex)) * input.color; + return GetOutputColor(texture0.Sample(sampler0, input.tex)) * input.color; } diff --git a/libs/SDL3/src/render/direct3d11/D3D11_VertexShader.h b/libs/SDL3/src/render/direct3d11/D3D11_VertexShader.h index 97beaa3..e6aac57 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_VertexShader.h +++ b/libs/SDL3/src/render/direct3d11/D3D11_VertexShader.h @@ -3,7 +3,7 @@ // Generated by Microsoft (R) HLSL Shader Compiler 10.1 // // -// Buffer Definitions: +// Buffer Definitions: // // cbuffer VertexShaderConstants // { @@ -18,7 +18,7 @@ // // Name Type Format Dim HLSL Bind Count // ------------------------------ ---------- ------- ----------- -------------- ------ -// VertexShaderConstants cbuffer NA NA cb0 1 +// VertexShaderConstants cbuffer NA NA cb0 1 // // // @@ -26,8 +26,8 @@ // // Name Index Mask Register SysValue Format Used // -------------------- ----- ------ -------- -------- ------- ------ -// POSITION 0 xyz 0 NONE float xyz -// TEXCOORD 0 xy 1 NONE float xy +// POSITION 0 xyz 0 NONE float xyz +// TEXCOORD 0 xy 1 NONE float xy // COLOR 0 xyzw 2 NONE float xyzw // // @@ -36,7 +36,7 @@ // Name Index Mask Register SysValue Format Used // -------------------- ----- ------ -------- -------- ------- ------ // SV_POSITION 0 xyzw 0 POS float xyzw -// TEXCOORD 0 xy 1 NONE float xy +// TEXCOORD 0 xy 1 NONE float xy // COLOR 0 xyzw 2 NONE float xyzw // // @@ -93,247 +93,247 @@ mad r1.xyzw, r0.zzzz, cb0[6].xyzw, r1.xyzw mad o0.xyzw, r0.wwww, cb0[7].xyzw, r1.xyzw mov o1.xy, v1.xyxx mov o2.xyzw, v2.xyzw -ret +ret // Approximately 11 instruction slots used #endif const BYTE g_main[] = { - 68, 88, 66, 67, 152, 172, - 81, 45, 198, 200, 12, 38, - 143, 4, 178, 228, 158, 175, - 169, 64, 1, 0, 0, 0, - 140, 5, 0, 0, 6, 0, - 0, 0, 56, 0, 0, 0, - 108, 1, 0, 0, 52, 3, - 0, 0, 176, 3, 0, 0, - 168, 4, 0, 0, 24, 5, - 0, 0, 65, 111, 110, 57, - 44, 1, 0, 0, 44, 1, - 0, 0, 0, 2, 254, 255, - 248, 0, 0, 0, 52, 0, - 0, 0, 1, 0, 36, 0, - 0, 0, 48, 0, 0, 0, - 48, 0, 0, 0, 36, 0, - 1, 0, 48, 0, 0, 0, - 0, 0, 8, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 2, 254, 255, - 31, 0, 0, 2, 5, 0, - 0, 128, 0, 0, 15, 144, - 31, 0, 0, 2, 5, 0, - 1, 128, 1, 0, 15, 144, - 31, 0, 0, 2, 5, 0, - 2, 128, 2, 0, 15, 144, - 5, 0, 0, 3, 0, 0, - 15, 128, 0, 0, 85, 144, - 2, 0, 228, 160, 4, 0, - 0, 4, 0, 0, 15, 128, - 0, 0, 0, 144, 1, 0, - 228, 160, 0, 0, 228, 128, - 4, 0, 0, 4, 0, 0, - 15, 128, 0, 0, 170, 144, - 3, 0, 228, 160, 0, 0, - 228, 128, 2, 0, 0, 3, - 0, 0, 15, 128, 0, 0, - 228, 128, 4, 0, 228, 160, - 5, 0, 0, 3, 1, 0, - 15, 128, 0, 0, 85, 128, - 6, 0, 228, 160, 4, 0, - 0, 4, 1, 0, 15, 128, - 0, 0, 0, 128, 5, 0, - 228, 160, 1, 0, 228, 128, - 4, 0, 0, 4, 1, 0, - 15, 128, 0, 0, 170, 128, - 7, 0, 228, 160, 1, 0, - 228, 128, 4, 0, 0, 4, - 0, 0, 15, 128, 0, 0, - 255, 128, 8, 0, 228, 160, - 1, 0, 228, 128, 4, 0, - 0, 4, 0, 0, 3, 192, - 0, 0, 255, 128, 0, 0, - 228, 160, 0, 0, 228, 128, - 1, 0, 0, 2, 0, 0, - 12, 192, 0, 0, 228, 128, - 1, 0, 0, 2, 0, 0, - 3, 224, 1, 0, 228, 144, - 1, 0, 0, 2, 1, 0, - 15, 224, 2, 0, 228, 144, - 255, 255, 0, 0, 83, 72, - 68, 82, 192, 1, 0, 0, - 64, 0, 1, 0, 112, 0, - 0, 0, 89, 0, 0, 4, - 70, 142, 32, 0, 0, 0, - 0, 0, 8, 0, 0, 0, - 95, 0, 0, 3, 114, 16, - 16, 0, 0, 0, 0, 0, - 95, 0, 0, 3, 50, 16, - 16, 0, 1, 0, 0, 0, - 95, 0, 0, 3, 242, 16, - 16, 0, 2, 0, 0, 0, - 103, 0, 0, 4, 242, 32, - 16, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 101, 0, - 0, 3, 50, 32, 16, 0, - 1, 0, 0, 0, 101, 0, - 0, 3, 242, 32, 16, 0, - 2, 0, 0, 0, 104, 0, - 0, 2, 2, 0, 0, 0, - 56, 0, 0, 8, 242, 0, - 16, 0, 0, 0, 0, 0, - 86, 21, 16, 0, 0, 0, - 0, 0, 70, 142, 32, 0, - 0, 0, 0, 0, 1, 0, - 0, 0, 50, 0, 0, 10, - 242, 0, 16, 0, 0, 0, - 0, 0, 6, 16, 16, 0, - 0, 0, 0, 0, 70, 142, - 32, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 70, 14, - 16, 0, 0, 0, 0, 0, - 50, 0, 0, 10, 242, 0, - 16, 0, 0, 0, 0, 0, - 166, 26, 16, 0, 0, 0, - 0, 0, 70, 142, 32, 0, - 0, 0, 0, 0, 2, 0, - 0, 0, 70, 14, 16, 0, - 0, 0, 0, 0, 0, 0, - 0, 8, 242, 0, 16, 0, - 0, 0, 0, 0, 70, 14, - 16, 0, 0, 0, 0, 0, - 70, 142, 32, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 56, 0, 0, 8, 242, 0, - 16, 0, 1, 0, 0, 0, - 86, 5, 16, 0, 0, 0, - 0, 0, 70, 142, 32, 0, - 0, 0, 0, 0, 5, 0, - 0, 0, 50, 0, 0, 10, - 242, 0, 16, 0, 1, 0, - 0, 0, 6, 0, 16, 0, - 0, 0, 0, 0, 70, 142, - 32, 0, 0, 0, 0, 0, - 4, 0, 0, 0, 70, 14, - 16, 0, 1, 0, 0, 0, - 50, 0, 0, 10, 242, 0, - 16, 0, 1, 0, 0, 0, - 166, 10, 16, 0, 0, 0, - 0, 0, 70, 142, 32, 0, - 0, 0, 0, 0, 6, 0, - 0, 0, 70, 14, 16, 0, - 1, 0, 0, 0, 50, 0, - 0, 10, 242, 32, 16, 0, - 0, 0, 0, 0, 246, 15, - 16, 0, 0, 0, 0, 0, - 70, 142, 32, 0, 0, 0, - 0, 0, 7, 0, 0, 0, - 70, 14, 16, 0, 1, 0, - 0, 0, 54, 0, 0, 5, - 50, 32, 16, 0, 1, 0, - 0, 0, 70, 16, 16, 0, - 1, 0, 0, 0, 54, 0, - 0, 5, 242, 32, 16, 0, - 2, 0, 0, 0, 70, 30, - 16, 0, 2, 0, 0, 0, - 62, 0, 0, 1, 83, 84, - 65, 84, 116, 0, 0, 0, - 11, 0, 0, 0, 2, 0, - 0, 0, 0, 0, 0, 0, - 6, 0, 0, 0, 8, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 2, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 82, 68, 69, 70, - 240, 0, 0, 0, 1, 0, - 0, 0, 84, 0, 0, 0, - 1, 0, 0, 0, 28, 0, - 0, 0, 0, 4, 254, 255, - 0, 1, 0, 0, 198, 0, - 0, 0, 60, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 86, 101, - 114, 116, 101, 120, 83, 104, - 97, 100, 101, 114, 67, 111, - 110, 115, 116, 97, 110, 116, - 115, 0, 171, 171, 60, 0, - 0, 0, 2, 0, 0, 0, - 108, 0, 0, 0, 128, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 156, 0, - 0, 0, 0, 0, 0, 0, - 64, 0, 0, 0, 2, 0, - 0, 0, 164, 0, 0, 0, - 0, 0, 0, 0, 180, 0, - 0, 0, 64, 0, 0, 0, - 64, 0, 0, 0, 2, 0, - 0, 0, 164, 0, 0, 0, - 0, 0, 0, 0, 109, 111, - 100, 101, 108, 0, 171, 171, - 2, 0, 3, 0, 4, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 112, 114, - 111, 106, 101, 99, 116, 105, - 111, 110, 65, 110, 100, 86, - 105, 101, 119, 0, 77, 105, - 99, 114, 111, 115, 111, 102, - 116, 32, 40, 82, 41, 32, - 72, 76, 83, 76, 32, 83, - 104, 97, 100, 101, 114, 32, - 67, 111, 109, 112, 105, 108, - 101, 114, 32, 49, 48, 46, - 49, 0, 171, 171, 73, 83, - 71, 78, 104, 0, 0, 0, - 3, 0, 0, 0, 8, 0, - 0, 0, 80, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 0, 0, 0, 0, 7, 7, - 0, 0, 89, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 1, 0, 0, 0, 3, 3, - 0, 0, 98, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 2, 0, 0, 0, 15, 15, - 0, 0, 80, 79, 83, 73, - 84, 73, 79, 78, 0, 84, - 69, 88, 67, 79, 79, 82, - 68, 0, 67, 79, 76, 79, - 82, 0, 79, 83, 71, 78, - 108, 0, 0, 0, 3, 0, - 0, 0, 8, 0, 0, 0, - 80, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 3, 0, 0, 0, 0, 0, - 0, 0, 15, 0, 0, 0, - 92, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 1, 0, - 0, 0, 3, 12, 0, 0, - 101, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 2, 0, - 0, 0, 15, 0, 0, 0, - 83, 86, 95, 80, 79, 83, - 73, 84, 73, 79, 78, 0, - 84, 69, 88, 67, 79, 79, - 82, 68, 0, 67, 79, 76, + 68, 88, 66, 67, 152, 172, + 81, 45, 198, 200, 12, 38, + 143, 4, 178, 228, 158, 175, + 169, 64, 1, 0, 0, 0, + 140, 5, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, + 108, 1, 0, 0, 52, 3, + 0, 0, 176, 3, 0, 0, + 168, 4, 0, 0, 24, 5, + 0, 0, 65, 111, 110, 57, + 44, 1, 0, 0, 44, 1, + 0, 0, 0, 2, 254, 255, + 248, 0, 0, 0, 52, 0, + 0, 0, 1, 0, 36, 0, + 0, 0, 48, 0, 0, 0, + 48, 0, 0, 0, 36, 0, + 1, 0, 48, 0, 0, 0, + 0, 0, 8, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 254, 255, + 31, 0, 0, 2, 5, 0, + 0, 128, 0, 0, 15, 144, + 31, 0, 0, 2, 5, 0, + 1, 128, 1, 0, 15, 144, + 31, 0, 0, 2, 5, 0, + 2, 128, 2, 0, 15, 144, + 5, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 85, 144, + 2, 0, 228, 160, 4, 0, + 0, 4, 0, 0, 15, 128, + 0, 0, 0, 144, 1, 0, + 228, 160, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 15, 128, 0, 0, 170, 144, + 3, 0, 228, 160, 0, 0, + 228, 128, 2, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 128, 4, 0, 228, 160, + 5, 0, 0, 3, 1, 0, + 15, 128, 0, 0, 85, 128, + 6, 0, 228, 160, 4, 0, + 0, 4, 1, 0, 15, 128, + 0, 0, 0, 128, 5, 0, + 228, 160, 1, 0, 228, 128, + 4, 0, 0, 4, 1, 0, + 15, 128, 0, 0, 170, 128, + 7, 0, 228, 160, 1, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 15, 128, 0, 0, + 255, 128, 8, 0, 228, 160, + 1, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 3, 192, + 0, 0, 255, 128, 0, 0, + 228, 160, 0, 0, 228, 128, + 1, 0, 0, 2, 0, 0, + 12, 192, 0, 0, 228, 128, + 1, 0, 0, 2, 0, 0, + 3, 224, 1, 0, 228, 144, + 1, 0, 0, 2, 1, 0, + 15, 224, 2, 0, 228, 144, + 255, 255, 0, 0, 83, 72, + 68, 82, 192, 1, 0, 0, + 64, 0, 1, 0, 112, 0, + 0, 0, 89, 0, 0, 4, + 70, 142, 32, 0, 0, 0, + 0, 0, 8, 0, 0, 0, + 95, 0, 0, 3, 114, 16, + 16, 0, 0, 0, 0, 0, + 95, 0, 0, 3, 50, 16, + 16, 0, 1, 0, 0, 0, + 95, 0, 0, 3, 242, 16, + 16, 0, 2, 0, 0, 0, + 103, 0, 0, 4, 242, 32, + 16, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 101, 0, + 0, 3, 50, 32, 16, 0, + 1, 0, 0, 0, 101, 0, + 0, 3, 242, 32, 16, 0, + 2, 0, 0, 0, 104, 0, + 0, 2, 2, 0, 0, 0, + 56, 0, 0, 8, 242, 0, + 16, 0, 0, 0, 0, 0, + 86, 21, 16, 0, 0, 0, + 0, 0, 70, 142, 32, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 50, 0, 0, 10, + 242, 0, 16, 0, 0, 0, + 0, 0, 6, 16, 16, 0, + 0, 0, 0, 0, 70, 142, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, + 50, 0, 0, 10, 242, 0, + 16, 0, 0, 0, 0, 0, + 166, 26, 16, 0, 0, 0, + 0, 0, 70, 142, 32, 0, + 0, 0, 0, 0, 2, 0, + 0, 0, 70, 14, 16, 0, + 0, 0, 0, 0, 0, 0, + 0, 8, 242, 0, 16, 0, + 0, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, + 70, 142, 32, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 56, 0, 0, 8, 242, 0, + 16, 0, 1, 0, 0, 0, + 86, 5, 16, 0, 0, 0, + 0, 0, 70, 142, 32, 0, + 0, 0, 0, 0, 5, 0, + 0, 0, 50, 0, 0, 10, + 242, 0, 16, 0, 1, 0, + 0, 0, 6, 0, 16, 0, + 0, 0, 0, 0, 70, 142, + 32, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 70, 14, + 16, 0, 1, 0, 0, 0, + 50, 0, 0, 10, 242, 0, + 16, 0, 1, 0, 0, 0, + 166, 10, 16, 0, 0, 0, + 0, 0, 70, 142, 32, 0, + 0, 0, 0, 0, 6, 0, + 0, 0, 70, 14, 16, 0, + 1, 0, 0, 0, 50, 0, + 0, 10, 242, 32, 16, 0, + 0, 0, 0, 0, 246, 15, + 16, 0, 0, 0, 0, 0, + 70, 142, 32, 0, 0, 0, + 0, 0, 7, 0, 0, 0, + 70, 14, 16, 0, 1, 0, + 0, 0, 54, 0, 0, 5, + 50, 32, 16, 0, 1, 0, + 0, 0, 70, 16, 16, 0, + 1, 0, 0, 0, 54, 0, + 0, 5, 242, 32, 16, 0, + 2, 0, 0, 0, 70, 30, + 16, 0, 2, 0, 0, 0, + 62, 0, 0, 1, 83, 84, + 65, 84, 116, 0, 0, 0, + 11, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, + 6, 0, 0, 0, 8, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 82, 68, 69, 70, + 240, 0, 0, 0, 1, 0, + 0, 0, 84, 0, 0, 0, + 1, 0, 0, 0, 28, 0, + 0, 0, 0, 4, 254, 255, + 0, 1, 0, 0, 198, 0, + 0, 0, 60, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 86, 101, + 114, 116, 101, 120, 83, 104, + 97, 100, 101, 114, 67, 111, + 110, 115, 116, 97, 110, 116, + 115, 0, 171, 171, 60, 0, + 0, 0, 2, 0, 0, 0, + 108, 0, 0, 0, 128, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 156, 0, + 0, 0, 0, 0, 0, 0, + 64, 0, 0, 0, 2, 0, + 0, 0, 164, 0, 0, 0, + 0, 0, 0, 0, 180, 0, + 0, 0, 64, 0, 0, 0, + 64, 0, 0, 0, 2, 0, + 0, 0, 164, 0, 0, 0, + 0, 0, 0, 0, 109, 111, + 100, 101, 108, 0, 171, 171, + 2, 0, 3, 0, 4, 0, + 4, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 112, 114, + 111, 106, 101, 99, 116, 105, + 111, 110, 65, 110, 100, 86, + 105, 101, 119, 0, 77, 105, + 99, 114, 111, 115, 111, 102, + 116, 32, 40, 82, 41, 32, + 72, 76, 83, 76, 32, 83, + 104, 97, 100, 101, 114, 32, + 67, 111, 109, 112, 105, 108, + 101, 114, 32, 49, 48, 46, + 49, 0, 171, 171, 73, 83, + 71, 78, 104, 0, 0, 0, + 3, 0, 0, 0, 8, 0, + 0, 0, 80, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 7, 7, + 0, 0, 89, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 3, 3, + 0, 0, 98, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 2, 0, 0, 0, 15, 15, + 0, 0, 80, 79, 83, 73, + 84, 73, 79, 78, 0, 84, + 69, 88, 67, 79, 79, 82, + 68, 0, 67, 79, 76, 79, + 82, 0, 79, 83, 71, 78, + 108, 0, 0, 0, 3, 0, + 0, 0, 8, 0, 0, 0, + 80, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 3, 0, 0, 0, 0, 0, + 0, 0, 15, 0, 0, 0, + 92, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 1, 0, + 0, 0, 3, 12, 0, 0, + 101, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 2, 0, + 0, 0, 15, 0, 0, 0, + 83, 86, 95, 80, 79, 83, + 73, 84, 73, 79, 78, 0, + 84, 69, 88, 67, 79, 79, + 82, 68, 0, 67, 79, 76, 79, 82, 0, 171 }; diff --git a/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c b/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c index 60f646f..14d4f1e 100644 --- a/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c +++ b/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -30,17 +30,15 @@ #include "../../video/SDL_pixels_c.h" #include +#ifdef HAVE_DXGI1_5_H +#include +#else #include +#endif #include #include "SDL_shaders_d3d11.h" -#if defined(_MSC_VER) && !defined(__clang__) -#define SDL_COMPOSE_ERROR(str) __FUNCTION__ ", " str -#else -#define SDL_COMPOSE_ERROR(str) SDL_STRINGIFY_ARG(__FUNCTION__) ", " str -#endif - #define SAFE_RELEASE(X) \ if ((X)) { \ IUnknown_Release(SDL_static_cast(IUnknown *, X)); \ @@ -50,16 +48,6 @@ /* !!! FIXME: vertex buffer bandwidth could be lower; only use UV coords when !!! FIXME: textures are needed. */ -// Sampler types -typedef enum -{ - D3D11_SAMPLER_NEAREST_CLAMP, - D3D11_SAMPLER_NEAREST_WRAP, - D3D11_SAMPLER_LINEAR_CLAMP, - D3D11_SAMPLER_LINEAR_WRAP, - D3D11_SAMPLER_COUNT -} D3D11_Sampler; - // Vertex shader, common values typedef struct { @@ -68,15 +56,19 @@ typedef struct } D3D11_VertexShaderConstants; // These should mirror the definitions in D3D11_PixelShader_Common.hlsli -//static const float TONEMAP_NONE = 0; +static const float TONEMAP_NONE = 0; //static const float TONEMAP_LINEAR = 1; static const float TONEMAP_CHROME = 2; //static const float TEXTURETYPE_NONE = 0; static const float TEXTURETYPE_RGB = 1; -static const float TEXTURETYPE_NV12 = 2; -static const float TEXTURETYPE_NV21 = 3; -static const float TEXTURETYPE_YUV = 4; +static const float TEXTURETYPE_RGB_PIXELART = 2; +static const float TEXTURETYPE_PALETTE_NEAREST = 3; +static const float TEXTURETYPE_PALETTE_LINEAR = 4; +static const float TEXTURETYPE_PALETTE_PIXELART = 5; +static const float TEXTURETYPE_NV12 = 6; +static const float TEXTURETYPE_NV21 = 7; +static const float TEXTURETYPE_YUV = 8; static const float INPUTTYPE_UNSPECIFIED = 0; static const float INPUTTYPE_SRGB = 1; @@ -90,6 +82,11 @@ typedef struct float input_type; float color_scale; + float texel_width; + float texel_height; + float texture_width; + float texture_height; + float tonemap_method; float tonemap_factor1; float tonemap_factor2; @@ -112,6 +109,13 @@ typedef struct SDL_FColor color; } D3D11_VertexPositionColor; +// Per-palette data +typedef struct +{ + ID3D11Texture2D *texture; + ID3D11ShaderResourceView *resourceView; +} D3D11_PaletteData; + // Per-texture data typedef struct { @@ -122,7 +126,6 @@ typedef struct ID3D11Texture2D *stagingTexture; int lockedTexturePositionX; int lockedTexturePositionY; - D3D11_Shader shader; const float *YCbCr_matrix; #ifdef SDL_HAVE_YUV // YV12 texture support @@ -155,12 +158,12 @@ typedef struct SDL_SharedObject *hDXGIMod; SDL_SharedObject *hD3D11Mod; IDXGIFactory2 *dxgiFactory; - IDXGIAdapter *dxgiAdapter; IDXGIDebug *dxgiDebug; ID3D11Device1 *d3dDevice; ID3D11DeviceContext1 *d3dContext; IDXGISwapChain1 *swapChain; DXGI_SWAP_EFFECT swapEffect; + UINT swapChainFlags; UINT syncInterval; UINT presentFlags; ID3D11RenderTargetView *mainRenderTargetView; @@ -172,7 +175,7 @@ typedef struct ID3D11PixelShader *pixelShaders[NUM_SHADERS]; int blendModesCount; D3D11_BlendMode *blendModes; - ID3D11SamplerState *samplers[D3D11_SAMPLER_COUNT]; + ID3D11SamplerState *samplers[RENDER_SAMPLER_COUNT]; D3D_FEATURE_LEVEL featureLevel; bool pixelSizeChanged; @@ -191,8 +194,10 @@ typedef struct ID3D11BlendState *currentBlendState; D3D11_Shader currentShader; D3D11_PixelShaderState currentShaderState[NUM_SHADERS]; + int numCurrentShaderResources; ID3D11ShaderResourceView *currentShaderResource; - ID3D11SamplerState *currentSampler; + int numCurrentShaderSamplers; + ID3D11SamplerState *currentShaderSampler; bool cliprectDirty; bool currentCliprectEnabled; SDL_Rect currentCliprect; @@ -210,6 +215,9 @@ typedef struct #pragma GCC diagnostic ignored "-Wunused-const-variable" #endif +#ifdef HAVE_DXGI1_5_H +static const GUID SDL_IID_IDXGIFactory5 = { 0x7632e1f5, 0xee65, 0x4dca, { 0x87, 0xfd, 0x84, 0xcd, 0x75, 0xf8, 0x83, 0x8d } }; +#endif static const GUID SDL_IID_IDXGIFactory2 = { 0x50c83a1c, 0xe072, 0x4c48, { 0x87, 0xb0, 0x36, 0x30, 0xfa, 0x36, 0xa6, 0xd0 } }; static const GUID SDL_IID_IDXGIDevice1 = { 0x77db970f, 0x6276, 0x48ba, { 0xba, 0x28, 0x07, 0x01, 0x43, 0xb4, 0x39, 0x2c } }; static const GUID SDL_IID_ID3D11Texture2D = { 0x6f15aaf2, 0xd208, 0x4e89, { 0x9a, 0xb4, 0x48, 0x95, 0x35, 0xd3, 0x4f, 0x9c } }; @@ -224,6 +232,8 @@ static const GUID SDL_DXGI_DEBUG_ALL = { 0xe48ae283, 0xda80, 0x490b, { 0x87, 0xe #pragma GCC diagnostic pop #endif +static bool D3D11_UpdateTextureInternal(D3D11_RenderData *rendererData, ID3D11Texture2D *texture, int bpp, int x, int y, int w, int h, const void *pixels, int pitch); + SDL_PixelFormat D3D11_DXGIFormatToSDLPixelFormat(DXGI_FORMAT dxgiFormat) { switch (dxgiFormat) { @@ -267,6 +277,7 @@ static DXGI_FORMAT SDLPixelFormatToDXGITextureFormat(Uint32 format, Uint32 outpu return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB; } return DXGI_FORMAT_B8G8R8X8_UNORM; + case SDL_PIXELFORMAT_INDEX8: case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: return DXGI_FORMAT_R8_UNORM; @@ -275,6 +286,10 @@ static DXGI_FORMAT SDLPixelFormatToDXGITextureFormat(Uint32 format, Uint32 outpu return DXGI_FORMAT_NV12; case SDL_PIXELFORMAT_P010: return DXGI_FORMAT_P010; + case SDL_PIXELFORMAT_RGB565: + return DXGI_FORMAT_B5G6R5_UNORM; + case SDL_PIXELFORMAT_ARGB1555: + return DXGI_FORMAT_B5G5R5A1_UNORM; default: return DXGI_FORMAT_UNKNOWN; } @@ -366,7 +381,6 @@ static void D3D11_ReleaseAll(SDL_Renderer *renderer) SAFE_RELEASE(data->d3dContext); SAFE_RELEASE(data->d3dDevice); - SAFE_RELEASE(data->dxgiAdapter); SAFE_RELEASE(data->dxgiFactory); data->swapEffect = (DXGI_SWAP_EFFECT)0; @@ -377,8 +391,10 @@ static void D3D11_ReleaseAll(SDL_Renderer *renderer) data->currentBlendState = NULL; data->currentShader = SHADER_NONE; SDL_zero(data->currentShaderState); + data->numCurrentShaderResources = 0; data->currentShaderResource = NULL; - data->currentSampler = NULL; + data->numCurrentShaderSamplers = 0; + data->currentShaderSampler = NULL; // Check for any leaks if in debug mode if (data->dxgiDebug) { @@ -483,7 +499,7 @@ static ID3D11BlendState *D3D11_CreateBlendState(SDL_Renderer *renderer, SDL_Blen blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; result = ID3D11Device_CreateBlendState(data->d3dDevice, &blendDesc, &blendState); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateBlendState"), result); + WIN_SetErrorFromHRESULT("ID3D11Device1::CreateBlendState", result); return NULL; } @@ -503,36 +519,35 @@ static ID3D11BlendState *D3D11_CreateBlendState(SDL_Renderer *renderer, SDL_Blen // Create resources that depend on the device. static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) { - typedef HRESULT(WINAPI * PFN_CREATE_DXGI_FACTORY)(REFIID riid, void **ppFactory); - typedef HRESULT(WINAPI * PFN_CREATE_DXGI_FACTORY2)(UINT flags, REFIID riid, void **ppFactory); - PFN_CREATE_DXGI_FACTORY CreateDXGIFactoryFunc = NULL; - PFN_CREATE_DXGI_FACTORY2 CreateDXGIFactory2Func = NULL; + typedef HRESULT (WINAPI *pfnCreateDXGIFactory)(REFIID riid, void **ppFactory); + typedef HRESULT (WINAPI *pfnCreateDXGIFactory2)(UINT flags, REFIID riid, void **ppFactory); + pfnCreateDXGIFactory pCreateDXGIFactory = NULL; + pfnCreateDXGIFactory2 pCreateDXGIFactory2 = NULL; D3D11_RenderData *data = (D3D11_RenderData *)renderer->internal; - PFN_D3D11_CREATE_DEVICE D3D11CreateDeviceFunc; + PFN_D3D11_CREATE_DEVICE pD3D11CreateDevice; + IDXGIAdapter *dxgiAdapter = NULL; ID3D11Device *d3dDevice = NULL; ID3D11DeviceContext *d3dContext = NULL; IDXGIDevice1 *dxgiDevice = NULL; HRESULT result = S_OK; + D3D_DRIVER_TYPE driverType = D3D_DRIVER_TYPE_UNKNOWN; UINT creationFlags = 0; bool createDebug; +#ifdef HAVE_DXGI1_5_H + IDXGIFactory5 *dxgiFactory5 = NULL; +#endif /* This array defines the set of DirectX hardware feature levels this app will support. * Note the ordering should be preserved. * Don't forget to declare your application's minimum required feature level in its - * description. All applications are assumed to support 9.1 unless otherwise stated. + * description. All applications are assumed to support 11.0 unless otherwise stated. */ D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_1, - D3D_FEATURE_LEVEL_11_0, - D3D_FEATURE_LEVEL_10_1, - D3D_FEATURE_LEVEL_10_0, - D3D_FEATURE_LEVEL_9_3, - D3D_FEATURE_LEVEL_9_2, - D3D_FEATURE_LEVEL_9_1 + D3D_FEATURE_LEVEL_11_0 }; D3D11_BUFFER_DESC constantBufferDesc; - D3D11_SAMPLER_DESC samplerDesc; D3D11_RASTERIZER_DESC rasterDesc; // See if we need debug interfaces @@ -544,10 +559,10 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) goto done; } - CreateDXGIFactory2Func = (PFN_CREATE_DXGI_FACTORY2)SDL_LoadFunction(data->hDXGIMod, "CreateDXGIFactory2"); - if (!CreateDXGIFactory2Func) { - CreateDXGIFactoryFunc = (PFN_CREATE_DXGI_FACTORY)SDL_LoadFunction(data->hDXGIMod, "CreateDXGIFactory"); - if (!CreateDXGIFactoryFunc) { + pCreateDXGIFactory2 = (pfnCreateDXGIFactory2)SDL_LoadFunction(data->hDXGIMod, "CreateDXGIFactory2"); + if (!pCreateDXGIFactory2) { + pCreateDXGIFactory = (pfnCreateDXGIFactory)SDL_LoadFunction(data->hDXGIMod, "CreateDXGIFactory"); + if (!pCreateDXGIFactory) { result = E_FAIL; goto done; } @@ -559,8 +574,8 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) goto done; } - D3D11CreateDeviceFunc = (PFN_D3D11_CREATE_DEVICE)SDL_LoadFunction(data->hD3D11Mod, "D3D11CreateDevice"); - if (!D3D11CreateDeviceFunc) { + pD3D11CreateDevice = (PFN_D3D11_CREATE_DEVICE)SDL_LoadFunction(data->hD3D11Mod, "D3D11CreateDevice"); + if (!pD3D11CreateDevice) { result = E_FAIL; goto done; } @@ -568,24 +583,24 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) if (createDebug) { #ifdef __IDXGIInfoQueue_INTERFACE_DEFINED__ IDXGIInfoQueue *dxgiInfoQueue = NULL; - PFN_CREATE_DXGI_FACTORY2 DXGIGetDebugInterfaceFunc; + pfnCreateDXGIFactory2 pDXGIGetDebugInterface1; // If the debug hint is set, also create the DXGI factory in debug mode - DXGIGetDebugInterfaceFunc = (PFN_CREATE_DXGI_FACTORY2)SDL_LoadFunction(data->hDXGIMod, "DXGIGetDebugInterface1"); - if (!DXGIGetDebugInterfaceFunc) { + pDXGIGetDebugInterface1 = (pfnCreateDXGIFactory2)SDL_LoadFunction(data->hDXGIMod, "DXGIGetDebugInterface1"); + if (!pDXGIGetDebugInterface1) { result = E_FAIL; goto done; } - result = DXGIGetDebugInterfaceFunc(0, &SDL_IID_IDXGIDebug1, (void **)&data->dxgiDebug); + result = pDXGIGetDebugInterface1(0, &SDL_IID_IDXGIDebug1, (void **)&data->dxgiDebug); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("DXGIGetDebugInterface1"), result); + WIN_SetErrorFromHRESULT("DXGIGetDebugInterface1", result); goto done; } - result = DXGIGetDebugInterfaceFunc(0, &SDL_IID_IDXGIInfoQueue, (void **)&dxgiInfoQueue); + result = pDXGIGetDebugInterface1(0, &SDL_IID_IDXGIInfoQueue, (void **)&dxgiInfoQueue); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("DXGIGetDebugInterface1"), result); + WIN_SetErrorFromHRESULT("DXGIGetDebugInterface1", result); goto done; } @@ -596,21 +611,44 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) creationFlags = DXGI_CREATE_FACTORY_DEBUG; } - if (CreateDXGIFactory2Func) { - result = CreateDXGIFactory2Func(creationFlags, &SDL_IID_IDXGIFactory2, (void **)&data->dxgiFactory); + if (pCreateDXGIFactory2) { + result = pCreateDXGIFactory2(creationFlags, &SDL_IID_IDXGIFactory2, (void **)&data->dxgiFactory); } else { - result = CreateDXGIFactoryFunc(&SDL_IID_IDXGIFactory2, (void **)&data->dxgiFactory); + result = pCreateDXGIFactory(&SDL_IID_IDXGIFactory2, (void **)&data->dxgiFactory); } if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("CreateDXGIFactory"), result); + WIN_SetErrorFromHRESULT("CreateDXGIFactory", result); goto done; } - // FIXME: Should we use the default adapter? - result = IDXGIFactory2_EnumAdapters(data->dxgiFactory, 0, &data->dxgiAdapter); - if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("D3D11CreateDevice"), result); - goto done; +#ifdef HAVE_DXGI1_5_H + // Check for tearing support, which requires the IDXGIFactory5 interface. + data->swapChainFlags = 0; + if (!(SDL_GetWindowFlags(renderer->window) & SDL_WINDOW_TRANSPARENT)) { + result = IDXGIFactory2_QueryInterface(data->dxgiFactory, &SDL_IID_IDXGIFactory5, (void **)&dxgiFactory5); + if (SUCCEEDED(result)) { + BOOL allowTearing = FALSE; + result = IDXGIFactory5_CheckFeatureSupport(dxgiFactory5, DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing)); + if (SUCCEEDED(result) && allowTearing) { + data->swapChainFlags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + } + IDXGIFactory5_Release(dxgiFactory5); + } + } +#endif // HAVE_DXGI1_5_H + + if (SDL_GetHintBoolean(SDL_HINT_RENDER_DIRECT3D11_WARP, false)) { + driverType = D3D_DRIVER_TYPE_WARP; + dxgiAdapter = NULL; + } else { + driverType = D3D_DRIVER_TYPE_UNKNOWN; + + // FIXME: Should we use the default adapter? + result = IDXGIFactory2_EnumAdapters(data->dxgiFactory, 0, &dxgiAdapter); + if (FAILED(result)) { + WIN_SetErrorFromHRESULT("EnumAdapters", result); + goto done; + } } /* This flag adds support for surfaces with a different color channel ordering @@ -629,9 +667,9 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) } // Create the Direct3D 11 API device object and a corresponding context. - result = D3D11CreateDeviceFunc( - data->dxgiAdapter, - D3D_DRIVER_TYPE_UNKNOWN, + result = pD3D11CreateDevice( + dxgiAdapter, + driverType, NULL, creationFlags, // Set set debug and Direct2D compatibility flags. featureLevels, // List of feature levels this app can support. @@ -642,25 +680,25 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) &d3dContext // Returns the device immediate context. ); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("D3D11CreateDevice"), result); + WIN_SetErrorFromHRESULT("D3D11CreateDevice", result); goto done; } result = ID3D11Device_QueryInterface(d3dDevice, &SDL_IID_ID3D11Device1, (void **)&data->d3dDevice); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device to ID3D11Device1"), result); + WIN_SetErrorFromHRESULT("ID3D11Device to ID3D11Device1", result); goto done; } result = ID3D11DeviceContext_QueryInterface(d3dContext, &SDL_IID_ID3D11DeviceContext1, (void **)&data->d3dContext); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext to ID3D11DeviceContext1"), result); + WIN_SetErrorFromHRESULT("ID3D11DeviceContext to ID3D11DeviceContext1", result); goto done; } result = ID3D11Device_QueryInterface(d3dDevice, &SDL_IID_IDXGIDevice1, (void **)&dxgiDevice); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device to IDXGIDevice1"), result); + WIN_SetErrorFromHRESULT("ID3D11Device to IDXGIDevice1", result); goto done; } @@ -669,7 +707,7 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) */ result = IDXGIDevice1_SetMaximumFrameLatency(dxgiDevice, 1); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGIDevice1::SetMaximumFrameLatency"), result); + WIN_SetErrorFromHRESULT("IDXGIDevice1::SetMaximumFrameLatency", result); goto done; } @@ -677,31 +715,7 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) * Max texture sizes are documented on MSDN, at: * http://msdn.microsoft.com/en-us/library/windows/apps/ff476876.aspx */ - switch (data->featureLevel) { - case D3D_FEATURE_LEVEL_11_1: - case D3D_FEATURE_LEVEL_11_0: - SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 16384); - break; - - case D3D_FEATURE_LEVEL_10_1: - case D3D_FEATURE_LEVEL_10_0: - SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 8192); - break; - - case D3D_FEATURE_LEVEL_9_3: - SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 4096); - break; - - case D3D_FEATURE_LEVEL_9_2: - case D3D_FEATURE_LEVEL_9_1: - SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 2048); - break; - - default: - SDL_SetError("%s, Unexpected feature level: %d", __FUNCTION__, data->featureLevel); - result = E_FAIL; - goto done; - } + SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 16384); if (!D3D11_CreateVertexShader(data->d3dDevice, &data->vertexShader, &data->inputLayout)) { goto done; @@ -717,42 +731,10 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) NULL, &data->vertexShaderConstants); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateBuffer [vertex shader constants]"), result); + WIN_SetErrorFromHRESULT("ID3D11Device1::CreateBuffer [vertex shader constants]", result); goto done; } - // Create samplers to use when drawing textures: - static struct - { - D3D11_FILTER filter; - D3D11_TEXTURE_ADDRESS_MODE address; - } samplerParams[] = { - { D3D11_FILTER_MIN_MAG_MIP_POINT, D3D11_TEXTURE_ADDRESS_CLAMP }, - { D3D11_FILTER_MIN_MAG_MIP_POINT, D3D11_TEXTURE_ADDRESS_WRAP }, - { D3D11_FILTER_MIN_MAG_MIP_LINEAR, D3D11_TEXTURE_ADDRESS_CLAMP }, - { D3D11_FILTER_MIN_MAG_MIP_LINEAR, D3D11_TEXTURE_ADDRESS_WRAP }, - }; - SDL_COMPILE_TIME_ASSERT(samplerParams_SIZE, SDL_arraysize(samplerParams) == D3D11_SAMPLER_COUNT); - SDL_zero(samplerDesc); - samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; - samplerDesc.MipLODBias = 0.0f; - samplerDesc.MaxAnisotropy = 1; - samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; - samplerDesc.MinLOD = 0.0f; - samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; - for (int i = 0; i < SDL_arraysize(samplerParams); ++i) { - samplerDesc.Filter = samplerParams[i].filter; - samplerDesc.AddressU = samplerParams[i].address; - samplerDesc.AddressV = samplerParams[i].address; - result = ID3D11Device_CreateSamplerState(data->d3dDevice, - &samplerDesc, - &data->samplers[i]); - if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateSamplerState [nearest-pixel filter]"), result); - goto done; - } - } - // Setup Direct3D rasterizer states SDL_zero(rasterDesc); rasterDesc.AntialiasedLineEnable = FALSE; @@ -767,14 +749,14 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) rasterDesc.SlopeScaledDepthBias = 0.0f; result = ID3D11Device_CreateRasterizerState(data->d3dDevice, &rasterDesc, &data->mainRasterizer); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateRasterizerState [main rasterizer]"), result); + WIN_SetErrorFromHRESULT("ID3D11Device1::CreateRasterizerState [main rasterizer]", result); goto done; } rasterDesc.ScissorEnable = TRUE; result = ID3D11Device_CreateRasterizerState(data->d3dDevice, &rasterDesc, &data->clippedRasterizer); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateRasterizerState [clipped rasterizer]"), result); + WIN_SetErrorFromHRESULT("ID3D11Device1::CreateRasterizerState [clipped rasterizer]", result); goto done; } @@ -792,6 +774,7 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) SDL_SetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_D3D11_DEVICE_POINTER, data->d3dDevice); done: + SAFE_RELEASE(dxgiAdapter); SAFE_RELEASE(d3dDevice); SAFE_RELEASE(d3dContext); SAFE_RELEASE(dxgiDevice); @@ -907,7 +890,7 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h) } else { swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Windows Store apps must use this SwapEffect. } - swapChainDesc.Flags = 0; + swapChainDesc.Flags = data->swapChainFlags; if (coreWindow) { result = IDXGIFactory2_CreateSwapChainForCoreWindow(data->dxgiFactory, @@ -917,7 +900,7 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h) NULL, // Allow on all displays. &data->swapChain); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGIFactory2::CreateSwapChainForCoreWindow"), result); + WIN_SetErrorFromHRESULT("IDXGIFactory2::CreateSwapChainForCoreWindow", result); goto done; } } else { @@ -937,13 +920,13 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h) NULL, // Allow on all displays. &data->swapChain); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGIFactory2::CreateSwapChainForHwnd"), result); + WIN_SetErrorFromHRESULT("IDXGIFactory2::CreateSwapChainForHwnd", result); goto done; } IDXGIFactory_MakeWindowAssociation(data->dxgiFactory, hwnd, DXGI_MWA_NO_WINDOW_CHANGES); #else - SDL_SetError(__FUNCTION__ ", Unable to find something to attach a swap chain to"); + SDL_SetError(SDL_FUNCTION ", Unable to find something to attach a swap chain to"); goto done; #endif // defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) / else } @@ -968,7 +951,7 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h) (colorspace_support & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) { result = IDXGISwapChain3_SetColorSpace1(swapChain3, colorspace); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain3::SetColorSpace1"), result); + WIN_SetErrorFromHRESULT("IDXGISwapChain3::SetColorSpace1", result); goto done; } } else if (colorspace != DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709) { @@ -993,6 +976,28 @@ static void D3D11_ReleaseMainRenderTargetView(SDL_Renderer *renderer) SAFE_RELEASE(data->mainRenderTargetView); } +static void D3D11_UpdatePresentFlags(SDL_Renderer *renderer) +{ + D3D11_RenderData *data = (D3D11_RenderData *)renderer->internal; + + if (data->syncInterval > 0) { + data->presentFlags = 0; + } else { + data->presentFlags = DXGI_PRESENT_DO_NOT_WAIT; +#ifdef HAVE_DXGI1_5_H + // Present tearing requires sync interval 0, a swap chain flag, and not in exclusive fullscreen mode. + if ((data->swapChainFlags & DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING)) { + HRESULT result = S_OK; + BOOL fullscreenState = FALSE; + result = IDXGISwapChain_GetFullscreenState(data->swapChain, &fullscreenState, NULL); + if (SUCCEEDED(result) && !fullscreenState) { + data->presentFlags = DXGI_PRESENT_ALLOW_TEARING; + } + } +#endif // HAVE_DXGI1_5_H + } +} + // Initialize all resources that change when the window's size changes. static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) { @@ -1009,7 +1014,7 @@ static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) */ SDL_GetWindowSizeInPixels(renderer->window, &w, &h); data->rotation = D3D11_GetCurrentRotation(); - // SDL_Log("%s: windowSize={%d,%d}, orientation=%d", __FUNCTION__, w, h, (int)data->rotation); + // SDL_Log("%s: windowSize={%d,%d}, orientation=%d", SDL_FUNCTION, w, h, (int)data->rotation); if (D3D11_IsDisplayRotated90Degrees(data->rotation)) { int tmp = w; w = h; @@ -1022,9 +1027,9 @@ static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) 0, w, h, DXGI_FORMAT_UNKNOWN, - 0); + data->swapChainFlags); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain::ResizeBuffers"), result); + WIN_SetErrorFromHRESULT("IDXGISwapChain::ResizeBuffers", result); goto done; } } else { @@ -1034,12 +1039,14 @@ static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) } } + D3D11_UpdatePresentFlags(renderer); + // Set the proper rotation for the swap chain. if (WIN_IsWindows8OrGreater()) { if (data->swapEffect == DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL) { result = IDXGISwapChain1_SetRotation(data->swapChain, data->rotation); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain1::SetRotation"), result); + WIN_SetErrorFromHRESULT("IDXGISwapChain1::SetRotation", result); goto done; } } @@ -1050,7 +1057,7 @@ static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) &SDL_IID_ID3D11Texture2D, (void **)&backBuffer); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain::GetBuffer [back-buffer]"), result); + WIN_SetErrorFromHRESULT("IDXGISwapChain::GetBuffer [back-buffer]", result); goto done; } @@ -1060,7 +1067,7 @@ static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) NULL, &data->mainRenderTargetView); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device::CreateRenderTargetView"), result); + WIN_SetErrorFromHRESULT("ID3D11Device::CreateRenderTargetView", result); goto done; } @@ -1149,6 +1156,73 @@ static bool GetTextureProperty(SDL_PropertiesID props, const char *name, ID3D11T return true; } +static bool D3D11_CreatePalette(SDL_Renderer *renderer, SDL_TexturePalette *palette) +{ + D3D11_RenderData *data = (D3D11_RenderData *)renderer->internal; + D3D11_PaletteData *palettedata = (D3D11_PaletteData *)SDL_calloc(1, sizeof(*palettedata)); + if (!palettedata) { + return false; + } + palette->internal = palettedata; + + if (!data->d3dDevice) { + return SDL_SetError("Device lost and couldn't be recovered"); + } + + D3D11_TEXTURE2D_DESC textureDesc; + SDL_zero(textureDesc); + textureDesc.Width = 256; + textureDesc.Height = 1; + textureDesc.MipLevels = 1; + textureDesc.ArraySize = 1; + textureDesc.Format = SDLPixelFormatToDXGITextureFormat(SDL_PIXELFORMAT_RGBA32, renderer->output_colorspace); + textureDesc.SampleDesc.Count = 1; + textureDesc.SampleDesc.Quality = 0; + textureDesc.MiscFlags = 0; + textureDesc.Usage = D3D11_USAGE_DEFAULT; + textureDesc.CPUAccessFlags = 0; + textureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + HRESULT result = ID3D11Device_CreateTexture2D(data->d3dDevice, &textureDesc, NULL, &palettedata->texture); + if (FAILED(result)) { + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D", result); + } + + D3D11_SHADER_RESOURCE_VIEW_DESC resourceViewDesc; + SDL_zero(resourceViewDesc); + resourceViewDesc.Format = SDLPixelFormatToDXGIMainResourceViewFormat(SDL_PIXELFORMAT_RGBA32, renderer->output_colorspace); + resourceViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + resourceViewDesc.Texture2D.MostDetailedMip = 0; + resourceViewDesc.Texture2D.MipLevels = textureDesc.MipLevels; + result = ID3D11Device_CreateShaderResourceView(data->d3dDevice, + (ID3D11Resource *)palettedata->texture, + &resourceViewDesc, + &palettedata->resourceView); + if (FAILED(result)) { + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateShaderResourceView", result); + } + return true; +} + +static bool D3D11_UpdatePalette(SDL_Renderer *renderer, SDL_TexturePalette *palette, int ncolors, SDL_Color *colors) +{ + D3D11_RenderData *data = (D3D11_RenderData *)renderer->internal; + D3D11_PaletteData *palettedata = (D3D11_PaletteData *)palette->internal; + + return D3D11_UpdateTextureInternal(data, palettedata->texture, 4, 0, 0, ncolors, 1, colors, ncolors * sizeof(*colors)); +} + +static void D3D11_DestroyPalette(SDL_Renderer *renderer, SDL_TexturePalette *palette) +{ + D3D11_PaletteData *palettedata = (D3D11_PaletteData *)palette->internal; + + if (palettedata) { + SAFE_RELEASE(palettedata->texture); + SAFE_RELEASE(palettedata->resourceView); + SDL_free(palettedata); + } +} + static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->internal; @@ -1164,7 +1238,7 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD if (textureFormat == DXGI_FORMAT_UNKNOWN) { return SDL_SetError("%s, An unsupported SDL pixel format (0x%x) was specified", - __FUNCTION__, texture->format); + SDL_FUNCTION, texture->format); } textureData = (D3D11_TextureData *)SDL_calloc(1, sizeof(*textureData)); @@ -1193,11 +1267,6 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD } textureData->w = (int)textureDesc.Width; textureData->h = (int)textureDesc.Height; - if (SDL_COLORSPACETRANSFER(texture->colorspace) == SDL_TRANSFER_CHARACTERISTICS_SRGB) { - textureData->shader = SHADER_RGB; - } else { - textureData->shader = SHADER_ADVANCED; - } if (texture->access == SDL_TEXTUREACCESS_STREAMING) { textureDesc.Usage = D3D11_USAGE_DYNAMIC; @@ -1222,10 +1291,11 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD NULL, &textureData->mainTexture); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D", result); } } SDL_SetPointerProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_D3D11_TEXTURE_POINTER, textureData->mainTexture); + #ifdef SDL_HAVE_YUV if (texture->format == SDL_PIXELFORMAT_YV12 || texture->format == SDL_PIXELFORMAT_IYUV) { @@ -1243,7 +1313,7 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD NULL, &textureData->mainTextureU); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D", result); } } SDL_SetPointerProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_D3D11_TEXTURE_U_POINTER, textureData->mainTextureU); @@ -1257,7 +1327,7 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD NULL, &textureData->mainTextureV); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D", result); } } SDL_SetPointerProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_D3D11_TEXTURE_V_POINTER, textureData->mainTextureV); @@ -1298,8 +1368,9 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD &resourceViewDesc, &textureData->mainTextureResourceView); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateShaderResourceView"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateShaderResourceView", result); } + #ifdef SDL_HAVE_YUV if (textureData->yuv) { result = ID3D11Device_CreateShaderResourceView(rendererData->d3dDevice, @@ -1307,14 +1378,14 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD &resourceViewDesc, &textureData->mainTextureResourceViewU); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateShaderResourceView"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateShaderResourceView", result); } result = ID3D11Device_CreateShaderResourceView(rendererData->d3dDevice, (ID3D11Resource *)textureData->mainTextureV, &resourceViewDesc, &textureData->mainTextureResourceViewV); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateShaderResourceView"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateShaderResourceView", result); } } @@ -1332,7 +1403,7 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD &nvResourceViewDesc, &textureData->mainTextureResourceViewNV); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateShaderResourceView"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateShaderResourceView", result); } } #endif // SDL_HAVE_YUV @@ -1349,7 +1420,7 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD &renderTargetViewDesc, &textureData->mainTextureRenderTargetView); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateRenderTargetView"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateRenderTargetView", result); } } @@ -1410,7 +1481,7 @@ static bool D3D11_UpdateTextureInternal(D3D11_RenderData *rendererData, ID3D11Te NULL, &stagingTexture); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D [create staging texture]"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D [create staging texture]", result); } // Get a write-only pointer to data in the staging texture: @@ -1422,7 +1493,7 @@ static bool D3D11_UpdateTextureInternal(D3D11_RenderData *rendererData, ID3D11Te &textureMemory); if (FAILED(result)) { SAFE_RELEASE(stagingTexture); - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext1::Map [map staging texture]"), result); + return WIN_SetErrorFromHRESULT("ID3D11DeviceContext1::Map [map staging texture]", result); } src = (const Uint8 *)pixels; @@ -1510,22 +1581,25 @@ static bool D3D11_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, #ifdef SDL_HAVE_YUV if (textureData->nv12) { - const Uint8 *Yplane = (const Uint8 *)srcPixels; - const Uint8 *UVplane = Yplane + rect->h * srcPitch; + int UVbpp = SDL_BYTESPERPIXEL(texture->format) * 2; + int Ypitch = srcPitch; + int UVpitch = (srcPitch + (UVbpp - 1)) & ~(UVbpp - 1); + const Uint8 *plane0 = (const Uint8 *)srcPixels; + const Uint8 *plane1 = plane0 + rect->h * srcPitch; - return D3D11_UpdateTextureNV(renderer, texture, rect, Yplane, srcPitch, UVplane, srcPitch); + return D3D11_UpdateTextureNV(renderer, texture, rect, plane0, Ypitch, plane1, UVpitch); } else if (textureData->yuv) { int Ypitch = srcPitch; int UVpitch = ((Ypitch + 1) / 2); - const Uint8 *Yplane = (const Uint8 *)srcPixels; - const Uint8 *Uplane = Yplane + rect->h * Ypitch; - const Uint8 *Vplane = Uplane + ((rect->h + 1) / 2) * UVpitch; + const Uint8 *plane0 = (const Uint8 *)srcPixels; + const Uint8 *plane1 = plane0 + rect->h * Ypitch; + const Uint8 *plane2 = plane1 + ((rect->h + 1) / 2) * UVpitch; if (texture->format == SDL_PIXELFORMAT_YV12) { - return D3D11_UpdateTextureYUV(renderer, texture, rect, Yplane, Ypitch, Vplane, UVpitch, Uplane, UVpitch); + return D3D11_UpdateTextureYUV(renderer, texture, rect, plane0, Ypitch, plane2, UVpitch, plane1, UVpitch); } else { - return D3D11_UpdateTextureYUV(renderer, texture, rect, Yplane, Ypitch, Uplane, UVpitch, Vplane, UVpitch); + return D3D11_UpdateTextureYUV(renderer, texture, rect, plane0, Ypitch, plane1, UVpitch, plane2, UVpitch); } } #endif @@ -1577,6 +1651,7 @@ static bool D3D11_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture, HRESULT result; D3D11_TEXTURE2D_DESC stagingTextureDesc; D3D11_MAPPED_SUBRESOURCE textureMemory; + int bpp = SDL_BYTESPERPIXEL(texture->format); if (!textureData) { return SDL_SetError("Texture is not currently available"); @@ -1603,7 +1678,7 @@ static bool D3D11_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture, NULL, &stagingTexture); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D [create staging texture]"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D [create staging texture]", result); } // Get a write-only pointer to data in the staging texture: @@ -1615,12 +1690,12 @@ static bool D3D11_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture, &textureMemory); if (FAILED(result)) { SAFE_RELEASE(stagingTexture); - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext1::Map [map staging texture]"), result); + return WIN_SetErrorFromHRESULT("ID3D11DeviceContext1::Map [map staging texture]", result); } src = Yplane; dst = (Uint8 *)textureMemory.pData; - length = w; + length = w * bpp; if (length == (UINT)Ypitch && length == textureMemory.RowPitch) { SDL_memcpy(dst, src, (size_t)length * h); } else { @@ -1638,7 +1713,7 @@ static bool D3D11_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture, } src = UVplane; - length = w; + length = ((w + 1) / 2) * 2 * bpp; h = (h + 1) / 2; if (stagingTextureDesc.Format == DXGI_FORMAT_P010) { length = (length + 3) & ~3; @@ -1728,7 +1803,7 @@ static bool D3D11_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, NULL, &textureData->stagingTexture); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D [create staging texture]"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D [create staging texture]", result); } // Get a write-only pointer to data in the staging texture: @@ -1740,7 +1815,7 @@ static bool D3D11_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, &textureMemory); if (FAILED(result)) { SAFE_RELEASE(textureData->stagingTexture); - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext1::Map [map staging texture]"), result); + return WIN_SetErrorFromHRESULT("ID3D11DeviceContext1::Map [map staging texture]", result); } /* Make note of where the staging texture will be written to @@ -1927,7 +2002,7 @@ static bool D3D11_UpdateVertexBuffer(SDL_Renderer *renderer, 0, &mappedResource); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext1::Map [vertex buffer]"), result); + return WIN_SetErrorFromHRESULT("ID3D11DeviceContext1::Map [vertex buffer]", result); } SDL_memcpy(mappedResource.pData, vertexData, dataSizeInBytes); ID3D11DeviceContext_Unmap(rendererData->d3dContext, (ID3D11Resource *)rendererData->vertexBuffers[vbidx], 0); @@ -1953,7 +2028,7 @@ static bool D3D11_UpdateVertexBuffer(SDL_Renderer *renderer, &vertexBufferData, &rendererData->vertexBuffers[vbidx]); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateBuffer [vertex buffer]"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateBuffer [vertex buffer]", result); } rendererData->vertexBufferSizes[vbidx] = dataSizeInBytes; @@ -1990,7 +2065,7 @@ static bool D3D11_UpdateViewport(SDL_Renderer *renderer) * SDL_CreateRenderer is calling it, and will call it again later * with a non-empty viewport. */ - // SDL_Log("%s, no viewport was set!", __FUNCTION__); + // SDL_Log("%s, no viewport was set!", SDL_FUNCTION); return false; } @@ -2057,7 +2132,7 @@ static bool D3D11_UpdateViewport(SDL_Renderer *renderer) d3dviewport.Height = orientationAlignedViewport.h; d3dviewport.MinDepth = 0.0f; d3dviewport.MaxDepth = 1.0f; - // SDL_Log("%s: D3D viewport = {%f,%f,%f,%f}", __FUNCTION__, d3dviewport.TopLeftX, d3dviewport.TopLeftY, d3dviewport.Width, d3dviewport.Height); + // SDL_Log("%s: D3D viewport = {%f,%f,%f,%f}", SDL_FUNCTION, d3dviewport.TopLeftX, d3dviewport.TopLeftY, d3dviewport.Width, d3dviewport.Height); ID3D11DeviceContext_RSSetViewports(data->d3dContext, 1, &d3dviewport); data->viewportDirty = false; @@ -2088,6 +2163,22 @@ static void D3D11_SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderC D3D11_TextureData *textureData = (D3D11_TextureData *)texture->internal; switch (texture->format) { + case SDL_PIXELFORMAT_INDEX8: + switch (cmd->data.draw.texture_scale_mode) { + case SDL_SCALEMODE_NEAREST: + constants->texture_type = TEXTURETYPE_PALETTE_NEAREST; + break; + case SDL_SCALEMODE_LINEAR: + constants->texture_type = TEXTURETYPE_PALETTE_LINEAR; + break; + case SDL_SCALEMODE_PIXELART: + constants->texture_type = TEXTURETYPE_PALETTE_PIXELART; + break; + default: + SDL_assert(!"Unknown scale mode"); + break; + } + break; case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: constants->texture_type = TEXTURETYPE_YUV; @@ -2106,7 +2197,11 @@ static void D3D11_SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderC constants->input_type = INPUTTYPE_HDR10; break; default: - constants->texture_type = TEXTURETYPE_RGB; + if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART) { + constants->texture_type = TEXTURETYPE_RGB_PIXELART; + } else { + constants->texture_type = TEXTURETYPE_RGB; + } if (texture->colorspace == SDL_COLORSPACE_SRGB_LINEAR) { constants->input_type = INPUTTYPE_SCRGB; } else if (texture->colorspace == SDL_COLORSPACE_HDR10) { @@ -2118,6 +2213,15 @@ static void D3D11_SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderC break; } + if (constants->texture_type == TEXTURETYPE_PALETTE_LINEAR || + constants->texture_type == TEXTURETYPE_PALETTE_PIXELART || + constants->texture_type == TEXTURETYPE_RGB_PIXELART) { + constants->texture_width = texture->w; + constants->texture_height = texture->h; + constants->texel_width = 1.0f / constants->texture_width; + constants->texel_height = 1.0f / constants->texture_height; + } + constants->sdr_white_point = texture->SDR_white_point; if (renderer->target) { @@ -2126,7 +2230,7 @@ static void D3D11_SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderC output_headroom = renderer->HDR_headroom; } - if (texture->HDR_headroom > output_headroom) { + if (texture->HDR_headroom > output_headroom && output_headroom > 0.0f) { constants->tonemap_method = TONEMAP_CHROME; constants->tonemap_factor1 = (output_headroom / (texture->HDR_headroom * texture->HDR_headroom)); constants->tonemap_factor2 = (1.0f / output_headroom); @@ -2138,34 +2242,54 @@ static void D3D11_SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderC } } +static D3D11_Shader SelectShader(const D3D11_PixelShaderConstants *shader_constants) +{ + if (!shader_constants) { + return SHADER_SOLID; + } + + if (shader_constants->texture_type == TEXTURETYPE_RGB && + shader_constants->input_type == INPUTTYPE_UNSPECIFIED && + shader_constants->tonemap_method == TONEMAP_NONE) { + return SHADER_RGB; + } + + return SHADER_ADVANCED; +} + static bool D3D11_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, - D3D11_Shader shader, const D3D11_PixelShaderConstants *shader_constants, - const int numShaderResources, ID3D11ShaderResourceView **shaderResources, - ID3D11SamplerState *sampler, const Float4X4 *matrix) + const D3D11_PixelShaderConstants *shader_constants, + int numShaderResources, ID3D11ShaderResourceView **shaderResources, + int numShaderSamplers, ID3D11SamplerState **shaderSamplers, const Float4X4 *matrix) { D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->internal; const Float4X4 *newmatrix = matrix ? matrix : &rendererData->identity; ID3D11RasterizerState *rasterizerState; ID3D11RenderTargetView *renderTargetView = D3D11_GetCurrentRenderTargetView(renderer); - ID3D11ShaderResourceView *shaderResource; const SDL_BlendMode blendMode = cmd->data.draw.blend; ID3D11BlendState *blendState = NULL; bool updateSubresource = false; + D3D11_Shader shader = SelectShader(shader_constants); D3D11_PixelShaderState *shader_state = &rendererData->currentShaderState[shader]; D3D11_PixelShaderConstants solid_constants; - if (numShaderResources > 0) { - shaderResource = shaderResources[0]; - } else { - shaderResource = NULL; + bool shaderResourcesChanged = false; + if (numShaderResources != rendererData->numCurrentShaderResources || + (numShaderResources > 0 && shaderResources[0] != rendererData->currentShaderResource)) { + shaderResourcesChanged = true; + } + + bool shaderSamplersChanged = false; + if (numShaderSamplers != rendererData->numCurrentShaderSamplers || + (numShaderSamplers > 0 && shaderSamplers[0] != rendererData->currentShaderSampler)) { + shaderSamplersChanged = true; } // Make sure the render target isn't bound to a shader - if (shaderResource != rendererData->currentShaderResource) { + if (shaderResourcesChanged) { ID3D11ShaderResourceView *pNullResource = NULL; ID3D11DeviceContext_PSSetShaderResources(rendererData->d3dContext, 0, 1, &pNullResource); - rendererData->currentShaderResource = NULL; } if (renderTargetView != rendererData->currentRenderTargetView) { @@ -2248,7 +2372,7 @@ static bool D3D11_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand * HRESULT result = ID3D11Device_CreateBuffer(rendererData->d3dDevice, &desc, &data, &shader_state->constants); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device::CreateBuffer [create shader constants]"), result); + WIN_SetErrorFromHRESULT("ID3D11Device::CreateBuffer [create shader constants]", result); return false; } SDL_memcpy(&shader_state->shader_constants, shader_constants, sizeof(*shader_constants)); @@ -2268,13 +2392,19 @@ static bool D3D11_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand * } rendererData->currentShader = shader; } - if (shaderResource != rendererData->currentShaderResource) { + if (shaderResourcesChanged) { ID3D11DeviceContext_PSSetShaderResources(rendererData->d3dContext, 0, numShaderResources, shaderResources); - rendererData->currentShaderResource = shaderResource; + rendererData->numCurrentShaderResources = numShaderResources; + if (numShaderResources > 0) { + rendererData->currentShaderResource = shaderResources[0]; + } } - if (sampler != rendererData->currentSampler) { - ID3D11DeviceContext_PSSetSamplers(rendererData->d3dContext, 0, 1, &sampler); - rendererData->currentSampler = sampler; + if (shaderSamplersChanged) { + ID3D11DeviceContext_PSSetSamplers(rendererData->d3dContext, 0, numShaderSamplers, shaderSamplers); + rendererData->numCurrentShaderSamplers = numShaderSamplers; + if (numShaderSamplers) { + rendererData->currentShaderSampler = shaderSamplers[0]; + } } if (updateSubresource == true || SDL_memcmp(&rendererData->vertexShaderConstantsData.model, newmatrix, sizeof(*newmatrix)) != 0) { @@ -2291,12 +2421,78 @@ static bool D3D11_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand * return true; } +static ID3D11SamplerState *D3D11_GetSamplerState(D3D11_RenderData *data, SDL_PixelFormat format, SDL_ScaleMode scale_mode, SDL_TextureAddressMode address_u, SDL_TextureAddressMode address_v) +{ + Uint32 key = RENDER_SAMPLER_HASHKEY(scale_mode, address_u, address_v); + SDL_assert(key < SDL_arraysize(data->samplers)); + if (!data->samplers[key]) { + D3D11_SAMPLER_DESC samplerDesc; + SDL_zero(samplerDesc); + samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; + samplerDesc.MipLODBias = 0.0f; + samplerDesc.MaxAnisotropy = 1; + samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; + samplerDesc.MinLOD = 0.0f; + samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; + switch (scale_mode) { + case SDL_SCALEMODE_NEAREST: + samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + break; + case SDL_SCALEMODE_PIXELART: // Uses linear sampling + case SDL_SCALEMODE_LINEAR: + if (format == SDL_PIXELFORMAT_INDEX8) { + // We'll do linear sampling in the shader + samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + } else { + samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + } + break; + default: + SDL_SetError("Unknown scale mode: %d", scale_mode); + return NULL; + } + switch (address_u) { + case SDL_TEXTURE_ADDRESS_CLAMP: + samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + break; + case SDL_TEXTURE_ADDRESS_WRAP: + samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; + break; + default: + SDL_SetError("Unknown texture address mode: %d", address_u); + return NULL; + } + switch (address_v) { + case SDL_TEXTURE_ADDRESS_CLAMP: + samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + break; + case SDL_TEXTURE_ADDRESS_WRAP: + samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; + break; + default: + SDL_SetError("Unknown texture address mode: %d", address_v); + return NULL; + } + HRESULT result = ID3D11Device_CreateSamplerState(data->d3dDevice, + &samplerDesc, + &data->samplers[key]); + if (FAILED(result)) { + WIN_SetErrorFromHRESULT("ID3D11Device::CreateSamplerState", result); + return NULL; + } + } + return data->samplers[key]; +} + static bool D3D11_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const Float4X4 *matrix) { SDL_Texture *texture = cmd->data.draw.texture; D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->internal; D3D11_TextureData *textureData = (D3D11_TextureData *)texture->internal; - ID3D11SamplerState *textureSampler; + int numShaderResources = 0; + ID3D11ShaderResourceView *shaderResources[3]; + int numShaderSamplers = 0; + ID3D11SamplerState *shaderSamplers[2]; D3D11_PixelShaderConstants constants; if (!textureData) { @@ -2305,57 +2501,35 @@ static bool D3D11_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand * D3D11_SetupShaderConstants(renderer, cmd, texture, &constants); - 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]; - break; - case SDL_TEXTURE_ADDRESS_WRAP: - textureSampler = rendererData->samplers[D3D11_SAMPLER_NEAREST_WRAP]; - break; - default: - return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode); - } - break; - case SDL_SCALEMODE_LINEAR: - switch (cmd->data.draw.texture_address_mode) { - case SDL_TEXTURE_ADDRESS_CLAMP: - textureSampler = rendererData->samplers[D3D11_SAMPLER_LINEAR_CLAMP]; - break; - case SDL_TEXTURE_ADDRESS_WRAP: - textureSampler = rendererData->samplers[D3D11_SAMPLER_LINEAR_WRAP]; - break; - default: - return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode); - } - break; - default: - return SDL_SetError("Unknown scale mode: %d", cmd->data.draw.texture_scale_mode); + shaderResources[numShaderResources++] = textureData->mainTextureResourceView; + + shaderSamplers[numShaderSamplers] = D3D11_GetSamplerState(rendererData, texture->format, cmd->data.draw.texture_scale_mode, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v); + if (!shaderSamplers[numShaderSamplers]) { + return false; } + ++numShaderSamplers; + + if (texture->palette) { + D3D11_PaletteData *palette = (D3D11_PaletteData *)texture->palette->internal; + + shaderResources[numShaderResources++] = palette->resourceView; + + shaderSamplers[numShaderSamplers] = D3D11_GetSamplerState(rendererData, SDL_PIXELFORMAT_UNKNOWN, SDL_SCALEMODE_NEAREST, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP); + if (!shaderSamplers[numShaderSamplers]) { + return false; + } + ++numShaderSamplers; + } + #ifdef SDL_HAVE_YUV if (textureData->yuv) { - ID3D11ShaderResourceView *shaderResources[3]; - - shaderResources[0] = textureData->mainTextureResourceView; - shaderResources[1] = textureData->mainTextureResourceViewU; - shaderResources[2] = textureData->mainTextureResourceViewV; - - return D3D11_SetDrawState(renderer, cmd, textureData->shader, &constants, - SDL_arraysize(shaderResources), shaderResources, textureSampler, matrix); - + shaderResources[numShaderResources++] = textureData->mainTextureResourceViewU; + shaderResources[numShaderResources++] = textureData->mainTextureResourceViewV; } else if (textureData->nv12) { - ID3D11ShaderResourceView *shaderResources[2]; - - shaderResources[0] = textureData->mainTextureResourceView; - shaderResources[1] = textureData->mainTextureResourceViewNV; - - return D3D11_SetDrawState(renderer, cmd, textureData->shader, &constants, - SDL_arraysize(shaderResources), shaderResources, textureSampler, matrix); + shaderResources[numShaderResources++] = textureData->mainTextureResourceViewNV; } #endif // SDL_HAVE_YUV - return D3D11_SetDrawState(renderer, cmd, textureData->shader, &constants, - 1, &textureData->mainTextureResourceView, textureSampler, matrix); + return D3D11_SetDrawState(renderer, cmd, &constants, numShaderResources, shaderResources, numShaderSamplers, shaderSamplers, matrix); } static void D3D11_DrawPrimitives(SDL_Renderer *renderer, D3D11_PRIMITIVE_TOPOLOGY primitiveTopology, const size_t vertexStart, const size_t vertexCount) @@ -2372,8 +2546,10 @@ static void D3D11_InvalidateCachedState(SDL_Renderer *renderer) data->currentRasterizerState = NULL; data->currentBlendState = NULL; data->currentShader = SHADER_NONE; + data->numCurrentShaderResources = 0; data->currentShaderResource = NULL; - data->currentSampler = NULL; + data->numCurrentShaderSamplers = 0; + data->currentShaderSampler = NULL; data->cliprectDirty = true; data->viewportDirty = true; } @@ -2447,26 +2623,60 @@ static bool D3D11_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd break; } - case SDL_RENDERCMD_DRAW_POINTS: - { - const size_t count = cmd->data.draw.count; - const size_t first = cmd->data.draw.first; - const size_t start = first / sizeof(D3D11_VertexPositionColor); - D3D11_SetDrawState(renderer, cmd, SHADER_SOLID, NULL, 0, NULL, NULL, NULL); - D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start, count); - break; - } - case SDL_RENDERCMD_DRAW_LINES: { - const size_t count = cmd->data.draw.count; + size_t count = cmd->data.draw.count; const size_t first = cmd->data.draw.first; const size_t start = first / sizeof(D3D11_VertexPositionColor); const D3D11_VertexPositionColor *verts = (D3D11_VertexPositionColor *)(((Uint8 *)vertices) + first); - D3D11_SetDrawState(renderer, cmd, SHADER_SOLID, NULL, 0, NULL, NULL, NULL); - D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP, start, count); - if (verts[0].pos.x != verts[count - 1].pos.x || verts[0].pos.y != verts[count - 1].pos.y) { - D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start + (count - 1), 1); + + D3D11_SetDrawState(renderer, cmd, NULL, 0, NULL, 0, NULL, NULL); + + // Add the final point in the line + size_t line_start = 0; + size_t line_end = line_start + count - 1; + if (verts[line_start].pos.x != verts[line_end].pos.x || verts[line_start].pos.y != verts[line_end].pos.y) { + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start + line_end, 1); + } + + if (count > 2) { + // joined lines cannot be grouped + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP, start, count); + } else { + // let's group non joined lines + SDL_RenderCommand *finalcmd = cmd; + SDL_RenderCommand *nextcmd; + float thiscolorscale = cmd->data.draw.color_scale; + SDL_BlendMode thisblend = cmd->data.draw.blend; + + for (nextcmd = cmd->next; nextcmd; nextcmd = nextcmd->next) { + const SDL_RenderCommandType nextcmdtype = nextcmd->command; + if (nextcmdtype != SDL_RENDERCMD_DRAW_LINES) { + if (nextcmdtype == SDL_RENDERCMD_SETDRAWCOLOR) { + // The vertex data has the draw color built in, ignore this + continue; + } + break; // can't go any further on this draw call, different render command up next. + } else if (nextcmd->data.draw.count != 2) { + break; // can't go any further on this draw call, those are joined lines + } else if (nextcmd->data.draw.blend != thisblend || + nextcmd->data.draw.color_scale != thiscolorscale) { + break; // can't go any further on this draw call, different blendmode copy up next. + } else { + finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command. + + // Add the final point in the line + line_start = count; + line_end = line_start + nextcmd->data.draw.count - 1; + if (verts[line_start].pos.x != verts[line_end].pos.x || verts[line_start].pos.y != verts[line_end].pos.y) { + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start + line_end, 1); + } + count += nextcmd->data.draw.count; + } + } + + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_LINELIST, start, count); + cmd = finalcmd; // skip any copy commands we just combined in here. } break; } @@ -2480,20 +2690,56 @@ static bool D3D11_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd case SDL_RENDERCMD_COPY_EX: // unused break; + case SDL_RENDERCMD_DRAW_POINTS: case SDL_RENDERCMD_GEOMETRY: { - SDL_Texture *texture = cmd->data.draw.texture; - const size_t count = cmd->data.draw.count; + /* as long as we have the same copy command in a row, with the + same texture, we can combine them all into a single draw call. */ + float thiscolorscale = cmd->data.draw.color_scale; + 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_u = cmd->data.draw.texture_address_mode_u; + SDL_TextureAddressMode thisaddressmode_v = cmd->data.draw.texture_address_mode_v; + const SDL_RenderCommandType thiscmdtype = cmd->command; + SDL_RenderCommand *finalcmd = cmd; + SDL_RenderCommand *nextcmd; + size_t count = cmd->data.draw.count; const size_t first = cmd->data.draw.first; const size_t start = first / sizeof(D3D11_VertexPositionColor); - - if (texture) { - D3D11_SetCopyState(renderer, cmd, NULL); - } else { - D3D11_SetDrawState(renderer, cmd, SHADER_SOLID, NULL, 0, NULL, NULL, NULL); + for (nextcmd = cmd->next; nextcmd; nextcmd = nextcmd->next) { + const SDL_RenderCommandType nextcmdtype = nextcmd->command; + if (nextcmdtype != thiscmdtype) { + if (nextcmdtype == SDL_RENDERCMD_SETDRAWCOLOR) { + // The vertex data has the draw color built in, ignore this + continue; + } + 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.texture_scale_mode != thisscalemode || + nextcmd->data.draw.texture_address_mode_u != thisaddressmode_u || + nextcmd->data.draw.texture_address_mode_v != thisaddressmode_v || + nextcmd->data.draw.blend != thisblend || + nextcmd->data.draw.color_scale != thiscolorscale) { + 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. + count += nextcmd->data.draw.count; + } } - D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST, start, count); + if (thistexture) { + D3D11_SetCopyState(renderer, cmd, NULL); + } else { + D3D11_SetDrawState(renderer, cmd, NULL, 0, NULL, 0, NULL, NULL); + } + + if (thiscmdtype == SDL_RENDERCMD_GEOMETRY) { + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST, start, count); + } else { + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start, count); + } + cmd = finalcmd; // skip any copy commands we just combined in here. break; } @@ -2522,13 +2768,13 @@ static SDL_Surface *D3D11_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rec renderTargetView = D3D11_GetCurrentRenderTargetView(renderer); if (!renderTargetView) { - SDL_SetError("%s, ID3D11DeviceContext::OMGetRenderTargets failed", __FUNCTION__); + SDL_SetError("%s, ID3D11DeviceContext::OMGetRenderTargets failed", SDL_FUNCTION); goto done; } ID3D11View_GetResource(renderTargetView, (ID3D11Resource **)&backBuffer); if (!backBuffer) { - SDL_SetError("%s, ID3D11View::GetResource failed", __FUNCTION__); + SDL_SetError("%s, ID3D11View::GetResource failed", SDL_FUNCTION); goto done; } @@ -2545,7 +2791,7 @@ static SDL_Surface *D3D11_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rec NULL, &stagingTexture); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D [create staging texture]"), result); + WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D [create staging texture]", result); goto done; } @@ -2577,7 +2823,7 @@ static SDL_Surface *D3D11_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rec 0, &textureMemory); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext1::Map [map staging texture]"), result); + WIN_SetErrorFromHRESULT("ID3D11DeviceContext1::Map [map staging texture]", result); goto done; } @@ -2616,12 +2862,6 @@ static bool D3D11_RenderPresent(SDL_Renderer *renderer) */ result = IDXGISwapChain1_Present1(data->swapChain, data->syncInterval, data->presentFlags, ¶meters); - /* Discard the contents of the render target. - * This is a valid operation only when the existing contents will be entirely - * overwritten. If dirty or scroll rects are used, this call should be removed. - */ - ID3D11DeviceContext1_DiscardView(data->d3dContext, (ID3D11View *)data->mainRenderTargetView); - // When the present flips, it unbinds the current view, so bind it again on the next draw call data->currentRenderTargetView = NULL; @@ -2638,9 +2878,9 @@ static bool D3D11_RenderPresent(SDL_Renderer *renderer) } else if (result == DXGI_ERROR_INVALID_CALL) { // We probably went through a fullscreen <-> windowed transition D3D11_CreateWindowSizeDependentResources(renderer); - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain::Present"), result); + WIN_SetErrorFromHRESULT("IDXGISwapChain::Present", result); } else { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain::Present"), result); + WIN_SetErrorFromHRESULT("IDXGISwapChain::Present", result); } return false; } @@ -2657,11 +2897,10 @@ static bool D3D11_SetVSync(SDL_Renderer *renderer, const int vsync) if (vsync > 0) { data->syncInterval = vsync; - data->presentFlags = 0; } else { data->syncInterval = 0; - data->presentFlags = DXGI_PRESENT_DO_NOT_WAIT; } + D3D11_UpdatePresentFlags(renderer); return true; } @@ -2691,6 +2930,9 @@ static bool D3D11_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL renderer->WindowEvent = D3D11_WindowEvent; renderer->SupportsBlendMode = D3D11_SupportsBlendMode; + renderer->CreatePalette = D3D11_CreatePalette; + renderer->UpdatePalette = D3D11_UpdatePalette; + renderer->DestroyPalette = D3D11_DestroyPalette; renderer->CreateTexture = D3D11_CreateTexture; renderer->UpdateTexture = D3D11_UpdateTexture; #ifdef SDL_HAVE_YUV @@ -2721,12 +2963,14 @@ static bool D3D11_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XRGB8888); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR2101010); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA64_FLOAT); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_INDEX8); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_YV12); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_IYUV); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV12); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV21); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_P010); + data->swapChainFlags = 0; data->syncInterval = 0; data->presentFlags = DXGI_PRESENT_DO_NOT_WAIT; @@ -2743,6 +2987,12 @@ static bool D3D11_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL return false; } + // DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_B5G5R5A1_UNORM is supported since Direct3D 11.1 on Windows 8 and later + if (data->featureLevel >= D3D_FEATURE_LEVEL_11_1 && WIN_IsWindows8OrGreater()) { + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGB565); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB1555); + } + return true; } diff --git a/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.c b/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.c index 18965e2..d360d64 100644 --- a/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.c +++ b/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -28,8 +28,6 @@ #include "SDL_shaders_d3d11.h" -#define SDL_COMPOSE_ERROR(str) SDL_STRINGIFY_ARG(__FUNCTION__) ", " str - #if SDL_WINAPI_FAMILY_PHONE #error Need to build shaders with level_9_3 #endif @@ -82,7 +80,7 @@ bool D3D11_CreateVertexShader(ID3D11Device1 *d3dDevice, ID3D11VertexShader **ver NULL, vertexShader); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateVertexShader"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateVertexShader", result); } // Create an input layout for SDL's vertex shader: @@ -93,7 +91,7 @@ bool D3D11_CreateVertexShader(ID3D11Device1 *d3dDevice, ID3D11VertexShader **ver sizeof(D3D11_VertexShader), inputLayout); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateInputLayout"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateInputLayout", result); } return true; } @@ -108,7 +106,7 @@ bool D3D11_CreatePixelShader(ID3D11Device1 *d3dDevice, D3D11_Shader shader, ID3D NULL, pixelShader); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreatePixelShader"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreatePixelShader", result); } return true; } diff --git a/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.h b/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.h index 2279fb0..7c38925 100644 --- a/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.h +++ b/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 diff --git a/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h b/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h index b8f40fe..812a3a5 100644 --- a/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h +++ b/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h @@ -4,8 +4,8 @@ ; ; Name Index Mask Register SysValue Format Used ; -------------------- ----- ------ -------- -------- ------- ------ -; SV_Position 0 xyzw 0 POS float -; TEXCOORD 0 xy 1 NONE float xy +; SV_Position 0 xyzw 0 POS float +; TEXCOORD 0 xy 1 NONE float xy ; COLOR 0 xyzw 2 NONE float xyzw ; ; @@ -15,9 +15,9 @@ ; -------------------- ----- ------ -------- -------- ------- ------ ; SV_Target 0 xyzw 0 TARGET float xyzw ; -; shader hash: f6874446eaee41c102142c02d5bb3ab7 +; shader hash: f246f76536638c165bb49487d071f8f4 ; -; Pipeline Runtime Information: +; Pipeline Runtime Information: ; ; Pixel Shader ; DepthOutput=0 @@ -28,15 +28,15 @@ ; ; Name Index InterpMode DynIdx ; -------------------- ----- ---------------------- ------ -; SV_Position 0 noperspective -; TEXCOORD 0 linear -; COLOR 0 linear +; SV_Position 0 noperspective +; TEXCOORD 0 linear +; COLOR 0 linear ; ; Output signature: ; ; Name Index InterpMode DynIdx ; -------------------- ----- ---------------------- ------ -; SV_Target 0 +; SV_Target 0 ; ; Buffer Definitions: ; @@ -50,16 +50,17 @@ ; float texture_type; ; Offset: 4 ; float input_type; ; Offset: 8 ; float color_scale; ; Offset: 12 -; float tonemap_method; ; Offset: 16 -; float tonemap_factor1; ; Offset: 20 -; float tonemap_factor2; ; Offset: 24 -; float sdr_white_point; ; Offset: 28 -; float4 Yoffset; ; Offset: 32 -; float4 Rcoeff; ; Offset: 48 -; float4 Gcoeff; ; Offset: 64 -; float4 Bcoeff; ; Offset: 80 -; -; } Constants; ; Offset: 0 Size: 96 +; float4 texel_size; ; Offset: 16 +; float tonemap_method; ; Offset: 32 +; float tonemap_factor1; ; Offset: 36 +; float tonemap_factor2; ; Offset: 40 +; float sdr_white_point; ; Offset: 44 +; float4 Yoffset; ; Offset: 48 +; float4 Rcoeff; ; Offset: 64 +; float4 Gcoeff; ; Offset: 80 +; float4 Bcoeff; ; Offset: 96 +; +; } Constants; ; Offset: 0 Size: 112 ; ; } ; @@ -70,6 +71,7 @@ ; ------------------------------ ---------- ------- ----------- ------- -------------- ------ ; Constants cbuffer NA NA CB0 cb1 1 ; sampler0 sampler NA NA S0 s0 1 +; sampler1 sampler NA NA S1 s1 1 ; texture0 texture f32 2d T0 t0 1 ; texture1 texture f32 2d T1 t1 1 ; texture2 texture f32 2d T2 t2 1 @@ -93,526 +95,856 @@ target triple = "dxil-ms-dx" %dx.types.ResRet.f32 = type { float, float, float, float, i32 } %"class.Texture2D >" = type { <4 x float>, %"class.Texture2D >::mips_type" } %"class.Texture2D >::mips_type" = type { i32 } -%Constants = type { float, float, float, float, float, float, float, float, <4 x float>, <4 x float>, <4 x float>, <4 x float> } +%Constants = type { float, float, float, float, <4 x float>, float, float, float, float, <4 x float>, <4 x float>, <4 x float>, <4 x float> } %struct.SamplerState = type { i32 } define void @main() { %1 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 0, i32 2, i32 2, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) %2 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 0, i32 1, i32 1, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) %3 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 0, i32 0, i32 0, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) - %4 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 3, i32 0, i32 0, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) - %5 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 2, i32 0, i32 1, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) - %6 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 0, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %7 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 1, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %8 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 2, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %9 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 3, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %10 = call float @dx.op.loadInput.f32(i32 4, i32 1, i32 0, i8 0, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %11 = call float @dx.op.loadInput.f32(i32 4, i32 1, i32 0, i8 1, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %12 = call %dx.types.CBufRet.f32 @dx.op.cbufferLoadLegacy.f32(i32 59, %dx.types.Handle %5, i32 0) ; CBufferLoadLegacy(handle,regIndex) - %13 = extractvalue %dx.types.CBufRet.f32 %12, 1 - %14 = fcmp fast oeq float %13, 0.000000e+00 - br i1 %14, label %114, label %15 + %4 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 3, i32 1, i32 1, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) + %5 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 3, i32 0, i32 0, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) + %6 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 2, i32 0, i32 1, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) + %7 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 0, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) + %8 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 1, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) + %9 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 2, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) + %10 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 3, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) + %11 = call float @dx.op.loadInput.f32(i32 4, i32 1, i32 0, i8 0, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) + %12 = call float @dx.op.loadInput.f32(i32 4, i32 1, i32 0, i8 1, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) + %13 = call %dx.types.CBufRet.f32 @dx.op.cbufferLoadLegacy.f32(i32 59, %dx.types.Handle %6, i32 0) ; CBufferLoadLegacy(handle,regIndex) + %14 = extractvalue %dx.types.CBufRet.f32 %13, 1 + %15 = fcmp fast oeq float %14, 0.000000e+00 + br i1 %15, label %428, label %16 -;