diff --git a/.github/actions/setup-ngage-sdk/action.yml b/.github/actions/setup-ngage-sdk/action.yml new file mode 100644 index 00000000..fa83418b --- /dev/null +++ b/.github/actions/setup-ngage-sdk/action.yml @@ -0,0 +1,102 @@ +name: 'Setup Nonka N-Gage SDK' +description: 'Download and setup Nokia N-Gage SDK' +inputs: + path: + description: 'Installation path' + default: 'default' +runs: + using: 'composite' + steps: + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: 'Verify platform' + id: calc + shell: sh + run: | + case "${{ runner.os }}-${{ runner.arch }}" in + "Windows-X86" | "Windows-X64") + echo "ok!" + echo "cache-key=ngage-sdk-windows" >> ${GITHUB_OUTPUT} + default_install_path="C:/ngagesdk" + ;; + *) + echo "Unsupported ${{ runner.os }}-${{ runner.arch }}" + exit 1; + ;; + esac + install_path="${{ inputs.path }}" + if [ "x$install_path" = "xdefault" ]; then + install_path="$default_install_path" + fi + echo "install-path=$install_path" >> ${GITHUB_OUTPUT} + + toolchain_repo="https://github.com/ngagesdk/ngage-toolchain" + toolchain_branch="main" + echo "toolchain-repo=${toolchain_repo}" >> ${GITHUB_OUTPUT} + echo "toolchain-branch=${toolchain_branch}" >> ${GITHUB_OUTPUT} + + sdk_repo="https://github.com/ngagesdk/sdk" + sdk_branch="main" + echo "sdk-repo=${sdk_repo}" >> ${GITHUB_OUTPUT} + echo "sdk-branch=${sdk_branch}" >> ${GITHUB_OUTPUT} + + tools_repo="https://github.com/ngagesdk/tools" + tools_branch="main" + echo "tools-repo=${tools_repo}" >> ${GITHUB_OUTPUT} + echo "tools-branch=${tools_branch}" >> ${GITHUB_OUTPUT} + + extras_repo="https://github.com/ngagesdk/extras" + extras_branch="main" + echo "extras-repo=${extras_repo}" >> ${GITHUB_OUTPUT} + echo "extras-branch=${extras_branch}" >> ${GITHUB_OUTPUT} +# - name: 'Restore cached ${{ steps.calc.outputs.archive }}' +# id: cache-restore +# uses: actions/cache/restore@v4 +# with: +# path: '${{ runner.temp }}' +# key: ${{ steps.calc.outputs.cache-key }} + - name: 'Download N-Gage SDK' +# if: ${{ !steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false' }} + shell: pwsh + run: | + + Invoke-WebRequest "${{ steps.calc.outputs.toolchain-repo }}/archive/refs/heads/${{ steps.calc.outputs.toolchain-branch }}.zip" -OutFile "${{ runner.temp }}/ngage-toolchain.zip" + Invoke-WebRequest "${{ steps.calc.outputs.sdk-repo }}/archive/refs/heads/${{ steps.calc.outputs.sdk-branch }}.zip" -OutFile "${{ runner.temp }}/sdk.zip" + Invoke-WebRequest "${{ steps.calc.outputs.tools-repo }}/archive/refs/heads/${{ steps.calc.outputs.tools-branch }}.zip" -OutFile "${{ runner.temp }}/tools.zip" + Invoke-WebRequest "${{ steps.calc.outputs.extras-repo }}/archive/refs/heads/${{ steps.calc.outputs.extras-branch }}.zip" -OutFile "${{ runner.temp }}/extras.zip" + +# - name: 'Cache ${{ steps.calc.outputs.archive }}' +# if: ${{ !steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false' }} +# uses: actions/cache/save@v4 +# with: +# path: | +# ${{ runner.temp }}/apps.zip +# ${{ runner.temp }}/sdk.zip +# ${{ runner.temp }}/tools.zip +# key: ${{ steps.calc.outputs.cache-key }} + - name: 'Extract N-Gage SDK' + shell: pwsh + run: | + New-Item -ItemType Directory -Path "${{ steps.calc.outputs.install-path }}" -Force + + New-Item -ItemType Directory -Path "${{ runner.temp }}/ngage-toolchain-temp" -Force + 7z "-o${{ runner.temp }}/ngage-toolchain-temp" x "${{ runner.temp }}/ngage-toolchain.zip" + Move-Item -Path "${{ runner.temp }}/ngage-toolchain-temp/ngage-toolchain-${{ steps.calc.outputs.toolchain-branch }}/*" -Destination "${{ steps.calc.outputs.install-path }}" + + 7z "-o${{ steps.calc.outputs.install-path }}/sdk" x "${{ runner.temp }}/sdk.zip" + Move-Item -Path "${{ steps.calc.outputs.install-path }}/sdk/sdk-${{ steps.calc.outputs.sdk-branch }}" -Destination "${{ steps.calc.outputs.install-path }}/sdk/sdk" + + 7z "-o${{ steps.calc.outputs.install-path }}/sdk" x "${{ runner.temp }}/tools.zip" + Move-Item -Path "${{ steps.calc.outputs.install-path }}/sdk/tools-${{ steps.calc.outputs.tools-branch }}" -Destination "${{ steps.calc.outputs.install-path }}/sdk/tools" + + 7z "-o${{ steps.calc.outputs.install-path }}/sdk" x "${{ runner.temp }}/extras.zip" + Move-Item -Path "${{ steps.calc.outputs.install-path }}/sdk/extras-${{ steps.calc.outputs.extras-branch }}" -Destination "${{ steps.calc.outputs.install-path }}/sdk/extras" + - name: 'Set output variables' + id: final + shell: sh + run: | + echo "${{ steps.calc.outputs.install-path }}/sdk/sdk/6.1/Shared/EPOC32/gcc/bin" >> $GITHUB_PATH + echo "${{ steps.calc.outputs.install-path }}/sdk/sdk/6.1/Shared/EPOC32/ngagesdk/bin" >> $GITHUB_PATH + echo "NGAGESDK=${{ steps.calc.outputs.install-path }}" >> $GITHUB_ENV + echo "CMAKE_TOOLCHAIN_FILE=${{ steps.calc.outputs.install-path }}/cmake/ngage-toolchain.cmake" >> $GITHUB_ENV diff --git a/.github/workflows/create-test-plan.py b/.github/workflows/create-test-plan.py index 8048e2bc..219a4de1 100755 --- a/.github/workflows/create-test-plan.py +++ b/.github/workflows/create-test-plan.py @@ -54,6 +54,7 @@ class SdlPlatform(Enum): Riscos = "riscos" FreeBSD = "freebsd" NetBSD = "netbsd" + NGage = "ngage" class Msys2Platform(Enum): @@ -113,7 +114,8 @@ JOB_SPECS = { "msvc-gdk-x64": JobSpec(name="GDK (MSVC, x64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-VC-GDK", msvc_arch=MsvcArch.X64, msvc_project="VisualC-GDK/SDL.sln", gdk=True, no_cmake=True, ), "ubuntu-22.04": JobSpec(name="Ubuntu 22.04", os=JobOs.Ubuntu22_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu22.04", ), "ubuntu-24.04-arm64": JobSpec(name="Ubuntu 24.04 (ARM64)", os=JobOs.Ubuntu24_04_arm, platform=SdlPlatform.Linux, artifact="SDL-ubuntu24.04-arm64", ), - "steamrt-sniper": JobSpec(name="Steam Linux Runtime (Sniper)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Linux, artifact="SDL-slrsniper", container="registry.gitlab.steamos.cloud/steamrt/sniper/sdk:beta", ), + "steamrt3": JobSpec(name="Steam Linux Runtime 3.0 (x86_64)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Linux, artifact="SDL-steamrt3", container="registry.gitlab.steamos.cloud/steamrt/sniper/sdk:latest", ), + "steamrt3-arm64": JobSpec(name="Steam Linux Runtime 3.0 (arm64)", os=JobOs.Ubuntu24_04_arm, platform=SdlPlatform.Linux, artifact="SDL-steamrt3-arm64", container="registry.gitlab.steamos.cloud/steamrt/sniper/sdk/arm64:latest", ), "ubuntu-intel-icx": JobSpec(name="Ubuntu 22.04 (Intel oneAPI)", os=JobOs.Ubuntu22_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu22.04-oneapi", intel=IntelCompiler.Icx, ), "ubuntu-intel-icc": JobSpec(name="Ubuntu 22.04 (Intel Compiler)", os=JobOs.Ubuntu22_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu22.04-icc", intel=IntelCompiler.Icc, ), "macos-framework-x64": JobSpec(name="MacOS (Framework) (x64)", os=JobOs.Macos13, platform=SdlPlatform.MacOS, artifact="SDL-macos-framework", apple_framework=True, apple_archs={AppleArch.Aarch64, AppleArch.X86_64, }, xcode=True, ), @@ -138,11 +140,12 @@ JOB_SPECS = { "riscos": JobSpec(name="RISC OS", os=JobOs.UbuntuLatest, platform=SdlPlatform.Riscos, artifact="SDL-riscos", container="riscosdotinfo/riscos-gccsdk-4.7:latest", ), "netbsd": JobSpec(name="NetBSD", os=JobOs.UbuntuLatest, platform=SdlPlatform.NetBSD, artifact="SDL-netbsd-x64", ), "freebsd": JobSpec(name="FreeBSD", os=JobOs.UbuntuLatest, platform=SdlPlatform.FreeBSD, artifact="SDL-freebsd-x64", ), + "ngage": JobSpec(name="N-Gage", os=JobOs.WindowsLatest, platform=SdlPlatform.NGage, artifact="SDL-ngage", ), } class StaticLibType(Enum): - MSVC = "SDL3-static.lib" + STATIC_LIB = "SDL3-static.lib" A = "libSDL3.a" @@ -222,6 +225,7 @@ class JobDetails: check_sources: bool = False setup_python: bool = False pypi_packages: list[str] = dataclasses.field(default_factory=list) + setup_gage_sdk_path: str = "" def to_workflow(self, enable_artifacts: bool) -> dict[str, str|bool]: data = { @@ -289,6 +293,7 @@ class JobDetails: "check-sources": self.check_sources, "setup-python": self.setup_python, "pypi-packages": my_shlex_join(self.pypi_packages), + "setup-ngage-sdk-path": self.setup_gage_sdk_path, } return {k: v for k, v in data.items() if v != ""} @@ -364,7 +369,7 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDeta job.msvc_project_flags.append("-p:TreatWarningsAsError=true") job.test_pkg_config = False job.shared_lib = SharedLibType.WIN32 - job.static_lib = StaticLibType.MSVC + job.static_lib = StaticLibType.STATIC_LIB job.cmake_arguments.extend(( "-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=ProgramDatabase", "-DCMAKE_EXE_LINKER_FLAGS=-DEBUG", @@ -739,6 +744,19 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDeta job.cpactions_arch = "x86-64" job.cpactions_setup_cmd = "export PATH=\"/usr/pkg/sbin:/usr/pkg/bin:/sbin:$PATH\"; export PKG_CONFIG_PATH=\"/usr/pkg/lib/pkgconfig\";export PKG_PATH=\"https://cdn.netBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r|cut -f \"1 2\" -d.)/All/\";echo \"PKG_PATH=$PKG_PATH\";echo \"uname -a -> \"$(uname -a)\"\";sudo -E sysctl -w security.pax.aslr.enabled=0;sudo -E sysctl -w security.pax.aslr.global=0;sudo -E pkgin clean;sudo -E pkgin update" job.cpactions_install_cmd = "sudo -E pkgin -y install cmake dbus pkgconf ninja-build pulseaudio libxkbcommon wayland wayland-protocols libinotify libusb1" + case SdlPlatform.NGage: + build_parallel = False + job.cmake_build_type = "Release" + job.setup_ninja = True + job.static_lib = StaticLibType.STATIC_LIB + job.shared_lib = None + job.clang_tidy = False + job.werror = False # FIXME: enable SDL_WERROR + job.shared = False + job.run_tests = False + job.setup_gage_sdk_path = "C:/ngagesdk" + job.cmake_toolchain_file = "C:/ngagesdk/cmake/ngage-toolchain.cmake" + job.test_pkg_config = False case _: raise ValueError(f"Unsupported platform={spec.platform}") diff --git a/.github/workflows/generic.yml b/.github/workflows/generic.yml index 9776431e..083859b3 100644 --- a/.github/workflows/generic.yml +++ b/.github/workflows/generic.yml @@ -93,6 +93,11 @@ jobs: with: arch: ${{ matrix.platform.msvc-vcvars-arch }} sdk: ${{ matrix.platform.msvc-vcvars-sdk }} + - name: 'Set up Nokia N-Gage SDK' + uses: ./.github/actions/setup-ngage-sdk + if: ${{ matrix.platform.setup-ngage-sdk-path != '' }} + with: + path: '${{ matrix.platform.setup-ngage-sdk-path }}' - name: 'Set up Windows GDK Desktop' uses: ./.github/actions/setup-gdk-desktop if: ${{ matrix.platform.setup-gdk-folder != '' }} diff --git a/.wikiheaders-options b/.wikiheaders-options index 9c2b5bb9..38a2f273 100644 --- a/.wikiheaders-options +++ b/.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,7 +26,7 @@ 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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0db59b3e..fd62e985 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ 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) @@ -155,7 +156,7 @@ 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) @@ -219,7 +220,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() @@ -414,6 +415,24 @@ 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_VIRTUAL_JOYSTICK OFF) +endif() + if(NOT (SDL_SHARED OR SDL_STATIC)) message(FATAL_ERROR "SDL_SHARED and SDL_STATIC cannot both be disabled") endif() @@ -1294,8 +1313,8 @@ if(ANDROID) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/android") sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/android/*.c") - sdl_sources("${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c") - set_property(SOURCE "${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c" APPEND_STRING PROPERTY COMPILE_FLAGS " -Wno-declaration-after-statement") + sdl_sources("${CMAKE_ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c") + set_property(SOURCE "${CMAKE_ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c" APPEND_STRING PROPERTY COMPILE_FLAGS " -Wno-declaration-after-statement") sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/android/*.c") set(HAVE_SDL_MISC TRUE) @@ -1736,6 +1755,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) sdl_sources( "${SDL3_SOURCE_DIR}/src/core/linux/SDL_dbus.c" "${SDL3_SOURCE_DIR}/src/core/linux/SDL_system_theme.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_progressbar.c" ) endif() @@ -2930,6 +2950,81 @@ elseif(N3DS) set(HAVE_SDL_LOCALE TRUE) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/io/n3ds/*.c") + +elseif(NGAGE) + + enable_language(CXX) + + set(SDL_MAIN_USE_CALLBACKS 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/ngage/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/ngage/*.cpp") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/ngage/*.cpp") + 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") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/ngage/*.cpp") + 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") + 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") + 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") + 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) @@ -3023,7 +3118,7 @@ if(SDL_GPU) set(SDL_GPU_D3D11 1) set(HAVE_SDL_GPU TRUE) endif() - if(SDL_RENDER_D3D12) + if(WINDOWS) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/gpu/d3d12/*.c") set(SDL_GPU_D3D12 1) set(HAVE_SDL_GPU TRUE) @@ -3110,8 +3205,8 @@ 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") else() @@ -3294,7 +3389,7 @@ else() endif() if(ANDROID) - sdl_include_directories(PRIVATE SYSTEM "${ANDROID_NDK}/sources/android/cpufeatures") + sdl_include_directories(PRIVATE SYSTEM "${CMAKE_ANDROID_NDK}/sources/android/cpufeatures") endif() if(APPLE) diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index baa2fffe..58194e72 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -168,8 +168,8 @@ - call $(ProjectDir)..\..\src\render\direct3d12\compile_shaders_xbox.bat $(ProjectDir)..\ - call $(ProjectDir)..\..\src\gpu\d3d12\compile_shaders_xbox.bat $(ProjectDir)..\ + call "$(ProjectDir)..\..\src\render\direct3d12\compile_shaders_xbox.bat" "$(ProjectDir)..\" + call "$(ProjectDir)..\..\src\gpu\d3d12\compile_shaders_xbox.bat" "$(ProjectDir)..\" @@ -713,8 +713,10 @@ + + diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index 26f22882..8a988ace 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -64,8 +64,10 @@ + + diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index c0a4d864..843f8e61 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -603,8 +603,10 @@ + + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index 2583c9f3..375c175c 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -1185,12 +1185,18 @@ joystick\hidapi + + joystick\hidapi + joystick\hidapi joystick\hidapi + + joystick\hidapi + joystick\hidapi diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index 8913d5a5..ffe8fa75 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -438,6 +438,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 */; }; @@ -540,6 +541,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, ); }; }; @@ -948,6 +950,7 @@ 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 = ""; }; @@ -1012,6 +1015,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 = ""; }; @@ -1927,7 +1931,9 @@ children = ( F3395BA72D9A5971007246C8 /* SDL_hidapi_8bitdo.c */, F32305FE28939F6400E66D30 /* SDL_hidapi_combined.c */, + 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 */, @@ -3056,6 +3062,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 */, @@ -3082,6 +3089,7 @@ 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 */, diff --git a/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3Config.cmake b/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3Config.cmake index 9d29aae9..b60ef0a7 100644 --- a/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3Config.cmake +++ b/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3Config.cmake @@ -92,7 +92,7 @@ if(NOT TARGET SDL3::Headers) add_library(SDL3::Headers INTERFACE IMPORTED) set_target_properties(SDL3::Headers PROPERTIES - INTERFACE_COMPILE_OPTIONS "SHELL:-F \"${_sdl3_framework_parent_path}\"" + INTERFACE_COMPILE_OPTIONS "-F${_sdl3_framework_parent_path}" ) endif() set(SDL3_Headers_FOUND TRUE) diff --git a/android-project/app/proguard-rules.pro b/android-project/app/proguard-rules.pro index fc0a4f5c..147cbd37 100644 --- a/android-project/app/proguard-rules.pro +++ b/android-project/app/proguard-rules.pro @@ -50,6 +50,8 @@ boolean supportsRelativeMouse(); int openFileDescriptor(java.lang.String, java.lang.String); boolean showFileDialog(java.lang.String[], boolean, boolean, int); + java.lang.String getPreferredLocales(); + java.lang.String formatLocale(java.util.Locale); } -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager { diff --git a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java index 642a9767..f7c56c44 100644 --- a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java +++ b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java @@ -288,9 +288,13 @@ public class HIDDeviceManager { 0x1532, // Razer Wildcat 0x20d6, // PowerA 0x24c6, // PowerA + 0x294b, // Snakebyte 0x2dc8, // 8BitDo 0x2e24, // Hyperkin + 0x2e95, // SCUF + 0x3285, // Nacon 0x3537, // GameSir + 0x366c, // ByoWave }; if (usbInterface.getId() == 0 && diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java index 85d51541..278be468 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -23,6 +23,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.LocaleList; import android.os.Message; import android.os.ParcelFileDescriptor; import android.util.DisplayMetrics; @@ -2116,6 +2117,44 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh int requestCode; boolean multipleChoice; } + + /** + * This method is called by SDL using JNI. + */ + public static String getPreferredLocales() { + String result = ""; + if (Build.VERSION.SDK_INT >= 24 /* Android 7 (N) */) { + LocaleList locales = LocaleList.getAdjustedDefault(); + for (int i = 0; i < locales.size(); i++) { + if (i != 0) result += ","; + result += formatLocale(locales.get(i)); + } + } else if (mCurrentLocale != null) { + result = formatLocale(mCurrentLocale); + } + return result; + } + + public static String formatLocale(Locale locale) { + String result = ""; + String lang = ""; + if (locale.getLanguage() == "in") { + // Indonesian is "id" according to ISO 639.2, but on Android is "in" because of Java backwards compatibility + lang = "id"; + } else if (locale.getLanguage() == "") { + // Make sure language is never empty + lang = "und"; + } else { + lang = locale.getLanguage(); + } + + if (locale.getCountry() == "") { + result = lang; + } else { + result = lang + "_" + locale.getCountry(); + } + return result; + } } /** diff --git a/build-scripts/wikiheaders.pl b/build-scripts/wikiheaders.pl index 019304e9..c2732af5 100755 --- a/build-scripts/wikiheaders.pl +++ b/build-scripts/wikiheaders.pl @@ -32,6 +32,7 @@ my $wikisubdir = ''; my $incsubdir = 'include'; my $readmesubdir = undef; my $apiprefixregex = undef; +my $apipropertyregex = undef; my $versionfname = 'include/SDL_version.h'; my $versionmajorregex = '\A\#define\s+SDL_MAJOR_VERSION\s+(\d+)\Z'; my $versionminorregex = '\A\#define\s+SDL_MINOR_VERSION\s+(\d+)\Z'; @@ -43,7 +44,6 @@ my $wikiurl = 'https://wiki.libsdl.org'; my $bugreporturl = 'https://github.com/libsdl-org/sdlwiki/issues/new'; my $srcpath = undef; my $wikipath = undef; -my $wikireadmesubdir = 'README'; my $warn_about_missing = 0; my $copy_direction = 0; my $optionsfname = undef; @@ -111,6 +111,7 @@ if (defined $optionsfname) { $srcpath = $val, next if $key eq 'srcpath'; $wikipath = $val, next if $key eq 'wikipath'; $apiprefixregex = $val, next if $key eq 'apiprefixregex'; + $apipropertyregex = $val, next if $key eq 'apipropertyregex'; $projectfullname = $val, next if $key eq 'projectfullname'; $projectshortname = $val, next if $key eq 'projectshortname'; $wikisubdir = $val, next if $key eq 'wikisubdir'; @@ -427,6 +428,7 @@ sub dewikify_chunk { # make sure these can't become part of roff syntax. $str =~ s/\./\\[char46]/gms; $str =~ s/"/\\(dq/gms; + $str =~ s/'/\\(aq/gms; if ($wikitype eq 'mediawiki') { # Dump obvious wikilinks. @@ -825,21 +827,23 @@ sub print_big_ascii_string { die("Don't have a big ascii entry for '$ch'!\n") if not defined $rowsref; my $row = @$rowsref[$rownum]; + my $outstr = ''; if ($lowascii) { my @x = split //, $row; foreach (@x) { - my $v = ($_ eq "\x{2588}") ? 'X' : ' '; - print $fh $v; + $outstr .= ($_ eq "\x{2588}") ? 'X' : ' '; } } else { - print $fh $row; + $outstr = $row; } $charidx++; - - if ($charidx < $charcount) { - print $fh " "; + if ($charidx == $charcount) { + $outstr =~ s/\s*\Z//; # dump extra spaces at the end of the line. + } else { + $outstr .= ' '; # space between glyphs. } + print $fh $outstr; } print $fh "\n"; } @@ -1033,7 +1037,6 @@ sub generate_quickref { my $incpath = "$srcpath"; $incpath .= "/$incsubdir" if $incsubdir ne ''; -my $wikireadmepath = "$wikipath/$wikireadmesubdir"; my $readmepath = undef; if (defined $readmesubdir) { $readmepath = "$srcpath/$readmesubdir"; @@ -1365,7 +1368,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) { @@ -2082,18 +2085,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 =~ /\AREADME\-.*?\.md\Z/) { # we only bridge Markdown files here that start with "README-". + filecopy("$wikipath/$dent", "$readmepath/$dent", "\n"); } - closedir(DH); } + closedir(DH); } } elsif ($copy_direction == -1) { # --copy-to-wiki @@ -2698,31 +2698,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 =~ /\AREADME\-.*?\.md\Z/) { # we only bridge Markdown files here that start with "README-". + 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\-.*?)\.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 = $_; @@ -2980,10 +2976,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/cmake/PreseedNokiaNGageCache.cmake b/cmake/PreseedNokiaNGageCache.cmake new file mode 100644 index 00000000..298177ba --- /dev/null +++ b/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(LINKER_SUPPORTS_VERSION_SCRIPT "" CACHE INTERNAL "Test LINKER_SUPPORTS_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/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake index 42e87036..06edce5b 100644 --- a/cmake/sdlchecks.cmake +++ b/cmake/sdlchecks.cmake @@ -594,6 +594,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}) @@ -814,7 +826,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) diff --git a/cmake/sdlplatform.cmake b/cmake/sdlplatform.cmake index 677b1870..60e0e77f 100644 --- a/cmake/sdlplatform.cmake +++ b/cmake/sdlplatform.cmake @@ -22,6 +22,8 @@ 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(VITA) diff --git a/cmake/test/CMakeLists.txt b/cmake/test/CMakeLists.txt index e3766f0e..ffaa1977 100644 --- a/cmake/test/CMakeLists.txt +++ b/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/cmake/test/main_gui.c b/cmake/test/main_gui.c index 18ed1010..c0c4f901 100644 --- a/cmake/test/main_gui.c +++ b/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/cmake/test/sdltest.c b/cmake/test/sdltest.c index f598a98c..baf8e9b5 100644 --- a/cmake/test/sdltest.c +++ b/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/docs/README-cmake.md b/docs/README-cmake.md index a87b0daf..7aea86ef 100644 --- a/docs/README-cmake.md +++ b/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,7 +328,7 @@ 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. +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/docs/README-documentation-rules.md b/docs/README-documentation-rules.md index 757a3c5f..e6dfa25e 100644 --- a/docs/README-documentation-rules.md +++ b/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) @@ -327,6 +333,16 @@ 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. Each individual piece of documentation becomes a separate page on the wiki, so @@ -340,7 +356,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. diff --git a/docs/README-emscripten.md b/docs/README-emscripten.md index 2b814689..54e7d2de 100644 --- a/docs/README-emscripten.md +++ b/docs/README-emscripten.md @@ -103,7 +103,7 @@ getting started. Another option is to use SDL' 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. @@ -230,7 +230,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 ``` diff --git a/docs/README-linux.md b/docs/README-linux.md index 8399881c..42891def 100644 --- a/docs/README-linux.md +++ b/docs/README-linux.md @@ -46,6 +46,7 @@ openSUSE Tumbleweed: libgbm-devel pipewire-devel libpulse-devel sndio-devel Mesa-libEGL-devel Arch: + sudo pacman -S alsa-lib cmake hidapi ibus jack libdecor libgl libpulse libusb libx11 libxcursor libxext libxinerama libxkbcommon libxrandr libxrender libxss libxtst mesa ninja pipewire sndio vulkan-driver vulkan-headers wayland wayland-protocols diff --git a/docs/README-ngage.md b/docs/README-ngage.md index 84192b01..ab5b402c 100644 --- a/docs/README-ngage.md +++ b/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#how-to-use-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/docs/README-platforms.md b/docs/README-platforms.md index 77ae411d..765cace3 100644 --- a/docs/README-platforms.md +++ b/docs/README-platforms.md @@ -11,7 +11,7 @@ - [macOS](README-macos.md) - [NetBSD](README-bsd.md) - [Nintendo Switch](README-switch.md) -- [Nintendo 3DS](README-3ds.md) +- [Nintendo 3DS](README-n3ds.md) - [OpenBSD](README-bsd.md) - [PlayStation 2](README-ps2.md) - [PlayStation 4](README-ps4.md) @@ -24,6 +24,7 @@ - [Windows](README-windows.md) - [Windows GDK](README-gdk.md) - [Xbox](README-gdk.md) +- [Nokia N-Gage](README-ngage.md) ## Unsupported Platforms @@ -33,7 +34,6 @@ All of these still work with [SDL2](/SDL2), which is an incompatible API, but an - Google Stadia - NaCL -- Nokia N-Gage - OS/2 - QNX - WinPhone diff --git a/docs/README-wayland.md b/docs/README-wayland.md index 75a9b906..a3cd06f7 100644 --- a/docs/README-wayland.md +++ b/docs/README-wayland.md @@ -59,6 +59,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/examples/CMakeLists.txt b/examples/CMakeLists.txt index 58406f62..1c5a40e6 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -111,6 +111,10 @@ macro(add_sdl_example_executable TARGET) elseif(EMSCRIPTEN) set_property(TARGET ${TARGET} PROPERTY SUFFIX ".html") target_link_options(${TARGET} PRIVATE -sALLOW_MEMORY_GROWTH=1) + 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) @@ -141,6 +145,7 @@ add_sdl_example_executable(audio-simple-playback SOURCES audio/01-simple-playbac 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(camera-read-and-draw SOURCES camera/01-read-and-draw/read-and-draw.c) diff --git a/examples/audio/04-multiple-streams/README.txt b/examples/audio/04-multiple-streams/README.txt index 9ef2275b..ad7f5f8a 100644 --- a/examples/audio/04-multiple-streams/README.txt +++ b/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/examples/audio/04-multiple-streams/multiple-streams.c b/examples/audio/04-multiple-streams/multiple-streams.c index 8d3bfaa0..188023c8 100644 --- a/examples/audio/04-multiple-streams/multiple-streams.c +++ b/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. * diff --git a/examples/audio/05-planar-data/README.txt b/examples/audio/05-planar-data/README.txt new file mode 100644 index 00000000..c7bd0ab5 --- /dev/null +++ b/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/examples/audio/05-planar-data/onmouseover.webp b/examples/audio/05-planar-data/onmouseover.webp new file mode 100644 index 00000000..90880c9e Binary files /dev/null and b/examples/audio/05-planar-data/onmouseover.webp differ diff --git a/examples/audio/05-planar-data/planar-data.c b/examples/audio/05-planar-data/planar-data.c new file mode 100644 index 00000000..f06ef696 --- /dev/null +++ b/examples/audio/05-planar-data/planar-data.c @@ -0,0 +1,366 @@ +/* + * 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, 0, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + 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) +{ + 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/examples/audio/05-planar-data/thumbnail.png b/examples/audio/05-planar-data/thumbnail.png new file mode 100644 index 00000000..3a040dfa Binary files /dev/null and b/examples/audio/05-planar-data/thumbnail.png differ diff --git a/examples/demo/04-bytepusher/bytepusher.c b/examples/demo/04-bytepusher/bytepusher.c index acb2ea48..c7e3e059 100644 --- a/examples/demo/04-bytepusher/bytepusher.c +++ b/examples/demo/04-bytepusher/bytepusher.c @@ -27,7 +27,6 @@ typedef struct { Uint8 ram[RAM_SIZE + 8]; - Uint8 screenbuf[SCREEN_W * SCREEN_H]; Uint64 last_tick; Uint64 tick_acc; SDL_Window* window; @@ -187,7 +186,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { } if (!(vm->screen = SDL_CreateSurfaceFrom( - SCREEN_W, SCREEN_H, SDL_PIXELFORMAT_INDEX8, vm->screenbuf, SCREEN_W + SCREEN_W, SCREEN_H, SDL_PIXELFORMAT_INDEX8, vm->ram, SCREEN_W ))) { return SDL_APP_FAILURE; } @@ -296,18 +295,18 @@ SDL_AppResult SDL_AppIterate(void* appstate) { 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->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); diff --git a/include/SDL3/SDL_assert.h b/include/SDL3/SDL_assert.h index 6c90acc0..ef5f85d0 100644 --- a/include/SDL3/SDL_assert.h +++ b/include/SDL3/SDL_assert.h @@ -132,7 +132,7 @@ extern "C" { #define SDL_TriggerBreakpoint() __debugbreak() #elif defined(_MSC_VER) && defined(_M_IX86) #define SDL_TriggerBreakpoint() { _asm { int 0x03 } } -#elif defined(ANDROID) +#elif defined(ANDROID) || defined(__SYMBIAN32__) #include #define SDL_TriggerBreakpoint() assert(0) #elif SDL_HAS_BUILTIN(__builtin_debugtrap) diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index c6acf885..c0aa7f7c 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -1021,7 +1021,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnbindAudioStream(SDL_AudioStream *stream); /** * Query an audio stream for its currently-bound device. * - * This reports the audio device that an audio stream is currently bound to. + * This reports the logical audio device that an audio stream is currently + * bound to. * * If not bound, or invalid, this returns zero, which is not a valid device * ID. @@ -1063,6 +1064,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. @@ -1073,6 +1085,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. * @@ -1149,14 +1164,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 @@ -1332,7 +1347,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 @@ -1414,6 +1429,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. * @@ -1583,8 +1728,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 @@ -1841,7 +1986,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. * @@ -1887,6 +2032,85 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroyAudioStream(SDL_AudioStream *stream) */ extern SDL_DECLSPEC SDL_AudioStream * SDLCALL SDL_OpenAudioDeviceStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec, SDL_AudioStreamCallback callback, void *userdata); +/** + * A callback that fires around an audio device's processing work. + * + * This callback fires when a logical audio device is about to start accessing + * its bound audio streams, and fires again when it has finished accessing + * them. It covers the range of one "iteration" of the audio device. + * + * It can be useful to use this callback to update state that must apply to + * all bound audio streams atomically: to make sure state changes don't happen + * while half of the streams are already processed for the latest audio + * buffer. + * + * This callback should run as quickly as possible and not block for any + * significant time, as this callback delays submission of data to the audio + * device, which can cause audio playback problems. This callback delays all + * audio processing across a single physical audio device: all its logical + * devices and all bound audio streams. Use it carefully. + * + * \param userdata a pointer provided by the app through + * SDL_SetAudioPostmixCallback, for its own use. + * \param devid the audio device this callback is running for. + * \param start true if this is the start of the iteration, false if the end. + * + * \threadsafety This will run from a background thread owned by SDL. The + * application is responsible for locking resources the callback + * touches that need to be protected. + * + * \since This datatype is available since SDL 3.4.0. + * + * \sa SDL_SetAudioIterationCallbacks + */ +typedef void (SDLCALL *SDL_AudioIterationCallback)(void *userdata, SDL_AudioDeviceID devid, bool start); + +/** + * Set callbacks that fire around a new iteration of audio device processing. + * + * Two callbacks are provided here: one that runs when a device is about to + * process its bound audio streams, and another that runs when the device has + * finished processing them. + * + * These callbacks can run at any time, and from any thread; if you need to + * serialize access to your app's data, you should provide and use a mutex or + * other synchronization device. + * + * Generally these callbacks are used to apply state that applies to multiple + * bound audio streams, with a guarantee that the audio device's thread isn't + * halfway through processing them. Generally a finer-grained lock through + * SDL_LockAudioStream() is more appropriate. + * + * The callbacks are extremely time-sensitive; the callback should do the + * least amount of work possible and return as quickly as it can. The longer + * the callback runs, the higher the risk of audio dropouts or other problems. + * + * This function will block until the audio device is in between iterations, + * so any existing callback that might be running will finish before this + * function sets the new callback and returns. + * + * Physical devices do not accept these callbacks, only logical devices + * created through SDL_OpenAudioDevice() can be. + * + * Setting a NULL callback function disables any previously-set callback. + * Either callback may be NULL, and the same callback is permitted to be used + * for both. + * + * \param devid the ID of an opened audio device. + * \param start a callback function to be called at the start of an iteration. + * Can be NULL. + * \param end a callback function to be called at the end of an iteration. Can + * be NULL. + * \param userdata app-controlled pointer passed to callback. Can be NULL. + * \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.4.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetAudioIterationCallbacks(SDL_AudioDeviceID devid, SDL_AudioIterationCallback start, SDL_AudioIterationCallback end, void *userdata); + /** * A callback that fires when data is about to be fed to an audio device. * diff --git a/include/SDL3/SDL_begin_code.h b/include/SDL3/SDL_begin_code.h index a6b47cf4..e306a906 100644 --- a/include/SDL3/SDL_begin_code.h +++ b/include/SDL3/SDL_begin_code.h @@ -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 position[4] SDL_ALIGNED(16); + * 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) @@ -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/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index 56a2194b..353ecd3e 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -135,7 +135,8 @@ typedef enum SDL_EventType /* 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 */ @@ -492,6 +493,8 @@ typedef struct SDL_MouseWheelEvent SDL_MouseWheelDirection direction; /**< Set to one of the SDL_MOUSEWHEEL_* defines. When FLIPPED the values in X and Y will be opposite. Multiply by -1 to change them back */ float mouse_x; /**< X coordinate, relative to window */ float mouse_y; /**< Y coordinate, relative to window */ + Sint32 integer_x; /**< The amount scrolled horizontally, accumulated to whole scroll "ticks" (added in 3.2.12) */ + Sint32 integer_y; /**< The amount scrolled vertically, accumulated to whole scroll "ticks" (added in 3.2.12) */ } SDL_MouseWheelEvent; /** @@ -779,7 +782,7 @@ typedef struct SDL_TouchFingerEvent } SDL_TouchFingerEvent; /** - * Pressure-sensitive pen proximity event structure (event.pmotion.*) + * 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 @@ -1565,6 +1568,38 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_RegisterEvents(int numevents); */ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowFromEvent(const SDL_Event *event); +/** + * Generate a human-readable 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/include/SDL3/SDL_filesystem.h b/include/SDL3/SDL_filesystem.h index af3ca27e..031feaf9 100644 --- a/include/SDL3/SDL_filesystem.h +++ b/include/SDL3/SDL_filesystem.h @@ -444,10 +444,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 diff --git a/include/SDL3/SDL_gamepad.h b/include/SDL3/SDL_gamepad.h index 07a39b55..460ae987 100644 --- a/include/SDL3/SDL_gamepad.h +++ b/include/SDL3/SDL_gamepad.h @@ -118,6 +118,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 +128,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 diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index 6a2e667f..b3ff2022 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -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` @@ -224,12 +230,19 @@ * - `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. + * ### D3D12 * - * **Metal:** Supported on macOS 10.14+ and iOS/tvOS 13.0+. Hardware - * requirements vary by operating system: + * 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_1. + * + * ### 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) @@ -237,6 +250,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 @@ -302,6 +335,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_ @@ -1312,10 +1378,15 @@ typedef struct SDL_GPUViewport * texture. * * If either of `pixels_per_row` or `rows_per_layer` is zero, then width and - * height of passed SDL_GPUTextureRegion to SDL_UploadToGPUTexture + * 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. * - * / SDL_DownloadFromGPUTexture are used as default values respectively and - * data is considered to be tightly packed. + * **WARNING**: 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. * @@ -1398,7 +1469,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. */ @@ -1527,8 +1598,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; @@ -1620,6 +1691,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 { @@ -1630,8 +1704,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; @@ -1643,6 +1717,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 { @@ -1748,8 +1824,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; @@ -1766,7 +1842,7 @@ 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. */ + 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; @@ -1787,9 +1863,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; @@ -1824,7 +1900,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; @@ -1931,8 +2007,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; @@ -1989,7 +2065,7 @@ 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; @@ -2009,7 +2085,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; @@ -2118,6 +2194,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. @@ -2128,6 +2211,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 @@ -2172,6 +2256,25 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( * - `SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING`: the prefix to * use for all vertex semantics, default is "TEXCOORD". * + * With the Vulkan renderer: + * + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOLEAN`: Enable + * 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. Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOLEAN`: Enable device + * feature depthClamp. If disabled, there is no depth clamp support and + * enable_depth_clip in SDL_GPURasterizerState must always be set to true. + * Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOLEAN`: Enable + * device feature drawIndirectFirstInstance. If disabled, the argument + * first_instance of SDL_GPUIndirectDrawCommand must be set to zero. + * Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOLEAN`: Enable + * device feature samplerAnisotropy. If disabled, enable_anisotropy of + * SDL_GPUSamplerCreateInfo must be set to false. Defaults to true. + * * \param props the properties to use. * \returns a GPU context on success or NULL on failure; call SDL_GetError() * for more information. @@ -2186,17 +2289,21 @@ 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_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_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_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_VULKAN_SHADERCLIPDISTANCE_BOOLEAN "SDL.gpu.device.create.vulkan.shaderclipdistance" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOLEAN "SDL.gpu.device.create.vulkan.depthclamp" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOLEAN "SDL.gpu.device.create.vulkan.drawindirectfirstinstance" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOLEAN "SDL.gpu.device.create.vulkan.sampleranisotropy" /** * Destroys a GPU context previously returned by SDL_CreateGPUDevice. @@ -2587,9 +2694,9 @@ extern SDL_DECLSPEC SDL_GPUShader * SDLCALL SDL_CreateGPUShader( * - `SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT`: (Direct3D 12 only) * if the texture usage is SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, clear * the texture to a depth of this value. Defaults to zero. - * - `SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_UINT8`: (Direct3D 12 + * - `SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_NUMBER`: (Direct3D 12 * only) if the texture usage is SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, - * clear the texture to a stencil of this value. Defaults to zero. + * clear the texture to a stencil of this Uint8 value. Defaults to zero. * - `SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING`: a name that can be displayed * in debugging tools. * @@ -2615,13 +2722,13 @@ extern SDL_DECLSPEC SDL_GPUTexture * SDLCALL SDL_CreateGPUTexture( SDL_GPUDevice *device, const SDL_GPUTextureCreateInfo *createinfo); -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_R_FLOAT "SDL.gpu.texture.create.d3d12.clear.r" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_G_FLOAT "SDL.gpu.texture.create.d3d12.clear.g" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_B_FLOAT "SDL.gpu.texture.create.d3d12.clear.b" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_A_FLOAT "SDL.gpu.texture.create.d3d12.clear.a" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT "SDL.gpu.texture.create.d3d12.clear.depth" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_UINT8 "SDL.gpu.texture.create.d3d12.clear.stencil" -#define SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING "SDL.gpu.texture.create.name" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_R_FLOAT "SDL.gpu.texture.create.d3d12.clear.r" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_G_FLOAT "SDL.gpu.texture.create.d3d12.clear.g" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_B_FLOAT "SDL.gpu.texture.create.d3d12.clear.b" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_A_FLOAT "SDL.gpu.texture.create.d3d12.clear.a" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT "SDL.gpu.texture.create.d3d12.clear.depth" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_NUMBER "SDL.gpu.texture.create.d3d12.clear.stencil" +#define SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING "SDL.gpu.texture.create.name" /** * Creates a buffer object to be used in graphics or compute workflows. @@ -2942,6 +3049,9 @@ extern SDL_DECLSPEC SDL_GPUCommandBuffer * SDLCALL SDL_AcquireGPUCommandBuffer( * 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. @@ -3895,7 +4005,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_ReleaseWindowFromGPUDevice( * supported via SDL_WindowSupportsGPUPresentMode / * SDL_WindowSupportsGPUSwapchainComposition prior to calling this function. * - * SDL_GPU_PRESENTMODE_VSYNC and SDL_GPU_SWAPCHAINCOMPOSITION_SDR are always + * SDL_GPU_PRESENTMODE_VSYNC with SDL_GPU_SWAPCHAINCOMPOSITION_SDR is always * supported. * * \param device a GPU context. @@ -3969,7 +4079,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 diff --git a/include/SDL3/SDL_haptic.h b/include/SDL3/SDL_haptic.h index a45335b2..9a20396d 100644 --- a/include/SDL3/SDL_haptic.h +++ b/include/SDL3/SDL_haptic.h @@ -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/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index df35bd87..7961157c 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -1072,8 +1072,8 @@ extern "C" { * * By default, SDL will try all available GPU backends in a reasonable order * until it finds one that can work, but this hint allows the app or user to - * force a specific target, such as "direct3d11" if, say, your hardware - * supports D3D12 but want to try using D3D11 instead. + * force a specific target, such as "direct3d12" if, say, your hardware + * supports Vulkan but you want to try using D3D12 instead. * * This hint should be set before any GPU functions are called. * @@ -1746,6 +1746,18 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_HIDAPI_8BITDO "SDL_JOYSTICK_HIDAPI_8BITDO" +/** + * A variable controlling whether the HIDAPI driver for Flydigi 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 default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI "SDL_JOYSTICK_HIDAPI_FLYDIGI" + /** * A variable controlling whether the HIDAPI driver for Nintendo Switch * controllers should be used. @@ -1949,6 +1961,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. @@ -2049,8 +2096,8 @@ extern "C" { * * The variable can be set to the following values: * - * - "0": RAWINPUT drivers are not used. - * - "1": RAWINPUT drivers are used. (default) + * - "0": RAWINPUT drivers are not used. (default) + * - "1": RAWINPUT drivers are used. * * This hint should be set before SDL is initialized. * diff --git a/include/SDL3/SDL_init.h b/include/SDL3/SDL_init.h index adf0de8a..557f2b99 100644 --- a/include/SDL3/SDL_init.h +++ b/include/SDL3/SDL_init.h @@ -79,7 +79,7 @@ typedef Uint32 SDL_InitFlags; #define SDL_INIT_AUDIO 0x00000010u /**< `SDL_INIT_AUDIO` implies `SDL_INIT_EVENTS` */ #define SDL_INIT_VIDEO 0x00000020u /**< `SDL_INIT_VIDEO` implies `SDL_INIT_EVENTS`, should be initialized on the main thread */ -#define SDL_INIT_JOYSTICK 0x00000200u /**< `SDL_INIT_JOYSTICK` implies `SDL_INIT_EVENTS`, should be initialized on the same thread as SDL_INIT_VIDEO on Windows if you don't set SDL_HINT_JOYSTICK_THREAD */ +#define SDL_INIT_JOYSTICK 0x00000200u /**< `SDL_INIT_JOYSTICK` implies `SDL_INIT_EVENTS` */ #define SDL_INIT_HAPTIC 0x00001000u #define SDL_INIT_GAMEPAD 0x00002000u /**< `SDL_INIT_GAMEPAD` implies `SDL_INIT_JOYSTICK` */ #define SDL_INIT_EVENTS 0x00004000u @@ -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. diff --git a/include/SDL3/SDL_iostream.h b/include/SDL3/SDL_iostream.h index 4ca16093..d1596f91 100644 --- a/include/SDL3/SDL_iostream.h +++ b/include/SDL3/SDL_iostream.h @@ -823,7 +823,7 @@ 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. @@ -846,7 +846,7 @@ 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. @@ -869,7 +869,7 @@ 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. @@ -892,7 +892,7 @@ 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. @@ -915,7 +915,7 @@ 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. @@ -938,7 +938,7 @@ 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. @@ -961,7 +961,7 @@ 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. @@ -984,7 +984,7 @@ 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. @@ -1007,7 +1007,7 @@ 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. @@ -1030,7 +1030,7 @@ 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. @@ -1053,7 +1053,7 @@ 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. @@ -1076,7 +1076,7 @@ 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. diff --git a/include/SDL3/SDL_log.h b/include/SDL3/SDL_log.h index 3fd7ec2e..2019c3b4 100644 --- a/include/SDL3/SDL_log.h +++ b/include/SDL3/SDL_log.h @@ -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. diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h index 32775b44..305a1a38 100644 --- a/include/SDL3/SDL_main.h +++ b/include/SDL3/SDL_main.h @@ -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. * @@ -512,7 +512,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. * @@ -618,8 +618,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_EnterAppMainCallbacks(int argc, char *argv[] * \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. + * currently uses `(CS_BYTEALIGNCLIENT \| CS_OWNDC)` regardless + * of what is specified here. * \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 diff --git a/include/SDL3/SDL_mouse.h b/include/SDL3/SDL_mouse.h index 354771fa..4ecd1d00 100644 --- a/include/SDL3/SDL_mouse.h +++ b/include/SDL3/SDL_mouse.h @@ -147,6 +147,19 @@ typedef enum SDL_MouseWheelDirection */ typedef Uint32 SDL_MouseButtonFlags; +#define SDL_BUTTON_LEFT 1 +#define SDL_BUTTON_MIDDLE 2 +#define SDL_BUTTON_RIGHT 3 +#define SDL_BUTTON_X1 4 +#define SDL_BUTTON_X2 5 + +#define SDL_BUTTON_MASK(X) (1u << ((X)-1)) +#define SDL_BUTTON_LMASK SDL_BUTTON_MASK(SDL_BUTTON_LEFT) +#define SDL_BUTTON_MMASK SDL_BUTTON_MASK(SDL_BUTTON_MIDDLE) +#define SDL_BUTTON_RMASK SDL_BUTTON_MASK(SDL_BUTTON_RIGHT) +#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. * @@ -174,7 +187,7 @@ typedef Uint32 SDL_MouseButtonFlags; * 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.2.6. + * \since This datatype is available since SDL 3.4.0. * * \sa SDL_SetRelativeMouseTransform */ @@ -186,20 +199,6 @@ typedef void (SDLCALL *SDL_MouseMotionTransformCallback)( float *x, float *y ); -#define SDL_BUTTON_LEFT 1 -#define SDL_BUTTON_MIDDLE 2 -#define SDL_BUTTON_RIGHT 3 -#define SDL_BUTTON_X1 4 -#define SDL_BUTTON_X2 5 - -#define SDL_BUTTON_MASK(X) (1u << ((X)-1)) -#define SDL_BUTTON_LMASK SDL_BUTTON_MASK(SDL_BUTTON_LEFT) -#define SDL_BUTTON_MMASK SDL_BUTTON_MASK(SDL_BUTTON_MIDDLE) -#define SDL_BUTTON_RMASK SDL_BUTTON_MASK(SDL_BUTTON_RIGHT) -#define SDL_BUTTON_X1MASK SDL_BUTTON_MASK(SDL_BUTTON_X1) -#define SDL_BUTTON_X2MASK SDL_BUTTON_MASK(SDL_BUTTON_X2) - - /* Function prototypes */ /** @@ -579,15 +578,16 @@ 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. 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. @@ -599,6 +599,7 @@ 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_CreateCursor * \sa SDL_CreateSystemCursor * \sa SDL_DestroyCursor diff --git a/include/SDL3/SDL_pixels.h b/include/SDL3/SDL_pixels.h index 294f1a79..f9d6e9e3 100644 --- a/include/SDL3/SDL_pixels.h +++ b/include/SDL3/SDL_pixels.h @@ -517,7 +517,7 @@ typedef enum SDL_PackedLayout * ABGR32, define a platform-independent encoding into bytes in the order * specified. For example, in RGB24 data, each pixel is encoded in 3 bytes * (red, green, blue) in that order, and in ABGR32 data, each pixel is - * encoded in 4 bytes alpha, blue, green, red) in that order. Use these + * encoded in 4 bytes (alpha, blue, green, red) in that order. Use these * names if the property of a format that is important to you is the order * of the bytes in memory or on disk. * - Names with a bit count per component, such as ARGB8888 and XRGB1555, are diff --git a/include/SDL3/SDL_platform_defines.h b/include/SDL3/SDL_platform_defines.h index 6b240a8b..f7f14be0 100644 --- a/include/SDL3/SDL_platform_defines.h +++ b/include/SDL3/SDL_platform_defines.h @@ -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,15 @@ #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 + #endif /* SDL_platform_defines_h_ */ diff --git a/include/SDL3/SDL_process.h b/include/SDL3/SDL_process.h index 0e19bfff..57e3afd9 100644 --- a/include/SDL3/SDL_process.h +++ b/include/SDL3/SDL_process.h @@ -195,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 @@ -231,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/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h index dc5107d6..8c6ee0cc 100644 --- a/include/SDL3/SDL_render.h +++ b/include/SDL3/SDL_render.h @@ -110,7 +110,7 @@ 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_TEXTURE_ADDRESS_WRAP /**< The texture is repeated (tiled) */ } SDL_TextureAddressMode; /** @@ -1666,8 +1666,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderViewport(SDL_Renderer *renderer, S * Return whether an explicit rectangle was set as the viewport. * * This is useful if you're saving and restoring the viewport and want to know - * whether you should restore a specific rectangle or NULL. Note that the - * viewport is always reset when changing rendering targets. + * whether you should restore a specific rectangle or NULL. * * Each render target has its own viewport. This function checks the viewport * for the current render target. diff --git a/include/SDL3/SDL_stdinc.h b/include/SDL3/SDL_stdinc.h index fd80d729..f198de75 100644 --- a/include/SDL3/SDL_stdinc.h +++ b/include/SDL3/SDL_stdinc.h @@ -4656,7 +4656,7 @@ extern SDL_DECLSPEC float SDLCALL SDL_atanf(float x); * * Domain: `-INF <= x <= INF`, `-INF <= y <= INF` * - * Range: `-Pi/2 <= y <= Pi/2` + * Range: `-Pi <= y <= Pi` * * This function operates on double-precision floating point values, use * SDL_atan2f for single-precision floats. @@ -4692,7 +4692,7 @@ extern SDL_DECLSPEC double SDLCALL SDL_atan2(double y, double x); * * Domain: `-INF <= x <= INF`, `-INF <= y <= INF` * - * Range: `-Pi/2 <= y <= Pi/2` + * Range: `-Pi <= y <= Pi` * * This function operates on single-precision floating point values, use * SDL_atan2 for double-precision floats. @@ -5974,8 +5974,12 @@ size_t wcslcpy(wchar_t *dst, const wchar_t *src, size_t size); size_t wcslcat(wchar_t *dst, const wchar_t *src, size_t size); #endif +#ifndef _WIN32 /* strdup is not ANSI but POSIX, and its prototype might be hidden... */ +/* not for windows: might conflict with string.h where strdup may have + * dllimport attribute: https://github.com/libsdl-org/SDL/issues/12948 */ char *strdup(const char *str); +#endif /* Starting LLVM 16, the analyser errors out if these functions do not have their prototype defined (clang-diagnostic-implicit-function-declaration) */ diff --git a/include/SDL3/SDL_storage.h b/include/SDL3/SDL_storage.h index 6837ebaa..1b20b34a 100644 --- a/include/SDL3/SDL_storage.h +++ b/include/SDL3/SDL_storage.h @@ -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/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h index 7cd9d2b1..69f4d69c 100644 --- a/include/SDL3/SDL_surface.h +++ b/include/SDL3/SDL_surface.h @@ -32,7 +32,8 @@ * 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: + * [SDL_image](https://wiki.libsdl.org/SDL3_image) + * : * * https://github.com/libsdl-org/SDL_image */ @@ -1136,9 +1137,6 @@ extern SDL_DECLSPEC bool SDLCALL SDL_FillSurfaceRects(SDL_Surface *dst, const SD * If either `srcrect` or `dstrect` are NULL, the entire surface (`src` or * `dst`) is copied while ensuring clipping to `dst->clip_rect`. * - * The final blit rectangles are saved in `srcrect` and `dstrect` after all - * clipping is performed. - * * The blit function should not be called on a locked surface. * * The blit semantics for surfaces with and without blending and colorkey are @@ -1283,10 +1281,11 @@ extern SDL_DECLSPEC bool SDLCALL SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src * * \param src the SDL_Surface structure to be copied from. * \param srcrect the SDL_Rect structure representing the rectangle to be - * copied, may not be NULL. + * copied, or NULL to copy the entire surface. * \param dst the SDL_Surface structure that is the blit target. * \param dstrect the SDL_Rect structure representing the target rectangle in - * the destination surface, may not be NULL. + * the destination surface, or NULL to fill the entire + * destination surface. * \param scaleMode the SDL_ScaleMode to be used. * \returns true on success or false on failure; call SDL_GetError() for more * information. diff --git a/include/SDL3/SDL_system.h b/include/SDL3/SDL_system.h index 294089ff..625db182 100644 --- a/include/SDL3/SDL_system.h +++ b/include/SDL3/SDL_system.h @@ -247,14 +247,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 diff --git a/include/SDL3/SDL_version.h b/include/SDL3/SDL_version.h index 77db3391..e3fb742d 100644 --- a/include/SDL3/SDL_version.h +++ b/include/SDL3/SDL_version.h @@ -148,13 +148,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. * - * 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. * diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index e3ea31e0..d5107b43 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -1188,6 +1188,16 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindow(const char *title, int * Popup windows implicitly do not have a border/decorations and do not appear * on the taskbar/dock or in lists of windows such as alt-tab menus. * + * By default, popup window positions will automatically be constrained to + * keep the entire window within display bounds. This can be overridden with + * the `SDL_PROP_WINDOW_CREATE_CONSTRAIN_POPUP_BOOLEAN` property. + * + * By default, popup menus will automatically grab keyboard focus from the + * parent when shown. This behavior can be overridden by setting the + * `SDL_WINDOW_NOT_FOCUSABLE` flag, setting the + * `SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN` property to false, or toggling + * it after creation via the `SDL_SetWindowFocusable()` function. + * * If a parent window is hidden or destroyed, any child popup windows will be * recursively hidden or destroyed as well. Child popup windows not explicitly * hidden will be restored when the parent is shown. @@ -1228,6 +1238,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_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 @@ -1289,7 +1303,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren * - `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 @@ -1297,7 +1311,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: * @@ -1356,6 +1370,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowWithProperties(SDL_Prop #define SDL_PROP_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN "SDL.window.create.always_on_top" #define SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN "SDL.window.create.borderless" +#define SDL_PROP_WINDOW_CREATE_CONSTRAIN_POPUP_BOOLEAN "SDL.window.create.constrain_popup" #define SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN "SDL.window.create.focusable" #define SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN "SDL.window.create.external_graphics_context" #define SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER "SDL.window.create.flags" @@ -1501,8 +1516,8 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *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: * @@ -1587,7 +1602,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" @@ -1665,15 +1680,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. @@ -1683,6 +1699,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); @@ -1876,7 +1894,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 diff --git a/include/SDL3/SDL_vulkan.h b/include/SDL3/SDL_vulkan.h index 710afbe6..e91e1484 100644 --- a/include/SDL3/SDL_vulkan.h +++ b/include/SDL3/SDL_vulkan.h @@ -51,14 +51,14 @@ extern "C" { #endif -/* Avoid including vulkan.h, don't define VkInstance if it's already included */ -#ifdef VULKAN_H_ +/* Avoid including vulkan_core.h, don't define VkInstance if it's already included */ +#ifdef VULKAN_CORE_H_ #define NO_SDL_VULKAN_TYPEDEFS #endif #ifndef NO_SDL_VULKAN_TYPEDEFS #define VK_DEFINE_HANDLE(object) typedef struct object##_T* object; -#if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) || (defined(__riscv) && __riscv_xlen == 64) #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object; #else #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object; diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index c8c02894..76916aff 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -33,6 +33,10 @@ #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 @@ -277,6 +281,7 @@ #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 @@ -365,6 +370,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 @@ -387,6 +395,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 @@ -438,6 +447,7 @@ #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 @@ -467,6 +477,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 @@ -501,6 +513,8 @@ /* 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 @@ -544,6 +558,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@ diff --git a/src/SDL.c b/src/SDL.c index 502f6617..34e15251 100644 --- a/src/SDL.c +++ b/src/SDL.c @@ -728,6 +728,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) diff --git a/src/SDL_error.c b/src/SDL_error.c index 3c62c8af..3f4273b4 100644 --- a/src/SDL_error.c +++ b/src/SDL_error.c @@ -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/src/SDL_hints.c b/src/SDL_hints.c index a10598f0..786d654a 100644 --- a/src/SDL_hints.c +++ b/src/SDL_hints.c @@ -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) { diff --git a/src/SDL_internal.h b/src/SDL_internal.h index a345252f..8fcd96a7 100644 --- a/src/SDL_internal.h +++ b/src/SDL_internal.h @@ -265,6 +265,12 @@ extern "C" { #include "SDL_utils_c.h" #include "SDL_hashtable.h" +#define PUSH_SDL_ERROR() \ + { char *_error = SDL_strdup(SDL_GetError()); + +#define POP_SDL_ERROR() \ + SDL_SetError("%s", _error); SDL_free(_error); } + // Do any initialization that needs to happen before threads are started extern void SDL_InitMainThread(void); diff --git a/src/SDL_log.c b/src/SDL_log.c index 10a814ff..da55dcf1 100644 --- a/src/SDL_log.c +++ b/src/SDL_log.c @@ -587,6 +587,25 @@ void SDL_LogMessageV(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_S return; } +#if defined(SDL_PLATFORM_NGAGE) + extern void NGAGE_vnprintf(char *buf, size_t size, const char *fmt, va_list ap); + char buf[1024]; + NGAGE_vnprintf(buf, sizeof(buf), fmt, ap); + +#ifdef ENABLE_FILE_LOG + FILE* file; + file = fopen("E:/SDL_Log.txt", "a"); + if (file) + { + vfprintf(file, fmt, ap); + fprintf(file, "\n"); + (void)fclose(file); + } +#endif + + return; +#endif + // Render into stack buffer va_copy(aq, ap); len = SDL_vsnprintf(stack_buf, sizeof(stack_buf), fmt, aq); @@ -767,9 +786,14 @@ static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority (void)fclose(pFile); } } +#elif defined(SDL_PLATFORM_NGAGE) + { + /* Nothing to do here. */ + } #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/src/atomic/SDL_atomic.c b/src/atomic/SDL_atomic.c index 5e7774da..7a575ac2 100644 --- a/src/atomic/SDL_atomic.c +++ b/src/atomic/SDL_atomic.c @@ -163,7 +163,7 @@ bool SDL_CompareAndSwapAtomicU32(SDL_AtomicU32 *a, Uint32 oldval, Uint32 newval) #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); diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index cf817e4f..7dc247b1 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -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 @@ -410,6 +413,7 @@ static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid, SDL_LockRWLockForReading(current_audio.device_hash_lock); SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &logdev); if (logdev) { + SDL_assert(logdev->instance_id == devid); device = logdev->physical_device; SDL_assert(device != NULL); RefPhysicalAudioDevice(device); // reference it, in case the logical device migrates to a new default. @@ -459,6 +463,7 @@ static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) // ! } else { SDL_LockRWLockForReading(current_audio.device_hash_lock); SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &device); + SDL_assert(device->instance_id == devid); SDL_UnlockRWLock(current_audio.device_hash_lock); if (!device) { @@ -883,6 +888,7 @@ static bool SDLCALL FindLowestDeviceID(void *userdata, const SDL_HashTable *tabl if (isphysical && (devid_recording == data->recording) && (devid < data->highest)) { data->highest = devid; data->result = (SDL_AudioDevice *) value; + SDL_assert(data->result->instance_id == devid); } return true; // keep iterating. } @@ -1051,7 +1057,10 @@ static bool SDLCALL DestroyOnePhysicalAudioDevice(void *userdata, const SDL_Hash const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; const bool isphysical = !!(devid & (1<<1)); if (isphysical) { - DestroyPhysicalAudioDevice((SDL_AudioDevice *) value); + SDL_AudioDevice *dev = (SDL_AudioDevice *) value; + + SDL_assert(dev->instance_id == devid); + DestroyPhysicalAudioDevice(dev); } return true; // keep iterating. } @@ -1064,9 +1073,16 @@ 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); @@ -1147,7 +1163,20 @@ 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)); - const int br = SDL_GetAtomicInt(&logdev->paused) ? 0 : SDL_GetAudioStreamDataAdjustGain(stream, device_buffer, buffer_size, logdev->gain); + int br = 0; + + if (!SDL_GetAtomicInt(&logdev->paused)) { + if (logdev->iteration_start) { + logdev->iteration_start(logdev->iteration_userdata, logdev->instance_id, true); + } + + br = SDL_GetAudioStreamDataAdjustGain(stream, device_buffer, buffer_size, logdev->gain); + + if (logdev->iteration_end) { + logdev->iteration_end(logdev->iteration_userdata, logdev->instance_id, false); + } + } + 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. @@ -1185,6 +1214,10 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) SDL_memset(mix_buffer, '\0', work_buffer_size); // start with silence. } + if (logdev->iteration_start) { + logdev->iteration_start(logdev->iteration_userdata, logdev->instance_id, true); + } + for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) { // We should have updated this elsewhere if the format changed! SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &outspec, NULL, NULL)); @@ -1207,6 +1240,10 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) } } + if (logdev->iteration_end) { + logdev->iteration_end(logdev->iteration_userdata, logdev->instance_id, false); + } + if (postmix) { SDL_assert(mix_buffer == device->postmix_buffer); postmix(logdev->postmix_userdata, &outspec, mix_buffer, work_buffer_size); @@ -1385,6 +1422,7 @@ static int SDLCALL RecordingAudioThread(void *devicep) // thread entry point typedef struct CountAudioDevicesData { int devs_seen; + int devs_skipped; const int num_devices; SDL_AudioDeviceID *result; const bool recording; @@ -1400,7 +1438,13 @@ static bool SDLCALL CountAudioDevices(void *userdata, const SDL_HashTable *table const bool isphysical = !!(devid & (1<<1)); if (isphysical && (devid_recording == data->recording)) { SDL_assert(data->devs_seen < data->num_devices); - data->result[data->devs_seen++] = devid; + SDL_AudioDevice *device = (SDL_AudioDevice *) value; // this is normally risky, but we hold the device_hash_lock here. + const bool zombie = SDL_GetAtomicInt(&device->zombie) != 0; + if (zombie) { + data->devs_skipped++; + } else { + data->result[data->devs_seen++] = devid; + } } return true; // keep iterating. } @@ -1416,10 +1460,11 @@ static SDL_AudioDeviceID *GetAudioDevices(int *count, bool recording) num_devices = SDL_GetAtomicInt(recording ? ¤t_audio.recording_device_count : ¤t_audio.playback_device_count); result = (SDL_AudioDeviceID *) SDL_malloc((num_devices + 1) * sizeof (SDL_AudioDeviceID)); if (result) { - CountAudioDevicesData data = { 0, num_devices, result, recording }; + CountAudioDevicesData data = { 0, 0, num_devices, result, recording }; SDL_IterateHashTable(current_audio.device_hash, CountAudioDevices, &data); - SDL_assert(data.devs_seen == num_devices); - result[data.devs_seen] = 0; // null-terminated. + 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); @@ -1464,6 +1509,7 @@ static bool SDLCALL FindAudioDeviceByCallback(void *userdata, const SDL_HashTabl 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. } } @@ -1502,12 +1548,33 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) { + // bit #1 of devid is set for physical devices and unset for logical. + const bool islogical = !(devid & (1<<1)); const char *result = NULL; - SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); - if (device) { - result = SDL_GetPersistentString(device->name); + const void *vdev = NULL; + + if (!SDL_GetCurrentAudioDriver()) { + SDL_SetError("Audio subsystem is not initialized"); + } else { + // This does not call ObtainPhysicalAudioDevice() because the device's name never changes, so + // it doesn't have to lock the whole device. However, just to make sure the device pointer itself + // remains valid (in case the device is unplugged at the wrong moment), we hold the + // device_hash_lock while we copy the string. + SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, &vdev); + if (!vdev) { + SDL_SetError("Invalid audio device instance ID"); + } else if (islogical) { + const SDL_LogicalAudioDevice *logdev = (const SDL_LogicalAudioDevice *) vdev; + SDL_assert(logdev->instance_id == devid); + result = SDL_GetPersistentString(logdev->physical_device->name); + } else { + const SDL_AudioDevice *device = (const SDL_AudioDevice *) vdev; + SDL_assert(device->instance_id == devid); + result = SDL_GetPersistentString(device->name); + } + SDL_UnlockRWLock(current_audio.device_hash_lock); } - ReleaseAudioDevice(device); return result; } @@ -1539,7 +1606,9 @@ int *SDL_GetAudioDeviceChannelMap(SDL_AudioDeviceID devid, int *count) SDL_AudioDevice *device = ObtainPhysicalAudioDeviceDefaultAllowed(devid); if (device) { channels = device->spec.channels; - result = SDL_ChannelMapDup(device->chmap, channels); + if (channels > 0 && device->chmap) { + result = SDL_ChannelMapDup(device->chmap, channels); + } } ReleaseAudioDevice(device); @@ -1715,13 +1784,18 @@ static bool OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec SDL_copyp(&spec, inspec ? inspec : &device->default_spec); PrepareAudioFormat(device->recording, &spec); - /* We allow the device format to change if it's better than the current settings (by various definitions of "better"). This prevents - something low quality, like an old game using S8/8000Hz audio, from ruining a music thing playing at CD quality that tries to open later. - (or some VoIP library that opens for mono output ruining your surround-sound game because it got there first). + /* We impose a simple minimum on device formats. This prevents something low quality, like an old game using S8/8000Hz audio, + from ruining a music thing playing at CD quality that tries to open later, or some VoIP library that opens for mono output + ruining your surround-sound game because it got there first. These are just requests! The backend may change any of these values during OpenDevice method! */ - device->spec.format = (SDL_AUDIO_BITSIZE(device->default_spec.format) >= SDL_AUDIO_BITSIZE(spec.format)) ? device->default_spec.format : spec.format; - device->spec.freq = SDL_max(device->default_spec.freq, spec.freq); - device->spec.channels = SDL_max(device->default_spec.channels, spec.channels); + + const SDL_AudioFormat minimum_format = device->recording ? DEFAULT_AUDIO_RECORDING_FORMAT : DEFAULT_AUDIO_PLAYBACK_FORMAT; + const int minimum_channels = device->recording ? DEFAULT_AUDIO_RECORDING_CHANNELS : DEFAULT_AUDIO_PLAYBACK_CHANNELS; + const int minimum_freq = device->recording ? DEFAULT_AUDIO_RECORDING_FREQUENCY : DEFAULT_AUDIO_PLAYBACK_FREQUENCY; + + device->spec.format = (SDL_AUDIO_BITSIZE(minimum_format) >= SDL_AUDIO_BITSIZE(spec.format)) ? minimum_format : spec.format; + device->spec.channels = SDL_max(minimum_channels, spec.channels); + device->spec.freq = SDL_max(minimum_freq, spec.freq); device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq); SDL_UpdatedAudioDeviceFormat(device); // start this off sane. @@ -1889,8 +1963,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) { @@ -1909,6 +1984,21 @@ bool SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallba return result; } +bool SDL_SetAudioIterationCallbacks(SDL_AudioDeviceID devid, SDL_AudioIterationCallback iter_start, SDL_AudioIterationCallback iter_end, void *userdata) +{ + SDL_AudioDevice *device = NULL; + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); + bool result = false; + if (logdev) { + logdev->iteration_start = iter_start; + logdev->iteration_end = iter_end; + logdev->iteration_userdata = userdata; + result = true; + } + ReleaseAudioDevice(device); + return result; +} + bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *streams, int num_streams) { const bool islogical = !(devid & (1<<1)); diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index f751b0e5..1d688400 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -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); @@ -768,16 +768,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,42 +827,16 @@ 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) @@ -857,9 +863,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,6 +874,181 @@ 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) +{ + if (!stream) { + return SDL_InvalidParamError("stream"); + } else if (!channel_buffers) { + return SDL_InvalidParamError("channel_buffers"); + } else if (num_samples < 0) { + return SDL_InvalidParamError("num_samples"); + } else 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) +{ + if (!stream) { + return SDL_InvalidParamError("stream"); + } else if (!buf) { + return SDL_InvalidParamError("buf"); + } else if (len < 0) { + return SDL_InvalidParamError("len"); + } else 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) { @@ -901,8 +1081,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 +1136,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 +1160,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 +1178,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 +1199,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 +1269,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 +1281,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 +1292,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). @@ -1326,11 +1506,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) { @@ -1357,8 +1532,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/src/audio/SDL_audioqueue.h b/src/audio/SDL_audioqueue.h index 46629462..dc3c71c2 100644 --- a/src/audio/SDL_audioqueue.h +++ b/src/audio/SDL_audioqueue.h @@ -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/src/audio/SDL_audiotypecvt.c b/src/audio/SDL_audiotypecvt.c index a27575f2..78c325e5 100644 --- a/src/audio/SDL_audiotypecvt.c +++ b/src/audio/SDL_audiotypecvt.c @@ -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 @@ -774,41 +774,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); }) } @@ -839,8 +839,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) { @@ -858,7 +858,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; @@ -867,12 +867,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; @@ -896,7 +896,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: @@ -905,22 +905,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/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 4a88bd23..603eaab1 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -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. @@ -201,7 +201,7 @@ struct SDL_AudioQueue; // forward decl. struct SDL_AudioStream { - SDL_Mutex* lock; + SDL_Mutex *lock; SDL_PropertiesID props; @@ -217,7 +217,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; @@ -264,6 +264,13 @@ struct SDL_LogicalAudioDevice // true if device was opened with SDL_OpenAudioDeviceStream (so it forbids binding changes, etc). bool simplified; + // If non-NULL, callback into the app that alerts it to start/end of device iteration. + SDL_AudioIterationCallback iteration_start; + SDL_AudioIterationCallback iteration_end; + + // App-supplied pointer for iteration callbacks. + void *iteration_userdata; + // If non-NULL, callback into the app that lets them access the final postmix buffer. SDL_AudioPostmixCallback postmix; @@ -386,6 +393,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/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c index 3360bec9..5436be07 100644 --- a/src/audio/aaudio/SDL_aaudio.c +++ b/src/audio/aaudio/SDL_aaudio.c @@ -308,6 +308,12 @@ static bool BuildAAudioStream(SDL_AudioDevice *device) ctx.AAudioStreamBuilder_setFormat(builder, format); ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq); ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels); + + // If no specific buffer size has been requested, the device will pick the optimal + if(SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES)) { + ctx.AAudioStreamBuilder_setBufferCapacityInFrames(builder, 2 * device->sample_frames); // AAudio requires that the buffer capacity is at least + ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, device->sample_frames); // twice the size of the data callback buffer size + } const aaudio_direction_t direction = (recording ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT); ctx.AAudioStreamBuilder_setDirection(builder, direction); @@ -366,7 +372,7 @@ static bool BuildAAudioStream(SDL_AudioDevice *device) hidden->processed_bytes = 0; hidden->callback_bytes = 0; - hidden->semaphore = SDL_CreateSemaphore(recording ? 0 : hidden->num_buffers); + hidden->semaphore = SDL_CreateSemaphore(recording ? 0 : hidden->num_buffers - 1); if (!hidden->semaphore) { LOGI("SDL Failed SDL_CreateSemaphore %s recording:%d", SDL_GetError(), recording); return false; diff --git a/src/audio/aaudio/SDL_aaudiofuncs.h b/src/audio/aaudio/SDL_aaudiofuncs.h index 02988212..1d9f7104 100644 --- a/src/audio/aaudio/SDL_aaudiofuncs.h +++ b/src/audio/aaudio/SDL_aaudiofuncs.h @@ -31,7 +31,7 @@ SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSamplesPerFrame, (AAudioStreamBuild SDL_PROC(void, AAudioStreamBuilder_setFormat, (AAudioStreamBuilder * builder, aaudio_format_t format)) SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * builder, aaudio_sharing_mode_t sharingMode)) SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction)) -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames)) +SDL_PROC(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames)) SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode)) SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) // API 28 SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) // API 28 diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c index badbc43e..f93433e6 100644 --- a/src/audio/alsa/SDL_alsa_audio.c +++ b/src/audio/alsa/SDL_alsa_audio.c @@ -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__) @@ -87,7 +88,7 @@ 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 *); @@ -348,28 +349,25 @@ static char *get_pcm_str(void *handle) // 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 rc = ALSA_snd_pcm_avail(device->hidden->pcm); + if (rc < 0) { const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0); 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; } @@ -431,8 +429,11 @@ 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! @@ -671,7 +672,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; } @@ -1156,7 +1157,7 @@ static bool ALSA_OpenDevice(SDL_AudioDevice *device) #if SDL_ALSA_DEBUG snd_pcm_uframes_t bufsize; ALSA_snd_pcm_hw_params_get_buffer_size(cfg_ctx.hwparams, &bufsize); - SDL_LogError(SDL_LOG_CATEGORY_AUDIO, + SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, "ALSA: period size = %ld, periods = %u, buffer size = %lu", cfg_ctx.persize, cfg_ctx.periods, bufsize); #endif @@ -1215,7 +1216,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 +1446,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() +{ + 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() +{ + if (udev_initialized) { + SDL_UDEV_DelCallback(ALSA_udev_callback); + SDL_UDEV_Quit(); + udev_initialized = false; + } +} + +#else + +static bool ALSA_start_udev() +{ + return false; +} + +static void ALSA_stop_udev() +{ +} + +#endif // SDL_USE_LIBUDEV + static void ALSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) { ALSA_guess_device_prefix(); @@ -1454,17 +1514,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 +1541,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/src/audio/emscripten/SDL_emscriptenaudio.c b/src/audio/emscripten/SDL_emscriptenaudio.c index 55fb5b49..4be91910 100644 --- a/src/audio/emscripten/SDL_emscriptenaudio.c +++ b/src/audio/emscripten/SDL_emscriptenaudio.c @@ -56,7 +56,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); @@ -189,7 +189,7 @@ static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device) } // limit to native freq - device->spec.freq = EM_ASM_INT({ return Module['SDL3'].audioContext.sampleRate; }); + device->spec.freq = MAIN_THREAD_EM_ASM_INT({ return Module['SDL3'].audioContext.sampleRate; }); device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq) * 2; // double the buffer size, some browsers need more, and we'll just have to live with the latency. SDL_UpdatedAudioDeviceFormat(device); diff --git a/src/audio/ngage/SDL_ngageaudio.c b/src/audio/ngage/SDL_ngageaudio.c new file mode 100644 index 00000000..3595bd70 --- /dev/null +++ b/src/audio/ngage/SDL_ngageaudio.c @@ -0,0 +1,103 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifdef SDL_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/src/audio/ngage/SDL_ngageaudio.cpp b/src/audio/ngage/SDL_ngageaudio.cpp new file mode 100644 index 00000000..9acf030c --- /dev/null +++ b/src/audio/ngage/SDL_ngageaudio.cpp @@ -0,0 +1,368 @@ +/* + 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. +*/ + +#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 resetted 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", __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/src/audio/ngage/SDL_ngageaudio.h b/src/audio/ngage/SDL_ngageaudio.h new file mode 100644 index 00000000..dda2c91e --- /dev/null +++ b/src/audio/ngage/SDL_ngageaudio.h @@ -0,0 +1,44 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_ngageaudio_h +#define SDL_ngageaudio_h + +typedef struct SDL_PrivateAudioData +{ + Uint8 *buffer; + +} SDL_PrivateAudioData; + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../SDL_sysaudio.h" + +SDL_AudioDevice *NGAGE_GetAudioDeviceAddr(); + +#ifdef __cplusplus +} +#endif + +#endif // SDL_ngageaudio_h diff --git a/src/audio/ngage/SDL_ngageaudio.hpp b/src/audio/ngage/SDL_ngageaudio.hpp new file mode 100644 index 00000000..e0635f5a --- /dev/null +++ b/src/audio/ngage/SDL_ngageaudio.hpp @@ -0,0 +1,98 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#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/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c index f070ea0d..a9e95a6f 100644 --- a/src/audio/pipewire/SDL_pipewire.c +++ b/src/audio/pipewire/SDL_pipewire.c @@ -546,7 +546,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); @@ -1114,7 +1114,13 @@ static bool PIPEWIRE_OpenDevice(SDL_AudioDevice *device) stream_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME); if (!stream_name || *stream_name == '\0') { - stream_name = "Audio Stream"; + if (app_name) { + stream_name = app_name; + } else if (app_id) { + stream_name = app_id; + } else { + stream_name = "SDL Audio Stream"; + } } /* diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 2403c7d0..4d618a6e 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -269,19 +269,9 @@ static const char *getAppName(void) return SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING); } -static void ThreadedMainloopSignal(void) -{ - PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // alert waiting threads to unblock. - - // we need to kill any SDL_SetError state; we didn't create this thread - // so its SDL TLS slot will leak otherwise, so we do this every time - // we're (presumably) losing control of the thread. - SDL_CleanupTLS(); -} - static void OperationStateChangeCallback(pa_operation *o, void *userdata) { - ThreadedMainloopSignal(); // just signal any waiting code, it can look up the details. + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details. } /* This function assume you are holding `mainloop`'s lock. The operation is unref'd in here, assuming @@ -323,7 +313,7 @@ static void DisconnectFromPulseServer(void) static void PulseContextStateChangeCallback(pa_context *context, void *userdata) { - ThreadedMainloopSignal(); // just signal any waiting code, it can look up the details. + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details. } static bool ConnectToPulseServer(void) @@ -410,7 +400,7 @@ static void WriteCallback(pa_stream *p, size_t nbytes, void *userdata) struct SDL_PrivateAudioData *h = (struct SDL_PrivateAudioData *)userdata; //SDL_Log("PULSEAUDIO WRITE CALLBACK! nbytes=%u", (unsigned int) nbytes); h->bytes_requested += nbytes; - ThreadedMainloopSignal(); + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } // This function waits until it is possible to write a full sound buffer @@ -481,7 +471,7 @@ static Uint8 *PULSEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) static void ReadCallback(pa_stream *p, size_t nbytes, void *userdata) { //SDL_Log("PULSEAUDIO READ CALLBACK! nbytes=%u", (unsigned int) nbytes); - ThreadedMainloopSignal(); // the recording code queries what it needs, we just need to signal to end any wait + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // the recording code queries what it needs, we just need to signal to end any wait } static bool PULSEAUDIO_WaitRecordingDevice(SDL_AudioDevice *device) @@ -602,7 +592,7 @@ static void PULSEAUDIO_CloseDevice(SDL_AudioDevice *device) static void PulseStreamStateChangeCallback(pa_stream *stream, void *userdata) { - ThreadedMainloopSignal(); // just signal any waiting code, it can look up the details. + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details. } static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) @@ -682,7 +672,8 @@ static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) paspec.rate = device->spec.freq; // Reduced prebuffering compared to the defaults. - paattr.fragsize = device->buffer_size; // despite the name, this is only used for recording devices, according to PulseAudio docs! + + paattr.fragsize = device->buffer_size * 2; // despite the name, this is only used for recording devices, according to PulseAudio docs! (times 2 because we want _more_ than our buffer size sent from the server at a time, which helps some drivers). paattr.tlength = device->buffer_size; paattr.prebuf = -1; paattr.maxlength = -1; @@ -803,7 +794,7 @@ static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, if (i) { AddPulseAudioDevice(false, i->description, i->name, i->index, &i->sample_spec); } - ThreadedMainloopSignal(); + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } // This is called when PulseAudio adds a recording ("source") device. @@ -813,7 +804,7 @@ static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_la if (i && (include_monitors || (i->monitor_of_sink == PA_INVALID_INDEX))) { AddPulseAudioDevice(true, i->description, i->name, i->index, &i->sample_spec); } - ThreadedMainloopSignal(); + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } static void ServerInfoCallback(pa_context *c, const pa_server_info *i, void *data) @@ -838,7 +829,7 @@ static void ServerInfoCallback(pa_context *c, const pa_server_info *i, void *dat } } - ThreadedMainloopSignal(); + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } static bool FindAudioDeviceByIndex(SDL_AudioDevice *device, void *userdata) @@ -882,7 +873,7 @@ static void HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint3 SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByCallback(FindAudioDeviceByIndex, (void *)(uintptr_t)idx)); } } - ThreadedMainloopSignal(); + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } static bool CheckDefaultDevice(const bool changed, char *device_path) diff --git a/src/audio/vita/SDL_vitaaudio.c b/src/audio/vita/SDL_vitaaudio.c index e194f212..86e8a691 100644 --- a/src/audio/vita/SDL_vitaaudio.c +++ b/src/audio/vita/SDL_vitaaudio.c @@ -130,7 +130,8 @@ static bool VITAAUD_OpenDevice(SDL_AudioDevice *device) static bool VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { - return (sceAudioOutOutput(device->hidden->port, buffer) == 0); + // sceAudioOutOutput returns amount of samples queued or < 0 on error + return (sceAudioOutOutput(device->hidden->port, buffer) >= 0); } // This function waits until it is possible to write a full sound buffer diff --git a/src/audio/wasapi/SDL_wasapi.c b/src/audio/wasapi/SDL_wasapi.c index db0974b0..4b782d34 100644 --- a/src/audio/wasapi/SDL_wasapi.c +++ b/src/audio/wasapi/SDL_wasapi.c @@ -750,7 +750,7 @@ static bool mgmtthrtask_PrepDevice(void *userdata) // 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; diff --git a/src/camera/coremedia/SDL_camera_coremedia.m b/src/camera/coremedia/SDL_camera_coremedia.m index 2ecfd13c..f58fb927 100644 --- a/src/camera/coremedia/SDL_camera_coremedia.m +++ b/src/camera/coremedia/SDL_camera_coremedia.m @@ -239,7 +239,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; } diff --git a/src/camera/emscripten/SDL_camera_emscripten.c b/src/camera/emscripten/SDL_camera_emscripten.c index fa2a5114..36418c57 100644 --- a/src/camera/emscripten/SDL_camera_emscripten.c +++ b/src/camera/emscripten/SDL_camera_emscripten.c @@ -61,7 +61,7 @@ static SDL_CameraFrameResult EMSCRIPTENCAMERA_AcquireFrame(SDL_Camera *device, S SDL3.camera.ctx2d.drawImage(SDL3.camera.video, 0, 0, w, h); const imgrgba = SDL3.camera.ctx2d.getImageData(0, 0, w, h).data; - Module.HEAPU8.set(imgrgba, rgba); + HEAPU8.set(imgrgba, rgba); return 1; }, device->actual_spec.width, device->actual_spec.height, rgba); diff --git a/src/camera/mediafoundation/SDL_camera_mediafoundation.c b/src/camera/mediafoundation/SDL_camera_mediafoundation.c index d9d627d0..433be4d8 100644 --- a/src/camera/mediafoundation/SDL_camera_mediafoundation.c +++ b/src/camera/mediafoundation/SDL_camera_mediafoundation.c @@ -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/src/camera/pipewire/SDL_camera_pipewire.c b/src/camera/pipewire/SDL_camera_pipewire.c index 255ea39a..056ed4f7 100644 --- a/src/camera/pipewire/SDL_camera_pipewire.c +++ b/src/camera/pipewire/SDL_camera_pipewire.c @@ -838,7 +838,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; diff --git a/src/camera/v4l2/SDL_camera_v4l2.c b/src/camera/v4l2/SDL_camera_v4l2.c index 9cdb54b0..664d4418 100644 --- a/src/camera/v4l2/SDL_camera_v4l2.c +++ b/src/camera/v4l2/SDL_camera_v4l2.c @@ -193,7 +193,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 +230,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 +241,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; diff --git a/src/camera/vita/SDL_camera_vita.c b/src/camera/vita/SDL_camera_vita.c index 42a5a89d..874c6d8d 100644 --- a/src/camera/vita/SDL_camera_vita.c +++ b/src/camera/vita/SDL_camera_vita.c @@ -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); } } @@ -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/src/core/SDL_core_unsupported.c b/src/core/SDL_core_unsupported.c index af963ed8..9ace0e0b 100644 --- a/src/core/SDL_core_unsupported.c +++ b/src/core/SDL_core_unsupported.c @@ -119,7 +119,7 @@ void *SDL_GetAndroidActivity(void) } SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidCachePath(void); -const char* SDL_GetAndroidCachePath(void) +const char *SDL_GetAndroidCachePath(void) { SDL_Unsupported(); return NULL; @@ -127,7 +127,7 @@ const char* SDL_GetAndroidCachePath(void) SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidExternalStoragePath(void); -const char* SDL_GetAndroidExternalStoragePath(void) +const char *SDL_GetAndroidExternalStoragePath(void) { SDL_Unsupported(); return NULL; @@ -172,7 +172,7 @@ bool SDL_SendAndroidMessage(Uint32 command, int param) } 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; diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index 7b666c5f..6cdffef2 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -371,6 +371,7 @@ static jmethodID midShowTextInput; static jmethodID midSupportsRelativeMouse; static jmethodID midOpenFileDescriptor; static jmethodID midShowFileDialog; +static jmethodID midGetPreferredLocales; // audio manager static jclass mAudioManagerClass; @@ -660,6 +661,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl midSupportsRelativeMouse = (*env)->GetStaticMethodID(env, mActivityClass, "supportsRelativeMouse", "()Z"); midOpenFileDescriptor = (*env)->GetStaticMethodID(env, mActivityClass, "openFileDescriptor", "(Ljava/lang/String;Ljava/lang/String;)I"); midShowFileDialog = (*env)->GetStaticMethodID(env, mActivityClass, "showFileDialog", "([Ljava/lang/String;ZZI)Z"); + midGetPreferredLocales = (*env)->GetStaticMethodID(env, mActivityClass, "getPreferredLocales", "()Ljava/lang/String;"); if (!midClipboardGetText || !midClipboardHasText || @@ -691,7 +693,8 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl !midShowTextInput || !midSupportsRelativeMouse || !midOpenFileDescriptor || - !midShowFileDialog) { + !midShowFileDialog || + !midGetPreferredLocales) { __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?"); } @@ -2585,65 +2588,22 @@ bool Android_JNI_ShowToast(const char *message, int duration, int gravity, int x bool Android_JNI_GetLocale(char *buf, size_t buflen) { - AConfiguration *cfg; - - SDL_assert(buflen > 6); - - // Need to re-create the asset manager if locale has changed (SDL_EVENT_LOCALE_CHANGED) - Internal_Android_Destroy_AssetManager(); - - if (!asset_manager) { - Internal_Android_Create_AssetManager(); - } - - if (!asset_manager) { - return false; - } - - cfg = AConfiguration_new(); - if (!cfg) { - return false; - } - - { - char language[2] = {}; - char country[2] = {}; - size_t id = 0; - - AConfiguration_fromAssetManager(cfg, asset_manager); - AConfiguration_getLanguage(cfg, language); - AConfiguration_getCountry(cfg, country); - - // Indonesian is "id" according to ISO 639.2, but on Android is "in" because of Java backwards compatibility - if (language[0] == 'i' && language[1] == 'n') { - language[1] = 'd'; - } - - // copy language (not null terminated) - if (language[0]) { - buf[id++] = language[0]; - if (language[1]) { - buf[id++] = language[1]; + bool result = false; + if (buf && buflen > 0) { + *buf = '\0'; + JNIEnv *env = Android_JNI_GetEnv(); + jstring string = (jstring)(*env)->CallStaticObjectMethod(env, mActivityClass, midGetPreferredLocales); + if (string) { + const char *utf8string = (*env)->GetStringUTFChars(env, string, NULL); + if (utf8string) { + result = true; + SDL_strlcpy(buf, utf8string, buflen); + (*env)->ReleaseStringUTFChars(env, string, utf8string); } + (*env)->DeleteLocalRef(env, string); } - - buf[id++] = '_'; - - // copy country (not null terminated) - if (country[0]) { - buf[id++] = country[0]; - if (country[1]) { - buf[id++] = country[1]; - } - } - - buf[id++] = '\0'; - SDL_assert(id <= buflen); } - - AConfiguration_delete(cfg); - - return true; + return result; } bool Android_JNI_OpenURL(const char *url) @@ -2731,7 +2691,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); @@ -2787,7 +2747,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) { diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h index 620639ca..925cc199 100644 --- a/src/core/android/SDL_android.h +++ b/src/core/android/SDL_android.h @@ -151,7 +151,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/src/core/freebsd/SDL_evdev_kbd_freebsd.c b/src/core/freebsd/SDL_evdev_kbd_freebsd.c index 16a21713..afd3ad05 100644 --- a/src/core/freebsd/SDL_evdev_kbd_freebsd.c +++ b/src/core/freebsd/SDL_evdev_kbd_freebsd.c @@ -324,7 +324,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/src/core/gdk/SDL_gdk.cpp b/src/core/gdk/SDL_gdk.cpp index 738daa5b..4e792ef2 100644 --- a/src/core/gdk/SDL_gdk.cpp +++ b/src/core/gdk/SDL_gdk.cpp @@ -105,7 +105,7 @@ bool GDK_RegisterChangeNotifications(void) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "[GDK] in RegisterAppConstrainedChangeNotification handler"); SDL_VideoDevice *_this = SDL_GetVideoDevice(); if (_this) { - if (constrained) { + if (constrained && !((_this->windows) && _this->windows->text_input_active)) { SDL_SetKeyboardFocus(NULL); } else { SDL_SetKeyboardFocus(_this->windows); diff --git a/src/core/haiku/SDL_BApp.h b/src/core/haiku/SDL_BApp.h index cbd3e3a0..d540c36d 100644 --- a/src/core/haiku/SDL_BApp.h +++ b/src/core/haiku/SDL_BApp.h @@ -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/src/core/haiku/SDL_BeApp.cc b/src/core/haiku/SDL_BeApp.cc index 350f7f3e..4737f48e 100644 --- a/src/core/haiku/SDL_BeApp.cc +++ b/src/core/haiku/SDL_BeApp.cc @@ -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/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index 5cb450c8..b61a1cd9 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -49,6 +49,7 @@ static bool LoadDBUSSyms(void) 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); @@ -67,6 +68,7 @@ static bool LoadDBUSSyms(void) 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(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); diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h index b22a92af..230b20fd 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -47,6 +47,7 @@ typedef struct SDL_DBusContext 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 *); @@ -66,6 +67,7 @@ typedef struct SDL_DBusContext 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 *); 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 *); diff --git a/src/core/linux/SDL_evdev.c b/src/core/linux/SDL_evdev.c index 5746e2ef..902f7e69 100644 --- a/src/core/linux/SDL_evdev.c +++ b/src/core/linux/SDL_evdev.c @@ -287,8 +287,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, diff --git a/src/core/linux/SDL_evdev.h b/src/core/linux/SDL_evdev.h index d3e2fe16..c3de16fe 100644 --- a/src/core/linux/SDL_evdev.h +++ b/src/core/linux/SDL_evdev.h @@ -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/src/core/linux/SDL_evdev_kbd.c b/src/core/linux/SDL_evdev_kbd.c index b1a56644..32340bff 100644 --- a/src/core/linux/SDL_evdev_kbd.c +++ b/src/core/linux/SDL_evdev_kbd.c @@ -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; @@ -978,7 +978,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/src/core/linux/SDL_evdev_kbd.h b/src/core/linux/SDL_evdev_kbd.h index 6ea19fb0..b7a2834c 100644 --- a/src/core/linux/SDL_evdev_kbd.h +++ b/src/core/linux/SDL_evdev_kbd.h @@ -27,7 +27,7 @@ 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/src/core/linux/SDL_progressbar.c b/src/core/linux/SDL_progressbar.c new file mode 100644 index 00000000..e50f8361 --- /dev/null +++ b/src/core/linux/SDL_progressbar.c @@ -0,0 +1,159 @@ +/* + 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_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() +{ + 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] != '_') { + SDL_memmove(app_id + 1, app_id, SDL_strlen(app_id) + 1); + app_id[0] = '_'; + } + + // Create full path + char path[1024]; + SDL_snprintf(path, sizeof(path), "/org/libsdl/%s_%d", app_id, getpid()); + + SDL_free(app_id); + + return SDL_strdup(path); +} + +static char *GetAppDesktopPath() +{ + 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"; + int dbus_type_boolean_str = DBUS_TYPE_BOOLEAN; + int dbus_type_double_str = DBUS_TYPE_DOUBLE; + + 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 paramter + 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, (const char *)&dbus_type_boolean_str, &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, (const char *)&dbus_type_double_str, &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/src/core/linux/SDL_progressbar.h b/src/core/linux/SDL_progressbar.h new file mode 100644 index 00000000..da9b815f --- /dev/null +++ b/src/core/linux/SDL_progressbar.h @@ -0,0 +1,30 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_prograssbar_h_ +#define SDL_prograssbar_h_ + +#include "../../video/SDL_sysvideo.h" +#include "SDL_internal.h" + +extern bool DBUS_ApplyWindowProgress(SDL_VideoDevice *_this, SDL_Window *window); + +#endif // SDL_prograssbar_h_ diff --git a/src/core/linux/SDL_udev.c b/src/core/linux/SDL_udev.c index 907c34c7..def0e6a8 100644 --- a/src/core/linux/SDL_udev.c +++ b/src/core/linux/SDL_udev.c @@ -224,7 +224,7 @@ bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *pr struct stat statbuf; char type; struct udev_device *dev; - const char* val; + const char *val; int class_temp; if (!_this) { diff --git a/src/core/ngage/SDL_ngage.cpp b/src/core/ngage/SDL_ngage.cpp new file mode 100644 index 00000000..5f14af1d --- /dev/null +++ b/src/core/ngage/SDL_ngage.cpp @@ -0,0 +1,77 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include +#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_printf(const char *fmt, ...) +{ + char buffer[512] = { 0 }; + + va_list ap; + va_start(ap, fmt); + vsprintf(buffer, fmt, ap); + va_end(ap); + + TBuf<512> buf; + buf.Copy(TPtrC8((TText8 *)buffer)); + + RDebug::Print(_L("%S"), &buf); +} + +void NGAGE_vnprintf(char *buf, size_t size, const char *fmt, va_list ap) +{ + char buffer[512] = { 0 }; + + vsprintf(buffer, fmt, ap); + + TBuf<512> tbuf; + tbuf.Copy(TPtrC8((TText8 *)buffer)); + + RDebug::Print(_L("%S"), &tbuf); + + strncpy(buf, buffer, size - 1); + buf[size - 1] = '\0'; +} + +TInt NGAGE_GetFreeHeapMemory() +{ + TInt free = 0; + return User::Available(free); +} + +#ifdef __cplusplus +} +#endif diff --git a/src/core/ngage/SDL_ngage.h b/src/core/ngage/SDL_ngage.h new file mode 100644 index 00000000..66c4c60e --- /dev/null +++ b/src/core/ngage/SDL_ngage.h @@ -0,0 +1,36 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_ngage_h +#define SDL_ngage_h + +#ifdef __cplusplus +extern "C" { +#endif + +bool NGAGE_IsClassicModel(); + +#ifdef __cplusplus +} +#endif + +#endif /* SDL_ngage_h */ diff --git a/src/core/windows/SDL_windows.c b/src/core/windows/SDL_windows.c index 3259e787..d96d8e0b 100644 --- a/src/core/windows/SDL_windows.c +++ b/src/core/windows/SDL_windows.c @@ -261,7 +261,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; @@ -270,37 +270,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 } diff --git a/src/cpuinfo/SDL_cpuinfo.c b/src/cpuinfo/SDL_cpuinfo.c index 81d1e914..fc747c19 100644 --- a/src/cpuinfo/SDL_cpuinfo.c +++ b/src/cpuinfo/SDL_cpuinfo.c @@ -115,7 +115,11 @@ #define CPU_CFG2_LSX (1 << 6) #define CPU_CFG2_LASX (1 << 7) -#if defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) && !defined(SDL_PLATFORM_MACOS) && !defined(SDL_PLATFORM_OPENBSD) && !defined(SDL_PLATFORM_FREEBSD) +#if !defined(SDL_CPUINFO_DISABLED) && \ + !((defined(SDL_PLATFORM_MACOS) && (defined(__ppc__) || defined(__ppc64__))) || (defined(SDL_PLATFORM_OPENBSD) && defined(__powerpc__))) && \ + !(defined(SDL_PLATFORM_FREEBSD) && defined(__powerpc__)) && \ + !(defined(SDL_PLATFORM_LINUX) && defined(__powerpc__) && defined(HAVE_GETAUXVAL)) && \ + defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) /* This is the brute force way of detecting instruction sets... the idea is borrowed from the libmpeg2 library - thanks! */ @@ -344,6 +348,8 @@ static int CPU_haveAltiVec(void) elf_aux_info(AT_HWCAP, &cpufeatures, sizeof(cpufeatures)); altivec = cpufeatures & PPC_FEATURE_HAS_ALTIVEC; return altivec; +#elif defined(SDL_PLATFORM_LINUX) && defined(__powerpc__) && defined(HAVE_GETAUXVAL) + altivec = getauxval(AT_HWCAP) & PPC_FEATURE_HAS_ALTIVEC; #elif defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) void (*handler)(int sig); handler = signal(SIGILL, illegal_instruction); @@ -415,6 +421,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) { @@ -462,6 +474,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. diff --git a/src/dialog/cocoa/SDL_cocoadialog.m b/src/dialog/cocoa/SDL_cocoadialog.m index fb9c5ad8..d12dab87 100644 --- a/src/dialog/cocoa/SDL_cocoadialog.m +++ b/src/dialog/cocoa/SDL_cocoadialog.m @@ -27,15 +27,44 @@ #import #import +static void AddFileExtensionType(NSMutableArray *types, const char *pattern_ptr) +{ + if (!*pattern_ptr) { + return; // in case the string had an extra ';' at the end. + } + + // -[UTType typeWithFilenameExtension] will return nil if there's a period in the string. It's better to + // allow too many files than not allow the one the user actually needs, so just take the part after the '.' + const char *dot = SDL_strrchr(pattern_ptr, '.'); + NSString *extstr = [NSString stringWithFormat: @"%s", dot ? (dot + 1) : pattern_ptr]; + if (@available(macOS 11.0, *)) { + UTType *uttype = [UTType typeWithFilenameExtension:extstr]; + if (uttype) { // still failed? Don't add the pattern. This is what the pre-macOS11 path does internally anyhow. + [types addObject:uttype]; + } + } else { + [types addObject:extstr]; + } +} + +static void ReactivateAfterDialog(void) +{ + for (NSRunningApplication *i in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) { + [i activateWithOptions:0]; + break; + } + [NSApp activateIgnoringOtherApps:YES]; +} + void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) { - SDL_Window* window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); + 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); @@ -87,7 +116,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil if (filters) { // On macOS 11.0 and up, this is an array of UTType. Prior to that, it's an array of NSString - NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:nfilters ]; + NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:nfilters]; int has_all_files = 0; for (int i = 0; i < nfilters; i++) { @@ -102,21 +131,14 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil for (char *c = pattern; *c; c++) { if (*c == ';') { *c = '\0'; - if(@available(macOS 11.0, *)) { - [types addObject: [UTType typeWithFilenameExtension:[NSString stringWithFormat: @"%s", pattern_ptr]]]; - } else { - [types addObject: [NSString stringWithFormat: @"%s", pattern_ptr]]; - } + AddFileExtensionType(types, pattern_ptr); pattern_ptr = c + 1; } else if (*c == '*') { has_all_files = 1; } } - if(@available(macOS 11.0, *)) { - [types addObject: [UTType typeWithFilenameExtension:[NSString stringWithFormat: @"%s", pattern_ptr]]]; - } else { - [types addObject: [NSString stringWithFormat: @"%s", pattern_ptr]]; - } + + AddFileExtensionType(types, pattern_ptr); // get the last piece of the string. SDL_free(pattern); } @@ -148,7 +170,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]; @@ -163,11 +185,13 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil const char *files[1] = { NULL }; callback(userdata, files, -1); } + + ReactivateAfterDialog(); }]; } 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]; @@ -182,6 +206,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil const char *files[1] = { NULL }; callback(userdata, files, -1); } + ReactivateAfterDialog(); } } diff --git a/src/dialog/haiku/SDL_haikudialog.cc b/src/dialog/haiku/SDL_haikudialog.cc index d60e3434..539d6a6a 100644 --- a/src/dialog/haiku/SDL_haikudialog.cc +++ b/src/dialog/haiku/SDL_haikudialog.cc @@ -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/src/dialog/unix/SDL_portaldialog.c b/src/dialog/unix/SDL_portaldialog.c index efecd124..dadfc9f5 100644 --- a/src/dialog/unix/SDL_portaldialog.c +++ b/src/dialog/unix/SDL_portaldialog.c @@ -288,12 +288,12 @@ 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); bool open_folders = false; switch (type) { diff --git a/src/dialog/windows/SDL_windowsdialog.c b/src/dialog/windows/SDL_windowsdialog.c index 6822944f..2f4a12e8 100644 --- a/src/dialog/windows/SDL_windowsdialog.c +++ b/src/dialog/windows/SDL_windowsdialog.c @@ -261,7 +261,7 @@ void windows_ShowFileDialog(void *ptr) chosen_files_list[nfiles] = NULL; - if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_folder, MAX_PATH, NULL, NULL) >= MAX_PATH) { + if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_folder, MAX_PATH, NULL, NULL) == 0) { SDL_SetError("Path too long or invalid character in path"); SDL_free(chosen_files_list); callback(userdata, NULL, -1); @@ -273,11 +273,11 @@ void windows_ShowFileDialog(void *ptr) SDL_strlcpy(chosen_file, chosen_folder, MAX_PATH); chosen_file[chosen_folder_size] = '\\'; - file_ptr += SDL_strlen(chosen_folder) + 1; + file_ptr += SDL_wcslen(file_ptr) + 1; while (*file_ptr) { nfiles++; - 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++) { @@ -295,7 +295,7 @@ void windows_ShowFileDialog(void *ptr) int diff = ((int) chosen_folder_size) + 1; - if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_file + diff, MAX_PATH - diff, NULL, NULL) >= MAX_PATH - diff) { + if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_file + diff, MAX_PATH - diff, NULL, NULL) == 0) { SDL_SetError("Path too long or invalid character in path"); for (size_t i = 0; i < nfiles - 1; i++) { @@ -308,7 +308,7 @@ void windows_ShowFileDialog(void *ptr) return; } - file_ptr += SDL_strlen(chosen_file) + 1 - diff; + file_ptr += SDL_wcslen(file_ptr) + 1; chosen_files_list[nfiles - 1] = SDL_strdup(chosen_file); @@ -327,7 +327,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 +348,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]); @@ -443,11 +443,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); } } diff --git a/src/dynapi/SDL_dynapi.h b/src/dynapi/SDL_dynapi.h index 99ef9a9e..e975be08 100644 --- a/src/dynapi/SDL_dynapi.h +++ b/src/dynapi/SDL_dynapi.h @@ -63,6 +63,8 @@ #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/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 44f9d0e0..91bd4300 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1250,6 +1250,10 @@ SDL3_0.0.0 { SDL_GetRenderTextureAddressMode; SDL_GetGPUDeviceProperties; SDL_CreateGPURenderer; + SDL_PutAudioStreamPlanarData; + SDL_SetAudioIterationCallbacks; + SDL_GetEventDescription; + SDL_PutAudioStreamDataNoCopy; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index a12f3fbf..fcc0bad7 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1275,3 +1275,7 @@ #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_SetAudioIterationCallbacks SDL_SetAudioIterationCallbacks_REAL +#define SDL_GetEventDescription SDL_GetEventDescription_REAL +#define SDL_PutAudioStreamDataNoCopy SDL_PutAudioStreamDataNoCopy_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index d7988ac2..60e0dfea 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -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),) @@ -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),(),) @@ -1283,3 +1283,7 @@ SDL_DYNAPI_PROC(bool,SDL_SetRenderTextureAddressMode,(SDL_Renderer *a,SDL_Textur 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_Window *a,SDL_GPUShaderFormat b,SDL_GPUDevice **c),(a,b,c),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(bool,SDL_SetAudioIterationCallbacks,(SDL_AudioDeviceID a,SDL_AudioIterationCallback b,SDL_AudioIterationCallback c,void *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) diff --git a/src/events/SDL_clipboardevents.c b/src/events/SDL_clipboardevents.c index d5cf8ad7..529af920 100644 --- a/src/events/SDL_clipboardevents.c +++ b/src/events/SDL_clipboardevents.c @@ -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/src/events/SDL_events.c b/src/events/SDL_events.c index 349d5750..c04d52b2 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -430,26 +430,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 @@ -633,9 +625,10 @@ static void SDL_LogEvent(const SDL_Event *event) #undef PRINT_MBUTTON_EVENT SDL_EVENT_CASE(SDL_EVENT_MOUSE_WHEEL) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u x=%g y=%g direction=%s)", + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u x=%g y=%g integer_x=%d integer_y=%d direction=%s)", (uint)event->wheel.timestamp, (uint)event->wheel.windowID, (uint)event->wheel.which, event->wheel.x, event->wheel.y, + (int)event->wheel.integer_x, (int)event->wheel.integer_y, event->wheel.direction == SDL_MOUSEWHEEL_NORMAL ? "normal" : "flipped"); break; @@ -879,12 +872,45 @@ 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 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_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) @@ -1076,16 +1102,11 @@ static void SDL_SendWakeupEvent(void) return; } - SDL_LockMutex(_this->wakeup_lock); - { - if (_this->wakeup_window) { - _this->SendWakeupEvent(_this, _this->wakeup_window); - - // No more wakeup events needed until we enter a new wait - _this->wakeup_window = NULL; - } + // We only want to do this once while waiting for an event, so set it to NULL atomically here + SDL_Window *wakeup_window = (SDL_Window *)SDL_SetAtomicPointer(&_this->wakeup_window, NULL); + if (wakeup_window) { + _this->SendWakeupEvent(_this, wakeup_window); } - SDL_UnlockMutex(_this->wakeup_lock); #endif } @@ -1379,9 +1400,7 @@ bool SDL_RunOnMainThread(SDL_MainThreadCallback callback, void *userdata, bool w return true; } - // Maximum wait of 30 seconds to prevent deadlocking forever - const Sint32 MAX_CALLBACK_WAIT = 30 * 1000; - SDL_WaitSemaphoreTimeout(entry->semaphore, MAX_CALLBACK_WAIT); + SDL_WaitSemaphore(entry->semaphore); switch (SDL_GetAtomicInt(&entry->state)) { case SDL_MAIN_CALLBACK_COMPLETE: @@ -1525,18 +1544,7 @@ static int SDL_WaitEventTimeout_Device(SDL_VideoDevice *_this, SDL_Window *wakeu */ SDL_PumpEventsInternal(true); - SDL_LockMutex(_this->wakeup_lock); - { - status = SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST); - // If status == 0 we are going to block so wakeup will be needed. - if (status == 0) { - _this->wakeup_window = wakeup_window; - } else { - _this->wakeup_window = NULL; - } - } - SDL_UnlockMutex(_this->wakeup_lock); - + status = SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST); if (status < 0) { // Got an error: return break; @@ -1549,8 +1557,6 @@ static int SDL_WaitEventTimeout_Device(SDL_VideoDevice *_this, SDL_Window *wakeu if (timeoutNS > 0) { Sint64 elapsed = SDL_GetTicksNS() - start; if (elapsed >= timeoutNS) { - // Set wakeup_window to NULL without holding the lock. - _this->wakeup_window = NULL; return 0; } loop_timeoutNS = (timeoutNS - elapsed); @@ -1563,9 +1569,9 @@ static int SDL_WaitEventTimeout_Device(SDL_VideoDevice *_this, SDL_Window *wakeu loop_timeoutNS = poll_intervalNS; } } + SDL_SetAtomicPointer(&_this->wakeup_window, wakeup_window); status = _this->WaitEventTimeout(_this, loop_timeoutNS); - // Set wakeup_window to NULL without holding the lock. - _this->wakeup_window = NULL; + SDL_SetAtomicPointer(&_this->wakeup_window, NULL); if (status == 0 && poll_intervalNS != SDL_MAX_SINT64 && loop_timeoutNS == poll_intervalNS) { // We may have woken up to poll. Try again continue; @@ -1823,7 +1829,7 @@ void SDL_SetEventEnabled(Uint32 type, bool enabled) Uint8 lo = (type & 0xff); if (SDL_disabled_events[hi] && - (SDL_disabled_events[hi]->bits[lo / 32] & (1 << (lo & 31)))) { + (SDL_disabled_events[hi]->bits[lo / 32] & (1U << (lo & 31)))) { current_state = false; } else { current_state = true; @@ -1832,7 +1838,7 @@ void SDL_SetEventEnabled(Uint32 type, bool enabled) if ((enabled != false) != current_state) { if (enabled) { SDL_assert(SDL_disabled_events[hi] != NULL); - SDL_disabled_events[hi]->bits[lo / 32] &= ~(1 << (lo & 31)); + SDL_disabled_events[hi]->bits[lo / 32] &= ~(1U << (lo & 31)); // Gamepad events depend on joystick events switch (type) { @@ -1863,7 +1869,7 @@ void SDL_SetEventEnabled(Uint32 type, bool enabled) } // Out of memory, nothing we can do... if (SDL_disabled_events[hi]) { - SDL_disabled_events[hi]->bits[lo / 32] |= (1 << (lo & 31)); + SDL_disabled_events[hi]->bits[lo / 32] |= (1U << (lo & 31)); SDL_FlushEvent(type); } } @@ -1882,7 +1888,7 @@ bool SDL_EventEnabled(Uint32 type) Uint8 lo = (type & 0xff); if (SDL_disabled_events[hi] && - (SDL_disabled_events[hi]->bits[lo / 32] & (1 << (lo & 31)))) { + (SDL_disabled_events[hi]->bits[lo / 32] & (1U << (lo & 31)))) { return false; } else { return true; diff --git a/src/events/SDL_keyboard.c b/src/events/SDL_keyboard.c index 766a0952..fc06f072 100644 --- a/src/events/SDL_keyboard.c +++ b/src/events/SDL_keyboard.c @@ -239,20 +239,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 (keymap && keymap->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) && - keymap && !keymap->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; @@ -490,7 +492,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; diff --git a/src/events/SDL_keymap.c b/src/events/SDL_keymap.c index f99a7975..318fc686 100644 --- a/src/events/SDL_keymap.c +++ b/src/events/SDL_keymap.c @@ -97,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) @@ -1059,7 +1117,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)) { @@ -1127,7 +1185,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/src/events/SDL_keymap_c.h b/src/events/SDL_keymap_c.h index a9c2c171..311e397e 100644 --- a/src/events/SDL_keymap_c.h +++ b/src/events/SDL_keymap_c.h @@ -34,7 +34,10 @@ typedef struct SDL_Keymap bool thai_keyboard; } SDL_Keymap; -SDL_Keymap *SDL_GetCurrentKeymap(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); diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c index 44f4f239..5838d47c 100644 --- a/src/events/SDL_mouse.c +++ b/src/events/SDL_mouse.c @@ -138,21 +138,25 @@ static void SDLCALL SDL_TouchMouseEventsChanged(void *userdata, const char *name #ifdef SDL_PLATFORM_VITA static void SDLCALL SDL_VitaTouchMouseDeviceChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { + Uint8 vita_touch_mouse_device = 1; + SDL_Mouse *mouse = (SDL_Mouse *)userdata; if (hint) { switch (*hint) { - default: case '0': - mouse->vita_touch_mouse_device = 1; + vita_touch_mouse_device = 1; break; case '1': - mouse->vita_touch_mouse_device = 2; + vita_touch_mouse_device = 2; break; case '2': - mouse->vita_touch_mouse_device = 3; + vita_touch_mouse_device = 3; + break; + default: break; } } + mouse->vita_touch_mouse_device = vita_touch_mouse_device; } #endif @@ -229,9 +233,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) @@ -304,7 +308,7 @@ 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; return true; } @@ -606,7 +610,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) @@ -812,7 +816,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); } @@ -1040,18 +1044,14 @@ void SDL_SendMouseWheel(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseI SDL_SetMouseFocus(window); } - // Accumulate fractional wheel motion if integer mode is enabled - if (mouse->integer_mode_flags & 2) { - mouse->integer_mode_residual_scroll_x = SDL_modff(mouse->integer_mode_residual_scroll_x + x, &x); - mouse->integer_mode_residual_scroll_y = SDL_modff(mouse->integer_mode_residual_scroll_y + y, &y); - } - if (x == 0.0f && y == 0.0f) { return; } // Post the event, if desired if (SDL_EventEnabled(SDL_EVENT_MOUSE_WHEEL)) { + float integer_x, integer_y; + if (!mouse->relative_mode || mouse->warp_emulation_active) { // We're not in relative mode, so all mouse events are global mouse events mouseID = SDL_GLOBAL_MOUSE_ID; @@ -1062,11 +1062,26 @@ void SDL_SendMouseWheel(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseI event.common.timestamp = timestamp; event.wheel.windowID = mouse->focus ? mouse->focus->id : 0; event.wheel.which = mouseID; - event.wheel.x = x; - event.wheel.y = y; event.wheel.direction = direction; event.wheel.mouse_x = mouse->x; event.wheel.mouse_y = mouse->y; + + mouse->residual_scroll_x = SDL_modff(mouse->residual_scroll_x + x, &integer_x); + event.wheel.integer_x = (Sint32)integer_x; + + mouse->residual_scroll_y = SDL_modff(mouse->residual_scroll_y + y, &integer_y); + event.wheel.integer_y = (Sint32)integer_y; + + // Return the accumulated values in x/y when integer wheel mode is enabled. + // This is necessary for compatibility with sdl2-compat 2.32.54. + if (mouse->integer_mode_flags & 2) { + event.wheel.x = integer_x; + event.wheel.y = integer_y; + } else { + event.wheel.x = x; + event.wheel.y = y; + } + SDL_PushEvent(&event); } } @@ -1289,7 +1304,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; } @@ -1303,8 +1318,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; } } @@ -1360,7 +1376,7 @@ bool SDL_SetRelativeMouseMode(bool enabled) if (enabled) { // Update cursor visibility before we potentially warp the mouse - SDL_SetCursor(NULL); + SDL_RedrawCursor(); } if (enabled && focusWindow) { @@ -1380,7 +1396,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 @@ -1599,6 +1615,26 @@ 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 (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. @@ -1607,7 +1643,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; } @@ -1627,23 +1663,10 @@ bool SDL_SetCursor(SDL_Cursor *cursor) } } mouse->cur_cursor = cursor; - } else { - if (mouse->focus) { - cursor = mouse->cur_cursor; - } else { - cursor = mouse->def_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; } @@ -1711,9 +1734,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; } @@ -1722,9 +1745,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; } @@ -1733,5 +1756,5 @@ bool SDL_CursorVisible(void) { SDL_Mouse *mouse = SDL_GetMouse(); - return mouse->cursor_shown; + return mouse->cursor_visible; } diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h index bfda17be..0bc913b9 100644 --- a/src/events/SDL_mouse_c.h +++ b/src/events/SDL_mouse_c.h @@ -99,8 +99,6 @@ typedef struct Uint8 integer_mode_flags; // 1 to enable mouse quantization, 2 to enable wheel quantization float integer_mode_residual_motion_x; float integer_mode_residual_motion_y; - float integer_mode_residual_scroll_x; - float integer_mode_residual_scroll_y; // Data common to all mice SDL_Window *focus; @@ -109,12 +107,14 @@ typedef struct float x_accu; float y_accu; float last_x, last_y; // the last reported x and y coordinates + float residual_scroll_x; + float residual_scroll_y; double click_motion_x; double click_motion_y; bool has_position; 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; @@ -148,7 +148,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; @@ -175,6 +175,9 @@ extern void SDL_SetMouseName(SDL_MouseID mouseID, const char *name); // 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); diff --git a/src/events/SDL_pen.c b/src/events/SDL_pen.c index 1ef7062a..cd3730ca 100644 --- a/src/events/SDL_pen.c +++ b/src/events/SDL_pen.c @@ -565,10 +565,19 @@ void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *wind event.pbutton.down = down; SDL_PushEvent(&event); - if (window && (pen_touching == instance_id)) { + if (window && (!pen_touching || (pen_touching == instance_id))) { SDL_Mouse *mouse = SDL_GetMouse(); if (mouse && mouse->pen_mouse_events) { - SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, button + 1, down); + static const Uint8 mouse_buttons[] = { + SDL_BUTTON_LEFT, + SDL_BUTTON_RIGHT, + SDL_BUTTON_MIDDLE, + SDL_BUTTON_X1, + SDL_BUTTON_X2 + }; + if (button < SDL_arraysize(mouse_buttons)) { + SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, mouse_buttons[button], down); + } } } } diff --git a/src/events/scancodes_xfree86.h b/src/events/scancodes_xfree86.h index 5e51bb17..81d625e8 100644 --- a/src/events/scancodes_xfree86.h +++ b/src/events/scancodes_xfree86.h @@ -371,10 +371,10 @@ static const SDL_Scancode xfree86_scancode_table2[] = { /* 188, 0x0bc */ SDL_SCANCODE_F18, // XF86Launch9 /* 189, 0x0bd */ SDL_SCANCODE_F19, // NoSymbol /* 190, 0x0be */ SDL_SCANCODE_F20, // XF86AudioMicMute - /* 191, 0x0bf */ SDL_SCANCODE_UNKNOWN, // XF86TouchpadToggle - /* 192, 0x0c0 */ SDL_SCANCODE_UNKNOWN, // XF86TouchpadOn - /* 193, 0x0c1 */ SDL_SCANCODE_UNKNOWN, // XF86TouchpadOff - /* 194, 0x0c2 */ SDL_SCANCODE_UNKNOWN, // NoSymbol + /* 191, 0x0bf */ SDL_SCANCODE_F21, // XF86TouchpadToggle + /* 192, 0x0c0 */ SDL_SCANCODE_F22, // XF86TouchpadOn + /* 193, 0x0c1 */ SDL_SCANCODE_F23, // XF86TouchpadOff + /* 194, 0x0c2 */ SDL_SCANCODE_F24, // NoSymbol /* 195, 0x0c3 */ SDL_SCANCODE_MODE, // Mode_switch /* 196, 0x0c4 */ SDL_SCANCODE_UNKNOWN, // NoSymbol /* 197, 0x0c5 */ SDL_SCANCODE_UNKNOWN, // NoSymbol diff --git a/src/filesystem/cocoa/SDL_sysfilesystem.m b/src/filesystem/cocoa/SDL_sysfilesystem.m index 5818764e..6ecef5df 100644 --- a/src/filesystem/cocoa/SDL_sysfilesystem.m +++ b/src/filesystem/cocoa/SDL_sysfilesystem.m @@ -136,7 +136,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/src/filesystem/gdk/SDL_sysfilesystem.cpp b/src/filesystem/gdk/SDL_sysfilesystem.cpp index ffafe43a..9a97f187 100644 --- a/src/filesystem/gdk/SDL_sysfilesystem.cpp +++ b/src/filesystem/gdk/SDL_sysfilesystem.cpp @@ -111,7 +111,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); diff --git a/src/filesystem/ngage/SDL_sysfilesystem.c b/src/filesystem/ngage/SDL_sysfilesystem.c new file mode 100644 index 00000000..bc33a2af --- /dev/null +++ b/src/filesystem/ngage/SDL_sysfilesystem.c @@ -0,0 +1,67 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + 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; + if (SDL_asprintf(&pref_path, "C:/System/Apps/%s/%s/", org, app) < 0) + return NULL; + else + 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/src/filesystem/ngage/SDL_sysfilesystem.cpp b/src/filesystem/ngage/SDL_sysfilesystem.cpp new file mode 100644 index 00000000..1c2b6aed --- /dev/null +++ b/src/filesystem/ngage/SDL_sysfilesystem.cpp @@ -0,0 +1,68 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + 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/src/filesystem/unix/SDL_sysfilesystem.c b/src/filesystem/unix/SDL_sysfilesystem.c index b0f2dd5c..858aaa2c 100644 --- a/src/filesystem/unix/SDL_sysfilesystem.c +++ b/src/filesystem/unix/SDL_sysfilesystem.c @@ -377,7 +377,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 +387,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 +449,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 +458,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 +503,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/src/filesystem/windows/SDL_sysfilesystem.c b/src/filesystem/windows/SDL_sysfilesystem.c index 39ba4148..a4c033f0 100644 --- a/src/filesystem/windows/SDL_sysfilesystem.c +++ b/src/filesystem/windows/SDL_sysfilesystem.c @@ -181,7 +181,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/src/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c index 44979a59..39cf2eb8 100644 --- a/src/gpu/SDL_gpu.c +++ b/src/gpu/SDL_gpu.c @@ -56,15 +56,47 @@ } #define CHECK_RENDERPASS \ - if (!((Pass *)render_pass)->in_progress) { \ + if (!((RenderPass *)render_pass)->in_progress) { \ SDL_assert_release(!"Render pass not in progress!"); \ return; \ } -#define CHECK_GRAPHICS_PIPELINE_BOUND \ - if (!((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->graphics_pipeline_bound) { \ - SDL_assert_release(!"Graphics pipeline not bound!"); \ - return; \ +#define CHECK_SAMPLER_TEXTURES \ + RenderPass *rp = (RenderPass *)render_pass; \ + for (Uint32 color_target_index = 0; color_target_index < rp->num_color_targets; color_target_index += 1) { \ + for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \ + if (rp->color_targets[color_target_index] == texture_sampler_bindings[texture_sampler_index].texture) { \ + SDL_assert_release(!"Texture cannot be simultaneously bound as a color target and a sampler!"); \ + } \ + } \ + } \ + \ + for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \ + if (rp->depth_stencil_target != NULL && rp->depth_stencil_target == texture_sampler_bindings[texture_sampler_index].texture) { \ + SDL_assert_release(!"Texture cannot be simultaneously bound as a depth stencil target and a sampler!"); \ + } \ + } + +#define CHECK_STORAGE_TEXTURES \ + RenderPass *rp = (RenderPass *)render_pass; \ + for (Uint32 color_target_index = 0; color_target_index < rp->num_color_targets; color_target_index += 1) { \ + for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \ + if (rp->color_targets[color_target_index] == storage_textures[texture_sampler_index]) { \ + SDL_assert_release(!"Texture cannot be simultaneously bound as a color target and a storage texture!"); \ + } \ + } \ + } \ + \ + for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \ + if (rp->depth_stencil_target != NULL && rp->depth_stencil_target == storage_textures[texture_sampler_index]) { \ + SDL_assert_release(!"Texture cannot be simultaneously bound as a depth stencil target and a storage texture!"); \ + } \ + } + +#define CHECK_GRAPHICS_PIPELINE_BOUND \ + if (!((RenderPass *)render_pass)->graphics_pipeline) { \ + SDL_assert_release(!"Graphics pipeline not bound!"); \ + return; \ } #define CHECK_COMPUTEPASS \ @@ -74,7 +106,7 @@ } #define CHECK_COMPUTE_PIPELINE_BOUND \ - if (!((CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER)->compute_pipeline_bound) { \ + if (!((ComputePass *)compute_pass)->compute_pipeline) { \ SDL_assert_release(!"Compute pipeline not bound!"); \ return; \ } @@ -137,23 +169,137 @@ ((CommandBufferCommonHeader *)command_buffer)->device #define RENDERPASS_COMMAND_BUFFER \ - ((Pass *)render_pass)->command_buffer + ((RenderPass *)render_pass)->command_buffer #define RENDERPASS_DEVICE \ ((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->device +#define RENDERPASS_BOUND_PIPELINE \ + ((RenderPass *)render_pass)->graphics_pipeline + #define COMPUTEPASS_COMMAND_BUFFER \ ((Pass *)compute_pass)->command_buffer #define COMPUTEPASS_DEVICE \ ((CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER)->device +#define COMPUTEPASS_BOUND_PIPELINE \ + ((ComputePass *)compute_pass)->compute_pipeline + #define COPYPASS_COMMAND_BUFFER \ ((Pass *)copy_pass)->command_buffer #define COPYPASS_DEVICE \ ((CommandBufferCommonHeader *)COPYPASS_COMMAND_BUFFER)->device +static bool TextureFormatIsComputeWritable[] = { + false, // INVALID + false, // A8_UNORM + true, // R8_UNORM + true, // R8G8_UNORM + true, // R8G8B8A8_UNORM + true, // R16_UNORM + true, // R16G16_UNORM + true, // R16G16B16A16_UNORM + true, // R10G10B10A2_UNORM + false, // B5G6R5_UNORM + false, // B5G5R5A1_UNORM + false, // B4G4R4A4_UNORM + false, // B8G8R8A8_UNORM + false, // BC1_UNORM + false, // BC2_UNORM + false, // BC3_UNORM + false, // BC4_UNORM + false, // BC5_UNORM + false, // BC7_UNORM + false, // BC6H_FLOAT + false, // BC6H_UFLOAT + true, // R8_SNORM + true, // R8G8_SNORM + true, // R8G8B8A8_SNORM + true, // R16_SNORM + true, // R16G16_SNORM + true, // R16G16B16A16_SNORM + true, // R16_FLOAT + true, // R16G16_FLOAT + true, // R16G16B16A16_FLOAT + true, // R32_FLOAT + true, // R32G32_FLOAT + true, // R32G32B32A32_FLOAT + true, // R11G11B10_UFLOAT + true, // R8_UINT + true, // R8G8_UINT + true, // R8G8B8A8_UINT + true, // R16_UINT + true, // R16G16_UINT + true, // R16G16B16A16_UINT + true, // R32_UINT + true, // R32G32_UINT + true, // R32G32B32A32_UINT + true, // R8_INT + true, // R8G8_INT + true, // R8G8B8A8_INT + true, // R16_INT + true, // R16G16_INT + true, // R16G16B16A16_INT + true, // R32_INT + true, // R32G32_INT + true, // R32G32B32A32_INT + false, // R8G8B8A8_UNORM_SRGB + false, // B8G8R8A8_UNORM_SRGB + false, // BC1_UNORM_SRGB + false, // BC3_UNORM_SRGB + false, // BC3_UNORM_SRGB + false, // BC7_UNORM_SRGB + false, // D16_UNORM + false, // D24_UNORM + false, // D32_FLOAT + false, // D24_UNORM_S8_UINT + false, // D32_FLOAT_S8_UINT + false, // ASTC_4x4_UNORM + false, // ASTC_5x4_UNORM + false, // ASTC_5x5_UNORM + false, // ASTC_6x5_UNORM + false, // ASTC_6x6_UNORM + false, // ASTC_8x5_UNORM + false, // ASTC_8x6_UNORM + false, // ASTC_8x8_UNORM + false, // ASTC_10x5_UNORM + false, // ASTC_10x6_UNORM + false, // ASTC_10x8_UNORM + false, // ASTC_10x10_UNORM + false, // ASTC_12x10_UNORM + false, // ASTC_12x12_UNORM + false, // ASTC_4x4_UNORM_SRGB + false, // ASTC_5x4_UNORM_SRGB + false, // ASTC_5x5_UNORM_SRGB + false, // ASTC_6x5_UNORM_SRGB + false, // ASTC_6x6_UNORM_SRGB + false, // ASTC_8x5_UNORM_SRGB + false, // ASTC_8x6_UNORM_SRGB + false, // ASTC_8x8_UNORM_SRGB + false, // ASTC_10x5_UNORM_SRGB + false, // ASTC_10x6_UNORM_SRGB + false, // ASTC_10x8_UNORM_SRGB + false, // ASTC_10x10_UNORM_SRGB + false, // ASTC_12x10_UNORM_SRGB + false, // ASTC_12x12_UNORM_SRGB + false, // ASTC_4x4_FLOAT + false, // ASTC_5x4_FLOAT + false, // ASTC_5x5_FLOAT + false, // ASTC_6x5_FLOAT + false, // ASTC_6x6_FLOAT + false, // ASTC_8x5_FLOAT + false, // ASTC_8x6_FLOAT + false, // ASTC_8x8_FLOAT + false, // ASTC_10x5_FLOAT + false, // ASTC_10x6_FLOAT + false, // ASTC_10x8_FLOAT + false, // ASTC_10x10_FLOAT + false, // ASTC_12x10_FLOAT + false // ASTC_12x12_FLOAT +}; + // Drivers #ifndef SDL_GPU_DISABLED @@ -371,13 +517,79 @@ void SDL_GPU_BlitCommon( SDL_EndGPURenderPass(render_pass); } +static void SDL_GPU_CheckGraphicsBindings(SDL_GPURenderPass *render_pass) +{ + RenderPass *rp = (RenderPass *)render_pass; + GraphicsPipelineCommonHeader *pipeline = (GraphicsPipelineCommonHeader *)RENDERPASS_BOUND_PIPELINE; + for (Uint32 i = 0; i < pipeline->num_vertex_samplers; i += 1) { + if (!rp->vertex_sampler_bound[i]) { + SDL_assert_release(!"Missing vertex sampler binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_vertex_storage_textures; i += 1) { + if (!rp->vertex_storage_texture_bound[i]) { + SDL_assert_release(!"Missing vertex storage texture binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_vertex_storage_buffers; i += 1) { + if (!rp->vertex_storage_buffer_bound[i]) { + SDL_assert_release(!"Missing vertex storage buffer binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_fragment_samplers; i += 1) { + if (!rp->fragment_sampler_bound[i]) { + SDL_assert_release(!"Missing fragment sampler binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_fragment_storage_textures; i += 1) { + if (!rp->fragment_storage_texture_bound[i]) { + SDL_assert_release(!"Missing fragment storage texture binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_fragment_storage_buffers; i += 1) { + if (!rp->fragment_storage_buffer_bound[i]) { + SDL_assert_release(!"Missing fragment storage buffer binding!"); + } + } +} + +static void SDL_GPU_CheckComputeBindings(SDL_GPUComputePass *compute_pass) +{ + ComputePass *cp = (ComputePass *)compute_pass; + ComputePipelineCommonHeader *pipeline = (ComputePipelineCommonHeader *)COMPUTEPASS_BOUND_PIPELINE; + for (Uint32 i = 0; i < pipeline->numSamplers; i += 1) { + if (!cp->sampler_bound[i]) { + SDL_assert_release(!"Missing compute sampler binding!"); + } + } + for (Uint32 i = 0; i < pipeline->numReadonlyStorageTextures; i += 1) { + if (!cp->read_only_storage_texture_bound[i]) { + SDL_assert_release(!"Missing compute readonly storage texture binding!"); + } + } + for (Uint32 i = 0; i < pipeline->numReadonlyStorageBuffers; i += 1) { + if (!cp->read_only_storage_buffer_bound[i]) { + SDL_assert_release(!"Missing compute readonly storage buffer binding!"); + } + } + for (Uint32 i = 0; i < pipeline->numReadWriteStorageTextures; i += 1) { + if (!cp->read_write_storage_texture_bound[i]) { + SDL_assert_release(!"Missing compute read-write storage texture binding!"); + } + } + for (Uint32 i = 0; i < pipeline->numReadWriteStorageBuffers; i += 1) { + if (!cp->read_write_storage_buffer_bound[i]) { + SDL_assert_release(!"Missing compute read-write storage buffer bbinding!"); + } + } +} + // Driver Functions #ifndef SDL_GPU_DISABLED static const SDL_GPUBootstrap * SDL_GPUSelectBackend(SDL_PropertiesID props) { Uint32 i; - SDL_GPUShaderFormat format_flags = 0; const char *gpudriver; SDL_VideoDevice *_this = SDL_GetVideoDevice(); @@ -386,25 +598,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); @@ -414,11 +607,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]; } } @@ -429,11 +618,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]; } } @@ -532,7 +717,6 @@ SDL_GPUDevice *SDL_CreateGPUDeviceWithProperties(SDL_PropertiesID props) result = selectedBackend->CreateDevice(debug_mode, preferLowPower, props); if (result != NULL) { result->backend = selectedBackend->name; - result->shader_formats = selectedBackend->shader_formats; result->debug_mode = debug_mode; } } @@ -728,6 +912,13 @@ bool SDL_GPUTextureSupportsFormat( CHECK_TEXTUREFORMAT_ENUM_INVALID(format, false) } + if ((usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE) || + (usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE)) { + if (!TextureFormatIsComputeWritable[format]) { + return false; + } + } + return device->SupportsTextureFormat( device->driverData, format, @@ -827,12 +1018,6 @@ SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline( SDL_assert_release(!"Format is not supported for color targets on this device!"); return NULL; } - if (graphicsPipelineCreateInfo->multisample_state.enable_alpha_to_coverage && - (IsIntegerFormat(graphicsPipelineCreateInfo->target_info.color_target_descriptions[i].format) - || IsCompressedFormat(graphicsPipelineCreateInfo->target_info.color_target_descriptions[i].format))) { - SDL_assert_release(!"Format is not compatible with alpha-to-coverage!"); - return NULL; - } if (graphicsPipelineCreateInfo->target_info.color_target_descriptions[i].blend_state.enable_blend) { const SDL_GPUColorTargetBlendState *blend_state = &graphicsPipelineCreateInfo->target_info.color_target_descriptions[i].blend_state; CHECK_BLENDFACTOR_ENUM_INVALID(blend_state->src_color_blendfactor, NULL) @@ -841,6 +1026,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) { @@ -854,6 +1041,18 @@ 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; @@ -1356,15 +1555,29 @@ SDL_GPUCommandBuffer *SDL_AcquireGPUCommandBuffer( commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; commandBufferHeader->device = device; commandBufferHeader->render_pass.command_buffer = command_buffer; - commandBufferHeader->render_pass.in_progress = false; - commandBufferHeader->graphics_pipeline_bound = false; commandBufferHeader->compute_pass.command_buffer = command_buffer; - commandBufferHeader->compute_pass.in_progress = false; - commandBufferHeader->compute_pipeline_bound = false; commandBufferHeader->copy_pass.command_buffer = command_buffer; - commandBufferHeader->copy_pass.in_progress = false; - commandBufferHeader->swapchain_texture_acquired = false; - commandBufferHeader->submitted = false; + + if (device->debug_mode) { + commandBufferHeader->render_pass.in_progress = false; + commandBufferHeader->render_pass.graphics_pipeline = NULL; + commandBufferHeader->compute_pass.in_progress = false; + commandBufferHeader->compute_pass.compute_pipeline = NULL; + commandBufferHeader->copy_pass.in_progress = false; + commandBufferHeader->swapchain_texture_acquired = false; + commandBufferHeader->submitted = false; + SDL_zeroa(commandBufferHeader->render_pass.vertex_sampler_bound); + SDL_zeroa(commandBufferHeader->render_pass.vertex_storage_texture_bound); + SDL_zeroa(commandBufferHeader->render_pass.vertex_storage_buffer_bound); + SDL_zeroa(commandBufferHeader->render_pass.fragment_sampler_bound); + SDL_zeroa(commandBufferHeader->render_pass.fragment_storage_texture_bound); + SDL_zeroa(commandBufferHeader->render_pass.fragment_storage_buffer_bound); + SDL_zeroa(commandBufferHeader->compute_pass.sampler_bound); + SDL_zeroa(commandBufferHeader->compute_pass.read_only_storage_texture_bound); + SDL_zeroa(commandBufferHeader->compute_pass.read_only_storage_buffer_bound); + SDL_zeroa(commandBufferHeader->compute_pass.read_write_storage_texture_bound); + SDL_zeroa(commandBufferHeader->compute_pass.read_write_storage_buffer_bound); + } return command_buffer; } @@ -1482,30 +1695,47 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( if (color_target_infos[i].cycle && color_target_infos[i].load_op == SDL_GPU_LOADOP_LOAD) { SDL_assert_release(!"Cannot cycle color target when load op is LOAD!"); + return NULL; } if (color_target_infos[i].store_op == SDL_GPU_STOREOP_RESOLVE || color_target_infos[i].store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) { if (color_target_infos[i].resolve_texture == NULL) { SDL_assert_release(!"Store op is RESOLVE or RESOLVE_AND_STORE but resolve_texture is NULL!"); + return NULL; } else { TextureCommonHeader *resolveTextureHeader = (TextureCommonHeader *)color_target_infos[i].resolve_texture; if (textureHeader->info.sample_count == SDL_GPU_SAMPLECOUNT_1) { SDL_assert_release(!"Store op is RESOLVE or RESOLVE_AND_STORE but texture is not multisample!"); + return NULL; } if (resolveTextureHeader->info.sample_count != SDL_GPU_SAMPLECOUNT_1) { SDL_assert_release(!"Resolve texture must have a sample count of 1!"); + return NULL; } if (resolveTextureHeader->info.format != textureHeader->info.format) { SDL_assert_release(!"Resolve texture must have the same format as its corresponding color target!"); + return NULL; } if (resolveTextureHeader->info.type == SDL_GPU_TEXTURETYPE_3D) { SDL_assert_release(!"Resolve texture must not be of TEXTURETYPE_3D!"); + return NULL; } if (!(resolveTextureHeader->info.usage & SDL_GPU_TEXTUREUSAGE_COLOR_TARGET)) { SDL_assert_release(!"Resolve texture usage must include COLOR_TARGET!"); + return NULL; } } } + + if (color_target_infos[i].layer_or_depth_plane >= textureHeader->info.layer_count_or_depth) { + SDL_assert_release(!"Color target layer index must be less than the texture's layer count!"); + return NULL; + } + + if (color_target_infos[i].mip_level >= textureHeader->info.num_levels) { + SDL_assert_release(!"Color target mip level must be less than the texture's level count!"); + return NULL; + } } if (depth_stencil_target_info != NULL) { @@ -1513,10 +1743,12 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( TextureCommonHeader *textureHeader = (TextureCommonHeader *)depth_stencil_target_info->texture; if (!(textureHeader->info.usage & SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)) { SDL_assert_release(!"Depth target must have been created with the DEPTH_STENCIL_TARGET usage flag!"); + return NULL; } if (depth_stencil_target_info->cycle && (depth_stencil_target_info->load_op == SDL_GPU_LOADOP_LOAD || depth_stencil_target_info->stencil_load_op == SDL_GPU_LOADOP_LOAD)) { SDL_assert_release(!"Cannot cycle depth target when load op or stencil load op is LOAD!"); + return NULL; } if (depth_stencil_target_info->store_op == SDL_GPU_STOREOP_RESOLVE || @@ -1524,6 +1756,7 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( depth_stencil_target_info->store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE || depth_stencil_target_info->stencil_store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) { SDL_assert_release(!"RESOLVE store ops are not supported for depth-stencil targets!"); + return NULL; } } } @@ -1535,7 +1768,18 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( depth_stencil_target_info); commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - commandBufferHeader->render_pass.in_progress = true; + + if (COMMAND_BUFFER_DEVICE->debug_mode) { + commandBufferHeader->render_pass.in_progress = true; + for (Uint32 i = 0; i < num_color_targets; i += 1) { + commandBufferHeader->render_pass.color_targets[i] = color_target_infos[i].texture; + } + commandBufferHeader->render_pass.num_color_targets = num_color_targets; + if (depth_stencil_target_info != NULL) { + commandBufferHeader->render_pass.depth_stencil_target = depth_stencil_target_info->texture; + } + } + return (SDL_GPURenderPass *)&(commandBufferHeader->render_pass); } @@ -1543,8 +1787,6 @@ void SDL_BindGPUGraphicsPipeline( SDL_GPURenderPass *render_pass, SDL_GPUGraphicsPipeline *graphics_pipeline) { - CommandBufferCommonHeader *commandBufferHeader; - if (render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; @@ -1558,8 +1800,10 @@ void SDL_BindGPUGraphicsPipeline( RENDERPASS_COMMAND_BUFFER, graphics_pipeline); - commandBufferHeader = (CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER; - commandBufferHeader->graphics_pipeline_bound = true; + + if (RENDERPASS_DEVICE->debug_mode) { + RENDERPASS_BOUND_PIPELINE = graphics_pipeline; + } } void SDL_SetGPUViewport( @@ -1709,6 +1953,15 @@ void SDL_BindGPUVertexSamplers( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + + if (!((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->ignore_render_pass_texture_validation) + { + CHECK_SAMPLER_TEXTURES + } + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->vertex_sampler_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindVertexSamplers( @@ -1735,6 +1988,11 @@ void SDL_BindGPUVertexStorageTextures( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + CHECK_STORAGE_TEXTURES + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->vertex_storage_texture_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindVertexStorageTextures( @@ -1761,6 +2019,10 @@ void SDL_BindGPUVertexStorageBuffers( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->vertex_storage_buffer_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindVertexStorageBuffers( @@ -1787,6 +2049,14 @@ void SDL_BindGPUFragmentSamplers( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + + if (!((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->ignore_render_pass_texture_validation) { + CHECK_SAMPLER_TEXTURES + } + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->fragment_sampler_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindFragmentSamplers( @@ -1813,6 +2083,11 @@ void SDL_BindGPUFragmentStorageTextures( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + CHECK_STORAGE_TEXTURES + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->fragment_storage_texture_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindFragmentStorageTextures( @@ -1839,6 +2114,10 @@ void SDL_BindGPUFragmentStorageBuffers( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->fragment_storage_buffer_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindFragmentStorageBuffers( @@ -1864,6 +2143,7 @@ void SDL_DrawGPUIndexedPrimitives( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS CHECK_GRAPHICS_PIPELINE_BOUND + SDL_GPU_CheckGraphicsBindings(render_pass); } RENDERPASS_DEVICE->DrawIndexedPrimitives( @@ -1890,6 +2170,7 @@ void SDL_DrawGPUPrimitives( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS CHECK_GRAPHICS_PIPELINE_BOUND + SDL_GPU_CheckGraphicsBindings(render_pass); } RENDERPASS_DEVICE->DrawPrimitives( @@ -1918,6 +2199,7 @@ void SDL_DrawGPUPrimitivesIndirect( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS CHECK_GRAPHICS_PIPELINE_BOUND + SDL_GPU_CheckGraphicsBindings(render_pass); } RENDERPASS_DEVICE->DrawPrimitivesIndirect( @@ -1945,6 +2227,7 @@ void SDL_DrawGPUIndexedPrimitivesIndirect( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS CHECK_GRAPHICS_PIPELINE_BOUND + SDL_GPU_CheckGraphicsBindings(render_pass); } RENDERPASS_DEVICE->DrawIndexedPrimitivesIndirect( @@ -1958,6 +2241,7 @@ void SDL_EndGPURenderPass( SDL_GPURenderPass *render_pass) { CommandBufferCommonHeader *commandBufferCommonHeader; + commandBufferCommonHeader = (CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER; if (render_pass == NULL) { SDL_InvalidParamError("render_pass"); @@ -1971,9 +2255,22 @@ void SDL_EndGPURenderPass( RENDERPASS_DEVICE->EndRenderPass( RENDERPASS_COMMAND_BUFFER); - commandBufferCommonHeader = (CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER; - commandBufferCommonHeader->render_pass.in_progress = false; - commandBufferCommonHeader->graphics_pipeline_bound = false; + if (RENDERPASS_DEVICE->debug_mode) { + commandBufferCommonHeader->render_pass.in_progress = false; + for (Uint32 i = 0; i < MAX_COLOR_TARGET_BINDINGS; i += 1) + { + commandBufferCommonHeader->render_pass.color_targets[i] = NULL; + } + commandBufferCommonHeader->render_pass.num_color_targets = 0; + commandBufferCommonHeader->render_pass.depth_stencil_target = NULL; + commandBufferCommonHeader->render_pass.graphics_pipeline = NULL; + SDL_zeroa(commandBufferCommonHeader->render_pass.vertex_sampler_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.vertex_storage_texture_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.vertex_storage_buffer_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.fragment_sampler_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.fragment_storage_texture_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.fragment_storage_buffer_bound); + } } // Compute Pass @@ -2017,6 +2314,16 @@ SDL_GPUComputePass *SDL_BeginGPUComputePass( SDL_assert_release(!"Texture must be created with COMPUTE_STORAGE_WRITE or COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE flag"); return NULL; } + + if (storage_texture_bindings[i].layer >= header->info.layer_count_or_depth) { + SDL_assert_release(!"Storage texture layer index must be less than the texture's layer count!"); + return NULL; + } + + if (storage_texture_bindings[i].mip_level >= header->info.num_levels) { + SDL_assert_release(!"Storage texture mip level must be less than the texture's level count!"); + return NULL; + } } // TODO: validate buffer usage? @@ -2030,7 +2337,19 @@ SDL_GPUComputePass *SDL_BeginGPUComputePass( num_storage_buffer_bindings); commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - commandBufferHeader->compute_pass.in_progress = true; + + if (COMMAND_BUFFER_DEVICE->debug_mode) { + commandBufferHeader->compute_pass.in_progress = true; + + for (Uint32 i = 0; i < num_storage_texture_bindings; i += 1) { + commandBufferHeader->compute_pass.read_write_storage_texture_bound[i] = true; + } + + for (Uint32 i = 0; i < num_storage_buffer_bindings; i += 1) { + commandBufferHeader->compute_pass.read_write_storage_buffer_bound[i] = true; + } + } + return (SDL_GPUComputePass *)&(commandBufferHeader->compute_pass); } @@ -2038,8 +2357,6 @@ void SDL_BindGPUComputePipeline( SDL_GPUComputePass *compute_pass, SDL_GPUComputePipeline *compute_pipeline) { - CommandBufferCommonHeader *commandBufferHeader; - if (compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; @@ -2057,8 +2374,10 @@ void SDL_BindGPUComputePipeline( COMPUTEPASS_COMMAND_BUFFER, compute_pipeline); - commandBufferHeader = (CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER; - commandBufferHeader->compute_pipeline_bound = true; + + if (COMPUTEPASS_DEVICE->debug_mode) { + COMPUTEPASS_BOUND_PIPELINE = compute_pipeline; + } } void SDL_BindGPUComputeSamplers( @@ -2078,6 +2397,10 @@ void SDL_BindGPUComputeSamplers( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((ComputePass *)compute_pass)->sampler_bound[first_slot + i] = true; + } } COMPUTEPASS_DEVICE->BindComputeSamplers( @@ -2104,6 +2427,10 @@ void SDL_BindGPUComputeStorageTextures( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((ComputePass *)compute_pass)->read_only_storage_texture_bound[first_slot + i] = true; + } } COMPUTEPASS_DEVICE->BindComputeStorageTextures( @@ -2130,6 +2457,10 @@ void SDL_BindGPUComputeStorageBuffers( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((ComputePass *)compute_pass)->read_only_storage_buffer_bound[first_slot + i] = true; + } } COMPUTEPASS_DEVICE->BindComputeStorageBuffers( @@ -2153,6 +2484,7 @@ void SDL_DispatchGPUCompute( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS CHECK_COMPUTE_PIPELINE_BOUND + SDL_GPU_CheckComputeBindings(compute_pass); } COMPUTEPASS_DEVICE->DispatchCompute( @@ -2175,6 +2507,7 @@ void SDL_DispatchGPUComputeIndirect( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS CHECK_COMPUTE_PIPELINE_BOUND + SDL_GPU_CheckComputeBindings(compute_pass); } COMPUTEPASS_DEVICE->DispatchComputeIndirect( @@ -2200,9 +2533,16 @@ void SDL_EndGPUComputePass( COMPUTEPASS_DEVICE->EndComputePass( COMPUTEPASS_COMMAND_BUFFER); - commandBufferCommonHeader = (CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER; - commandBufferCommonHeader->compute_pass.in_progress = false; - commandBufferCommonHeader->compute_pipeline_bound = false; + if (COMPUTEPASS_DEVICE->debug_mode) { + commandBufferCommonHeader = (CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER; + commandBufferCommonHeader->compute_pass.in_progress = false; + commandBufferCommonHeader->compute_pass.compute_pipeline = false; + SDL_zeroa(commandBufferCommonHeader->compute_pass.sampler_bound); + SDL_zeroa(commandBufferCommonHeader->compute_pass.read_only_storage_texture_bound); + SDL_zeroa(commandBufferCommonHeader->compute_pass.read_only_storage_buffer_bound); + SDL_zeroa(commandBufferCommonHeader->compute_pass.read_write_storage_texture_bound); + SDL_zeroa(commandBufferCommonHeader->compute_pass.read_write_storage_buffer_bound); + } } // TransferBuffer Data @@ -2260,7 +2600,11 @@ SDL_GPUCopyPass *SDL_BeginGPUCopyPass( command_buffer); commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - commandBufferHeader->copy_pass.in_progress = true; + + if (COMMAND_BUFFER_DEVICE->debug_mode) { + commandBufferHeader->copy_pass.in_progress = true; + } + return (SDL_GPUCopyPass *)&(commandBufferHeader->copy_pass); } @@ -2518,7 +2862,9 @@ void SDL_EndGPUCopyPass( COPYPASS_DEVICE->EndCopyPass( COPYPASS_COMMAND_BUFFER); - ((CommandBufferCommonHeader *)COPYPASS_COMMAND_BUFFER)->copy_pass.in_progress = false; + if (COPYPASS_DEVICE->debug_mode) { + ((CommandBufferCommonHeader *)COPYPASS_COMMAND_BUFFER)->copy_pass.in_progress = false; + } } void SDL_GenerateMipmapsForGPUTexture( @@ -2548,11 +2894,19 @@ void SDL_GenerateMipmapsForGPUTexture( SDL_assert_release(!"GenerateMipmaps texture must be created with SAMPLER and COLOR_TARGET usage flags!"); return; } + + CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; + commandBufferHeader->ignore_render_pass_texture_validation = true; } COMMAND_BUFFER_DEVICE->GenerateMipmaps( command_buffer, texture); + + if (COMMAND_BUFFER_DEVICE->debug_mode) { + CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; + commandBufferHeader->ignore_render_pass_texture_validation = false; + } } void SDL_BlitGPUTexture( diff --git a/src/gpu/SDL_sysgpu.h b/src/gpu/SDL_sysgpu.h index 9a51daeb..b4b54b26 100644 --- a/src/gpu/SDL_sysgpu.h +++ b/src/gpu/SDL_sysgpu.h @@ -24,6 +24,21 @@ #ifndef SDL_GPU_DRIVER_H #define SDL_GPU_DRIVER_H +// GraphicsDevice Limits + +#define MAX_TEXTURE_SAMPLERS_PER_STAGE 16 +#define MAX_STORAGE_TEXTURES_PER_STAGE 8 +#define MAX_STORAGE_BUFFERS_PER_STAGE 8 +#define MAX_UNIFORM_BUFFERS_PER_STAGE 4 +#define MAX_COMPUTE_WRITE_TEXTURES 8 +#define MAX_COMPUTE_WRITE_BUFFERS 8 +#define UNIFORM_BUFFER_SIZE 32768 +#define MAX_VERTEX_BUFFERS 16 +#define MAX_VERTEX_ATTRIBUTES 16 +#define MAX_COLOR_TARGET_BINDINGS 4 +#define MAX_PRESENT_COUNT 16 +#define MAX_FRAMES_IN_FLIGHT 3 + // Common Structs typedef struct Pass @@ -32,16 +47,51 @@ typedef struct Pass bool in_progress; } Pass; +typedef struct ComputePass +{ + SDL_GPUCommandBuffer *command_buffer; + bool in_progress; + + SDL_GPUComputePipeline *compute_pipeline; + + bool sampler_bound[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + bool read_only_storage_texture_bound[MAX_STORAGE_TEXTURES_PER_STAGE]; + bool read_only_storage_buffer_bound[MAX_STORAGE_BUFFERS_PER_STAGE]; + bool read_write_storage_texture_bound[MAX_COMPUTE_WRITE_TEXTURES]; + bool read_write_storage_buffer_bound[MAX_COMPUTE_WRITE_BUFFERS]; +} ComputePass; + +typedef struct RenderPass +{ + SDL_GPUCommandBuffer *command_buffer; + bool in_progress; + SDL_GPUTexture *color_targets[MAX_COLOR_TARGET_BINDINGS]; + Uint32 num_color_targets; + SDL_GPUTexture *depth_stencil_target; + + SDL_GPUGraphicsPipeline *graphics_pipeline; + + bool vertex_sampler_bound[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + bool vertex_storage_texture_bound[MAX_STORAGE_TEXTURES_PER_STAGE]; + bool vertex_storage_buffer_bound[MAX_STORAGE_BUFFERS_PER_STAGE]; + + bool fragment_sampler_bound[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + bool fragment_storage_texture_bound[MAX_STORAGE_TEXTURES_PER_STAGE]; + bool fragment_storage_buffer_bound[MAX_STORAGE_BUFFERS_PER_STAGE]; +} RenderPass; + typedef struct CommandBufferCommonHeader { SDL_GPUDevice *device; - Pass render_pass; - bool graphics_pipeline_bound; - Pass compute_pass; - bool compute_pipeline_bound; + + RenderPass render_pass; + ComputePass compute_pass; + Pass copy_pass; bool swapchain_texture_acquired; bool submitted; + // used to avoid tripping assert on GenerateMipmaps + bool ignore_render_pass_texture_validation; } CommandBufferCommonHeader; typedef struct TextureCommonHeader @@ -49,6 +99,29 @@ typedef struct TextureCommonHeader SDL_GPUTextureCreateInfo info; } TextureCommonHeader; +typedef struct GraphicsPipelineCommonHeader +{ + Uint32 num_vertex_samplers; + Uint32 num_vertex_storage_textures; + Uint32 num_vertex_storage_buffers; + Uint32 num_vertex_uniform_buffers; + + Uint32 num_fragment_samplers; + Uint32 num_fragment_storage_textures; + Uint32 num_fragment_storage_buffers; + Uint32 num_fragment_uniform_buffers; +} GraphicsPipelineCommonHeader; + +typedef struct ComputePipelineCommonHeader +{ + Uint32 numSamplers; + Uint32 numReadonlyStorageTextures; + Uint32 numReadonlyStorageBuffers; + Uint32 numReadWriteStorageTextures; + Uint32 numReadWriteStorageBuffers; + Uint32 numUniformBuffers; +} ComputePipelineCommonHeader; + typedef struct BlitFragmentUniforms { // texcoord space @@ -438,6 +511,89 @@ static inline bool IsCompressedFormat( } } +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; @@ -452,21 +608,6 @@ static inline Uint32 BytesPerRow( return blocksPerRow * SDL_GPUTextureFormatTexelBlockSize(format); } -// GraphicsDevice Limits - -#define MAX_TEXTURE_SAMPLERS_PER_STAGE 16 -#define MAX_STORAGE_TEXTURES_PER_STAGE 8 -#define MAX_STORAGE_BUFFERS_PER_STAGE 8 -#define MAX_UNIFORM_BUFFERS_PER_STAGE 4 -#define MAX_COMPUTE_WRITE_TEXTURES 8 -#define MAX_COMPUTE_WRITE_BUFFERS 8 -#define UNIFORM_BUFFER_SIZE 32768 -#define MAX_VERTEX_BUFFERS 16 -#define MAX_VERTEX_ATTRIBUTES 16 -#define MAX_COLOR_TARGET_BINDINGS 4 -#define MAX_PRESENT_COUNT 16 -#define MAX_FRAMES_IN_FLIGHT 3 - // Internal Macros #define EXPAND_ARRAY_IF_NEEDED(arr, elementType, newCount, capacity, newCapacity) \ @@ -1047,8 +1188,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/src/gpu/d3d12/SDL_gpu_d3d12.c b/src/gpu/d3d12/SDL_gpu_d3d12.c index 1843acd0..09f6f348 100644 --- a/src/gpu/d3d12/SDL_gpu_d3d12.c +++ b/src/gpu/d3d12/SDL_gpu_d3d12.c @@ -463,6 +463,115 @@ static DXGI_FORMAT SDLToD3D12_DepthFormat[] = { }; SDL_COMPILE_TIME_ASSERT(SDLToD3D12_DepthFormat, SDL_arraysize(SDLToD3D12_DepthFormat) == SDL_GPU_TEXTUREFORMAT_MAX_ENUM_VALUE); +static DXGI_FORMAT SDLToD3D12_TypelessFormat[] = { + DXGI_FORMAT_UNKNOWN, // INVALID + DXGI_FORMAT_UNKNOWN, // A8_UNORM + DXGI_FORMAT_UNKNOWN, // R8_UNORM + DXGI_FORMAT_UNKNOWN, // R8G8_UNORM + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_UNORM + DXGI_FORMAT_UNKNOWN, // R16_UNORM + DXGI_FORMAT_UNKNOWN, // R16G16_UNORM + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_UNORM + DXGI_FORMAT_UNKNOWN, // R10G10B10A2_UNORM + DXGI_FORMAT_UNKNOWN, // B5G6R5_UNORM + DXGI_FORMAT_UNKNOWN, // B5G5R5A1_UNORM + DXGI_FORMAT_UNKNOWN, // B4G4R4A4_UNORM + DXGI_FORMAT_UNKNOWN, // B8G8R8A8_UNORM + DXGI_FORMAT_UNKNOWN, // BC1_UNORM + DXGI_FORMAT_UNKNOWN, // BC2_UNORM + DXGI_FORMAT_UNKNOWN, // BC3_UNORM + DXGI_FORMAT_UNKNOWN, // BC4_UNORM + DXGI_FORMAT_UNKNOWN, // BC5_UNORM + DXGI_FORMAT_UNKNOWN, // BC7_UNORM + DXGI_FORMAT_UNKNOWN, // BC6H_FLOAT + DXGI_FORMAT_UNKNOWN, // BC6H_UFLOAT + DXGI_FORMAT_UNKNOWN, // R8_SNORM + DXGI_FORMAT_UNKNOWN, // R8G8_SNORM + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_SNORM + DXGI_FORMAT_UNKNOWN, // R16_SNORM + DXGI_FORMAT_UNKNOWN, // R16G16_SNORM + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_SNORM + DXGI_FORMAT_UNKNOWN, // R16_FLOAT + DXGI_FORMAT_UNKNOWN, // R16G16_FLOAT + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_FLOAT + DXGI_FORMAT_UNKNOWN, // R32_FLOAT + DXGI_FORMAT_UNKNOWN, // R32G32_FLOAT + DXGI_FORMAT_UNKNOWN, // R32G32B32A32_FLOAT + DXGI_FORMAT_UNKNOWN, // R11G11B10_UFLOAT + DXGI_FORMAT_UNKNOWN, // R8_UINT + DXGI_FORMAT_UNKNOWN, // R8G8_UINT + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_UINT + DXGI_FORMAT_UNKNOWN, // R16_UINT + DXGI_FORMAT_UNKNOWN, // R16G16_UINT + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_UINT + DXGI_FORMAT_UNKNOWN, // R32_UINT + DXGI_FORMAT_UNKNOWN, // R32G32_UINT + DXGI_FORMAT_UNKNOWN, // R32G32B32A32_UINT + DXGI_FORMAT_UNKNOWN, // R8_INT + DXGI_FORMAT_UNKNOWN, // R8G8_INT + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_INT + DXGI_FORMAT_UNKNOWN, // R16_INT + DXGI_FORMAT_UNKNOWN, // R16G16_INT + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_INT + DXGI_FORMAT_UNKNOWN, // R32_INT + DXGI_FORMAT_UNKNOWN, // R32G32_INT + DXGI_FORMAT_UNKNOWN, // R32G32B32A32_INT + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // B8G8R8A8_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // BC1_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // BC2_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // BC3_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // BC7_UNORM_SRGB + DXGI_FORMAT_R16_TYPELESS, // D16_UNORM + DXGI_FORMAT_R24G8_TYPELESS, // D24_UNORM + DXGI_FORMAT_R32_TYPELESS, // D32_FLOAT + DXGI_FORMAT_R24G8_TYPELESS, // D24_UNORM_S8_UINT + DXGI_FORMAT_R32G8X24_TYPELESS, // D32_FLOAT_S8_UINT + DXGI_FORMAT_UNKNOWN, // ASTC_4x4_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_5x4_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_5x5_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_6x5_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_6x6_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_8x5_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_8x6_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_8x8_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_10x5_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_10x6_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_10x8_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_10x10_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_12x10_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_12x12_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_4x4_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_5x4_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_5x5_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_6x5_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_6x6_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_8x5_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_8x6_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_8x8_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_10x5_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_10x6_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_10x8_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_10x10_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_12x10_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_12x12_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_4x4_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_5x4_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_5x5_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_6x5_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_6x6_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_8x5_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_8x6_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_8x8_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_10x5_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_10x6_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_10x8_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_10x10_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_12x10_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_12x12_FLOAT +}; +SDL_COMPILE_TIME_ASSERT(SDLToD3D12_TypelessFormat, SDL_arraysize(SDLToD3D12_TypelessFormat) == SDL_GPU_TEXTUREFORMAT_MAX_ENUM_VALUE); + static D3D12_COMPARISON_FUNC SDLToD3D12_CompareOp[] = { D3D12_COMPARISON_FUNC_NEVER, // INVALID D3D12_COMPARISON_FUNC_NEVER, // NEVER @@ -895,26 +1004,38 @@ struct D3D12CommandBuffer Uint32 vertexBufferOffsets[MAX_VERTEX_BUFFERS]; Uint32 vertexBufferCount; - D3D12Texture *vertexSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Sampler *vertexSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Texture *vertexStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - D3D12Buffer *vertexStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE vertexSamplerTextureDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE vertexSamplerDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE vertexStorageTextureDescriptorHandles[MAX_STORAGE_TEXTURES_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE vertexStorageBufferDescriptorHandles[MAX_STORAGE_BUFFERS_PER_STAGE]; + D3D12UniformBuffer *vertexUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE]; - D3D12Texture *fragmentSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Sampler *fragmentSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Texture *fragmentStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - D3D12Buffer *fragmentStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE fragmentSamplerTextureDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE fragmentSamplerDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE fragmentStorageTextureDescriptorHandles[MAX_STORAGE_TEXTURES_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE fragmentStorageBufferDescriptorHandles[MAX_STORAGE_BUFFERS_PER_STAGE]; + D3D12UniformBuffer *fragmentUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE]; - D3D12Texture *computeSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Sampler *computeSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE computeSamplerTextureDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE computeSamplerDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE computeReadOnlyStorageTextureDescriptorHandles[MAX_STORAGE_TEXTURES_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE computeReadOnlyStorageBufferDescriptorHandles[MAX_STORAGE_BUFFERS_PER_STAGE]; + + // Track these separately because barriers can happen mid compute pass D3D12Texture *computeReadOnlyStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; D3D12Buffer *computeReadOnlyStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + + D3D12_CPU_DESCRIPTOR_HANDLE computeReadWriteStorageTextureDescriptorHandles[MAX_COMPUTE_WRITE_TEXTURES]; + D3D12_CPU_DESCRIPTOR_HANDLE computeReadWriteStorageBufferDescriptorHandles[MAX_COMPUTE_WRITE_BUFFERS]; + + // Track these separately because they are bound when the compute pass begins D3D12TextureSubresource *computeReadWriteStorageTextureSubresources[MAX_COMPUTE_WRITE_TEXTURES]; Uint32 computeReadWriteStorageTextureSubresourceCount; D3D12Buffer *computeReadWriteStorageBuffers[MAX_COMPUTE_WRITE_BUFFERS]; Uint32 computeReadWriteStorageBufferCount; + D3D12UniformBuffer *computeUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE]; // Resource tracking @@ -978,22 +1099,14 @@ typedef struct D3D12GraphicsRootSignature struct D3D12GraphicsPipeline { + GraphicsPipelineCommonHeader header; + ID3D12PipelineState *pipelineState; D3D12GraphicsRootSignature *rootSignature; SDL_GPUPrimitiveType primitiveType; Uint32 vertexStrides[MAX_VERTEX_BUFFERS]; - Uint32 vertexSamplerCount; - Uint32 vertexUniformBufferCount; - Uint32 vertexStorageBufferCount; - Uint32 vertexStorageTextureCount; - - Uint32 fragmentSamplerCount; - Uint32 fragmentUniformBufferCount; - Uint32 fragmentStorageBufferCount; - Uint32 fragmentStorageTextureCount; - SDL_AtomicInt referenceCount; }; @@ -1012,16 +1125,11 @@ typedef struct D3D12ComputeRootSignature struct D3D12ComputePipeline { + ComputePipelineCommonHeader header; + ID3D12PipelineState *pipelineState; D3D12ComputeRootSignature *rootSignature; - Uint32 numSamplers; - Uint32 numReadOnlyStorageTextures; - Uint32 numReadOnlyStorageBuffers; - Uint32 numReadWriteStorageTextures; - Uint32 numReadWriteStorageBuffers; - Uint32 numUniformBuffers; - SDL_AtomicInt referenceCount; }; @@ -1102,7 +1210,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 @@ -2104,15 +2212,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; @@ -2142,12 +2250,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; @@ -2482,12 +2590,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, const Uint8 *code, size_t codeSize, + 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) { @@ -2705,8 +2833,10 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( ID3D12PipelineState *pipelineState; if (!D3D12_INTERNAL_CreateShaderBytecode( + renderer, createinfo->code, createinfo->code_size, + createinfo->format, &bytecode, &bytecodeSize)) { return NULL; @@ -2753,12 +2883,12 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( computePipeline->pipelineState = pipelineState; computePipeline->rootSignature = rootSignature; - computePipeline->numSamplers = createinfo->num_samplers; - computePipeline->numReadOnlyStorageTextures = createinfo->num_readonly_storage_textures; - computePipeline->numReadOnlyStorageBuffers = createinfo->num_readonly_storage_buffers; - computePipeline->numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures; - computePipeline->numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers; - computePipeline->numUniformBuffers = createinfo->num_uniform_buffers; + computePipeline->header.numSamplers = createinfo->num_samplers; + computePipeline->header.numReadonlyStorageTextures = createinfo->num_readonly_storage_textures; + computePipeline->header.numReadonlyStorageBuffers = createinfo->num_readonly_storage_buffers; + computePipeline->header.numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures; + computePipeline->header.numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers; + computePipeline->header.numUniformBuffers = createinfo->num_uniform_buffers; SDL_SetAtomicInt(&computePipeline->referenceCount, 0); if (renderer->debug_mode && SDL_HasProperty(createinfo->props, SDL_PROP_GPU_COMPUTEPIPELINE_CREATE_NAME_STRING)) { @@ -3039,15 +3169,15 @@ static SDL_GPUGraphicsPipeline *D3D12_CreateGraphicsPipeline( pipeline->primitiveType = createinfo->primitive_type; - pipeline->vertexSamplerCount = vertShader->num_samplers; - pipeline->vertexStorageTextureCount = vertShader->numStorageTextures; - pipeline->vertexStorageBufferCount = vertShader->numStorageBuffers; - pipeline->vertexUniformBufferCount = vertShader->numUniformBuffers; + pipeline->header.num_vertex_samplers = vertShader->num_samplers; + pipeline->header.num_vertex_storage_textures = vertShader->numStorageTextures; + pipeline->header.num_vertex_storage_buffers = vertShader->numStorageBuffers; + pipeline->header.num_vertex_uniform_buffers = vertShader->numUniformBuffers; - pipeline->fragmentSamplerCount = fragShader->num_samplers; - pipeline->fragmentStorageTextureCount = fragShader->numStorageTextures; - pipeline->fragmentStorageBufferCount = fragShader->numStorageBuffers; - pipeline->fragmentUniformBufferCount = fragShader->numUniformBuffers; + pipeline->header.num_fragment_samplers = fragShader->num_samplers; + pipeline->header.num_fragment_storage_textures = fragShader->numStorageTextures; + pipeline->header.num_fragment_storage_buffers = fragShader->numStorageBuffers; + pipeline->header.num_fragment_uniform_buffers = fragShader->numUniformBuffers; SDL_SetAtomicInt(&pipeline->referenceCount, 0); @@ -3113,13 +3243,16 @@ static SDL_GPUShader *D3D12_CreateShader( SDL_GPURenderer *driverData, const SDL_GPUShaderCreateInfo *createinfo) { + D3D12Renderer *renderer = (D3D12Renderer *)driverData; void *bytecode; size_t bytecodeSize; D3D12Shader *shader; if (!D3D12_INTERNAL_CreateShaderBytecode( + renderer, createinfo->code, createinfo->code_size, + createinfo->format, &bytecode, &bytecodeSize)) { return NULL; @@ -3159,6 +3292,10 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( D3D12_CLEAR_VALUE clearValue; DXGI_FORMAT format; bool useClearValue = false; + bool needsSRV = + (createinfo->usage & SDL_GPU_TEXTUREUSAGE_SAMPLER) || + (createinfo->usage & SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ) || + (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ); bool needsUAV = (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE) || (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE); @@ -3178,6 +3315,7 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( if (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COLOR_TARGET) { resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; useClearValue = true; + clearValue.Format = format; clearValue.Color[0] = SDL_GetFloatProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_R_FLOAT, 0); clearValue.Color[1] = SDL_GetFloatProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_G_FLOAT, 0); clearValue.Color[2] = SDL_GetFloatProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_B_FLOAT, 0); @@ -3187,9 +3325,10 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( if (createinfo->usage & SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET) { resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; useClearValue = true; + clearValue.Format = SDLToD3D12_DepthFormat[createinfo->format]; clearValue.DepthStencil.Depth = SDL_GetFloatProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT, 0); - clearValue.DepthStencil.Stencil = (UINT8)SDL_GetNumberProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_UINT8, 0); - format = SDLToD3D12_DepthFormat[createinfo->format]; + clearValue.DepthStencil.Stencil = (UINT8)SDL_GetNumberProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_NUMBER, 0); + format = needsSRV ? SDLToD3D12_TypelessFormat[createinfo->format] : SDLToD3D12_DepthFormat[createinfo->format]; } if (needsUAV) { @@ -3206,7 +3345,7 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( if (createinfo->type != SDL_GPU_TEXTURETYPE_3D) { desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; - desc.Alignment = isSwapchainTexture ? 0 : D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; + desc.Alignment = isSwapchainTexture ? 0 : isMultisample ? D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT : D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; desc.Width = createinfo->width; desc.Height = createinfo->height; desc.DepthOrArraySize = (UINT16)createinfo->layer_count_or_depth; @@ -3231,7 +3370,6 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( } initialState = isSwapchainTexture ? D3D12_RESOURCE_STATE_PRESENT : D3D12_INTERNAL_DefaultTextureResourceState(createinfo->usage); - clearValue.Format = desc.Format; res = ID3D12Device_CreateCommittedResource( renderer->device, @@ -3251,9 +3389,7 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( texture->resource = handle; // Create the SRV if applicable - if ((createinfo->usage & SDL_GPU_TEXTUREUSAGE_SAMPLER) || - (createinfo->usage & SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ) || - (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ)) { + if (needsSRV) { D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc; D3D12_INTERNAL_AssignStagingDescriptorHandle( @@ -4496,14 +4632,14 @@ static void D3D12_BindGraphicsPipeline( d3d12CommandBuffer->needFragmentUniformBufferBind[i] = true; } - for (i = 0; i < pipeline->vertexUniformBufferCount; i += 1) { + for (i = 0; i < pipeline->header.num_vertex_uniform_buffers; i += 1) { if (d3d12CommandBuffer->vertexUniformBuffers[i] == NULL) { d3d12CommandBuffer->vertexUniformBuffers[i] = D3D12_INTERNAL_AcquireUniformBufferFromPool( d3d12CommandBuffer); } } - for (i = 0; i < pipeline->fragmentUniformBufferCount; i += 1) { + for (i = 0; i < pipeline->header.num_fragment_uniform_buffers; i += 1) { if (d3d12CommandBuffer->fragmentUniformBuffers[i] == NULL) { d3d12CommandBuffer->fragmentUniformBuffers[i] = D3D12_INTERNAL_AcquireUniformBufferFromPool( d3d12CommandBuffer); @@ -4570,21 +4706,21 @@ static void D3D12_BindVertexSamplers( D3D12TextureContainer *container = (D3D12TextureContainer *)textureSamplerBindings[i].texture; D3D12Sampler *sampler = (D3D12Sampler *)textureSamplerBindings[i].sampler; - if (d3d12CommandBuffer->vertexSamplers[firstSlot + i] != sampler) { + if (d3d12CommandBuffer->vertexSamplerDescriptorHandles[firstSlot + i].ptr != sampler->handle.cpuHandle.ptr) { D3D12_INTERNAL_TrackSampler( d3d12CommandBuffer, sampler); - d3d12CommandBuffer->vertexSamplers[firstSlot + i] = sampler; + d3d12CommandBuffer->vertexSamplerDescriptorHandles[firstSlot + i] = sampler->handle.cpuHandle; d3d12CommandBuffer->needVertexSamplerBind = true; } - if (d3d12CommandBuffer->vertexSamplerTextures[firstSlot + i] != container->activeTexture) { + if (d3d12CommandBuffer->vertexSamplerTextureDescriptorHandles[firstSlot + i].ptr != container->activeTexture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture( d3d12CommandBuffer, container->activeTexture); - d3d12CommandBuffer->vertexSamplerTextures[firstSlot + i] = container->activeTexture; + d3d12CommandBuffer->vertexSamplerTextureDescriptorHandles[firstSlot + i] = container->activeTexture->srvHandle.cpuHandle; d3d12CommandBuffer->needVertexSamplerBind = true; } } @@ -4602,10 +4738,10 @@ static void D3D12_BindVertexStorageTextures( D3D12TextureContainer *container = (D3D12TextureContainer *)storageTextures[i]; D3D12Texture *texture = container->activeTexture; - if (d3d12CommandBuffer->vertexStorageTextures[firstSlot + i] != texture) { + if (d3d12CommandBuffer->vertexStorageTextureDescriptorHandles[firstSlot + i].ptr != texture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture(d3d12CommandBuffer, texture); - d3d12CommandBuffer->vertexStorageTextures[firstSlot + i] = texture; + d3d12CommandBuffer->vertexStorageTextureDescriptorHandles[firstSlot + i] = texture->srvHandle.cpuHandle; d3d12CommandBuffer->needVertexStorageTextureBind = true; } } @@ -4621,12 +4757,12 @@ static void D3D12_BindVertexStorageBuffers( for (Uint32 i = 0; i < numBindings; i += 1) { D3D12BufferContainer *container = (D3D12BufferContainer *)storageBuffers[i]; - if (d3d12CommandBuffer->vertexStorageBuffers[firstSlot + i] != container->activeBuffer) { + if (d3d12CommandBuffer->vertexStorageBufferDescriptorHandles[firstSlot + i].ptr != container->activeBuffer->srvDescriptor.cpuHandle.ptr) { D3D12_INTERNAL_TrackBuffer( d3d12CommandBuffer, container->activeBuffer); - d3d12CommandBuffer->vertexStorageBuffers[firstSlot + i] = container->activeBuffer; + d3d12CommandBuffer->vertexStorageBufferDescriptorHandles[firstSlot + i] = container->activeBuffer->srvDescriptor.cpuHandle; d3d12CommandBuffer->needVertexStorageBufferBind = true; } } @@ -4644,21 +4780,21 @@ static void D3D12_BindFragmentSamplers( D3D12TextureContainer *container = (D3D12TextureContainer *)textureSamplerBindings[i].texture; D3D12Sampler *sampler = (D3D12Sampler *)textureSamplerBindings[i].sampler; - if (d3d12CommandBuffer->fragmentSamplers[firstSlot + i] != sampler) { + if (d3d12CommandBuffer->fragmentSamplerDescriptorHandles[firstSlot + i].ptr != sampler->handle.cpuHandle.ptr) { D3D12_INTERNAL_TrackSampler( d3d12CommandBuffer, sampler); - d3d12CommandBuffer->fragmentSamplers[firstSlot + i] = sampler; + d3d12CommandBuffer->fragmentSamplerDescriptorHandles[firstSlot + i] = sampler->handle.cpuHandle; d3d12CommandBuffer->needFragmentSamplerBind = true; } - if (d3d12CommandBuffer->fragmentSamplerTextures[firstSlot + i] != container->activeTexture) { + if (d3d12CommandBuffer->fragmentSamplerTextureDescriptorHandles[firstSlot + i].ptr != container->activeTexture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture( d3d12CommandBuffer, container->activeTexture); - d3d12CommandBuffer->fragmentSamplerTextures[firstSlot + i] = container->activeTexture; + d3d12CommandBuffer->fragmentSamplerTextureDescriptorHandles[firstSlot + i] = container->activeTexture->srvHandle.cpuHandle; d3d12CommandBuffer->needFragmentSamplerBind = true; } } @@ -4676,10 +4812,10 @@ static void D3D12_BindFragmentStorageTextures( D3D12TextureContainer *container = (D3D12TextureContainer *)storageTextures[i]; D3D12Texture *texture = container->activeTexture; - if (d3d12CommandBuffer->fragmentStorageTextures[firstSlot + i] != texture) { + if (d3d12CommandBuffer->fragmentStorageTextureDescriptorHandles[firstSlot + i].ptr != texture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture(d3d12CommandBuffer, texture); - d3d12CommandBuffer->fragmentStorageTextures[firstSlot + i] = texture; + d3d12CommandBuffer->fragmentStorageTextureDescriptorHandles[firstSlot + i] = texture->srvHandle.cpuHandle; d3d12CommandBuffer->needFragmentStorageTextureBind = true; } } @@ -4696,12 +4832,12 @@ static void D3D12_BindFragmentStorageBuffers( for (Uint32 i = 0; i < numBindings; i += 1) { D3D12BufferContainer *container = (D3D12BufferContainer *)storageBuffers[i]; - if (d3d12CommandBuffer->fragmentStorageBuffers[firstSlot + i] != container->activeBuffer) { + if (d3d12CommandBuffer->fragmentStorageBufferDescriptorHandles[firstSlot + i].ptr != container->activeBuffer->srvDescriptor.cpuHandle.ptr) { D3D12_INTERNAL_TrackBuffer( d3d12CommandBuffer, container->activeBuffer); - d3d12CommandBuffer->fragmentStorageBuffers[firstSlot + i] = container->activeBuffer; + d3d12CommandBuffer->fragmentStorageBufferDescriptorHandles[firstSlot + i] = container->activeBuffer->srvDescriptor.cpuHandle; d3d12CommandBuffer->needFragmentStorageBufferBind = true; } } @@ -4782,15 +4918,19 @@ static void D3D12_INTERNAL_WriteGPUDescriptors( gpuBaseDescriptor->ptr = heap->descriptorHeapGPUStart.ptr + (heap->currentDescriptorIndex * heap->descriptorSize); for (Uint32 i = 0; i < resourceHandleCount; i += 1) { - ID3D12Device_CopyDescriptorsSimple( - commandBuffer->renderer->device, - 1, - gpuHeapCpuHandle, - resourceDescriptorHandles[i], - heapType); + // This will crash the driver if it gets a null handle! Cool! + if (resourceDescriptorHandles[i].ptr != 0) + { + ID3D12Device_CopyDescriptorsSimple( + commandBuffer->renderer->device, + 1, + gpuHeapCpuHandle, + resourceDescriptorHandles[i], + heapType); - heap->currentDescriptorIndex += 1; - gpuHeapCpuHandle.ptr += heap->descriptorSize; + heap->currentDescriptorIndex += 1; + gpuHeapCpuHandle.ptr += heap->descriptorSize; + } } } @@ -4820,19 +4960,21 @@ static void D3D12_INTERNAL_BindGraphicsResources( 0, commandBuffer->vertexBufferCount, vertexBufferViews); + + commandBuffer->needVertexBufferBind = false; } if (commandBuffer->needVertexSamplerBind) { - if (graphicsPipeline->vertexSamplerCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->vertexSamplerCount; i += 1) { - cpuHandles[i] = commandBuffer->vertexSamplers[i]->handle.cpuHandle; + if (graphicsPipeline->header.num_vertex_samplers > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_samplers; i += 1) { + cpuHandles[i] = commandBuffer->vertexSamplerDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, cpuHandles, - graphicsPipeline->vertexSamplerCount, + graphicsPipeline->header.num_vertex_samplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4840,15 +4982,15 @@ static void D3D12_INTERNAL_BindGraphicsResources( graphicsPipeline->rootSignature->vertexSamplerRootIndex, gpuDescriptorHandle); - for (Uint32 i = 0; i < graphicsPipeline->vertexSamplerCount; i += 1) { - cpuHandles[i] = commandBuffer->vertexSamplerTextures[i]->srvHandle.cpuHandle; + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_samplers; i += 1) { + cpuHandles[i] = commandBuffer->vertexSamplerTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->vertexSamplerCount, + graphicsPipeline->header.num_vertex_samplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4860,16 +5002,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needVertexStorageTextureBind) { - if (graphicsPipeline->vertexStorageTextureCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->vertexStorageTextureCount; i += 1) { - cpuHandles[i] = commandBuffer->vertexStorageTextures[i]->srvHandle.cpuHandle; + if (graphicsPipeline->header.num_vertex_storage_textures > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_storage_textures; i += 1) { + cpuHandles[i] = commandBuffer->vertexStorageTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->vertexStorageTextureCount, + graphicsPipeline->header.num_vertex_storage_textures, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4881,16 +5023,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needVertexStorageBufferBind) { - if (graphicsPipeline->vertexStorageBufferCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->vertexStorageBufferCount; i += 1) { - cpuHandles[i] = commandBuffer->vertexStorageBuffers[i]->srvDescriptor.cpuHandle; + if (graphicsPipeline->header.num_vertex_storage_buffers > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_storage_buffers; i += 1) { + cpuHandles[i] = commandBuffer->vertexStorageBufferDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->vertexStorageBufferCount, + graphicsPipeline->header.num_vertex_storage_buffers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4903,7 +5045,7 @@ static void D3D12_INTERNAL_BindGraphicsResources( for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) { if (commandBuffer->needVertexUniformBufferBind[i]) { - if (graphicsPipeline->vertexUniformBufferCount > i) { + if (graphicsPipeline->header.num_vertex_uniform_buffers > i) { ID3D12GraphicsCommandList_SetGraphicsRootConstantBufferView( commandBuffer->graphicsCommandList, graphicsPipeline->rootSignature->vertexUniformBufferRootIndex[i], @@ -4914,16 +5056,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needFragmentSamplerBind) { - if (graphicsPipeline->fragmentSamplerCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->fragmentSamplerCount; i += 1) { - cpuHandles[i] = commandBuffer->fragmentSamplers[i]->handle.cpuHandle; + if (graphicsPipeline->header.num_fragment_samplers > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_samplers; i += 1) { + cpuHandles[i] = commandBuffer->fragmentSamplerDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, cpuHandles, - graphicsPipeline->fragmentSamplerCount, + graphicsPipeline->header.num_fragment_samplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4931,15 +5073,15 @@ static void D3D12_INTERNAL_BindGraphicsResources( graphicsPipeline->rootSignature->fragmentSamplerRootIndex, gpuDescriptorHandle); - for (Uint32 i = 0; i < graphicsPipeline->fragmentSamplerCount; i += 1) { - cpuHandles[i] = commandBuffer->fragmentSamplerTextures[i]->srvHandle.cpuHandle; + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_samplers; i += 1) { + cpuHandles[i] = commandBuffer->fragmentSamplerTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->fragmentSamplerCount, + graphicsPipeline->header.num_fragment_samplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4951,16 +5093,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needFragmentStorageTextureBind) { - if (graphicsPipeline->fragmentStorageTextureCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->fragmentStorageTextureCount; i += 1) { - cpuHandles[i] = commandBuffer->fragmentStorageTextures[i]->srvHandle.cpuHandle; + if (graphicsPipeline->header.num_fragment_storage_textures > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_storage_textures; i += 1) { + cpuHandles[i] = commandBuffer->fragmentStorageTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->fragmentStorageTextureCount, + graphicsPipeline->header.num_fragment_storage_textures, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4972,16 +5114,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needFragmentStorageBufferBind) { - if (graphicsPipeline->fragmentStorageBufferCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->fragmentStorageBufferCount; i += 1) { - cpuHandles[i] = commandBuffer->fragmentStorageBuffers[i]->srvDescriptor.cpuHandle; + if (graphicsPipeline->header.num_fragment_storage_buffers > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_storage_buffers; i += 1) { + cpuHandles[i] = commandBuffer->fragmentStorageBufferDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->fragmentStorageBufferCount, + graphicsPipeline->header.num_fragment_storage_buffers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4994,7 +5136,7 @@ static void D3D12_INTERNAL_BindGraphicsResources( for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) { if (commandBuffer->needFragmentUniformBufferBind[i]) { - if (graphicsPipeline->fragmentUniformBufferCount > i) { + if (graphicsPipeline->header.num_fragment_uniform_buffers > i) { ID3D12GraphicsCommandList_SetGraphicsRootConstantBufferView( commandBuffer->graphicsCommandList, graphicsPipeline->rootSignature->fragmentUniformBufferRootIndex[i], @@ -5159,15 +5301,15 @@ static void D3D12_EndRenderPass( SDL_zeroa(d3d12CommandBuffer->vertexBufferOffsets); d3d12CommandBuffer->vertexBufferCount = 0; - SDL_zeroa(d3d12CommandBuffer->vertexSamplerTextures); - SDL_zeroa(d3d12CommandBuffer->vertexSamplers); - SDL_zeroa(d3d12CommandBuffer->vertexStorageTextures); - SDL_zeroa(d3d12CommandBuffer->vertexStorageBuffers); + SDL_zeroa(d3d12CommandBuffer->vertexSamplerTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->vertexSamplerDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->vertexStorageTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->vertexStorageBufferDescriptorHandles); - SDL_zeroa(d3d12CommandBuffer->fragmentSamplerTextures); - SDL_zeroa(d3d12CommandBuffer->fragmentSamplers); - SDL_zeroa(d3d12CommandBuffer->fragmentStorageTextures); - SDL_zeroa(d3d12CommandBuffer->fragmentStorageBuffers); + SDL_zeroa(d3d12CommandBuffer->fragmentSamplerTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->fragmentSamplerDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->fragmentStorageTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->fragmentStorageBufferDescriptorHandles); } // Compute Pass @@ -5201,6 +5343,7 @@ static void D3D12_BeginComputePass( D3D12_RESOURCE_STATE_UNORDERED_ACCESS); d3d12CommandBuffer->computeReadWriteStorageTextureSubresources[i] = subresource; + d3d12CommandBuffer->computeReadWriteStorageTextureDescriptorHandles[i] = subresource->uavHandle.cpuHandle; D3D12_INTERNAL_TrackTexture( d3d12CommandBuffer, @@ -5219,6 +5362,7 @@ static void D3D12_BeginComputePass( D3D12_RESOURCE_STATE_UNORDERED_ACCESS); d3d12CommandBuffer->computeReadWriteStorageBuffers[i] = buffer; + d3d12CommandBuffer->computeReadWriteStorageBufferDescriptorHandles[i] = buffer->uavDescriptor.cpuHandle; D3D12_INTERNAL_TrackBuffer( d3d12CommandBuffer, @@ -5260,7 +5404,7 @@ static void D3D12_BindComputePipeline( d3d12CommandBuffer->needComputeUniformBufferBind[i] = true; } - for (Uint32 i = 0; i < pipeline->numUniformBuffers; i += 1) { + for (Uint32 i = 0; i < pipeline->header.numUniformBuffers; i += 1) { if (d3d12CommandBuffer->computeUniformBuffers[i] == NULL) { d3d12CommandBuffer->computeUniformBuffers[i] = D3D12_INTERNAL_AcquireUniformBufferFromPool( d3d12CommandBuffer); @@ -5270,9 +5414,9 @@ static void D3D12_BindComputePipeline( D3D12_INTERNAL_TrackComputePipeline(d3d12CommandBuffer, pipeline); // Bind write-only resources after setting root signature - if (pipeline->numReadWriteStorageTextures > 0) { - for (Uint32 i = 0; i < pipeline->numReadWriteStorageTextures; i += 1) { - cpuHandles[i] = d3d12CommandBuffer->computeReadWriteStorageTextureSubresources[i]->uavHandle.cpuHandle; + if (pipeline->header.numReadWriteStorageTextures > 0) { + for (Uint32 i = 0; i < pipeline->header.numReadWriteStorageTextures; i += 1) { + cpuHandles[i] = d3d12CommandBuffer->computeReadWriteStorageTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( @@ -5288,9 +5432,9 @@ static void D3D12_BindComputePipeline( gpuDescriptorHandle); } - if (pipeline->numReadWriteStorageBuffers > 0) { - for (Uint32 i = 0; i < pipeline->numReadWriteStorageBuffers; i += 1) { - cpuHandles[i] = d3d12CommandBuffer->computeReadWriteStorageBuffers[i]->uavDescriptor.cpuHandle; + if (pipeline->header.numReadWriteStorageBuffers > 0) { + for (Uint32 i = 0; i < pipeline->header.numReadWriteStorageBuffers; i += 1) { + cpuHandles[i] = d3d12CommandBuffer->computeReadWriteStorageBufferDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( @@ -5319,21 +5463,21 @@ static void D3D12_BindComputeSamplers( D3D12TextureContainer *container = (D3D12TextureContainer *)textureSamplerBindings[i].texture; D3D12Sampler *sampler = (D3D12Sampler *)textureSamplerBindings[i].sampler; - if (d3d12CommandBuffer->computeSamplers[firstSlot + i] != sampler) { + if (d3d12CommandBuffer->computeSamplerDescriptorHandles[firstSlot + i].ptr != sampler->handle.cpuHandle.ptr) { D3D12_INTERNAL_TrackSampler( d3d12CommandBuffer, (D3D12Sampler *)textureSamplerBindings[i].sampler); - d3d12CommandBuffer->computeSamplers[firstSlot + i] = (D3D12Sampler *)textureSamplerBindings[i].sampler; + d3d12CommandBuffer->computeSamplerDescriptorHandles[firstSlot + i] = sampler->handle.cpuHandle; d3d12CommandBuffer->needComputeSamplerBind = true; } - if (d3d12CommandBuffer->computeSamplerTextures[firstSlot + i] != container->activeTexture) { + if (d3d12CommandBuffer->computeSamplerTextureDescriptorHandles[firstSlot + i].ptr != container->activeTexture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture( d3d12CommandBuffer, container->activeTexture); - d3d12CommandBuffer->computeSamplerTextures[firstSlot + i] = container->activeTexture; + d3d12CommandBuffer->computeSamplerTextureDescriptorHandles[firstSlot + i] = container->activeTexture->srvHandle.cpuHandle; d3d12CommandBuffer->needComputeSamplerBind = true; } } @@ -5370,6 +5514,7 @@ static void D3D12_BindComputeStorageTextures( container->activeTexture); d3d12CommandBuffer->computeReadOnlyStorageTextures[firstSlot + i] = container->activeTexture; + d3d12CommandBuffer->computeReadOnlyStorageTextureDescriptorHandles[firstSlot + i] = container->activeTexture->srvHandle.cpuHandle; d3d12CommandBuffer->needComputeReadOnlyStorageTextureBind = true; } } @@ -5407,6 +5552,7 @@ static void D3D12_BindComputeStorageBuffers( buffer); d3d12CommandBuffer->computeReadOnlyStorageBuffers[firstSlot + i] = buffer; + d3d12CommandBuffer->computeReadOnlyStorageBufferDescriptorHandles[firstSlot + i] = buffer->srvDescriptor.cpuHandle; d3d12CommandBuffer->needComputeReadOnlyStorageBufferBind = true; } } @@ -5442,16 +5588,16 @@ static void D3D12_INTERNAL_BindComputeResources( D3D12_GPU_DESCRIPTOR_HANDLE gpuDescriptorHandle; if (commandBuffer->needComputeSamplerBind) { - if (computePipeline->numSamplers > 0) { - for (Uint32 i = 0; i < computePipeline->numSamplers; i += 1) { - cpuHandles[i] = commandBuffer->computeSamplers[i]->handle.cpuHandle; + if (computePipeline->header.numSamplers > 0) { + for (Uint32 i = 0; i < computePipeline->header.numSamplers; i += 1) { + cpuHandles[i] = commandBuffer->computeSamplerDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, cpuHandles, - computePipeline->numSamplers, + computePipeline->header.numSamplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetComputeRootDescriptorTable( @@ -5459,15 +5605,15 @@ static void D3D12_INTERNAL_BindComputeResources( computePipeline->rootSignature->samplerRootIndex, gpuDescriptorHandle); - for (Uint32 i = 0; i < computePipeline->numSamplers; i += 1) { - cpuHandles[i] = commandBuffer->computeSamplerTextures[i]->srvHandle.cpuHandle; + for (Uint32 i = 0; i < computePipeline->header.numSamplers; i += 1) { + cpuHandles[i] = commandBuffer->computeSamplerTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - computePipeline->numSamplers, + computePipeline->header.numSamplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetComputeRootDescriptorTable( @@ -5479,16 +5625,16 @@ static void D3D12_INTERNAL_BindComputeResources( } if (commandBuffer->needComputeReadOnlyStorageTextureBind) { - if (computePipeline->numReadOnlyStorageTextures > 0) { - for (Uint32 i = 0; i < computePipeline->numReadOnlyStorageTextures; i += 1) { - cpuHandles[i] = commandBuffer->computeReadOnlyStorageTextures[i]->srvHandle.cpuHandle; + if (computePipeline->header.numReadonlyStorageTextures > 0) { + for (Uint32 i = 0; i < computePipeline->header.numReadonlyStorageTextures; i += 1) { + cpuHandles[i] = commandBuffer->computeReadOnlyStorageTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - computePipeline->numReadOnlyStorageTextures, + computePipeline->header.numReadonlyStorageTextures, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetComputeRootDescriptorTable( @@ -5500,16 +5646,16 @@ static void D3D12_INTERNAL_BindComputeResources( } if (commandBuffer->needComputeReadOnlyStorageBufferBind) { - if (computePipeline->numReadOnlyStorageBuffers > 0) { - for (Uint32 i = 0; i < computePipeline->numReadOnlyStorageBuffers; i += 1) { - cpuHandles[i] = commandBuffer->computeReadOnlyStorageBuffers[i]->srvDescriptor.cpuHandle; + if (computePipeline->header.numReadonlyStorageBuffers > 0) { + for (Uint32 i = 0; i < computePipeline->header.numReadonlyStorageBuffers; i += 1) { + cpuHandles[i] = commandBuffer->computeReadOnlyStorageBufferDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - computePipeline->numReadOnlyStorageBuffers, + computePipeline->header.numReadonlyStorageBuffers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetComputeRootDescriptorTable( @@ -5522,7 +5668,7 @@ static void D3D12_INTERNAL_BindComputeResources( for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) { if (commandBuffer->needComputeUniformBufferBind[i]) { - if (computePipeline->numUniformBuffers > i) { + if (computePipeline->header.numUniformBuffers > i) { ID3D12GraphicsCommandList_SetComputeRootConstantBufferView( commandBuffer->graphicsCommandList, computePipeline->rootSignature->uniformBufferRootIndex[i], @@ -5621,8 +5767,11 @@ static void D3D12_EndComputePass( } } - SDL_zeroa(d3d12CommandBuffer->computeSamplerTextures); - SDL_zeroa(d3d12CommandBuffer->computeSamplers); + SDL_zeroa(d3d12CommandBuffer->computeSamplerTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->computeSamplerDescriptorHandles); + + SDL_zeroa(d3d12CommandBuffer->computeReadWriteStorageTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->computeReadWriteStorageBufferDescriptorHandles); d3d12CommandBuffer->currentComputePipeline = NULL; } @@ -5797,6 +5946,10 @@ 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, @@ -5834,7 +5987,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; @@ -6105,6 +6260,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; @@ -6447,9 +6605,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); } } @@ -6465,9 +6621,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 @@ -7207,20 +7361,22 @@ static SDL_GPUCommandBuffer *D3D12_AcquireCommandBuffer( SDL_zeroa(commandBuffer->vertexBufferOffsets); commandBuffer->vertexBufferCount = 0; - SDL_zeroa(commandBuffer->vertexSamplerTextures); - SDL_zeroa(commandBuffer->vertexSamplers); - SDL_zeroa(commandBuffer->vertexStorageTextures); - SDL_zeroa(commandBuffer->vertexStorageBuffers); + SDL_zeroa(commandBuffer->vertexSamplerTextureDescriptorHandles); + SDL_zeroa(commandBuffer->vertexSamplerDescriptorHandles); + SDL_zeroa(commandBuffer->vertexStorageTextureDescriptorHandles); + SDL_zeroa(commandBuffer->vertexStorageBufferDescriptorHandles); SDL_zeroa(commandBuffer->vertexUniformBuffers); - SDL_zeroa(commandBuffer->fragmentSamplerTextures); - SDL_zeroa(commandBuffer->fragmentSamplers); - SDL_zeroa(commandBuffer->fragmentStorageTextures); - SDL_zeroa(commandBuffer->fragmentStorageBuffers); + SDL_zeroa(commandBuffer->fragmentSamplerTextureDescriptorHandles); + SDL_zeroa(commandBuffer->fragmentSamplerDescriptorHandles); + SDL_zeroa(commandBuffer->fragmentStorageTextureDescriptorHandles); + SDL_zeroa(commandBuffer->fragmentStorageBufferDescriptorHandles); SDL_zeroa(commandBuffer->fragmentUniformBuffers); - SDL_zeroa(commandBuffer->computeSamplerTextures); - SDL_zeroa(commandBuffer->computeSamplers); + SDL_zeroa(commandBuffer->computeSamplerTextureDescriptorHandles); + SDL_zeroa(commandBuffer->computeSamplerDescriptorHandles); + SDL_zeroa(commandBuffer->computeReadOnlyStorageTextureDescriptorHandles); + SDL_zeroa(commandBuffer->computeReadOnlyStorageBufferDescriptorHandles); SDL_zeroa(commandBuffer->computeReadOnlyStorageTextures); SDL_zeroa(commandBuffer->computeReadOnlyStorageBuffers); SDL_zeroa(commandBuffer->computeReadWriteStorageTextureSubresources); @@ -7365,7 +7521,7 @@ static bool D3D12_INTERNAL_AcquireSwapchainTexture( 1, &barrierDesc); - *swapchainTexture = (SDL_GPUTexture*)&windowData->textureContainers[swapchainIndex]; + *swapchainTexture = (SDL_GPUTexture *)&windowData->textureContainers[swapchainIndex]; return true; } @@ -7777,7 +7933,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; } @@ -8175,7 +8331,7 @@ 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; @@ -8191,6 +8347,17 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) IDXGIFactory6 *factory6; IDXGIAdapter1 *adapter; + // 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? d3d12Dll = SDL_LoadObject(D3D12_DLL); @@ -8285,6 +8452,18 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) (void **)&device); if (SUCCEEDED(res)) { + 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); @@ -8293,6 +8472,11 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) SDL_UnloadObject(d3d12Dll); SDL_UnloadObject(dxgiDll); + 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; @@ -8303,38 +8487,38 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) } #if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) && defined(HAVE_IDXGIINFOQUEUE) -static void D3D12_INTERNAL_TryInitializeDXGIDebug(D3D12Renderer *renderer) +static bool D3D12_INTERNAL_TryInitializeDXGIDebug(D3D12Renderer *renderer) { PFN_DXGI_GET_DEBUG_INTERFACE DXGIGetDebugInterfaceFunc; HRESULT res; renderer->dxgidebug_dll = SDL_LoadObject(DXGIDEBUG_DLL); if (renderer->dxgidebug_dll == NULL) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not find " DXGIDEBUG_DLL); - return; + return false; } DXGIGetDebugInterfaceFunc = (PFN_DXGI_GET_DEBUG_INTERFACE)SDL_LoadFunction( renderer->dxgidebug_dll, DXGI_GET_DEBUG_INTERFACE_FUNC); if (DXGIGetDebugInterfaceFunc == NULL) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not load function: " DXGI_GET_DEBUG_INTERFACE_FUNC); - return; + return false; } res = DXGIGetDebugInterfaceFunc(&D3D_IID_IDXGIDebug, (void **)&renderer->dxgiDebug); if (FAILED(res)) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not get IDXGIDebug interface"); + return false; } res = DXGIGetDebugInterfaceFunc(&D3D_IID_IDXGIInfoQueue, (void **)&renderer->dxgiInfoQueue); if (FAILED(res)) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not get IDXGIInfoQueue interface"); + return false; } + + return true; } #endif -static void D3D12_INTERNAL_TryInitializeD3D12Debug(D3D12Renderer *renderer) +static bool D3D12_INTERNAL_TryInitializeD3D12Debug(D3D12Renderer *renderer) { PFN_D3D12_GET_DEBUG_INTERFACE D3D12GetDebugInterfaceFunc; HRESULT res; @@ -8343,21 +8527,20 @@ static void D3D12_INTERNAL_TryInitializeD3D12Debug(D3D12Renderer *renderer) renderer->d3d12_dll, D3D12_GET_DEBUG_INTERFACE_FUNC); if (D3D12GetDebugInterfaceFunc == NULL) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not load function: " D3D12_GET_DEBUG_INTERFACE_FUNC); - return; + return false; } res = D3D12GetDebugInterfaceFunc(D3D_GUID(D3D_IID_ID3D12Debug), (void **)&renderer->d3d12Debug); if (FAILED(res)) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not get ID3D12Debug interface"); - return; + return false; } ID3D12Debug_EnableDebugLayer(renderer->d3d12Debug); + return true; } #if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) -static bool D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(D3D12Renderer *renderer) +static void D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(D3D12Renderer *renderer) { ID3D12InfoQueue *infoQueue = NULL; D3D12_MESSAGE_SEVERITY severities[] = { D3D12_MESSAGE_SEVERITY_INFO }; @@ -8369,7 +8552,7 @@ static bool D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(D3D12Renderer *rende D3D_GUID(D3D_IID_ID3D12InfoQueue), (void **)&infoQueue); if (FAILED(res)) { - CHECK_D3D12_ERROR_AND_RETURN("Failed to convert ID3D12Device to ID3D12InfoQueue", false); + return; } SDL_zero(filter); @@ -8385,8 +8568,6 @@ static bool D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(D3D12Renderer *rende true); ID3D12InfoQueue_Release(infoQueue); - - return true; } static void WINAPI D3D12_INTERNAL_OnD3D12DebugInfoMsg( @@ -8530,6 +8711,7 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD 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); @@ -8541,8 +8723,10 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD #ifdef HAVE_IDXGIINFOQUEUE // Initialize the DXGI debug layer, if applicable if (debugMode) { - D3D12_INTERNAL_TryInitializeDXGIDebug(renderer); + hasDxgiDebug = D3D12_INTERNAL_TryInitializeDXGIDebug(renderer); } +#else + hasDxgiDebug = true; #endif // Load the CreateDXGIFactory1 function @@ -8699,7 +8883,27 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD // Initialize the D3D12 debug layer, if applicable if (debugMode) { - D3D12_INTERNAL_TryInitializeD3D12Debug(renderer); + bool hasD3d12Debug = D3D12_INTERNAL_TryInitializeD3D12Debug(renderer); +#if (defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + if (hasD3d12Debug) { + SDL_LogInfo( + SDL_LOG_CATEGORY_GPU, + "Validation layers enabled, expect debug level performance!"); +#else + if (hasDxgiDebug && hasD3d12Debug) { + SDL_LogInfo( + SDL_LOG_CATEGORY_GPU, + "Validation layers enabled, expect debug level performance!"); + } else if (hasDxgiDebug || hasD3d12Debug) { + SDL_LogWarn( + SDL_LOG_CATEGORY_GPU, + "Validation layers partially enabled, some warnings may not be available"); +#endif + } else { + SDL_LogWarn( + SDL_LOG_CATEGORY_GPU, + "Validation layers not found, continuing without validation"); + } } // Create the D3D12Device @@ -8746,9 +8950,7 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD // Initialize the D3D12 debug info queue, if applicable if (debugMode) { - if (!D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(renderer)) { - return NULL; - } + D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(renderer); D3D12_INTERNAL_TryInitializeD3D12DebugInfoLogger(renderer); } #endif @@ -9029,8 +9231,23 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD return NULL; } + SDL_GPUShaderFormat shaderFormats = SDL_GPU_SHADERFORMAT_DXBC; + + D3D12_FEATURE_DATA_SHADER_MODEL shaderModel; + shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_0; + + res = ID3D12Device_CheckFeatureSupport( + renderer->device, + D3D12_FEATURE_SHADER_MODEL, + &shaderModel, + sizeof(shaderModel)); + if (SUCCEEDED(res) && shaderModel.HighestShaderModel >= D3D_SHADER_MODEL_6_0) { + shaderFormats |= SDL_GPU_SHADERFORMAT_DXIL; + } + ASSIGN_DRIVER(D3D12) result->driverData = (SDL_GPURenderer *)renderer; + result->shader_formats = shaderFormats; result->debug_mode = debugMode; renderer->sdlGPUDevice = result; @@ -9039,7 +9256,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/src/gpu/metal/SDL_gpu_metal.m b/src/gpu/metal/SDL_gpu_metal.m index d83079f5..26dea454 100644 --- a/src/gpu/metal/SDL_gpu_metal.m +++ b/src/gpu/metal/SDL_gpu_metal.m @@ -476,33 +476,21 @@ typedef struct MetalShader typedef struct MetalGraphicsPipeline { + GraphicsPipelineCommonHeader header; + id handle; SDL_GPURasterizerState rasterizerState; SDL_GPUPrimitiveType primitiveType; id depth_stencil_state; - - Uint32 vertexSamplerCount; - Uint32 vertexUniformBufferCount; - Uint32 vertexStorageBufferCount; - Uint32 vertexStorageTextureCount; - - Uint32 fragmentSamplerCount; - Uint32 fragmentUniformBufferCount; - Uint32 fragmentStorageBufferCount; - Uint32 fragmentStorageTextureCount; } MetalGraphicsPipeline; typedef struct MetalComputePipeline { + ComputePipelineCommonHeader header; + id handle; - Uint32 numSamplers; - Uint32 numReadonlyStorageTextures; - Uint32 numReadWriteStorageTextures; - Uint32 numReadonlyStorageBuffers; - Uint32 numReadWriteStorageBuffers; - Uint32 numUniformBuffers; Uint32 threadcountX; Uint32 threadcountY; Uint32 threadcountZ; @@ -836,6 +824,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, @@ -864,6 +863,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, @@ -1069,12 +1073,12 @@ static SDL_GPUComputePipeline *METAL_CreateComputePipeline( pipeline = SDL_calloc(1, sizeof(MetalComputePipeline)); pipeline->handle = handle; - pipeline->numSamplers = createinfo->num_samplers; - pipeline->numReadonlyStorageTextures = createinfo->num_readonly_storage_textures; - pipeline->numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures; - pipeline->numReadonlyStorageBuffers = createinfo->num_readonly_storage_buffers; - pipeline->numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers; - pipeline->numUniformBuffers = createinfo->num_uniform_buffers; + pipeline->header.numSamplers = createinfo->num_samplers; + pipeline->header.numReadonlyStorageTextures = createinfo->num_readonly_storage_textures; + pipeline->header.numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures; + pipeline->header.numReadonlyStorageBuffers = createinfo->num_readonly_storage_buffers; + pipeline->header.numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers; + pipeline->header.numUniformBuffers = createinfo->num_uniform_buffers; pipeline->threadcountX = createinfo->threadcount_x; pipeline->threadcountY = createinfo->threadcount_y; pipeline->threadcountZ = createinfo->threadcount_z; @@ -1218,14 +1222,14 @@ static SDL_GPUGraphicsPipeline *METAL_CreateGraphicsPipeline( result->depth_stencil_state = depthStencilState; result->rasterizerState = createinfo->rasterizer_state; result->primitiveType = createinfo->primitive_type; - result->vertexSamplerCount = vertexShader->numSamplers; - result->vertexUniformBufferCount = vertexShader->numUniformBuffers; - result->vertexStorageBufferCount = vertexShader->numStorageBuffers; - result->vertexStorageTextureCount = vertexShader->numStorageTextures; - result->fragmentSamplerCount = fragmentShader->numSamplers; - result->fragmentUniformBufferCount = fragmentShader->numUniformBuffers; - result->fragmentStorageBufferCount = fragmentShader->numStorageBuffers; - result->fragmentStorageTextureCount = fragmentShader->numStorageTextures; + result->header.num_vertex_samplers = vertexShader->numSamplers; + result->header.num_vertex_uniform_buffers = vertexShader->numUniformBuffers; + result->header.num_vertex_storage_buffers = vertexShader->numStorageBuffers; + result->header.num_vertex_storage_textures = vertexShader->numStorageTextures; + result->header.num_fragment_samplers = fragmentShader->numSamplers; + result->header.num_fragment_uniform_buffers = fragmentShader->numUniformBuffers; + result->header.num_fragment_storage_buffers = fragmentShader->numStorageBuffers; + result->header.num_fragment_storage_textures = fragmentShader->numStorageTextures; return (SDL_GPUGraphicsPipeline *)result; } } @@ -2423,14 +2427,14 @@ static void METAL_BindGraphicsPipeline( metalCommandBuffer->needFragmentUniformBufferBind[i] = true; } - for (i = 0; i < pipeline->vertexUniformBufferCount; i += 1) { + for (i = 0; i < pipeline->header.num_vertex_uniform_buffers; i += 1) { if (metalCommandBuffer->vertexUniformBuffers[i] == NULL) { metalCommandBuffer->vertexUniformBuffers[i] = METAL_INTERNAL_AcquireUniformBufferFromPool( metalCommandBuffer); } } - for (i = 0; i < pipeline->fragmentUniformBufferCount; i += 1) { + for (i = 0; i < pipeline->header.num_fragment_uniform_buffers; i += 1) { if (metalCommandBuffer->fragmentUniformBuffers[i] == NULL) { metalCommandBuffer->fragmentUniformBuffers[i] = METAL_INTERNAL_AcquireUniformBufferFromPool( metalCommandBuffer); @@ -2661,11 +2665,11 @@ static void METAL_INTERNAL_BindGraphicsResources( // Vertex Samplers+Textures if (commandBuffer->needVertexSamplerBind) { - if (graphicsPipeline->vertexSamplerCount > 0) { + if (graphicsPipeline->header.num_vertex_samplers > 0) { [commandBuffer->renderEncoder setVertexSamplerStates:commandBuffer->vertexSamplers - withRange:NSMakeRange(0, graphicsPipeline->vertexSamplerCount)]; + withRange:NSMakeRange(0, graphicsPipeline->header.num_vertex_samplers)]; [commandBuffer->renderEncoder setVertexTextures:commandBuffer->vertexTextures - withRange:NSMakeRange(0, graphicsPipeline->vertexSamplerCount)]; + withRange:NSMakeRange(0, graphicsPipeline->header.num_vertex_samplers)]; } commandBuffer->needVertexSamplerBind = false; } @@ -2673,10 +2677,10 @@ static void METAL_INTERNAL_BindGraphicsResources( // Vertex Storage Textures if (commandBuffer->needVertexStorageTextureBind) { - if (graphicsPipeline->vertexStorageTextureCount > 0) { + if (graphicsPipeline->header.num_vertex_storage_textures > 0) { [commandBuffer->renderEncoder setVertexTextures:commandBuffer->vertexStorageTextures - withRange:NSMakeRange(graphicsPipeline->vertexSamplerCount, - graphicsPipeline->vertexStorageTextureCount)]; + withRange:NSMakeRange(graphicsPipeline->header.num_vertex_samplers, + graphicsPipeline->header.num_vertex_storage_textures)]; } commandBuffer->needVertexStorageTextureBind = false; } @@ -2684,20 +2688,20 @@ static void METAL_INTERNAL_BindGraphicsResources( // Vertex Storage Buffers if (commandBuffer->needVertexStorageBufferBind) { - if (graphicsPipeline->vertexStorageBufferCount > 0) { + if (graphicsPipeline->header.num_vertex_storage_buffers > 0) { [commandBuffer->renderEncoder setVertexBuffers:commandBuffer->vertexStorageBuffers offsets:offsets - withRange:NSMakeRange(graphicsPipeline->vertexUniformBufferCount, - graphicsPipeline->vertexStorageBufferCount)]; + withRange:NSMakeRange(graphicsPipeline->header.num_vertex_uniform_buffers, + graphicsPipeline->header.num_vertex_storage_buffers)]; } commandBuffer->needVertexStorageBufferBind = false; } // Vertex Uniform Buffers - for (Uint32 i = 0; i < graphicsPipeline->vertexUniformBufferCount; i += 1) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_uniform_buffers; i += 1) { if (commandBuffer->needVertexUniformBufferBind[i]) { - if (graphicsPipeline->vertexUniformBufferCount > i) { + if (graphicsPipeline->header.num_vertex_uniform_buffers > i) { [commandBuffer->renderEncoder setVertexBuffer:commandBuffer->vertexUniformBuffers[i]->handle offset:commandBuffer->vertexUniformBuffers[i]->drawOffset @@ -2710,11 +2714,11 @@ static void METAL_INTERNAL_BindGraphicsResources( // Fragment Samplers+Textures if (commandBuffer->needFragmentSamplerBind) { - if (graphicsPipeline->fragmentSamplerCount > 0) { + if (graphicsPipeline->header.num_fragment_samplers > 0) { [commandBuffer->renderEncoder setFragmentSamplerStates:commandBuffer->fragmentSamplers - withRange:NSMakeRange(0, graphicsPipeline->fragmentSamplerCount)]; + withRange:NSMakeRange(0, graphicsPipeline->header.num_fragment_samplers)]; [commandBuffer->renderEncoder setFragmentTextures:commandBuffer->fragmentTextures - withRange:NSMakeRange(0, graphicsPipeline->fragmentSamplerCount)]; + withRange:NSMakeRange(0, graphicsPipeline->header.num_fragment_samplers)]; } commandBuffer->needFragmentSamplerBind = false; } @@ -2722,10 +2726,10 @@ static void METAL_INTERNAL_BindGraphicsResources( // Fragment Storage Textures if (commandBuffer->needFragmentStorageTextureBind) { - if (graphicsPipeline->fragmentStorageTextureCount > 0) { + if (graphicsPipeline->header.num_fragment_storage_textures > 0) { [commandBuffer->renderEncoder setFragmentTextures:commandBuffer->fragmentStorageTextures - withRange:NSMakeRange(graphicsPipeline->fragmentSamplerCount, - graphicsPipeline->fragmentStorageTextureCount)]; + withRange:NSMakeRange(graphicsPipeline->header.num_fragment_samplers, + graphicsPipeline->header.num_fragment_storage_textures)]; } commandBuffer->needFragmentStorageTextureBind = false; } @@ -2733,20 +2737,20 @@ static void METAL_INTERNAL_BindGraphicsResources( // Fragment Storage Buffers if (commandBuffer->needFragmentStorageBufferBind) { - if (graphicsPipeline->fragmentStorageBufferCount > 0) { + if (graphicsPipeline->header.num_fragment_storage_buffers > 0) { [commandBuffer->renderEncoder setFragmentBuffers:commandBuffer->fragmentStorageBuffers offsets:offsets - withRange:NSMakeRange(graphicsPipeline->fragmentUniformBufferCount, - graphicsPipeline->fragmentStorageBufferCount)]; + withRange:NSMakeRange(graphicsPipeline->header.num_fragment_uniform_buffers, + graphicsPipeline->header.num_fragment_storage_buffers)]; } commandBuffer->needFragmentStorageBufferBind = false; } // Fragment Uniform Buffers - for (Uint32 i = 0; i < graphicsPipeline->fragmentUniformBufferCount; i += 1) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_uniform_buffers; i += 1) { if (commandBuffer->needFragmentUniformBufferBind[i]) { - if (graphicsPipeline->fragmentUniformBufferCount > i) { + if (graphicsPipeline->header.num_fragment_uniform_buffers > i) { [commandBuffer->renderEncoder setFragmentBuffer:commandBuffer->fragmentUniformBuffers[i]->handle offset:commandBuffer->fragmentUniformBuffers[i]->drawOffset @@ -2765,38 +2769,38 @@ static void METAL_INTERNAL_BindComputeResources( NSUInteger offsets[MAX_STORAGE_BUFFERS_PER_STAGE] = { 0 }; if (commandBuffer->needComputeSamplerBind) { - if (computePipeline->numSamplers > 0) { + if (computePipeline->header.numSamplers > 0) { [commandBuffer->computeEncoder setTextures:commandBuffer->computeSamplerTextures - withRange:NSMakeRange(0, computePipeline->numSamplers)]; + withRange:NSMakeRange(0, computePipeline->header.numSamplers)]; [commandBuffer->computeEncoder setSamplerStates:commandBuffer->computeSamplers - withRange:NSMakeRange(0, computePipeline->numSamplers)]; + withRange:NSMakeRange(0, computePipeline->header.numSamplers)]; } commandBuffer->needComputeSamplerBind = false; } if (commandBuffer->needComputeReadOnlyStorageTextureBind) { - if (computePipeline->numReadonlyStorageTextures > 0) { + if (computePipeline->header.numReadonlyStorageTextures > 0) { [commandBuffer->computeEncoder setTextures:commandBuffer->computeReadOnlyTextures withRange:NSMakeRange( - computePipeline->numSamplers, - computePipeline->numReadonlyStorageTextures)]; + computePipeline->header.numSamplers, + computePipeline->header.numReadonlyStorageTextures)]; } commandBuffer->needComputeReadOnlyStorageTextureBind = false; } if (commandBuffer->needComputeReadOnlyStorageBufferBind) { - if (computePipeline->numReadonlyStorageBuffers > 0) { + if (computePipeline->header.numReadonlyStorageBuffers > 0) { [commandBuffer->computeEncoder setBuffers:commandBuffer->computeReadOnlyBuffers offsets:offsets - withRange:NSMakeRange(computePipeline->numUniformBuffers, - computePipeline->numReadonlyStorageBuffers)]; + withRange:NSMakeRange(computePipeline->header.numUniformBuffers, + computePipeline->header.numReadonlyStorageBuffers)]; } commandBuffer->needComputeReadOnlyStorageBufferBind = false; } for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) { if (commandBuffer->needComputeUniformBufferBind[i]) { - if (computePipeline->numUniformBuffers > i) { + if (computePipeline->header.numUniformBuffers > i) { [commandBuffer->computeEncoder setBuffer:commandBuffer->computeUniformBuffers[i]->handle offset:commandBuffer->computeUniformBuffers[i]->drawOffset @@ -3144,7 +3148,7 @@ static void METAL_BindComputePipeline( metalCommandBuffer->needComputeUniformBufferBind[i] = true; } - for (Uint32 i = 0; i < pipeline->numUniformBuffers; i += 1) { + for (Uint32 i = 0; i < pipeline->header.numUniformBuffers; i += 1) { if (metalCommandBuffer->computeUniformBuffers[i] == NULL) { metalCommandBuffer->computeUniformBuffers[i] = METAL_INTERNAL_AcquireUniformBufferFromPool( metalCommandBuffer); @@ -3152,22 +3156,22 @@ static void METAL_BindComputePipeline( } // Bind write-only resources - if (pipeline->numReadWriteStorageTextures > 0) { + if (pipeline->header.numReadWriteStorageTextures > 0) { [metalCommandBuffer->computeEncoder setTextures:metalCommandBuffer->computeReadWriteTextures withRange:NSMakeRange( - pipeline->numSamplers + - pipeline->numReadonlyStorageTextures, - pipeline->numReadWriteStorageTextures)]; + pipeline->header.numSamplers + + pipeline->header.numReadonlyStorageTextures, + pipeline->header.numReadWriteStorageTextures)]; } NSUInteger offsets[MAX_COMPUTE_WRITE_BUFFERS] = { 0 }; - if (pipeline->numReadWriteStorageBuffers > 0) { + if (pipeline->header.numReadWriteStorageBuffers > 0) { [metalCommandBuffer->computeEncoder setBuffers:metalCommandBuffer->computeReadWriteBuffers offsets:offsets withRange:NSMakeRange( - pipeline->numUniformBuffers + - pipeline->numReadonlyStorageBuffers, - pipeline->numReadWriteStorageBuffers)]; + pipeline->header.numUniformBuffers + + pipeline->header.numReadonlyStorageBuffers, + pipeline->header.numReadWriteStorageBuffers)]; } } } @@ -4291,8 +4295,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); } @@ -4594,6 +4603,7 @@ static SDL_GPUDevice *METAL_CreateDevice(bool debugMode, bool preferLowPower, SD SDL_GPUDevice *result = SDL_calloc(1, sizeof(SDL_GPUDevice)); ASSIGN_DRIVER(METAL) result->driverData = (SDL_GPURenderer *)renderer; + result->shader_formats = SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_METALLIB; renderer->sdlGPUDevice = result; return result; @@ -4602,7 +4612,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/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index ebfeb2dd..068d6820 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -703,7 +703,7 @@ typedef struct WindowData // Synchronization primitives VkSemaphore imageAvailableSemaphore[MAX_FRAMES_IN_FLIGHT]; - VkSemaphore renderFinishedSemaphore[MAX_FRAMES_IN_FLIGHT]; + VkSemaphore *renderFinishedSemaphore; SDL_GPUFence *inFlightFences[MAX_FRAMES_IN_FLIGHT]; Uint32 frameCounter; @@ -793,13 +793,13 @@ typedef struct DescriptorSetLayout typedef struct GraphicsPipelineResourceLayoutHashTableKey { Uint32 vertexSamplerCount; - Uint32 vertexStorageBufferCount; Uint32 vertexStorageTextureCount; + Uint32 vertexStorageBufferCount; Uint32 vertexUniformBufferCount; Uint32 fragmentSamplerCount; - Uint32 fragmentStorageBufferCount; Uint32 fragmentStorageTextureCount; + Uint32 fragmentStorageBufferCount; Uint32 fragmentUniformBufferCount; } GraphicsPipelineResourceLayoutHashTableKey; @@ -817,18 +817,20 @@ typedef struct VulkanGraphicsPipelineResourceLayout DescriptorSetLayout *descriptorSetLayouts[4]; Uint32 vertexSamplerCount; - Uint32 vertexStorageBufferCount; Uint32 vertexStorageTextureCount; + Uint32 vertexStorageBufferCount; Uint32 vertexUniformBufferCount; Uint32 fragmentSamplerCount; - Uint32 fragmentStorageBufferCount; Uint32 fragmentStorageTextureCount; + Uint32 fragmentStorageBufferCount; Uint32 fragmentUniformBufferCount; } VulkanGraphicsPipelineResourceLayout; typedef struct VulkanGraphicsPipeline { + GraphicsPipelineCommonHeader header; + VkPipeline pipeline; SDL_GPUPrimitiveType primitiveType; @@ -872,6 +874,8 @@ typedef struct VulkanComputePipelineResourceLayout typedef struct VulkanComputePipeline { + ComputePipelineCommonHeader header; + VkShaderModule shaderModule; VkPipeline pipeline; VulkanComputePipelineResourceLayout *resourceLayout; @@ -1009,25 +1013,33 @@ typedef struct VulkanCommandBuffer Uint32 vertexBufferCount; bool needVertexBufferBind; - VulkanTexture *vertexSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanSampler *vertexSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanTexture *vertexStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - VulkanBuffer *vertexStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + VkImageView vertexSamplerTextureViewBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkSampler vertexSamplerBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkImageView vertexStorageTextureViewBindings[MAX_STORAGE_TEXTURES_PER_STAGE]; + VkBuffer vertexStorageBufferBindings[MAX_STORAGE_BUFFERS_PER_STAGE]; - VulkanTexture *fragmentSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanSampler *fragmentSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanTexture *fragmentStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - VulkanBuffer *fragmentStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + VkImageView fragmentSamplerTextureViewBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkSampler fragmentSamplerBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkImageView fragmentStorageTextureViewBindings[MAX_STORAGE_TEXTURES_PER_STAGE]; + VkBuffer fragmentStorageBufferBindings[MAX_STORAGE_BUFFERS_PER_STAGE]; + VkImageView computeSamplerTextureViewBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkSampler computeSamplerBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkImageView readOnlyComputeStorageTextureViewBindings[MAX_STORAGE_TEXTURES_PER_STAGE]; + VkBuffer readOnlyComputeStorageBufferBindings[MAX_STORAGE_BUFFERS_PER_STAGE]; + + // Track these separately because barriers can happen mid compute pass + VulkanTexture *readOnlyComputeStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; + VulkanBuffer *readOnlyComputeStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + + VkImageView readWriteComputeStorageTextureViewBindings[MAX_COMPUTE_WRITE_TEXTURES]; + VkBuffer readWriteComputeStorageBufferBindings[MAX_COMPUTE_WRITE_BUFFERS]; + + // Track these separately because they are barriered when the compute pass begins VulkanTextureSubresource *readWriteComputeStorageTextureSubresources[MAX_COMPUTE_WRITE_TEXTURES]; Uint32 readWriteComputeStorageTextureSubresourceCount; VulkanBuffer *readWriteComputeStorageBuffers[MAX_COMPUTE_WRITE_BUFFERS]; - VulkanTexture *computeSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanSampler *computeSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanTexture *readOnlyComputeStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - VulkanBuffer *readOnlyComputeStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; - // Uniform buffers VulkanUniformBuffer *vertexUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE]; @@ -1088,6 +1100,7 @@ struct VulkanRenderer VkPhysicalDevice physicalDevice; VkPhysicalDeviceProperties2KHR physicalDeviceProperties; VkPhysicalDeviceDriverPropertiesKHR physicalDeviceDriverProperties; + VkPhysicalDeviceFeatures desiredDeviceFeatures; VkDevice logicalDevice; Uint8 integratedMemoryNotification; Uint8 outOfDeviceLocalMemoryWarning; @@ -1178,6 +1191,9 @@ struct VulkanRenderer SDL_Mutex *acquireUniformBufferLock; SDL_Mutex *renderPassFetchLock; SDL_Mutex *framebufferFetchLock; + SDL_Mutex *graphicsPipelineLayoutFetchLock; + SDL_Mutex *computePipelineLayoutFetchLock; + SDL_Mutex *descriptorSetLayoutFetchLock; SDL_Mutex *windowLock; Uint8 defragInProgress; @@ -3161,7 +3177,6 @@ static void VULKAN_INTERNAL_DestroySwapchain( SDL_free(windowData->textureContainers[i].activeTexture->subresources); SDL_free(windowData->textureContainers[i].activeTexture); } - windowData->imageCount = 0; SDL_free(windowData->textureContainers); windowData->textureContainers = NULL; @@ -3190,7 +3205,8 @@ static void VULKAN_INTERNAL_DestroySwapchain( NULL); windowData->imageAvailableSemaphore[i] = VK_NULL_HANDLE; } - + } + for (i = 0; i < windowData->imageCount; i += 1) { if (windowData->renderFinishedSemaphore[i]) { renderer->vkDestroySemaphore( renderer->logicalDevice, @@ -3199,6 +3215,10 @@ static void VULKAN_INTERNAL_DestroySwapchain( windowData->renderFinishedSemaphore[i] = VK_NULL_HANDLE; } } + SDL_free(windowData->renderFinishedSemaphore); + windowData->renderFinishedSemaphore = NULL; + + windowData->imageCount = 0; } static void VULKAN_INTERNAL_DestroyGraphicsPipelineResourceLayout( @@ -3277,7 +3297,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) @@ -3308,7 +3328,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) @@ -3341,7 +3361,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) @@ -3698,10 +3718,13 @@ static DescriptorSetLayout *VULKAN_INTERNAL_FetchDescriptorSetLayout( key.writeStorageBufferCount = writeStorageBufferCount; key.uniformBufferCount = uniformBufferCount; + SDL_LockMutex(renderer->descriptorSetLayoutFetchLock); + if (SDL_FindInHashTable( renderer->descriptorSetLayoutHashTable, (const void *)&key, (const void **)&layout)) { + SDL_UnlockMutex(renderer->descriptorSetLayoutFetchLock); return layout; } @@ -3784,7 +3807,10 @@ static DescriptorSetLayout *VULKAN_INTERNAL_FetchDescriptorSetLayout( NULL, &descriptorSetLayout); - CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateDescriptorSetLayout, NULL); + if (vulkanResult != VK_SUCCESS) { + SDL_UnlockMutex(renderer->descriptorSetLayoutFetchLock); + CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateDescriptorSetLayout, NULL); + } layout = SDL_malloc(sizeof(DescriptorSetLayout)); layout->descriptorSetLayout = descriptorSetLayout; @@ -3806,6 +3832,7 @@ static DescriptorSetLayout *VULKAN_INTERNAL_FetchDescriptorSetLayout( (const void *)allocedKey, (const void *)layout, true); + SDL_UnlockMutex(renderer->descriptorSetLayoutFetchLock); return layout; } @@ -3826,10 +3853,14 @@ static VulkanGraphicsPipelineResourceLayout *VULKAN_INTERNAL_FetchGraphicsPipeli key.fragmentStorageTextureCount = fragmentShader->numStorageTextures; key.fragmentStorageBufferCount = fragmentShader->numStorageBuffers; key.fragmentUniformBufferCount = fragmentShader->numUniformBuffers; + + SDL_LockMutex(renderer->graphicsPipelineLayoutFetchLock); + if (SDL_FindInHashTable( renderer->graphicsPipelineResourceLayoutHashTable, (const void *)&key, (const void **)&pipelineResourceLayout)) { + SDL_UnlockMutex(renderer->graphicsPipelineLayoutFetchLock); return pipelineResourceLayout; } @@ -3912,6 +3943,7 @@ static VulkanGraphicsPipelineResourceLayout *VULKAN_INTERNAL_FetchGraphicsPipeli if (vulkanResult != VK_SUCCESS) { VULKAN_INTERNAL_DestroyGraphicsPipelineResourceLayout(renderer, pipelineResourceLayout); + SDL_UnlockMutex(renderer->graphicsPipelineLayoutFetchLock); CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreatePipelineLayout, NULL); } @@ -3923,6 +3955,7 @@ static VulkanGraphicsPipelineResourceLayout *VULKAN_INTERNAL_FetchGraphicsPipeli (const void *)allocedKey, (const void *)pipelineResourceLayout, true); + SDL_UnlockMutex(renderer->graphicsPipelineLayoutFetchLock); return pipelineResourceLayout; } @@ -3941,10 +3974,13 @@ static VulkanComputePipelineResourceLayout *VULKAN_INTERNAL_FetchComputePipeline key.readWriteStorageBufferCount = createinfo->num_readwrite_storage_buffers; key.uniformBufferCount = createinfo->num_uniform_buffers; + SDL_LockMutex(renderer->computePipelineLayoutFetchLock); + if (SDL_FindInHashTable( renderer->computePipelineResourceLayoutHashTable, (const void *)&key, (const void **)&pipelineResourceLayout)) { + SDL_UnlockMutex(renderer->computePipelineLayoutFetchLock); return pipelineResourceLayout; } @@ -4013,6 +4049,7 @@ static VulkanComputePipelineResourceLayout *VULKAN_INTERNAL_FetchComputePipeline if (vulkanResult != VK_SUCCESS) { VULKAN_INTERNAL_DestroyComputePipelineResourceLayout(renderer, pipelineResourceLayout); + SDL_UnlockMutex(renderer->computePipelineLayoutFetchLock); CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreatePipelineLayout, NULL); } @@ -4024,6 +4061,7 @@ static VulkanComputePipelineResourceLayout *VULKAN_INTERNAL_FetchComputePipeline (const void *)allocedKey, (const void *)pipelineResourceLayout, true); + SDL_UnlockMutex(renderer->computePipelineLayoutFetchLock); return pipelineResourceLayout; } @@ -4758,6 +4796,12 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false); } + windowData->inFlightFences[i] = NULL; + } + + windowData->renderFinishedSemaphore = SDL_malloc( + sizeof(VkSemaphore) * windowData->imageCount); + for (i = 0; i < windowData->imageCount; i += 1) { vulkanResult = renderer->vkCreateSemaphore( renderer->logicalDevice, &semaphoreCreateInfo, @@ -4777,8 +4821,6 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( windowData->swapchain = VK_NULL_HANDLE; CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false); } - - windowData->inFlightFences[i] = NULL; } windowData->needsSwapchainRecreate = false; @@ -4914,6 +4956,9 @@ static void VULKAN_DestroyDevice( SDL_DestroyMutex(renderer->acquireUniformBufferLock); SDL_DestroyMutex(renderer->renderPassFetchLock); SDL_DestroyMutex(renderer->framebufferFetchLock); + SDL_DestroyMutex(renderer->graphicsPipelineLayoutFetchLock); + SDL_DestroyMutex(renderer->computePipelineLayoutFetchLock); + SDL_DestroyMutex(renderer->descriptorSetLayoutFetchLock); SDL_DestroyMutex(renderer->windowLock); renderer->vkDestroyDevice(renderer->logicalDevice, NULL); @@ -5070,8 +5115,8 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pBufferInfo = NULL; - imageInfos[imageInfoCount].sampler = commandBuffer->vertexSamplers[i]->sampler; - imageInfos[imageInfoCount].imageView = commandBuffer->vertexSamplerTextures[i]->fullView; + imageInfos[imageInfoCount].sampler = commandBuffer->vertexSamplerBindings[i]; + imageInfos[imageInfoCount].imageView = commandBuffer->vertexSamplerTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -5094,7 +5139,7 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pBufferInfo = NULL; imageInfos[imageInfoCount].sampler = VK_NULL_HANDLE; - imageInfos[imageInfoCount].imageView = commandBuffer->vertexStorageTextures[i]->fullView; + imageInfos[imageInfoCount].imageView = commandBuffer->vertexStorageTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_GENERAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -5116,7 +5161,7 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pImageInfo = NULL; - bufferInfos[bufferInfoCount].buffer = commandBuffer->vertexStorageBuffers[i]->buffer; + bufferInfos[bufferInfoCount].buffer = commandBuffer->vertexStorageBufferBindings[i]; bufferInfos[bufferInfoCount].offset = 0; bufferInfos[bufferInfoCount].range = VK_WHOLE_SIZE; @@ -5189,8 +5234,8 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pBufferInfo = NULL; - imageInfos[imageInfoCount].sampler = commandBuffer->fragmentSamplers[i]->sampler; - imageInfos[imageInfoCount].imageView = commandBuffer->fragmentSamplerTextures[i]->fullView; + imageInfos[imageInfoCount].sampler = commandBuffer->fragmentSamplerBindings[i]; + imageInfos[imageInfoCount].imageView = commandBuffer->fragmentSamplerTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -5213,7 +5258,7 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pBufferInfo = NULL; imageInfos[imageInfoCount].sampler = VK_NULL_HANDLE; - imageInfos[imageInfoCount].imageView = commandBuffer->fragmentStorageTextures[i]->fullView; + imageInfos[imageInfoCount].imageView = commandBuffer->fragmentStorageTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_GENERAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -5235,7 +5280,7 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pImageInfo = NULL; - bufferInfos[bufferInfoCount].buffer = commandBuffer->fragmentStorageBuffers[i]->buffer; + bufferInfos[bufferInfoCount].buffer = commandBuffer->fragmentStorageBufferBindings[i]; bufferInfos[bufferInfoCount].offset = 0; bufferInfos[bufferInfoCount].range = VK_WHOLE_SIZE; @@ -6508,9 +6553,38 @@ static SDL_GPUGraphicsPipeline *VULKAN_CreateGraphicsPipeline( &nameInfo); } + // Put this data in the pipeline we can do validation in gpu.c + graphicsPipeline->header.num_vertex_samplers = graphicsPipeline->resourceLayout->vertexSamplerCount; + graphicsPipeline->header.num_vertex_storage_buffers = graphicsPipeline->resourceLayout->vertexStorageBufferCount; + graphicsPipeline->header.num_vertex_storage_textures = graphicsPipeline->resourceLayout->vertexStorageTextureCount; + graphicsPipeline->header.num_vertex_uniform_buffers = graphicsPipeline->resourceLayout->vertexUniformBufferCount; + graphicsPipeline->header.num_fragment_samplers = graphicsPipeline->resourceLayout->fragmentSamplerCount; + graphicsPipeline->header.num_fragment_storage_buffers = graphicsPipeline->resourceLayout->fragmentStorageBufferCount; + graphicsPipeline->header.num_fragment_storage_textures = graphicsPipeline->resourceLayout->fragmentStorageTextureCount; + graphicsPipeline->header.num_fragment_uniform_buffers = graphicsPipeline->resourceLayout->fragmentUniformBufferCount; + return (SDL_GPUGraphicsPipeline *)graphicsPipeline; } +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) @@ -6526,6 +6600,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; @@ -6602,6 +6680,14 @@ static SDL_GPUComputePipeline *VULKAN_CreateComputePipeline( &nameInfo); } + // Track these here for debug layer + vulkanComputePipeline->header.numSamplers = vulkanComputePipeline->resourceLayout->numSamplers; + vulkanComputePipeline->header.numReadonlyStorageTextures = vulkanComputePipeline->resourceLayout->numReadonlyStorageTextures; + vulkanComputePipeline->header.numReadonlyStorageBuffers = vulkanComputePipeline->resourceLayout->numReadonlyStorageBuffers; + vulkanComputePipeline->header.numReadWriteStorageTextures = vulkanComputePipeline->resourceLayout->numReadWriteStorageTextures; + vulkanComputePipeline->header.numReadWriteStorageBuffers = vulkanComputePipeline->resourceLayout->numReadWriteStorageBuffers; + vulkanComputePipeline->header.numUniformBuffers = vulkanComputePipeline->resourceLayout->numUniformBuffers; + return (SDL_GPUComputePipeline *)vulkanComputePipeline; } @@ -6671,6 +6757,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; @@ -7398,21 +7488,21 @@ static void VULKAN_BindVertexSamplers( VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)textureSamplerBindings[i].texture; VulkanSampler *sampler = (VulkanSampler *)textureSamplerBindings[i].sampler; - if (vulkanCommandBuffer->vertexSamplers[firstSlot + i] != sampler) { + if (vulkanCommandBuffer->vertexSamplerBindings[firstSlot + i] != sampler->sampler) { VULKAN_INTERNAL_TrackSampler( vulkanCommandBuffer, (VulkanSampler *)textureSamplerBindings[i].sampler); - vulkanCommandBuffer->vertexSamplers[firstSlot + i] = (VulkanSampler *)textureSamplerBindings[i].sampler; + vulkanCommandBuffer->vertexSamplerBindings[firstSlot + i] = sampler->sampler; vulkanCommandBuffer->needNewVertexResourceDescriptorSet = true; } - if (vulkanCommandBuffer->vertexSamplerTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->vertexSamplerTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->vertexSamplerTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->vertexSamplerTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewVertexResourceDescriptorSet = true; } } @@ -7429,12 +7519,12 @@ static void VULKAN_BindVertexStorageTextures( for (Uint32 i = 0; i < numBindings; i += 1) { VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)storageTextures[i]; - if (vulkanCommandBuffer->vertexStorageTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->vertexStorageTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->vertexStorageTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->vertexStorageTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewVertexResourceDescriptorSet = true; } } @@ -7451,12 +7541,12 @@ static void VULKAN_BindVertexStorageBuffers( for (Uint32 i = 0; i < numBindings; i += 1) { VulkanBufferContainer *bufferContainer = (VulkanBufferContainer *)storageBuffers[i]; - if (vulkanCommandBuffer->vertexStorageBuffers[firstSlot + i] != bufferContainer->activeBuffer) { + if (vulkanCommandBuffer->vertexStorageBufferBindings[firstSlot + i] != bufferContainer->activeBuffer->buffer) { VULKAN_INTERNAL_TrackBuffer( vulkanCommandBuffer, bufferContainer->activeBuffer); - vulkanCommandBuffer->vertexStorageBuffers[firstSlot + i] = bufferContainer->activeBuffer; + vulkanCommandBuffer->vertexStorageBufferBindings[firstSlot + i] = bufferContainer->activeBuffer->buffer; vulkanCommandBuffer->needNewVertexResourceDescriptorSet = true; } } @@ -7474,21 +7564,21 @@ static void VULKAN_BindFragmentSamplers( VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)textureSamplerBindings[i].texture; VulkanSampler *sampler = (VulkanSampler *)textureSamplerBindings[i].sampler; - if (vulkanCommandBuffer->fragmentSamplers[firstSlot + i] != sampler) { + if (vulkanCommandBuffer->fragmentSamplerBindings[firstSlot + i] != sampler->sampler) { VULKAN_INTERNAL_TrackSampler( vulkanCommandBuffer, (VulkanSampler *)textureSamplerBindings[i].sampler); - vulkanCommandBuffer->fragmentSamplers[firstSlot + i] = (VulkanSampler *)textureSamplerBindings[i].sampler; + vulkanCommandBuffer->fragmentSamplerBindings[firstSlot + i] = sampler->sampler; vulkanCommandBuffer->needNewFragmentResourceDescriptorSet = true; } - if (vulkanCommandBuffer->fragmentSamplerTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->fragmentSamplerTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->fragmentSamplerTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->fragmentSamplerTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewFragmentResourceDescriptorSet = true; } } @@ -7505,12 +7595,12 @@ static void VULKAN_BindFragmentStorageTextures( for (Uint32 i = 0; i < numBindings; i += 1) { VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)storageTextures[i]; - if (vulkanCommandBuffer->fragmentStorageTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->fragmentStorageTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->fragmentStorageTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->fragmentStorageTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewFragmentResourceDescriptorSet = true; } } @@ -7529,12 +7619,12 @@ static void VULKAN_BindFragmentStorageBuffers( for (i = 0; i < numBindings; i += 1) { bufferContainer = (VulkanBufferContainer *)storageBuffers[i]; - if (vulkanCommandBuffer->fragmentStorageBuffers[firstSlot + i] != bufferContainer->activeBuffer) { + if (vulkanCommandBuffer->fragmentStorageBufferBindings[firstSlot + i] != bufferContainer->activeBuffer->buffer) { VULKAN_INTERNAL_TrackBuffer( vulkanCommandBuffer, bufferContainer->activeBuffer); - vulkanCommandBuffer->fragmentStorageBuffers[firstSlot + i] = bufferContainer->activeBuffer; + vulkanCommandBuffer->fragmentStorageBufferBindings[firstSlot + i] = bufferContainer->activeBuffer->buffer; vulkanCommandBuffer->needNewFragmentResourceDescriptorSet = true; } } @@ -8047,15 +8137,15 @@ static void VULKAN_EndRenderPass( SDL_zeroa(vulkanCommandBuffer->vertexBufferOffsets); vulkanCommandBuffer->vertexBufferCount = 0; - SDL_zeroa(vulkanCommandBuffer->vertexSamplers); - SDL_zeroa(vulkanCommandBuffer->vertexSamplerTextures); - SDL_zeroa(vulkanCommandBuffer->vertexStorageTextures); - SDL_zeroa(vulkanCommandBuffer->vertexStorageBuffers); + SDL_zeroa(vulkanCommandBuffer->vertexSamplerBindings); + SDL_zeroa(vulkanCommandBuffer->vertexSamplerTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->vertexStorageTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->vertexStorageBufferBindings); - SDL_zeroa(vulkanCommandBuffer->fragmentSamplers); - SDL_zeroa(vulkanCommandBuffer->fragmentSamplerTextures); - SDL_zeroa(vulkanCommandBuffer->fragmentStorageTextures); - SDL_zeroa(vulkanCommandBuffer->fragmentStorageBuffers); + SDL_zeroa(vulkanCommandBuffer->fragmentSamplerBindings); + SDL_zeroa(vulkanCommandBuffer->fragmentSamplerTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->fragmentStorageTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->fragmentStorageBufferBindings); } static void VULKAN_BeginComputePass( @@ -8085,6 +8175,7 @@ static void VULKAN_BeginComputePass( VULKAN_TEXTURE_USAGE_MODE_COMPUTE_STORAGE_READ_WRITE); vulkanCommandBuffer->readWriteComputeStorageTextureSubresources[i] = subresource; + vulkanCommandBuffer->readWriteComputeStorageTextureViewBindings[i] = subresource->computeWriteView; VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, @@ -8098,9 +8189,10 @@ static void VULKAN_BeginComputePass( vulkanCommandBuffer, bufferContainer, storageBufferBindings[i].cycle, - VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ); + VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ_WRITE); vulkanCommandBuffer->readWriteComputeStorageBuffers[i] = buffer; + vulkanCommandBuffer->readWriteComputeStorageBufferBindings[i] = buffer->buffer; VULKAN_INTERNAL_TrackBuffer( vulkanCommandBuffer, @@ -8152,21 +8244,21 @@ static void VULKAN_BindComputeSamplers( VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)textureSamplerBindings[i].texture; VulkanSampler *sampler = (VulkanSampler *)textureSamplerBindings[i].sampler; - if (vulkanCommandBuffer->computeSamplers[firstSlot + i] != sampler) { + if (vulkanCommandBuffer->computeSamplerBindings[firstSlot + i] != sampler->sampler) { VULKAN_INTERNAL_TrackSampler( vulkanCommandBuffer, sampler); - vulkanCommandBuffer->computeSamplers[firstSlot + i] = sampler; + vulkanCommandBuffer->computeSamplerBindings[firstSlot + i] = sampler->sampler; vulkanCommandBuffer->needNewComputeReadOnlyDescriptorSet = true; } - if (vulkanCommandBuffer->computeSamplerTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->computeSamplerTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->computeSamplerTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->computeSamplerTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewComputeReadOnlyDescriptorSet = true; } } @@ -8207,6 +8299,7 @@ static void VULKAN_BindComputeStorageTextures( textureContainer->activeTexture); vulkanCommandBuffer->readOnlyComputeStorageTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->readOnlyComputeStorageTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewComputeReadOnlyDescriptorSet = true; } } @@ -8246,6 +8339,7 @@ static void VULKAN_BindComputeStorageBuffers( bufferContainer->activeBuffer); vulkanCommandBuffer->readOnlyComputeStorageBuffers[firstSlot + i] = bufferContainer->activeBuffer; + vulkanCommandBuffer->readOnlyComputeStorageBufferBindings[firstSlot + i] = bufferContainer->activeBuffer->buffer; vulkanCommandBuffer->needNewComputeReadOnlyDescriptorSet = true; } } @@ -8320,8 +8414,8 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pBufferInfo = NULL; - imageInfos[imageInfoCount].sampler = commandBuffer->computeSamplers[i]->sampler; - imageInfos[imageInfoCount].imageView = commandBuffer->computeSamplerTextures[i]->fullView; + imageInfos[imageInfoCount].sampler = commandBuffer->computeSamplerBindings[i]; + imageInfos[imageInfoCount].imageView = commandBuffer->computeSamplerTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -8344,7 +8438,7 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pBufferInfo = NULL; imageInfos[imageInfoCount].sampler = VK_NULL_HANDLE; - imageInfos[imageInfoCount].imageView = commandBuffer->readOnlyComputeStorageTextures[i]->fullView; + imageInfos[imageInfoCount].imageView = commandBuffer->readOnlyComputeStorageTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_GENERAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -8366,7 +8460,7 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pImageInfo = NULL; - bufferInfos[bufferInfoCount].buffer = commandBuffer->readOnlyComputeStorageBuffers[i]->buffer; + bufferInfos[bufferInfoCount].buffer = commandBuffer->readOnlyComputeStorageBufferBindings[i]; bufferInfos[bufferInfoCount].offset = 0; bufferInfos[bufferInfoCount].range = VK_WHOLE_SIZE; @@ -8401,7 +8495,7 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pBufferInfo = NULL; imageInfos[imageInfoCount].sampler = VK_NULL_HANDLE; - imageInfos[imageInfoCount].imageView = commandBuffer->readWriteComputeStorageTextureSubresources[i]->computeWriteView; + imageInfos[imageInfoCount].imageView = commandBuffer->readWriteComputeStorageTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_GENERAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -8423,7 +8517,7 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pImageInfo = NULL; - bufferInfos[bufferInfoCount].buffer = commandBuffer->readWriteComputeStorageBuffers[i]->buffer; + bufferInfos[bufferInfoCount].buffer = commandBuffer->readWriteComputeStorageBufferBindings[i]; bufferInfos[bufferInfoCount].offset = 0; bufferInfos[bufferInfoCount].range = VK_WHOLE_SIZE; @@ -8590,9 +8684,12 @@ static void VULKAN_EndComputePass( } } - // we don't need a barrier because sampler state is always the default if sampler bit is set - SDL_zeroa(vulkanCommandBuffer->computeSamplerTextures); - SDL_zeroa(vulkanCommandBuffer->computeSamplers); + // we don't need a barrier for sampler resources because sampler state is always the default if sampler bit is set + SDL_zeroa(vulkanCommandBuffer->computeSamplerTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->computeSamplerBindings); + + SDL_zeroa(vulkanCommandBuffer->readWriteComputeStorageTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->readWriteComputeStorageBufferBindings); vulkanCommandBuffer->currentComputePipeline = NULL; @@ -9458,21 +9555,23 @@ static SDL_GPUCommandBuffer *VULKAN_AcquireCommandBuffer( SDL_zeroa(commandBuffer->vertexBufferOffsets); commandBuffer->vertexBufferCount = 0; - SDL_zeroa(commandBuffer->vertexSamplerTextures); - SDL_zeroa(commandBuffer->vertexSamplers); - SDL_zeroa(commandBuffer->vertexStorageTextures); - SDL_zeroa(commandBuffer->vertexStorageBuffers); + SDL_zeroa(commandBuffer->vertexSamplerTextureViewBindings); + SDL_zeroa(commandBuffer->vertexSamplerBindings); + SDL_zeroa(commandBuffer->vertexStorageTextureViewBindings); + SDL_zeroa(commandBuffer->vertexStorageBufferBindings); - SDL_zeroa(commandBuffer->fragmentSamplerTextures); - SDL_zeroa(commandBuffer->fragmentSamplers); - SDL_zeroa(commandBuffer->fragmentStorageTextures); - SDL_zeroa(commandBuffer->fragmentStorageBuffers); + SDL_zeroa(commandBuffer->fragmentSamplerTextureViewBindings); + SDL_zeroa(commandBuffer->fragmentSamplerBindings); + SDL_zeroa(commandBuffer->fragmentStorageTextureViewBindings); + SDL_zeroa(commandBuffer->fragmentStorageBufferBindings); SDL_zeroa(commandBuffer->readWriteComputeStorageTextureSubresources); commandBuffer->readWriteComputeStorageTextureSubresourceCount = 0; SDL_zeroa(commandBuffer->readWriteComputeStorageBuffers); - SDL_zeroa(commandBuffer->computeSamplerTextures); - SDL_zeroa(commandBuffer->computeSamplers); + SDL_zeroa(commandBuffer->computeSamplerTextureViewBindings); + SDL_zeroa(commandBuffer->computeSamplerBindings); + SDL_zeroa(commandBuffer->readOnlyComputeStorageTextureViewBindings); + SDL_zeroa(commandBuffer->readOnlyComputeStorageBufferBindings); SDL_zeroa(commandBuffer->readOnlyComputeStorageTextures); SDL_zeroa(commandBuffer->readOnlyComputeStorageBuffers); @@ -9969,7 +10068,7 @@ static bool VULKAN_INTERNAL_AcquireSwapchainTexture( } vulkanCommandBuffer->signalSemaphores[vulkanCommandBuffer->signalSemaphoreCount] = - windowData->renderFinishedSemaphore[windowData->frameCounter]; + windowData->renderFinishedSemaphore[swapchainImageIndex]; vulkanCommandBuffer->signalSemaphoreCount += 1; *swapchainTexture = (SDL_GPUTexture *)swapchainTextureContainer; @@ -10012,7 +10111,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) { @@ -10510,7 +10609,7 @@ static bool VULKAN_Submit( presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.pNext = NULL; presentInfo.pWaitSemaphores = - &presentData->windowData->renderFinishedSemaphore[presentData->windowData->frameCounter]; + &presentData->windowData->renderFinishedSemaphore[presentData->swapchainImageIndex]; presentInfo.waitSemaphoreCount = 1; presentInfo.pSwapchains = &presentData->windowData->swapchain; presentInfo.swapchainCount = 1; @@ -10523,7 +10622,7 @@ 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) { @@ -11161,12 +11260,14 @@ static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( renderer->vkGetPhysicalDeviceFeatures( physicalDevice, &deviceFeatures); - if (!deviceFeatures.independentBlend || - !deviceFeatures.imageCubeArray || - !deviceFeatures.depthClamp || - !deviceFeatures.shaderClipDistance || - !deviceFeatures.drawIndirectFirstInstance || - !deviceFeatures.sampleRateShading) { + + if ((!deviceFeatures.independentBlend && renderer->desiredDeviceFeatures.independentBlend) || + (!deviceFeatures.imageCubeArray && renderer->desiredDeviceFeatures.imageCubeArray) || + (!deviceFeatures.depthClamp && renderer->desiredDeviceFeatures.depthClamp) || + (!deviceFeatures.shaderClipDistance && renderer->desiredDeviceFeatures.shaderClipDistance) || + (!deviceFeatures.drawIndirectFirstInstance && renderer->desiredDeviceFeatures.drawIndirectFirstInstance) || + (!deviceFeatures.sampleRateShading && renderer->desiredDeviceFeatures.sampleRateShading) || + (!deviceFeatures.samplerAnisotropy && renderer->desiredDeviceFeatures.samplerAnisotropy)) { return 0; } @@ -11382,7 +11483,6 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( { VkResult vulkanResult; VkDeviceCreateInfo deviceCreateInfo; - VkPhysicalDeviceFeatures desiredDeviceFeatures; VkPhysicalDeviceFeatures haveDeviceFeatures; VkPhysicalDevicePortabilitySubsetFeaturesKHR portabilityFeatures; const char **deviceExtensions; @@ -11406,22 +11506,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; - desiredDeviceFeatures.sampleRateShading = VK_TRUE; - if (haveDeviceFeatures.fillModeNonSolid) { - desiredDeviceFeatures.fillModeNonSolid = VK_TRUE; + renderer->desiredDeviceFeatures.fillModeNonSolid = VK_TRUE; renderer->supportsFillModeNonSolid = true; } if (haveDeviceFeatures.multiDrawIndirect) { - desiredDeviceFeatures.multiDrawIndirect = VK_TRUE; + renderer->desiredDeviceFeatures.multiDrawIndirect = VK_TRUE; renderer->supportsMultiDrawIndirect = true; } @@ -11462,7 +11553,7 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( deviceCreateInfo.enabledExtensionCount); CreateDeviceExtensionArray(&renderer->supports, deviceExtensions); deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions; - deviceCreateInfo.pEnabledFeatures = &desiredDeviceFeatures; + deviceCreateInfo.pEnabledFeatures = &renderer->desiredDeviceFeatures; vulkanResult = renderer->vkCreateDevice( renderer->physicalDevice, @@ -11547,11 +11638,15 @@ static bool VULKAN_INTERNAL_PrepareVulkan( 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; - Uint8 result; + 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; @@ -11561,16 +11656,27 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this) return false; } - renderer = (VulkanRenderer *)SDL_malloc(sizeof(VulkanRenderer)); - SDL_memset(renderer, '\0', sizeof(VulkanRenderer)); + renderer = (VulkanRenderer *)SDL_calloc(1, sizeof(*renderer)); + if (renderer) { + // Opt out device features (higher compatibility in exchange for reduced functionality) + renderer->desiredDeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOLEAN, true) ? VK_TRUE : VK_FALSE; - result = VULKAN_INTERNAL_PrepareVulkan(renderer); + // These features have near universal support so they are always enabled + renderer->desiredDeviceFeatures.independentBlend = VK_TRUE; + renderer->desiredDeviceFeatures.sampleRateShading = VK_TRUE; + renderer->desiredDeviceFeatures.imageCubeArray = VK_TRUE; - if (result) { - renderer->vkDestroyInstance(renderer->instance, NULL); + result = VULKAN_INTERNAL_PrepareVulkan(renderer); + if (result) { + renderer->vkDestroyInstance(renderer->instance, NULL); + } + SDL_free(renderer); } - SDL_free(renderer); SDL_Vulkan_UnloadLibrary(); + return result; } @@ -11591,12 +11697,27 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S return NULL; } - renderer = (VulkanRenderer *)SDL_malloc(sizeof(VulkanRenderer)); - SDL_memset(renderer, '\0', sizeof(VulkanRenderer)); + renderer = (VulkanRenderer *)SDL_calloc(1, sizeof(*renderer)); + if (!renderer) { + SDL_Vulkan_UnloadLibrary(); + return false; + } + renderer->debugMode = debugMode; renderer->preferLowPower = preferLowPower; renderer->allowedFramesInFlight = 2; + // Opt out device features (higher compatibility in exchange for reduced functionality) + renderer->desiredDeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + + // These features have near universal support so they are always enabled + renderer->desiredDeviceFeatures.independentBlend = VK_TRUE; + renderer->desiredDeviceFeatures.sampleRateShading = VK_TRUE; + renderer->desiredDeviceFeatures.imageCubeArray = VK_TRUE; + if (!VULKAN_INTERNAL_PrepareVulkan(renderer)) { SDL_free(renderer); SDL_Vulkan_UnloadLibrary(); @@ -11715,6 +11836,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S ASSIGN_DRIVER(VULKAN) result->driverData = (SDL_GPURenderer *)renderer; + result->shader_formats = SDL_GPU_SHADERFORMAT_SPIRV; /* * Create initial swapchain array @@ -11734,6 +11856,9 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->acquireUniformBufferLock = SDL_CreateMutex(); renderer->renderPassFetchLock = SDL_CreateMutex(); renderer->framebufferFetchLock = SDL_CreateMutex(); + renderer->graphicsPipelineLayoutFetchLock = SDL_CreateMutex(); + renderer->computePipelineLayoutFetchLock = SDL_CreateMutex(); + renderer->descriptorSetLayoutFetchLock = SDL_CreateMutex(); renderer->windowLock = SDL_CreateMutex(); /* @@ -11794,7 +11919,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->renderPassHashTable = SDL_CreateHashTable( 0, // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful. - false, // manually synchronized due to timing + false, // manually synchronized due to lookup timing VULKAN_INTERNAL_RenderPassHashFunction, VULKAN_INTERNAL_RenderPassHashKeyMatch, VULKAN_INTERNAL_RenderPassHashDestroy, @@ -11810,7 +11935,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->graphicsPipelineResourceLayoutHashTable = SDL_CreateHashTable( 0, // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful. - true, // thread-safe + false, // manually synchronized due to lookup timing VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashFunction, VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashKeyMatch, VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashDestroy, @@ -11818,7 +11943,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->computePipelineResourceLayoutHashTable = SDL_CreateHashTable( 0, // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful. - true, // thread-safe + false, // manually synchronized due to lookup timing VULKAN_INTERNAL_ComputePipelineResourceLayoutHashFunction, VULKAN_INTERNAL_ComputePipelineResourceLayoutHashKeyMatch, VULKAN_INTERNAL_ComputePipelineResourceLayoutHashDestroy, @@ -11826,7 +11951,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->descriptorSetLayoutHashTable = SDL_CreateHashTable( 0, // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful. - true, // thread-safe + false, // manually synchronized due to lookup timing VULKAN_INTERNAL_DescriptorSetLayoutHashFunction, VULKAN_INTERNAL_DescriptorSetLayoutHashKeyMatch, VULKAN_INTERNAL_DescriptorSetLayoutHashDestroy, @@ -11905,7 +12030,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/src/haptic/SDL_haptic.c b/src/haptic/SDL_haptic.c index 1c945252..2e8024fa 100644 --- a/src/haptic/SDL_haptic.c +++ b/src/haptic/SDL_haptic.c @@ -418,7 +418,7 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) void SDL_CloseHaptic(SDL_Haptic *haptic) { - int i; + SDL_HapticEffectID i; SDL_Haptic *hapticlist; SDL_Haptic *hapticlistprev; @@ -522,9 +522,9 @@ 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); @@ -564,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."); @@ -573,7 +573,7 @@ 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); @@ -606,7 +606,7 @@ 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); @@ -628,7 +628,7 @@ 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); @@ -650,7 +650,7 @@ 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,); @@ -673,7 +673,7 @@ 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); diff --git a/src/haptic/SDL_syshaptic.h b/src/haptic/SDL_syshaptic.h index ec60a71e..21b3afd1 100644 --- a/src/haptic/SDL_syshaptic.h +++ b/src/haptic/SDL_syshaptic.h @@ -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/src/haptic/hidapi/SDL_hidapihaptic.c b/src/haptic/hidapi/SDL_hidapihaptic.c index 309145b8..7f59f6c9 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic.c +++ b/src/haptic/hidapi/SDL_hidapihaptic.c @@ -237,37 +237,37 @@ void SDL_HIDAPI_HapticQuit(void) } } -int SDL_HIDAPI_HapticNewEffect(SDL_Haptic *haptic, const SDL_HapticEffect *base) +SDL_HapticEffectID SDL_HIDAPI_HapticNewEffect(SDL_Haptic *haptic, const SDL_HapticEffect *base) { SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; return device->driver->CreateEffect(device, base); } -bool SDL_HIDAPI_HapticUpdateEffect(SDL_Haptic *haptic, int id, const SDL_HapticEffect *data) +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, int id, Uint32 iterations) +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, int id) +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, int 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, int 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); diff --git a/src/haptic/hidapi/SDL_hidapihaptic.h b/src/haptic/hidapi/SDL_hidapihaptic.h index cc82291e..4734bbc5 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic.h +++ b/src/haptic/hidapi/SDL_hidapihaptic.h @@ -33,12 +33,12 @@ bool SDL_HIDAPI_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystic bool SDL_HIDAPI_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick); void SDL_HIDAPI_HapticClose(SDL_Haptic *haptic); void SDL_HIDAPI_HapticQuit(void); -int SDL_HIDAPI_HapticNewEffect(SDL_Haptic *haptic, const SDL_HapticEffect *base); -bool SDL_HIDAPI_HapticUpdateEffect(SDL_Haptic *haptic, int id, const SDL_HapticEffect *data); -bool SDL_HIDAPI_HapticRunEffect(SDL_Haptic *haptic, int id, Uint32 iterations); -bool SDL_HIDAPI_HapticStopEffect(SDL_Haptic *haptic, int id); -void SDL_HIDAPI_HapticDestroyEffect(SDL_Haptic *haptic, int id); -bool SDL_HIDAPI_HapticGetEffectStatus(SDL_Haptic *haptic, int id); +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); diff --git a/src/haptic/hidapi/SDL_hidapihaptic_c.h b/src/haptic/hidapi/SDL_hidapihaptic_c.h index 83f1a89b..7d678811 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_c.h +++ b/src/haptic/hidapi/SDL_hidapihaptic_c.h @@ -41,7 +41,7 @@ typedef struct SDL_HIDAPI_HapticDevice struct SDL_HIDAPI_HapticDriver { - bool (*JoystickSupported)(SDL_Joystick *joystick); /* return SDL_TRUE if haptic can be opened from the joystick */ + 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 */ @@ -52,19 +52,19 @@ struct SDL_HIDAPI_HapticDriver 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 */ - int (*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, int id, const SDL_HapticEffect *data); /* returns 0 on success, negative number on error */ - bool (*RunEffect)(SDL_HIDAPI_HapticDevice *device, int id, Uint32 iterations); /* returns 0 on success, negative number on error */ - bool (*StopEffect)(SDL_HIDAPI_HapticDevice *device, int id); /* returns 0 on success, negative number on error */ - void (*DestroyEffect)(SDL_HIDAPI_HapticDevice *device, int id); /* returns 0 on success, negative number on error */ - bool (*GetEffectStatus)(SDL_HIDAPI_HapticDevice *device, int id); /* returns 0 if not playing, 1 if playing, negative number on error */ - bool (*SetGain)(SDL_HIDAPI_HapticDevice *device, int gain); /* gain 0 - 100, returns 0 on success, negative number on error */ - bool (*SetAutocenter)(SDL_HIDAPI_HapticDevice *device, int autocenter); /* gain 0 - 100, returns 0 on success, negative number on error */ - bool (*Pause)(SDL_HIDAPI_HapticDevice *device); /* returns 0 on success, negative number on error */ - bool (*Resume)(SDL_HIDAPI_HapticDevice *device); /* returns 0 on success, negative number on error */ - bool (*StopEffects)(SDL_HIDAPI_HapticDevice *device); /* returns 0 on success, negative number on error */ + 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_ \ No newline at end of file +#endif //SDL_joystick_c_h_ diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index 944bcc3a..ad9bf0d9 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -302,7 +302,7 @@ static Uint16 get_effect_replay_delay(SDL_HapticEffect *effect) Bernat Arlandis `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git */ -static int lg4ff_play_effect(struct lg4ff_device *device, int effect_id, int value) +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(); @@ -334,7 +334,7 @@ static int lg4ff_play_effect(struct lg4ff_device *device, int effect_id, int val 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, int id) +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(); @@ -995,11 +995,11 @@ static int SDL_HIDAPI_HapticDriverLg4ff_NumAxes(SDL_HIDAPI_HapticDevice *device) return 1; } -static int SDL_HIDAPI_HapticDriverLg4ff_CreateEffect(SDL_HIDAPI_HapticDevice *device, const SDL_HapticEffect *data) +static SDL_HapticEffectID SDL_HIDAPI_HapticDriverLg4ff_CreateEffect(SDL_HIDAPI_HapticDevice *device, const SDL_HapticEffect *data) { lg4ff_device *ctx = (lg4ff_device *)device->ctx; - int i; - int state_slot = -1; + SDL_HapticEffectID i; + SDL_HapticEffectID state_slot = -1; int ret; if (!SDL_HIDAPI_HapticDriverLg4ff_EffectSupported(device, data)) { SDL_SetError("Unsupported effect"); @@ -1031,7 +1031,7 @@ static int SDL_HIDAPI_HapticDriverLg4ff_CreateEffect(SDL_HIDAPI_HapticDevice *de } // assumes ctx->mutex locked -static bool lg4ff_effect_slot_valid_active(lg4ff_device *ctx, int id) +static bool lg4ff_effect_slot_valid_active(lg4ff_device *ctx, SDL_HapticEffectID id) { if (id >= LG4FF_MAX_EFFECTS || id < 0) { return false; @@ -1042,7 +1042,7 @@ static bool lg4ff_effect_slot_valid_active(lg4ff_device *ctx, int id) return true; } -static bool SDL_HIDAPI_HapticDriverLg4ff_UpdateEffect(SDL_HIDAPI_HapticDevice *device, int id, const SDL_HapticEffect *data) +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; @@ -1060,7 +1060,7 @@ static bool SDL_HIDAPI_HapticDriverLg4ff_UpdateEffect(SDL_HIDAPI_HapticDevice *d return ret == 0; } -static bool SDL_HIDAPI_HapticDriverLg4ff_RunEffect(SDL_HIDAPI_HapticDevice *device, int id, Uint32 iterations) +static bool SDL_HIDAPI_HapticDriverLg4ff_RunEffect(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id, Uint32 iterations) { lg4ff_device *ctx = (lg4ff_device *)device->ctx; int ret; @@ -1078,12 +1078,12 @@ static bool SDL_HIDAPI_HapticDriverLg4ff_RunEffect(SDL_HIDAPI_HapticDevice *devi return ret == 0; } -static bool SDL_HIDAPI_HapticDriverLg4ff_StopEffect(SDL_HIDAPI_HapticDevice *device, int id) +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, int id) +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; @@ -1101,7 +1101,7 @@ static void SDL_HIDAPI_HapticDriverLg4ff_DestroyEffect(SDL_HIDAPI_HapticDevice * SDL_UnlockMutex(ctx->mutex); } -static bool SDL_HIDAPI_HapticDriverLg4ff_GetEffectStatus(SDL_HIDAPI_HapticDevice *device, int id) +static bool SDL_HIDAPI_HapticDriverLg4ff_GetEffectStatus(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id) { lg4ff_device *ctx = (lg4ff_device *)device->ctx; bool ret = false; diff --git a/src/hidapi/SDL_hidapi.c b/src/hidapi/SDL_hidapi.c index b14b75eb..41e24ec8 100644 --- a/src/hidapi/SDL_hidapi.c +++ b/src/hidapi/SDL_hidapi.c @@ -807,6 +807,8 @@ typedef struct LIBUSB_hid_device_ LIBUSB_hid_device; #define hid_send_feature_report LIBUSB_hid_send_feature_report #define hid_set_nonblocking LIBUSB_hid_set_nonblocking #define hid_write LIBUSB_hid_write +#define hid_version LIBUSB_hid_version +#define hid_version_str LIBUSB_hid_version_str #define input_report LIBUSB_input_report #define make_path LIBUSB_make_path #define new_hid_device LIBUSB_new_hid_device @@ -1107,6 +1109,11 @@ 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 && product_id == USB_PRODUCT_FLYDIGI_GAMEPAD) { + 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 @@ -1171,9 +1178,9 @@ int SDL_hid_init(void) 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; \ +#define LOAD_LIBUSB_SYMBOL(type, func) \ + if ((libusb_ctx.func = (type)SDL_LoadFunction(libusb_ctx.libhandle, "libusb_" #func)) == NULL) { \ + loaded = false; \ } #else #define LOAD_LIBUSB_SYMBOL(type, func) \ diff --git a/src/hidapi/android/hid.cpp b/src/hidapi/android/hid.cpp index 887390ed..2dbfb8ed 100644 --- a/src/hidapi/android/hid.cpp +++ b/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); @@ -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/src/hidapi/libusb/hid.c b/src/hidapi/libusb/hid.c index f4b1ccb3..94cc50dc 100644 --- a/src/hidapi/libusb/hid.c +++ b/src/hidapi/libusb/hid.c @@ -71,6 +71,11 @@ extern "C" { #define DETACH_KERNEL_DRIVER #endif +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:5287) /* operands are different enum types */ +#endif + /* Uncomment to enable the retrieval of Usage and Usage Page in hid_enumerate(). Warning, on platforms different from FreeBSD this is very invasive as it requires the detach @@ -879,9 +884,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 && @@ -1238,6 +1247,7 @@ static void init_xbox360(libusb_device_handle *device_handle, unsigned short idV (void)conf_desc; if ((idVendor == 0x05ac && idProduct == 0x055b) /* Gamesir-G3w */ || + (idVendor == 0x20d6 && idProduct == 0x4010) /* PowerA Battle Dragon Advanced Wireless Controller */ || idVendor == 0x0f0d /* Hori Xbox controllers */) { unsigned char data[20]; @@ -2139,6 +2149,10 @@ uint16_t get_usb_code_for_current_locale(void) return 0x0; } +#if defined(_MSC_VER) +#pragma warning (pop) +#endif + #ifdef __cplusplus } #endif diff --git a/src/io/windows/SDL_asyncio_windows_ioring.c b/src/io/windows/SDL_asyncio_windows_ioring.c index 2d38efc3..497456cf 100644 --- a/src/io/windows/SDL_asyncio_windows_ioring.c +++ b/src/io/windows/SDL_asyncio_windows_ioring.c @@ -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)) \ diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c index f956e2fb..8a5ecc0a 100644 --- a/src/joystick/SDL_gamepad.c +++ b/src/joystick/SDL_gamepad.c @@ -62,6 +62,7 @@ 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; @@ -710,9 +711,10 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) 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,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); } else if (vendor == USB_VENDOR_NINTENDO && (guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCLeft || guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCRight || @@ -777,9 +779,29 @@ 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)) { + 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 (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 { // 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 @@ -796,20 +818,36 @@ 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_IsJoystick8BitDoController(vendor, product)) { - SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,paddle3:b14,paddle4:b13", sizeof(mapping_string)); + } else if (SDL_IsJoystickFlydigiController(vendor, product)) { + SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,paddle3:b13,paddle4:b14,", sizeof(mapping_string)); + switch (guid.data[15]) { + case 20: + case 21: + case 22: + case 23: + case 28: + case 80: + case 81: + case 85: + case 105: + // Vader series of controllers have C/Z buttons + SDL_strlcat(mapping_string, "misc2:b15,misc3:b16,", sizeof(mapping_string)); + break; + } + } 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: @@ -921,7 +959,7 @@ static GamepadMapping_t *SDL_PrivateMatchGamepadMappingForGUID(SDL_GUID guid, bo // An exact match, including CRC return mapping; } else if (crc && exact_match_crc) { - return NULL; + continue; } if (!best_match) { @@ -992,7 +1030,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); @@ -1107,7 +1146,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; @@ -1117,7 +1156,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: @@ -1139,7 +1188,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); } /* @@ -1166,6 +1215,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(); @@ -1176,12 +1226,17 @@ 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; @@ -1272,7 +1327,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; @@ -1347,6 +1402,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) { @@ -1368,6 +1425,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; } @@ -1394,6 +1453,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 @@ -1794,6 +1858,11 @@ static GamepadMapping_t *SDL_PrivateGenerateAutomaticGamepadMapping(const char * char name_string[128]; char mapping[1024]; + // Remove the CRC from the GUID + // We already know that this GUID doesn't have a mapping without the CRC, and we want newly + // added mappings without a CRC to override this mapping. + SDL_SetJoystickGUIDCRC(&guid, 0); + // Remove any commas in the name SDL_strlcpy(name_string, name, sizeof(name_string)); { @@ -1954,7 +2023,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; @@ -1967,23 +2066,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; @@ -1999,9 +2098,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; @@ -2015,6 +2116,28 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa 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; @@ -2046,18 +2169,31 @@ 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); if (negate) { @@ -2094,20 +2230,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; } @@ -2123,9 +2246,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; } @@ -2762,6 +2884,7 @@ SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) gamepad->joystick = SDL_OpenJoystick(instance_id); if (!gamepad->joystick) { + SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false); SDL_free(gamepad); SDL_UnlockJoysticks(); return NULL; @@ -2770,6 +2893,7 @@ SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) if (gamepad->joystick->naxes) { gamepad->last_match_axis = (SDL_GamepadBinding **)SDL_calloc(gamepad->joystick->naxes, sizeof(*gamepad->last_match_axis)); if (!gamepad->last_match_axis) { + SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false); SDL_CloseJoystick(gamepad->joystick); SDL_free(gamepad); SDL_UnlockJoysticks(); @@ -2779,6 +2903,7 @@ SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) if (gamepad->joystick->nhats) { gamepad->last_hat_mask = (Uint8 *)SDL_calloc(gamepad->joystick->nhats, sizeof(*gamepad->last_hat_mask)); if (!gamepad->last_hat_mask) { + SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false); SDL_CloseJoystick(gamepad->joystick); SDL_free(gamepad->last_match_axis); SDL_free(gamepad); @@ -2997,6 +3122,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: diff --git a/src/joystick/SDL_gamepad_db.h b/src/joystick/SDL_gamepad_db.h index 3ea4d133..94b164f0 100644 --- a/src/joystick/SDL_gamepad_db.h +++ b/src/joystick/SDL_gamepad_db.h @@ -197,7 +197,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 +206,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,", @@ -215,7 +215,7 @@ static const char *s_GamepadMappings[] = { "03000000362800000100000000000000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,x:b1,y:b2,", "03000000782300000a10000000000000,Onlive Wireless Controller,a:b15,b:b14,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b11,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b13,y:b12,", "030000006b14000001a1000000000000,Orange Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,", - "0300000009120000072f000000000000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:-a2,leftx:a0,lefty:a1,righttrigger:-a5,start:b11,x:b3,y:b4,", + "0300000009120000072f000000000000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:-a2,leftx:a0,lefty:a1,righttrigger:-a5,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", "03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,", "030000006f0e00000901000000000000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "03000000632500002306000000000000,PS Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", @@ -242,6 +242,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 +311,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,", @@ -413,15 +413,16 @@ 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,start:b11,x:b3,y:b4,", + "0300000009120000072f000000010000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a2,leftx:a0,lefty:a1,righttrigger:a5,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", "030000006f0e00000901000002010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", "030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", @@ -550,9 +551,10 @@ static const char *s_GamepadMappings[] = { "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,", @@ -622,7 +624,6 @@ static const char *s_GamepadMappings[] = { "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,", @@ -640,7 +641,8 @@ 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,", + "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,", @@ -652,7 +654,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,", @@ -699,6 +700,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,", + "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,", "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,", "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,", @@ -795,6 +797,7 @@ static const char *s_GamepadMappings[] = { "03000000c0160000e105000010010000,Xin-Mo Dual Arcade,crc:82d5,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,", /* Ultimate Atari Fight Stick */ "03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000120c0000101e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000120c0000182e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,", "03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", @@ -867,7 +870,9 @@ static const char *s_GamepadMappings[] = { "05000000ac05000001000000ff076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "05000000ac050000020000004f066d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,", "05000000ac05000004000000a8986d04,8BitDo Micro gamepad,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,lefttrigger:b12,rightshoulder:b13,righttrigger:b14,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000ac05000004000000fd216d04,8BitDo Pro 2,crc:ac95,a:b3,b:b2,back:b6,dpdown:b9,dpleft:b10,dpright:b11,dpup:b12,guide:b4,leftshoulder:b13,leftstick:b14,lefttrigger:+a2,leftx:a0,lefty:a1~,paddle1:b1,paddle2:b0,rightshoulder:b16,rightstick:b17,righttrigger:+a5,rightx:a3,righty:a4~,start:b5,x:b8,y:b7,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000ac050000040000003b8a6d04,8BitDo SN30 Pro+,crc:3e00,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,leftstick:b12,lefttrigger:b13,leftx:a0,lefty:a1~,rightshoulder:b14,rightstick:b15,righttrigger:b16,rightx:a2,righty:a3~,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000ac05000004000000209f6d04,8Bitdo SN30 Pro,crc:40d6,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,leftstick:b12,lefttrigger:b13,leftx:a0,lefty:a1~,rightshoulder:b14,rightstick:b15,righttrigger:b16,rightx:a2,righty:a3~,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000008a35000003010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "050000008a35000004010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "050000007e050000062000000f060000,Nintendo Switch Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index a1118bd9..0e9fcab3 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -456,6 +456,15 @@ static Uint32 initial_flightstick_devices[] = { MAKE_VIDPID(0x044f, 0x0402), // HOTAS Warthog Joystick MAKE_VIDPID(0x044f, 0xb10a), // ThrustMaster, Inc. T.16000M Joystick MAKE_VIDPID(0x046d, 0xc215), // Logitech Extreme 3D + MAKE_VIDPID(0x0583, 0x6258), // Padix USB joystick with viewfinder + MAKE_VIDPID(0x0583, 0x688f), // Padix QF-688uv Windstorm Pro + MAKE_VIDPID(0x0583, 0x7070), // Padix QF-707u Bazooka + MAKE_VIDPID(0x0583, 0xa019), // Padix USB vibration joystick with viewfinder + MAKE_VIDPID(0x0583, 0xa131), // Padix USB Wireless 2.4GHz + MAKE_VIDPID(0x0583, 0xa209), // Padix MetalStrike ForceFeedback + MAKE_VIDPID(0x0583, 0xb010), // Padix MetalStrike Pro + MAKE_VIDPID(0x0583, 0xb012), // Padix Wireless MetalStrike + MAKE_VIDPID(0x0583, 0xb013), // Padix USB Wireless 2.4GHZ MAKE_VIDPID(0x0738, 0x2221), // Saitek Pro Flight X-56 Rhino Stick MAKE_VIDPID(0x10f5, 0x7084), // Turtle Beach VelocityOne MAKE_VIDPID(0x231d, 0x0126), // Gunfighter Mk.III 'Space Combat Edition' (right) @@ -470,7 +479,13 @@ 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(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 = { @@ -543,6 +558,14 @@ static Uint32 initial_wheel_devices[] = { MAKE_VIDPID(0x046d, 0xca03), // Logitech Momo Racing MAKE_VIDPID(0x0483, 0x0522), // Simagic Wheelbase (including M10, Alpha Mini, Alpha, Alpha U) MAKE_VIDPID(0x0483, 0xa355), // VRS DirectForce Pro Wheel Base + MAKE_VIDPID(0x0583, 0xa132), // Padix USB Wireless 2.4GHz Wheelpad + MAKE_VIDPID(0x0583, 0xa133), // Padix USB Wireless 2.4GHz Wheel + MAKE_VIDPID(0x0583, 0xa202), // Padix Force Feedback Wheel + MAKE_VIDPID(0x0583, 0xb002), // Padix Vibration USB Wheel + MAKE_VIDPID(0x0583, 0xb005), // Padix USB Wheel + MAKE_VIDPID(0x0583, 0xb008), // Padix USB Wireless 2.4GHz Wheel + MAKE_VIDPID(0x0583, 0xb009), // Padix USB Wheel + MAKE_VIDPID(0x0583, 0xb018), // Padix TW6 Wheel MAKE_VIDPID(0x0eb7, 0x0001), // Fanatec ClubSport Wheel Base V2 MAKE_VIDPID(0x0eb7, 0x0004), // Fanatec ClubSport Wheel Base V2.5 MAKE_VIDPID(0x0eb7, 0x0005), // Fanatec CSL Elite Wheel Base+ (PS4) @@ -805,8 +828,6 @@ bool SDL_InitJoysticks(void) SDL_joysticks_initialized = true; - SDL_InitGamepadMappings(); - SDL_LoadVIDPIDList(&old_xboxone_controllers); SDL_LoadVIDPIDList(&arcadestick_devices); SDL_LoadVIDPIDList(&blacklist_devices); @@ -817,6 +838,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); @@ -1338,9 +1361,35 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id) // If this joystick is known to have all zero centered axes, skip the auto-centering code if (SDL_JoystickAxesCenteredAtZero(joystick)) { - int i; + for (int i = 0; i < joystick->naxes; ++i) { + joystick->axes[i].has_initial_value = true; + } + } - for (i = 0; i < joystick->naxes; ++i) { + // We know the initial values for HIDAPI and XInput joysticks + if ((SDL_IsJoystickHIDAPI(joystick->guid) || + SDL_IsJoystickXInput(joystick->guid) || + SDL_IsJoystickRAWINPUT(joystick->guid) || + SDL_IsJoystickWGI(joystick->guid)) && + joystick->naxes >= SDL_GAMEPAD_AXIS_COUNT) { + int left_trigger, right_trigger; + if (SDL_IsJoystickXInput(joystick->guid)) { + left_trigger = 2; + right_trigger = 5; + } else { + left_trigger = SDL_GAMEPAD_AXIS_LEFT_TRIGGER; + right_trigger = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; + } + for (int i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) { + int initial_value; + if (i == left_trigger || i == right_trigger) { + initial_value = SDL_MIN_SINT16; + } else { + initial_value = 0; + } + joystick->axes[i].value = initial_value; + joystick->axes[i].zero = initial_value; + joystick->axes[i].initial_value = initial_value; joystick->axes[i].has_initial_value = true; } } @@ -1986,6 +2035,14 @@ bool SDL_RumbleJoystickTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint result = true; } else { result = joystick->driver->RumbleTriggers(joystick, left_rumble, right_rumble); + if (result) { + joystick->trigger_rumble_resend = SDL_GetTicks() + SDL_RUMBLE_RESEND_MS; + if (joystick->trigger_rumble_resend == 0) { + joystick->trigger_rumble_resend = 1; + } + } else { + joystick->trigger_rumble_resend = 0; + } } if (result) { @@ -1996,6 +2053,7 @@ bool SDL_RumbleJoystickTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint joystick->trigger_rumble_expiration = SDL_GetTicks() + SDL_min(duration_ms, SDL_MAX_RUMBLE_DURATION_MS); } else { joystick->trigger_rumble_expiration = 0; + joystick->trigger_rumble_resend = 0; } } } @@ -2651,6 +2709,15 @@ void SDL_UpdateJoysticks(void) if (joystick->trigger_rumble_expiration && now >= joystick->trigger_rumble_expiration) { SDL_RumbleJoystickTriggers(joystick, 0, 0, 0); + joystick->trigger_rumble_resend = 0; + } + + if (joystick->trigger_rumble_resend && now >= joystick->trigger_rumble_resend) { + joystick->driver->RumbleTriggers(joystick, joystick->left_trigger_rumble, joystick->right_trigger_rumble); + joystick->trigger_rumble_resend = now + SDL_RUMBLE_RESEND_MS; + if (joystick->trigger_rumble_resend == 0) { + joystick->trigger_rumble_resend = 1; + } } } @@ -2902,8 +2969,7 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons 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)) { @@ -3135,9 +3201,9 @@ 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_IsJoystick8BitDoController(Uint16 vendor_id, Uint16 product_id) +bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id) { - return vendor_id == USB_VENDOR_8BITDO && (product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS); + return vendor_id == USB_VENDOR_FLYDIGI && product_id == USB_PRODUCT_FLYDIGI_GAMEPAD; } bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id) diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h index 61c7b32b..cbc33608 100644 --- a/src/joystick/SDL_joystick_c.h +++ b/src/joystick/SDL_joystick_c.h @@ -135,8 +135,8 @@ 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 a 8BitDo controller -extern bool SDL_IsJoystick8BitDoController(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); diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h index f8f2d1af..041ebc3b 100644 --- a/src/joystick/SDL_sysjoystick.h +++ b/src/joystick/SDL_sysjoystick.h @@ -113,6 +113,7 @@ struct SDL_Joystick Uint16 left_trigger_rumble _guarded; Uint16 right_trigger_rumble _guarded; Uint64 trigger_rumble_expiration _guarded; + Uint64 trigger_rumble_resend _guarded; Uint8 led_red _guarded; Uint8 led_green _guarded; diff --git a/src/joystick/apple/SDL_mfijoystick.m b/src/joystick/apple/SDL_mfijoystick.m index 811a9f1a..001ef314 100644 --- a/src/joystick/apple/SDL_mfijoystick.m +++ b/src/joystick/apple/SDL_mfijoystick.m @@ -346,7 +346,9 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle (device->is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 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_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, "")))) { // The HIDAPI driver is taking care of this device return false; } diff --git a/src/joystick/controller_list.h b/src/joystick/controller_list.h index cd476e30..63107389 100644 --- a/src/joystick/controller_list.h +++ b/src/joystick/controller_list.h @@ -96,6 +96,7 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x0c12, 0x0ef6 ), k_eControllerType_PS4Controller, NULL }, // Hitbox Arcade Stick { MAKE_CONTROLLER_ID( 0x0c12, 0x1cf6 ), k_eControllerType_PS4Controller, NULL }, // EMIO PS4 Elite Controller { MAKE_CONTROLLER_ID( 0x0c12, 0x1e10 ), k_eControllerType_PS4Controller, NULL }, // P4 Wired Gamepad generic knock off - lightbar but not trackpad or gyro + { MAKE_CONTROLLER_ID( 0x0c12, 0x2e18 ), k_eControllerType_PS4Controller, NULL }, // ZEROPLUS P4 Wired Gamepad { MAKE_CONTROLLER_ID( 0x0e6f, 0x0203 ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS (PS4 peripheral but no trackpad/lightbar) { MAKE_CONTROLLER_ID( 0x0e6f, 0x0207 ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS V2 w/ Touchpad for PS4 { MAKE_CONTROLLER_ID( 0x0e6f, 0x020a ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS PS4/PS5 (PS4 mode) diff --git a/src/joystick/darwin/SDL_iokitjoystick.c b/src/joystick/darwin/SDL_iokitjoystick.c index 9327276a..e87ab824 100644 --- a/src/joystick/darwin/SDL_iokitjoystick.c +++ b/src/joystick/darwin/SDL_iokitjoystick.c @@ -27,6 +27,7 @@ #include "SDL_iokitjoystick_c.h" #include "../hidapi/SDL_hidapijoystick_c.h" #include "../../haptic/darwin/SDL_syshaptic_c.h" // For haptic hot plugging +#include "../usb_ids.h" #define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick") @@ -213,6 +214,29 @@ static bool GetHIDScaledCalibratedState(recDevice *pDevice, recElement *pElement return result; } +static bool GetHIDScaledCalibratedState_NACON_Revolution_X_Unlimited(recDevice *pDevice, recElement *pElement, SInt32 min, SInt32 max, SInt32 *pValue) +{ + if (pElement->minReport == 0 && pElement->maxReport == 255) { + return GetHIDScaledCalibratedState(pDevice, pElement, min, max, pValue); + } + + // This device thumbstick axes have an unusual axis range that + // doesn't work with GetHIDScaledCalibratedState() above. + // + // See https://github.com/libsdl-org/SDL/issues/13143 for details + if (GetHIDElementState(pDevice, pElement, pValue)) { + if (*pValue >= 0) { + // Negative axis values range from 32767 (at rest) to 0 (minimum) + *pValue = -32767 + *pValue; + } else if (*pValue < 0) { + // Positive axis values range from -32768 (at rest) to 0 (maximum) + *pValue = 32768 + *pValue; + } + return true; + } + return false; +} + static void JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender) { recDevice *device = (recDevice *)ctx; @@ -506,6 +530,11 @@ static bool GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice) pDevice->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, (Uint16)vendor, (Uint16)product, (Uint16)version, manufacturer_string, product_string, 0, 0); pDevice->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot((Uint16)vendor, (Uint16)product, product_string); + if (vendor == USB_VENDOR_NACON_ALT && + product == USB_PRODUCT_NACON_REVOLUTION_X_UNLIMITED_BT) { + pDevice->nacon_revolution_x_unlimited = true; + } + array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone); if (array) { AddHIDElements(array, pDevice); @@ -957,7 +986,11 @@ static void DARWIN_JoystickUpdate(SDL_Joystick *joystick) i = 0; while (element) { - goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value); + if (device->nacon_revolution_x_unlimited) { + goodRead = GetHIDScaledCalibratedState_NACON_Revolution_X_Unlimited(device, element, -32768, 32767, &value); + } else { + goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value); + } if (goodRead) { SDL_SendJoystickAxis(timestamp, joystick, i, value); } diff --git a/src/joystick/darwin/SDL_iokitjoystick_c.h b/src/joystick/darwin/SDL_iokitjoystick_c.h index 91deb240..3a70d2bd 100644 --- a/src/joystick/darwin/SDL_iokitjoystick_c.h +++ b/src/joystick/darwin/SDL_iokitjoystick_c.h @@ -72,6 +72,7 @@ struct joystick_hwdata int instance_id; SDL_GUID guid; int steam_virtual_gamepad_slot; + bool nacon_revolution_x_unlimited; struct joystick_hwdata *pNext; // next device }; diff --git a/src/joystick/gdk/SDL_gameinputjoystick.cpp b/src/joystick/gdk/SDL_gameinputjoystick.cpp index 46d4ecc5..67cf51fa 100644 --- a/src/joystick/gdk/SDL_gameinputjoystick.cpp +++ b/src/joystick/gdk/SDL_gameinputjoystick.cpp @@ -209,8 +209,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) @@ -697,7 +697,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) GAMEINPUT_UpdatePowerInfo(joystick, device); } -static void GAMEINPUT_JoystickClose(SDL_Joystick* joystick) +static void GAMEINPUT_JoystickClose(SDL_Joystick *joystick) { GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; diff --git a/src/joystick/hidapi/SDL_hidapi_8bitdo.c b/src/joystick/hidapi/SDL_hidapi_8bitdo.c index ca601180..d1167586 100644 --- a/src/joystick/hidapi/SDL_hidapi_8bitdo.c +++ b/src/joystick/hidapi/SDL_hidapi_8bitdo.c @@ -42,8 +42,21 @@ enum 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 SENSOR_INTERVAL_NS 8000000ULL +#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 { @@ -56,13 +69,16 @@ typedef struct 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; // Microseconds. Simulate onboard clock. Advance by known rate: SENSOR_INTERVAL_NS == 8ms = 125 Hz + 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) @@ -112,9 +128,30 @@ 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) { - return SDL_IsJoystick8BitDoController(vendor_id, product_id); + 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_ULTIMATE2_WIRELESS: + return true; + default: + break; + } + } + return false; } static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device) @@ -128,13 +165,60 @@ static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device) 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; - Uint8 data[USB_PACKET_LENGTH]; - int size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 80); - if (size >= ULTIMATE2_WIRELESS_V103_REPORT_SIZE) { - ctx->sensors_supported = true; - ctx->rumble_supported = true; - ctx->powerstate_supported = true; + 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"); } return HIDAPI_JoystickConnected(device, NULL); @@ -149,6 +233,45 @@ static void HIDAPI_Driver8BitDo_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, { } +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. + 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: + default: + return 120; + break; + } +} + #ifndef DEG2RAD #define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) #endif @@ -162,17 +285,29 @@ static bool HIDAPI_Driver8BitDo_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys SDL_zeroa(ctx->last_state); // Initialize the joystick capabilities - joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS; + if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || + device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT || + 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) { - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 125.0f); - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 125.0f); + // 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; - ctx->gyroScale = DEG2RAD(2048) / INT16_MAX; // Hardware senses +/- 2048 Degrees per second mapped to +/- INT16_MAX + // Hardware senses +/- N Degrees per second mapped to +/- INT16_MAX + ctx->gyroScale = DEG2RAD(ABITDO_GYRO_MAX_DEGREES_PER_SECOND) / INT16_MAX; } return true; @@ -232,11 +367,95 @@ static bool HIDAPI_Driver8BitDo_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *dev } 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(); - if (data[0] != 0x03 && data[0] != 0x01) { + + 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; } @@ -297,7 +516,7 @@ static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[9] & 0x40) != 0)); } - if (ctx->last_state[10] != data[10]) { + 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)); } @@ -355,18 +574,35 @@ static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr 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 += SENSOR_INTERVAL_NS; + 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. @@ -414,7 +650,12 @@ static bool HIDAPI_Driver8BitDo_UpdateDevice(SDL_HIDAPI_Device *device) continue; } - HIDAPI_Driver8BitDo_HandleStatePacket(joystick, ctx, data, size); + 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) { diff --git a/src/joystick/hidapi/SDL_hidapi_flydigi.c b/src/joystick/hidapi/SDL_hidapi_flydigi.c new file mode 100644 index 00000000..1ee50d1e --- /dev/null +++ b/src/joystick/hidapi/SDL_hidapi_flydigi.c @@ -0,0 +1,518 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + 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_FLYDIGI + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_FLYDIGI_PROTOCOL +#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 tool 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 observed in testcontroller tool connection at 500hz */ +#define SENSOR_INTERVAL_VADER_PRO4_WIRED_RATE_HZ 500 +#define SENSOR_INTERVAL_VADER_PRO4_WIRED_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER_PRO4_WIRED_RATE_HZ) + +#define FLYDIGI_CMD_REPORT_ID 0x05 +#define FLYDIGI_HAPTIC_COMMAND 0x0F +#define FLYDIGI_GET_CONFIG_COMMAND 0xEB +#define FLYDIGI_GET_INFO_COMMAND 0xEC + +#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) + +typedef struct +{ + Uint8 deviceID; + bool has_cz; + 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; + 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) +{ + return SDL_IsJoystickFlydigiController(vendor_id, product_id) && interface_number == 2; +} + +static void UpdateDeviceIdentity(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_CMD_REPORT_ID, FLYDIGI_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 = data[9] | (data[10] << 8); + + 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; + } + } + } + + if (ctx->deviceID == 0) { + // Try to guess from the name of the controller + if (SDL_strstr(device->name, "VADER") != NULL) { + if (SDL_strstr(device->name, "VADER2") != NULL) { + ctx->deviceID = 20; + } else if (SDL_strstr(device->name, "VADER3") != NULL) { + ctx->deviceID = 28; + } else if (SDL_strstr(device->name, "VADER4") != NULL) { + ctx->deviceID = 85; + } + } else if (SDL_strstr(device->name, "APEX") != NULL) { + if (SDL_strstr(device->name, "APEX2") != NULL) { + ctx->deviceID = 19; + } else if (SDL_strstr(device->name, "APEX3") != NULL) { + ctx->deviceID = 24; + } else if (SDL_strstr(device->name, "APEX4") != NULL) { + ctx->deviceID = 84; + } + } + } + device->guid.data[15] = ctx->deviceID; + + // 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 (ctx->deviceID) { + case 19: + HIDAPI_SetDeviceName(device, "Flydigi Apex 2"); + break; + case 24: + case 26: + case 29: + HIDAPI_SetDeviceName(device, "Flydigi Apex 3"); + break; + case 84: + // The Apex 4 controller has sensors, but they're only reported when gyro mouse is enabled + HIDAPI_SetDeviceName(device, "Flydigi Apex 4"); + break; + case 20: + case 21: + case 23: + // 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 22: + HIDAPI_SetDeviceName(device, "Flydigi Vader 2 Pro"); + ctx->has_cz = true; + break; + case 28: + HIDAPI_SetDeviceName(device, "Flydigi Vader 3"); + ctx->has_cz = true; + break; + case 80: + case 81: + 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_VADER_PRO4_WIRED_NS; + break; + case 85: + case 105: + 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_VADER_PRO4_WIRED_NS; + break; + default: + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Unknown FlyDigi controller with ID %d, name '%s'", ctx->deviceID, device->name); + break; + } +} + +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; + } + device->context = ctx; + + UpdateDeviceIdentity(device); + + return HIDAPI_JoystickConnected(device, NULL); +} + +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) +{ +} + +#ifndef DEG2RAD +#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) +#endif + +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; + } + 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_VADER_PRO4_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) +{ + Uint8 rumble_packet[4] = { FLYDIGI_CMD_REPORT_ID, FLYDIGI_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"); + } + 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_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + if (data[0] != 0x04 && data[0] != 0xFE) { + // We don't know how to handle this report + return; + } + + Uint8 extra_button_index = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS; + + if (ctx->last_state[9] != data[9]) { + Uint8 hat; + + switch (data[9] & 0x0F) { + case 0b0001: + hat = SDL_HAT_UP; + break; + case 0b0011: + hat = SDL_HAT_RIGHTUP; + break; + case 0b0010: + hat = SDL_HAT_RIGHT; + break; + case 0b0110: + hat = SDL_HAT_RIGHTDOWN; + break; + case 0b0100: + hat = SDL_HAT_DOWN; + break; + case 0b1100: + hat = SDL_HAT_LEFTDOWN; + break; + case 0b1000: + hat = SDL_HAT_LEFT; + break; + case 0b1001: + 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); + + values[0] = -LOAD16(data[11], data[12]) * ctx->accelScale; // Acceleration along pitch axis + values[1] = LOAD16(data[15], data[16]) * ctx->accelScale; // Acceleration along yaw axis + values[2] = LOAD16(data[13], data[14]) * 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_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; + + 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_FLYDIGI_PROTOCOL + HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + HIDAPI_DriverFlydigi_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_DriverFlydigi_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +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/src/joystick/hidapi/SDL_hidapi_gamecube.c b/src/joystick/hidapi/SDL_hidapi_gamecube.c index 4d45c7a2..b95fb89c 100644 --- a/src/joystick/hidapi/SDL_hidapi_gamecube.c +++ b/src/joystick/hidapi/SDL_hidapi_gamecube.c @@ -31,7 +31,9 @@ #ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE // Define this if you want to log all packets from the controller -// #define DEBUG_GAMECUBE_PROTOCOL +#if 0 +#define DEBUG_GAMECUBE_PROTOCOL +#endif #define MAX_CONTROLLERS 4 @@ -74,7 +76,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; } @@ -119,22 +122,15 @@ static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) } device->context = ctx; - ctx->joysticks[0] = 0; - ctx->joysticks[1] = 0; - ctx->joysticks[2] = 0; - ctx->joysticks[3] = 0; ctx->rumble[0] = rumbleMagic; - ctx->useRumbleBrake = false; if (device->vendor_id != USB_VENDOR_NINTENDO) { ctx->pc_mode = true; } if (ctx->pc_mode) { - for (i = 0; i < MAX_CONTROLLERS; ++i) { - ResetAxisRange(ctx, i); - HIDAPI_JoystickConnected(device, &ctx->joysticks[i]); - } + ResetAxisRange(ctx, 0); + HIDAPI_JoystickConnected(device, &ctx->joysticks[0]); } else { // This is all that's needed to initialize the device. Really! if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) { @@ -204,69 +200,61 @@ static void HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device { } -static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, const Uint8 *packet, int size) +static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, const Uint8 *packet, bool invert_c_stick) { SDL_Joystick *joystick; - Uint8 i, v; + const Uint8 i = 0; // We have a separate context for each connected controller in PC mode, just use the first index + Uint8 v; Sint16 axis_value; Uint64 timestamp = SDL_GetTicksNS(); - if (size != 10) { - return; // How do we handle this packet? - } - - i = packet[0] - 1; - if (i >= MAX_CONTROLLERS) { - return; // How do we handle this packet? - } - joystick = SDL_GetJoystickFromID(ctx->joysticks[i]); if (!joystick) { // Hasn't been opened yet, skip return; } -#define READ_BUTTON(off, flag, button) \ - SDL_SendJoystickButton( \ - timestamp, \ - joystick, \ - button, \ +#define READ_BUTTON(off, flag, button) \ + SDL_SendJoystickButton( \ + timestamp, \ + joystick, \ + button, \ ((packet[off] & flag) != 0)); - READ_BUTTON(1, 0x02, 0) // A - READ_BUTTON(1, 0x04, 1) // B - READ_BUTTON(1, 0x08, 3) // Y - READ_BUTTON(1, 0x01, 2) // X - READ_BUTTON(2, 0x80, 4) // DPAD_LEFT - READ_BUTTON(2, 0x20, 5) // DPAD_RIGHT - READ_BUTTON(2, 0x40, 6) // DPAD_DOWN - READ_BUTTON(2, 0x10, 7) // DPAD_UP - READ_BUTTON(2, 0x02, 8) // START - READ_BUTTON(1, 0x80, 9) // RIGHTSHOULDER + READ_BUTTON(0, 0x02, 0) // A + READ_BUTTON(0, 0x04, 1) // B + READ_BUTTON(0, 0x08, 3) // Y + READ_BUTTON(0, 0x01, 2) // X + READ_BUTTON(1, 0x80, 4) // DPAD_LEFT + READ_BUTTON(1, 0x20, 5) // DPAD_RIGHT + READ_BUTTON(1, 0x40, 6) // DPAD_DOWN + READ_BUTTON(1, 0x10, 7) // DPAD_UP + READ_BUTTON(1, 0x02, 8) // START + READ_BUTTON(0, 0x80, 9) // RIGHTSHOULDER /* These two buttons are for the bottoms of the analog triggers. * More than likely, you're going to want to read the axes instead! * -flibit */ - READ_BUTTON(1, 0x20, 10) // TRIGGERRIGHT - READ_BUTTON(1, 0x10, 11) // TRIGGERLEFT + READ_BUTTON(0, 0x20, 10) // TRIGGERRIGHT + READ_BUTTON(0, 0x10, 11) // TRIGGERLEFT #undef READ_BUTTON -#define READ_AXIS(off, axis, invert) \ - v = invert ? (0xff - packet[off]) : packet[off]; \ - if (v < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ - ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ - if (v > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ - ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ +#define READ_AXIS(off, axis, invert) \ + v = (invert) ? (0xff - packet[off]) : packet[off]; \ + if (v < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ + ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ + if (v > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ + ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ axis_value = (Sint16)HIDAPI_RemapVal(v, ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \ - SDL_SendJoystickAxis( \ - timestamp, \ - joystick, \ + SDL_SendJoystickAxis( \ + timestamp, \ + joystick, \ axis, axis_value); - READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX, 0) - READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY, 1) - READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTX, 0) - READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTY, 1) - READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0) - READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0) + READ_AXIS(2, SDL_GAMEPAD_AXIS_LEFTX, 0) + READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTY, 1) + READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTX, invert_c_stick ? 1 : 0) + READ_AXIS(4, SDL_GAMEPAD_AXIS_RIGHTY, invert_c_stick ? 0 : 1) + READ_AXIS(6, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0) + READ_AXIS(7, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0) #undef READ_AXIS } @@ -365,7 +353,18 @@ static bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device) HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size); #endif if (ctx->pc_mode) { - HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, size); + if (size == 10) { + // This is the older firmware + // The first byte is the index of the connected controller + // The C stick has an inverted value range compared to the left stick + HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, &packet[1], true); + } else if (size == 9) { + // This is the newer firmware (version 0x7) + // The C stick has the same value range compared to the left stick + HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, false); + } else { + // How do we handle this packet? + } } else { HIDAPI_DriverGameCube_HandleNintendoPacket(device, ctx, packet, size); } diff --git a/src/joystick/hidapi/SDL_hidapi_gip.c b/src/joystick/hidapi/SDL_hidapi_gip.c new file mode 100644 index 00000000..a61906d8 --- /dev/null +++ b/src/joystick/hidapi/SDL_hidapi_gip.c @@ -0,0 +1,2966 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_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 Comand 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) +{ + if (metadata->device.audio_formats) { + SDL_free(metadata->device.audio_formats); + } + if (metadata->device.preferred_types) { + int i; + for (i = 0; i < metadata->device.num_preferred_types; i++) { + if (metadata->device.preferred_types[i]) { + SDL_free(metadata->device.preferred_types[i]); + } + } + SDL_free(metadata->device.preferred_types); + } + if (metadata->device.supported_interfaces) { + SDL_free(metadata->device.supported_interfaces); + } + if (metadata->device.hid_descriptor) { + SDL_free(metadata->device.hid_descriptor); + } + + if (metadata->message_metadata) { + SDL_free(metadata->message_metadata); + } + SDL_memset(metadata, 0, sizeof(*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", true); + } + 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", true); + } + 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; + + 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, true); + } + 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/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/src/joystick/hidapi/SDL_hidapi_lg4ff.c index 84378d79..90191198 100644 --- a/src/joystick/hidapi/SDL_hidapi_lg4ff.c +++ b/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -75,7 +75,7 @@ static int HIDAPI_DriverLg4ff_GetNumberOfButtons(Uint32 device_id) case USB_DEVICE_ID_LOGITECH_G29_WHEEL: return 25; case USB_DEVICE_ID_LOGITECH_G27_WHEEL: - return 22; + return 23; case USB_DEVICE_ID_LOGITECH_G25_WHEEL: return 19; case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: @@ -647,6 +647,19 @@ static bool HIDAPI_DriverLg4ff_HandleState(SDL_HIDAPI_Device *device, 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); diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c index 7404bf22..d4332df7 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps4.c +++ b/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -1003,8 +1003,8 @@ static bool HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device static void HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS4_Context *ctx, PS4StatePacket_t *packet, int size) { - static const float TOUCHPAD_SCALEX = 1.0f / 1920; - static const float TOUCHPAD_SCALEY = 1.0f / 920; // This is noted as being 944 resolution, but 920 feels better + static const float TOUCHPAD_SCALEX = 5.20833333e-4f; // 1.0f / 1920 + static const float TOUCHPAD_SCALEY = 1.08695652e-3f; // 1.0f / 920 // This is noted as being 944 resolution, but 920 feels better Sint16 axis; bool touchpad_down; int touchpad_x, touchpad_y; diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c index abf59a87..e4017008 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps5.c +++ b/src/joystick/hidapi/SDL_hidapi_ps5.c @@ -1355,8 +1355,8 @@ static void HIDAPI_DriverPS5_HandleStatePacketCommon(SDL_Joystick *joystick, SDL static void HIDAPI_DriverPS5_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacket_t *packet, Uint64 timestamp) { - static const float TOUCHPAD_SCALEX = 1.0f / 1920; - static const float TOUCHPAD_SCALEY = 1.0f / 1070; + static const float TOUCHPAD_SCALEX = 5.20833333e-4f; // 1.0f / 1920 + static const float TOUCHPAD_SCALEY = 9.34579439e-4f; // 1.0f / 1070 bool touchpad_down; int touchpad_x, touchpad_y; @@ -1406,8 +1406,8 @@ static void HIDAPI_DriverPS5_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_d static void HIDAPI_DriverPS5_HandleStatePacketAlt(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacketAlt_t *packet, Uint64 timestamp) { - static const float TOUCHPAD_SCALEX = 1.0f / 1920; - static const float TOUCHPAD_SCALEY = 1.0f / 1070; + static const float TOUCHPAD_SCALEX = 5.20833333e-4f; // 1.0f / 1920 + static const float TOUCHPAD_SCALEY = 9.34579439e-4f; // 1.0f / 1070 bool touchpad_down; int touchpad_x, touchpad_y; diff --git a/src/joystick/hidapi/SDL_hidapi_steamdeck.c b/src/joystick/hidapi/SDL_hidapi_steamdeck.c index 75a13cc1..213b2449 100644 --- a/src/joystick/hidapi/SDL_hidapi_steamdeck.c +++ b/src/joystick/hidapi/SDL_hidapi_steamdeck.c @@ -233,6 +233,18 @@ static void HIDAPI_DriverSteamDeck_HandleState(SDL_HIDAPI_Device *device, 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_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); } /*****************************************************************************************************/ @@ -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/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c index 0e7b823c..36865508 100644 --- a/src/joystick/hidapi/SDL_hidapi_switch.c +++ b/src/joystick/hidapi/SDL_hidapi_switch.c @@ -58,9 +58,7 @@ #define SWITCH_GYRO_SCALE 14.2842f #define SWITCH_ACCEL_SCALE 4096.f -#define SWITCH_GYRO_SCALE_OFFSET 13371.0f #define SWITCH_GYRO_SCALE_MULT 936.0f -#define SWITCH_ACCEL_SCALE_OFFSET 16384.0f #define SWITCH_ACCEL_SCALE_MULT 4.0f enum @@ -930,13 +928,14 @@ static bool SetIMUEnabled(SDL_DriverSwitch_Context *ctx, bool enabled) static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx) { - Uint8 *pLeftStickCal; - Uint8 *pRightStickCal; + Uint8 *pLeftStickCal = NULL; + Uint8 *pRightStickCal = NULL; size_t stick, axis; SwitchSubcommandInputPacket_t *user_reply = NULL; SwitchSubcommandInputPacket_t *factory_reply = NULL; SwitchSPIOpData_t readUserParams; SwitchSPIOpData_t readFactoryParams; + Uint8 userParamsReadSuccessCount = 0; // Read User Calibration Info readUserParams.unAddress = k_unSPIStickUserCalibrationStartOffset; @@ -949,33 +948,46 @@ static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx) readFactoryParams.unAddress = k_unSPIStickFactoryCalibrationStartOffset; readFactoryParams.ucLength = k_unSPIStickFactoryCalibrationLength; - const int MAX_ATTEMPTS = 3; - for (int attempt = 0; ; ++attempt) { - if (!WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readFactoryParams, sizeof(readFactoryParams), &factory_reply)) { - return false; - } - - if (factory_reply->stickFactoryCalibration.opData.unAddress == k_unSPIStickFactoryCalibrationStartOffset) { - // We successfully read the calibration data - break; - } - - if (attempt == MAX_ATTEMPTS) { - return false; - } - } - // Automatically select the user calibration if magic bytes are set if (user_reply && user_reply->stickUserCalibration.rgucLeftMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucLeftMagic[1] == 0xA1) { + userParamsReadSuccessCount += 1; pLeftStickCal = user_reply->stickUserCalibration.rgucLeftCalibration; - } else { - pLeftStickCal = factory_reply->stickFactoryCalibration.rgucLeftCalibration; } if (user_reply && user_reply->stickUserCalibration.rgucRightMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucRightMagic[1] == 0xA1) { + userParamsReadSuccessCount += 1; pRightStickCal = user_reply->stickUserCalibration.rgucRightCalibration; - } else { - pRightStickCal = factory_reply->stickFactoryCalibration.rgucRightCalibration; + } + + // Only read the factory calibration info if we failed to receive the correct magic bytes + if (userParamsReadSuccessCount < 2) { + // Read Factory Calibration Info + readFactoryParams.unAddress = k_unSPIStickFactoryCalibrationStartOffset; + readFactoryParams.ucLength = k_unSPIStickFactoryCalibrationLength; + + const int MAX_ATTEMPTS = 3; + for (int attempt = 0;; ++attempt) { + if (!WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readFactoryParams, sizeof(readFactoryParams), &factory_reply)) { + return false; + } + + if (factory_reply->stickFactoryCalibration.opData.unAddress == k_unSPIStickFactoryCalibrationStartOffset) { + // We successfully read the calibration data + pLeftStickCal = factory_reply->stickFactoryCalibration.rgucLeftCalibration; + pRightStickCal = factory_reply->stickFactoryCalibration.rgucRightCalibration; + break; + } + + if (attempt == MAX_ATTEMPTS) { + return false; + } + } + } + + // If we still don't have calibration data, return false + if (pLeftStickCal == NULL || pRightStickCal == NULL) + { + return false; } /* Stick calibration values are 12-bits each and are packed by bit @@ -1044,6 +1056,8 @@ static bool LoadIMUCalibration(SDL_DriverSwitch_Context *ctx) if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readParams, sizeof(readParams), &reply)) { Uint8 *pIMUScale; Sint16 sAccelRawX, sAccelRawY, sAccelRawZ, sGyroRawX, sGyroRawY, sGyroRawZ; + Sint16 sAccelSensCoeffX, sAccelSensCoeffY, sAccelSensCoeffZ; + Sint16 sGyroSensCoeffX, sGyroSensCoeffY, sGyroSensCoeffZ; // IMU scale gives us multipliers for converting raw values to real world values pIMUScale = reply->spiReadData.rgucReadData; @@ -1052,10 +1066,18 @@ static bool LoadIMUCalibration(SDL_DriverSwitch_Context *ctx) sAccelRawY = (pIMUScale[3] << 8) | pIMUScale[2]; sAccelRawZ = (pIMUScale[5] << 8) | pIMUScale[4]; + sAccelSensCoeffX = (pIMUScale[7] << 8) | pIMUScale[6]; + sAccelSensCoeffY = (pIMUScale[9] << 8) | pIMUScale[8]; + sAccelSensCoeffZ = (pIMUScale[11] << 8) | pIMUScale[10]; + sGyroRawX = (pIMUScale[13] << 8) | pIMUScale[12]; sGyroRawY = (pIMUScale[15] << 8) | pIMUScale[14]; sGyroRawZ = (pIMUScale[17] << 8) | pIMUScale[16]; + sGyroSensCoeffX = (pIMUScale[19] << 8) | pIMUScale[18]; + sGyroSensCoeffY = (pIMUScale[21] << 8) | pIMUScale[20]; + sGyroSensCoeffZ = (pIMUScale[23] << 8) | pIMUScale[22]; + // Check for user calibration data. If it's present and set, it'll override the factory settings readParams.unAddress = k_unSPIIMUUserScaleStartOffset; readParams.ucLength = k_unSPIIMUUserScaleLength; @@ -1072,14 +1094,14 @@ static bool LoadIMUCalibration(SDL_DriverSwitch_Context *ctx) } // Accelerometer scale - ctx->m_IMUScaleData.fAccelScaleX = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawX) * SDL_STANDARD_GRAVITY; - ctx->m_IMUScaleData.fAccelScaleY = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawY) * SDL_STANDARD_GRAVITY; - ctx->m_IMUScaleData.fAccelScaleZ = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawZ) * SDL_STANDARD_GRAVITY; + ctx->m_IMUScaleData.fAccelScaleX = SWITCH_ACCEL_SCALE_MULT / ((float)sAccelSensCoeffX - (float)sAccelRawX) * SDL_STANDARD_GRAVITY; + ctx->m_IMUScaleData.fAccelScaleY = SWITCH_ACCEL_SCALE_MULT / ((float)sAccelSensCoeffY - (float)sAccelRawY) * SDL_STANDARD_GRAVITY; + ctx->m_IMUScaleData.fAccelScaleZ = SWITCH_ACCEL_SCALE_MULT / ((float)sAccelSensCoeffZ - (float)sAccelRawZ) * SDL_STANDARD_GRAVITY; // Gyro scale - ctx->m_IMUScaleData.fGyroScaleX = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawX) * SDL_PI_F / 180.0f; - ctx->m_IMUScaleData.fGyroScaleY = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawY) * SDL_PI_F / 180.0f; - ctx->m_IMUScaleData.fGyroScaleZ = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawZ) * SDL_PI_F / 180.0f; + ctx->m_IMUScaleData.fGyroScaleX = SWITCH_GYRO_SCALE_MULT / ((float)sGyroSensCoeffX - (float)sGyroRawX) * SDL_PI_F / 180.0f; + ctx->m_IMUScaleData.fGyroScaleY = SWITCH_GYRO_SCALE_MULT / ((float)sGyroSensCoeffY - (float)sGyroRawY) * SDL_PI_F / 180.0f; + ctx->m_IMUScaleData.fGyroScaleZ = SWITCH_GYRO_SCALE_MULT / ((float)sGyroSensCoeffZ - (float)sGyroRawZ) * SDL_PI_F / 180.0f; } else { // Use default values @@ -1101,14 +1123,17 @@ static Sint16 ApplyStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, i { sRawValue -= ctx->m_StickCalData[nStick].axis[nAxis].sCenter; - if (sRawValue > ctx->m_StickExtents[nStick].axis[nAxis].sMax) { - ctx->m_StickExtents[nStick].axis[nAxis].sMax = sRawValue; + if (sRawValue >= 0) { + if (sRawValue > ctx->m_StickExtents[nStick].axis[nAxis].sMax) { + ctx->m_StickExtents[nStick].axis[nAxis].sMax = sRawValue; + } + return (Sint16)HIDAPI_RemapVal(sRawValue, 0, ctx->m_StickExtents[nStick].axis[nAxis].sMax, 0, SDL_MAX_SINT16); + } else { + if (sRawValue < ctx->m_StickExtents[nStick].axis[nAxis].sMin) { + ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue; + } + return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, 0, SDL_MIN_SINT16, 0); } - if (sRawValue < ctx->m_StickExtents[nStick].axis[nAxis].sMin) { - ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue; - } - - return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, ctx->m_StickExtents[nStick].axis[nAxis].sMax, SDL_MIN_SINT16, SDL_MAX_SINT16); } static Sint16 ApplySimpleStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue) @@ -1118,14 +1143,17 @@ static Sint16 ApplySimpleStickCalibration(SDL_DriverSwitch_Context *ctx, int nSt sRawValue -= usJoystickCenter; - if (sRawValue > ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax) { - ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax = sRawValue; + if (sRawValue >= 0) { + if (sRawValue > ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax) { + ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax = sRawValue; + } + return (Sint16)HIDAPI_RemapVal(sRawValue, 0, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax, 0, SDL_MAX_SINT16); + } else { + if (sRawValue < ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin) { + ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin = sRawValue; + } + return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin, 0, SDL_MIN_SINT16, 0); } - if (sRawValue < ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin) { - ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin = sRawValue; - } - - return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax, SDL_MIN_SINT16, SDL_MAX_SINT16); } static Uint8 RemapButton(SDL_DriverSwitch_Context *ctx, Uint8 button) @@ -1367,7 +1395,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]; diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c index 48f1a4b3..5d26deaf 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -82,6 +82,9 @@ 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 @@ -91,6 +94,9 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = { #ifdef SDL_JOYSTICK_HIDAPI_8BITDO &SDL_HIDAPI_Driver8BitDo, #endif +#ifdef SDL_JOYSTICK_HIDAPI_FLYDIGI + &SDL_HIDAPI_DriverFlydigi, +#endif }; static int SDL_HIDAPI_numdrivers = 0; static SDL_AtomicInt SDL_HIDAPI_updating_devices; @@ -294,9 +300,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; @@ -344,7 +354,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) { if (device->usage_page && device->usage_page != USAGE_PAGE_GENERIC_DESKTOP) { return NULL; } @@ -757,11 +767,12 @@ bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoysti ++SDL_HIDAPI_numjoysticks; - SDL_PrivateJoystickAdded(joystickID); - if (pJoystickID) { *pJoystickID = joystickID; } + + SDL_PrivateJoystickAdded(joystickID); + return true; } @@ -1057,6 +1068,11 @@ static bool HIDAPI_CreateCombinedJoyCons(void) info.usage = USB_USAGE_GENERIC_GAMEPAD; info.manufacturer_string = L"Nintendo"; info.product_string = L"Switch Joy-Con (L/R)"; + if (children[0]->is_bluetooth || children[1]->is_bluetooth) { + info.bus_type = SDL_HID_API_BUS_BLUETOOTH; + } else { + info.bus_type = SDL_HID_API_BUS_USB; + } combined = HIDAPI_AddDevice(&info, 2, children); if (combined && combined->driver) { diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h index bc027bce..f6b8ebfa 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -42,6 +42,8 @@ #define SDL_JOYSTICK_HIDAPI_STEAM_HORI #define SDL_JOYSTICK_HIDAPI_LG4FF #define SDL_JOYSTICK_HIDAPI_8BITDO +#define SDL_JOYSTICK_HIDAPI_FLYDIGI +#define SDL_JOYSTICK_HIDAPI_GIP // Joystick capability definitions #define SDL_JOYSTICK_CAP_MONO_LED 0x00000001 @@ -141,6 +143,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; @@ -161,6 +164,7 @@ extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLg4ff; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_Driver8BitDo; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverFlydigi; // 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/src/joystick/usb_ids.h b/src/joystick/usb_ids.h index 6b0c8fb4..33a8a6cb 100644 --- a/src/joystick/usb_ids.h +++ b/src/joystick/usb_ids.h @@ -32,6 +32,7 @@ #define USB_VENDOR_BACKBONE 0x358a #define USB_VENDOR_GAMESIR 0x3537 #define USB_VENDOR_DRAGONRISE 0x0079 +#define USB_VENDOR_FLYDIGI 0x04b4 #define USB_VENDOR_GOOGLE 0x18d1 #define USB_VENDOR_HORI 0x0f0d #define USB_VENDOR_HP 0x03f0 @@ -60,13 +61,24 @@ #define USB_VENDOR_ZEROPLUS 0x0c12 #define USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS 0x6012 +#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_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_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_EVORETRO_GAMECUBE_ADAPTER2 0x1844 +#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3 0x1846 +#define USB_PRODUCT_FLYDIGI_GAMEPAD 0x2412 #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 @@ -79,6 +91,7 @@ #define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRED 0x0d17 #define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS 0x0d18 #define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRED 0x0d19 +#define USB_PRODUCT_NACON_REVOLUTION_X_UNLIMITED_BT 0x0689 #define USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER 0x0337 #define USB_PRODUCT_NINTENDO_N64_CONTROLLER 0x2019 #define USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER 0x201e @@ -92,6 +105,8 @@ #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_POWERA_MINI 0x541a #define USB_PRODUCT_RAZER_ATROX 0x0a00 #define USB_PRODUCT_RAZER_KITSUNE 0x1012 #define USB_PRODUCT_RAZER_PANTHERA 0x0401 @@ -117,8 +132,10 @@ #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_T_FLIGHT_HOTAS_ONE 0xb68c #define USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE 0x1142 #define USB_PRODUCT_VICTRIX_FS_PRO 0x0203 #define USB_PRODUCT_VICTRIX_FS_PRO_V2 0x0207 @@ -146,6 +163,7 @@ // USB usage pages #define USB_USAGEPAGE_GENERIC_DESKTOP 0x0001 #define USB_USAGEPAGE_BUTTON 0x0009 +#define USB_USAGEPAGE_VENDOR_FLYDIGI 0xFFA0 // USB usages for USAGE_PAGE_GENERIC_DESKTOP #define USB_USAGE_GENERIC_POINTER 0x0001 diff --git a/src/joystick/windows/SDL_dinputjoystick.c b/src/joystick/windows/SDL_dinputjoystick.c index b00218d9..a9638823 100644 --- a/src/joystick/windows/SDL_dinputjoystick.c +++ b/src/joystick/windows/SDL_dinputjoystick.c @@ -293,7 +293,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; } diff --git a/src/joystick/windows/SDL_rawinputjoystick.c b/src/joystick/windows/SDL_rawinputjoystick.c index 3e2b270f..8590d9a8 100644 --- a/src/joystick/windows/SDL_rawinputjoystick.c +++ b/src/joystick/windows/SDL_rawinputjoystick.c @@ -158,6 +158,7 @@ struct joystick_hwdata Uint8 wgi_correlation_count; Uint8 wgi_uncorrelate_count; WindowsGamingInputGamepadState *wgi_slot; + struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; #endif bool triggers_rumbling; @@ -449,7 +450,6 @@ typedef struct WindowsGamingInputGamepadState bool used; // Is currently mapped to an SDL device bool connected; // Just used during update to track disconnected Uint8 correlation_id; - struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; } WindowsGamingInputGamepadState; static struct @@ -1030,7 +1030,7 @@ static bool RAWINPUT_JoystickInit(void) { SDL_assert(!SDL_RAWINPUT_inited); - if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT, true)) { + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT, false)) { return true; } @@ -1482,12 +1482,11 @@ static bool RAWINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency #ifdef SDL_JOYSTICK_RAWINPUT_WGI // Save off the motor state in case trigger rumble is started - WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; - HRESULT hr; - gamepad_state->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; - gamepad_state->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16; + ctx->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; + ctx->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16; if (!rumbled && ctx->wgi_correlated) { - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration); + WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; + HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, ctx->vibration); if (SUCCEEDED(hr)) { rumbled = true; } @@ -1509,12 +1508,11 @@ static bool RAWINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_ #ifdef SDL_JOYSTICK_RAWINPUT_WGI RAWINPUT_DeviceContext *ctx = joystick->hwdata; + ctx->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16; + ctx->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16; if (ctx->wgi_correlated) { WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; - HRESULT hr; - gamepad_state->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16; - gamepad_state->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16; - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration); + HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, ctx->vibration); if (!SUCCEEDED(hr)) { return SDL_SetError("Setting vibration failed: 0x%lx", hr); } diff --git a/src/locale/ngage/SDL_syslocale.cpp b/src/locale/ngage/SDL_syslocale.cpp new file mode 100644 index 00000000..4ab0eb73 --- /dev/null +++ b/src/locale/ngage/SDL_syslocale.cpp @@ -0,0 +1,307 @@ +/* + 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_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/src/main/SDL_main_callbacks.c b/src/main/SDL_main_callbacks.c index 9f3d9c13..913c9a71 100644 --- a/src/main/SDL_main_callbacks.c +++ b/src/main/SDL_main_callbacks.c @@ -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; diff --git a/src/main/SDL_runapp.c b/src/main/SDL_runapp.c index ddda4a63..eccdd3fc 100644 --- a/src/main/SDL_runapp.c +++ b/src/main/SDL_runapp.c @@ -24,7 +24,7 @@ * 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) -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; @@ -32,7 +32,7 @@ int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserv { // 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 }; + static char *argvdummy[2] = { dummyargv0, NULL }; argc = 1; argv = argvdummy; } diff --git a/src/main/emscripten/SDL_sysmain_callbacks.c b/src/main/emscripten/SDL_sysmain_callbacks.c index d3b2f95f..babffb3b 100644 --- a/src/main/emscripten/SDL_sysmain_callbacks.c +++ b/src/main/emscripten/SDL_sysmain_callbacks.c @@ -34,7 +34,7 @@ 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); if (rc == SDL_APP_CONTINUE) { diff --git a/src/main/emscripten/SDL_sysmain_runapp.c b/src/main/emscripten/SDL_sysmain_runapp.c index 20dd6ebe..8e2252ee 100644 --- a/src/main/emscripten/SDL_sysmain_runapp.c +++ b/src/main/emscripten/SDL_sysmain_runapp.c @@ -26,7 +26,7 @@ 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; diff --git a/src/main/gdk/SDL_sysmain_runapp.cpp b/src/main/gdk/SDL_sysmain_runapp.cpp index d7bdea0a..5a202259 100644 --- a/src/main/gdk/SDL_sysmain_runapp.cpp +++ b/src/main/gdk/SDL_sysmain_runapp.cpp @@ -40,7 +40,7 @@ static BOOL OutOfMemory(void) /* 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, char **, SDL_main_func mainFunction, void *reserved) { LPWSTR *argvw; char **argv; diff --git a/src/main/generic/SDL_sysmain_callbacks.c b/src/main/generic/SDL_sysmain_callbacks.c index 716489f1..afe5dbde 100644 --- a/src/main/generic/SDL_sysmain_callbacks.c +++ b/src/main/generic/SDL_sysmain_callbacks.c @@ -51,7 +51,7 @@ 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) { diff --git a/src/main/ios/SDL_sysmain_callbacks.m b/src/main/ios/SDL_sysmain_callbacks.m index becbda27..8ebaec67 100644 --- a/src/main/ios/SDL_sysmain_callbacks.m +++ b/src/main/ios/SDL_sysmain_callbacks.m @@ -64,7 +64,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/src/main/n3ds/SDL_sysmain_runapp.c b/src/main/n3ds/SDL_sysmain_runapp.c index 74ead997..06e4bf41 100644 --- a/src/main/n3ds/SDL_sysmain_runapp.c +++ b/src/main/n3ds/SDL_sysmain_runapp.c @@ -25,7 +25,7 @@ #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 diff --git a/src/main/ngage/SDL_sysmain_callbacks.c b/src/main/ngage/SDL_sysmain_callbacks.c new file mode 100644 index 00000000..7a6e7ecc --- /dev/null +++ b/src/main/ngage/SDL_sysmain_callbacks.c @@ -0,0 +1,31 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#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/src/main/ngage/SDL_sysmain_main.cpp b/src/main/ngage/SDL_sysmain_main.cpp new file mode 100644 index 00000000..c03634fb --- /dev/null +++ b/src/main/ngage/SDL_sysmain_main.cpp @@ -0,0 +1,199 @@ +/* + 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. +*/ +#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/src/main/ngage/SDL_sysmain_main.hpp b/src/main/ngage/SDL_sysmain_main.hpp new file mode 100644 index 00000000..da99b6f4 --- /dev/null +++ b/src/main/ngage/SDL_sysmain_main.hpp @@ -0,0 +1,46 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_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/src/main/ps2/SDL_sysmain_runapp.c b/src/main/ps2/SDL_sysmain_runapp.c index 23443c5a..658bce30 100644 --- a/src/main/ps2/SDL_sysmain_runapp.c +++ b/src/main/ps2/SDL_sysmain_runapp.c @@ -64,7 +64,7 @@ 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; (void)reserved; diff --git a/src/main/psp/SDL_sysmain_runapp.c b/src/main/psp/SDL_sysmain_runapp.c index e89f5ffe..7f09914b 100644 --- a/src/main/psp/SDL_sysmain_runapp.c +++ b/src/main/psp/SDL_sysmain_runapp.c @@ -69,7 +69,7 @@ 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(); diff --git a/src/main/windows/SDL_sysmain_runapp.c b/src/main/windows/SDL_sysmain_runapp.c index d8c3fac1..d2055397 100644 --- a/src/main/windows/SDL_sysmain_runapp.c +++ b/src/main/windows/SDL_sysmain_runapp.c @@ -36,7 +36,7 @@ static int OutOfMemory(void) return -1; } -int MINGW32_FORCEALIGN SDL_RunApp(int _argc, char* _argv[], SDL_main_func mainFunction, 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 */ diff --git a/src/process/SDL_process.c b/src/process/SDL_process.c index 3ccf5d58..5db6db49 100644 --- a/src/process/SDL_process.c +++ b/src/process/SDL_process.c @@ -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 defined(SDL_PLATFORM_WINDOWS) + const char *cmdline = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, NULL); + if ((!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 if (!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) { diff --git a/src/process/windows/SDL_windowsprocess.c b/src/process/windows/SDL_windowsprocess.c index 3e0249eb..65d8a394 100644 --- a/src/process/windows/SDL_windowsprocess.c +++ b/src/process/windows/SDL_windowsprocess.c @@ -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,6 +245,7 @@ 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); @@ -286,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; } diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 3cb5c8d2..60265b81 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -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 @@ -2627,11 +2630,12 @@ static void UpdateLogicalPresentation(SDL_Renderer *renderer) const float logical_h = view->logical_h; int iwidth, iheight; - if (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; @@ -2739,6 +2743,8 @@ static void UpdateLogicalPresentation(SDL_Renderer *renderer) view->pixel_h = (int) view->logical_dst_rect.h; UpdatePixelViewport(renderer, view); UpdatePixelClipRect(renderer, view); + QueueCmdSetViewport(renderer); + QueueCmdSetClipRect(renderer); } bool SDL_SetRenderLogicalPresentation(SDL_Renderer *renderer, int w, int h, SDL_RendererLogicalPresentation mode) @@ -3659,7 +3665,7 @@ bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count #endif SDL_RenderViewState *view = renderer->view; - const bool islogical = ((view == &renderer->main_view) && (view->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED)); + const bool islogical = (view->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED); if (islogical || (renderer->line_method == SDL_RENDERLINEMETHOD_GEOMETRY)) { const float scale_x = view->current_scale.x; @@ -3678,7 +3684,7 @@ bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count int num_indices = 0; const int size_indices = 4; int cur_index = -4; - const int is_looping = (points[0].x == points[count - 1].x && points[0].y == points[count - 1].y); + const bool is_looping = (points[0].x == points[count - 1].x && points[0].y == points[count - 1].y); SDL_FPoint p; // previous point p.x = p.y = 0.0f; /* p q @@ -3710,7 +3716,7 @@ bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count num_indices += 3; // closed polyline, don´t draw twice the point - if (i || is_looping == 0) { + if (i || !is_looping) { ADD_TRIANGLE(4, 5, 6) ADD_TRIANGLE(4, 6, 7) } @@ -3985,8 +3991,7 @@ bool SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_F real_srcrect.w = (float)texture->w; real_srcrect.h = (float)texture->h; if (srcrect) { - if (!SDL_GetRectIntersectionFloat(srcrect, &real_srcrect, &real_srcrect) || - real_srcrect.w == 0.0f || real_srcrect.h == 0.0f) { + if (!SDL_GetRectIntersectionFloat(srcrect, &real_srcrect, &real_srcrect)) { return true; } } diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h index 5109b934..d42f5362 100644 --- a/src/render/SDL_sysrender.h +++ b/src/render/SDL_sysrender.h @@ -357,6 +357,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; diff --git a/src/render/direct3d12/SDL_render_d3d12.c b/src/render/direct3d12/SDL_render_d3d12.c index 7b21e963..12492ed7 100644 --- a/src/render/direct3d12/SDL_render_d3d12.c +++ b/src/render/direct3d12/SDL_render_d3d12.c @@ -1512,7 +1512,7 @@ static void D3D12_FreeSRVIndex(SDL_Renderer *renderer, SIZE_T index) static bool GetTextureProperty(SDL_PropertiesID props, const char *name, ID3D12Resource **texture) { - IUnknown *unknown = (IUnknown*)SDL_GetPointerProperty(props, name, NULL); + IUnknown *unknown = (IUnknown *)SDL_GetPointerProperty(props, name, NULL); if (unknown) { #if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) HRESULT result = unknown->QueryInterface(D3D_GUID(SDL_IID_ID3D12Resource), (void **)texture); diff --git a/src/render/ngage/SDL_render_ngage.c b/src/render/ngage/SDL_render_ngage.c new file mode 100644 index 00000000..3956286d --- /dev/null +++ b/src/render/ngage/SDL_render_ngage.c @@ -0,0 +1,544 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_RENDER_NGAGE + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#ifndef Int2Fix +#define Int2Fix(i) ((i) << 16) +#endif + +#ifndef Fix2Int +#define Fix2Int(i) ((((unsigned int)(i) > 0xFFFF0000) ? 0 : ((i) >> 16))) +#endif + +#ifndef Fix2Real +#define Fix2Real(i) ((i) / 65536.0) +#endif + +#ifndef Real2Fix +#define Real2Fix(i) ((int)((i) * 65536.0)) +#endif + +#include "../SDL_sysrender.h" +#include "SDL_render_ngage_c.h" + +static void NGAGE_WindowEvent(SDL_Renderer *renderer, const SDL_WindowEvent *event); +static bool NGAGE_GetOutputSize(SDL_Renderer *renderer, int *w, int *h); +static bool NGAGE_SupportsBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode); +static bool NGAGE_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props); +static bool NGAGE_QueueSetViewport(SDL_Renderer *renderer, SDL_RenderCommand *cmd); +static bool NGAGE_QueueSetDrawColor(SDL_Renderer *renderer, SDL_RenderCommand *cmd); +static bool NGAGE_QueueDrawVertices(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count); +static bool NGAGE_QueueFillRects(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FRect *rects, int count); +static bool NGAGE_QueueCopy(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FRect *dstrect); +static bool NGAGE_QueueCopyEx(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, const SDL_FRect *srcquad, const SDL_FRect *dstrect, const double angle, const SDL_FPoint *center, const SDL_FlipMode flip, float scale_x, float scale_y); +static bool NGAGE_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, const float *xy, int xy_stride, const SDL_FColor *color, int color_stride, const float *uv, int uv_stride, int num_vertices, const void *indices, int num_indices, int size_indices, float scale_x, float scale_y); + +static void NGAGE_InvalidateCachedState(SDL_Renderer *renderer); +static bool NGAGE_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize); +static bool NGAGE_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch); + +static bool NGAGE_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, void **pixels, int *pitch); +static void NGAGE_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture); +static void NGAGE_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode); +static bool NGAGE_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture); +static SDL_Surface *NGAGE_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect); +static bool NGAGE_RenderPresent(SDL_Renderer *renderer); +static void NGAGE_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture); + +static void NGAGE_DestroyRenderer(SDL_Renderer *renderer); + +static bool NGAGE_SetVSync(SDL_Renderer *renderer, int vsync); + +static bool NGAGE_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_PropertiesID create_props) +{ + SDL_SetupRendererColorspace(renderer, create_props); + + if (renderer->output_colorspace != SDL_COLORSPACE_RGB_DEFAULT) { + return SDL_SetError("Unsupported output colorspace"); + } + + NGAGE_RendererData *phdata = SDL_calloc(1, sizeof(NGAGE_RendererData)); + if (!phdata) { + SDL_OutOfMemory(); + return false; + } + + renderer->WindowEvent = NGAGE_WindowEvent; + renderer->GetOutputSize = NGAGE_GetOutputSize; + renderer->SupportsBlendMode = NGAGE_SupportsBlendMode; + renderer->CreateTexture = NGAGE_CreateTexture; + renderer->QueueSetViewport = NGAGE_QueueSetViewport; + renderer->QueueSetDrawColor = NGAGE_QueueSetDrawColor; + renderer->QueueDrawPoints = NGAGE_QueueDrawVertices; + renderer->QueueDrawLines = NGAGE_QueueDrawVertices; + renderer->QueueFillRects = NGAGE_QueueFillRects; + renderer->QueueCopy = NGAGE_QueueCopy; + renderer->QueueCopyEx = NGAGE_QueueCopyEx; + renderer->QueueGeometry = NGAGE_QueueGeometry; + + renderer->InvalidateCachedState = NGAGE_InvalidateCachedState; + renderer->RunCommandQueue = NGAGE_RunCommandQueue; + renderer->UpdateTexture = NGAGE_UpdateTexture; + renderer->LockTexture = NGAGE_LockTexture; + renderer->UnlockTexture = NGAGE_UnlockTexture; + // renderer->SetTextureScaleMode = NGAGE_SetTextureScaleMode; + renderer->SetRenderTarget = NGAGE_SetRenderTarget; + renderer->RenderReadPixels = NGAGE_RenderReadPixels; + renderer->RenderPresent = NGAGE_RenderPresent; + renderer->DestroyTexture = NGAGE_DestroyTexture; + + renderer->DestroyRenderer = NGAGE_DestroyRenderer; + + renderer->SetVSync = NGAGE_SetVSync; + + renderer->name = NGAGE_RenderDriver.name; + renderer->window = window; + renderer->internal = phdata; + + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XRGB4444); + SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 1024); + SDL_SetHintWithPriority(SDL_HINT_RENDER_LINE_METHOD, "2", SDL_HINT_OVERRIDE); + + return true; +} + +SDL_RenderDriver NGAGE_RenderDriver = { + NGAGE_CreateRenderer, + "N-Gage" +}; + +static void NGAGE_WindowEvent(SDL_Renderer *renderer, const SDL_WindowEvent *event) +{ + return; +} + +static bool NGAGE_GetOutputSize(SDL_Renderer *renderer, int *w, int *h) +{ + return true; +} + +static bool NGAGE_SupportsBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode) +{ + switch (blendMode) { + case SDL_BLENDMODE_NONE: + case SDL_BLENDMODE_MOD: + return true; + default: + return false; + } +} + +static bool NGAGE_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) +{ + NGAGE_TextureData *data = (NGAGE_TextureData *)SDL_calloc(1, sizeof(*data)); + if (!data) { + return false; + } + + if (!NGAGE_CreateTextureData(data, texture->w, texture->h)) { + SDL_free(data); + return false; + } + + SDL_Surface *surface = SDL_CreateSurface(texture->w, texture->h, texture->format); + if (!surface) { + SDL_free(data); + return false; + } + + data->surface = surface; + texture->internal = data; + + return true; +} + +static bool NGAGE_QueueSetViewport(SDL_Renderer *renderer, SDL_RenderCommand *cmd) +{ + if (!cmd->data.viewport.rect.w && !cmd->data.viewport.rect.h) { + SDL_Rect viewport = { 0, 0, NGAGE_SCREEN_WIDTH, NGAGE_SCREEN_HEIGHT }; + SDL_SetRenderViewport(renderer, &viewport); + } + + return true; +} + +static bool NGAGE_QueueSetDrawColor(SDL_Renderer *renderer, SDL_RenderCommand *cmd) +{ + return true; +} + +static bool NGAGE_QueueDrawVertices(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count) +{ + NGAGE_Vertex *verts = (NGAGE_Vertex *)SDL_AllocateRenderVertices(renderer, count * sizeof(NGAGE_Vertex), 0, &cmd->data.draw.first); + if (!verts) { + return false; + } + + cmd->data.draw.count = count; + + for (int i = 0; i < count; i++, points++) { + int fixed_x = Real2Fix(points->x); + int fixed_y = Real2Fix(points->y); + + verts[i].x = Fix2Int(fixed_x); + verts[i].y = Fix2Int(fixed_y); + + Uint32 color = NGAGE_ConvertColor(cmd->data.draw.color.r, cmd->data.draw.color.g, cmd->data.draw.color.b, cmd->data.draw.color.a, cmd->data.draw.color_scale); + + verts[i].color.a = (Uint8)(color >> 24); + verts[i].color.b = (Uint8)(color >> 16); + verts[i].color.g = (Uint8)(color >> 8); + verts[i].color.r = (Uint8)color; + } + + return true; +} + +static bool NGAGE_QueueFillRects(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FRect *rects, int count) +{ + NGAGE_Vertex *verts = (NGAGE_Vertex *)SDL_AllocateRenderVertices(renderer, count * 2 * sizeof(NGAGE_Vertex), 0, &cmd->data.draw.first); + if (!verts) { + return false; + } + + cmd->data.draw.count = count; + + for (int i = 0; i < count; i++, rects++) { + verts[i * 2].x = Real2Fix(rects->x); + verts[i * 2].y = Real2Fix(rects->y); + verts[i * 2 + 1].x = Real2Fix(rects->w); + verts[i * 2 + 1].y = Real2Fix(rects->h); + + verts[i * 2].x = Fix2Int(verts[i * 2].x); + verts[i * 2].y = Fix2Int(verts[i * 2].y); + verts[i * 2 + 1].x = Fix2Int(verts[i * 2 + 1].x); + verts[i * 2 + 1].y = Fix2Int(verts[i * 2 + 1].y); + + Uint32 color = NGAGE_ConvertColor(cmd->data.draw.color.r, cmd->data.draw.color.g, cmd->data.draw.color.b, cmd->data.draw.color.a, cmd->data.draw.color_scale); + + verts[i * 2].color.a = (Uint8)(color >> 24); + verts[i * 2].color.b = (Uint8)(color >> 16); + verts[i * 2].color.g = (Uint8)(color >> 8); + verts[i * 2].color.r = (Uint8)color; + } + + return true; +} + +static bool NGAGE_QueueCopy(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FRect *dstrect) +{ + SDL_Rect *verts = (SDL_Rect *)SDL_AllocateRenderVertices(renderer, 2 * sizeof(SDL_Rect), 0, &cmd->data.draw.first); + + if (!verts) { + return false; + } + + cmd->data.draw.count = 1; + + verts->x = (int)srcrect->x; + verts->y = (int)srcrect->y; + verts->w = (int)srcrect->w; + verts->h = (int)srcrect->h; + + verts++; + + verts->x = (int)dstrect->x; + verts->y = (int)dstrect->y; + verts->w = (int)dstrect->w; + verts->h = (int)dstrect->h; + + return true; +} + +static bool NGAGE_QueueCopyEx(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, const SDL_FRect *srcquad, const SDL_FRect *dstrect, const double angle, const SDL_FPoint *center, const SDL_FlipMode flip, float scale_x, float scale_y) +{ + NGAGE_CopyExData *verts = (NGAGE_CopyExData *)SDL_AllocateRenderVertices(renderer, sizeof(NGAGE_CopyExData), 0, &cmd->data.draw.first); + + if (!verts) { + return false; + } + + cmd->data.draw.count = 1; + + verts->srcrect.x = (int)srcquad->x; + verts->srcrect.y = (int)srcquad->y; + verts->srcrect.w = (int)srcquad->w; + verts->srcrect.h = (int)srcquad->h; + verts->dstrect.x = (int)dstrect->x; + verts->dstrect.y = (int)dstrect->y; + verts->dstrect.w = (int)dstrect->w; + verts->dstrect.h = (int)dstrect->h; + + verts->angle = Real2Fix(angle); + verts->center.x = Real2Fix(center->x); + verts->center.y = Real2Fix(center->y); + verts->scale_x = Real2Fix(scale_x); + verts->scale_y = Real2Fix(scale_y); + + verts->flip = flip; + + return true; +} + +static bool NGAGE_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, const float *xy, int xy_stride, const SDL_FColor *color, int color_stride, const float *uv, int uv_stride, int num_vertices, const void *indices, int num_indices, int size_indices, float scale_x, float scale_y) +{ + return true; +} + +static void NGAGE_InvalidateCachedState(SDL_Renderer *renderer) +{ + return; +} + +static bool NGAGE_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) +{ + NGAGE_RendererData *phdata = (NGAGE_RendererData *)renderer->internal; + if (!phdata) { + return false; + } + phdata->viewport = 0; + + while (cmd) { + switch (cmd->command) { + case SDL_RENDERCMD_NO_OP: + break; + case SDL_RENDERCMD_SETVIEWPORT: + phdata->viewport = &cmd->data.viewport.rect; + break; + + case SDL_RENDERCMD_SETCLIPRECT: + { + const SDL_Rect *rect = &cmd->data.cliprect.rect; + + if (cmd->data.cliprect.enabled) { + NGAGE_SetClipRect(rect); + } + + break; + } + + case SDL_RENDERCMD_SETDRAWCOLOR: + { + break; + } + + case SDL_RENDERCMD_CLEAR: + { + Uint32 color = NGAGE_ConvertColor(cmd->data.color.color.r, cmd->data.color.color.g, cmd->data.color.color.b, cmd->data.color.color.a, cmd->data.color.color_scale); + + NGAGE_Clear(color); + break; + } + + case SDL_RENDERCMD_DRAW_POINTS: + { + NGAGE_Vertex *verts = (NGAGE_Vertex *)(((Uint8 *)vertices) + cmd->data.draw.first); + const int count = cmd->data.draw.count; + + // Apply viewport. + if (phdata->viewport && (phdata->viewport->x || phdata->viewport->y)) { + for (int i = 0; i < count; i++) { + verts[i].x += phdata->viewport->x; + verts[i].y += phdata->viewport->y; + } + } + + NGAGE_DrawPoints(verts, count); + break; + } + case SDL_RENDERCMD_DRAW_LINES: + { + NGAGE_Vertex *verts = (NGAGE_Vertex *)(((Uint8 *)vertices) + cmd->data.draw.first); + const int count = cmd->data.draw.count; + + // Apply viewport. + if (phdata->viewport && (phdata->viewport->x || phdata->viewport->y)) { + for (int i = 0; i < count; i++) { + verts[i].x += phdata->viewport->x; + verts[i].y += phdata->viewport->y; + } + } + + NGAGE_DrawLines(verts, count); + break; + } + + case SDL_RENDERCMD_FILL_RECTS: + { + NGAGE_Vertex *verts = (NGAGE_Vertex *)(((Uint8 *)vertices) + cmd->data.draw.first); + const int count = cmd->data.draw.count; + + // Apply viewport. + if (phdata->viewport && (phdata->viewport->x || phdata->viewport->y)) { + for (int i = 0; i < count; i++) { + verts[i].x += phdata->viewport->x; + verts[i].y += phdata->viewport->y; + } + } + + NGAGE_FillRects(verts, count); + break; + } + + case SDL_RENDERCMD_COPY: + { + SDL_Rect *verts = (SDL_Rect *)(((Uint8 *)vertices) + cmd->data.draw.first); + SDL_Rect *srcrect = verts; + SDL_Rect *dstrect = verts + 1; + SDL_Texture *texture = cmd->data.draw.texture; + + // Apply viewport. + if (phdata->viewport && (phdata->viewport->x || phdata->viewport->y)) { + dstrect->x += phdata->viewport->x; + dstrect->y += phdata->viewport->y; + } + + NGAGE_Copy(renderer, texture, srcrect, dstrect); + break; + } + + case SDL_RENDERCMD_COPY_EX: + { + NGAGE_CopyExData *copydata = (NGAGE_CopyExData *)(((Uint8 *)vertices) + cmd->data.draw.first); + SDL_Texture *texture = cmd->data.draw.texture; + + // Apply viewport. + if (phdata->viewport && (phdata->viewport->x || phdata->viewport->y)) { + copydata->dstrect.x += phdata->viewport->x; + copydata->dstrect.y += phdata->viewport->y; + } + + NGAGE_CopyEx(renderer, texture, copydata); + break; + } + + case SDL_RENDERCMD_GEOMETRY: + { + break; + } + } + cmd = cmd->next; + } + + return true; +} + +static bool NGAGE_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch) +{ + NGAGE_TextureData *phdata = (NGAGE_TextureData *)texture->internal; + + SDL_Surface *surface = phdata->surface; + Uint8 *src, *dst; + int row; + size_t length; + + if (SDL_MUSTLOCK(surface)) { + if (!SDL_LockSurface(surface)) { + return false; + } + } + src = (Uint8 *)pixels; + dst = (Uint8 *)surface->pixels + + rect->y * surface->pitch + + rect->x * surface->fmt->bytes_per_pixel; + + length = (size_t)rect->w * surface->fmt->bytes_per_pixel; + for (row = 0; row < rect->h; ++row) { + SDL_memcpy(dst, src, length); + src += pitch; + dst += surface->pitch; + } + if (SDL_MUSTLOCK(surface)) { + SDL_UnlockSurface(surface); + } + + return true; +} + +static bool NGAGE_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, void **pixels, int *pitch) +{ + NGAGE_TextureData *phdata = (NGAGE_TextureData *)texture->internal; + SDL_Surface *surface = phdata->surface; + + *pixels = + (void *)((Uint8 *)surface->pixels + rect->y * surface->pitch + + rect->x * surface->fmt->bytes_per_pixel); + *pitch = surface->pitch; + return true; +} + +static void NGAGE_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture) +{ +} + +static void NGAGE_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode) +{ +} + +static bool NGAGE_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) +{ + return true; +} + +static SDL_Surface *NGAGE_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect) +{ + return (SDL_Surface *)0; +} + +static bool NGAGE_RenderPresent(SDL_Renderer *renderer) +{ + NGAGE_Flip(); + + return true; +} + +static void NGAGE_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture) +{ + NGAGE_TextureData *data = (NGAGE_TextureData *)texture->internal; + if (data) { + SDL_DestroySurface(data->surface); + NGAGE_DestroyTextureData(data); + SDL_free(data); + texture->internal = 0; + } +} + +static void NGAGE_DestroyRenderer(SDL_Renderer *renderer) +{ + NGAGE_RendererData *phdata = (NGAGE_RendererData *)renderer->internal; + if (phdata) { + SDL_free(phdata); + renderer->internal = 0; + } +} + +static bool NGAGE_SetVSync(SDL_Renderer *renderer, int vsync) +{ + return true; +} + +#endif // SDL_VIDEO_RENDER_NGAGE diff --git a/src/render/ngage/SDL_render_ngage.cpp b/src/render/ngage/SDL_render_ngage.cpp new file mode 100644 index 00000000..cfc9bf8f --- /dev/null +++ b/src/render/ngage/SDL_render_ngage.cpp @@ -0,0 +1,750 @@ +/* + 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. +*/ +#ifdef __cplusplus +extern "C" { +#endif + +#include "../../events/SDL_keyboard_c.h" +#include "../SDL_sysrender.h" +#include "SDL_internal.h" +#include "SDL_render_ngage_c.h" + +#ifdef __cplusplus +} +#endif + +#ifdef SDL_VIDEO_RENDER_NGAGE + +#include "SDL_render_ngage_c.hpp" +#include "SDL_render_ops.hpp" + +const TUint32 WindowClientHandle = 0x571D0A; + +extern CRenderer *gRenderer; + +#ifdef __cplusplus +extern "C" { +#endif + +void NGAGE_Clear(const Uint32 color) +{ + gRenderer->Clear(color); +} + +bool NGAGE_Copy(SDL_Renderer *renderer, SDL_Texture *texture, SDL_Rect *srcrect, SDL_Rect *dstrect) +{ + return gRenderer->Copy(renderer, texture, srcrect, dstrect); +} + +bool NGAGE_CopyEx(SDL_Renderer *renderer, SDL_Texture *texture, NGAGE_CopyExData *copydata) +{ + return gRenderer->CopyEx(renderer, texture, copydata); +} + +bool NGAGE_CreateTextureData(NGAGE_TextureData *data, const int width, const int height) +{ + return gRenderer->CreateTextureData(data, width, height); +} + +void NGAGE_DestroyTextureData(NGAGE_TextureData *data) +{ + if (data) { + delete data->bitmap; + data->bitmap = NULL; + } +} + +void NGAGE_DrawLines(NGAGE_Vertex *verts, const int count) +{ + gRenderer->DrawLines(verts, count); +} + +void NGAGE_DrawPoints(NGAGE_Vertex *verts, const int count) +{ + gRenderer->DrawPoints(verts, count); +} + +void NGAGE_FillRects(NGAGE_Vertex *verts, const int count) +{ + gRenderer->FillRects(verts, count); +} + +void NGAGE_Flip() +{ + gRenderer->Flip(); +} + +void NGAGE_SetClipRect(const SDL_Rect *rect) +{ + gRenderer->SetClipRect(rect->x, rect->y, rect->w, rect->h); +} + +void NGAGE_SetDrawColor(const Uint32 color) +{ + if (gRenderer) { + gRenderer->SetDrawColor(color); + } +} + +void NGAGE_PumpEventsInternal() +{ + gRenderer->PumpEvents(); +} + +void NGAGE_SuspendScreenSaverInternal(bool suspend) +{ + gRenderer->SuspendScreenSaver(suspend); +} + +#ifdef __cplusplus +} +#endif + +CRenderer *CRenderer::NewL() +{ + CRenderer *self = new (ELeave) CRenderer(); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; +} + +CRenderer::CRenderer() : iRenderer(0), iDirectScreen(0), iScreenGc(0), iWsSession(), iWsWindowGroup(), iWsWindowGroupID(0), iWsWindow(), iWsScreen(0), iWsEventStatus(), iWsEvent(), iShowFPS(EFalse), iFPS(0), iFont(0) {} + +CRenderer::~CRenderer() +{ + delete iRenderer; + iRenderer = 0; +} + +void CRenderer::ConstructL() +{ + TInt error = KErrNone; + + error = iWsSession.Connect(); + if (error != KErrNone) { + SDL_Log("Failed to connect to window server: %d", error); + User::Leave(error); + } + + iWsScreen = new (ELeave) CWsScreenDevice(iWsSession); + error = iWsScreen->Construct(); + if (error != KErrNone) { + SDL_Log("Failed to construct screen device: %d", error); + User::Leave(error); + } + + iWsWindowGroup = RWindowGroup(iWsSession); + error = iWsWindowGroup.Construct(WindowClientHandle); + if (error != KErrNone) { + SDL_Log("Failed to construct window group: %d", error); + User::Leave(error); + } + iWsWindowGroup.SetOrdinalPosition(0); + + RProcess thisProcess; + TParse exeName; + exeName.Set(thisProcess.FileName(), NULL, NULL); + TBuf<32> winGroupName; + winGroupName.Append(0); + winGroupName.Append(0); + winGroupName.Append(0); // UID + winGroupName.Append(0); + winGroupName.Append(exeName.Name()); // Caption + winGroupName.Append(0); + winGroupName.Append(0); // DOC name + iWsWindowGroup.SetName(winGroupName); + + iWsWindow = RWindow(iWsSession); + error = iWsWindow.Construct(iWsWindowGroup, WindowClientHandle - 1); + if (error != KErrNone) { + SDL_Log("Failed to construct window: %d", error); + User::Leave(error); + } + iWsWindow.SetBackgroundColor(KRgbWhite); + iWsWindow.SetRequiredDisplayMode(EColor4K); + iWsWindow.Activate(); + iWsWindow.SetSize(iWsScreen->SizeInPixels()); + iWsWindow.SetVisible(ETrue); + + iWsWindowGroupID = iWsWindowGroup.Identifier(); + + TRAPD(errc, iRenderer = iRenderer->NewL()); + if (errc != KErrNone) { + SDL_Log("Failed to create renderer: %d", errc); + return; + } + + iDirectScreen = CDirectScreenAccess::NewL( + iWsSession, + *(iWsScreen), + iWsWindow, *this); + + // Select font. + TFontSpec fontSpec(_L("LatinBold12"), 12); + TInt errd = iWsScreen->GetNearestFontInTwips((CFont *&)iFont, fontSpec); + if (errd != KErrNone) { + SDL_Log("Failed to get font: %d", errd); + return; + } + + // Activate events. + iWsEventStatus = KRequestPending; + iWsSession.EventReady(&iWsEventStatus); + + DisableKeyBlocking(); + + iIsFocused = ETrue; + iShowFPS = EFalse; + iSuspendScreenSaver = EFalse; + + if (!iDirectScreen->IsActive()) { + TRAPD(err, iDirectScreen->StartL()); + if (KErrNone != err) { + return; + } + iDirectScreen->ScreenDevice()->SetAutoUpdate(ETrue); + } +} + +void CRenderer::Restart(RDirectScreenAccess::TTerminationReasons aReason) +{ + if (!iDirectScreen->IsActive()) { + TRAPD(err, iDirectScreen->StartL()); + if (KErrNone != err) { + return; + } + iDirectScreen->ScreenDevice()->SetAutoUpdate(ETrue); + } +} + +void CRenderer::AbortNow(RDirectScreenAccess::TTerminationReasons aReason) +{ + if (iDirectScreen->IsActive()) { + iDirectScreen->Cancel(); + } +} + +void CRenderer::Clear(TUint32 iColor) +{ + if (iRenderer && iRenderer->Gc()) { + iRenderer->Gc()->SetBrushColor(iColor); + iRenderer->Gc()->Clear(); + } +} + +#ifdef __cplusplus +extern "C" { +#endif + +Uint32 NGAGE_ConvertColor(float r, float g, float b, float a, float color_scale) +{ + TFixed ff = 255 << 16; // 255.f + + TFixed scalef = Real2Fix(color_scale); + TFixed rf = Real2Fix(r); + TFixed gf = Real2Fix(g); + TFixed bf = Real2Fix(b); + TFixed af = Real2Fix(a); + + rf = FixMul(rf, scalef); + gf = FixMul(gf, scalef); + bf = FixMul(bf, scalef); + + rf = SDL_clamp(rf, 0, ff); + gf = SDL_clamp(gf, 0, ff); + bf = SDL_clamp(bf, 0, ff); + af = SDL_clamp(af, 0, ff); + + rf = FixMul(rf, ff) >> 16; + gf = FixMul(gf, ff) >> 16; + bf = FixMul(bf, ff) >> 16; + af = FixMul(af, ff) >> 16; + + return (af << 24) | (bf << 16) | (gf << 8) | rf; +} + +#ifdef __cplusplus +} +#endif + +bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect, const SDL_Rect *dstrect) +{ + if (!texture) { + return false; + } + + NGAGE_TextureData *phdata = (NGAGE_TextureData *)texture->internal; + if (!phdata) { + return false; + } + + SDL_FColor *c = &texture->color; + int w = phdata->surface->w; + int h = phdata->surface->h; + int pitch = phdata->surface->pitch; + void *source = phdata->surface->pixels; + void *dest; + + if (!source) { + return false; + } + + void *pixel_buffer_a = SDL_calloc(1, pitch * h); + if (!pixel_buffer_a) { + return false; + } + dest = pixel_buffer_a; + + void *pixel_buffer_b = SDL_calloc(1, pitch * h); + if (!pixel_buffer_b) { + SDL_free(pixel_buffer_a); + return false; + } + + if (c->a != 1.f || c->r != 1.f || c->g != 1.f || c->b != 1.f) { + ApplyColorMod(dest, source, pitch, w, h, texture->color); + + source = dest; + } + + float sx; + float sy; + SDL_GetRenderScale(renderer, &sx, &sy); + + if (sx != 1.f || sy != 1.f) { + TFixed scale_x = Real2Fix(sx); + TFixed scale_y = Real2Fix(sy); + TFixed center_x = Int2Fix(w / 2); + TFixed center_y = Int2Fix(h / 2); + + dest == pixel_buffer_a ? dest = pixel_buffer_b : dest = pixel_buffer_a; + + ApplyScale(dest, source, pitch, w, h, center_x, center_y, scale_x, scale_y); + + source = dest; + } + + Mem::Copy(phdata->bitmap->DataAddress(), source, pitch * h); + SDL_free(pixel_buffer_a); + SDL_free(pixel_buffer_b); + + if (phdata->bitmap) { + TRect aSource(TPoint(srcrect->x, srcrect->y), TSize(srcrect->w, srcrect->h)); + TPoint aDest(dstrect->x, dstrect->y); + iRenderer->Gc()->BitBlt(aDest, phdata->bitmap, aSource); + } + + return true; +} + +bool CRenderer::CopyEx(SDL_Renderer *renderer, SDL_Texture *texture, const NGAGE_CopyExData *copydata) +{ + NGAGE_TextureData *phdata = (NGAGE_TextureData *)texture->internal; + if (!phdata) { + return false; + } + + SDL_FColor *c = &texture->color; + int w = phdata->surface->w; + int h = phdata->surface->h; + int pitch = phdata->surface->pitch; + void *source = phdata->surface->pixels; + void *dest; + + if (!source) { + return false; + } + + void *pixel_buffer_a = SDL_calloc(1, pitch * h); + if (!pixel_buffer_a) { + return false; + } + dest = pixel_buffer_a; + + void *pixel_buffer_b = SDL_calloc(1, pitch * h); + if (!pixel_buffer_a) { + SDL_free(pixel_buffer_a); + return false; + } + + if (copydata->flip) { + ApplyFlip(dest, source, pitch, w, h, copydata->flip); + source = dest; + } + + if (copydata->scale_x != 1.f || copydata->scale_y != 1.f) { + dest == pixel_buffer_a ? dest = pixel_buffer_b : dest = pixel_buffer_a; + ApplyScale(dest, source, pitch, w, h, copydata->center.x, copydata->center.y, copydata->scale_x, copydata->scale_y); + source = dest; + } + + if (copydata->angle) { + dest == pixel_buffer_a ? dest = pixel_buffer_b : dest = pixel_buffer_a; + ApplyRotation(dest, source, pitch, w, h, copydata->center.x, copydata->center.y, copydata->angle); + source = dest; + } + + if (c->a != 1.f || c->r != 1.f || c->g != 1.f || c->b != 1.f) { + dest == pixel_buffer_a ? dest = pixel_buffer_b : dest = pixel_buffer_a; + ApplyColorMod(dest, source, pitch, w, h, texture->color); + source = dest; + } + + Mem::Copy(phdata->bitmap->DataAddress(), source, pitch * h); + SDL_free(pixel_buffer_a); + SDL_free(pixel_buffer_b); + + if (phdata->bitmap) { + TRect aSource(TPoint(copydata->srcrect.x, copydata->srcrect.y), TSize(copydata->srcrect.w, copydata->srcrect.h)); + TPoint aDest(copydata->dstrect.x, copydata->dstrect.y); + iRenderer->Gc()->BitBlt(aDest, phdata->bitmap, aSource); + } + + return true; +} + +bool CRenderer::CreateTextureData(NGAGE_TextureData *aTextureData, const TInt aWidth, const TInt aHeight) +{ + if (!aTextureData) { + return false; + } + + aTextureData->bitmap = new CFbsBitmap(); + if (!aTextureData->bitmap) { + return false; + } + + TInt error = aTextureData->bitmap->Create(TSize(aWidth, aHeight), EColor4K); + if (error != KErrNone) { + delete aTextureData->bitmap; + aTextureData->bitmap = NULL; + return false; + } + + return true; +} + +void CRenderer::DrawLines(NGAGE_Vertex *aVerts, const TInt aCount) +{ + if (iRenderer && iRenderer->Gc()) { + TPoint *aPoints = new TPoint[aCount]; + + for (TInt i = 0; i < aCount; i++) { + aPoints[i] = TPoint(aVerts[i].x, aVerts[i].y); + } + + TUint32 aColor = (((TUint8)aVerts->color.a << 24) | + ((TUint8)aVerts->color.b << 16) | + ((TUint8)aVerts->color.g << 8) | + (TUint8)aVerts->color.r); + + iRenderer->Gc()->SetPenColor(aColor); + iRenderer->Gc()->DrawPolyLineNoEndPoint(aPoints, aCount); + + delete[] aPoints; + } +} + +void CRenderer::DrawPoints(NGAGE_Vertex *aVerts, const TInt aCount) +{ + if (iRenderer && iRenderer->Gc()) { + for (TInt i = 0; i < aCount; i++, aVerts++) { + TUint32 aColor = (((TUint8)aVerts->color.a << 24) | + ((TUint8)aVerts->color.b << 16) | + ((TUint8)aVerts->color.g << 8) | + (TUint8)aVerts->color.r); + + iRenderer->Gc()->SetPenColor(aColor); + iRenderer->Gc()->Plot(TPoint(aVerts->x, aVerts->y)); + } + } +} + +void CRenderer::FillRects(NGAGE_Vertex *aVerts, const TInt aCount) +{ + if (iRenderer && iRenderer->Gc()) { + for (TInt i = 0; i < aCount; i++, aVerts++) { + TPoint pos(aVerts[i].x, aVerts[i].y); + TSize size( + aVerts[i + 1].x, + aVerts[i + 1].y); + TRect rect(pos, size); + + TUint32 aColor = (((TUint8)aVerts->color.a << 24) | + ((TUint8)aVerts->color.b << 16) | + ((TUint8)aVerts->color.g << 8) | + (TUint8)aVerts->color.r); + + iRenderer->Gc()->SetPenColor(aColor); + iRenderer->Gc()->SetBrushColor(aColor); + iRenderer->Gc()->DrawRect(rect); + } + } +} + +void CRenderer::Flip() +{ + if (!iRenderer) { + SDL_Log("iRenderer is NULL."); + return; + } + + if (!iIsFocused) { + return; + } + +#ifdef SDL_VIDEO_RENDER_NGAGE_FPS + iRenderer->Gc()->UseFont(iFont); + + if (iShowFPS && iRenderer->Gc()) { + UpdateFPS(); + + TBuf<64> info; + + iRenderer->Gc()->SetPenStyle(CGraphicsContext::ESolidPen); + iRenderer->Gc()->SetBrushStyle(CGraphicsContext::ENullBrush); + iRenderer->Gc()->SetPenColor(KRgbCyan); + + TRect aTextRect(TPoint(3, 203 - iFont->HeightInPixels()), TSize(45, iFont->HeightInPixels() + 2)); + iRenderer->Gc()->SetBrushStyle(CGraphicsContext::ESolidBrush); + iRenderer->Gc()->SetBrushColor(KRgbBlack); + iRenderer->Gc()->DrawRect(aTextRect); + + // Draw messages. + info.Format(_L("FPS: %d"), iFPS); + iRenderer->Gc()->DrawText(info, TPoint(5, 203)); + } else { + // This is a workaround that helps regulating the FPS. + iRenderer->Gc()->DrawText(_L(""), TPoint(0, 0)); + } + iRenderer->Gc()->DiscardFont(); +#endif // SDL_VIDEO_RENDER_NGAGE_FPS + iRenderer->Flip(iDirectScreen); + + // Keep the backlight on. + if (iSuspendScreenSaver) { + User::ResetInactivityTime(); + } + // Suspend the current thread for a short while. + // Give some time to other threads and active objects. + User::After(0); +} + +void CRenderer::SetDrawColor(TUint32 iColor) +{ + if (iRenderer && iRenderer->Gc()) { + iRenderer->Gc()->SetPenColor(iColor); + iRenderer->Gc()->SetBrushColor(iColor); + iRenderer->Gc()->SetBrushStyle(CGraphicsContext::ESolidBrush); + + TRAPD(err, iRenderer->SetCurrentColor(iColor)); + if (err != KErrNone) { + return; + } + } +} + +void CRenderer::SetClipRect(TInt aX, TInt aY, TInt aWidth, TInt aHeight) +{ + if (iRenderer && iRenderer->Gc()) { + TRect viewportRect(aX, aY, aX + aWidth, aY + aHeight); + iRenderer->Gc()->SetClippingRect(viewportRect); + } +} + +#ifdef SDL_VIDEO_RENDER_NGAGE_FPS +void CRenderer::UpdateFPS() +{ + static TTime lastTime; + static TInt frameCount = 0; + TTime currentTime; + const TUint KOneSecond = 1000000; // 1s in ms. + + currentTime.HomeTime(); + ++frameCount; + + TTimeIntervalMicroSeconds timeDiff = currentTime.MicroSecondsFrom(lastTime); + + if (timeDiff.Int64() >= KOneSecond) { + // Calculate FPS. + iFPS = frameCount; + + // Reset frame count and last time. + frameCount = 0; + lastTime = currentTime; + } +} +#endif + +void CRenderer::SuspendScreenSaver(TBool aSuspend) +{ + iSuspendScreenSaver = aSuspend; +} + +static SDL_Scancode ConvertScancode(int key) +{ + SDL_Keycode keycode; + + switch (key) { + case EStdKeyBackspace: // Clear key + keycode = SDLK_BACKSPACE; + break; + case 0x31: // 1 + keycode = SDLK_1; + break; + case 0x32: // 2 + keycode = SDLK_2; + break; + case 0x33: // 3 + keycode = SDLK_3; + break; + case 0x34: // 4 + keycode = SDLK_4; + break; + case 0x35: // 5 + keycode = SDLK_5; + break; + case 0x36: // 6 + keycode = SDLK_6; + break; + case 0x37: // 7 + keycode = SDLK_7; + break; + case 0x38: // 8 + keycode = SDLK_8; + break; + case 0x39: // 9 + keycode = SDLK_9; + break; + case 0x30: // 0 + keycode = SDLK_0; + break; + case 0x2a: // Asterisk + keycode = SDLK_ASTERISK; + break; + case EStdKeyHash: // Hash + keycode = SDLK_HASH; + break; + case EStdKeyDevice0: // Left softkey + keycode = SDLK_SOFTLEFT; + break; + case EStdKeyDevice1: // Right softkey + keycode = SDLK_SOFTRIGHT; + break; + case EStdKeyApplication0: // Call softkey + keycode = SDLK_CALL; + break; + case EStdKeyApplication1: // End call softkey + keycode = SDLK_ENDCALL; + break; + case EStdKeyDevice3: // Middle softkey + keycode = SDLK_SELECT; + break; + case EStdKeyUpArrow: // Up arrow + keycode = SDLK_UP; + break; + case EStdKeyDownArrow: // Down arrow + keycode = SDLK_DOWN; + break; + case EStdKeyLeftArrow: // Left arrow + keycode = SDLK_LEFT; + break; + case EStdKeyRightArrow: // Right arrow + keycode = SDLK_RIGHT; + break; + default: + keycode = SDLK_UNKNOWN; + break; + } + + return SDL_GetScancodeFromKey(keycode, NULL); +} + +void CRenderer::HandleEvent(const TWsEvent &aWsEvent) +{ + Uint64 timestamp; + + switch (aWsEvent.Type()) { + case EEventKeyDown: /* Key events */ + timestamp = SDL_GetPerformanceCounter(); + SDL_SendKeyboardKey(timestamp, 1, aWsEvent.Key()->iCode, ConvertScancode(aWsEvent.Key()->iScanCode), true); + +#ifdef SDL_VIDEO_RENDER_NGAGE_FPS + if (aWsEvent.Key()->iScanCode == EStdKeyHash) { + if (iShowFPS) { + iShowFPS = EFalse; + } else { + iShowFPS = ETrue; + } + } +#endif + + break; + case EEventKeyUp: /* Key events */ + timestamp = SDL_GetPerformanceCounter(); + SDL_SendKeyboardKey(timestamp, 1, aWsEvent.Key()->iCode, ConvertScancode(aWsEvent.Key()->iScanCode), false); + + case EEventFocusGained: + DisableKeyBlocking(); + if (!iDirectScreen->IsActive()) { + TRAPD(err, iDirectScreen->StartL()); + if (KErrNone != err) { + return; + } + iDirectScreen->ScreenDevice()->SetAutoUpdate(ETrue); + iIsFocused = ETrue; + } + Flip(); + break; + case EEventFocusLost: + { + if (iDirectScreen->IsActive()) { + iDirectScreen->Cancel(); + } + + iIsFocused = EFalse; + break; + } + default: + break; + } +} + +void CRenderer::DisableKeyBlocking() +{ + TRawEvent aEvent; + + aEvent.Set((TRawEvent::TType) /*EDisableKeyBlock*/ 51); + iWsSession.SimulateRawEvent(aEvent); +} + +void CRenderer::PumpEvents() +{ + while (iWsEventStatus != KRequestPending) { + iWsSession.GetEvent(iWsEvent); + HandleEvent(iWsEvent); + iWsEventStatus = KRequestPending; + iWsSession.EventReady(&iWsEventStatus); + } +} + +#endif // SDL_VIDEO_RENDER_NGAGE diff --git a/src/render/ngage/SDL_render_ngage_c.h b/src/render/ngage/SDL_render_ngage_c.h new file mode 100644 index 00000000..2adab733 --- /dev/null +++ b/src/render/ngage/SDL_render_ngage_c.h @@ -0,0 +1,105 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef ngage_video_render_ngage_c_h +#define ngage_video_render_ngage_c_h + +#define NGAGE_SCREEN_WIDTH 176 +#define NGAGE_SCREEN_HEIGHT 208 + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../SDL_sysrender.h" + +typedef struct NGAGE_RendererData +{ + SDL_Rect *viewport; + +} NGAGE_RendererData; + +typedef struct NGAGE_Vertex +{ + int x; + int y; + + struct + { + Uint8 a; + Uint8 r; + Uint8 g; + Uint8 b; + + } color; + +} NGAGE_Vertex; + +typedef struct CFbsBitmap CFbsBitmap; + +typedef struct NGAGE_TextureData +{ + CFbsBitmap *bitmap; + SDL_Surface *surface; + +} NGAGE_TextureData; + +typedef struct NGAGE_CopyExData +{ + SDL_Rect srcrect; + SDL_Rect dstrect; + + int angle; + + struct + { + int x; + int y; + + } center; + + SDL_FlipMode flip; + + int scale_x; + int scale_y; + +} NGAGE_CopyExData; + +void NGAGE_Clear(const Uint32 color); +Uint32 NGAGE_ConvertColor(float r, float g, float b, float a, float color_scale); +bool NGAGE_Copy(SDL_Renderer *renderer, SDL_Texture *texture, SDL_Rect *srcrect, SDL_Rect *dstrect); +bool NGAGE_CopyEx(SDL_Renderer *renderer, SDL_Texture *texture, NGAGE_CopyExData *copydata); +bool NGAGE_CreateTextureData(NGAGE_TextureData *data, const int width, const int height); +void NGAGE_DestroyTextureData(NGAGE_TextureData *data); +void NGAGE_DrawLines(NGAGE_Vertex *verts, const int count); +void NGAGE_DrawPoints(NGAGE_Vertex *verts, const int count); +void NGAGE_FillRects(NGAGE_Vertex *verts, const int count); +void NGAGE_Flip(void); +void NGAGE_SetClipRect(const SDL_Rect *rect); +void NGAGE_SetDrawColor(const Uint32 color); +void NGAGE_PumpEventsInternal(void); +void NGAGE_SuspendScreenSaverInternal(bool suspend); + +#ifdef __cplusplus +} +#endif + +#endif // ngage_video_render_ngage_c_h diff --git a/src/render/ngage/SDL_render_ngage_c.hpp b/src/render/ngage/SDL_render_ngage_c.hpp new file mode 100644 index 00000000..958901f2 --- /dev/null +++ b/src/render/ngage/SDL_render_ngage_c.hpp @@ -0,0 +1,91 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef ngage_video_render_ngage_c_hpp +#define ngage_video_render_ngage_c_hpp + +#include "SDL_render_ngage_c.h" +#include +#include +#include + +class CRenderer : public MDirectScreenAccess +{ + public: + static CRenderer *NewL(); + virtual ~CRenderer(); + + // Rendering functions. + void Clear(TUint32 iColor); + bool Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect, const SDL_Rect *dstrect); + bool CopyEx(SDL_Renderer *renderer, SDL_Texture *texture, const NGAGE_CopyExData *copydata); + bool CreateTextureData(NGAGE_TextureData *aTextureData, const TInt aWidth, const TInt aHeight); + void DrawLines(NGAGE_Vertex *aVerts, const TInt aCount); + void DrawPoints(NGAGE_Vertex *aVerts, const TInt aCount); + void FillRects(NGAGE_Vertex *aVerts, const TInt aCount); + void Flip(); + void SetDrawColor(TUint32 iColor); + void SetClipRect(TInt aX, TInt aY, TInt aWidth, TInt aHeight); + void UpdateFPS(); + void SuspendScreenSaver(TBool aSuspend); + + // Event handling. + void DisableKeyBlocking(); + void HandleEvent(const TWsEvent &aWsEvent); + void PumpEvents(); + + private: + CRenderer(); + void ConstructL(void); + + // BackBuffer. + CNRenderer *iRenderer; + + // Direct screen access. + CDirectScreenAccess *iDirectScreen; + CFbsBitGc *iScreenGc; + TBool iIsFocused; + + // Window server session. + RWsSession iWsSession; + RWindowGroup iWsWindowGroup; + TInt iWsWindowGroupID; + RWindow iWsWindow; + CWsScreenDevice *iWsScreen; + + // Event handling. + TRequestStatus iWsEventStatus; + TWsEvent iWsEvent; + + // MDirectScreenAccess functions. + void Restart(RDirectScreenAccess::TTerminationReasons aReason); + void AbortNow(RDirectScreenAccess::TTerminationReasons aReason); + + // Frame per second. + TBool iShowFPS; + TUint iFPS; + const CFont *iFont; + + // Screen saver. + TBool iSuspendScreenSaver; +}; + +#endif // ngage_video_render_ngage_c_hpp diff --git a/src/render/ngage/SDL_render_ops.cpp b/src/render/ngage/SDL_render_ops.cpp new file mode 100644 index 00000000..60fdd33e --- /dev/null +++ b/src/render/ngage/SDL_render_ops.cpp @@ -0,0 +1,152 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include <3dtypes.h> +#include "SDL_render_ops.hpp" + +void ApplyColorMod(void *dest, void *source, int pitch, int width, int height, SDL_FColor color) +{ + TUint16 *src_pixels = static_cast(source); + TUint16 *dst_pixels = static_cast(dest); + + TFixed rf = Real2Fix(color.r); + TFixed gf = Real2Fix(color.g); + TFixed bf = Real2Fix(color.b); + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + TUint16 pixel = src_pixels[y * pitch / 2 + x]; + TUint8 r = (pixel & 0xF800) >> 8; + TUint8 g = (pixel & 0x07E0) >> 3; + TUint8 b = (pixel & 0x001F) << 3; + r = FixMul(r, rf); + g = FixMul(g, gf); + b = FixMul(b, bf); + dst_pixels[y * pitch / 2 + x] = (r << 8) | (g << 3) | (b >> 3); + } + } +} + +void ApplyFlip(void *dest, void *source, int pitch, int width, int height, SDL_FlipMode flip) +{ + TUint16 *src_pixels = static_cast(source); + TUint16 *dst_pixels = static_cast(dest); + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + int src_x = x; + int src_y = y; + + if (flip & SDL_FLIP_HORIZONTAL) + { + src_x = width - 1 - x; + } + + if (flip & SDL_FLIP_VERTICAL) + { + src_y = height - 1 - y; + } + + dst_pixels[y * pitch / 2 + x] = src_pixels[src_y * pitch / 2 + src_x]; + } + } +} + +void ApplyRotation(void *dest, void *source, int pitch, int width, int height, TFixed center_x, TFixed center_y, TFixed angle) +{ + TUint16 *src_pixels = static_cast(source); + TUint16 *dst_pixels = static_cast(dest); + + TFixed cos_angle = 0; + TFixed sin_angle = 0; + + if (angle != 0) + { + FixSinCos(angle, sin_angle, cos_angle); + } + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + // Translate point to origin. + TFixed translated_x = Int2Fix(x) - center_x; + TFixed translated_y = Int2Fix(y) - center_y; + + // Rotate point (clockwise). + TFixed rotated_x = FixMul(translated_x, cos_angle) + FixMul(translated_y, sin_angle); + TFixed rotated_y = FixMul(translated_y, cos_angle) - FixMul(translated_x, sin_angle); + + // Translate point back. + int final_x = Fix2Int(rotated_x + center_x); + int final_y = Fix2Int(rotated_y + center_y); + + // Check bounds. + if (final_x >= 0 && final_x < width && final_y >= 0 && final_y < height) + { + dst_pixels[y * pitch / 2 + x] = src_pixels[final_y * pitch / 2 + final_x]; + } + else + { + dst_pixels[y * pitch / 2 + x] = 0; + } + } + } +} + +void ApplyScale(void *dest, void *source, int pitch, int width, int height, TFixed center_x, TFixed center_y, TFixed scale_x, TFixed scale_y) +{ + TUint16 *src_pixels = static_cast(source); + TUint16 *dst_pixels = static_cast(dest); + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + // Translate point to origin. + TFixed translated_x = Int2Fix(x) - center_x; + TFixed translated_y = Int2Fix(y) - center_y; + + // Scale point. + TFixed scaled_x = FixDiv(translated_x, scale_x); + TFixed scaled_y = FixDiv(translated_y, scale_y); + + // Translate point back. + int final_x = Fix2Int(scaled_x + center_x); + int final_y = Fix2Int(scaled_y + center_y); + + // Check bounds. + if (final_x >= 0 && final_x < width && final_y >= 0 && final_y < height) + { + dst_pixels[y * pitch / 2 + x] = src_pixels[final_y * pitch / 2 + final_x]; + } + else + { + dst_pixels[y * pitch / 2 + x] = 0; + } + } + } +} diff --git a/src/render/ngage/SDL_render_ops.hpp b/src/render/ngage/SDL_render_ops.hpp new file mode 100644 index 00000000..fa92757b --- /dev/null +++ b/src/render/ngage/SDL_render_ops.hpp @@ -0,0 +1,32 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef ngage_video_render_ops_hpp +#define ngage_video_render_ops_hpp + +#include <3dtypes.h> + +void ApplyColorMod(void *dest, void *source, int pitch, int width, int height, SDL_FColor color); +void ApplyFlip(void *dest, void *source, int pitch, int width, int height, SDL_FlipMode flip); +void ApplyRotation(void *dest, void *source, int pitch, int width, int height, TFixed center_x, TFixed center_y, TFixed angle); +void ApplyScale(void *dest, void *source, int pitch, int width, int height, TFixed center_x, TFixed center_y, TFixed scale_x, TFixed scale_y); + +#endif // ngage_video_render_ops_hpp diff --git a/src/render/ps2/SDL_render_ps2.c b/src/render/ps2/SDL_render_ps2.c index ca0af6c5..f2b9ddae 100644 --- a/src/render/ps2/SDL_render_ps2.c +++ b/src/render/ps2/SDL_render_ps2.c @@ -60,7 +60,7 @@ typedef struct static int vsync_sema_id = 0; // PRIVATE METHODS -static int vsync_handler(void) +static int vsync_handler(int reason) { iSignalSema(vsync_sema_id); diff --git a/src/render/vitagxm/SDL_render_vita_gxm.c b/src/render/vitagxm/SDL_render_vita_gxm.c index 79dd1163..039155b7 100644 --- a/src/render/vitagxm/SDL_render_vita_gxm.c +++ b/src/render/vitagxm/SDL_render_vita_gxm.c @@ -820,6 +820,35 @@ static SceGxmTextureAddrMode TranslateAddressMode(SDL_TextureAddressMode mode) } } +static void ClampCliprectToViewport(SDL_Rect *clip, const SDL_Rect *viewport) +{ + int max_x_v, max_y_v, max_x_c, max_y_c; + + if (clip->x < 0) { + clip->w += clip->x; + clip->x = 0; + } + + if (clip->y < 0) { + clip->h += clip->y; + clip->y = 0; + } + + max_x_c = clip->x + clip->w; + max_y_c = clip->y + clip->h; + + max_x_v = viewport->x + viewport->w; + max_y_v = viewport->y + viewport->h; + + if (max_x_c > max_x_v) { + clip->w -= (max_x_v - max_x_c); + } + + if (max_y_c > max_y_v) { + clip->h -= (max_y_v - max_y_c); + } +} + static bool SetDrawState(VITA_GXM_RenderData *data, const SDL_RenderCommand *cmd) { SDL_Texture *texture = cmd->data.draw.texture; @@ -862,9 +891,13 @@ static bool SetDrawState(VITA_GXM_RenderData *data, const SDL_RenderCommand *cmd data->drawstate.cliprect_enabled_dirty = false; } - if (data->drawstate.cliprect_enabled && data->drawstate.cliprect_dirty) { - const SDL_Rect *rect = &data->drawstate.cliprect; - set_clip_rectangle(data, rect->x, rect->y, rect->x + rect->w, rect->y + rect->h); + if ((data->drawstate.cliprect_enabled || data->drawstate.viewport_is_set) && data->drawstate.cliprect_dirty) { + SDL_Rect rect; + SDL_copyp(&rect, &data->drawstate.cliprect); + if (data->drawstate.viewport_is_set) { + ClampCliprectToViewport(&rect, &data->drawstate.viewport); + } + set_clip_rectangle(data, rect.x, rect.y, rect.x + rect.w, rect.y + rect.h); data->drawstate.cliprect_dirty = false; } @@ -952,20 +985,31 @@ static void VITA_GXM_InvalidateCachedState(SDL_Renderer *renderer) static bool VITA_GXM_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) { VITA_GXM_RenderData *data = (VITA_GXM_RenderData *)renderer->internal; + int w, h; + StartDrawing(renderer); data->drawstate.target = renderer->target; if (!data->drawstate.target) { - int w, h; SDL_GetWindowSizeInPixels(renderer->window, &w, &h); - if ((w != data->drawstate.drawablew) || (h != data->drawstate.drawableh)) { - data->drawstate.viewport_dirty = true; // if the window dimensions changed, invalidate the current viewport, etc. - data->drawstate.cliprect_dirty = true; - data->drawstate.drawablew = w; - data->drawstate.drawableh = h; + } else { + float fw, fh; + if (!SDL_GetTextureSize(renderer->target, &fw, &fh)) { + w = data->drawstate.drawablew; + h = data->drawstate.drawableh; + } else { + w = (int)SDL_roundf(fw); + h = (int)SDL_roundf(fh); } } + if ((w != data->drawstate.drawablew) || (h != data->drawstate.drawableh)) { + data->drawstate.viewport_dirty = true; // if the window dimensions changed, invalidate the current viewport, etc. + data->drawstate.cliprect_dirty = true; + data->drawstate.drawablew = w; + data->drawstate.drawableh = h; + } + while (cmd) { switch (cmd->command) { @@ -976,6 +1020,16 @@ static bool VITA_GXM_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand * SDL_copyp(viewport, &cmd->data.viewport.rect); data->drawstate.viewport_dirty = true; data->drawstate.cliprect_dirty = true; + data->drawstate.viewport_is_set = viewport->x != 0 || viewport->y != 0 || viewport->w != data->drawstate.drawablew || viewport->h != data->drawstate.drawableh; + if (!data->drawstate.cliprect_enabled) { + if (data->drawstate.viewport_is_set) { + SDL_copyp(&data->drawstate.cliprect, viewport); + data->drawstate.cliprect.x = 0; + data->drawstate.cliprect.y = 0; + } else { + data->drawstate.cliprect_enabled_dirty = true; + } + } } break; } @@ -983,9 +1037,15 @@ static bool VITA_GXM_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand * case SDL_RENDERCMD_SETCLIPRECT: { const SDL_Rect *rect = &cmd->data.cliprect.rect; + const SDL_Rect *viewport = &data->drawstate.viewport; if (data->drawstate.cliprect_enabled != cmd->data.cliprect.enabled) { data->drawstate.cliprect_enabled = cmd->data.cliprect.enabled; data->drawstate.cliprect_enabled_dirty = true; + if (!data->drawstate.cliprect_enabled && data->drawstate.viewport_is_set) { + SDL_copyp(&data->drawstate.cliprect, viewport); + data->drawstate.cliprect.x = 0; + data->drawstate.cliprect.y = 0; + } } if (SDL_memcmp(&data->drawstate.cliprect, rect, sizeof(*rect)) != 0) { diff --git a/src/render/vitagxm/SDL_render_vita_gxm_types.h b/src/render/vitagxm/SDL_render_vita_gxm_types.h index f56814be..6fc2c9e8 100644 --- a/src/render/vitagxm/SDL_render_vita_gxm_types.h +++ b/src/render/vitagxm/SDL_render_vita_gxm_types.h @@ -105,6 +105,7 @@ typedef struct { SDL_Rect viewport; bool viewport_dirty; + bool viewport_is_set; SDL_Texture *texture; SDL_Texture *target; SDL_FColor color; diff --git a/src/render/vulkan/SDL_render_vulkan.c b/src/render/vulkan/SDL_render_vulkan.c index 82ee9f8a..0fe78973 100644 --- a/src/render/vulkan/SDL_render_vulkan.c +++ b/src/render/vulkan/SDL_render_vulkan.c @@ -1607,7 +1607,7 @@ static VkSemaphore VULKAN_CreateSemaphore(VULKAN_RenderData *rendererData) return semaphore; } -static bool VULKAN_DeviceExtensionsFound(VULKAN_RenderData *rendererData, int extensionsToCheck, const char* const* extNames) +static bool VULKAN_DeviceExtensionsFound(VULKAN_RenderData *rendererData, int extensionsToCheck, const char * const *extNames) { uint32_t extensionCount; bool foundExtensions = true; @@ -2364,7 +2364,7 @@ static VkResult VULKAN_CreateSwapChain(SDL_Renderer *renderer, int w, int h) } // Create descriptor pools - start by allocating one per swapchain image, let it grow if more are needed - rendererData->descriptorPools = (VkDescriptorPool **)SDL_calloc(rendererData->swapchainImageCount, sizeof(VkDescriptorPool*)); + rendererData->descriptorPools = (VkDescriptorPool **)SDL_calloc(rendererData->swapchainImageCount, sizeof(VkDescriptorPool *)); rendererData->numDescriptorPools = (uint32_t *)SDL_calloc(rendererData->swapchainImageCount, sizeof(uint32_t)); for (uint32_t i = 0; i < rendererData->swapchainImageCount; i++) { // Start by just allocating one pool, it will grow if needed @@ -2394,7 +2394,7 @@ static VkResult VULKAN_CreateSwapChain(SDL_Renderer *renderer, int w, int h) } // Upload buffers - rendererData->uploadBuffers = (VULKAN_Buffer **)SDL_calloc(rendererData->swapchainImageCount, sizeof(VULKAN_Buffer*)); + rendererData->uploadBuffers = (VULKAN_Buffer **)SDL_calloc(rendererData->swapchainImageCount, sizeof(VULKAN_Buffer *)); for (uint32_t i = 0; i < rendererData->swapchainImageCount; i++) { rendererData->uploadBuffers[i] = (VULKAN_Buffer *)SDL_calloc(SDL_VULKAN_NUM_UPLOAD_BUFFERS, sizeof(VULKAN_Buffer)); } @@ -2402,7 +2402,7 @@ static VkResult VULKAN_CreateSwapChain(SDL_Renderer *renderer, int w, int h) rendererData->currentUploadBuffer = (int *)SDL_calloc(rendererData->swapchainImageCount, sizeof(int)); // Constant buffers - rendererData->constantBuffers = (VULKAN_Buffer **)SDL_calloc(rendererData->swapchainImageCount, sizeof(VULKAN_Buffer*)); + rendererData->constantBuffers = (VULKAN_Buffer **)SDL_calloc(rendererData->swapchainImageCount, sizeof(VULKAN_Buffer *)); rendererData->numConstantBuffers = (uint32_t *)SDL_calloc(rendererData->swapchainImageCount, sizeof(uint32_t)); for (uint32_t i = 0; i < rendererData->swapchainImageCount; i++) { // Start with just allocating one, will grow if needed @@ -3939,7 +3939,7 @@ static bool VULKAN_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cm return true; } -static SDL_Surface* VULKAN_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect) +static SDL_Surface *VULKAN_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect) { VULKAN_RenderData *rendererData = (VULKAN_RenderData *)renderer->internal; VkImage backBuffer; diff --git a/src/stdlib/SDL_stdlib.c b/src/stdlib/SDL_stdlib.c index 093b2bec..64637201 100644 --- a/src/stdlib/SDL_stdlib.c +++ b/src/stdlib/SDL_stdlib.c @@ -535,8 +535,8 @@ void *SDL_aligned_alloc(size_t alignment, size_t size) Uint8 *result = NULL; size_t requested_size = size; - if (alignment < sizeof(void*)) { - alignment = sizeof(void*); + if (alignment < sizeof(void *)) { + alignment = sizeof(void *); } padding = (alignment - (size % alignment)); diff --git a/src/stdlib/SDL_string.c b/src/stdlib/SDL_string.c index 79679a1f..77b99b5f 100644 --- a/src/stdlib/SDL_string.c +++ b/src/stdlib/SDL_string.c @@ -34,6 +34,8 @@ #if defined(__SIZEOF_WCHAR_T__) #define SDL_SIZEOF_WCHAR_T __SIZEOF_WCHAR_T__ +#elif defined(SDL_PLATFORM_NGAGE) +#define SDL_SIZEOF_WCHAR_T 2 #elif defined(SDL_PLATFORM_WINDOWS) #define SDL_SIZEOF_WCHAR_T 2 #else // assume everything else is UTF-32 (add more tests if compiler-assert fails below!) diff --git a/src/stdlib/SDL_vacopy.h b/src/stdlib/SDL_vacopy.h index fee560e4..0c4efc1a 100644 --- a/src/stdlib/SDL_vacopy.h +++ b/src/stdlib/SDL_vacopy.h @@ -20,11 +20,12 @@ */ // Do our best to make sure va_copy is working -#if defined(_MSC_VER) && _MSC_VER <= 1800 +#if (defined(_MSC_VER) && _MSC_VER <= 1800) || defined(__SYMBIAN32__) // Visual Studio 2013 tries to link with _vacopy in the C runtime. Newer versions do an inline assignment #undef va_copy #define va_copy(dst, src) dst = src #elif defined(__GNUC__) && (__GNUC__ < 3) #define va_copy(dst, src) __va_copy(dst, src) + #endif diff --git a/src/storage/SDL_sysstorage.h b/src/storage/SDL_sysstorage.h index f047e551..e932d690 100644 --- a/src/storage/SDL_sysstorage.h +++ b/src/storage/SDL_sysstorage.h @@ -28,14 +28,14 @@ typedef struct TitleStorageBootStrap { const char *name; const char *desc; - SDL_Storage *(*create)(const char*, SDL_PropertiesID); + SDL_Storage *(*create)(const char *, SDL_PropertiesID); } TitleStorageBootStrap; typedef struct UserStorageBootStrap { const char *name; const char *desc; - SDL_Storage *(*create)(const char*, const char*, SDL_PropertiesID); + SDL_Storage *(*create)(const char *, const char *, SDL_PropertiesID); } UserStorageBootStrap; // Not all of these are available in a given build. Use #ifdefs, etc. diff --git a/src/storage/steam/SDL_steamstorage.c b/src/storage/steam/SDL_steamstorage.c index 8f735f84..4c92e426 100644 --- a/src/storage/steam/SDL_steamstorage.c +++ b/src/storage/steam/SDL_steamstorage.c @@ -41,7 +41,7 @@ typedef struct STEAM_RemoteStorage static bool STEAM_CloseStorage(void *userdata) { bool result = true; - STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage *)userdata; void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); if (steamremotestorage == NULL) { result = SDL_SetError("SteamRemoteStorage unavailable"); @@ -60,7 +60,7 @@ static bool STEAM_StorageReady(void *userdata) static bool STEAM_GetStoragePathInfo(void *userdata, const char *path, SDL_PathInfo *info) { - STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage *)userdata; void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); if (steamremotestorage == NULL) { return SDL_SetError("SteamRemoteStorage unavailable"); @@ -77,7 +77,7 @@ static bool STEAM_GetStoragePathInfo(void *userdata, const char *path, SDL_PathI static bool STEAM_ReadStorageFile(void *userdata, const char *path, void *destination, Uint64 length) { bool result = false; - STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage *)userdata; void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); if (steamremotestorage == NULL) { return SDL_SetError("SteamRemoteStorage unavailable"); @@ -96,7 +96,7 @@ static bool STEAM_ReadStorageFile(void *userdata, const char *path, void *destin static bool STEAM_WriteStorageFile(void *userdata, const char *path, const void *source, Uint64 length) { bool result = false; - STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage *)userdata; void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); if (steamremotestorage == NULL) { return SDL_SetError("SteamRemoteStorage unavailable"); @@ -115,7 +115,7 @@ static bool STEAM_WriteStorageFile(void *userdata, const char *path, const void static Uint64 STEAM_GetStorageSpaceRemaining(void *userdata) { Uint64 total, remaining; - STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage *)userdata; void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); if (steamremotestorage == NULL) { SDL_SetError("SteamRemoteStorage unavailable"); @@ -149,7 +149,7 @@ static SDL_Storage *STEAM_User_Create(const char *org, const char *app, SDL_Prop STEAM_RemoteStorage *steam; void *steamremotestorage; - steam = (STEAM_RemoteStorage*) SDL_malloc(sizeof(STEAM_RemoteStorage)); + steam = (STEAM_RemoteStorage *)SDL_malloc(sizeof(STEAM_RemoteStorage)); if (steam == NULL) { return NULL; } diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index 2622b02a..5f1ab6b9 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -2511,13 +2511,7 @@ SDL_AppResult SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const /* Ctrl-G toggle mouse grab */ SDL_Window *window = SDL_GetWindowFromEvent(event); if (window) { - if (SDL_RectEmpty(SDL_GetWindowMouseRect(window))) { - SDL_Rect r = { 10, 10, 200, 200}; - SDL_SetWindowMouseRect(window, &r); - } else { - SDL_SetWindowMouseRect(window, NULL); - } - //SDL_SetWindowMouseGrab(window, !SDL_GetWindowMouseGrab(window)); + SDL_SetWindowMouseGrab(window, !SDL_GetWindowMouseGrab(window)); } } break; diff --git a/src/test/SDL_test_memory.c b/src/test/SDL_test_memory.c index e1bec17f..0c7e0fbc 100644 --- a/src/test/SDL_test_memory.c +++ b/src/test/SDL_test_memory.c @@ -95,7 +95,7 @@ static unsigned int get_allocation_bucket(void *mem) return index; } -static SDL_tracked_allocation* SDL_GetTrackedAllocation(void *mem) +static SDL_tracked_allocation *SDL_GetTrackedAllocation(void *mem) { SDL_tracked_allocation *entry; LOCK_ALLOCATOR(); @@ -216,9 +216,9 @@ static void SDL_UntrackAllocation(void *mem) UNLOCK_ALLOCATOR(); } -static void rand_fill_memory(void* ptr, size_t start, size_t end) +static void rand_fill_memory(void *ptr, size_t start, size_t end) { - Uint8* mem = (Uint8*) ptr; + Uint8 *mem = (Uint8 *)ptr; size_t i; if (!s_randfill_allocations) diff --git a/src/time/ngage/SDL_systime.cpp b/src/time/ngage/SDL_systime.cpp new file mode 100644 index 00000000..e8c40f0f --- /dev/null +++ b/src/time/ngage/SDL_systime.cpp @@ -0,0 +1,184 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + 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_TIME_NGAGE + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static TTime UnixEpoch(); + +void SDL_GetSystemTimeLocalePreferences(SDL_DateFormat *df, SDL_TimeFormat *tf) +{ + TLanguage language = User::Language(); + + switch (language) { + case ELangFrench: + case ELangSwissFrench: + case ELangBelgianFrench: + case ELangInternationalFrench: + case ELangGerman: + case ELangSwissGerman: + case ELangAustrian: + case ELangSpanish: + case ELangInternationalSpanish: + case ELangLatinAmericanSpanish: + case ELangItalian: + case ELangSwissItalian: + case ELangSwedish: + case ELangFinlandSwedish: + case ELangDanish: + case ELangNorwegian: + case ELangNorwegianNynorsk: + case ELangFinnish: + case ELangPortuguese: + case ELangBrazilianPortuguese: + case ELangTurkish: + case ELangCyprusTurkish: + case ELangIcelandic: + case ELangRussian: + case ELangHungarian: + case ELangDutch: + case ELangBelgianFlemish: + case ELangCzech: + case ELangSlovak: + case ELangPolish: + case ELangSlovenian: + case ELangTaiwanChinese: + case ELangHongKongChinese: + case ELangPrcChinese: + case ELangJapanese: + case ELangThai: + case ELangAfrikaans: + case ELangAlbanian: + case ELangAmharic: + case ELangArabic: + case ELangArmenian: + case ELangAzerbaijani: + case ELangBelarussian: + case ELangBengali: + case ELangBulgarian: + case ELangBurmese: + case ELangCatalan: + case ELangCroatian: + case ELangEstonian: + case ELangFarsi: + case ELangScotsGaelic: + case ELangGeorgian: + case ELangGreek: + case ELangCyprusGreek: + case ELangGujarati: + case ELangHebrew: + case ELangHindi: + case ELangIndonesian: + case ELangIrish: + case ELangKannada: + case ELangKazakh: + case ELangKhmer: + case ELangKorean: + case ELangLao: + case ELangLatvian: + case ELangLithuanian: + case ELangMacedonian: + case ELangMalay: + case ELangMalayalam: + case ELangMarathi: + case ELangMoldavian: + case ELangMongolian: + case ELangPunjabi: + case ELangRomanian: + case ELangSerbian: + case ELangSinhalese: + case ELangSomali: + case ELangSwahili: + case ELangTajik: + case ELangTamil: + case ELangTelugu: + case ELangTibetan: + case ELangTigrinya: + case ELangTurkmen: + case ELangUkrainian: + case ELangUrdu: + case ELangUzbek: + case ELangVietnamese: + case ELangWelsh: + case ELangZulu: + *df = SDL_DATE_FORMAT_DDMMYYYY; + *tf = SDL_TIME_FORMAT_24HR; + break; + case ELangAmerican: + case ELangCanadianEnglish: + case ELangInternationalEnglish: + case ELangSouthAfricanEnglish: + case ELangAustralian: + case ELangNewZealand: + case ELangCanadianFrench: + *df = SDL_DATE_FORMAT_MMDDYYYY; + *tf = SDL_TIME_FORMAT_12HR; + break; + case ELangEnglish: + case ELangOther: + default: + *df = SDL_DATE_FORMAT_DDMMYYYY; + *tf = SDL_TIME_FORMAT_24HR; + break; + } +} + +bool SDL_GetCurrentTime(SDL_Time *ticks) +{ + if (!ticks) { + return SDL_InvalidParamError("ticks"); + } + + TTime now; + now.UniversalTime(); + + TTimeIntervalMicroSeconds interval = now.MicroSecondsFrom(UnixEpoch()); + TInt64 interval_ns = interval.Int64() * 1000; + Uint32 ns_low = interval_ns.Low(); + Uint32 ns_high = interval_ns.High(); + + *ticks = ((Uint64)ns_high << 32) | ns_low; + + return true; +} + +static TTime UnixEpoch() +{ + _LIT(KUnixEpoch, "19700101:000000.000000"); + TTime epochTime; + epochTime.Set(KUnixEpoch); + return epochTime; +} + +#ifdef __cplusplus +} +#endif + +#endif // SDL_TIME_NGAGE diff --git a/src/timer/ngage/SDL_systimer.cpp b/src/timer/ngage/SDL_systimer.cpp new file mode 100644 index 00000000..f3039bd0 --- /dev/null +++ b/src/timer/ngage/SDL_systimer.cpp @@ -0,0 +1,48 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +Uint64 SDL_GetPerformanceCounter(void) +{ + return static_cast(User::TickCount()); +} + +Uint64 SDL_GetPerformanceFrequency(void) +{ + // On Symbian S60v1, tick frequency is 64 Hz => 1 tick = 15,625 microseconds. + return 64; +} + +void SDL_SYS_DelayNS(Uint64 ns) +{ + User::After(SDL_NS_TO_US(ns)); +} + +#ifdef __cplusplus +} +#endif diff --git a/src/tray/unix/SDL_tray.c b/src/tray/unix/SDL_tray.c index dc2d0ca6..4e010ec8 100644 --- a/src/tray/unix/SDL_tray.c +++ b/src/tray/unix/SDL_tray.c @@ -40,7 +40,7 @@ /* Glib 2.0 */ typedef unsigned long gulong; -typedef void* gpointer; +typedef void *gpointer; typedef char gchar; typedef int gint; typedef unsigned int guint; @@ -58,8 +58,8 @@ typedef enum static gulong (*g_signal_connect_data)(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags); static void (*g_object_unref)(gpointer object); static gchar *(*g_mkdtemp)(gchar *template); -gpointer (*g_object_ref_sink)(gpointer object); -gpointer (*g_object_ref)(gpointer object); +static gpointer (*g_object_ref_sink)(gpointer object); +static gpointer (*g_object_ref)(gpointer object); // glib_typeof requires compiler-specific code and includes that are too complex // to be worth copy-pasting here @@ -88,11 +88,11 @@ typedef struct _GtkCheckMenuItem GtkCheckMenuItem; static gboolean (*gtk_init_check)(int *argc, char ***argv); static gboolean (*gtk_main_iteration_do)(gboolean blocking); -static GtkWidget* (*gtk_menu_new)(void); -static GtkWidget* (*gtk_separator_menu_item_new)(void); -static GtkWidget* (*gtk_menu_item_new_with_label)(const gchar *label); +static GtkWidget *(*gtk_menu_new)(void); +static GtkWidget *(*gtk_separator_menu_item_new)(void); +static GtkWidget *(*gtk_menu_item_new_with_label)(const gchar *label); static void (*gtk_menu_item_set_submenu)(GtkMenuItem *menu_item, GtkWidget *submenu); -static GtkWidget* (*gtk_check_menu_item_new_with_label)(const gchar *label); +static GtkWidget *(*gtk_check_menu_item_new_with_label)(const gchar *label); static void (*gtk_check_menu_item_set_active)(GtkCheckMenuItem *check_menu_item, gboolean is_active); static void (*gtk_widget_set_sensitive)(GtkWidget *widget, gboolean sensitive); static void (*gtk_widget_show)(GtkWidget *widget); @@ -541,7 +541,7 @@ SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry) return NULL; } - entry->submenu->menu = (GtkMenuShell *)gtk_menu_new(); + entry->submenu->menu = g_object_ref_sink(gtk_menu_new()); entry->submenu->parent_tray = NULL; entry->submenu->parent_entry = entry; entry->submenu->nEntries = 0; diff --git a/src/tray/windows/SDL_tray.c b/src/tray/windows/SDL_tray.c index 18008ee2..15021ac7 100644 --- a/src/tray/windows/SDL_tray.c +++ b/src/tray/windows/SDL_tray.c @@ -544,7 +544,7 @@ void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label) mii.dwTypeData = label_w; mii.cch = (UINT) SDL_wcslen(label_w); - if (!SetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, TRUE, &mii)) { + if (!SetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, FALSE, &mii)) { SDL_SetError("Couldn't update tray entry label"); } diff --git a/src/video/SDL_blit_A.c b/src/video/SDL_blit_A.c index 1cefd456..fc5ad1d7 100644 --- a/src/video/SDL_blit_A.c +++ b/src/video/SDL_blit_A.c @@ -486,8 +486,8 @@ static void SDL_TARGETING("mmx") Blit565to565SurfaceAlphaMMX(SDL_BlitInfo *info) d &= 0x07e0f81f; *dstp++ = (Uint16)(d | d >> 16); },{ - src1 = *(__m64*)srcp; // 4 src pixels -> src1 - dst1 = *(__m64*)dstp; // 4 dst pixels -> dst1 + src1 = *(__m64 *)srcp; // 4 src pixels -> src1 + dst1 = *(__m64 *)dstp; // 4 dst pixels -> dst1 // red src2 = src1; @@ -536,7 +536,7 @@ static void SDL_TARGETING("mmx") Blit565to565SurfaceAlphaMMX(SDL_BlitInfo *info) mm_res = _mm_or_si64(mm_res, dst2); // RED | GREEN | BLUE -> mm_res - *(__m64*)dstp = mm_res; // mm_res -> 4 dst pixels + *(__m64 *)dstp = mm_res; // mm_res -> 4 dst pixels srcp += 4; dstp += 4; @@ -624,8 +624,8 @@ static void SDL_TARGETING("mmx") Blit555to555SurfaceAlphaMMX(SDL_BlitInfo *info) d &= 0x03e07c1f; *dstp++ = (Uint16)(d | d >> 16); },{ - src1 = *(__m64*)srcp; // 4 src pixels -> src1 - dst1 = *(__m64*)dstp; // 4 dst pixels -> dst1 + src1 = *(__m64 *)srcp; // 4 src pixels -> src1 + dst1 = *(__m64 *)dstp; // 4 dst pixels -> dst1 // red -- process the bits in place src2 = src1; @@ -674,7 +674,7 @@ static void SDL_TARGETING("mmx") Blit555to555SurfaceAlphaMMX(SDL_BlitInfo *info) mm_res = _mm_or_si64(mm_res, dst2); // RED | GREEN | BLUE -> mm_res - *(__m64*)dstp = mm_res; // mm_res -> 4 dst pixels + *(__m64 *)dstp = mm_res; // mm_res -> 4 dst pixels srcp += 4; dstp += 4; @@ -1061,8 +1061,8 @@ static void SDL_TARGETING("sse4.1") Blit8888to8888PixelAlphaSwizzleSSE41(SDL_Bli __m128i dst_hi = _mm_maddubs_epi16(srca_hi, _mm_unpackhi_epi8(src128, dst128)); // dst += 0x1U (use 0x80 to round instead of floor) + 128*255 (to fix maddubs result) - dst_lo = _mm_add_epi16(dst_lo, _mm_set1_epi16(1 + 128*255)); - dst_hi = _mm_add_epi16(dst_hi, _mm_set1_epi16(1 + 128*255)); + dst_lo = _mm_add_epi16(dst_lo, _mm_set1_epi16(1 + 128 * 255)); + dst_hi = _mm_add_epi16(dst_hi, _mm_set1_epi16(1 + 128 * 255)); // dst = (dst + (dst >> 8)) >> 8 = (dst * 257) >> 16 dst_lo = _mm_mulhi_epu16(dst_lo, _mm_set1_epi16(257)); @@ -1165,8 +1165,8 @@ static void SDL_TARGETING("avx2") Blit8888to8888PixelAlphaSwizzleAVX2(SDL_BlitIn __m256i dst_hi = _mm256_maddubs_epi16(alpha_hi, _mm256_unpackhi_epi8(src256, dst256)); // dst += 0x1U (use 0x80 to round instead of floor) + 128*255 (to fix maddubs result) - dst_lo = _mm256_add_epi16(dst_lo, _mm256_set1_epi16(1 + 128*255)); - dst_hi = _mm256_add_epi16(dst_hi, _mm256_set1_epi16(1 + 128*255)); + dst_lo = _mm256_add_epi16(dst_lo, _mm256_set1_epi16(1 + 128 * 255)); + dst_hi = _mm256_add_epi16(dst_hi, _mm256_set1_epi16(1 + 128 * 255)); // dst = (dst + (dst >> 8)) >> 8 = (dst * 257) >> 16 dst_lo = _mm256_mulhi_epu16(dst_lo, _mm256_set1_epi16(257)); @@ -1290,8 +1290,8 @@ static void Blit8888to8888PixelAlphaSwizzleNEON(SDL_BlitInfo *info) // Process 1 pixel per iteration, max 3 iterations, same calculations as above for (; i < width; ++i) { // Top 32-bits will be not used in src32 & dst32 - uint8x8_t src32 = vreinterpret_u8_u32(vld1_dup_u32((Uint32*)src)); - uint8x8_t dst32 = vreinterpret_u8_u32(vld1_dup_u32((Uint32*)dst)); + uint8x8_t src32 = vreinterpret_u8_u32(vld1_dup_u32((Uint32 *)src)); + uint8x8_t dst32 = vreinterpret_u8_u32(vld1_dup_u32((Uint32 *)dst)); uint8x8_t srcA = vtbl1_u8(src32, vget_low_u8(alpha_splat_mask)); src32 = vtbl1_u8(src32, vget_low_u8(convert_mask)); @@ -1309,7 +1309,7 @@ static void Blit8888to8888PixelAlphaSwizzleNEON(SDL_BlitInfo *info) } // Save the result, only low 32-bits - vst1_lane_u32((Uint32*)dst, vreinterpret_u32_u8(dst32), 0); + vst1_lane_u32((Uint32 *)dst, vreinterpret_u32_u8(dst32), 0); src += 4; dst += 4; diff --git a/src/video/SDL_blit_N.c b/src/video/SDL_blit_N.c index dd157901..9c7b6b30 100644 --- a/src/video/SDL_blit_N.c +++ b/src/video/SDL_blit_N.c @@ -2258,7 +2258,7 @@ static void BlitNtoNKey(SDL_BlitInfo *info) /* *INDENT-OFF* */ // clang-format off DUFFS_LOOP( { - Uint32 *src32 = (Uint32*)src; + Uint32 *src32 = (Uint32 *)src; if ((*src32 & rgbmask) != ckey) { dst[0] = src[p0]; @@ -2366,7 +2366,7 @@ static void BlitNtoNKey(SDL_BlitInfo *info) /* *INDENT-OFF* */ // clang-format off DUFFS_LOOP( { - Uint32 *src32 = (Uint32*)src; + Uint32 *src32 = (Uint32 *)src; if ((*src32 & rgbmask) != ckey) { dst[0] = src[p0]; dst[1] = src[p1]; @@ -2516,7 +2516,7 @@ static void BlitNtoNKeyCopyAlpha(SDL_BlitInfo *info) /* *INDENT-OFF* */ // clang-format off DUFFS_LOOP( { - Uint32 *src32 = (Uint32*)src; + Uint32 *src32 = (Uint32 *)src; if ((*src32 & rgbmask) != ckey) { dst[0] = src[p0]; dst[1] = src[p1]; @@ -2777,7 +2777,7 @@ static void Blit8888to8888PixelSwizzleNEON(SDL_BlitInfo *info) // Process 1 pixel per iteration, max 3 iterations, same calculations as above for (; i < width; ++i) { // Top 32-bits will be not used in src32 - uint8x8_t src32 = vreinterpret_u8_u32(vld1_dup_u32((Uint32*)src)); + uint8x8_t src32 = vreinterpret_u8_u32(vld1_dup_u32((Uint32 *)src)); // Convert to dst format src32 = vtbl1_u8(src32, vget_low_u8(convert_mask)); @@ -2788,7 +2788,7 @@ static void Blit8888to8888PixelSwizzleNEON(SDL_BlitInfo *info) } // Save the result, only low 32-bits - vst1_lane_u32((Uint32*)dst, vreinterpret_u32_u8(src32), 0); + vst1_lane_u32((Uint32 *)dst, vreinterpret_u32_u8(src32), 0); src += 4; dst += 4; @@ -2829,7 +2829,7 @@ static void Blit_3or4_to_3or4__same_rgb(SDL_BlitInfo *info) /* *INDENT-OFF* */ // clang-format off DUFFS_LOOP( { - Uint32 *dst32 = (Uint32*)dst; + Uint32 *dst32 = (Uint32 *)dst; Uint8 s0 = src[i0]; Uint8 s1 = src[i1]; Uint8 s2 = src[i2]; @@ -2901,7 +2901,7 @@ static void Blit_3or4_to_3or4__inversed_rgb(SDL_BlitInfo *info) /* *INDENT-OFF* */ // clang-format off DUFFS_LOOP( { - Uint32 *dst32 = (Uint32*)dst; + Uint32 *dst32 = (Uint32 *)dst; Uint8 s0 = src[i0]; Uint8 s1 = src[i1]; Uint8 s2 = src[i2]; @@ -2929,7 +2929,7 @@ static void Blit_3or4_to_3or4__inversed_rgb(SDL_BlitInfo *info) /* *INDENT-OFF* */ // clang-format off DUFFS_LOOP( { - Uint32 *dst32 = (Uint32*)dst; + Uint32 *dst32 = (Uint32 *)dst; Uint8 s0 = src[i0]; Uint8 s1 = src[i1]; Uint8 s2 = src[i2]; diff --git a/src/video/SDL_stretch.c b/src/video/SDL_stretch.c index 51311fd8..daed7e8a 100644 --- a/src/video/SDL_stretch.c +++ b/src/video/SDL_stretch.c @@ -288,7 +288,7 @@ typedef struct color_t #if 0 static void printf_64(const char *str, void *var) { - uint8_t *val = (uint8_t*) var; + uint8_t *val = (uint8_t *)var; printf(" * %s: %02x %02x %02x %02x _ %02x %02x %02x %02x\n", str, val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7]); } @@ -394,7 +394,7 @@ static bool scale_mat(const Uint32 *src, int src_w, int src_h, int src_pitch, Ui #if 0 static void SDL_TARGETING("sse2") printf_128(const char *str, __m128i var) { - uint16_t *val = (uint16_t*) &var; + uint16_t *val = (uint16_t *)&var; printf(" * %s: %04x %04x %04x %04x _ %04x %04x %04x %04x\n", str, val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7]); } diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c index c5291ade..f40a92d8 100644 --- a/src/video/SDL_surface.c +++ b/src/video/SDL_surface.c @@ -1101,9 +1101,9 @@ bool SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surfac int dst_w, dst_h; // Make sure the surfaces aren't locked - if (!SDL_SurfaceValid(src)) { + if (!SDL_SurfaceValid(src) || !src->pixels) { return SDL_InvalidParamError("src"); - } else if (!SDL_SurfaceValid(dst)) { + } else if (!SDL_SurfaceValid(dst) || !dst->pixels) { return SDL_InvalidParamError("dst"); } else if ((src->flags & SDL_SURFACE_LOCKED) || (dst->flags & SDL_SURFACE_LOCKED)) { return SDL_SetError("Surfaces must not be locked during blit"); @@ -1142,6 +1142,13 @@ bool SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surfac return SDL_BlitSurface(src, srcrect, dst, dstrect); } + if (src_w == 0) { + src_w = 1; + } + if (src_h == 0) { + src_h = 1; + } + scaling_w = (double)dst_w / src_w; scaling_h = (double)dst_h / src_h; diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 5fe87a86..9f02c6d1 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -104,6 +104,7 @@ struct SDL_Window bool last_position_pending; // This should NOT be cleared by the backend, as it is used for fullscreen positioning. bool last_size_pending; // This should be cleared by the backend if the new size cannot be applied. bool update_fullscreen_on_display_changed; + bool constrain_popup; bool is_destroying; bool is_dropping; // drag/drop in progress, expecting SDL_SendDropComplete(). @@ -133,6 +134,9 @@ struct SDL_Window SDL_WindowData *internal; + // If a toplevel window, holds the current keyboard focus for grabbing popups. + SDL_Window *keyboard_focus; + SDL_Window *prev; SDL_Window *next; @@ -332,7 +336,7 @@ struct SDL_VideoDevice */ bool (*Vulkan_LoadLibrary)(SDL_VideoDevice *_this, const char *path); void (*Vulkan_UnloadLibrary)(SDL_VideoDevice *_this); - char const* const* (*Vulkan_GetInstanceExtensions)(SDL_VideoDevice *_this, Uint32 *count); + char const * const *(*Vulkan_GetInstanceExtensions)(SDL_VideoDevice *_this, Uint32 *count); bool (*Vulkan_CreateSurface)(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface); void (*Vulkan_DestroySurface)(SDL_VideoDevice *_this, VkInstance instance, VkSurfaceKHR surface, const struct VkAllocationCallbacks *allocator); bool (*Vulkan_GetPresentationSupport)(SDL_VideoDevice *_this, VkInstance instance, VkPhysicalDevice physicalDevice, Uint32 queueFamilyIndex); @@ -401,8 +405,7 @@ struct SDL_VideoDevice bool checked_texture_framebuffer; bool is_dummy; bool suspend_screensaver; - SDL_Window *wakeup_window; - SDL_Mutex *wakeup_lock; // Initialized only if WaitEventTimeout/SendWakeupEvent are supported + void *wakeup_window; int num_displays; SDL_VideoDisplay **displays; SDL_Rect desktop_bounds; @@ -527,6 +530,7 @@ extern VideoBootStrap PSP_bootstrap; extern VideoBootStrap VITA_bootstrap; extern VideoBootStrap RISCOS_bootstrap; extern VideoBootStrap N3DS_bootstrap; +extern VideoBootStrap NGAGE_bootstrap; extern VideoBootStrap RPI_bootstrap; extern VideoBootStrap KMSDRM_bootstrap; extern VideoBootStrap DUMMY_bootstrap; @@ -571,6 +575,8 @@ extern bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags); extern bool SDL_HasWindows(void); extern void SDL_RelativeToGlobalForWindow(SDL_Window *window, int rel_x, int rel_y, int *abs_x, int *abs_y); extern void SDL_GlobalToRelativeForWindow(SDL_Window *window, int abs_x, int abs_y, int *rel_x, int *rel_y); +extern bool SDL_ShouldFocusPopup(SDL_Window *window); +extern bool SDL_ShouldRelinquishPopupFocus(SDL_Window *window, SDL_Window **new_focus); extern void SDL_OnDisplayAdded(SDL_VideoDisplay *display); extern void SDL_OnDisplayMoved(SDL_VideoDisplay *display); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index db2637bc..60331688 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -119,6 +119,9 @@ static VideoBootStrap *bootstrap[] = { #ifdef SDL_VIDEO_DRIVER_N3DS &N3DS_bootstrap, #endif +#ifdef SDL_VIDEO_DRIVER_NGAGE + &NGAGE_bootstrap, +#endif #ifdef SDL_VIDEO_DRIVER_KMSDRM &KMSDRM_bootstrap, #endif @@ -1731,12 +1734,25 @@ SDL_VideoDisplay *SDL_GetVideoDisplayForFullscreenWindow(SDL_Window *window) return SDL_GetVideoDisplay(displayID); } +#define SDL_PROP_SDL2_COMPAT_WINDOW_PREFERRED_FULLSCREEN_DISPLAY "sdl2-compat.window.preferred_fullscreen_display" + SDL_DisplayID SDL_GetDisplayForWindow(SDL_Window *window) { SDL_DisplayID displayID = 0; CHECK_WINDOW_MAGIC(window, 0); + /* sdl2-compat calls this function to get a display on which to make the window fullscreen, + * so pass it the preferred fullscreen display ID in a property. + */ + SDL_PropertiesID window_props = SDL_GetWindowProperties(window); + SDL_VideoDisplay *fs_display = SDL_GetVideoDisplayForFullscreenWindow(window); + if (fs_display) { + SDL_SetNumberProperty(window_props, SDL_PROP_SDL2_COMPAT_WINDOW_PREFERRED_FULLSCREEN_DISPLAY, fs_display->id); + } else { + SDL_ClearProperty(window_props, SDL_PROP_SDL2_COMPAT_WINDOW_PREFERRED_FULLSCREEN_DISPLAY); + } + // An explicit fullscreen display overrides all if (window->flags & SDL_WINDOW_FULLSCREEN) { displayID = window->current_fullscreen_mode.displayID; @@ -2253,6 +2269,12 @@ static void SDL_FinishWindowCreation(SDL_Window *window, SDL_WindowFlags flags) SDL_ShowWindow(window); } } + +#if defined(SDL_PLATFORM_LINUX) + // On Linux the progress state is persisted throughout multiple program runs, so reset state on window creation + SDL_SetWindowProgressState(window, SDL_PROGRESS_STATE_NONE); + SDL_SetWindowProgressValue(window, 0.0f); +#endif } static bool SDL_ContextNotSupported(const char *name) @@ -2491,6 +2513,7 @@ SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props) window->is_destroying = false; window->last_displayID = SDL_GetDisplayForWindow(window); window->external_graphics_context = external_graphics_context; + window->constrain_popup = SDL_GetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_CONSTRAIN_POPUP_BOOLEAN, true); if (_this->windows) { _this->windows->prev = window; @@ -2501,7 +2524,9 @@ SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props) SDL_UpdateWindowHierarchy(window, parent); if (_this->CreateSDLWindow && !_this->CreateSDLWindow(_this, window, props)) { + PUSH_SDL_ERROR() SDL_DestroyWindow(window); + POP_SDL_ERROR() return NULL; } @@ -2749,7 +2774,7 @@ SDL_Window *SDL_GetWindowFromID(SDL_WindowID id) } } } - SDL_SetError("Invalid window ID"); \ + SDL_SetError("Invalid window ID"); return NULL; } @@ -3647,6 +3672,10 @@ bool SDL_SetWindowParent(SDL_Window *window, SDL_Window *parent) CHECK_WINDOW_NOT_POPUP(parent, false); } + if (window == parent) { + return SDL_SetError("Cannot set the parent of a window to itself."); + } + if (!_this->SetWindowParent) { return SDL_Unsupported(); } @@ -3692,6 +3721,48 @@ bool SDL_SetWindowModal(SDL_Window *window, bool modal) return _this->SetWindowModal(_this, window, modal); } +bool SDL_ShouldRelinquishPopupFocus(SDL_Window *window, SDL_Window **new_focus) +{ + SDL_Window *focus = window->parent; + bool set_focus = !!(window->flags & SDL_WINDOW_INPUT_FOCUS); + + // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed, and can grab the keyboard focus. + while (SDL_WINDOW_IS_POPUP(focus) && ((focus->flags & SDL_WINDOW_NOT_FOCUSABLE) || focus->is_hiding || focus->is_destroying)) { + focus = focus->parent; + + // If some window in the chain currently had focus, set it to the new lowest-level window. + if (!set_focus) { + set_focus = !!(focus->flags & SDL_WINDOW_INPUT_FOCUS); + } + } + + *new_focus = focus; + return set_focus; +} + +bool SDL_ShouldFocusPopup(SDL_Window *window) +{ + SDL_Window *toplevel_parent; + for (toplevel_parent = window->parent; SDL_WINDOW_IS_POPUP(toplevel_parent); toplevel_parent = toplevel_parent->parent) { + } + + SDL_Window *current_focus = toplevel_parent->keyboard_focus; + bool found_higher_focus = false; + + /* Traverse the window tree from the currently focused window to the toplevel parent and see if we encounter + * the new focus request. If the new window is found, a higher-level window already has focus. + */ + SDL_Window *w; + for (w = current_focus; w != toplevel_parent; w = w->parent) { + if (w == window) { + found_higher_focus = true; + break; + } + } + + return !found_higher_focus || w == toplevel_parent; +} + bool SDL_SetWindowFocusable(SDL_Window *window, bool focusable) { CHECK_WINDOW_MAGIC(window, false); @@ -4068,7 +4139,7 @@ void SDL_OnWindowLiveResizeUpdate(SDL_Window *window) SDL_IterateMainCallbacks(false); } else { // Send an expose event so the application can redraw - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 1, 0); } SDL_PumpEventMaintenance(); @@ -4331,9 +4402,7 @@ void SDL_DestroyWindow(SDL_Window *window) _this->current_glwin = NULL; } - if (_this->wakeup_window == window) { - _this->wakeup_window = NULL; - } + SDL_CompareAndSwapAtomicPointer(&_this->wakeup_window, window, NULL); // Now invalidate magic SDL_SetObjectValid(window, SDL_OBJECT_TYPE_WINDOW, false); @@ -5086,8 +5155,7 @@ bool SDL_GL_GetAttribute(SDL_GLAttr attr, int *value) } if (fbo_type != GL_NONE) { glGetFramebufferAttachmentParameterivFunc(GL_FRAMEBUFFER, attachment, attachmentattrib, (GLint *)value); - } - else { + } else { *value = 0; } if (glBindFramebufferFunc && (current_fbo != 0)) { @@ -5318,7 +5386,7 @@ bool SDL_GL_SwapWindow(SDL_Window *window) bool SDL_GL_DestroyContext(SDL_GLContext context) { if (!_this) { - return SDL_UninitializedVideo(); \ + return SDL_UninitializedVideo(); } if (!context) { return SDL_InvalidParamError("context"); @@ -5956,7 +6024,7 @@ void SDL_Vulkan_UnloadLibrary(void) } } -char const* const* SDL_Vulkan_GetInstanceExtensions(Uint32 *count) +char const * const *SDL_Vulkan_GetInstanceExtensions(Uint32 *count) { return _this->Vulkan_GetInstanceExtensions(_this, count); } diff --git a/src/video/android/SDL_androidvulkan.c b/src/video/android/SDL_androidvulkan.c index 4d38388f..2ab925f7 100644 --- a/src/video/android/SDL_androidvulkan.c +++ b/src/video/android/SDL_androidvulkan.c @@ -110,8 +110,7 @@ void Android_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) } } -char const* const* Android_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - Uint32 *count) +char const * const *Android_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count) { static const char *const extensionsForAndroid[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_ANDROID_SURFACE_EXTENSION_NAME diff --git a/src/video/android/SDL_androidvulkan.h b/src/video/android/SDL_androidvulkan.h index 4f7cd51f..410455fa 100644 --- a/src/video/android/SDL_androidvulkan.h +++ b/src/video/android/SDL_androidvulkan.h @@ -36,7 +36,7 @@ extern bool Android_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); extern void Android_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -extern char const* const* Android_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); +extern char const * const *Android_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); extern bool Android_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, diff --git a/src/video/cocoa/SDL_cocoaclipboard.m b/src/video/cocoa/SDL_cocoaclipboard.m index 42c2ad64..7039ff6a 100644 --- a/src/video/cocoa/SDL_cocoaclipboard.m +++ b/src/video/cocoa/SDL_cocoaclipboard.m @@ -158,6 +158,17 @@ bool Cocoa_SetClipboardData(SDL_VideoDevice *_this) @autoreleasepool { SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)_this->internal; NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + + // SetClipboardText specialization so text is available after the app quits + if (_this->clipboard_callback && _this->num_clipboard_mime_types == 1) { + if (SDL_strncmp(_this->clipboard_mime_types[0], "text/plain;charset=utf-8", 24) == 0) { + [pasteboard declareTypes:@[ NSPasteboardTypeString ] owner:nil]; + [pasteboard setString:@((char *)_this->clipboard_userdata) forType:NSPasteboardTypeString]; + data.clipboard_count = [pasteboard changeCount]; + return true; + } + } + NSPasteboardItem *newItem = [NSPasteboardItem new]; NSMutableArray *utiTypes = [NSMutableArray new]; Cocoa_PasteboardDataProvider *provider = [[Cocoa_PasteboardDataProvider alloc] initWith: _this->clipboard_callback userData: _this->clipboard_userdata]; diff --git a/src/video/cocoa/SDL_cocoaevents.m b/src/video/cocoa/SDL_cocoaevents.m index 58cae995..fd178b8d 100644 --- a/src/video/cocoa/SDL_cocoaevents.m +++ b/src/video/cocoa/SDL_cocoaevents.m @@ -311,15 +311,26 @@ static void Cocoa_DispatchEvent(NSEvent *theEvent) - (void)applicationDidFinishLaunching:(NSNotification *)notification { - if (!SDL_GetHintBoolean("SDL_MAC_REGISTER_ACTIVATION_HANDLERS", true)) + if (!SDL_GetHintBoolean("SDL_MAC_REGISTER_ACTIVATION_HANDLERS", true)) { return; + } /* The menu bar of SDL apps which don't have the typical .app bundle * structure fails to work the first time a window is created (until it's * de-focused and re-focused), if this call is in Cocoa_RegisterApp instead - * of here. https://bugzilla.libsdl.org/show_bug.cgi?id=3051 + * of here. https://github.com/libsdl-org/SDL/issues/1913 */ - if (!SDL_GetHintBoolean(SDL_HINT_MAC_BACKGROUND_APP, false)) { + + /* this apparently became unnecessary on macOS 14.0, and will addition pop up a + hidden dock if you're moving the mouse during launch, so change the default + behaviour there. https://github.com/libsdl-org/SDL/issues/10340 + (13.6 still needs it, presumably 13.7 does, too.) */ + bool background_app_default = false; + if (@available(macOS 14.0, *)) { + background_app_default = true; /* by default, don't explicitly activate the dock and then us again to force to foreground */ + } + + if (!SDL_GetHintBoolean(SDL_HINT_MAC_BACKGROUND_APP, background_app_default)) { // Get more aggressive for Catalina: activate the Dock first so we definitely reset all activation state. for (NSRunningApplication *i in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) { [i activateWithOptions:NSApplicationActivateIgnoringOtherApps]; diff --git a/src/video/cocoa/SDL_cocoamouse.m b/src/video/cocoa/SDL_cocoamouse.m index 530ca0c8..f8f58297 100644 --- a/src/video/cocoa/SDL_cocoamouse.m +++ b/src/video/cocoa/SDL_cocoamouse.m @@ -450,12 +450,18 @@ void Cocoa_HandleMouseEvent(SDL_VideoDevice *_this, NSEvent *event) // All events except NSEventTypeMouseExited can only happen if the window // has mouse focus, so we'll always set the focus even if we happen to miss // NSEventTypeMouseEntered, which apparently happens if the window is - // created under the mouse on macOS 12.7 + // created under the mouse on macOS 12.7. But, only set the focus if + // the event acutally has a non-NULL window, otherwise what would happen + // is that after an NSEventTypeMouseEntered there would sometimes be + // NSEventTypeMouseMoved without a window causing us to suppress subsequent + // mouse move events. NSEventType event_type = [event type]; if (event_type == NSEventTypeMouseExited) { Cocoa_MouseFocus = NULL; } else { - Cocoa_MouseFocus = [event window]; + if ([event window] != NULL) { + Cocoa_MouseFocus = [event window]; + } } switch (event_type) { diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 81baf782..3738f957 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -49,9 +49,6 @@ static void Cocoa_VideoQuit(SDL_VideoDevice *_this); static void Cocoa_DeleteDevice(SDL_VideoDevice *device) { @autoreleasepool { - if (device->wakeup_lock) { - SDL_DestroyMutex(device->wakeup_lock); - } CFBridgingRelease(device->internal); SDL_free(device); } @@ -81,7 +78,6 @@ static SDL_VideoDevice *Cocoa_CreateDevice(void) return NULL; } device->internal = (SDL_VideoData *)CFBridgingRetain(data); - device->wakeup_lock = SDL_CreateMutex(); device->system_theme = Cocoa_GetSystemTheme(); // Set the function pointers @@ -248,7 +244,7 @@ void Cocoa_VideoQuit(SDL_VideoDevice *_this) SDL_SystemTheme Cocoa_GetSystemTheme(void) { if (@available(macOS 10.14, *)) { - NSAppearance* appearance = [[NSApplication sharedApplication] effectiveAppearance]; + NSAppearance *appearance = [[NSApplication sharedApplication] effectiveAppearance]; if ([appearance.name containsString: @"Dark"]) { return SDL_SYSTEM_THEME_DARK; diff --git a/src/video/cocoa/SDL_cocoavulkan.h b/src/video/cocoa/SDL_cocoavulkan.h index 86e634ed..42bd0193 100644 --- a/src/video/cocoa/SDL_cocoavulkan.h +++ b/src/video/cocoa/SDL_cocoavulkan.h @@ -36,7 +36,7 @@ extern bool Cocoa_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); extern void Cocoa_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -extern char const* const* Cocoa_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); +extern char const * const *Cocoa_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); extern bool Cocoa_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, diff --git a/src/video/cocoa/SDL_cocoavulkan.m b/src/video/cocoa/SDL_cocoavulkan.m index a4406275..534a3743 100644 --- a/src/video/cocoa/SDL_cocoavulkan.m +++ b/src/video/cocoa/SDL_cocoavulkan.m @@ -161,8 +161,7 @@ void Cocoa_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) } } -char const* const* Cocoa_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - Uint32 *count) +char const * const *Cocoa_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count) { static const char *const extensionsForCocoa[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_EXT_METAL_SURFACE_EXTENSION_NAME, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index 6df69f44..67f1519e 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/src/video/cocoa/SDL_cocoawindow.h @@ -146,7 +146,6 @@ typedef enum @property(nonatomic) BOOL was_zoomed; @property(nonatomic) NSInteger window_number; @property(nonatomic) NSInteger flash_request; -@property(nonatomic) SDL_Window *keyboard_focus; @property(nonatomic) SDL3Cocoa_WindowListener *listener; @property(nonatomic) NSModalSession modal_session; @property(nonatomic) SDL_CocoaVideoData *videodata; diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m index 800db71f..12135e5d 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/src/video/cocoa/SDL_cocoawindow.m @@ -707,10 +707,7 @@ static SDL_Window *GetParentToplevelWindow(SDL_Window *window) static void Cocoa_SetKeyboardFocus(SDL_Window *window, bool set_active_focus) { SDL_Window *toplevel = GetParentToplevelWindow(window); - SDL_CocoaWindowData *toplevel_data; - - toplevel_data = (__bridge SDL_CocoaWindowData *)toplevel->internal; - toplevel_data.keyboard_focus = window; + toplevel->keyboard_focus = window; if (set_active_focus && !window->is_hiding && !window->is_destroying) { SDL_SetKeyboardFocus(window); @@ -743,7 +740,7 @@ static NSCursor *Cocoa_GetDesiredCursor(void) { SDL_Mouse *mouse = SDL_GetMouse(); - if (mouse->cursor_shown && mouse->cur_cursor && !mouse->relative_mode) { + if (mouse->cursor_visible && mouse->cur_cursor && !mouse->relative_mode) { return (__bridge NSCursor *)mouse->cur_cursor->internal; } @@ -1252,7 +1249,7 @@ static NSCursor *Cocoa_GetDesiredCursor(void) // We're going to get keyboard events, since we're key. // This needs to be done before restoring the relative mouse mode. - Cocoa_SetKeyboardFocus(_data.keyboard_focus ? _data.keyboard_focus : window, true); + Cocoa_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window, true); // If we just gained focus we need the updated mouse position if (!(window->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE)) { @@ -2022,6 +2019,7 @@ static void Cocoa_SendMouseButtonClicks(SDL_Mouse *mouse, NSEvent *theEvent, SDL @interface SDL3View : NSView { SDL_Window *_sdlWindow; + NSTrackingArea *_trackingArea; // only used on macOS <= 11.0 } - (void)setSDLWindow:(SDL_Window *)window; @@ -2033,6 +2031,7 @@ static void Cocoa_SendMouseButtonClicks(SDL_Mouse *mouse, NSEvent *theEvent, SDL - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent; - (BOOL)wantsUpdateLayer; - (void)updateLayer; +- (void)updateTrackingAreas; @end @implementation SDL3View @@ -2104,15 +2103,61 @@ static void Cocoa_SendMouseButtonClicks(SDL_Mouse *mouse, NSEvent *theEvent, SDL - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent { - if (SDL_GetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH)) { + if (_sdlWindow->flags & SDL_WINDOW_POPUP_MENU) { + return YES; + } else if (SDL_GetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH)) { return SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, false); } else { return SDL_GetHintBoolean("SDL_MAC_MOUSE_FOCUS_CLICKTHROUGH", false); } } +// NSTrackingArea is how Cocoa tells you when the mouse cursor has entered or +// left certain regions. We put one over our entire window so we know when +// it has "mouse focus." +- (void)updateTrackingAreas +{ + [super updateTrackingAreas]; + + SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)_sdlWindow->internal; + if (_trackingArea) { + [self removeTrackingArea:_trackingArea]; + } + _trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:NSTrackingMouseEnteredAndExited|NSTrackingActiveAlways owner:windata.listener userInfo:nil]; + [self addTrackingArea:_trackingArea]; +} @end +static void Cocoa_UpdateMouseFocus() +{ + const NSPoint mouseLocation = [NSEvent mouseLocation]; + + // Find the topmost window under the pointer and send a motion event if it is an SDL window. + [NSApp enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack + usingBlock:^(NSWindow *nswin, BOOL *stop) { + NSRect r = [nswin contentRectForFrameRect:[nswin frame]]; + if (NSPointInRect(mouseLocation, r)) { + SDL_VideoDevice *vid = SDL_GetVideoDevice(); + SDL_Window *sdlwindow; + for (sdlwindow = vid->windows; sdlwindow; sdlwindow = sdlwindow->next) { + if (nswin == ((__bridge SDL_CocoaWindowData *)sdlwindow->internal).nswindow) { + break; + } + } + *stop = YES; + if (sdlwindow) { + int wx, wy; + SDL_RelativeToGlobalForWindow(sdlwindow, sdlwindow->x, sdlwindow->y, &wx, &wy); + + // Calculate the cursor coordinates relative to the window. + const float dx = mouseLocation.x - wx; + const float dy = (CGDisplayPixelsHigh(kCGDirectMainDisplay) - mouseLocation.y) - wy; + SDL_SendMouseMotion(0, sdlwindow, SDL_GLOBAL_MOUSE_ID, false, dx, dy); + } + } + }]; +} + static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow *nswindow, NSView *nsview) { @autoreleasepool { @@ -2211,8 +2256,11 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow if (window->flags & SDL_WINDOW_TOOLTIP) { [nswindow setIgnoresMouseEvents:YES]; [nswindow setAcceptsMouseMovedEvents:NO]; - } else if (window->flags & SDL_WINDOW_POPUP_MENU) { - Cocoa_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus()); + } else if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_HIDDEN)) { + if (!(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + Cocoa_SetKeyboardFocus(window, true); + } + Cocoa_UpdateMouseFocus(); } } @@ -2301,7 +2349,7 @@ bool Cocoa_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properti rect.origin.y -= screenRect.origin.y; // Constrain the popup - if (SDL_WINDOW_IS_POPUP(window)) { + if (SDL_WINDOW_IS_POPUP(window) && window->constrain_popup) { if (rect.origin.x + rect.size.width > screenRect.origin.x + screenRect.size.width) { rect.origin.x -= (rect.origin.x + rect.size.width) - (screenRect.origin.x + screenRect.size.width); } @@ -2457,7 +2505,7 @@ bool Cocoa_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) ConvertNSRect(&rect); // Position and constrain the popup - if (SDL_WINDOW_IS_POPUP(window)) { + if (SDL_WINDOW_IS_POPUP(window) && window->constrain_popup) { NSRect screenRect = [ScreenForRect(&rect) frame]; if (rect.origin.x + rect.size.width > screenRect.origin.x + screenRect.size.width) { @@ -2532,8 +2580,8 @@ void Cocoa_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window) SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal; NSSize maxSize; - maxSize.width = window->max_w; - maxSize.height = window->max_h; + maxSize.width = window->max_w ? window->max_w : CGFLOAT_MAX; + maxSize.height = window->max_h ? window->max_h : CGFLOAT_MAX; [windata.nswindow setContentMaxSize:maxSize]; } @@ -2597,6 +2645,11 @@ void Cocoa_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) [nswindow orderWindow:NSWindowBelow relativeTo:[[NSApp keyWindow] windowNumber]]; } } + } else if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (!(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + Cocoa_SetKeyboardFocus(window, true); + } + Cocoa_UpdateMouseFocus(); } } [nswindow setIsVisible:YES]; @@ -2629,21 +2682,11 @@ void Cocoa_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) Cocoa_SetWindowModal(_this, window, false); // Transfer keyboard focus back to the parent when closing a popup menu - if (window->flags & SDL_WINDOW_POPUP_MENU) { - SDL_Window *new_focus = window->parent; - bool set_focus = window == SDL_GetKeyboardFocus(); - - // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed. - while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) { - new_focus = new_focus->parent; - - // If some window in the chain currently had focus, set it to the new lowest-level window. - if (!set_focus) { - set_focus = new_focus == SDL_GetKeyboardFocus(); - } - } - + if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); Cocoa_SetKeyboardFocus(new_focus, set_focus); + Cocoa_UpdateMouseFocus(); } else if (window->parent && waskey) { /* Key status is not automatically set on the parent when a child is hidden. Check if the * child window was key, and set the first visible parent to be key if so. @@ -3068,20 +3111,19 @@ void Cocoa_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) #endif // SDL_VIDEO_OPENGL SDL_Window *topmost = GetParentToplevelWindow(window); - SDL_CocoaWindowData *topmost_data = (__bridge SDL_CocoaWindowData *)topmost->internal; /* Reset the input focus of the root window if this window is still set as keyboard focus. * SDL_DestroyWindow will have already taken care of reassigning focus if this is the SDL * keyboard focus, this ensures that an inactive window with this window set as input focus * does not try to reference it the next time it gains focus. */ - if (topmost_data.keyboard_focus == window) { + if (topmost->keyboard_focus == window) { SDL_Window *new_focus = window; while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) { new_focus = new_focus->parent; } - topmost_data.keyboard_focus = new_focus; + topmost->keyboard_focus = new_focus; } if ([data.listener isInFullscreenSpace]) { @@ -3246,6 +3288,20 @@ bool Cocoa_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOper bool Cocoa_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable) { + if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (!(window->flags & SDL_WINDOW_HIDDEN)) { + if (!focusable && (window->flags & SDL_WINDOW_INPUT_FOCUS)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); + Cocoa_SetKeyboardFocus(new_focus, set_focus); + } else if (focusable) { + if (SDL_ShouldFocusPopup(window)) { + Cocoa_SetKeyboardFocus(window, true); + } + } + } + } + return true; // just succeed, the real work is done elsewhere. } diff --git a/src/video/emscripten/SDL_emscriptenframebuffer.c b/src/video/emscripten/SDL_emscriptenframebuffer.c index 503fac68..c487d579 100644 --- a/src/video/emscripten/SDL_emscriptenframebuffer.c +++ b/src/video/emscripten/SDL_emscriptenframebuffer.c @@ -78,7 +78,7 @@ bool Emscripten_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wind if (!Module['SDL3']) Module['SDL3'] = {}; var SDL3 = Module['SDL3']; if (SDL3.ctxCanvas !== canvas) { - SDL3.ctx = Module['createContext'](canvas, false, true); + SDL3.ctx = Browser.createContext(canvas, false, true); SDL3.ctxCanvas = canvas; } if (SDL3.w !== w || SDL3.h !== h || SDL3.imageCtx !== SDL3.ctx) { @@ -110,7 +110,7 @@ bool Emscripten_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wind data32.set(HEAP32.subarray(src, src + num)); var data8 = SDL3.data8; var i = 3; - var j = i + 4*num; + var j = i + 4 * num; if (num % 8 == 0) { // unrolling gives big speedups while (i < j) { diff --git a/src/video/emscripten/SDL_emscriptenopengles.c b/src/video/emscripten/SDL_emscriptenopengles.c index 227cdc55..bb490bb0 100644 --- a/src/video/emscripten/SDL_emscriptenopengles.c +++ b/src/video/emscripten/SDL_emscriptenopengles.c @@ -101,7 +101,7 @@ SDL_GLContext Emscripten_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window * context = emscripten_webgl_create_context(window_data->canvas_id, &attribs); - if (context < 0) { + if (!context) { SDL_SetError("Could not create webgl context"); return NULL; } diff --git a/src/video/emscripten/SDL_emscriptenvideo.c b/src/video/emscripten/SDL_emscriptenvideo.c index 8ddcb95a..d1f1a696 100644 --- a/src/video/emscripten/SDL_emscriptenvideo.c +++ b/src/video/emscripten/SDL_emscriptenvideo.c @@ -64,7 +64,7 @@ static SDL_SystemTheme Emscripten_GetSystemTheme(void) /* Technically, light theme can mean explicit light theme or no preference. https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme#syntax */ - int theme_code = EM_ASM_INT({ + int theme_code = MAIN_THREAD_EM_ASM_INT({ if (!window.matchMedia) { return -1; } diff --git a/src/video/haiku/SDL_bclipboard.cc b/src/video/haiku/SDL_bclipboard.cc index ff2bc6b6..1334b727 100644 --- a/src/video/haiku/SDL_bclipboard.cc +++ b/src/video/haiku/SDL_bclipboard.cc @@ -59,7 +59,7 @@ char *HAIKU_GetClipboardText(SDL_VideoDevice *_this) { if (be_clipboard->Lock()) { if ((clip = be_clipboard->Data())) { // Presumably the string of characters is ascii-format - clip->FindData("text/plain", B_MIME_TYPE, (const void**)&text, + clip->FindData("text/plain", B_MIME_TYPE, (const void **)&text, &length); } be_clipboard->Unlock(); diff --git a/src/video/haiku/SDL_bmessagebox.cc b/src/video/haiku/SDL_bmessagebox.cc index 75bef70e..4400a918 100644 --- a/src/video/haiku/SDL_bmessagebox.cc +++ b/src/video/haiku/SDL_bmessagebox.cc @@ -324,7 +324,7 @@ protected: } virtual void - SetTitle(const char* aTitle) + SetTitle(const char *aTitle) { fTitle = aTitle; BAlert::SetTitle(aTitle); diff --git a/src/video/haiku/SDL_bmodes.cc b/src/video/haiku/SDL_bmodes.cc index 32c8b6d4..3f314b95 100644 --- a/src/video/haiku/SDL_bmodes.cc +++ b/src/video/haiku/SDL_bmodes.cc @@ -178,7 +178,7 @@ static void _BDisplayModeToSdlDisplayMode(display_mode *bmode, SDL_DisplayMode * get_refresh_rate(*bmode, &mode->refresh_rate_numerator, &mode->refresh_rate_denominator); #if WRAP_BMODE - SDL_DisplayModeData *data = (SDL_DisplayModeData*)SDL_calloc(1, sizeof(SDL_DisplayModeData)); + SDL_DisplayModeData *data = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData)); data->bmode = bmode; mode->internal = data; diff --git a/src/video/haiku/SDL_bopengl.cc b/src/video/haiku/SDL_bopengl.cc index 469b7f6c..f0879574 100644 --- a/src/video/haiku/SDL_bopengl.cc +++ b/src/video/haiku/SDL_bopengl.cc @@ -94,7 +94,7 @@ bool HAIKU_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window * window) bool HAIKU_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window * window, SDL_GLContext context) { - BGLView* glView = (BGLView*)context; + BGLView *glView = (BGLView *)context; // printf("HAIKU_GL_MakeCurrent(%llx), win = %llx, thread = %d\n", (uint64)context, (uint64)window, find_thread(NULL)); if (glView) { if ((glView->Window() == NULL) || (!window) || (_ToBeWin(window)->GetGLView() != glView)) { @@ -150,8 +150,8 @@ SDL_GLContext HAIKU_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window * window bool HAIKU_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context) { // printf("HAIKU_GL_DestroyContext(%llx), thread = %d\n", (uint64)context, find_thread(NULL)); - BGLView* glView = (BGLView*)context; - SDL_BWin *bwin = (SDL_BWin*)glView->Window(); + BGLView *glView = (BGLView *)context; + SDL_BWin *bwin = (SDL_BWin *)glView->Window(); if (!bwin) { delete glView; } else { diff --git a/src/video/kmsdrm/SDL_kmsdrmmouse.c b/src/video/kmsdrm/SDL_kmsdrmmouse.c index bb50ae39..383d7d43 100644 --- a/src/video/kmsdrm/SDL_kmsdrmmouse.c +++ b/src/video/kmsdrm/SDL_kmsdrmmouse.c @@ -50,7 +50,7 @@ static void KMSDRM_FreeCursor(SDL_Cursor *cursor); // KMSDRM_ShowCursor() simply shows or hides the cursor it receives: it does NOT // mind if it's mouse->cur_cursor, etc. // -If KMSDRM_ShowCursor() returns successfully, that cursor becomes -// mouse->cur_cursor and mouse->cursor_shown is 1. +// mouse->cur_cursor and mouse->cursor_visible is 1. /**************************************************************************************/ static SDL_Cursor *KMSDRM_CreateDefaultCursor(void) diff --git a/src/video/kmsdrm/SDL_kmsdrmvideo.c b/src/video/kmsdrm/SDL_kmsdrmvideo.c index be1db82b..257b35d3 100644 --- a/src/video/kmsdrm/SDL_kmsdrmvideo.c +++ b/src/video/kmsdrm/SDL_kmsdrmvideo.c @@ -588,88 +588,51 @@ static void KMSDRM_DeinitDisplays(SDL_VideoDevice *_this) } } -static uint32_t KMSDRM_CrtcGetPropId(uint32_t drm_fd, - drmModeObjectPropertiesPtr props, - char const *name) +static bool KMSDRM_ConnectorCheckVrrCapable(uint32_t drm_fd, uint32_t output_id) { - uint32_t i, prop_id = 0; - - for (i = 0; !prop_id && i < props->count_props; ++i) { - drmModePropertyPtr drm_prop = - KMSDRM_drmModeGetProperty(drm_fd, props->props[i]); - - if (!drm_prop) { - continue; - } - - if (SDL_strcmp(drm_prop->name, name) == 0) { - prop_id = drm_prop->prop_id; - } - - KMSDRM_drmModeFreeProperty(drm_prop); - } - - return prop_id; -} - -static bool KMSDRM_VrrPropId(uint32_t drm_fd, uint32_t crtc_id, uint32_t *vrr_prop_id) -{ - drmModeObjectPropertiesPtr drm_props; - - drm_props = KMSDRM_drmModeObjectGetProperties(drm_fd, - crtc_id, - DRM_MODE_OBJECT_CRTC); - - if (!drm_props) { - return false; - } - - *vrr_prop_id = KMSDRM_CrtcGetPropId(drm_fd, - drm_props, - "VRR_ENABLED"); - - KMSDRM_drmModeFreeObjectProperties(drm_props); - - return true; -} - -static bool KMSDRM_ConnectorCheckVrrCapable(uint32_t drm_fd, - uint32_t output_id, - char const *name) -{ - uint32_t i; bool found = false; uint64_t prop_value = 0; - drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd, - output_id, - DRM_MODE_OBJECT_CONNECTOR); - - if (!props) { - return false; - } - - for (i = 0; !found && i < props->count_props; ++i) { - drmModePropertyPtr drm_prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]); - - if (!drm_prop) { - continue; + drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd, output_id, DRM_MODE_OBJECT_CONNECTOR); + if (props) { + for (uint32_t i = 0; !found && i < props->count_props; ++i) { + drmModePropertyPtr prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]); + if (prop) { + if (SDL_strcasecmp(prop->name, "VRR_CAPABLE") == 0) { + prop_value = props->prop_values[i]; + found = true; + } + KMSDRM_drmModeFreeProperty(prop); + } } - - if (SDL_strcasecmp(drm_prop->name, name) == 0) { - prop_value = props->prop_values[i]; - found = true; - } - - KMSDRM_drmModeFreeProperty(drm_prop); + KMSDRM_drmModeFreeObjectProperties(props); } if (found) { return prop_value ? true : false; } - return false; } +static bool KMSDRM_VrrPropId(uint32_t drm_fd, uint32_t crtc_id, uint32_t *vrr_prop_id) +{ + bool found = false; + drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd, crtc_id, DRM_MODE_OBJECT_CRTC); + if (props) { + for (uint32_t i = 0; !found && i < props->count_props; ++i) { + drmModePropertyPtr prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]); + if (prop) { + if (SDL_strcmp(prop->name, "VRR_ENABLED") == 0) { + *vrr_prop_id = prop->prop_id; + found = true; + } + KMSDRM_drmModeFreeProperty(prop); + } + } + KMSDRM_drmModeFreeObjectProperties(props); + } + return found; +} + static void KMSDRM_CrtcSetVrr(uint32_t drm_fd, uint32_t crtc_id, bool enabled) { uint32_t vrr_prop_id; @@ -677,119 +640,67 @@ static void KMSDRM_CrtcSetVrr(uint32_t drm_fd, uint32_t crtc_id, bool enabled) return; } - KMSDRM_drmModeObjectSetProperty(drm_fd, - crtc_id, - DRM_MODE_OBJECT_CRTC, - vrr_prop_id, - enabled); + KMSDRM_drmModeObjectSetProperty(drm_fd, crtc_id, DRM_MODE_OBJECT_CRTC, vrr_prop_id, enabled); } static bool KMSDRM_CrtcGetVrr(uint32_t drm_fd, uint32_t crtc_id) { - uint32_t object_prop_id, vrr_prop_id; - drmModeObjectPropertiesPtr props; - bool object_prop_value; - int i; + uint32_t vrr_prop_id = 0; + bool found = false; + uint64_t prop_value = 0; if (!KMSDRM_VrrPropId(drm_fd, crtc_id, &vrr_prop_id)) { return false; } - props = KMSDRM_drmModeObjectGetProperties(drm_fd, - crtc_id, - DRM_MODE_OBJECT_CRTC); - - if (!props) { - return false; + drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd, crtc_id, DRM_MODE_OBJECT_CRTC); + if (props) { + for (uint32_t i = 0; !found && i < props->count_props; ++i) { + drmModePropertyPtr prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]); + if (prop) { + if (prop->prop_id == vrr_prop_id) { + prop_value = props->prop_values[i]; + found = true; + } + KMSDRM_drmModeFreeProperty(prop); + } + } + KMSDRM_drmModeFreeObjectProperties(props); } - - for (i = 0; i < props->count_props; ++i) { - drmModePropertyPtr drm_prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]); - - if (!drm_prop) { - continue; - } - - object_prop_id = drm_prop->prop_id; - object_prop_value = props->prop_values[i] ? true : false; - - KMSDRM_drmModeFreeProperty(drm_prop); - - if (object_prop_id == vrr_prop_id) { - return object_prop_value; - } + if (found) { + return prop_value ? true : false; } return false; } -static bool KMSDRM_OrientationPropId(uint32_t drm_fd, uint32_t crtc_id, uint32_t *orientation_prop_id) -{ - drmModeObjectPropertiesPtr drm_props; - - drm_props = KMSDRM_drmModeObjectGetProperties(drm_fd, - crtc_id, - DRM_MODE_OBJECT_CONNECTOR); - - if (!drm_props) { - return false; - } - - *orientation_prop_id = KMSDRM_CrtcGetPropId(drm_fd, - drm_props, - "panel orientation"); - - KMSDRM_drmModeFreeObjectProperties(drm_props); - - return true; -} - static int KMSDRM_CrtcGetOrientation(uint32_t drm_fd, uint32_t crtc_id) { - uint32_t orientation_prop_id; - drmModeObjectPropertiesPtr props; - int i; - bool done = false; + bool found = false; int orientation = 0; - if (!KMSDRM_OrientationPropId(drm_fd, crtc_id, &orientation_prop_id)) { - return orientation; - } - - props = KMSDRM_drmModeObjectGetProperties(drm_fd, - crtc_id, - DRM_MODE_OBJECT_CONNECTOR); - - if (!props) { - return orientation; - } - - for (i = 0; i < props->count_props && !done; ++i) { - drmModePropertyPtr drm_prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]); - - if (!drm_prop) { - continue; - } - - if (drm_prop->prop_id == orientation_prop_id && (drm_prop->flags & DRM_MODE_PROP_ENUM)) { - if (drm_prop->count_enums) { - // "Normal" is the default of no rotation (0 degrees) - if (SDL_strcmp(drm_prop->enums[0].name, "Left Side Up") == 0) { - orientation = 90; - } else if (SDL_strcmp(drm_prop->enums[0].name, "Upside Down") == 0) { - orientation = 180; - } else if (SDL_strcmp(drm_prop->enums[0].name, "Right Side Up") == 0) { - orientation = 270; + drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd, crtc_id, DRM_MODE_OBJECT_CONNECTOR); + if (props) { + for (uint32_t i = 0; !found && i < props->count_props; ++i) { + drmModePropertyPtr prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]); + if (prop) { + if (SDL_strcasecmp(prop->name, "panel orientation") == 0 && (prop->flags & DRM_MODE_PROP_ENUM)) { + if (prop->count_enums) { + // "Normal" is the default of no rotation (0 degrees) + if (SDL_strcmp(prop->enums[0].name, "Left Side Up") == 0) { + orientation = 90; + } else if (SDL_strcmp(prop->enums[0].name, "Upside Down") == 0) { + orientation = 180; + } else if (SDL_strcmp(prop->enums[0].name, "Right Side Up") == 0) { + orientation = 270; + } + } + found = true; } + KMSDRM_drmModeFreeProperty(prop); } - - done = true; } - - KMSDRM_drmModeFreeProperty(drm_prop); + KMSDRM_drmModeFreeObjectProperties(props); } - - KMSDRM_drmModeFreeObjectProperties(props); - return orientation; } @@ -964,7 +875,7 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto // save previous vrr state dispdata->saved_vrr = KMSDRM_CrtcGetVrr(viddata->drm_fd, crtc->crtc_id); // try to enable vrr - if (KMSDRM_ConnectorCheckVrrCapable(viddata->drm_fd, connector->connector_id, "VRR_CAPABLE")) { + if (KMSDRM_ConnectorCheckVrrCapable(viddata->drm_fd, connector->connector_id)) { SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Enabling VRR"); KMSDRM_CrtcSetVrr(viddata->drm_fd, crtc->crtc_id, true); } @@ -1368,9 +1279,14 @@ bool KMSDRM_CreateSurfaces(SDL_VideoDevice *_this, SDL_Window *window) windata->gs = KMSDRM_gbm_surface_create(viddata->gbm_dev, dispdata->mode.hdisplay, dispdata->mode.vdisplay, surface_fmt, surface_flags); - + if (!windata->gs && errno == ENOSYS) { + // Try again without the scanout flags, needed on NVIDIA drivers + windata->gs = KMSDRM_gbm_surface_create(viddata->gbm_dev, + dispdata->mode.hdisplay, dispdata->mode.vdisplay, + surface_fmt, 0); + } if (!windata->gs) { - return SDL_SetError("Could not create GBM surface"); + return SDL_SetError("Could not create GBM surface: %s", strerror(errno)); } /* We can't get the EGL context yet because SDL_CreateRenderer has not been called, @@ -1729,9 +1645,9 @@ bool KMSDRM_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert } /* Create the window surfaces with the size we have just chosen. - Needs the window diverdata in place. */ + Needs the window driverdata in place. */ if (!KMSDRM_CreateSurfaces(_this, window)) { - return SDL_SetError("Can't window GBM/EGL surfaces on window creation."); + return false; } } // NON-Vulkan block ends. diff --git a/src/video/kmsdrm/SDL_kmsdrmvulkan.c b/src/video/kmsdrm/SDL_kmsdrmvulkan.c index d58277b7..e60af3ff 100644 --- a/src/video/kmsdrm/SDL_kmsdrmvulkan.c +++ b/src/video/kmsdrm/SDL_kmsdrmvulkan.c @@ -140,8 +140,7 @@ void KMSDRM_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) // members of the VkInstanceCreateInfo struct passed to // vkCreateInstance(). /*********************************************************************/ -char const* const* KMSDRM_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - Uint32 *count) +char const * const *KMSDRM_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count) { static const char *const extensionsForKMSDRM[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_DISPLAY_EXTENSION_NAME diff --git a/src/video/kmsdrm/SDL_kmsdrmvulkan.h b/src/video/kmsdrm/SDL_kmsdrmvulkan.h index aabfd92e..3a32ce29 100644 --- a/src/video/kmsdrm/SDL_kmsdrmvulkan.h +++ b/src/video/kmsdrm/SDL_kmsdrmvulkan.h @@ -35,7 +35,7 @@ extern bool KMSDRM_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); extern void KMSDRM_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -extern char const* const* KMSDRM_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); +extern char const * const *KMSDRM_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); extern bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, diff --git a/src/video/ngage/SDL_ngagevideo.c b/src/video/ngage/SDL_ngagevideo.c new file mode 100644 index 00000000..a01885bf --- /dev/null +++ b/src/video/ngage/SDL_ngagevideo.c @@ -0,0 +1,175 @@ +/* + 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_sysvideo.h" + +#ifdef SDL_VIDEO_DRIVER_NGAGE + +#include "SDL_ngagevideo.h" + +#define NGAGE_VIDEO_DRIVER_NAME "N-Gage" + +static void NGAGE_DeleteDevice(SDL_VideoDevice *device); +static bool NGAGE_VideoInit(SDL_VideoDevice *device); +static void NGAGE_VideoQuit(SDL_VideoDevice *device); + +static bool NGAGE_GetDisplayBounds(SDL_VideoDevice *device, SDL_VideoDisplay *display, SDL_Rect *rect); +static bool NGAGE_GetDisplayModes(SDL_VideoDevice *device, SDL_VideoDisplay *display); + +static void NGAGE_PumpEvents(SDL_VideoDevice *device); + +static bool NGAGE_SuspendScreenSaver(SDL_VideoDevice *device); + +static SDL_VideoDevice *NGAGE_CreateDevice(void) +{ + SDL_VideoDevice *device; + SDL_VideoData *phdata; + + // Initialize all variables that we clean on shutdown. + device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); + if (!device) { + SDL_OutOfMemory(); + return (SDL_VideoDevice *)0; + } + + // Initialize internal N-Gage specific data. + phdata = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); + if (!phdata) { + SDL_OutOfMemory(); + SDL_free(device); + return (SDL_VideoDevice *)0; + } + + device->internal = phdata; + + device->name = "Nokia N-Gage"; + + device->VideoInit = NGAGE_VideoInit; + device->VideoQuit = NGAGE_VideoQuit; + + device->GetDisplayBounds = NGAGE_GetDisplayBounds; + device->GetDisplayModes = NGAGE_GetDisplayModes; + + device->PumpEvents = NGAGE_PumpEvents; + + device->SuspendScreenSaver = NGAGE_SuspendScreenSaver; + + device->free = NGAGE_DeleteDevice; + + device->device_caps = VIDEO_DEVICE_CAPS_FULLSCREEN_ONLY; + + return device; +} + +VideoBootStrap NGAGE_bootstrap = { + NGAGE_VIDEO_DRIVER_NAME, + "N-Gage Video Driver", + NGAGE_CreateDevice, + 0 +}; + +static void NGAGE_DeleteDevice(SDL_VideoDevice *device) +{ + SDL_free(device->internal); + SDL_free(device); +} + +static bool NGAGE_VideoInit(SDL_VideoDevice *device) +{ + SDL_VideoData *phdata = (SDL_VideoData *)device->internal; + + if (!phdata) { + return false; + } + + SDL_zero(phdata->mode); + SDL_zero(phdata->display); + + phdata->mode.w = 176; + phdata->mode.h = 208; + phdata->mode.refresh_rate = 60.0f; + phdata->mode.format = SDL_PIXELFORMAT_XRGB4444; + + phdata->display.name = "N-Gage"; + phdata->display.desktop_mode = phdata->mode; + + if (SDL_AddVideoDisplay(&phdata->display, false) == 0) { + return false; + } + + return true; +} + +static void NGAGE_VideoQuit(SDL_VideoDevice *device) +{ + SDL_VideoData *phdata = (SDL_VideoData *)device->internal; + + if (phdata) { + SDL_zero(phdata->mode); + SDL_zero(phdata->display); + } +} + +static bool NGAGE_GetDisplayBounds(SDL_VideoDevice *device, SDL_VideoDisplay *display, SDL_Rect *rect) +{ + if (!display) { + return false; + } + + rect->x = 0; + rect->y = 0; + rect->w = display->current_mode->w; + rect->h = display->current_mode->h; + + return true; +} + +static bool NGAGE_GetDisplayModes(SDL_VideoDevice *device, SDL_VideoDisplay *display) +{ + SDL_VideoData *phdata = (SDL_VideoData *)device->internal; + SDL_DisplayMode mode; + + SDL_zero(mode); + mode.w = phdata->mode.w; + mode.h = phdata->mode.h; + mode.refresh_rate = phdata->mode.refresh_rate; + mode.format = phdata->mode.format; + + if (!SDL_AddFullscreenDisplayMode(display, &mode)) { + return false; + } + + return true; +} + +#include "../../render/ngage/SDL_render_ngage_c.h" + +static void NGAGE_PumpEvents(SDL_VideoDevice *device) +{ + NGAGE_PumpEventsInternal(); +} + +static bool NGAGE_SuspendScreenSaver(SDL_VideoDevice *device) +{ + NGAGE_SuspendScreenSaverInternal(device->suspend_screensaver); + return true; +} + +#endif // SDL_VIDEO_DRIVER_NGAGE diff --git a/src/video/ngage/SDL_ngagevideo.h b/src/video/ngage/SDL_ngagevideo.h new file mode 100644 index 00000000..3153d6be --- /dev/null +++ b/src/video/ngage/SDL_ngagevideo.h @@ -0,0 +1,39 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_DRIVER_NGAGE + +#include "../SDL_sysvideo.h" + +#ifndef _SDL_ngagevideo_h +#define _SDL_ngagevideo_h + +typedef struct SDL_VideoData +{ + SDL_DisplayMode mode; + SDL_VideoDisplay display; + +} SDL_VideoData; + +#endif // _SDL_ngagevideo_h + +#endif // SDL_VIDEO_DRIVER_NGAGE diff --git a/src/video/openvr/SDL_openvrvideo.c b/src/video/openvr/SDL_openvrvideo.c index 6387c374..f8bf9772 100644 --- a/src/video/openvr/SDL_openvrvideo.c +++ b/src/video/openvr/SDL_openvrvideo.c @@ -22,7 +22,9 @@ #ifdef SDL_VIDEO_DRIVER_OPENVR +#if 0 #define DEBUG_OPENVR +#endif #include "../../events/SDL_mouse_c.h" #include "../../events/SDL_keyboard_c.h" @@ -106,7 +108,7 @@ static BOOL (*ov_wglMakeCurrent)(HDC, HGLRC); #define OPENVR_DEFAULT_WIDTH 1920 #define OPENVR_DEFAULT_HEIGHT 1080 -#define OPENVR_SetupProc(proc) { proc = (void*)SDL_GL_GetProcAddress((#proc)+3); if (!proc) { failed_extension = (#proc)+3; } } +#define OPENVR_SetupProc(proc) { proc = (void *)SDL_GL_GetProcAddress((#proc)+3); if (!proc) { failed_extension = (#proc)+3; } } static bool OPENVR_InitExtensions(SDL_VideoDevice *_this) { @@ -209,7 +211,7 @@ static bool OPENVR_VideoInit(SDL_VideoDevice *_this) } display.internal = (SDL_DisplayData *)data; - display.name = (char*)"OpenVRDisplay"; + display.name = (char *)"OpenVRDisplay"; SDL_AddVideoDisplay(&display, false); return true; @@ -248,7 +250,7 @@ static uint32_t *ImageSDLToOpenVRGL(SDL_Surface * surf, bool bFlipY) int x, y; uint32_t * pxd = SDL_malloc(4 * surf->w * surf->h); for(y = 0; y < h; y++) { - uint32_t * iline = (uint32_t*)&(((uint8_t*)surf->pixels)[y*pitch]); + uint32_t * iline = (uint32_t *)&(((uint8_t *)surf->pixels)[y * pitch]); uint32_t * oline = &pxd[(bFlipY?(h-y-1):y)*w]; for(x = 0; x < w; x++) { @@ -428,7 +430,7 @@ static void OPENVR_VirtualControllerUpdate(void *userdata) xval *= -1.0f; if (a == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || a == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) xval = xval * 2.0f - 1.0f; - //SDL_SetJoystickVirtualAxis(joystick, a, analog_input_action.x*32767); + //SDL_SetJoystickVirtualAxis(joystick, a, analog_input_action.x * 32767); xval *= SDL_JOYSTICK_AXIS_MAX; SDL_SetJoystickVirtualAxis(joystick, a, xval); #ifdef DEBUG_OPENVR @@ -445,7 +447,7 @@ static void OPENVR_VirtualControllerUpdate(void *userdata) static bool OPENVR_SetupJoystickBasedOnLoadedActionManifest(SDL_VideoData * videodata) { SDL_VirtualJoystickDesc desc; - int virtual_index; + SDL_JoystickID virtual_id; EVRInputError e = 0; @@ -537,22 +539,22 @@ static bool OPENVR_SetupJoystickBasedOnLoadedActionManifest(SDL_VideoData * vide desc.RumbleTriggers = OPENVR_VirtualControllerRumbleTriggers; desc.Update = OPENVR_VirtualControllerUpdate; desc.userdata = videodata; - virtual_index = SDL_AttachVirtualJoystick(&desc); + virtual_id = SDL_AttachVirtualJoystick(&desc); - if (virtual_index < 0) { + if (!virtual_id) { + return SDL_SetError("OPENVR: Couldn't attach virtual joystick device: %s", SDL_GetError()); + } + + videodata->virtual_joystick = SDL_OpenJoystick(virtual_id); + if (!videodata->virtual_joystick) { return SDL_SetError("OPENVR: Couldn't open virtual joystick device: %s", SDL_GetError()); - } else { - videodata->virtual_joystick = SDL_OpenJoystick(virtual_index); - if (!videodata->virtual_joystick) { - return SDL_SetError("OPENVR: Couldn't open virtual joystick device: %s", SDL_GetError()); - } } #ifdef DEBUG_OPENVR SDL_Log("Loaded virtual joystick with %d buttons and %d axes", videodata->input_action_handles_buttons_count, videodata->input_action_handles_axes_count); #endif - return false; + return true; } static bool OPENVR_InitializeOverlay(SDL_VideoDevice *_this,SDL_Window *window) @@ -593,7 +595,7 @@ static bool OPENVR_InitializeOverlay(SDL_VideoDevice *_this,SDL_Window *window) return SDL_SetError("Could not create cursor overlay (%d)", result ); } SDL_PropertiesID props = SDL_GetWindowProperties(window); - SDL_SetNumberProperty(props, SDL_PROP_WINDOW_OPENVR_OVERLAY_ID, videodata->overlayID); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_OPENVR_OVERLAY_ID_NUMBER, videodata->overlayID); SDL_free(cursorname); videodata->bHasShownOverlay = false; } @@ -710,7 +712,7 @@ static bool OPENVR_ReleaseFrame(SDL_VideoDevice *_this) if (videodata->overlaytexture != 0 && videodata->targh == videodata->last_targh && videodata->targw == videodata->last_targw) { - // Only submit frames to OpenVR if the textu re exists. + // Only submit frames to OpenVR if the texture exists. struct Texture_t tex; // Setup a Texture_t object to send in the texture. @@ -1037,7 +1039,7 @@ static SDL_GLContext OVR_EGL_CreateContext(SDL_VideoDevice *_this, SDL_Window * ov_glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions); for(int i = 0; i < numExtensions; i++) { - const char * ccc = (const char*)ov_glGetStringi(GL_EXTENSIONS, i); + const char * ccc = (const char *)ov_glGetStringi(GL_EXTENSIONS, i); if (SDL_strcmp(ccc, "GL_KHR_debug") == 0) { #ifdef DEBUG_OPENVR SDL_Log("Found renderdoc debug extension."); @@ -1325,7 +1327,7 @@ static bool OPENVR_ShowCursor(SDL_Cursor * cursor) hotspot.v[0] = (float)ovrc->hot_x / (float)ovrc->w; hotspot.v[1] = (float)ovrc->hot_y / (float)ovrc->h; - texture.handle = (void*)(intptr_t)(ovrc->texture_id_handle); + texture.handle = (void *)(intptr_t)(ovrc->texture_id_handle); texture.eType = ETextureType_TextureType_OpenGL; texture.eColorSpace = EColorSpace_ColorSpace_Auto; @@ -1393,7 +1395,7 @@ static bool OPENVR_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window * window, SD SDL_free(pixels); ov_glBindTexture(GL_TEXTURE_2D, 0); - texture.handle = (void*)(intptr_t)(texture_id_handle); + texture.handle = (void *)(intptr_t)(texture_id_handle); texture.eType = ETextureType_TextureType_OpenGL; texture.eColorSpace = EColorSpace_ColorSpace_Auto; diff --git a/src/video/psp/SDL_pspvideo.c b/src/video/psp/SDL_pspvideo.c index 2458235e..afdb6bba 100644 --- a/src/video/psp/SDL_pspvideo.c +++ b/src/video/psp/SDL_pspvideo.c @@ -118,6 +118,8 @@ static SDL_VideoDevice *PSP_Create(void) device->PumpEvents = PSP_PumpEvents; + device->device_caps = VIDEO_DEVICE_CAPS_FULLSCREEN_ONLY; + return device; } diff --git a/src/video/riscos/SDL_riscosmodes.c b/src/video/riscos/SDL_riscosmodes.c index d4e9a534..05da0463 100644 --- a/src/video/riscos/SDL_riscosmodes.c +++ b/src/video/riscos/SDL_riscosmodes.c @@ -302,7 +302,7 @@ bool RISCOS_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SD } // Update cursor visibility, since it may have been disabled by the mode change. - SDL_SetCursor(NULL); + SDL_RedrawCursor(); return true; } diff --git a/src/video/stb_image.h b/src/video/stb_image.h index 72e5f4e7..6c735a81 100644 --- a/src/video/stb_image.h +++ b/src/video/stb_image.h @@ -100,7 +100,7 @@ RECENT REVISION HISTORY: Bug & warning fixes Marc LeBlanc David Woo Guillaume George Martins Mozeiko Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski - Phil Jordan Dave Moore Roy Eltham + Phil Jordan Henner Zeller Dave Moore Roy Eltham Hayaki Saito Nathan Reed Won Chun Luke Graham Johan Duparc Nick Verigakis the Horde3D community Thomas Ruf Ronny Chevalier github:rlyeh @@ -1914,6 +1914,7 @@ static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int r int i,j; unsigned char *good; + if (data == NULL) return data; if (req_comp == img_n) return data; STBI_ASSERT(req_comp >= 1 && req_comp <= 4); diff --git a/src/video/uikit/SDL_uikitappdelegate.m b/src/video/uikit/SDL_uikitappdelegate.m index 6a37e511..3c1bb373 100644 --- a/src/video/uikit/SDL_uikitappdelegate.m +++ b/src/video/uikit/SDL_uikitappdelegate.m @@ -39,7 +39,7 @@ static int forward_argc; static char **forward_argv; static int exit_status; -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 i; diff --git a/src/video/uikit/SDL_uikitmessagebox.m b/src/video/uikit/SDL_uikitmessagebox.m index a57b3f76..b96f5fde 100644 --- a/src/video/uikit/SDL_uikitmessagebox.m +++ b/src/video/uikit/SDL_uikitmessagebox.m @@ -124,31 +124,30 @@ static BOOL UIKit_ShowMessageBoxAlertController(const SDL_MessageBoxData *messag return YES; } -static void UIKit_ShowMessageBoxImpl(const SDL_MessageBoxData *messageboxdata, int *buttonID, int *result) +typedef struct UIKit_ShowMessageBoxData +{ + const SDL_MessageBoxData *messageboxdata; + int *buttonID; + bool result; +} UIKit_ShowMessageBoxData; + +static void SDLCALL UIKit_ShowMessageBoxMainThreadCallback(void *userdata) { @autoreleasepool { - if (UIKit_ShowMessageBoxAlertController(messageboxdata, buttonID)) { - *result = true; - } else { - *result = SDL_SetError("Could not show message box."); - } + UIKit_ShowMessageBoxData *data = (UIKit_ShowMessageBoxData *) userdata; + data->result = UIKit_ShowMessageBoxAlertController(data->messageboxdata, data->buttonID); } } bool UIKit_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) { - @autoreleasepool { - __block int result = true; - - if ([NSThread isMainThread]) { - UIKit_ShowMessageBoxImpl(messageboxdata, buttonID, &result); - } else { - dispatch_sync(dispatch_get_main_queue(), ^{ - UIKit_ShowMessageBoxImpl(messageboxdata, buttonID, &result); - }); - } - return result; + UIKit_ShowMessageBoxData data = { messageboxdata, buttonID, false }; + if (!SDL_RunOnMainThread(UIKit_ShowMessageBoxMainThreadCallback, &data, true)) { + return false; + } else if (!data.result) { + return SDL_SetError("Could not show message box."); } + return true; } #endif // SDL_VIDEO_DRIVER_UIKIT diff --git a/src/video/uikit/SDL_uikitview.m b/src/video/uikit/SDL_uikitview.m index ba3b09bf..ed649b1f 100644 --- a/src/video/uikit/SDL_uikitview.m +++ b/src/video/uikit/SDL_uikitview.m @@ -240,7 +240,7 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick; int i; SDL_MouseButtonFlags buttons = SDL_GetMouseState(NULL, NULL); - for (i = 0; i < MAX_MOUSE_BUTTONS; ++i) { + for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) { if (buttons & SDL_BUTTON_MASK(i)) { SDL_SendMouseButton(UIKit_GetEventTimestamp([touch timestamp]), sdlwindow, SDL_GLOBAL_MOUSE_ID, (Uint8)i, false); } diff --git a/src/video/uikit/SDL_uikitvulkan.h b/src/video/uikit/SDL_uikitvulkan.h index 6957670b..4687c937 100644 --- a/src/video/uikit/SDL_uikitvulkan.h +++ b/src/video/uikit/SDL_uikitvulkan.h @@ -36,7 +36,7 @@ extern bool UIKit_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); extern void UIKit_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -extern char const* const* UIKit_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); +extern char const * const *UIKit_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); extern bool UIKit_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, diff --git a/src/video/uikit/SDL_uikitvulkan.m b/src/video/uikit/SDL_uikitvulkan.m index 332593b7..860b2981 100644 --- a/src/video/uikit/SDL_uikitvulkan.m +++ b/src/video/uikit/SDL_uikitvulkan.m @@ -167,8 +167,7 @@ void UIKit_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) } } -char const* const* UIKit_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - Uint32 *count) +char const * const *UIKit_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count) { static const char *const extensionsForUIKit[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_EXT_METAL_SURFACE_EXTENSION_NAME diff --git a/src/video/vita/SDL_vitavideo.c b/src/video/vita/SDL_vitavideo.c index a4e6ca3f..23c4e0bb 100644 --- a/src/video/vita/SDL_vitavideo.c +++ b/src/video/vita/SDL_vitavideo.c @@ -147,6 +147,8 @@ static SDL_VideoDevice *VITA_Create(void) device->PumpEvents = VITA_PumpEvents; + device->device_caps = VIDEO_DEVICE_CAPS_FULLSCREEN_ONLY; + return device; } diff --git a/src/video/vita/SDL_vitavideo.h b/src/video/vita/SDL_vitavideo.h index 7b9a78fb..c8f7ade0 100644 --- a/src/video/vita/SDL_vitavideo.h +++ b/src/video/vita/SDL_vitavideo.h @@ -77,8 +77,6 @@ extern SDL_Window *Vita_Window; // Display and window functions extern bool VITA_VideoInit(SDL_VideoDevice *_this); extern void VITA_VideoQuit(SDL_VideoDevice *_this); -extern bool VITA_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display); -extern bool VITA_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode); extern bool VITA_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); extern void VITA_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); extern bool VITA_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); diff --git a/src/video/vivante/SDL_vivantevulkan.c b/src/video/vivante/SDL_vivantevulkan.c index a021a99e..30629eb9 100644 --- a/src/video/vivante/SDL_vivantevulkan.c +++ b/src/video/vivante/SDL_vivantevulkan.c @@ -117,8 +117,7 @@ void VIVANTE_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) } } -char const* const* VIVANTE_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - Uint32 *count) +char const * const *VIVANTE_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count) { static const char *const extensionsForVivante[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_DISPLAY_EXTENSION_NAME diff --git a/src/video/vivante/SDL_vivantevulkan.h b/src/video/vivante/SDL_vivantevulkan.h index 49f14d3a..d2b62dd1 100644 --- a/src/video/vivante/SDL_vivantevulkan.h +++ b/src/video/vivante/SDL_vivantevulkan.h @@ -35,7 +35,7 @@ extern bool VIVANTE_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); extern void VIVANTE_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -extern char const* const* VIVANTE_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); +extern char const * const *VIVANTE_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); extern bool VIVANTE_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, diff --git a/src/video/wayland/SDL_waylanddatamanager.c b/src/video/wayland/SDL_waylanddatamanager.c index d5e39a77..3f4c0a28 100644 --- a/src/video/wayland/SDL_waylanddatamanager.c +++ b/src/video/wayland/SDL_waylanddatamanager.c @@ -148,8 +148,8 @@ static SDL_MimeDataList *mime_data_list_find(struct wl_list *list, } static bool mime_data_list_add(struct wl_list *list, - const char *mime_type, - const void *buffer, size_t length) + const char *mime_type, + const void *buffer, size_t length) { bool result = true; size_t mime_type_length = 0; @@ -231,7 +231,10 @@ ssize_t Wayland_data_source_send(SDL_WaylandDataSource *source, const char *mime const void *data = NULL; size_t length = 0; - if (source->callback) { + if (SDL_strcmp(mime_type, SDL_DATA_ORIGIN_MIME) == 0) { + data = source->data_device->id_str; + length = SDL_strlen(source->data_device->id_str); + } else if (source->callback) { data = source->callback(source->userdata.data, mime_type, &length); } @@ -263,8 +266,8 @@ void Wayland_data_source_set_callback(SDL_WaylandDataSource *source, } void Wayland_primary_selection_source_set_callback(SDL_WaylandPrimarySelectionSource *source, - SDL_ClipboardDataCallback callback, - void *userdata) + SDL_ClipboardDataCallback callback, + void *userdata) { if (source) { source->callback = callback; @@ -352,6 +355,113 @@ void Wayland_primary_selection_source_destroy(SDL_WaylandPrimarySelectionSource } } +static void offer_source_done_handler(void *data, struct wl_callback *callback, uint32_t callback_data) +{ + if (!callback) { + return; + } + + SDL_WaylandDataOffer *offer = data; + char *id = NULL; + size_t length = 0; + + wl_callback_destroy(offer->callback); + offer->callback = NULL; + + while (read_pipe(offer->read_fd, (void **)&id, &length) > 0) { + } + close(offer->read_fd); + offer->read_fd = -1; + + if (id) { + const bool source_is_external = SDL_strncmp(offer->data_device->id_str, id, length) != 0; + SDL_free(id); + if (source_is_external) { + Wayland_data_offer_notify_from_mimes(offer, false); + } + } +} + +static struct wl_callback_listener offer_source_listener = { + offer_source_done_handler +}; + +static void Wayland_data_offer_check_source(SDL_WaylandDataOffer *offer, const char *mime_type) +{ + SDL_WaylandDataDevice *data_device = NULL; + int pipefd[2]; + + if (!offer) { + SDL_SetError("Invalid data offer"); + } + data_device = offer->data_device; + if (!data_device) { + SDL_SetError("Data device not initialized"); + } else if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) == -1) { + SDL_SetError("Could not read pipe"); + } else { + if (offer->callback) { + wl_callback_destroy(offer->callback); + } + if (offer->read_fd >= 0) { + close(offer->read_fd); + } + + offer->read_fd = pipefd[0]; + + wl_data_offer_receive(offer->offer, mime_type, pipefd[1]); + close(pipefd[1]); + + offer->callback = wl_display_sync(offer->data_device->seat->display->display); + wl_callback_add_listener(offer->callback, &offer_source_listener, offer); + + WAYLAND_wl_display_flush(data_device->seat->display->display); + } +} + +void Wayland_data_offer_notify_from_mimes(SDL_WaylandDataOffer *offer, bool check_origin) +{ + int nformats = 0; + char **new_mime_types = NULL; + if (offer) { + size_t alloc_size = 0; + + // Do a first pass to compute allocation size. + SDL_MimeDataList *item = NULL; + wl_list_for_each(item, &offer->mimes, link) { + // If origin metadata is found, queue a check and wait for confirmation that this offer isn't recursive. + if (check_origin && SDL_strcmp(item->mime_type, SDL_DATA_ORIGIN_MIME) == 0) { + Wayland_data_offer_check_source(offer, item->mime_type); + return; + } + + ++nformats; + alloc_size += SDL_strlen(item->mime_type) + 1; + } + + alloc_size += (nformats + 1) * sizeof(char *); + + new_mime_types = SDL_AllocateTemporaryMemory(alloc_size); + if (!new_mime_types) { + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "unable to allocate new_mime_types"); + return; + } + + // Second pass to fill. + char *strPtr = (char *)(new_mime_types + nformats + 1); + item = NULL; + int i = 0; + wl_list_for_each(item, &offer->mimes, link) { + new_mime_types[i] = strPtr; + strPtr = stpcpy(strPtr, item->mime_type) + 1; + i++; + } + new_mime_types[nformats] = NULL; + } + + SDL_SendClipboardUpdate(false, new_mime_types, nformats); +} + void *Wayland_data_offer_receive(SDL_WaylandDataOffer *offer, const char *mime_type, size_t *length) { @@ -433,7 +543,7 @@ bool Wayland_primary_selection_offer_add_mime(SDL_WaylandPrimarySelectionOffer * } bool Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer, - const char *mime_type) + const char *mime_type) { bool found = false; @@ -444,7 +554,7 @@ bool Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer, } bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOffer *offer, - const char *mime_type) + const char *mime_type) { bool found = false; @@ -457,6 +567,12 @@ bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOffer * void Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer) { if (offer) { + if (offer->callback) { + wl_callback_destroy(offer->callback); + } + if (offer->read_fd >= 0) { + close(offer->read_fd); + } wl_data_offer_destroy(offer->offer); mime_data_list_free(&offer->mimes); SDL_free(offer); @@ -522,6 +638,9 @@ bool Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device, mime_type); } + // Advertise the data origin MIME + wl_data_source_offer(source->source, SDL_DATA_ORIGIN_MIME); + if (index == 0) { Wayland_data_device_clear_selection(data_device); result = SDL_SetError("No mime data"); diff --git a/src/video/wayland/SDL_waylanddatamanager.h b/src/video/wayland/SDL_waylanddatamanager.h index 276620f9..bdde6a23 100644 --- a/src/video/wayland/SDL_waylanddatamanager.h +++ b/src/video/wayland/SDL_waylanddatamanager.h @@ -31,6 +31,10 @@ #define TEXT_MIME "text/plain;charset=utf-8" #define FILE_MIME "text/uri-list" #define FILE_PORTAL_MIME "application/vnd.portal.filetransfer" +#define SDL_DATA_ORIGIN_MIME "application/x-sdl3-source-id" + +typedef struct SDL_WaylandDataDevice SDL_WaylandDataDevice; +typedef struct SDL_WaylandPrimarySelectionDevice SDL_WaylandPrimarySelectionDevice; typedef struct { @@ -49,7 +53,7 @@ typedef struct SDL_WaylandUserdata typedef struct { struct wl_data_source *source; - void *data_device; + SDL_WaylandDataDevice *data_device; SDL_ClipboardDataCallback callback; SDL_WaylandUserdata userdata; } SDL_WaylandDataSource; @@ -57,8 +61,8 @@ typedef struct typedef struct { struct zwp_primary_selection_source_v1 *source; - void *data_device; - void *primary_selection_device; + SDL_WaylandDataDevice *data_device; + SDL_WaylandPrimarySelectionDevice *primary_selection_device; SDL_ClipboardDataCallback callback; SDL_WaylandUserdata userdata; } SDL_WaylandPrimarySelectionSource; @@ -67,20 +71,25 @@ typedef struct { struct wl_data_offer *offer; struct wl_list mimes; - void *data_device; + SDL_WaylandDataDevice *data_device; + + // Callback data for queued receive. + struct wl_callback *callback; + int read_fd; } SDL_WaylandDataOffer; typedef struct { struct zwp_primary_selection_offer_v1 *offer; struct wl_list mimes; - void *primary_selection_device; + SDL_WaylandPrimarySelectionDevice *primary_selection_device; } SDL_WaylandPrimarySelectionOffer; -typedef struct +struct SDL_WaylandDataDevice { struct wl_data_device *data_device; struct SDL_WaylandSeat *seat; + char *id_str; // Drag and Drop uint32_t drag_serial; @@ -93,9 +102,9 @@ typedef struct // Clipboard and Primary Selection uint32_t selection_serial; SDL_WaylandDataSource *selection_source; -} SDL_WaylandDataDevice; +}; -typedef struct +struct SDL_WaylandPrimarySelectionDevice { struct zwp_primary_selection_device_v1 *primary_selection_device; struct SDL_WaylandSeat *seat; @@ -103,7 +112,7 @@ typedef struct uint32_t selection_serial; SDL_WaylandPrimarySelectionSource *selection_source; SDL_WaylandPrimarySelectionOffer *selection_offer; -} SDL_WaylandPrimarySelectionDevice; +}; // Wayland Data Source / Primary Selection Source - (Sending) extern SDL_WaylandDataSource *Wayland_data_source_create(SDL_VideoDevice *_this); @@ -136,13 +145,15 @@ extern void *Wayland_primary_selection_offer_receive(SDL_WaylandPrimarySelection const char *mime_type, size_t *length); extern bool Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer, - const char *mime_type); + const char *mime_type); +extern void Wayland_data_offer_notify_from_mimes(SDL_WaylandDataOffer *offer, + bool check_origin); extern bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOffer *offer, - const char *mime_type); + const char *mime_type); extern bool Wayland_data_offer_add_mime(SDL_WaylandDataOffer *offer, - const char *mime_type); + const char *mime_type); extern bool Wayland_primary_selection_offer_add_mime(SDL_WaylandPrimarySelectionOffer *offer, - const char *mime_type); + const char *mime_type); extern void Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer); extern void Wayland_primary_selection_offer_destroy(SDL_WaylandPrimarySelectionOffer *offer); diff --git a/src/video/wayland/SDL_waylanddyn.h b/src/video/wayland/SDL_waylanddyn.h index fd85a7c2..8a2c8aba 100644 --- a/src/video/wayland/SDL_waylanddyn.h +++ b/src/video/wayland/SDL_waylanddyn.h @@ -59,6 +59,11 @@ enum libdecor_window_state; (WAYLAND_VERSION_MAJOR == x && WAYLAND_VERSION_MINOR > y) || \ (WAYLAND_VERSION_MAJOR == x && WAYLAND_VERSION_MINOR == y && WAYLAND_VERSION_MICRO >= z)) +#define SDL_XKBCOMMON_CHECK_VERSION(x, y, z) \ + (SDL_XKBCOMMON_VERSION_MAJOR > x || \ + (SDL_XKBCOMMON_VERSION_MAJOR == x && SDL_XKBCOMMON_VERSION_MINOR > y) || \ + (SDL_XKBCOMMON_VERSION_MAJOR == x && SDL_XKBCOMMON_VERSION_MINOR == y && SDL_XKBCOMMON_VERSION_PATCH >= z)) + #ifdef HAVE_LIBDECOR_H #define SDL_LIBDECOR_CHECK_VERSION(x, y, z) \ (SDL_LIBDECOR_VERSION_MAJOR > x || \ diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 00b77dd5..8d591481 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -90,7 +90,7 @@ // Scoped function declarations static void Wayland_SeatUpdateKeyboardGrab(SDL_WaylandSeat *seat); -struct SDL_WaylandTouchPoint +typedef struct { SDL_TouchID id; wl_fixed_t fx; @@ -98,11 +98,11 @@ struct SDL_WaylandTouchPoint struct wl_surface *surface; struct wl_list link; -}; +} SDL_WaylandTouchPoint; static void Wayland_SeatAddTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface *surface) { - struct SDL_WaylandTouchPoint *tp = SDL_malloc(sizeof(struct SDL_WaylandTouchPoint)); + SDL_WaylandTouchPoint *tp = SDL_malloc(sizeof(SDL_WaylandTouchPoint)); SDL_zerop(tp); tp->id = id; @@ -113,9 +113,37 @@ static void Wayland_SeatAddTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed WAYLAND_wl_list_insert(&seat->touch.points, &tp->link); } +static void Wayland_SeatCancelTouch(SDL_WaylandSeat *seat, SDL_WaylandTouchPoint *tp) +{ + if (tp->surface) { + SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(tp->surface); + + if (window_data) { + const float x = (float)(wl_fixed_to_double(tp->fx) / window_data->current.logical_width); + const float y = (float)(wl_fixed_to_double(tp->fy) / window_data->current.logical_height); + + SDL_SendTouch(0, (SDL_TouchID)(uintptr_t)seat->touch.wl_touch, + (SDL_FingerID)(tp->id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_CANCELED, x, y, 0.0f); + + --window_data->active_touch_count; + + /* If the window currently has mouse focus and has no currently active keyboards, pointers, + * or touch events, then consider mouse focus to be lost. + */ + if (SDL_GetMouseFocus() == window_data->sdlwindow && !window_data->keyboard_focus_count && + !window_data->pointer_focus_count && !window_data->active_touch_count) { + SDL_SetMouseFocus(NULL); + } + } + } + + WAYLAND_wl_list_remove(&tp->link); + SDL_free(tp); +} + static void Wayland_SeatUpdateTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface **surface) { - struct SDL_WaylandTouchPoint *tp; + SDL_WaylandTouchPoint *tp; wl_list_for_each (tp, &seat->touch.points, link) { if (tp->id == id) { @@ -131,7 +159,7 @@ static void Wayland_SeatUpdateTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fi static void Wayland_SeatRemoveTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed_t *fx, wl_fixed_t *fy, struct wl_surface **surface) { - struct SDL_WaylandTouchPoint *tp; + SDL_WaylandTouchPoint *tp; wl_list_for_each (tp, &seat->touch.points, link) { if (tp->id == id) { @@ -152,23 +180,6 @@ static void Wayland_SeatRemoveTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fi } } -static bool Wayland_SurfaceHasActiveTouches(SDL_VideoData *display, struct wl_surface *surface) -{ - struct SDL_WaylandTouchPoint *tp; - SDL_WaylandSeat *seat; - - // Check all seats for active touches on the surface. - wl_list_for_each (seat, &display->seat_list, link) { - wl_list_for_each (tp, &seat->touch.points, link) { - if (tp->surface == surface) { - return true; - } - } - } - - return false; -} - static void Wayland_GetScaledMouseRect(SDL_Window *window, SDL_Rect *scaled_rect) { SDL_WindowData *window_data = window->internal; @@ -179,18 +190,11 @@ static void Wayland_GetScaledMouseRect(SDL_Window *window, SDL_Rect *scaled_rect scaled_rect->h = (int)SDL_ceil(window->mouse_rect.h / window_data->pointer_scale.y); } -static Uint64 Wayland_GetEventTimestamp(Uint64 nsTimestamp) +static Uint64 Wayland_AdjustEventTimestampBase(Uint64 nsTimestamp) { - static Uint64 last; - static Uint64 timestamp_offset; + static Uint64 timestamp_offset = 0; const Uint64 now = SDL_GetTicksNS(); - if (nsTimestamp < last) { - // 32-bit timer rollover, bump the offset - timestamp_offset += SDL_MS_TO_NS(0x100000000LLU); - } - last = nsTimestamp; - if (!timestamp_offset) { timestamp_offset = (now - nsTimestamp); } @@ -204,6 +208,45 @@ static Uint64 Wayland_GetEventTimestamp(Uint64 nsTimestamp) return nsTimestamp; } +/* This should only be called with 32-bit millisecond timestamps received in Wayland events! + * No synthetic or high-res timestamps, as they can corrupt the rollover offset! + */ +static Uint64 Wayland_EventTimestampMSToNS(Uint32 wl_timestamp_ms) +{ + static Uint64 timestamp_offset = 0; + static Uint32 last = 0; + + // Handle 32-bit timer rollover. + if (wl_timestamp_ms < last) { + timestamp_offset += SDL_MS_TO_NS(SDL_UINT64_C(0x100000000)); + } + last = wl_timestamp_ms; + + return SDL_MS_TO_NS(wl_timestamp_ms) + timestamp_offset; +} + +/* Even if high-res timestamps are available, the millisecond timestamps are still processed + * to accumulate the rollover offset if needed later. + */ + +static Uint64 Wayland_GetKeyboardTimestamp(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms) +{ + const Uint64 adjustedTimestampNS = Wayland_EventTimestampMSToNS(wl_timestamp_ms); + return Wayland_AdjustEventTimestampBase(seat->keyboard.timestamps ? seat->keyboard.highres_timestamp_ns : adjustedTimestampNS); +} + +static Uint64 Wayland_GetPointerTimestamp(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms) +{ + const Uint64 adjustedTimestampMS = Wayland_EventTimestampMSToNS(wl_timestamp_ms); + return Wayland_AdjustEventTimestampBase(seat->pointer.timestamps ? seat->pointer.highres_timestamp_ns : adjustedTimestampMS); +} + +Uint64 Wayland_GetTouchTimestamp(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms) +{ + const Uint64 adjustedTimestampMS = Wayland_EventTimestampMSToNS(wl_timestamp_ms); + return Wayland_AdjustEventTimestampBase(seat->touch.timestamps ? seat->touch.highres_timestamp_ns : adjustedTimestampMS); +} + static void input_timestamp_listener(void *data, struct zwp_input_timestamps_v1 *zwp_input_timestamps_v1, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { @@ -214,42 +257,6 @@ static const struct zwp_input_timestamps_v1_listener timestamp_listener = { input_timestamp_listener }; -static Uint64 Wayland_GetKeyboardTimestamp(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms) -{ - if (wl_timestamp_ms) { - return Wayland_GetEventTimestamp(seat->keyboard.highres_timestamp_ns ? seat->keyboard.highres_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms)); - } - - return 0; -} - -static Uint64 Wayland_GetKeyboardTimestampRaw(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms) -{ - if (wl_timestamp_ms) { - return seat->keyboard.highres_timestamp_ns ? seat->keyboard.highres_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms); - } - - return 0; -} - -static Uint64 Wayland_GetPointerTimestamp(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms) -{ - if (wl_timestamp_ms) { - return Wayland_GetEventTimestamp(seat->pointer.highres_timestamp_ns ? seat->pointer.highres_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms)); - } - - return 0; -} - -Uint64 Wayland_GetTouchTimestamp(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms) -{ - if (wl_timestamp_ms) { - return Wayland_GetEventTimestamp(seat->touch.highres_timestamp_ns ? seat->touch.highres_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms)); - } - - return 0; -} - static void Wayland_SeatRegisterInputTimestampListeners(SDL_WaylandSeat *seat) { if (seat->display->input_timestamps_manager) { @@ -301,10 +308,11 @@ void Wayland_DisplayInitCursorShapeManager(SDL_VideoData *display) static bool keyboard_repeat_handle(SDL_WaylandKeyboardRepeat *repeat_info, Uint64 elapsed) { bool ret = false; + while (elapsed >= repeat_info->next_repeat_ns) { if (repeat_info->scancode != SDL_SCANCODE_UNKNOWN) { - const Uint64 timestamp = repeat_info->wl_press_time_ns + repeat_info->next_repeat_ns; - SDL_SendKeyboardKeyIgnoreModifiers(Wayland_GetEventTimestamp(timestamp), repeat_info->keyboard_id, repeat_info->key, repeat_info->scancode, true); + const Uint64 timestamp = repeat_info->base_time_ns + repeat_info->next_repeat_ns; + SDL_SendKeyboardKeyIgnoreModifiers(Wayland_AdjustEventTimestampBase(timestamp), repeat_info->keyboard_id, repeat_info->key, repeat_info->scancode, true); } if (repeat_info->text[0]) { SDL_SendKeyboardText(repeat_info->text); @@ -323,8 +331,8 @@ static void keyboard_repeat_clear(SDL_WaylandKeyboardRepeat *repeat_info) repeat_info->is_key_down = false; } -static void keyboard_repeat_set(SDL_WaylandKeyboardRepeat *repeat_info, Uint32 keyboard_id, uint32_t key, Uint64 wl_press_time_ns, - uint32_t scancode, bool has_text, char text[8]) +static void keyboard_repeat_set(SDL_WaylandKeyboardRepeat *repeat_info, Uint32 keyboard_id, uint32_t key, Uint32 wl_press_time_ms, + Uint64 base_time_ns, uint32_t scancode, bool has_text, char text[8]) { if (!repeat_info->is_initialized || !repeat_info->repeat_rate) { return; @@ -332,12 +340,13 @@ static void keyboard_repeat_set(SDL_WaylandKeyboardRepeat *repeat_info, Uint32 k repeat_info->is_key_down = true; repeat_info->keyboard_id = keyboard_id; repeat_info->key = key; - repeat_info->wl_press_time_ns = wl_press_time_ns; + repeat_info->wl_press_time_ms = wl_press_time_ms; + repeat_info->base_time_ns = base_time_ns; repeat_info->sdl_press_time_ns = SDL_GetTicksNS(); repeat_info->next_repeat_ns = SDL_MS_TO_NS(repeat_info->repeat_delay_ms); repeat_info->scancode = scancode; if (has_text) { - SDL_copyp(repeat_info->text, text); + SDL_memcpy(repeat_info->text, text, sizeof(repeat_info->text)); } else { repeat_info->text[0] = '\0'; } @@ -431,7 +440,7 @@ int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS) // If key repeat is active, we'll need to cap our maximum wait time to handle repeats wl_list_for_each (seat, &d->seat_list, link) { if (keyboard_repeat_is_set(&seat->keyboard.repeat)) { - if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap()) { + if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap(true)) { SDL_SetKeymap(seat->keyboard.sdl_keymap, true); SDL_SetModState(seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers); } @@ -470,7 +479,7 @@ int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS) // If key repeat is active, we might have woken up to generate a key event if (key_repeat_active) { wl_list_for_each (seat, &d->seat_list, link) { - if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap()) { + if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap(true)) { SDL_SetKeymap(seat->keyboard.sdl_keymap, true); SDL_SetModState(seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers); } @@ -541,7 +550,7 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this) wl_list_for_each (seat, &d->seat_list, link) { if (keyboard_repeat_is_set(&seat->keyboard.repeat)) { - if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap()) { + if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap(true)) { SDL_SetKeymap(seat->keyboard.sdl_keymap, true); SDL_SetModState(seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers); } @@ -570,17 +579,15 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this) } } -static void pointer_handle_motion(void *data, struct wl_pointer *pointer, - uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w) +static void pointer_handle_motion_common(SDL_WaylandSeat *seat, Uint64 nsTimestamp, wl_fixed_t sx_w, wl_fixed_t sy_w) { - SDL_WaylandSeat *seat = data; SDL_WindowData *window_data = seat->pointer.focus; SDL_Window *window = window_data ? window_data->sdlwindow : NULL; if (window_data) { const float sx = (float)(wl_fixed_to_double(sx_w) * window_data->pointer_scale.x); const float sy = (float)(wl_fixed_to_double(sy_w) * window_data->pointer_scale.y); - SDL_SendMouseMotion(Wayland_GetPointerTimestamp(seat, time), window_data->sdlwindow, seat->pointer.sdl_id, false, sx, sy); + SDL_SendMouseMotion(nsTimestamp, window_data->sdlwindow, seat->pointer.sdl_id, false, sx, sy); seat->pointer.last_motion.x = (int)SDL_floorf(sx); seat->pointer.last_motion.y = (int)SDL_floorf(sy); @@ -674,6 +681,13 @@ static void pointer_handle_motion(void *data, struct wl_pointer *pointer, } } +static void pointer_handle_motion(void *data, struct wl_pointer *pointer, + uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w) +{ + SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data; + pointer_handle_motion_common(seat, Wayland_GetPointerTimestamp(seat, time), sx_w, sy_w); +} + static void pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx_w, wl_fixed_t sy_w) @@ -702,7 +716,7 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer, * FIXME: This causes a movement event with an anomalous timestamp when * the cursor enters the window. */ - pointer_handle_motion(data, pointer, 0, sx_w, sy_w); + pointer_handle_motion_common(seat, 0, sx_w, sy_w); // Update the pointer grab state. Wayland_SeatUpdatePointerGrab(seat); @@ -738,18 +752,18 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer, SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data; seat->pointer.focus = NULL; seat->pointer.buttons_pressed = 0; - SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_LEFT, false); - SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_RIGHT, false); - SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_MIDDLE, false); - SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_X1, false); - SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_X2, false); + SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_LEFT, false); + SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_RIGHT, false); + SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_MIDDLE, false); + SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_X1, false); + SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_X2, false); /* A pointer leave event may be emitted if the compositor hides the pointer in response to receiving a touch event. * Don't relinquish focus if the surface has active touches, as the compositor is just transitioning from mouse to touch mode. */ SDL_Window *mouse_focus = SDL_GetMouseFocus(); const bool had_focus = mouse_focus && window->sdlwindow == mouse_focus; - if (!--window->pointer_focus_count && had_focus && !Wayland_SurfaceHasActiveTouches(seat->display, surface)) { + if (!--window->pointer_focus_count && had_focus && !window->active_touch_count) { SDL_SetMouseFocus(NULL); } @@ -841,11 +855,10 @@ static bool Wayland_ProcessHitTest(SDL_WaylandSeat *seat, Uint32 serial) } static void pointer_handle_button_common(SDL_WaylandSeat *seat, uint32_t serial, - uint32_t time, uint32_t button, uint32_t state_w) + Uint64 nsTimestamp, uint32_t button, uint32_t state_w) { SDL_WindowData *window = seat->pointer.focus; enum wl_pointer_button_state state = state_w; - Uint64 timestamp = Wayland_GetPointerTimestamp(seat, time); Uint8 sdl_button; const bool down = (state != 0); @@ -900,7 +913,7 @@ static void pointer_handle_button_common(SDL_WaylandSeat *seat, uint32_t serial, * * The mouse is not captured in relative mode. */ - if (!seat->display->relative_mode_enabled || !Wayland_SeatHasRelativePointerFocus(seat)) { + if (!seat->pointer.relative_pointer) { if (seat->pointer.buttons_pressed != 0) { window->sdlwindow->flags |= SDL_WINDOW_MOUSE_CAPTURE; } else { @@ -909,7 +922,7 @@ static void pointer_handle_button_common(SDL_WaylandSeat *seat, uint32_t serial, } if (!ignore_click) { - SDL_SendMouseButton(timestamp, window->sdlwindow, seat->pointer.sdl_id, sdl_button, down); + SDL_SendMouseButton(nsTimestamp, window->sdlwindow, seat->pointer.sdl_id, sdl_button, down); } } } @@ -918,15 +931,13 @@ static void pointer_handle_button(void *data, struct wl_pointer *pointer, uint32 uint32_t time, uint32_t button, uint32_t state_w) { SDL_WaylandSeat *seat = data; - - pointer_handle_button_common(seat, serial, time, button, state_w); + pointer_handle_button_common(seat, serial, Wayland_GetPointerTimestamp(seat, time), button, state_w); } static void pointer_handle_axis_common_v1(SDL_WaylandSeat *seat, - uint32_t time, uint32_t axis, wl_fixed_t value) + Uint64 nsTimestamp, uint32_t axis, wl_fixed_t value) { SDL_WindowData *window = seat->pointer.focus; - const Uint64 timestamp = Wayland_GetPointerTimestamp(seat, time); const enum wl_pointer_axis a = axis; if (seat->pointer.focus) { @@ -948,7 +959,7 @@ static void pointer_handle_axis_common_v1(SDL_WaylandSeat *seat, x /= WAYLAND_WHEEL_AXIS_UNIT; y /= WAYLAND_WHEEL_AXIS_UNIT; - SDL_SendMouseWheel(timestamp, window->sdlwindow, seat->pointer.sdl_id, x, y, SDL_MOUSEWHEEL_NORMAL); + SDL_SendMouseWheel(nsTimestamp, window->sdlwindow, seat->pointer.sdl_id, x, y, SDL_MOUSEWHEEL_NORMAL); } } @@ -1029,12 +1040,13 @@ static void pointer_handle_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { SDL_WaylandSeat *seat = data; + const Uint64 nsTimestamp = Wayland_GetPointerTimestamp(seat, time); if (wl_seat_get_version(seat->wl_seat) >= WL_POINTER_FRAME_SINCE_VERSION) { - seat->pointer.current_axis_info.timestamp_ns = Wayland_GetPointerTimestamp(seat, time); + seat->pointer.current_axis_info.timestamp_ns = nsTimestamp; pointer_handle_axis_common(seat, AXIS_EVENT_CONTINUOUS, axis, value); } else { - pointer_handle_axis_common_v1(seat, time, axis, value); + pointer_handle_axis_common_v1(seat, nsTimestamp, axis, value); } } @@ -1153,30 +1165,23 @@ static void relative_pointer_handle_relative_motion(void *data, wl_fixed_t dy_unaccel_w) { SDL_WaylandSeat *seat = data; + SDL_WindowData *window = seat->pointer.focus; + SDL_Mouse *mouse = SDL_GetMouse(); - if (seat->display->relative_mode_enabled) { - SDL_WindowData *window = seat->pointer.focus; + // Relative pointer event times are in microsecond granularity. + const Uint64 timestamp = Wayland_AdjustEventTimestampBase(SDL_US_TO_NS(((Uint64)time_hi << 32) | (Uint64)time_lo)); - // Relative motion follows keyboard focus. - if (Wayland_SeatHasRelativePointerFocus(seat)) { - SDL_Mouse *mouse = SDL_GetMouse(); - - // Relative pointer event times are in microsecond granularity. - const Uint64 timestamp = Wayland_GetEventTimestamp(SDL_US_TO_NS(((Uint64)time_hi << 32) | (Uint64)time_lo)); - - double dx; - double dy; - if (mouse->InputTransform || !mouse->enable_relative_system_scale) { - dx = wl_fixed_to_double(dx_unaccel_w); - dy = wl_fixed_to_double(dy_unaccel_w); - } else { - dx = wl_fixed_to_double(dx_w) * window->pointer_scale.x; - dy = wl_fixed_to_double(dy_w) * window->pointer_scale.y; - } - - SDL_SendMouseMotion(timestamp, window->sdlwindow, seat->pointer.sdl_id, true, (float)dx, (float)dy); - } + double dx; + double dy; + if (mouse->InputTransform || !mouse->enable_relative_system_scale) { + dx = wl_fixed_to_double(dx_unaccel_w); + dy = wl_fixed_to_double(dy_unaccel_w); + } else { + dx = wl_fixed_to_double(dx_w) * window->pointer_scale.x; + dy = wl_fixed_to_double(dy_w) * window->pointer_scale.y; } + + SDL_SendMouseMotion(timestamp, window->sdlwindow, seat->pointer.sdl_id, true, (float)dx, (float)dy); } static const struct zwp_relative_pointer_v1_listener relative_pointer_listener = { @@ -1243,6 +1248,7 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri y = (float)wl_fixed_to_double(fy) / (window_data->current.logical_height - 1); } + ++window_data->active_touch_count; SDL_SetMouseFocus(window_data->sdlwindow); SDL_SendTouch(Wayland_GetTouchTimestamp(seat, timestamp), (SDL_TouchID)(uintptr_t)touch, @@ -1269,11 +1275,13 @@ static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial SDL_SendTouch(Wayland_GetTouchTimestamp(seat, timestamp), (SDL_TouchID)(uintptr_t)touch, (SDL_FingerID)(id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_UP, x, y, 0.0f); - /* If the window currently has mouse focus, the keyboard focus is another window or NULL, the window has no - * pointers active on it, and the surface has no active touch events, then consider mouse focus to be lost. + --window_data->active_touch_count; + + /* If the window currently has mouse focus and has no currently active keyboards, pointers, + * or touch events, then consider mouse focus to be lost. */ - if (SDL_GetMouseFocus() == window_data->sdlwindow && seat->keyboard.focus != window_data && - !window_data->pointer_focus_count && !Wayland_SurfaceHasActiveTouches(seat->display, surface)) { + if (SDL_GetMouseFocus() == window_data->sdlwindow && !window_data->keyboard_focus_count && + !window_data->pointer_focus_count && !window_data->active_touch_count) { SDL_SetMouseFocus(NULL); } } @@ -1295,7 +1303,7 @@ static void touch_handler_motion(void *data, struct wl_touch *touch, uint32_t ti const float x = (float)wl_fixed_to_double(fx) / window_data->current.logical_width; const float y = (float)wl_fixed_to_double(fy) / window_data->current.logical_height; - SDL_SendTouchMotion(Wayland_GetPointerTimestamp(seat, timestamp), (SDL_TouchID)(uintptr_t)touch, + SDL_SendTouchMotion(Wayland_GetTouchTimestamp(seat, timestamp), (SDL_TouchID)(uintptr_t)touch, (SDL_FingerID)(id + 1), window_data->sdlwindow, x, y, 1.0f); } } @@ -1308,39 +1316,11 @@ static void touch_handler_frame(void *data, struct wl_touch *touch) static void touch_handler_cancel(void *data, struct wl_touch *touch) { SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data; - struct SDL_WaylandTouchPoint *tp, *temp; + SDL_WaylandTouchPoint *tp, *temp; + // Need the safe loop variant here as cancelling a touch point removes it from the list. wl_list_for_each_safe (tp, temp, &seat->touch.points, link) { - bool removed = false; - - if (tp->surface) { - SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(tp->surface); - - if (window_data) { - const float x = (float)(wl_fixed_to_double(tp->fx) / window_data->current.logical_width); - const float y = (float)(wl_fixed_to_double(tp->fy) / window_data->current.logical_height); - - SDL_SendTouch(0, (SDL_TouchID)(uintptr_t)touch, - (SDL_FingerID)(tp->id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_CANCELED, x, y, 0.0f); - - // Remove the touch from the list before checking for still-active touches on the surface. - WAYLAND_wl_list_remove(&tp->link); - removed = true; - - /* If the window currently has mouse focus, the keyboard focus is another window or NULL, the window has no - * pointers active on it, and the surface has no active touch events, then consider mouse focus to be lost. - */ - if (SDL_GetMouseFocus() == window_data->sdlwindow && seat->keyboard.focus != window_data && - !window_data->pointer_focus_count && !Wayland_SurfaceHasActiveTouches(seat->display, tp->surface)) { - SDL_SetMouseFocus(NULL); - } - } - } - - if (!removed) { - WAYLAND_wl_list_remove(&tp->link); - } - SDL_free(tp); + Wayland_SeatCancelTouch(seat, tp); } } @@ -1413,21 +1393,21 @@ static void Wayland_UpdateKeymap(SDL_WaylandSeat *seat) xkb_mod_mask_t xkb_mask; } const keymod_masks[] = { { SDL_KMOD_NONE, 0 }, - { SDL_KMOD_SHIFT, seat->keyboard.xkb.idx_shift }, - { SDL_KMOD_CAPS, seat->keyboard.xkb.idx_caps }, - { SDL_KMOD_SHIFT | SDL_KMOD_CAPS, seat->keyboard.xkb.idx_shift | seat->keyboard.xkb.idx_caps }, - { SDL_KMOD_MODE, seat->keyboard.xkb.idx_mod5 }, - { SDL_KMOD_MODE | SDL_KMOD_SHIFT, seat->keyboard.xkb.idx_mod5 | seat->keyboard.xkb.idx_shift }, - { SDL_KMOD_MODE | SDL_KMOD_CAPS, seat->keyboard.xkb.idx_mod5 | seat->keyboard.xkb.idx_caps }, - { SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, seat->keyboard.xkb.idx_mod5 | seat->keyboard.xkb.idx_shift | seat->keyboard.xkb.idx_caps }, - { SDL_KMOD_LEVEL5, seat->keyboard.xkb.idx_mod3 }, - { SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT, seat->keyboard.xkb.idx_mod3 | seat->keyboard.xkb.idx_shift }, - { SDL_KMOD_LEVEL5 | SDL_KMOD_CAPS, seat->keyboard.xkb.idx_mod3 | seat->keyboard.xkb.idx_caps }, - { SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, seat->keyboard.xkb.idx_mod3 | seat->keyboard.xkb.idx_shift | seat->keyboard.xkb.idx_caps }, - { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE, seat->keyboard.xkb.idx_mod3 | seat->keyboard.xkb.idx_mod5 }, - { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT, seat->keyboard.xkb.idx_mod3 | seat->keyboard.xkb.idx_mod5 | seat->keyboard.xkb.idx_shift }, - { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_CAPS, seat->keyboard.xkb.idx_mod3 | seat->keyboard.xkb.idx_mod5 | seat->keyboard.xkb.idx_caps }, - { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, seat->keyboard.xkb.idx_mod3 | seat->keyboard.xkb.idx_mod5 | seat->keyboard.xkb.idx_shift | seat->keyboard.xkb.idx_caps }, + { SDL_KMOD_SHIFT, seat->keyboard.xkb.shift_mask }, + { SDL_KMOD_CAPS, seat->keyboard.xkb.caps_mask }, + { SDL_KMOD_SHIFT | SDL_KMOD_CAPS, seat->keyboard.xkb.shift_mask | seat->keyboard.xkb.caps_mask }, + { SDL_KMOD_MODE, seat->keyboard.xkb.level3_mask }, + { SDL_KMOD_MODE | SDL_KMOD_SHIFT, seat->keyboard.xkb.level3_mask | seat->keyboard.xkb.shift_mask }, + { SDL_KMOD_MODE | SDL_KMOD_CAPS, seat->keyboard.xkb.level3_mask | seat->keyboard.xkb.caps_mask }, + { SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, seat->keyboard.xkb.level3_mask | seat->keyboard.xkb.shift_mask | seat->keyboard.xkb.caps_mask }, + { SDL_KMOD_LEVEL5, seat->keyboard.xkb.level5_mask }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT, seat->keyboard.xkb.level5_mask | seat->keyboard.xkb.shift_mask }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_CAPS, seat->keyboard.xkb.level5_mask | seat->keyboard.xkb.caps_mask }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, seat->keyboard.xkb.level5_mask | seat->keyboard.xkb.shift_mask | seat->keyboard.xkb.caps_mask }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE, seat->keyboard.xkb.level5_mask | seat->keyboard.xkb.level3_mask }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT, seat->keyboard.xkb.level5_mask | seat->keyboard.xkb.level3_mask | seat->keyboard.xkb.shift_mask }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_CAPS, seat->keyboard.xkb.level5_mask | seat->keyboard.xkb.level3_mask | seat->keyboard.xkb.caps_mask }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, seat->keyboard.xkb.level5_mask | seat->keyboard.xkb.level3_mask | seat->keyboard.xkb.shift_mask | seat->keyboard.xkb.caps_mask }, }; if (!seat->keyboard.is_virtual) { @@ -1448,8 +1428,8 @@ static void Wayland_UpdateKeymap(SDL_WaylandSeat *seat) for (int i = 0; i < SDL_arraysize(keymod_masks); ++i) { keymap.modstate = keymod_masks[i].sdl_mask; WAYLAND_xkb_state_update_mask(keymap.state, - keymod_masks[i].xkb_mask & (seat->keyboard.xkb.idx_shift | seat->keyboard.xkb.idx_mod5 | seat->keyboard.xkb.idx_mod3), 0, keymod_masks[i].xkb_mask & seat->keyboard.xkb.idx_caps, - 0, 0, seat->keyboard.xkb.current_group); + keymod_masks[i].xkb_mask & (seat->keyboard.xkb.shift_mask | seat->keyboard.xkb.level3_mask | seat->keyboard.xkb.level5_mask), 0, keymod_masks[i].xkb_mask & seat->keyboard.xkb.caps_mask, + 0, 0, seat->keyboard.xkb.current_layout); WAYLAND_xkb_keymap_key_for_each(seat->keyboard.xkb.keymap, Wayland_keymap_iter, &keymap); @@ -1509,17 +1489,29 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, return; } +#if SDL_XKBCOMMON_CHECK_VERSION(1, 10, 0) + seat->keyboard.xkb.shift_mask = WAYLAND_xkb_keymap_mod_get_mask(seat->keyboard.xkb.keymap, XKB_MOD_NAME_SHIFT); + seat->keyboard.xkb.ctrl_mask = WAYLAND_xkb_keymap_mod_get_mask(seat->keyboard.xkb.keymap, XKB_MOD_NAME_CTRL); + seat->keyboard.xkb.alt_mask = WAYLAND_xkb_keymap_mod_get_mask(seat->keyboard.xkb.keymap, XKB_VMOD_NAME_ALT); + seat->keyboard.xkb.gui_mask = WAYLAND_xkb_keymap_mod_get_mask(seat->keyboard.xkb.keymap, XKB_VMOD_NAME_SUPER); + seat->keyboard.xkb.level3_mask = WAYLAND_xkb_keymap_mod_get_mask(seat->keyboard.xkb.keymap, XKB_VMOD_NAME_LEVEL3); + seat->keyboard.xkb.level5_mask = WAYLAND_xkb_keymap_mod_get_mask(seat->keyboard.xkb.keymap, XKB_VMOD_NAME_LEVEL5); + seat->keyboard.xkb.num_mask = WAYLAND_xkb_keymap_mod_get_mask(seat->keyboard.xkb.keymap, XKB_VMOD_NAME_NUM); + seat->keyboard.xkb.caps_mask = WAYLAND_xkb_keymap_mod_get_mask(seat->keyboard.xkb.keymap, XKB_MOD_NAME_CAPS); +#else #define GET_MOD_INDEX(mod) \ WAYLAND_xkb_keymap_mod_get_index(seat->keyboard.xkb.keymap, XKB_MOD_NAME_##mod) - seat->keyboard.xkb.idx_shift = 1 << GET_MOD_INDEX(SHIFT); - seat->keyboard.xkb.idx_ctrl = 1 << GET_MOD_INDEX(CTRL); - seat->keyboard.xkb.idx_alt = 1 << GET_MOD_INDEX(ALT); - seat->keyboard.xkb.idx_gui = 1 << GET_MOD_INDEX(LOGO); - seat->keyboard.xkb.idx_mod3 = 1 << GET_MOD_INDEX(MOD3); - seat->keyboard.xkb.idx_mod5 = 1 << GET_MOD_INDEX(MOD5); - seat->keyboard.xkb.idx_num = 1 << GET_MOD_INDEX(NUM); - seat->keyboard.xkb.idx_caps = 1 << GET_MOD_INDEX(CAPS); + seat->keyboard.xkb.shift_mask = 1 << GET_MOD_INDEX(SHIFT); + seat->keyboard.xkb.ctrl_mask = 1 << GET_MOD_INDEX(CTRL); + seat->keyboard.xkb.alt_mask = 1 << GET_MOD_INDEX(ALT); + seat->keyboard.xkb.gui_mask = 1 << GET_MOD_INDEX(LOGO); + // Note: This is correct: Mod3 is typically level 5 shift, and Mod5 is typically level 3 shift. + seat->keyboard.xkb.level3_mask = 1 << GET_MOD_INDEX(MOD5); + seat->keyboard.xkb.level5_mask = 1 << GET_MOD_INDEX(MOD3); + seat->keyboard.xkb.num_mask = 1 << GET_MOD_INDEX(NUM); + seat->keyboard.xkb.caps_mask = 1 << GET_MOD_INDEX(CAPS); #undef GET_MOD_INDEX +#endif if (seat->keyboard.xkb.state != NULL) { /* if there's already a state, throw it away rather than leaking it before @@ -1543,7 +1535,7 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, seat->keyboard.is_virtual = WAYLAND_xkb_keymap_layout_get_name(seat->keyboard.xkb.keymap, 0) == NULL; // Update the keymap if changed. - if (seat->keyboard.xkb.current_group != XKB_GROUP_INVALID) { + if (seat->keyboard.xkb.current_layout != XKB_LAYOUT_INVALID) { Wayland_UpdateKeymap(seat); } @@ -1599,7 +1591,7 @@ static SDL_Scancode Wayland_GetScancodeForKey(SDL_WaylandSeat *seat, uint32_t ke scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, key); } else { const xkb_keysym_t *syms; - if (WAYLAND_xkb_keymap_key_get_syms_by_level(seat->keyboard.xkb.keymap, key + 8, seat->keyboard.xkb.current_group, 0, &syms) > 0) { + if (WAYLAND_xkb_keymap_key_get_syms_by_level(seat->keyboard.xkb.keymap, key + 8, seat->keyboard.xkb.current_layout, 0, &syms) > 0) { scancode = SDL_GetScancodeFromKeySym(syms[0], key); } } @@ -1615,7 +1607,7 @@ static void Wayland_ReconcileModifiers(SDL_WaylandSeat *seat, bool key_pressed) * pressed state via means other than pressing the physical key. */ if (!key_pressed) { - if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.idx_shift) { + if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.shift_mask) { if (!(seat->keyboard.pressed_modifiers & SDL_KMOD_SHIFT)) { seat->keyboard.pressed_modifiers |= SDL_KMOD_SHIFT; } @@ -1623,7 +1615,7 @@ static void Wayland_ReconcileModifiers(SDL_WaylandSeat *seat, bool key_pressed) seat->keyboard.pressed_modifiers &= ~SDL_KMOD_SHIFT; } - if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.idx_ctrl) { + if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.ctrl_mask) { if (!(seat->keyboard.pressed_modifiers & SDL_KMOD_CTRL)) { seat->keyboard.pressed_modifiers |= SDL_KMOD_CTRL; } @@ -1631,7 +1623,7 @@ static void Wayland_ReconcileModifiers(SDL_WaylandSeat *seat, bool key_pressed) seat->keyboard.pressed_modifiers &= ~SDL_KMOD_CTRL; } - if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.idx_alt) { + if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.alt_mask) { if (!(seat->keyboard.pressed_modifiers & SDL_KMOD_ALT)) { seat->keyboard.pressed_modifiers |= SDL_KMOD_ALT; } @@ -1639,7 +1631,7 @@ static void Wayland_ReconcileModifiers(SDL_WaylandSeat *seat, bool key_pressed) seat->keyboard.pressed_modifiers &= ~SDL_KMOD_ALT; } - if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.idx_gui) { + if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.gui_mask) { if (!(seat->keyboard.pressed_modifiers & SDL_KMOD_GUI)) { seat->keyboard.pressed_modifiers |= SDL_KMOD_GUI; } @@ -1647,24 +1639,21 @@ static void Wayland_ReconcileModifiers(SDL_WaylandSeat *seat, bool key_pressed) seat->keyboard.pressed_modifiers &= ~SDL_KMOD_GUI; } - /* Note: This is not backwards: in the default keymap, Mod5 is typically - * level 3 shift, and Mod3 is typically level 5 shift. - */ - if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.idx_mod3) { - if (!(seat->keyboard.pressed_modifiers & SDL_KMOD_LEVEL5)) { - seat->keyboard.pressed_modifiers |= SDL_KMOD_LEVEL5; - } - } else { - seat->keyboard.pressed_modifiers &= ~SDL_KMOD_LEVEL5; - } - - if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.idx_mod5) { + if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.level3_mask) { if (!(seat->keyboard.pressed_modifiers & SDL_KMOD_MODE)) { seat->keyboard.pressed_modifiers |= SDL_KMOD_MODE; } } else { seat->keyboard.pressed_modifiers &= ~SDL_KMOD_MODE; } + + if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.level5_mask) { + if (!(seat->keyboard.pressed_modifiers & SDL_KMOD_LEVEL5)) { + seat->keyboard.pressed_modifiers |= SDL_KMOD_LEVEL5; + } + } else { + seat->keyboard.pressed_modifiers &= ~SDL_KMOD_LEVEL5; + } } /* If a latch or lock was activated by a keypress, the latch/lock will @@ -1674,7 +1663,7 @@ static void Wayland_ReconcileModifiers(SDL_WaylandSeat *seat, bool key_pressed) * The modifier will remain active until the latch/lock is released by * the system. */ - if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_shift) { + if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.shift_mask) { if (seat->keyboard.pressed_modifiers & SDL_KMOD_SHIFT) { seat->keyboard.locked_modifiers &= ~SDL_KMOD_SHIFT; seat->keyboard.locked_modifiers |= (seat->keyboard.pressed_modifiers & SDL_KMOD_SHIFT); @@ -1685,7 +1674,7 @@ static void Wayland_ReconcileModifiers(SDL_WaylandSeat *seat, bool key_pressed) seat->keyboard.locked_modifiers &= ~SDL_KMOD_SHIFT; } - if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_ctrl) { + if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.ctrl_mask) { if (seat->keyboard.pressed_modifiers & SDL_KMOD_CTRL) { seat->keyboard.locked_modifiers &= ~SDL_KMOD_CTRL; seat->keyboard.locked_modifiers |= (seat->keyboard.pressed_modifiers & SDL_KMOD_CTRL); @@ -1696,7 +1685,7 @@ static void Wayland_ReconcileModifiers(SDL_WaylandSeat *seat, bool key_pressed) seat->keyboard.locked_modifiers &= ~SDL_KMOD_CTRL; } - if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_alt) { + if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.alt_mask) { if (seat->keyboard.pressed_modifiers & SDL_KMOD_ALT) { seat->keyboard.locked_modifiers &= ~SDL_KMOD_ALT; seat->keyboard.locked_modifiers |= (seat->keyboard.pressed_modifiers & SDL_KMOD_ALT); @@ -1707,7 +1696,7 @@ static void Wayland_ReconcileModifiers(SDL_WaylandSeat *seat, bool key_pressed) seat->keyboard.locked_modifiers &= ~SDL_KMOD_ALT; } - if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_gui) { + if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.gui_mask) { if (seat->keyboard.pressed_modifiers & SDL_KMOD_GUI) { seat->keyboard.locked_modifiers &= ~SDL_KMOD_GUI; seat->keyboard.locked_modifiers |= (seat->keyboard.pressed_modifiers & SDL_KMOD_GUI); @@ -1718,27 +1707,26 @@ static void Wayland_ReconcileModifiers(SDL_WaylandSeat *seat, bool key_pressed) seat->keyboard.locked_modifiers &= ~SDL_KMOD_GUI; } - // As above, this is correct: Mod3 is typically level 5 shift, and Mod5 is typically level 3 shift. - if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_mod3) { - seat->keyboard.locked_modifiers |= SDL_KMOD_LEVEL5; - } else { - seat->keyboard.locked_modifiers &= ~SDL_KMOD_LEVEL5; - } - - if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_mod5) { + if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.level3_mask) { seat->keyboard.locked_modifiers |= SDL_KMOD_MODE; } else { seat->keyboard.locked_modifiers &= ~SDL_KMOD_MODE; } + if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.level5_mask) { + seat->keyboard.locked_modifiers |= SDL_KMOD_LEVEL5; + } else { + seat->keyboard.locked_modifiers &= ~SDL_KMOD_LEVEL5; + } + // Capslock and Numlock can only be locked, not pressed. - if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_caps) { + if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.caps_mask) { seat->keyboard.locked_modifiers |= SDL_KMOD_CAPS; } else { seat->keyboard.locked_modifiers &= ~SDL_KMOD_CAPS; } - if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_num) { + if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.num_mask) { seat->keyboard.locked_modifiers |= SDL_KMOD_NUM; } else { seat->keyboard.locked_modifiers &= ~SDL_KMOD_NUM; @@ -1824,7 +1812,7 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, seat->keyboard.focus = window; // Restore the keyboard focus to the child popup that was holding it - SDL_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window->sdlwindow); + SDL_SetKeyboardFocus(window->sdlwindow->keyboard_focus ? window->sdlwindow->keyboard_focus : window->sdlwindow); // Update the keyboard grab and any relative pointer grabs related to this keyboard focus. Wayland_SeatUpdateKeyboardGrab(seat); @@ -1842,7 +1830,7 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, Uint64 timestamp = SDL_GetTicksNS(); window->last_focus_event_time_ns = timestamp; - if (SDL_GetCurrentKeymap() != seat->keyboard.sdl_keymap) { + if (SDL_GetCurrentKeymap(true) != seat->keyboard.sdl_keymap) { SDL_SetKeymap(seat->keyboard.sdl_keymap, true); } @@ -1924,8 +1912,7 @@ static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, /* If the window has mouse focus, has no pointers within it, and no active touches, consider * mouse focus to be lost. */ - if (SDL_GetMouseFocus() == window->sdlwindow && !window->pointer_focus_count && - !Wayland_SurfaceHasActiveTouches(seat->display, surface)) { + if (SDL_GetMouseFocus() == window->sdlwindow && !window->pointer_focus_count && !window->active_touch_count) { SDL_SetMouseFocus(NULL); } } @@ -1989,11 +1976,11 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, char text[8]; bool has_text = false; bool handled_by_ime = false; - const Uint64 timestamp_raw_ns = Wayland_GetKeyboardTimestampRaw(seat, time); + const Uint64 timestamp_ns = Wayland_GetKeyboardTimestamp(seat, time); Wayland_UpdateImplicitGrabSerial(seat, serial); - if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap()) { + if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap(true)) { SDL_SetKeymap(seat->keyboard.sdl_keymap, true); SDL_SetModState(seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers); } @@ -2010,7 +1997,8 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, * Using SDL_GetTicks would be wrong, as it would report when the release event is processed, * which may be off if the application hasn't pumped events for a while. */ - keyboard_repeat_handle(&seat->keyboard.repeat, timestamp_raw_ns - seat->keyboard.repeat.wl_press_time_ns); + const Uint64 elapsed = SDL_MS_TO_NS(time - seat->keyboard.repeat.wl_press_time_ms); + keyboard_repeat_handle(&seat->keyboard.repeat, elapsed); keyboard_repeat_clear(&seat->keyboard.repeat); } keyboard_input_get_text(text, seat, key, false, &handled_by_ime); @@ -2018,9 +2006,8 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, const SDL_Scancode scancode = Wayland_GetScancodeForKey(seat, key); Wayland_HandleModifierKeys(seat, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED); - Uint64 timestamp = Wayland_GetKeyboardTimestamp(seat, time); - SDL_SendKeyboardKeyIgnoreModifiers(timestamp, seat->keyboard.sdl_id, key, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED); + SDL_SendKeyboardKeyIgnoreModifiers(timestamp_ns, seat->keyboard.sdl_id, key, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED); if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { if (has_text && !(SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_ALT))) { @@ -2029,7 +2016,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, } } if (seat->keyboard.xkb.keymap && WAYLAND_xkb_keymap_key_repeats(seat->keyboard.xkb.keymap, key + 8)) { - keyboard_repeat_set(&seat->keyboard.repeat, seat->keyboard.sdl_id, key, timestamp_raw_ns, scancode, has_text, text); + keyboard_repeat_set(&seat->keyboard.repeat, seat->keyboard.sdl_id, key, time, timestamp_ns, scancode, has_text, text); } } } @@ -2065,12 +2052,12 @@ static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, } } - if (group == seat->keyboard.xkb.current_group) { + if (group == seat->keyboard.xkb.current_layout) { return; } // The layout changed, remap and fire an event. Virtual keyboards use the default keymap. - seat->keyboard.xkb.current_group = group; + seat->keyboard.xkb.current_layout = group; Wayland_UpdateKeymap(seat); } @@ -2092,26 +2079,6 @@ static const struct wl_keyboard_listener keyboard_listener = { keyboard_handle_repeat_info, // Version 4 }; -static void Wayland_SeatCreateRelativePointer(SDL_WaylandSeat *seat) -{ - if (seat->display->relative_pointer_manager) { - if (seat->pointer.wl_pointer && !seat->pointer.relative_pointer) { - seat->pointer.relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(seat->display->relative_pointer_manager, seat->pointer.wl_pointer); - zwp_relative_pointer_v1_add_listener(seat->pointer.relative_pointer, - &relative_pointer_listener, - seat); - } - } -} - -void Wayland_DisplayInitRelativePointerManager(SDL_VideoData *display) -{ - SDL_WaylandSeat *seat; - wl_list_for_each(seat, &display->seat_list, link) { - Wayland_SeatCreateRelativePointer(seat); - } -} - static void Wayland_SeatDestroyPointer(SDL_WaylandSeat *seat, bool send_event) { // Make sure focus is removed from a surface before the pointer is destroyed. @@ -2174,7 +2141,7 @@ static void Wayland_SeatDestroyKeyboard(SDL_WaylandSeat *seat, bool send_event) SDL_RemoveKeyboard(seat->keyboard.sdl_id, send_event); if (seat->keyboard.sdl_keymap) { - if (seat->keyboard.sdl_keymap == SDL_GetCurrentKeymap()) { + if (seat->keyboard.sdl_keymap == SDL_GetCurrentKeymap(true)) { SDL_SetKeymap(NULL, false); } SDL_DestroyKeymap(seat->keyboard.sdl_keymap); @@ -2254,8 +2221,6 @@ static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum w wl_pointer_set_user_data(seat->pointer.wl_pointer, seat); wl_pointer_add_listener(seat->pointer.wl_pointer, &pointer_listener, seat); - Wayland_SeatCreateRelativePointer(seat); - seat->pointer.sdl_id = SDL_GetNextObjectID(); if (seat->name) { @@ -2518,6 +2483,7 @@ static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_ data_device->seat->display->last_incoming_data_offer_seat = data_device->seat; data_offer->offer = id; data_offer->data_device = data_device; + data_offer->read_fd = -1; WAYLAND_wl_list_init(&(data_offer->mimes)); wl_data_offer_set_user_data(id, data_offer); wl_data_offer_add_listener(id, &data_offer_listener, data_offer); @@ -2759,41 +2725,6 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_d data_device->drag_offer = NULL; } -static void notifyFromMimes(struct wl_list *mimes) -{ - int nformats = 0; - char **new_mime_types = NULL; - if (mimes) { - nformats = WAYLAND_wl_list_length(mimes); - size_t alloc_size = (nformats + 1) * sizeof(char *); - - /* do a first pass to compute allocation size */ - SDL_MimeDataList *item = NULL; - wl_list_for_each(item, mimes, link) { - alloc_size += SDL_strlen(item->mime_type) + 1; - } - - new_mime_types = SDL_AllocateTemporaryMemory(alloc_size); - if (!new_mime_types) { - SDL_LogError(SDL_LOG_CATEGORY_INPUT, "unable to allocate new_mime_types"); - return; - } - - /* second pass to fill*/ - char *strPtr = (char *)(new_mime_types + nformats + 1); - item = NULL; - int i = 0; - wl_list_for_each(item, mimes, link) { - new_mime_types[i] = strPtr; - strPtr = stpcpy(strPtr, item->mime_type) + 1; - i++; - } - new_mime_types[nformats] = NULL; - } - - SDL_SendClipboardUpdate(false, new_mime_types, nformats); -} - static void data_device_handle_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) { @@ -2812,7 +2743,7 @@ static void data_device_handle_selection(void *data, struct wl_data_device *wl_d data_device->selection_offer = offer; } - notifyFromMimes(offer ? &offer->mimes : NULL); + Wayland_data_offer_notify_from_mimes(offer, true); } static const struct wl_data_device_listener data_device_listener = { @@ -2942,6 +2873,29 @@ static const struct zwp_text_input_v3_listener text_input_listener = { text_input_done }; +static void Wayland_DataDeviceSetID(SDL_WaylandDataDevice *data_device) +{ + if (!data_device->id_str) +#ifdef SDL_USE_LIBDBUS + { + SDL_DBusContext *dbus = SDL_DBus_GetContext(); + if (dbus) { + const char *id = dbus->bus_get_unique_name(dbus->session_conn); + if (id) { + data_device->id_str = SDL_strdup(id); + } + } + } + if (!data_device->id_str) +#endif + { + char id[24]; + Uint64 pid = (Uint64)getpid(); + SDL_snprintf(id, sizeof(id), "%" SDL_PRIu64, pid); + data_device->id_str = SDL_strdup(id); + } +} + static void Wayland_SeatCreateDataDevice(SDL_WaylandSeat *seat) { if (!seat->display->data_device_manager) { @@ -2960,6 +2914,7 @@ static void Wayland_SeatCreateDataDevice(SDL_WaylandSeat *seat) if (!data_device->data_device) { SDL_free(data_device); } else { + Wayland_DataDeviceSetID(data_device); wl_data_device_set_user_data(data_device->data_device, data_device); wl_data_device_add_listener(data_device->data_device, &data_device_listener, data_device); @@ -3249,7 +3204,7 @@ static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool return; // Not a pen we report on. } - const Uint64 timestamp = Wayland_GetEventTimestamp(SDL_MS_TO_NS(time)); + const Uint64 timestamp = Wayland_AdjustEventTimestampBase(Wayland_EventTimestampMSToNS(time)); const SDL_PenID instance_id = sdltool->instance_id; SDL_Window *window = sdltool->tool_focus; @@ -3354,7 +3309,6 @@ static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { static void Wayland_SeatInitTabletSupport(SDL_WaylandSeat *seat) { - WAYLAND_wl_list_init(&seat->tablet.tool_list); seat->tablet.wl_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(seat->display->tablet_manager, seat->wl_seat); zwp_tablet_seat_v2_add_listener(seat->tablet.wl_tablet_seat, &tablet_seat_listener, seat); } @@ -3387,7 +3341,7 @@ static void Wayland_SeatDestroyTablet(SDL_WaylandSeat *seat, bool send_events) SDL_RemoveAllPenDevices(Wayland_remove_all_pens_callback, NULL); } - if (seat && seat->tablet.wl_tablet_seat) { + if (seat->tablet.wl_tablet_seat) { zwp_tablet_seat_v2_destroy(seat->tablet.wl_tablet_seat); seat->tablet.wl_tablet_seat = NULL; } @@ -3407,10 +3361,11 @@ void Wayland_DisplayCreateSeat(SDL_VideoData *display, struct wl_seat *wl_seat, WAYLAND_wl_list_insert(display->seat_list.prev, &seat->link); WAYLAND_wl_list_init(&seat->touch.points); + WAYLAND_wl_list_init(&seat->tablet.tool_list); seat->wl_seat = wl_seat; seat->display = display; seat->registry_id = id; - seat->keyboard.xkb.current_group = XKB_GROUP_INVALID; + seat->keyboard.xkb.current_layout = XKB_LAYOUT_INVALID; Wayland_SeatCreateDataDevice(seat); Wayland_SeatCreatePrimarySelectionDevice(seat); @@ -3426,6 +3381,36 @@ void Wayland_DisplayCreateSeat(SDL_VideoData *display, struct wl_seat *wl_seat, WAYLAND_wl_display_flush(display->display); } +void Wayland_DisplayRemoveWindowReferencesFromSeats(SDL_VideoData *display, SDL_WindowData *window) +{ + SDL_WaylandSeat *seat; + wl_list_for_each (seat, &display->seat_list, link) + { + if (seat->keyboard.focus == window) { + keyboard_handle_leave(seat, seat->keyboard.wl_keyboard, 0, window->surface); + } + + if (seat->pointer.focus == window) { + pointer_handle_leave(seat, seat->pointer.wl_pointer, 0, window->surface); + } + + // Need the safe loop variant here as cancelling a touch point removes it from the list. + SDL_WaylandTouchPoint *tp, *temp; + wl_list_for_each_safe (tp, temp, &seat->touch.points, link) { + if (tp->surface == window->surface) { + Wayland_SeatCancelTouch(seat, tp); + } + } + + SDL_WaylandPenTool *tool; + wl_list_for_each (tool, &seat->tablet.tool_list, link) { + if (tool->tool_focus == window->sdlwindow) { + tablet_tool_handle_proximity_out(tool, tool->wltool); + } + } + } +} + void Wayland_SeatDestroy(SDL_WaylandSeat *seat, bool send_events) { if (!seat) { @@ -3449,6 +3434,7 @@ void Wayland_SeatDestroy(SDL_WaylandSeat *seat, bool send_events) wl_data_device_destroy(seat->data_device->data_device); } } + SDL_free(seat->data_device->id_str); SDL_free(seat->data_device); } @@ -3484,16 +3470,36 @@ void Wayland_SeatDestroy(SDL_WaylandSeat *seat, bool send_events) SDL_free(seat); } -bool Wayland_SeatHasRelativePointerFocus(SDL_WaylandSeat *seat) +static void Wayland_SeatUpdateRelativePointer(SDL_WaylandSeat *seat) { - /* If a seat has both keyboard and pointer capabilities, relative focus will follow the keyboard - * attached to that seat. Otherwise, relative focus will be gained if any other seat has keyboard - * focus on the window with pointer focus. - */ - if (seat->keyboard.wl_keyboard) { - return seat->keyboard.focus && seat->keyboard.focus == seat->pointer.focus; - } else { - return seat->pointer.focus && seat->pointer.focus->keyboard_focus_count != 0; + if (seat->display->relative_pointer_manager) { + bool relative_focus = false; + + if (seat->pointer.focus) { + /* If a seat has both keyboard and pointer capabilities, relative focus will follow the keyboard + * attached to that seat. Otherwise, relative focus will be gained if any other seat has keyboard + * focus on the window with pointer focus. + */ + if (seat->pointer.focus->sdlwindow->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE) { + if (seat->keyboard.wl_keyboard) { + relative_focus = seat->keyboard.focus == seat->pointer.focus; + } else { + relative_focus = seat->pointer.focus->keyboard_focus_count != 0; + } + } else { + relative_focus = SDL_GetMouse()->warp_emulation_active; + } + } + + if (relative_focus) { + if (!seat->pointer.relative_pointer) { + seat->pointer.relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(seat->display->relative_pointer_manager, seat->pointer.wl_pointer); + zwp_relative_pointer_v1_add_listener(seat->pointer.relative_pointer, &relative_pointer_listener, seat); + } + } else if (seat->pointer.relative_pointer) { + zwp_relative_pointer_v1_destroy(seat->pointer.relative_pointer); + seat->pointer.relative_pointer = NULL; + } } } @@ -3526,11 +3532,10 @@ static void Wayland_SeatUpdateKeyboardGrab(SDL_WaylandSeat *seat) void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat) { SDL_VideoData *display = seat->display; + Wayland_SeatUpdateRelativePointer(seat); if (display->pointer_constraints) { - const bool has_relative_focus = Wayland_SeatHasRelativePointerFocus(seat); - - if (seat->pointer.locked_pointer && (!display->relative_mode_enabled || !has_relative_focus)) { + if (seat->pointer.locked_pointer && !seat->pointer.relative_pointer) { zwp_locked_pointer_v1_destroy(seat->pointer.locked_pointer); seat->pointer.locked_pointer = NULL; @@ -3540,7 +3545,7 @@ void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat) if (seat->pointer.wl_pointer) { // If relative mode is active, and the pointer focus matches the keyboard focus, lock it. - if (seat->display->relative_mode_enabled && has_relative_focus) { + if (seat->pointer.relative_pointer) { if (!seat->pointer.locked_pointer) { // Creating a lock on a surface with an active confinement region on the same seat is a protocol error. if (seat->pointer.confined_pointer) { diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h index 3c3aed69..79a334e3 100644 --- a/src/video/wayland/SDL_waylandevents_c.h +++ b/src/video/wayland/SDL_waylandevents_c.h @@ -42,17 +42,18 @@ enum SDL_WaylandAxisEvent typedef struct { - int32_t repeat_rate; // Repeat rate in range of [1, 1000] character(s) per second - int32_t repeat_delay_ms; // Time to first repeat event in milliseconds - Uint32 keyboard_id; // ID of the source keyboard. + Sint32 repeat_rate; // Repeat rate in range of [1, 1000] character(s) per second + Sint32 repeat_delay_ms; // Time to first repeat event in milliseconds + Uint32 keyboard_id; // ID of the source keyboard. bool is_initialized; bool is_key_down; - uint32_t key; - Uint64 wl_press_time_ns; // Key press time as reported by the Wayland API + Uint32 key; + Uint32 wl_press_time_ms; // Key press time as reported by the Wayland API in milliseconds + Uint64 base_time_ns; // Key press time as reported by the Wayland API in nanoseconds Uint64 sdl_press_time_ns; // Key press time expressed in SDL ticks Uint64 next_repeat_ns; // Next repeat event in nanoseconds - uint32_t scancode; + Uint32 scancode; char text[8]; } SDL_WaylandKeyboardRepeat; @@ -93,22 +94,22 @@ typedef struct SDL_WaylandSeat struct xkb_compose_table *compose_table; struct xkb_compose_state *compose_state; - // Keyboard layout "group" - Uint32 current_group; + // Current keyboard layout (aka 'group') + xkb_layout_index_t current_layout; // Modifier bitshift values - Uint32 idx_shift; - Uint32 idx_ctrl; - Uint32 idx_alt; - Uint32 idx_gui; - Uint32 idx_mod3; - Uint32 idx_mod5; - Uint32 idx_num; - Uint32 idx_caps; + xkb_mod_mask_t shift_mask; + xkb_mod_mask_t ctrl_mask; + xkb_mod_mask_t alt_mask; + xkb_mod_mask_t gui_mask; + xkb_mod_mask_t level3_mask; + xkb_mod_mask_t level5_mask; + xkb_mod_mask_t num_mask; + xkb_mod_mask_t caps_mask; // Current system modifier flags - Uint32 wl_pressed_modifiers; - Uint32 wl_locked_modifiers; + xkb_mod_mask_t wl_pressed_modifiers; + xkb_mod_mask_t wl_locked_modifiers; } xkb; } keyboard; @@ -170,7 +171,8 @@ typedef struct SDL_WaylandSeat struct { struct zwp_text_input_v3 *zwp_text_input; - SDL_Rect cursor_rect; + SDL_Rect text_input_rect; + int text_input_cursor; bool enabled; bool has_preedit; } text_input; @@ -191,7 +193,6 @@ extern int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS); extern void Wayland_DisplayInitInputTimestampManager(SDL_VideoData *display); extern void Wayland_DisplayInitCursorShapeManager(SDL_VideoData *display); -extern void Wayland_DisplayInitRelativePointerManager(SDL_VideoData *display); extern void Wayland_DisplayInitTabletManager(SDL_VideoData *display); extern void Wayland_DisplayInitDataDeviceManager(SDL_VideoData *display); extern void Wayland_DisplayInitPrimarySelectionDeviceManager(SDL_VideoData *display); @@ -201,10 +202,10 @@ extern void Wayland_DisplayCreateTextInputManager(SDL_VideoData *d, uint32_t id) extern void Wayland_DisplayCreateSeat(SDL_VideoData *display, struct wl_seat *wl_seat, Uint32 id); extern void Wayland_SeatDestroy(SDL_WaylandSeat *seat, bool send_events); -extern bool Wayland_SeatHasRelativePointerFocus(SDL_WaylandSeat *seat); extern void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat); extern void Wayland_DisplayUpdatePointerGrabs(SDL_VideoData *display, SDL_WindowData *window); extern void Wayland_DisplayUpdateKeyboardGrabs(SDL_VideoData *display, SDL_WindowData *window); +extern void Wayland_DisplayRemoveWindowReferencesFromSeats(SDL_VideoData *display, SDL_WindowData *window); /* The implicit grab serial needs to be updated on: * - Keyboard key down/up diff --git a/src/video/wayland/SDL_waylandkeyboard.c b/src/video/wayland/SDL_waylandkeyboard.c index 967d76ce..ae5bb13b 100644 --- a/src/video/wayland/SDL_waylandkeyboard.c +++ b/src/video/wayland/SDL_waylandkeyboard.c @@ -61,6 +61,8 @@ void Wayland_UpdateTextInput(SDL_VideoData *display) if (seat->text_input.zwp_text_input) { if (focus && focus->text_input_props.active) { + SDL_Window *window = focus->sdlwindow; + // Enabling will reset all state, so don't do it redundantly. if (!seat->text_input.enabled) { seat->text_input.enabled = true; @@ -68,15 +70,24 @@ void Wayland_UpdateTextInput(SDL_VideoData *display) // Now that it's enabled, set the input properties zwp_text_input_v3_set_content_type(seat->text_input.zwp_text_input, focus->text_input_props.hint, focus->text_input_props.purpose); - if (!SDL_RectEmpty(&focus->sdlwindow->text_input_rect)) { - SDL_copyp(&seat->text_input.cursor_rect, &focus->sdlwindow->text_input_rect); + if (!SDL_RectEmpty(&window->text_input_rect)) { + const SDL_Rect scaled_rect = { + (int)SDL_floor(window->text_input_rect.x / focus->pointer_scale.x), + (int)SDL_floor(window->text_input_rect.y / focus->pointer_scale.y), + (int)SDL_ceil(window->text_input_rect.w / focus->pointer_scale.x), + (int)SDL_ceil(window->text_input_rect.h / focus->pointer_scale.y) + }; + const int scaled_cursor = (int)SDL_floor(window->text_input_cursor / focus->pointer_scale.x); - // This gets reset on enable so we have to cache it + SDL_copyp(&seat->text_input.text_input_rect, &scaled_rect); + seat->text_input.text_input_cursor = scaled_cursor; + + // Clamp the x value so it doesn't run too far past the end of the text input area. zwp_text_input_v3_set_cursor_rectangle(seat->text_input.zwp_text_input, - focus->sdlwindow->text_input_rect.x, - focus->sdlwindow->text_input_rect.y, - focus->sdlwindow->text_input_rect.w, - focus->sdlwindow->text_input_rect.h); + SDL_min(scaled_rect.x + scaled_cursor, scaled_rect.x + scaled_rect.w), + scaled_rect.y, + 1, + scaled_rect.h); } zwp_text_input_v3_commit(seat->text_input.zwp_text_input); @@ -88,7 +99,8 @@ void Wayland_UpdateTextInput(SDL_VideoData *display) } else { if (seat->text_input.enabled) { seat->text_input.enabled = false; - SDL_zero(seat->text_input.cursor_rect); + SDL_zero(seat->text_input.text_input_rect); + seat->text_input.text_input_cursor = 0; zwp_text_input_v3_disable(seat->text_input.zwp_text_input); zwp_text_input_v3_commit(seat->text_input.zwp_text_input); } @@ -203,13 +215,25 @@ bool Wayland_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window) wl_list_for_each (seat, &internal->seat_list, link) { if (seat->text_input.zwp_text_input && seat->keyboard.focus == window->internal) { - if (!SDL_RectsEqual(&window->text_input_rect, &seat->text_input.cursor_rect)) { - SDL_copyp(&seat->text_input.cursor_rect, &window->text_input_rect); + SDL_WindowData *wind = window->internal; + const SDL_Rect scaled_rect = { + (int)SDL_floor(window->text_input_rect.x / wind->pointer_scale.x), + (int)SDL_floor(window->text_input_rect.y / wind->pointer_scale.y), + (int)SDL_ceil(window->text_input_rect.w / wind->pointer_scale.x), + (int)SDL_ceil(window->text_input_rect.h / wind->pointer_scale.y) + }; + const int scaled_cursor = (int)SDL_floor(window->text_input_cursor / wind->pointer_scale.x); + + if (!SDL_RectsEqual(&scaled_rect, &seat->text_input.text_input_rect) || scaled_cursor != seat->text_input.text_input_cursor) { + SDL_copyp(&seat->text_input.text_input_rect, &scaled_rect); + seat->text_input.text_input_cursor = scaled_cursor; + + // Clamp the x value so it doesn't run too far past the end of the text input area. zwp_text_input_v3_set_cursor_rectangle(seat->text_input.zwp_text_input, - window->text_input_rect.x, - window->text_input_rect.y, - window->text_input_rect.w, - window->text_input_rect.h); + SDL_min(scaled_rect.x + scaled_cursor, scaled_rect.x + scaled_rect.w), + scaled_rect.y, + 1, + scaled_rect.h); zwp_text_input_v3_commit(seat->text_input.zwp_text_input); } } diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c index 956087fd..82a1fdfe 100644 --- a/src/video/wayland/SDL_waylandmouse.c +++ b/src/video/wayland/SDL_waylandmouse.c @@ -38,6 +38,7 @@ #include "cursor-shape-v1-client-protocol.h" #include "pointer-constraints-unstable-v1-client-protocol.h" #include "viewporter-client-protocol.h" +#include "pointer-warp-v1-client-protocol.h" #include "../../SDL_hints_c.h" @@ -196,7 +197,7 @@ static DBusHandlerResult Wayland_DBusCursorMessageFilter(DBusConnection *conn, D if (dbus_cursor_size != new_cursor_size) { dbus_cursor_size = new_cursor_size; - SDL_SetCursor(NULL); // Force cursor update + SDL_RedrawCursor(); // Force cursor update } } else if (SDL_strcmp(CURSOR_THEME_KEY, key) == 0) { const char *new_cursor_theme = NULL; @@ -223,7 +224,7 @@ static DBusHandlerResult Wayland_DBusCursorMessageFilter(DBusConnection *conn, D // Purge the current cached themes and force a cursor refresh. Wayland_FreeCursorThemes(vdata); - SDL_SetCursor(NULL); + SDL_RedrawCursor(); } } else { goto not_our_signal; @@ -832,43 +833,50 @@ void Wayland_SeatWarpMouse(SDL_WaylandSeat *seat, SDL_WindowData *window, float SDL_VideoData *d = vd->internal; if (seat->pointer.wl_pointer) { - bool toggle_lock = !seat->pointer.locked_pointer; - bool update_grabs = false; + if (d->wp_pointer_warp_v1) { + // It's a protocol error to warp the pointer outside of the surface, so clamp the position. + const wl_fixed_t f_x = wl_fixed_from_double(SDL_clamp(x / window->pointer_scale.x, 0, window->current.logical_width)); + const wl_fixed_t f_y = wl_fixed_from_double(SDL_clamp(y / window->pointer_scale.y, 0, window->current.logical_height)); + wp_pointer_warp_v1_warp_pointer(d->wp_pointer_warp_v1, window->surface, seat->pointer.wl_pointer, f_x, f_y, seat->pointer.enter_serial); + } else { + bool toggle_lock = !seat->pointer.locked_pointer; + bool update_grabs = false; - /* The pointer confinement protocol allows setting a hint to warp the pointer, - * but only when the pointer is locked. - * - * Lock the pointer, set the position hint, unlock, and hope for the best. - */ - if (toggle_lock) { - if (seat->pointer.confined_pointer) { - zwp_confined_pointer_v1_destroy(seat->pointer.confined_pointer); - seat->pointer.confined_pointer = NULL; - update_grabs = true; + /* The pointer confinement protocol allows setting a hint to warp the pointer, + * but only when the pointer is locked. + * + * Lock the pointer, set the position hint, unlock, and hope for the best. + */ + if (toggle_lock) { + if (seat->pointer.confined_pointer) { + zwp_confined_pointer_v1_destroy(seat->pointer.confined_pointer); + seat->pointer.confined_pointer = NULL; + update_grabs = true; + } + seat->pointer.locked_pointer = zwp_pointer_constraints_v1_lock_pointer(d->pointer_constraints, window->surface, + seat->pointer.wl_pointer, NULL, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); } - seat->pointer.locked_pointer = zwp_pointer_constraints_v1_lock_pointer(d->pointer_constraints, window->surface, - seat->pointer.wl_pointer, NULL, - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); - } - const wl_fixed_t f_x = wl_fixed_from_double(x / window->pointer_scale.x); - const wl_fixed_t f_y = wl_fixed_from_double(y / window->pointer_scale.y); - zwp_locked_pointer_v1_set_cursor_position_hint(seat->pointer.locked_pointer, f_x, f_y); - wl_surface_commit(window->surface); + const wl_fixed_t f_x = wl_fixed_from_double(x / window->pointer_scale.x); + const wl_fixed_t f_y = wl_fixed_from_double(y / window->pointer_scale.y); + zwp_locked_pointer_v1_set_cursor_position_hint(seat->pointer.locked_pointer, f_x, f_y); + wl_surface_commit(window->surface); - if (toggle_lock) { - zwp_locked_pointer_v1_destroy(seat->pointer.locked_pointer); - seat->pointer.locked_pointer = NULL; + if (toggle_lock) { + zwp_locked_pointer_v1_destroy(seat->pointer.locked_pointer); + seat->pointer.locked_pointer = NULL; - if (update_grabs) { - Wayland_SeatUpdatePointerGrab(seat); + if (update_grabs) { + Wayland_SeatUpdatePointerGrab(seat); + } } - } - /* NOTE: There is a pending warp event under discussion that should replace this when available. - * https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/340 - */ - SDL_SendMouseMotion(0, window->sdlwindow, seat->pointer.sdl_id, false, x, y); + /* NOTE: There is a pending warp event under discussion that should replace this when available. + * https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/340 + */ + SDL_SendMouseMotion(0, window->sdlwindow, seat->pointer.sdl_id, false, x, y); + } } } @@ -881,8 +889,7 @@ static bool Wayland_WarpMouseRelative(SDL_Window *window, float x, float y) if (d->pointer_constraints) { wl_list_for_each (seat, &d->seat_list, link) { - if (wind == seat->pointer.focus || - (!seat->pointer.focus && wind == seat->keyboard.focus)) { + if (wind == seat->pointer.focus) { Wayland_SeatWarpMouse(seat, wind, x, y); } } @@ -939,7 +946,7 @@ static bool Wayland_SetRelativeMouseMode(bool enabled) return SDL_SetError("Failed to enable relative mode: compositor lacks support for the required zwp_pointer_constraints_v1 protocol"); } - data->relative_mode_enabled = enabled; + // Windows have a relative mode flag, so just update the grabs on a state change. Wayland_DisplayUpdatePointerGrabs(data, NULL); return true; } @@ -958,16 +965,22 @@ static bool Wayland_SetRelativeMouseMode(bool enabled) */ static SDL_MouseButtonFlags SDLCALL Wayland_GetGlobalMouseState(float *x, float *y) { - SDL_Mouse *mouse = SDL_GetMouse(); + const SDL_Mouse *mouse = SDL_GetMouse(); SDL_MouseButtonFlags result = 0; // If there is no window with mouse focus, we have no idea what the actual position or button state is. if (mouse->focus) { + SDL_VideoData *video_data = SDL_GetVideoDevice()->internal; + SDL_WaylandSeat *seat; int off_x, off_y; SDL_RelativeToGlobalForWindow(mouse->focus, mouse->focus->x, mouse->focus->y, &off_x, &off_y); - result = SDL_GetMouseState(x, y); *x = mouse->x + off_x; *y = mouse->y + off_y; + + // Query the buttons from the seats directly, as this may be called from within a hit test handler. + wl_list_for_each (seat, &video_data->seat_list, link) { + result |= seat->pointer.buttons_pressed; + } } else { *x = 0.f; *y = 0.f; @@ -1025,7 +1038,7 @@ void Wayland_RecreateCursors(void) } if (mouse->cur_cursor) { Wayland_RecreateCursor(mouse->cur_cursor, vdata); - if (mouse->cursor_shown) { + if (mouse->cursor_visible) { Wayland_ShowCursor(mouse->cur_cursor); } } @@ -1115,14 +1128,11 @@ void Wayland_SeatUpdateCursor(SDL_WaylandSeat *seat) SDL_Mouse *mouse = SDL_GetMouse(); SDL_WindowData *pointer_focus = seat->pointer.focus; - if (pointer_focus) { - const bool has_relative_focus = Wayland_SeatHasRelativePointerFocus(seat); - - if (!seat->display->relative_mode_enabled || !has_relative_focus || mouse->relative_mode_cursor_visible) { + if (pointer_focus && mouse->cursor_visible) { + if (!seat->pointer.relative_pointer || !mouse->relative_mode_hide_cursor) { const SDL_HitTestResult rc = pointer_focus->hit_test_result; - if ((seat->display->relative_mode_enabled && has_relative_focus) || - rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) { + if (seat->pointer.relative_pointer || rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) { Wayland_SeatSetCursor(seat, mouse->cur_cursor); } else { Wayland_SeatSetCursor(seat, sys_cursors[rc]); diff --git a/src/video/wayland/SDL_waylandsym.h b/src/video/wayland/SDL_waylandsym.h index 846c876a..d04223f3 100644 --- a/src/video/wayland/SDL_waylandsym.h +++ b/src/video/wayland/SDL_waylandsym.h @@ -84,13 +84,13 @@ SDL_WAYLAND_SYM(const char * const *, wl_proxy_get_tag, (struct wl_proxy *)) * non-optional when we are compiling against Wayland 1.20. We don't * explicitly call them ourselves, though, so if we are only compiling * against Wayland 1.18, they're unnecessary. */ -SDL_WAYLAND_SYM(struct wl_proxy*, wl_proxy_marshal_flags, (struct wl_proxy *proxy, uint32_t opcode, const struct wl_interface *interfac, uint32_t version, uint32_t flags, ...)) -SDL_WAYLAND_SYM(struct wl_proxy*, wl_proxy_marshal_array_flags, (struct wl_proxy *proxy, uint32_t opcode, const struct wl_interface *interface, uint32_t version, uint32_t flags, union wl_argument *args)) +SDL_WAYLAND_SYM(struct wl_proxy *, wl_proxy_marshal_flags, (struct wl_proxy *proxy, uint32_t opcode, const struct wl_interface *interfac, uint32_t version, uint32_t flags, ...)) +SDL_WAYLAND_SYM(struct wl_proxy *, wl_proxy_marshal_array_flags, (struct wl_proxy *proxy, uint32_t opcode, const struct wl_interface *interface, uint32_t version, uint32_t flags, union wl_argument *args)) #endif #if 0 // TODO RECONNECT: See waylandvideo.c for more information! #if SDL_WAYLAND_CHECK_VERSION(broken, on, purpose) -SDL_WAYLAND_SYM(int, wl_display_reconnect, (struct wl_display*)) +SDL_WAYLAND_SYM(int, wl_display_reconnect, (struct wl_display *)) #endif #endif // 0 @@ -149,7 +149,7 @@ SDL_WAYLAND_SYM(void, xkb_compose_state_unref, (struct xkb_compose_state *) ) SDL_WAYLAND_SYM(enum xkb_compose_feed_result, xkb_compose_state_feed, (struct xkb_compose_state *, xkb_keysym_t) ) SDL_WAYLAND_SYM(enum xkb_compose_status, xkb_compose_state_get_status, (struct xkb_compose_state *) ) SDL_WAYLAND_SYM(xkb_keysym_t, xkb_compose_state_get_one_sym, (struct xkb_compose_state *) ) -SDL_WAYLAND_SYM(void, xkb_keymap_key_for_each, (struct xkb_keymap *, xkb_keymap_key_iter_t, void*) ) +SDL_WAYLAND_SYM(void, xkb_keymap_key_for_each, (struct xkb_keymap *, xkb_keymap_key_iter_t, void *) ) SDL_WAYLAND_SYM(int, xkb_keymap_key_get_syms_by_level, (struct xkb_keymap *, xkb_keycode_t, xkb_layout_index_t, @@ -158,7 +158,10 @@ SDL_WAYLAND_SYM(int, xkb_keymap_key_get_syms_by_level, (struct xkb_keymap *, SDL_WAYLAND_SYM(uint32_t, xkb_keysym_to_utf32, (xkb_keysym_t) ) SDL_WAYLAND_SYM(uint32_t, xkb_keymap_mod_get_index, (struct xkb_keymap *, const char *) ) -SDL_WAYLAND_SYM(const char *, xkb_keymap_layout_get_name, (struct xkb_keymap*, xkb_layout_index_t)) +SDL_WAYLAND_SYM(const char *, xkb_keymap_layout_get_name, (struct xkb_keymap *, xkb_layout_index_t)) +#if SDL_XKBCOMMON_CHECK_VERSION(1, 10, 0) +SDL_WAYLAND_SYM(xkb_mod_mask_t, xkb_keymap_mod_get_mask, (struct xkb_keymap *, const char *)) +#endif #ifdef HAVE_LIBDECOR_H SDL_WAYLAND_MODULE(WAYLAND_LIBDECOR) diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index c185cb59..a1b52766 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -24,6 +24,7 @@ #ifdef SDL_VIDEO_DRIVER_WAYLAND #include "../../core/linux/SDL_system_theme.h" +#include "../../core/linux/SDL_progressbar.h" #include "../../events/SDL_events_c.h" #include "SDL_waylandclipboard.h" @@ -65,6 +66,7 @@ #include "xdg-shell-client-protocol.h" #include "xdg-toplevel-icon-v1-client-protocol.h" #include "color-management-v1-client-protocol.h" +#include "pointer-warp-v1-client-protocol.h" #ifdef HAVE_LIBDECOR_H #include @@ -446,9 +448,6 @@ static void Wayland_DeleteDevice(SDL_VideoDevice *device) WAYLAND_wl_display_disconnect(data->display); SDL_ClearProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER); } - if (device->wakeup_lock) { - SDL_DestroyMutex(device->wakeup_lock); - } SDL_free(data); SDL_free(device); SDL_WAYLAND_UnloadSymbols(); @@ -495,6 +494,9 @@ static bool Wayland_IsPreferred(struct wl_display *display) wl_registry_destroy(registry); + if (!preferred_data.has_fifo_v1) { + SDL_LogInfo(SDL_LOG_CATEGORY_VIDEO, "This compositor lacks support for the fifo-v1 protocol; falling back to XWayland for GPU performance reasons (set SDL_VIDEO_DRIVER=wayland to override)"); + } return preferred_data.has_fifo_v1; } @@ -572,7 +574,6 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols) } device->internal = data; - device->wakeup_lock = SDL_CreateMutex(); // Set the function pointers device->VideoInit = Wayland_VideoInit; @@ -626,9 +627,13 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols) device->DestroyWindow = Wayland_DestroyWindow; device->SetWindowHitTest = Wayland_SetWindowHitTest; device->FlashWindow = Wayland_FlashWindow; +#ifdef SDL_USE_LIBDBUS + device->ApplyWindowProgress = DBUS_ApplyWindowProgress; +#endif // SDL_USE_LIBDBUS device->HasScreenKeyboardSupport = Wayland_HasScreenKeyboardSupport; device->ShowWindowSystemMenu = Wayland_ShowWindowSystemMenu; device->SyncWindow = Wayland_SyncWindow; + device->SetWindowFocusable = Wayland_SetWindowFocusable; #ifdef SDL_USE_LIBDBUS if (SDL_SystemTheme_Init()) @@ -1259,7 +1264,6 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); } else if (SDL_strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) { d->relative_pointer_manager = wl_registry_bind(d->registry, id, &zwp_relative_pointer_manager_v1_interface, 1); - Wayland_DisplayInitRelativePointerManager(d); } else if (SDL_strcmp(interface, "zwp_pointer_constraints_v1") == 0) { d->pointer_constraints = wl_registry_bind(d->registry, id, &zwp_pointer_constraints_v1_interface, 1); } else if (SDL_strcmp(interface, "zwp_keyboard_shortcuts_inhibit_manager_v1") == 0) { @@ -1308,6 +1312,8 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint } else if (SDL_strcmp(interface, "wp_color_manager_v1") == 0) { d->wp_color_manager_v1 = wl_registry_bind(d->registry, id, &wp_color_manager_v1_interface, 1); Wayland_InitColorManager(d); + } else if (SDL_strcmp(interface, "wp_pointer_warp_v1") == 0) { + d->wp_pointer_warp_v1 = wl_registry_bind(d->registry, id, &wp_pointer_warp_v1_interface, 1); } } @@ -1617,6 +1623,11 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this) data->wp_color_manager_v1 = NULL; } + if (data->wp_pointer_warp_v1) { + wp_pointer_warp_v1_destroy(data->wp_pointer_warp_v1); + data->wp_pointer_warp_v1 = NULL; + } + if (data->compositor) { wl_compositor_destroy(data->compositor); data->compositor = NULL; diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index 33f20919..e9647311 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -65,6 +65,7 @@ struct SDL_VideoData } shell; struct zwp_relative_pointer_manager_v1 *relative_pointer_manager; struct zwp_pointer_constraints_v1 *pointer_constraints; + struct wp_pointer_warp_v1 *wp_pointer_warp_v1; struct wp_cursor_shape_manager_v1 *cursor_shape_manager; struct wl_data_device_manager *data_device_manager; struct zwp_primary_selection_device_manager_v1 *primary_selection_device_manager; @@ -96,9 +97,7 @@ struct SDL_VideoData int output_count; int output_max; - bool relative_mode_enabled; bool display_externally_owned; - bool scale_to_display_enabled; }; diff --git a/src/video/wayland/SDL_waylandvulkan.c b/src/video/wayland/SDL_waylandvulkan.c index 956be463..30f7da68 100644 --- a/src/video/wayland/SDL_waylandvulkan.c +++ b/src/video/wayland/SDL_waylandvulkan.c @@ -115,7 +115,7 @@ void Wayland_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) } } -char const* const* Wayland_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count) +char const * const *Wayland_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count) { static const char *const extensionsForWayland[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME diff --git a/src/video/wayland/SDL_waylandvulkan.h b/src/video/wayland/SDL_waylandvulkan.h index 58be0bca..4a308eb1 100644 --- a/src/video/wayland/SDL_waylandvulkan.h +++ b/src/video/wayland/SDL_waylandvulkan.h @@ -35,7 +35,7 @@ extern bool Wayland_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); extern void Wayland_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -extern char const* const* Wayland_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); +extern char const * const *Wayland_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); extern bool Wayland_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index 8ef5794a..df0898e6 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -1677,7 +1677,7 @@ static const struct wp_color_management_surface_feedback_v1_listener color_manag feedback_surface_preferred_changed }; -static void SetKeyboardFocus(SDL_Window *window, bool set_focus) +static void Wayland_SetKeyboardFocus(SDL_Window *window, bool set_focus) { SDL_Window *toplevel = window; @@ -1686,7 +1686,7 @@ static void SetKeyboardFocus(SDL_Window *window, bool set_focus) toplevel = toplevel->parent; } - toplevel->internal->keyboard_focus = window; + toplevel->keyboard_focus = window; if (set_focus && !window->is_hiding && !window->is_destroying) { SDL_SetKeyboardFocus(window); @@ -1794,7 +1794,7 @@ static struct wl_callback_listener show_hide_sync_listener = { static void exported_handle_handler(void *data, struct zxdg_exported_v2 *zxdg_exported_v2, const char *handle) { - SDL_WindowData *wind = (SDL_WindowData*)data; + SDL_WindowData *wind = (SDL_WindowData *)data; SDL_PropertiesID props = SDL_GetWindowProperties(wind->sdlwindow); SDL_SetStringProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING, handle); @@ -1916,8 +1916,9 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) data->shell_surface.xdg.popup.xdg_positioner = xdg_wm_base_create_positioner(c->shell.xdg); xdg_positioner_set_anchor(data->shell_surface.xdg.popup.xdg_positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); xdg_positioner_set_anchor_rect(data->shell_surface.xdg.popup.xdg_positioner, 0, 0, parent->internal->current.logical_width, parent->internal->current.logical_width); - xdg_positioner_set_constraint_adjustment(data->shell_surface.xdg.popup.xdg_positioner, - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y); + + const Uint32 constraint = window->constrain_popup ? (XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y) : XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE; + xdg_positioner_set_constraint_adjustment(data->shell_surface.xdg.popup.xdg_positioner, constraint); xdg_positioner_set_gravity(data->shell_surface.xdg.popup.xdg_positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); xdg_positioner_set_size(data->shell_surface.xdg.popup.xdg_positioner, data->current.logical_width, data->current.logical_height); @@ -1946,8 +1947,8 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) wl_region_add(region, 0, 0, 0, 0); wl_surface_set_input_region(data->surface, region); wl_region_destroy(region); - } else if (window->flags & SDL_WINDOW_POPUP_MENU) { - SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus()); + } else if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + Wayland_SetKeyboardFocus(window, true); } SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_POPUP_POINTER, data->shell_surface.xdg.popup.xdg_popup); @@ -2064,7 +2065,7 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) data->show_hide_sync_required = true; struct wl_callback *cb = wl_display_sync(_this->internal->display); - wl_callback_add_listener(cb, &show_hide_sync_listener, (void*)((uintptr_t)window->id)); + wl_callback_add_listener(cb, &show_hide_sync_listener, (void *)((uintptr_t)window->id)); data->showing_window = true; SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_SHOWN, 0, 0); @@ -2094,21 +2095,10 @@ static void Wayland_ReleasePopup(SDL_VideoDevice *_this, SDL_Window *popup) return; } - if (popup->flags & SDL_WINDOW_POPUP_MENU) { - SDL_Window *new_focus = popup->parent; - bool set_focus = popup == SDL_GetKeyboardFocus(); - - // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed. - while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) { - new_focus = new_focus->parent; - - // If some window in the chain currently had focus, set it to the new lowest-level window. - if (!set_focus) { - set_focus = new_focus == SDL_GetKeyboardFocus(); - } - } - - SetKeyboardFocus(new_focus, set_focus); + if ((popup->flags & SDL_WINDOW_POPUP_MENU) && !(popup->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(popup, &new_focus); + Wayland_SetKeyboardFocus(new_focus, set_focus); } xdg_popup_destroy(popupdata->shell_surface.xdg.popup.xdg_popup); @@ -2191,7 +2181,7 @@ void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) wind->show_hide_sync_required = true; struct wl_callback *cb = wl_display_sync(_this->internal->display); - wl_callback_add_listener(cb, &show_hide_sync_listener, (void*)((uintptr_t)window->id)); + wl_callback_add_listener(cb, &show_hide_sync_listener, (void *)((uintptr_t)window->id)); } static void handle_xdg_activation_done(void *data, @@ -2219,7 +2209,7 @@ static const struct xdg_activation_token_v1_listener activation_listener_xdg = { * * As you might expect from Wayland, the general policy is to go with #2 unless * the client can prove to the compositor beyond a reasonable doubt that raising - * the window will not be malicuous behavior. + * the window will not be malicious behavior. * * For SDL this means RaiseWindow and FlashWindow both use the same protocol, * but in different ways: RaiseWindow will provide as _much_ information as @@ -3002,6 +2992,27 @@ bool Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window) return true; } +bool Wayland_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable) +{ + if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (!(window->flags & SDL_WINDOW_HIDDEN)) { + if (!focusable && (window->flags & SDL_WINDOW_INPUT_FOCUS)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); + Wayland_SetKeyboardFocus(new_focus, set_focus); + } else if (focusable) { + if (SDL_ShouldFocusPopup(window)) { + Wayland_SetKeyboardFocus(window, true); + } + } + } + + return true; + } + + return SDL_SetError("wayland: focus can only be toggled on popup menu windows"); +} + void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y) { SDL_WindowData *wind = window->internal; @@ -3082,6 +3093,12 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) WAYLAND_wl_display_roundtrip(data->display); } + /* The compositor should have relinquished keyboard, pointer, touch, and tablet tool focus when the toplevel + * window was destroyed upon being hidden, but there is no guarantee of this, so ensure that all references + * to the window held by seats are released before destroying the underlying surface and struct. + */ + Wayland_DisplayRemoveWindowReferencesFromSeats(data, wind); + #ifdef SDL_VIDEO_OPENGL_EGL if (wind->egl_surface) { SDL_EGL_DestroySurface(_this, wind->egl_surface); diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 327d2965..7099462a 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -126,17 +126,16 @@ struct SDL_WindowData SDL_DisplayData **outputs; int num_outputs; - SDL_Window *keyboard_focus; - char *app_id; double scale_factor; struct Wayland_SHMBuffer *icon_buffers; int icon_buffer_count; - // Keyboard and pointer focus refcount. + // Keyboard, pointer, and touch focus refcount. int keyboard_focus_count; int pointer_focus_count; + int active_touch_count; struct { @@ -249,6 +248,7 @@ extern void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y); extern void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); extern bool Wayland_SuspendScreenSaver(SDL_VideoDevice *_this); extern bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon); +extern bool Wayland_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable); extern float Wayland_GetWindowContentScale(SDL_VideoDevice *_this, SDL_Window *window); extern void *Wayland_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size); diff --git a/src/video/windows/SDL_surface_utils.c b/src/video/windows/SDL_surface_utils.c index 7f9245c1..3e804381 100644 --- a/src/video/windows/SDL_surface_utils.c +++ b/src/video/windows/SDL_surface_utils.c @@ -46,7 +46,7 @@ HICON CreateIconFromSurface(SDL_Surface *surface) bmpInfo.bmiHeader.biCompression = BI_RGB; HDC hdc = GetDC(NULL); - void* pBits = NULL; + void *pBits = NULL; HBITMAP hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, &pBits, NULL, 0); if (!hBitmap) { ReleaseDC(NULL, hdc); diff --git a/src/video/windows/SDL_windowsclipboard.c b/src/video/windows/SDL_windowsclipboard.c index 39f86ef3..4d1e9766 100644 --- a/src/video/windows/SDL_windowsclipboard.c +++ b/src/video/windows/SDL_windowsclipboard.c @@ -193,7 +193,7 @@ static bool WIN_SetClipboardText(SDL_VideoDevice *_this, const char *mime_type) clipboard_data = _this->clipboard_callback(_this->clipboard_userdata, mime_type, &clipboard_data_size); if (clipboard_data && clipboard_data_size > 0) { SIZE_T i, size; - LPTSTR tstr = (WCHAR *)SDL_iconv_string("UTF-16LE", "UTF-8", (const char *)clipboard_data, clipboard_data_size); + LPWSTR tstr = (WCHAR *)SDL_iconv_string("UTF-16LE", "UTF-8", (const char *)clipboard_data, clipboard_data_size); if (!tstr) { return SDL_SetError("Couldn't convert text from UTF-8"); } @@ -210,7 +210,7 @@ static bool WIN_SetClipboardText(SDL_VideoDevice *_this, const char *mime_type) // Save the data to the clipboard hMem = GlobalAlloc(GMEM_MOVEABLE, size); if (hMem) { - LPTSTR dst = (LPTSTR)GlobalLock(hMem); + LPWSTR dst = (LPWSTR)GlobalLock(hMem); if (dst) { // Copy the text over, adding carriage returns as necessary for (i = 0; tstr[i]; ++i) { diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index 7c0c5590..58fafd38 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -321,42 +321,7 @@ static void WIN_CheckAsyncMouseRelease(Uint64 timestamp, SDL_WindowData *data) data->mouse_button_flags = (WPARAM)-1; } -static void WIN_UpdateMouseCapture(void) -{ - SDL_Window *focusWindow = SDL_GetKeyboardFocus(); - - if (focusWindow && (focusWindow->flags & SDL_WINDOW_MOUSE_CAPTURE)) { - SDL_WindowData *data = focusWindow->internal; - - if (!data->mouse_tracked) { - POINT cursorPos; - - if (GetCursorPos(&cursorPos) && ScreenToClient(data->hwnd, &cursorPos)) { - bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0; - SDL_MouseID mouseID = SDL_GLOBAL_MOUSE_ID; - - SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, mouseID, false, (float)cursorPos.x, (float)cursorPos.y); - SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, - !swapButtons ? SDL_BUTTON_LEFT : SDL_BUTTON_RIGHT, - (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0); - SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, - !swapButtons ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT, - (GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0); - SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, - SDL_BUTTON_MIDDLE, - (GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0); - SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, - SDL_BUTTON_X1, - (GetAsyncKeyState(VK_XBUTTON1) & 0x8000) != 0); - SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, - SDL_BUTTON_X2, - (GetAsyncKeyState(VK_XBUTTON2) & 0x8000) != 0); - } - } - } -} - -static void WIN_UpdateFocus(SDL_Window *window, bool expect_focus) +static void WIN_UpdateFocus(SDL_Window *window, bool expect_focus, DWORD pos) { SDL_WindowData *data = window->internal; HWND hwnd = data->hwnd; @@ -389,11 +354,12 @@ static void WIN_UpdateFocus(SDL_Window *window, bool expect_focus) } } - SDL_SetKeyboardFocus(data->keyboard_focus ? data->keyboard_focus : window); + SDL_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window); // In relative mode we are guaranteed to have mouse focus if we have keyboard focus if (!SDL_GetMouse()->relative_mode) { - GetCursorPos(&cursorPos); + cursorPos.x = (LONG)GET_X_LPARAM(pos); + cursorPos.y = (LONG)GET_Y_LPARAM(pos); ScreenToClient(hwnd, &cursorPos); SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, SDL_GLOBAL_MOUSE_ID, false, (float)cursorPos.x, (float)cursorPos.y); } @@ -476,11 +442,6 @@ static SDL_MOUSE_EVENT_SOURCE GetMouseMessageSource(ULONG extrainfo) return SDL_MOUSE_EVENT_SOURCE_PEN; } } - /* Sometimes WM_INPUT events won't have the correct touch signature, - so we have to rely purely on the touch bit being set. */ - if (SDL_TouchDevicesAvailable() && extrainfo & 0x80) { - return SDL_MOUSE_EVENT_SOURCE_TOUCH; - } return SDL_MOUSE_EVENT_SOURCE_MOUSE; } #endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) @@ -618,7 +579,8 @@ static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_VideoData *data, HANDL return; } - if (GetMouseMessageSource(rawmouse->ulExtraInformation) != SDL_MOUSE_EVENT_SOURCE_MOUSE) { + if (GetMouseMessageSource(rawmouse->ulExtraInformation) != SDL_MOUSE_EVENT_SOURCE_MOUSE || + (SDL_TouchDevicesAvailable() && (rawmouse->ulExtraInformation & 0x80) == 0x80)) { return; } @@ -739,6 +701,10 @@ static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_VideoData *data, HANDL float fAmount = (float)amount / WHEEL_DELTA; SDL_SendMouseWheel(WIN_GetEventTimestamp(), window, mouseID, fAmount, 0.0f, SDL_MOUSEWHEEL_NORMAL); } + + /* Invalidate the mouse button flags. If we don't do this then disabling raw input + will cause held down mouse buttons to persist when released. */ + windowdata->mouse_button_flags = (WPARAM)-1; } } @@ -1239,22 +1205,19 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara case WM_NCACTIVATE: { // Don't immediately clip the cursor in case we're clicking minimize/maximize buttons - // This is the only place that this flag is set. This causes all subsequent calls to - // WIN_UpdateClipCursor for this window to be no-ops in this frame's message-pumping. - // This flag is unset at the end of message pumping each frame for every window, and - // should never be carried over between frames. - data->skip_update_clipcursor = true; + data->postpone_clipcursor = true; + data->clipcursor_queued = true; /* Update the focus here, since it's possible to get WM_ACTIVATE and WM_SETFOCUS without actually being the foreground window, but this appears to get called in all cases where the global foreground window changes to and from this window. */ - WIN_UpdateFocus(data->window, !!wParam); + WIN_UpdateFocus(data->window, !!wParam, GetMessagePos()); } break; case WM_ACTIVATE: { // Update the focus in case we changed focus to a child window and then away from the application - WIN_UpdateFocus(data->window, !!LOWORD(wParam)); + WIN_UpdateFocus(data->window, !!LOWORD(wParam), GetMessagePos()); } break; case WM_MOUSEACTIVATE: @@ -1277,14 +1240,14 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara case WM_SETFOCUS: { // Update the focus in case it's changing between top-level windows in the same application - WIN_UpdateFocus(data->window, true); + WIN_UpdateFocus(data->window, true, GetMessagePos()); } break; case WM_KILLFOCUS: case WM_ENTERIDLE: { // Update the focus in case it's changing between top-level windows in the same application - WIN_UpdateFocus(data->window, false); + WIN_UpdateFocus(data->window, false, GetMessagePos()); } break; case WM_POINTERENTER: @@ -1365,8 +1328,10 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara const Uint64 timestamp = WIN_GetEventTimestamp(); SDL_Window *window = data->window; + const bool istouching = IS_POINTER_INCONTACT_WPARAM(wParam) && IS_POINTER_FIRSTBUTTON_WPARAM(wParam); + // if lifting off, do it first, so any motion changes don't cause app issues. - if (msg == WM_POINTERUP) { + if (!istouching) { SDL_SendPenTouch(timestamp, pen, window, (pen_info.penFlags & PEN_FLAG_INVERTED) != 0, false); } @@ -1396,7 +1361,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara } // if setting down, do it last, so the pen is positioned correctly from the first contact. - if (msg == WM_POINTERDOWN) { + if (istouching) { SDL_SendPenTouch(timestamp, pen, window, (pen_info.penFlags & PEN_FLAG_INVERTED) != 0, true); } @@ -1412,9 +1377,8 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara window->flags & (SDL_WINDOW_MOUSE_RELATIVE_MODE | SDL_WINDOW_MOUSE_GRABBED) || (window->mouse_rect.w > 0 && window->mouse_rect.h > 0) ); - if (wish_clip_cursor) { - data->skip_update_clipcursor = false; - WIN_UpdateClipCursor(window); + if (wish_clip_cursor) { // queue clipcursor refresh on pump finish + data->clipcursor_queued = true; } } @@ -1439,6 +1403,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, SDL_GLOBAL_MOUSE_ID, false, (float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam)); } } + } break; case WM_LBUTTONUP: @@ -1502,8 +1467,10 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara if (!(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) { if (SDL_GetMouseFocus() == data->window && !SDL_GetMouse()->relative_mode && !IsIconic(hwnd)) { SDL_Mouse *mouse; + DWORD pos = GetMessagePos(); POINT cursorPos; - GetCursorPos(&cursorPos); + cursorPos.x = GET_X_LPARAM(pos); + cursorPos.y = GET_Y_LPARAM(pos); ScreenToClient(hwnd, &cursorPos); mouse = SDL_GetMouse(); if (!mouse->was_touch_mouse_events) { // we're not a touch handler causing a mouse leave? @@ -1642,7 +1609,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara // Reference: https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/ if (SendMessage(hwnd, WM_NCHITTEST, wParam, lParam) == HTCAPTION) { POINT cursorPos; - GetCursorPos(&cursorPos); + GetCursorPos(&cursorPos); // want the most current pos so as to not cause position change ScreenToClient(hwnd, &cursorPos); PostMessage(hwnd, WM_MOUSEMOVE, 0, cursorPos.x | (((Uint32)((Sint16)cursorPos.y)) << 16)); } @@ -1751,8 +1718,8 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara SDL_Window *win; const SDL_DisplayID original_displayID = data->last_displayID; const WINDOWPOS *windowpos = (WINDOWPOS *)lParam; - const bool iconic = IsIconic(hwnd); - const bool zoomed = IsZoomed(hwnd); + bool iconic; + bool zoomed; RECT rect; int x, y; int w, h; @@ -1761,6 +1728,11 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_SHOWN, 0, 0); } + // These must be set after sending SDL_EVENT_WINDOW_SHOWN as that may apply pending + // window operations that change the window state. + iconic = IsIconic(hwnd); + zoomed = IsZoomed(hwnd); + if (iconic) { SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); } else if (zoomed) { @@ -1856,6 +1828,9 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara data->initial_size_rect.bottom = data->window->y + data->window->h; SetTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks, USER_TIMER_MINIMUM, NULL); + + // Reset the keyboard, as we won't get any key up events during the modal loop + SDL_ResetKeyboard(); } } break; @@ -2521,10 +2496,6 @@ void WIN_PumpEvents(SDL_VideoDevice *_this) #pragma warning(pop) #endif int new_messages = 0; -#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) - const bool *keystate; - SDL_Window *focusWindow; -#endif if (_this->internal->gameinput_context) { WIN_UpdateGameInput(_this); @@ -2580,7 +2551,7 @@ void WIN_PumpEvents(SDL_VideoDevice *_this) You won't get a KEYUP until both are released, and that keyup will only be for the second key you released. Take heroic measures and check the keystate as of the last handled event, and if we think a key is pressed when Windows doesn't, unstick it in SDL's state. */ - keystate = SDL_GetKeyboardState(NULL); + const bool *keystate = SDL_GetKeyboardState(NULL); if (keystate[SDL_SCANCODE_LSHIFT] && !(GetKeyState(VK_LSHIFT) & 0x8000)) { SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_LSHIFT, false); } @@ -2591,7 +2562,7 @@ void WIN_PumpEvents(SDL_VideoDevice *_this) /* The Windows key state gets lost when using Windows+Space or Windows+G shortcuts and not grabbing the keyboard. Note: If we *are* grabbing the keyboard, GetKeyState() will return inaccurate results for VK_LWIN and VK_RWIN but we don't need it anyway. */ - focusWindow = SDL_GetKeyboardFocus(); + SDL_Window *focusWindow = SDL_GetKeyboardFocus(); if (!focusWindow || !(focusWindow->flags & SDL_WINDOW_KEYBOARD_GRABBED)) { if (keystate[SDL_SCANCODE_LGUI] && !(GetKeyState(VK_LWIN) & 0x8000)) { SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_LGUI, false); @@ -2601,21 +2572,68 @@ void WIN_PumpEvents(SDL_VideoDevice *_this) } } - // Update the clipping rect in case someone else has stolen it + // fire queued clipcursor refreshes if (_this) { SDL_Window *window = _this->windows; while (window) { + bool refresh_clipcursor = false; SDL_WindowData *data = window->internal; - if (data && data->skip_update_clipcursor) { - data->skip_update_clipcursor = false; + if (data) { + refresh_clipcursor = data->clipcursor_queued; + data->clipcursor_queued = false; // Must be cleared unconditionally. + data->postpone_clipcursor = false; // Must be cleared unconditionally. + // Must happen before UpdateClipCursor. + // Although its occurrence currently + // always coincides with the queuing of + // clipcursor, it is logically distinct + // and this coincidence might no longer + // be true in the future. + // Ergo this placement concordantly + // conveys its unconditionality + // vis-a-vis the queuing of clipcursor. + } + if (refresh_clipcursor) { WIN_UpdateClipCursor(window); } window = window->next; } } - // Update mouse capture - WIN_UpdateMouseCapture(); + // Synchronize internal mouse capture state to the most current cursor state + // since for whatever reason we are not depending exclusively on SetCapture/ + // ReleaseCapture to pipe in out-of-window mouse events. + // Formerly WIN_UpdateMouseCapture(). + // TODO: can this go before clipcursor? + focusWindow = SDL_GetKeyboardFocus(); + if (focusWindow && (focusWindow->flags & SDL_WINDOW_MOUSE_CAPTURE)) { + SDL_WindowData *data = focusWindow->internal; + + if (!data->mouse_tracked) { + POINT cursorPos; + + if (GetCursorPos(&cursorPos) && ScreenToClient(data->hwnd, &cursorPos)) { + bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0; + SDL_MouseID mouseID = SDL_GLOBAL_MOUSE_ID; + + SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, mouseID, false, (float)cursorPos.x, (float)cursorPos.y); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + !swapButtons ? SDL_BUTTON_LEFT : SDL_BUTTON_RIGHT, + (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + !swapButtons ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT, + (GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + SDL_BUTTON_MIDDLE, + (GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + SDL_BUTTON_X1, + (GetAsyncKeyState(VK_XBUTTON1) & 0x8000) != 0); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + SDL_BUTTON_X2, + (GetAsyncKeyState(VK_XBUTTON2) & 0x8000) != 0); + } + } + } if (!_this->internal->gameinput_context) { WIN_CheckKeyboardAndMouseHotplug(_this, false); diff --git a/src/video/windows/SDL_windowsgameinput.cpp b/src/video/windows/SDL_windowsgameinput.cpp index 7ea77ecc..265cb6dd 100644 --- a/src/video/windows/SDL_windowsgameinput.cpp +++ b/src/video/windows/SDL_windowsgameinput.cpp @@ -185,7 +185,7 @@ done: static void CALLBACK GAMEINPUT_InternalDeviceCallback( _In_ GameInputCallbackToken callbackToken, - _In_ void* context, + _In_ void *context, _In_ IGameInputDevice *pDevice, _In_ uint64_t timestamp, _In_ GameInputDeviceStatus currentStatus, @@ -279,6 +279,9 @@ static void GAMEINPUT_InitialMouseReading(WIN_GameInputData *data, SDL_Window *w bool down = ((state.buttons & mask) != 0); SDL_SendMouseButton(timestamp, window, mouseID, GAMEINPUT_button_map[i], down); } + + // Invalidate mouse button flags + window->internal->mouse_button_flags = (WPARAM)-1; } } @@ -308,6 +311,9 @@ static void GAMEINPUT_HandleMouseDelta(WIN_GameInputData *data, SDL_Window *wind SDL_SendMouseButton(timestamp, window, mouseID, GAMEINPUT_button_map[i], down); } } + + // Invalidate mouse button flags + window->internal->mouse_button_flags = (WPARAM)-1; } if (delta.wheelX || delta.wheelY) { float fAmountX = (float)delta.wheelX / WHEEL_DELTA; @@ -594,7 +600,7 @@ void WIN_QuitGameInput(SDL_VideoDevice *_this) #else // !HAVE_GAMEINPUT_H -bool WIN_InitGameInput(SDL_VideoDevice* _this) +bool WIN_InitGameInput(SDL_VideoDevice *_this) { return SDL_Unsupported(); } @@ -604,12 +610,12 @@ bool WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this) return SDL_Unsupported(); } -void WIN_UpdateGameInput(SDL_VideoDevice* _this) +void WIN_UpdateGameInput(SDL_VideoDevice *_this) { return; } -void WIN_QuitGameInput(SDL_VideoDevice* _this) +void WIN_QuitGameInput(SDL_VideoDevice *_this) { return; } diff --git a/src/video/windows/SDL_windowsmodes.c b/src/video/windows/SDL_windowsmodes.c index 77ebab29..34c5bd73 100644 --- a/src/video/windows/SDL_windowsmodes.c +++ b/src/video/windows/SDL_windowsmodes.c @@ -46,7 +46,7 @@ static void WIN_UpdateDisplayMode(SDL_VideoDevice *_this, LPCWSTR deviceName, DW data->DeviceMode.dmFields = (DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS); // NOLINTNEXTLINE(bugprone-assignment-in-if-condition): No simple way to extract the assignment - if (index == ENUM_CURRENT_SETTINGS && (hdc = CreateDC(deviceName, NULL, NULL, NULL)) != NULL) { + if (index == ENUM_CURRENT_SETTINGS && (hdc = CreateDCW(deviceName, NULL, NULL, NULL)) != NULL) { char bmi_data[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)]; LPBITMAPINFO bmi; HBITMAP hbm; @@ -158,7 +158,7 @@ static void WIN_ReleaseDXGIOutput(void *dxgi_output) #endif } -static SDL_DisplayOrientation WIN_GetNaturalOrientation(DEVMODE *mode) +static SDL_DisplayOrientation WIN_GetNaturalOrientation(DEVMODEW *mode) { int width = mode->dmPelsWidth; int height = mode->dmPelsHeight; @@ -177,7 +177,7 @@ static SDL_DisplayOrientation WIN_GetNaturalOrientation(DEVMODE *mode) } } -static SDL_DisplayOrientation WIN_GetDisplayOrientation(DEVMODE *mode) +static SDL_DisplayOrientation WIN_GetDisplayOrientation(DEVMODEW *mode) { if (WIN_GetNaturalOrientation(mode) == SDL_ORIENTATION_LANDSCAPE) { switch (mode->dmDisplayOrientation) { @@ -208,7 +208,7 @@ static SDL_DisplayOrientation WIN_GetDisplayOrientation(DEVMODE *mode) } } -static void WIN_GetRefreshRate(void *dxgi_output, DEVMODE *mode, int *numerator, int *denominator) +static void WIN_GetRefreshRate(void *dxgi_output, DEVMODEW *mode, int *numerator, int *denominator) { // We're not currently using DXGI to query display modes, so fake NTSC timings switch (mode->dmDisplayFrequency) { @@ -274,7 +274,7 @@ static float WIN_GetContentScale(SDL_VideoDevice *_this, HMONITOR hMonitor) static bool WIN_GetDisplayMode(SDL_VideoDevice *_this, void *dxgi_output, HMONITOR hMonitor, LPCWSTR deviceName, DWORD index, SDL_DisplayMode *mode, SDL_DisplayOrientation *natural_orientation, SDL_DisplayOrientation *current_orientation) { SDL_DisplayModeData *data; - DEVMODE devmode; + DEVMODEW devmode; devmode.dmSize = sizeof(devmode); devmode.dmDriverExtra = 0; diff --git a/src/video/windows/SDL_windowsmodes.h b/src/video/windows/SDL_windowsmodes.h index 3d294c31..e49817ca 100644 --- a/src/video/windows/SDL_windowsmodes.h +++ b/src/video/windows/SDL_windowsmodes.h @@ -41,7 +41,7 @@ struct SDL_DisplayData struct SDL_DisplayModeData { - DEVMODE DeviceMode; + DEVMODEW DeviceMode; }; extern bool WIN_InitModes(SDL_VideoDevice *_this); diff --git a/src/video/windows/SDL_windowsmouse.c b/src/video/windows/SDL_windowsmouse.c index 3d6bcc42..17666e63 100644 --- a/src/video/windows/SDL_windowsmouse.c +++ b/src/video/windows/SDL_windowsmouse.c @@ -205,39 +205,30 @@ static HBITMAP CreateMaskBitmap(SDL_Surface *surface, bool is_monochrome) static HCURSOR WIN_CreateHCursor(SDL_Surface *surface, int hot_x, int hot_y) { - HCURSOR hcursor; - ICONINFO ii; + HCURSOR hcursor = NULL; bool is_monochrome = IsMonochromeSurface(surface); - - SDL_zero(ii); - ii.fIcon = FALSE; - ii.xHotspot = (DWORD)hot_x; - ii.yHotspot = (DWORD)hot_y; - ii.hbmMask = CreateMaskBitmap(surface, is_monochrome); - ii.hbmColor = is_monochrome ? NULL : CreateColorBitmap(surface); + ICONINFO ii = { + .fIcon = FALSE, + .xHotspot = (DWORD)hot_x, + .yHotspot = (DWORD)hot_y, + .hbmMask = CreateMaskBitmap(surface, is_monochrome), + .hbmColor = is_monochrome ? NULL : CreateColorBitmap(surface) + }; if (!ii.hbmMask || (!is_monochrome && !ii.hbmColor)) { SDL_SetError("Couldn't create cursor bitmaps"); - if (ii.hbmMask) { - DeleteObject(ii.hbmMask); - } - if (ii.hbmColor) { - DeleteObject(ii.hbmColor); - } - return NULL; + goto cleanup; } hcursor = CreateIconIndirect(&ii); if (!hcursor) { - WIN_SetError("CreateIconIndirect()"); - DeleteObject(ii.hbmMask); - if (ii.hbmColor) { - DeleteObject(ii.hbmColor); - } - return NULL; + WIN_SetError("CreateIconIndirect failed"); } - DeleteObject(ii.hbmMask); +cleanup: + if (ii.hbmMask) { + DeleteObject(ii.hbmMask); + } if (ii.hbmColor) { DeleteObject(ii.hbmColor); } @@ -705,8 +696,8 @@ static void ReadMouseCurve(int v, Uint64 xs[5], Uint64 ys[5]) ys[0] = 0; // first node must always be origin int i; for (i = 1; i < 5; i++) { - xs[i] = (7 * (Uint64)xbuff[i*2]); - ys[i] = (v * (Uint64)ybuff[i*2]) << 17; + xs[i] = (7 * (Uint64)xbuff[i * 2]); + ys[i] = (v * (Uint64)ybuff[i * 2]) << 17; } } diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 25dea1f3..2c7fbe29 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -124,9 +124,6 @@ static void WIN_DeleteDevice(SDL_VideoDevice *device) SDL_UnloadObject(data->dxgiDLL); } #endif - if (device->wakeup_lock) { - SDL_DestroyMutex(device->wakeup_lock); - } SDL_free(device->internal->rawinput); SDL_free(device->internal); SDL_free(device); @@ -152,7 +149,6 @@ static SDL_VideoDevice *WIN_CreateDevice(void) return NULL; } device->internal = data; - device->wakeup_lock = SDL_CreateMutex(); device->system_theme = WIN_GetSystemTheme(); #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) @@ -172,8 +168,8 @@ static SDL_VideoDevice *WIN_CreateDevice(void) data->GetDpiForWindow = (UINT (WINAPI *)(HWND))SDL_LoadFunction(data->userDLL, "GetDpiForWindow"); data->AreDpiAwarenessContextsEqual = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT))SDL_LoadFunction(data->userDLL, "AreDpiAwarenessContextsEqual"); data->IsValidDpiAwarenessContext = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT))SDL_LoadFunction(data->userDLL, "IsValidDpiAwarenessContext"); - data->GetDisplayConfigBufferSizes = (LONG (WINAPI *)(UINT32,UINT32*,UINT32* ))SDL_LoadFunction(data->userDLL, "GetDisplayConfigBufferSizes"); - data->QueryDisplayConfig = (LONG (WINAPI *)(UINT32,UINT32*,DISPLAYCONFIG_PATH_INFO*,UINT32*,DISPLAYCONFIG_MODE_INFO*,DISPLAYCONFIG_TOPOLOGY_ID*))SDL_LoadFunction(data->userDLL, "QueryDisplayConfig"); + data->GetDisplayConfigBufferSizes = (LONG (WINAPI *)(UINT32,UINT32 *,UINT32 *))SDL_LoadFunction(data->userDLL, "GetDisplayConfigBufferSizes"); + data->QueryDisplayConfig = (LONG (WINAPI *)(UINT32,UINT32 *,DISPLAYCONFIG_PATH_INFO*,UINT32 *,DISPLAYCONFIG_MODE_INFO*,DISPLAYCONFIG_TOPOLOGY_ID*))SDL_LoadFunction(data->userDLL, "QueryDisplayConfig"); data->DisplayConfigGetDeviceInfo = (LONG (WINAPI *)(DISPLAYCONFIG_DEVICE_INFO_HEADER*))SDL_LoadFunction(data->userDLL, "DisplayConfigGetDeviceInfo"); data->GetPointerType = (BOOL (WINAPI *)(UINT32, POINTER_INPUT_TYPE *))SDL_LoadFunction(data->userDLL, "GetPointerType"); data->GetPointerPenInfo = (BOOL (WINAPI *)(UINT32, POINTER_PEN_INFO *))SDL_LoadFunction(data->userDLL, "GetPointerPenInfo"); @@ -628,7 +624,7 @@ bool D3D_LoadDLL(void **pD3DDLL, IDirect3D9 **pDirect3D9Interface) if (*pD3DDLL) { /* *INDENT-OFF* */ // clang-format off typedef IDirect3D9 *(WINAPI *Direct3DCreate9_t)(UINT SDKVersion); - typedef HRESULT (WINAPI* Direct3DCreate9Ex_t)(UINT SDKVersion, IDirect3D9Ex** ppD3D); + typedef HRESULT (WINAPI* Direct3DCreate9Ex_t)(UINT SDKVersion, IDirect3D9Ex **ppD3D); /* *INDENT-ON* */ // clang-format on Direct3DCreate9_t Direct3DCreate9Func; diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index 0e9c50eb..fd321610 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -448,8 +448,8 @@ struct SDL_VideoData BOOL (WINAPI *AreDpiAwarenessContextsEqual)(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT); BOOL (WINAPI *IsValidDpiAwarenessContext)(DPI_AWARENESS_CONTEXT); // DisplayConfig functions - LONG (WINAPI *GetDisplayConfigBufferSizes)( UINT32, UINT32*, UINT32* ); - LONG (WINAPI *QueryDisplayConfig)( UINT32, UINT32*, DISPLAYCONFIG_PATH_INFO*, UINT32*, DISPLAYCONFIG_MODE_INFO*, DISPLAYCONFIG_TOPOLOGY_ID*); + LONG (WINAPI *GetDisplayConfigBufferSizes)( UINT32, UINT32 *, UINT32 *); + LONG (WINAPI *QueryDisplayConfig)( UINT32, UINT32 *, DISPLAYCONFIG_PATH_INFO*, UINT32 *, DISPLAYCONFIG_MODE_INFO*, DISPLAYCONFIG_TOPOLOGY_ID*); LONG (WINAPI *DisplayConfigGetDeviceInfo)( DISPLAYCONFIG_DEVICE_INFO_HEADER*); /* *INDENT-ON* */ // clang-format on diff --git a/src/video/windows/SDL_windowsvulkan.c b/src/video/windows/SDL_windowsvulkan.c index 0b16d550..04c87a31 100644 --- a/src/video/windows/SDL_windowsvulkan.c +++ b/src/video/windows/SDL_windowsvulkan.c @@ -110,8 +110,7 @@ void WIN_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) } } -char const* const* WIN_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - Uint32 *count) +char const * const *WIN_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count) { static const char *const extensionsForWin32[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME diff --git a/src/video/windows/SDL_windowsvulkan.h b/src/video/windows/SDL_windowsvulkan.h index 34f3a66a..8652b952 100644 --- a/src/video/windows/SDL_windowsvulkan.h +++ b/src/video/windows/SDL_windowsvulkan.h @@ -35,7 +35,7 @@ extern bool WIN_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); extern void WIN_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -extern char const* const* WIN_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); +extern char const * const *WIN_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); extern bool WIN_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c index 91c7ac93..fe61315a 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -33,6 +33,7 @@ #include "../SDL_sysvideo.h" #include "SDL_windowsvideo.h" +#include "SDL_windowskeyboard.h" #include "SDL_windowswindow.h" // Dropfile support @@ -185,7 +186,7 @@ static DWORD GetWindowStyleEx(SDL_Window *window) } #ifdef HAVE_SHOBJIDL_CORE_H -static ITaskbarList3 *GetTaskbarList(SDL_Window* window) +static ITaskbarList3 *GetTaskbarList(SDL_Window *window) { const SDL_WindowData *data = window->internal; SDL_assert(data->taskbar_button_created); @@ -376,6 +377,10 @@ bool WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags, SDL_WindowRec // Update any child windows for (child_window = window->first_child; child_window; child_window = child_window->next_sibling) { + if (!child_window->internal) { + // This child window is not yet fully initialized. + continue; + } if (!WIN_SetWindowPositionInternal(child_window, flags, SDL_WINDOWRECT_CURRENT)) { result = false; } @@ -662,7 +667,7 @@ static void CleanupWindowData(SDL_VideoDevice *_this, SDL_Window *window) static void WIN_ConstrainPopup(SDL_Window *window, bool output_to_pending) { - // Clamp popup windows to the output borders + // Possibly clamp popup windows to the output borders if (SDL_WINDOW_IS_POPUP(window)) { SDL_Window *w; SDL_DisplayID displayID; @@ -673,28 +678,30 @@ static void WIN_ConstrainPopup(SDL_Window *window, bool output_to_pending) const int height = window->last_size_pending ? window->pending.h : window->floating.h; int offset_x = 0, offset_y = 0; - // Calculate the total offset from the parents - for (w = window->parent; SDL_WINDOW_IS_POPUP(w); w = w->parent) { + if (window->constrain_popup) { + // Calculate the total offset from the parents + for (w = window->parent; SDL_WINDOW_IS_POPUP(w); w = w->parent) { + offset_x += w->x; + offset_y += w->y; + } + offset_x += w->x; offset_y += w->y; - } + abs_x += offset_x; + abs_y += offset_y; - offset_x += w->x; - offset_y += w->y; - abs_x += offset_x; - abs_y += offset_y; - - // Constrain the popup window to the display of the toplevel parent - displayID = SDL_GetDisplayForWindow(w); - SDL_GetDisplayBounds(displayID, &rect); - if (abs_x + width > rect.x + rect.w) { - abs_x -= (abs_x + width) - (rect.x + rect.w); + // Constrain the popup window to the display of the toplevel parent + displayID = SDL_GetDisplayForWindow(w); + SDL_GetDisplayBounds(displayID, &rect); + if (abs_x + width > rect.x + rect.w) { + abs_x -= (abs_x + width) - (rect.x + rect.w); + } + if (abs_y + height > rect.y + rect.h) { + abs_y -= (abs_y + height) - (rect.y + rect.h); + } + abs_x = SDL_max(abs_x, rect.x); + abs_y = SDL_max(abs_y, rect.y); } - if (abs_y + height > rect.y + rect.h) { - abs_y -= (abs_y + height) - (rect.y + rect.h); - } - abs_x = SDL_max(abs_x, rect.x); - abs_y = SDL_max(abs_y, rect.y); if (output_to_pending) { window->pending.x = abs_x - offset_x; @@ -719,7 +726,7 @@ static void WIN_SetKeyboardFocus(SDL_Window *window, bool set_active_focus) toplevel = toplevel->parent; } - toplevel->internal->keyboard_focus = window; + toplevel->keyboard_focus = window; if (set_active_focus && !window->is_hiding && !window->is_destroying) { SDL_SetKeyboardFocus(window); @@ -774,6 +781,9 @@ bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properties return false; } + // Ensure that the IME isn't active on the new window until explicitly requested. + WIN_StopTextInput(_this, window); + // Inform Windows of the frame change so we can respond to WM_NCCALCSIZE SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE); @@ -1056,8 +1066,9 @@ void WIN_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int * void WIN_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) { + SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; DWORD style; - HWND hwnd; bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, true); @@ -1066,20 +1077,33 @@ void WIN_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) WIN_SetWindowPosition(_this, window); } - hwnd = window->internal->hwnd; + // If the window isn't borderless and will be fullscreen, use the borderless style to hide the initial borders. + if (window->pending_flags & SDL_WINDOW_FULLSCREEN) { + if (!(window->flags & SDL_WINDOW_BORDERLESS)) { + window->flags |= SDL_WINDOW_BORDERLESS; + style = GetWindowLong(hwnd, GWL_STYLE); + style &= ~STYLE_MASK; + style |= GetWindowStyle(window); + SetWindowLong(hwnd, GWL_STYLE, style); + window->flags &= ~SDL_WINDOW_BORDERLESS; + } + } style = GetWindowLong(hwnd, GWL_EXSTYLE); if (style & WS_EX_NOACTIVATE) { bActivate = false; } + + data->showing_window = true; if (bActivate) { ShowWindow(hwnd, SW_SHOW); } else { // Use SetWindowPos instead of ShowWindow to avoid activating the parent window if this is a child window SetWindowPos(hwnd, NULL, 0, 0, 0, 0, window->internal->copybits_flag | SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); } + data->showing_window = false; - if (window->flags & SDL_WINDOW_POPUP_MENU && bActivate) { - WIN_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus()); + if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE) && bActivate) { + WIN_SetKeyboardFocus(window, true); } if (window->flags & SDL_WINDOW_MODAL) { WIN_SetWindowModal(_this, window, true); @@ -1096,21 +1120,10 @@ void WIN_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) ShowWindow(hwnd, SW_HIDE); - // Transfer keyboard focus back to the parent - if (window->flags & SDL_WINDOW_POPUP_MENU) { - SDL_Window *new_focus = window->parent; - bool set_focus = window == SDL_GetKeyboardFocus(); - - // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed. - while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) { - new_focus = new_focus->parent; - - // If some window in the chain currently had keyboard focus, set it to the new lowest-level window. - if (!set_focus) { - set_focus = new_focus == SDL_GetKeyboardFocus(); - } - } - + // Transfer keyboard focus back to the parent from a grabbing popup. + if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); WIN_SetKeyboardFocus(new_focus, set_focus); } } @@ -1148,7 +1161,7 @@ void WIN_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window) } if (bActivate) { SetForegroundWindow(hwnd); - if (window->flags & SDL_WINDOW_POPUP_MENU) { + if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { WIN_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus()); } } else { @@ -1235,10 +1248,12 @@ void WIN_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *data = window->internal; if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { - HWND hwnd = data->hwnd; - data->expected_resize = true; - ShowWindow(hwnd, SW_RESTORE); - data->expected_resize = false; + if (!data->showing_window || window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED)) { + HWND hwnd = data->hwnd; + data->expected_resize = true; + ShowWindow(hwnd, SW_RESTORE); + data->expected_resize = false; + } } else { data->windowed_mode_was_maximized = false; } @@ -1439,7 +1454,7 @@ void *WIN_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t char *filename_utf8; void *iccProfileData = NULL; - filename_utf8 = WIN_StringToUTF8(data->ICMFileName); + filename_utf8 = WIN_StringToUTF8W(data->ICMFileName); if (filename_utf8) { iccProfileData = SDL_LoadFile(filename_utf8, size); if (!iccProfileData) { @@ -1623,7 +1638,7 @@ void WIN_UnclipCursorForWindow(SDL_Window *window) { void WIN_UpdateClipCursor(SDL_Window *window) { SDL_WindowData *data = window->internal; - if (data->in_title_click || data->focus_click_pending || data->skip_update_clipcursor) { + if (data->in_title_click || data->focus_click_pending || data->postpone_clipcursor) { return; } @@ -2050,7 +2065,7 @@ static STDMETHODIMP SDLDropTarget_Drop(SDLDropTarget *target, ". In Drop Text for GlobalLock, format %08x '%s', memory (%lu) %p", fetc.cfFormat, format_mime, (unsigned long)bsize, buffer); if (buffer) { - buffer = WIN_StringToUTF8((const wchar_t *)buffer); + buffer = WIN_StringToUTF8W((const wchar_t *)buffer); if (buffer) { const size_t lbuffer = SDL_strlen((const char *)buffer); SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, @@ -2197,8 +2212,8 @@ void WIN_AcceptDragAndDrop(SDL_Window *window, bool accept) drop_target->lpVtbl = vtDropTarget; drop_target->window = window; drop_target->hwnd = data->hwnd; - drop_target->format_file = RegisterClipboardFormat(L"text/uri-list"); - drop_target->format_text = RegisterClipboardFormat(L"text/plain;charset=utf-8"); + drop_target->format_file = RegisterClipboardFormatW(L"text/uri-list"); + drop_target->format_text = RegisterClipboardFormatW(L"text/plain;charset=utf-8"); data->drop_target = drop_target; SDLDropTarget_AddRef(drop_target); RegisterDragDrop(data->hwnd, (LPDROPTARGET)drop_target); @@ -2249,7 +2264,7 @@ bool WIN_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperat return true; } -bool WIN_ApplyWindowProgress(SDL_VideoDevice *_this, SDL_Window* window) +bool WIN_ApplyWindowProgress(SDL_VideoDevice *_this, SDL_Window *window) { #ifdef HAVE_SHOBJIDL_CORE_H SDL_WindowData *data = window->internal; @@ -2315,24 +2330,40 @@ void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y) bool WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable) { - SDL_WindowData *data = window->internal; - HWND hwnd = data->hwnd; - const LONG style = GetWindowLong(hwnd, GWL_EXSTYLE); + if (!SDL_WINDOW_IS_POPUP(window)) { + SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; + const LONG style = GetWindowLong(hwnd, GWL_EXSTYLE); - SDL_assert(style != 0); + SDL_assert(style != 0); - if (focusable) { - if (style & WS_EX_NOACTIVATE) { - if (SetWindowLong(hwnd, GWL_EXSTYLE, style & ~WS_EX_NOACTIVATE) == 0) { - return WIN_SetError("SetWindowLong()"); + if (focusable) { + if (style & WS_EX_NOACTIVATE) { + if (SetWindowLong(hwnd, GWL_EXSTYLE, style & ~WS_EX_NOACTIVATE) == 0) { + return WIN_SetError("SetWindowLong()"); + } + } + } else { + if (!(style & WS_EX_NOACTIVATE)) { + if (SetWindowLong(hwnd, GWL_EXSTYLE, style | WS_EX_NOACTIVATE) == 0) { + return WIN_SetError("SetWindowLong()"); + } } } - } else { - if (!(style & WS_EX_NOACTIVATE)) { - if (SetWindowLong(hwnd, GWL_EXSTYLE, style | WS_EX_NOACTIVATE) == 0) { - return WIN_SetError("SetWindowLong()"); + } else if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (!(window->flags & SDL_WINDOW_HIDDEN)) { + if (!focusable && (window->flags & SDL_WINDOW_INPUT_FOCUS)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); + WIN_SetKeyboardFocus(new_focus, set_focus); + } else if (focusable) { + if (SDL_ShouldFocusPopup(window)) { + WIN_SetKeyboardFocus(window, true); + } } } + + return true; } return true; diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h index d23d83c0..8401ab65 100644 --- a/src/video/windows/SDL_windowswindow.h +++ b/src/video/windows/SDL_windowswindow.h @@ -78,11 +78,13 @@ struct SDL_WindowData bool in_border_change; bool in_title_click; Uint8 focus_click_pending; - bool skip_update_clipcursor; + bool postpone_clipcursor; + bool clipcursor_queued; bool windowed_mode_was_maximized; bool in_window_deactivation; bool force_ws_maximizebox; bool disable_move_size_events; + bool showing_window; int in_modal_loop; RECT initial_size_rect; RECT cursor_clipped_rect; // last successfully committed clipping rect for this window @@ -91,7 +93,6 @@ struct SDL_WindowData bool destroy_parent_with_window; SDL_DisplayID last_displayID; WCHAR *ICMFileName; - SDL_Window *keyboard_focus; SDL_WindowEraseBackgroundMode hint_erase_background_mode; bool taskbar_button_created; struct SDL_VideoData *videodata; diff --git a/src/video/x11/SDL_x11dyn.h b/src/video/x11/SDL_x11dyn.h index 9667ce64..b5bfbf5a 100644 --- a/src/video/x11/SDL_x11dyn.h +++ b/src/video/x11/SDL_x11dyn.h @@ -71,6 +71,9 @@ #ifdef SDL_VIDEO_DRIVER_X11_XSHAPE #include #endif +#ifdef SDL_VIDEO_DRIVER_X11_XTEST +#include +#endif #ifdef __cplusplus extern "C" { diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 96bf8c7a..ba0e7acf 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -771,7 +771,7 @@ static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xeven /* the new mime formats are the SDL_FORMATS property as an array of Atoms */ Atom atom = None; Atom *patom; - unsigned char* data = NULL; + unsigned char *data = NULL; int format_property = 0; unsigned long length = 0; unsigned long bytes_left = 0; @@ -780,8 +780,8 @@ static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xeven X11_XGetWindowProperty(display, GetWindow(_this), videodata->atoms.SDL_FORMATS, 0, 200, 0, XA_ATOM, &atom, &format_property, &length, &bytes_left, &data); - int allocationsize = (length + 1) * sizeof(char*); - for (j = 0, patom = (Atom*)data; j < length; j++, patom++) { + int allocationsize = (length + 1) * sizeof(char *); + for (j = 0, patom = (Atom *)data; j < length; j++, patom++) { char *atomStr = X11_XGetAtomName(display, *patom); allocationsize += SDL_strlen(atomStr) + 1; X11_XFree(atomStr); @@ -791,7 +791,7 @@ static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xeven if (new_mime_types) { char *strPtr = (char *)(new_mime_types + length + 1); - for (j = 0, patom = (Atom*)data; j < length; j++, patom++) { + for (j = 0, patom = (Atom *)data; j < length; j++, patom++) { char *atomStr = X11_XGetAtomName(display, *patom); new_mime_types[j] = strPtr; strPtr = stpcpy(strPtr, atomStr) + 1; @@ -1111,6 +1111,41 @@ void X11_GetBorderValues(SDL_WindowData *data) } } +void X11_EmitConfigureNotifyEvents(SDL_WindowData *data, XConfigureEvent *xevent) +{ + if (xevent->x != data->last_xconfigure.x || + xevent->y != data->last_xconfigure.y) { + if (!data->size_move_event_flags) { + SDL_Window *w; + int x = xevent->x; + int y = xevent->y; + + data->pending_operation &= ~X11_PENDING_OP_MOVE; + SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y); + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y); + + for (w = data->window->first_child; w; w = w->next_sibling) { + // Don't update hidden child popup windows, their relative position doesn't change + if (SDL_WINDOW_IS_POPUP(w) && !(w->flags & SDL_WINDOW_HIDDEN)) { + X11_UpdateWindowPosition(w, true); + } + } + } + } + + if (xevent->width != data->last_xconfigure.width || + xevent->height != data->last_xconfigure.height) { + if (!data->size_move_event_flags) { + data->pending_operation &= ~X11_PENDING_OP_RESIZE; + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED, + xevent->width, + xevent->height); + } + } + + SDL_copyp(&data->last_xconfigure, xevent); +} + static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) { SDL_VideoData *videodata = _this->internal; @@ -1306,8 +1341,10 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) SDL_SendMouseMotion(0, data->window, SDL_GLOBAL_MOUSE_ID, false, (float)xevent->xcrossing.x, (float)xevent->xcrossing.y); } - // We ungrab in LeaveNotify, so we may need to grab again here - SDL_UpdateWindowGrab(data->window); + // We ungrab in LeaveNotify, so we may need to grab again here, but not if captured, as the capture can be lost. + if (!(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) { + SDL_UpdateWindowGrab(data->window); + } X11_ProcessHitTest(_this, data, mouse->last_x, mouse->last_y, true); } break; @@ -1462,9 +1499,8 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) xevent->xconfigure.x, xevent->xconfigure.y, xevent->xconfigure.width, xevent->xconfigure.height); #endif - // Real configure notify events are relative to the parent, synthetic events are absolute. - if (!xevent->xconfigure.send_event) - { + // Real configure notify events are relative to the parent, synthetic events are absolute. + if (!xevent->xconfigure.send_event) { unsigned int NumChildren; Window ChildReturn, Root, Parent; Window *Children; @@ -1477,41 +1513,24 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) &ChildReturn); } - if (xevent->xconfigure.x != data->last_xconfigure.x || - xevent->xconfigure.y != data->last_xconfigure.y) { - if (!data->size_move_event_flags) { - SDL_Window *w; - int x = xevent->xconfigure.x; - int y = xevent->xconfigure.y; + /* Some window managers send ConfigureNotify before PropertyNotify when changing state (Xfce and + * fvwm are known to do this), which is backwards from other window managers, as well as what is + * expected by SDL and its clients. Defer emitting the size/move events until the corresponding + * PropertyNotify arrives for consistency. + */ + const Uint32 changed = X11_GetNetWMState(_this, data->window, xevent->xproperty.window) ^ data->window->flags; + if (changed & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED)) { + SDL_copyp(&data->pending_xconfigure, &xevent->xconfigure); + data->emit_size_move_after_property_notify = true; + } - data->pending_operation &= ~X11_PENDING_OP_MOVE; - SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y); - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y); - - for (w = data->window->first_child; w; w = w->next_sibling) { - // Don't update hidden child popup windows, their relative position doesn't change - if (SDL_WINDOW_IS_POPUP(w) && !(w->flags & SDL_WINDOW_HIDDEN)) { - X11_UpdateWindowPosition(w, true); - } - } - } + if (!data->emit_size_move_after_property_notify) { + X11_EmitConfigureNotifyEvents(data, &xevent->xconfigure); } #ifdef SDL_VIDEO_DRIVER_X11_XSYNC X11_HandleConfigure(data->window, &xevent->xconfigure); #endif /* SDL_VIDEO_DRIVER_X11_XSYNC */ - - if (xevent->xconfigure.width != data->last_xconfigure.width || - xevent->xconfigure.height != data->last_xconfigure.height) { - if (!data->size_move_event_flags) { - data->pending_operation &= ~X11_PENDING_OP_RESIZE; - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED, - xevent->xconfigure.width, - xevent->xconfigure.height); - } - } - - data->last_xconfigure = xevent->xconfigure; } break; // Have we been requested to quit (or another client message?) @@ -1909,6 +1928,10 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) } } } + if (data->emit_size_move_after_property_notify) { + X11_EmitConfigureNotifyEvents(data, &data->pending_xconfigure); + data->emit_size_move_after_property_notify = false; + } if ((flags & SDL_WINDOW_INPUT_FOCUS)) { if (data->pending_move) { DispatchWindowMove(_this, data, &data->pending_move_point); @@ -1928,13 +1951,12 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) right approach, but it seems to work. */ X11_UpdateKeymap(_this, true); } else if (xevent->xproperty.atom == videodata->atoms._NET_FRAME_EXTENTS) { - /* Events are disabled when leaving fullscreen until the borders appear to avoid - * incorrect size/position events. - */ + X11_GetBorderValues(data); if (data->size_move_event_flags) { + /* Events are disabled when leaving fullscreen until the borders appear to avoid + * incorrect size/position events on compositing window managers. + */ data->size_move_event_flags &= ~X11_SIZE_MOVE_EVENTS_WAIT_FOR_BORDERS; - X11_GetBorderValues(data); - } if (!(data->window->flags & SDL_WINDOW_FULLSCREEN) && data->toggle_borders) { data->toggle_borders = false; diff --git a/src/video/x11/SDL_x11keyboard.c b/src/video/x11/SDL_x11keyboard.c index cff07889..8406f486 100644 --- a/src/video/x11/SDL_x11keyboard.c +++ b/src/video/x11/SDL_x11keyboard.c @@ -762,7 +762,9 @@ void X11_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_Prop break; } (void)SDL_snprintf(deeplink, sizeof(deeplink), - "steam://open/keyboard?XPosition=0&YPosition=0&Width=0&Height=0&Mode=%d", + "steam://open/keyboard?XPosition=%i&YPosition=%i&Width=%i&Height=%i&Mode=%d", + window->text_input_rect.x, window->text_input_rect.y, + window->text_input_rect.w, window->text_input_rect.h, mode); SDL_OpenURL(deeplink); videodata->steam_keyboard_open = true; diff --git a/src/video/x11/SDL_x11messagebox.c b/src/video/x11/SDL_x11messagebox.c index 8aa1c6a8..35afe6a2 100644 --- a/src/video/x11/SDL_x11messagebox.c +++ b/src/video/x11/SDL_x11messagebox.c @@ -30,7 +30,10 @@ #include #include +#ifndef SDL_FORK_MESSAGEBOX #define SDL_FORK_MESSAGEBOX 1 +#endif + #define SDL_SET_LOCALE 1 #if SDL_FORK_MESSAGEBOX @@ -48,7 +51,7 @@ static const char g_MessageBoxFontLatin1[] = "-*-*-medium-r-normal--0-120-*-*-p-0-iso8859-1"; -static const char* g_MessageBoxFont[] = { +static const char *g_MessageBoxFont[] = { "-*-*-medium-r-normal--*-120-*-*-*-*-iso10646-1", // explicitly unicode (iso10646-1) "-*-*-medium-r-*--*-120-*-*-*-*-iso10646-1", // explicitly unicode (iso10646-1) "-misc-*-*-*-*--*-*-*-*-*-*-iso10646-1", // misc unicode (fix for some systems) @@ -68,10 +71,6 @@ static const SDL_MessageBoxColor g_default_colors[SDL_MESSAGEBOX_COLOR_COUNT] = { 205, 202, 53 }, // SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED, }; -#define SDL_MAKE_RGB(_r, _g, _b) (((Uint32)(_r) << 16) | \ - ((Uint32)(_g) << 8) | \ - ((Uint32)(_b))) - typedef struct SDL_MessageBoxButtonDataX11 { int x, y; // Text position @@ -95,6 +94,8 @@ typedef struct SDL_MessageBoxDataX11 Display *display; int screen; Window window; + Visual *visual; + Colormap cmap; #ifdef SDL_VIDEO_DRIVER_X11_XDBE XdbeBackBuffer buf; bool xdbe; // Whether Xdbe is present or not @@ -102,6 +103,9 @@ typedef struct SDL_MessageBoxDataX11 long event_mask; Atom wm_protocols; Atom wm_delete_message; +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR + bool xrandr; // Whether Xrandr is present or not +#endif int dialog_width; // Dialog box width. int dialog_height; // Dialog box height. @@ -122,7 +126,7 @@ typedef struct SDL_MessageBoxDataX11 const SDL_MessageBoxButtonData *buttondata; SDL_MessageBoxButtonDataX11 buttonpos[MAX_BUTTONS]; - Uint32 color[SDL_MESSAGEBOX_COLOR_COUNT]; + XColor xcolor[SDL_MESSAGEBOX_COLOR_COUNT]; const SDL_MessageBoxData *messageboxdata; } SDL_MessageBoxDataX11; @@ -199,7 +203,12 @@ static bool X11_MessageBoxInit(SDL_MessageBoxDataX11 *data, const SDL_MessageBox if (!data->display) { return SDL_SetError("Couldn't open X11 display"); } - + +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR + int xrandr_event_base, xrandr_error_base; + data->xrandr = X11_XRRQueryExtension(data->display, &xrandr_event_base, &xrandr_error_base); +#endif + #ifdef X_HAVE_UTF8_STRING if (SDL_X11_HAVE_UTF8) { char **missing = NULL; @@ -233,9 +242,12 @@ static bool X11_MessageBoxInit(SDL_MessageBoxDataX11 *data, const SDL_MessageBox colorhints = g_default_colors; } - // Convert our SDL_MessageBoxColor r,g,b values to packed RGB format. + // Convert colors to 16 bpc XColor format for (i = 0; i < SDL_MESSAGEBOX_COLOR_COUNT; i++) { - data->color[i] = SDL_MAKE_RGB(colorhints[i].r, colorhints[i].g, colorhints[i].b); + data->xcolor[i].flags = DoRed|DoGreen|DoBlue; + data->xcolor[i].red = colorhints[i].r * 257; + data->xcolor[i].green = colorhints[i].g * 257; + data->xcolor[i].blue = colorhints[i].b * 257; } return true; @@ -418,13 +430,20 @@ static void X11_MessageBoxShutdown(SDL_MessageBoxDataX11 *data) // Create and set up our X11 dialog box indow. static bool X11_MessageBoxCreateWindow(SDL_MessageBoxDataX11 *data) { - int x, y; + int x, y, i; XSizeHints *sizehints; XSetWindowAttributes wnd_attr; Atom _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_DIALOG; Display *display = data->display; SDL_WindowData *windowdata = NULL; const SDL_MessageBoxData *messageboxdata = data->messageboxdata; +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR +#ifdef XRANDR_DISABLED_BY_DEFAULT + const bool use_xrandr_by_default = false; +#else + const bool use_xrandr_by_default = true; +#endif +#endif if (messageboxdata->window) { SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(messageboxdata->window); @@ -434,17 +453,24 @@ static bool X11_MessageBoxCreateWindow(SDL_MessageBoxDataX11 *data) data->screen = DefaultScreen(display); } + data->visual = DefaultVisual(display, data->screen); + data->cmap = DefaultColormap(display, data->screen); + for (i = 0; i < SDL_MESSAGEBOX_COLOR_COUNT; i++) { + X11_XAllocColor(display, data->cmap, &data->xcolor[i]); + } + data->event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask | StructureNotifyMask | FocusChangeMask | PointerMotionMask; wnd_attr.event_mask = data->event_mask; + wnd_attr.colormap = data->cmap; data->window = X11_XCreateWindow( display, RootWindow(display, data->screen), 0, 0, data->dialog_width, data->dialog_height, - 0, CopyFromParent, InputOutput, CopyFromParent, - CWEventMask, &wnd_attr); + 0, DefaultDepth(display, data->screen), InputOutput, data->visual, + CWEventMask | CWColormap, &wnd_attr); if (data->window == None) { return SDL_SetError("Couldn't create X window"); } @@ -497,7 +523,29 @@ static bool X11_MessageBoxCreateWindow(SDL_MessageBoxDataX11 *data) const SDL_DisplayData *dpydata = dpy->internal; x = dpydata->x + ((dpy->current_mode->w - data->dialog_width) / 2); y = dpydata->y + ((dpy->current_mode->h - data->dialog_height) / 3); - } else { // oh well. This will misposition on a multi-head setup. Init first next time. + } +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR + else if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XRANDR, use_xrandr_by_default) && data->xrandr) { + XRRScreenResources *screen = X11_XRRGetScreenResourcesCurrent(display, DefaultRootWindow(display)); + if (!screen) { + goto XRANDRBAIL; + } + if (!screen->ncrtc) { + goto XRANDRBAIL; + } + + XRRCrtcInfo *crtc_info = X11_XRRGetCrtcInfo(display, screen, screen->crtcs[0]); + if (crtc_info) { + x = (crtc_info->width - data->dialog_width) / 2; + y = (crtc_info->height - data->dialog_height) / 3; + } else { + goto XRANDRBAIL; + } + } +#endif + else { + // oh well. This will misposition on a multi-head setup. Init first next time. + XRANDRBAIL: x = (DisplayWidth(display, data->screen) - data->dialog_width) / 2; y = (DisplayHeight(display, data->screen) - data->dialog_height) / 3; } @@ -552,10 +600,10 @@ static void X11_MessageBoxDraw(SDL_MessageBoxDataX11 *data, GC ctx) } #endif - X11_XSetForeground(display, ctx, data->color[SDL_MESSAGEBOX_COLOR_BACKGROUND]); + X11_XSetForeground(display, ctx, data->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].pixel); X11_XFillRectangle(display, window, ctx, 0, 0, data->dialog_width, data->dialog_height); - X11_XSetForeground(display, ctx, data->color[SDL_MESSAGEBOX_COLOR_TEXT]); + X11_XSetForeground(display, ctx, data->xcolor[SDL_MESSAGEBOX_COLOR_TEXT].pixel); for (i = 0; i < data->numlines; i++) { TextLineData *plinedata = &data->linedata[i]; @@ -579,17 +627,17 @@ static void X11_MessageBoxDraw(SDL_MessageBoxDataX11 *data, GC ctx) int border = (buttondata->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) ? 2 : 0; int offset = ((data->mouse_over_index == i) && (data->button_press_index == data->mouse_over_index)) ? 1 : 0; - X11_XSetForeground(display, ctx, data->color[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND]); + X11_XSetForeground(display, ctx, data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND].pixel); X11_XFillRectangle(display, window, ctx, buttondatax11->rect.x - border, buttondatax11->rect.y - border, buttondatax11->rect.w + 2 * border, buttondatax11->rect.h + 2 * border); - X11_XSetForeground(display, ctx, data->color[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER]); + X11_XSetForeground(display, ctx, data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].pixel); X11_XDrawRectangle(display, window, ctx, buttondatax11->rect.x, buttondatax11->rect.y, buttondatax11->rect.w, buttondatax11->rect.h); - X11_XSetForeground(display, ctx, (data->mouse_over_index == i) ? data->color[SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED] : data->color[SDL_MESSAGEBOX_COLOR_TEXT]); + X11_XSetForeground(display, ctx, (data->mouse_over_index == i) ? data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED].pixel : data->xcolor[SDL_MESSAGEBOX_COLOR_TEXT].pixel); #ifdef X_HAVE_UTF8_STRING if (SDL_X11_HAVE_UTF8) { @@ -640,8 +688,8 @@ static bool X11_MessageBoxLoop(SDL_MessageBoxDataX11 *data) #endif SDL_zero(ctx_vals); - ctx_vals.foreground = data->color[SDL_MESSAGEBOX_COLOR_BACKGROUND]; - ctx_vals.background = data->color[SDL_MESSAGEBOX_COLOR_BACKGROUND]; + ctx_vals.foreground = data->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].pixel; + ctx_vals.background = data->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].pixel; if (!have_utf8) { gcflags |= GCFont; diff --git a/src/video/x11/SDL_x11mouse.c b/src/video/x11/SDL_x11mouse.c index 343d8654..6cfe1c05 100644 --- a/src/video/x11/SDL_x11mouse.c +++ b/src/video/x11/SDL_x11mouse.c @@ -552,7 +552,7 @@ void X11_QuitMouse(SDL_VideoDevice *_this) void X11_SetHitTestCursor(SDL_HitTestResult rc) { if (rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) { - SDL_SetCursor(NULL); + SDL_RedrawCursor(); } else { X11_ShowCursor(sys_cursors[rc]); } diff --git a/src/video/x11/SDL_x11opengl.c b/src/video/x11/SDL_x11opengl.c index d46409f4..e005400c 100644 --- a/src/video/x11/SDL_x11opengl.c +++ b/src/video/x11/SDL_x11opengl.c @@ -610,9 +610,9 @@ static int X11_GL_GetAttributes(SDL_VideoDevice *_this, Display *display, int sc } //get the first transparent Visual -static XVisualInfo* X11_GL_GetTransparentVisualInfo(Display *display, int screen) +static XVisualInfo *X11_GL_GetTransparentVisualInfo(Display *display, int screen) { - XVisualInfo* visualinfo = NULL; + XVisualInfo *visualinfo = NULL; XVisualInfo vi_in; int out_count = 0; @@ -621,7 +621,7 @@ static XVisualInfo* X11_GL_GetTransparentVisualInfo(Display *display, int screen if (visualinfo != NULL) { int i = 0; for (i = 0; i < out_count; i++) { - XVisualInfo* v = &visualinfo[i]; + XVisualInfo *v = &visualinfo[i]; Uint32 format = X11_GetPixelFormatFromVisualInfo(display, v); if (SDL_ISPIXELFORMAT_ALPHA(format)) { vi_in.screen = screen; @@ -697,7 +697,7 @@ XVisualInfo *X11_GL_GetVisual(SDL_VideoDevice *_this, Display *display, int scre Uint32 format = X11_GetPixelFormatFromVisualInfo(display, vinfo); if (!SDL_ISPIXELFORMAT_ALPHA(format)) { // not transparent! - XVisualInfo* visualinfo = X11_GL_GetTransparentVisualInfo(display, screen); + XVisualInfo *visualinfo = X11_GL_GetTransparentVisualInfo(display, screen); if (visualinfo != NULL) { X11_XFree(vinfo); vinfo = visualinfo; @@ -856,7 +856,7 @@ SDL_GLContext X11_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window) if (transparent && (framebuffer_config != NULL)) { int i; for (i = 0; i < fbcount; i++) { - XVisualInfo* vinfo_temp = _this->gl_data->glXGetVisualFromFBConfig(display, framebuffer_config[i]); + XVisualInfo *vinfo_temp = _this->gl_data->glXGetVisualFromFBConfig(display, framebuffer_config[i]); if ( vinfo_temp != NULL) { Uint32 format = X11_GetPixelFormatFromVisualInfo(display, vinfo_temp); if (SDL_ISPIXELFORMAT_ALPHA(format)) { diff --git a/src/video/x11/SDL_x11pen.c b/src/video/x11/SDL_x11pen.c index f16da51c..c29c629c 100644 --- a/src/video/x11/SDL_x11pen.c +++ b/src/video/x11/SDL_x11pen.c @@ -283,14 +283,16 @@ static X11_PenHandle *X11_MaybeAddPen(SDL_VideoDevice *_this, const XIDeviceInfo X11_PenHandle *X11_MaybeAddPenByDeviceID(SDL_VideoDevice *_this, int deviceid) { - SDL_VideoData *data = _this->internal; - int num_device_info = 0; - XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, deviceid, &num_device_info); - if (device_info) { - SDL_assert(num_device_info == 1); - X11_PenHandle *handle = X11_MaybeAddPen(_this, device_info); - X11_XIFreeDeviceInfo(device_info); - return handle; + if (X11_Xinput2IsInitialized()) { + SDL_VideoData *data = _this->internal; + int num_device_info = 0; + XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, deviceid, &num_device_info); + if (device_info) { + SDL_assert(num_device_info == 1); + X11_PenHandle *handle = X11_MaybeAddPen(_this, device_info); + X11_XIFreeDeviceInfo(device_info); + return handle; + } } return NULL; } @@ -306,6 +308,10 @@ void X11_RemovePenByDeviceID(int deviceid) void X11_InitPen(SDL_VideoDevice *_this) { + if (!X11_Xinput2IsInitialized()) { + return; // we need XIQueryDevice() for this. + } + SDL_VideoData *data = _this->internal; #define LOOKUP_PEN_ATOM(X) X11_XInternAtom(data->display, X, False) diff --git a/src/video/x11/SDL_x11sym.h b/src/video/x11/SDL_x11sym.h index 26a3ce62..24f7eb29 100644 --- a/src/video/x11/SDL_x11sym.h +++ b/src/video/x11/SDL_x11sym.h @@ -45,6 +45,7 @@ SDL_X11_SYM(Cursor,XCreatePixmapCursor,(Display* a,Pixmap b,Pixmap c,XColor* d,X SDL_X11_SYM(Cursor,XCreateFontCursor,(Display* a,unsigned int b)) SDL_X11_SYM(XFontSet,XCreateFontSet,(Display* a, _Xconst char* b, char*** c, int* d, char** e)) SDL_X11_SYM(GC,XCreateGC,(Display* a,Drawable b,unsigned long c,XGCValues* d)) +SDL_X11_SYM(Status,XAllocColor,(Display *a, Colormap b, XColor *c)) SDL_X11_SYM(XImage*,XCreateImage,(Display* a,Visual* b,unsigned int c,int d,int e,char* f,unsigned int g,unsigned int h,int i,int j)) SDL_X11_SYM(Window,XCreateWindow,(Display* a,Window b,int c,int d,unsigned int e,unsigned int f,unsigned int g,int h,unsigned int i,Visual* j,unsigned long k,XSetWindowAttributes* l)) SDL_X11_SYM(int,XDefineCursor,(Display* a,Window b,Cursor c)) diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index a03b97fb..dd4e635d 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -25,6 +25,7 @@ #include // For getpid() and readlink() #include "../../core/linux/SDL_system_theme.h" +#include "../../core/linux/SDL_progressbar.h" #include "../../events/SDL_keyboard_c.h" #include "../../events/SDL_mouse_c.h" #include "../SDL_pixels_c.h" @@ -64,9 +65,6 @@ static void X11_DeleteDevice(SDL_VideoDevice *device) X11_XCloseDisplay(data->request_display); } SDL_free(data->windowlist); - if (device->wakeup_lock) { - SDL_DestroyMutex(device->wakeup_lock); - } SDL_free(device->internal); SDL_free(device); @@ -79,23 +77,6 @@ static bool X11_IsXWayland(Display *d) return X11_XQueryExtension(d, "XWAYLAND", &opcode, &event, &error) == True; } -static bool X11_CheckCurrentDesktop(const char *name) -{ - SDL_Environment *env = SDL_GetEnvironment(); - - const char *desktopVar = SDL_GetEnvironmentVariable(env, "DESKTOP_SESSION"); - if (desktopVar && SDL_strcasecmp(desktopVar, name) == 0) { - return true; - } - - desktopVar = SDL_GetEnvironmentVariable(env, "XDG_CURRENT_DESKTOP"); - if (desktopVar && SDL_strcasestr(desktopVar, name)) { - return true; - } - - return false; -} - static SDL_VideoDevice *X11_CreateDevice(void) { SDL_VideoDevice *device; @@ -147,8 +128,6 @@ static SDL_VideoDevice *X11_CreateDevice(void) return NULL; } - device->wakeup_lock = SDL_CreateMutex(); - #ifdef X11_DEBUG X11_XSynchronize(data->display, True); #endif @@ -204,6 +183,9 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->AcceptDragAndDrop = X11_AcceptDragAndDrop; device->UpdateWindowShape = X11_UpdateWindowShape; device->FlashWindow = X11_FlashWindow; +#ifdef SDL_USE_LIBDBUS + device->ApplyWindowProgress = DBUS_ApplyWindowProgress; +#endif // SDL_USE_LIBDBUS device->ShowWindowSystemMenu = X11_ShowWindowSystemMenu; device->SetWindowFocusable = X11_SetWindowFocusable; device->SyncWindow = X11_SyncWindow; @@ -276,17 +258,13 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->device_caps = VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT; - /* Openbox doesn't send the new window dimensions when entering fullscreen, so the events must be synthesized. - * This is otherwise not wanted, as it can break fullscreen window positioning on multi-monitor configurations. - */ - if (!X11_CheckCurrentDesktop("openbox")) { - device->device_caps |= VIDEO_DEVICE_CAPS_SENDS_DISPLAY_CHANGES; - } - data->is_xwayland = X11_IsXWayland(x11_display); if (data->is_xwayland) { + SDL_LogInfo(SDL_LOG_CATEGORY_VIDEO, "Detected XWayland"); + device->device_caps |= VIDEO_DEVICE_CAPS_MODE_SWITCHING_EMULATED | - VIDEO_DEVICE_CAPS_DISABLE_MOUSE_WARP_ON_FULLSCREEN_TRANSITIONS; + VIDEO_DEVICE_CAPS_DISABLE_MOUSE_WARP_ON_FULLSCREEN_TRANSITIONS | + VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS; } return device; diff --git a/src/video/x11/SDL_x11vulkan.c b/src/video/x11/SDL_x11vulkan.c index 065549b8..cf15a076 100644 --- a/src/video/x11/SDL_x11vulkan.c +++ b/src/video/x11/SDL_x11vulkan.c @@ -144,8 +144,7 @@ void X11_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) } } -char const* const* X11_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - Uint32 *count) +char const * const *X11_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count) { SDL_VideoData *videoData = _this->internal; if (videoData->vulkan_xlib_xcb_library) { diff --git a/src/video/x11/SDL_x11vulkan.h b/src/video/x11/SDL_x11vulkan.h index 2a625967..6fdededa 100644 --- a/src/video/x11/SDL_x11vulkan.h +++ b/src/video/x11/SDL_x11vulkan.h @@ -32,7 +32,7 @@ typedef xcb_connection_t *(*PFN_XGetXCBConnection)(Display *dpy); extern bool X11_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); extern void X11_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -extern char const* const* X11_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); +extern char const * const *X11_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); extern bool X11_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c index ca340953..b0f8fed6 100644 --- a/src/video/x11/SDL_x11window.c +++ b/src/video/x11/SDL_x11window.c @@ -71,7 +71,7 @@ static Bool isUnmapNotify(Display *dpy, XEvent *ev, XPointer win) // NOLINT(read /* static Bool isConfigureNotify(Display *dpy, XEvent *ev, XPointer win) { - return ev->type == ConfigureNotify && ev->xconfigure.window == *((Window*)win); + return ev->type == ConfigureNotify && ev->xconfigure.window == *((Window *)win); } static Bool X11_XIfEventTimeout(Display *display, XEvent *event_return, Bool (*predicate)(), XPointer arg, int timeoutMS) { @@ -86,6 +86,23 @@ static Bool X11_XIfEventTimeout(Display *display, XEvent *event_return, Bool (*p } */ +static bool X11_CheckCurrentDesktop(const char *name) +{ + SDL_Environment *env = SDL_GetEnvironment(); + const char *desktopVar = SDL_GetEnvironmentVariable(env, "DESKTOP_SESSION"); + if (desktopVar && SDL_strcasecmp(desktopVar, name) == 0) { + return true; + } + + desktopVar = SDL_GetEnvironmentVariable(env, "XDG_CURRENT_DESKTOP"); + + if (desktopVar && SDL_strcasestr(desktopVar, name)) { + return true; + } + + return false; +} + static bool X11_IsWindowMapped(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *data = window->internal; @@ -215,28 +232,30 @@ static void X11_ConstrainPopup(SDL_Window *window, bool output_to_pending) int abs_y = window->last_position_pending ? window->pending.y : window->floating.y; int offset_x = 0, offset_y = 0; - // Calculate the total offset from the parents - for (w = window->parent; SDL_WINDOW_IS_POPUP(w); w = w->parent) { + if (window->constrain_popup) { + // Calculate the total offset from the parents + for (w = window->parent; SDL_WINDOW_IS_POPUP(w); w = w->parent) { + offset_x += w->x; + offset_y += w->y; + } + offset_x += w->x; offset_y += w->y; - } + abs_x += offset_x; + abs_y += offset_y; - offset_x += w->x; - offset_y += w->y; - abs_x += offset_x; - abs_y += offset_y; + displayID = SDL_GetDisplayForWindow(w); - displayID = SDL_GetDisplayForWindow(w); - - SDL_GetDisplayBounds(displayID, &rect); - if (abs_x + window->w > rect.x + rect.w) { - abs_x -= (abs_x + window->w) - (rect.x + rect.w); + SDL_GetDisplayBounds(displayID, &rect); + if (abs_x + window->w > rect.x + rect.w) { + abs_x -= (abs_x + window->w) - (rect.x + rect.w); + } + if (abs_y + window->h > rect.y + rect.h) { + abs_y -= (abs_y + window->h) - (rect.y + rect.h); + } + abs_x = SDL_max(abs_x, rect.x); + abs_y = SDL_max(abs_y, rect.y); } - if (abs_y + window->h > rect.y + rect.h) { - abs_y -= (abs_y + window->h) - (rect.y + rect.h); - } - abs_x = SDL_max(abs_x, rect.x); - abs_y = SDL_max(abs_y, rect.y); if (output_to_pending) { window->pending.x = abs_x - offset_x; @@ -257,7 +276,7 @@ static void X11_SetKeyboardFocus(SDL_Window *window, bool set_active_focus) toplevel = toplevel->parent; } - toplevel->internal->keyboard_focus = window; + toplevel->keyboard_focus = window; if (set_active_focus && !window->is_hiding && !window->is_destroying) { SDL_SetKeyboardFocus(window); @@ -1550,9 +1569,9 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) X11_XFlush(display); } - // Popup menus grab the keyboard - if (window->flags & SDL_WINDOW_POPUP_MENU) { - X11_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus()); + // Grabbing popup menus get keyboard focus. + if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + X11_SetKeyboardFocus(window, true); } // Get some valid border values, if we haven't received them yet @@ -1574,6 +1593,15 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) X11_XMoveWindow(display, data->xwindow, x, y); } + /* XMonad ignores size hints and shrinks the client area to overlay borders on fixed-size windows, + * even if no borders were requested, resulting in the window client area being smaller than + * requested. Calling XResizeWindow after mapping seems to fix it, even though resizing fixed-size + * windows in this manner doesn't work on any other window manager. + */ + if (!(window->flags & SDL_WINDOW_RESIZABLE) && X11_CheckCurrentDesktop("xmonad")) { + X11_XResizeWindow(display, data->xwindow, window->w, window->h); + } + /* Some window managers can send garbage coordinates while mapping the window, so don't emit size and position * events during the initial configure events. */ @@ -1582,6 +1610,12 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) X11_PumpEvents(_this); data->size_move_event_flags = 0; + /* A MapNotify or PropertyNotify may not have arrived, so ensure that the shown event is dispatched + * to apply pending state before clearing the flag. + */ + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_SHOWN, 0, 0); + data->was_shown = true; + // If a configure event was received (type is non-zero), send the final window size and coordinates. if (data->last_xconfigure.type) { int x, y; @@ -1609,20 +1643,9 @@ void X11_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) } // Transfer keyboard focus back to the parent - if (window->flags & SDL_WINDOW_POPUP_MENU) { - SDL_Window *new_focus = window->parent; - bool set_focus = window == SDL_GetKeyboardFocus(); - - // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed. - while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) { - new_focus = new_focus->parent; - - // If some window in the chain currently had focus, set it to the new lowest-level window. - if (!set_focus) { - set_focus = new_focus == SDL_GetKeyboardFocus(); - } - } - + if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); X11_SetKeyboardFocus(new_focus, set_focus); } @@ -2340,21 +2363,37 @@ bool X11_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window) bool X11_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable) { - SDL_WindowData *data = window->internal; - Display *display = data->videodata->display; - XWMHints *wmhints; + if (!SDL_WINDOW_IS_POPUP(window)) { + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + XWMHints *wmhints; - wmhints = X11_XGetWMHints(display, data->xwindow); - if (!wmhints) { - return SDL_SetError("Couldn't get WM hints"); + wmhints = X11_XGetWMHints(display, data->xwindow); + if (!wmhints) { + return SDL_SetError("Couldn't get WM hints"); + } + + wmhints->input = focusable ? True : False; + wmhints->flags |= InputHint; + + X11_XSetWMHints(display, data->xwindow, wmhints); + X11_XFree(wmhints); + } else if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (!(window->flags & SDL_WINDOW_HIDDEN)) { + if (!focusable && (window->flags & SDL_WINDOW_INPUT_FOCUS)) { + SDL_Window *new_focus; + const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); + X11_SetKeyboardFocus(new_focus, set_focus); + } else if (focusable) { + if (SDL_ShouldFocusPopup(window)) { + X11_SetKeyboardFocus(window, true); + } + } + } + + return true; } - wmhints->input = focusable ? True : False; - wmhints->flags |= InputHint; - - X11_XSetWMHints(display, data->xwindow, wmhints); - X11_XFree(wmhints); - return true; } diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h index 2d7b3239..89333407 100644 --- a/src/video/x11/SDL_x11window.h +++ b/src/video/x11/SDL_x11window.h @@ -68,13 +68,13 @@ struct SDL_WindowData bool pending_move; SDL_Point pending_move_point; XConfigureEvent last_xconfigure; + XConfigureEvent pending_xconfigure; struct SDL_VideoData *videodata; unsigned long user_time; Atom xdnd_req; Window xdnd_source; bool flashing_window; Uint64 flash_cancel_time; - SDL_Window *keyboard_focus; #ifdef SDL_VIDEO_OPENGL_EGL EGLSurface egl_surface; #endif @@ -116,6 +116,7 @@ struct SDL_WindowData bool toggle_borders; bool fullscreen_borders_forced_on; bool was_shown; + bool emit_size_move_after_property_notify; SDL_HitTestResult hit_test_result; XPoint xim_spot; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 164e2a14..40156a6e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -32,8 +32,6 @@ if(NOT (MSVC AND SDL_CPU_ARM64)) find_package(OpenGL) endif() -set(SDL_TEST_EXECUTABLES) - add_library(sdltests_utils OBJECT testutils.c ) @@ -101,7 +99,7 @@ define_property(TARGET PROPERTY SDL_NONINTERACTIVE BRIEF_DOCS "If true, target i define_property(TARGET PROPERTY SDL_NONINTERACTIVE_ARGUMENTS BRIEF_DOCS "Argument(s) to run executable in non-interactive mode." FULL_DOCS "Argument(s) to run executable in non-interactive mode.") define_property(TARGET PROPERTY SDL_NONINTERACTIVE_TIMEOUT BRIEF_DOCS "Timeout for noninteractive executable." FULL_DOCS "Timeout for noninteractive executable.") -macro(add_sdl_test_executable TARGET) +function(add_sdl_test_executable TARGET) cmake_parse_arguments(AST "BUILD_DEPENDENT;NONINTERACTIVE;NEEDS_RESOURCES;TESTUTILS;THREADS;NO_C90;MAIN_CALLBACKS;NOTRACKMEM" "" "DEPENDS;DISABLE_THREADS_ARGS;NONINTERACTIVE_TIMEOUT;NONINTERACTIVE_ARGS;INSTALLED_ARGS;SOURCES" ${ARGN}) if(AST_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unknown argument(s): ${AST_UNPARSED_ARGUMENTS}") @@ -133,7 +131,8 @@ macro(add_sdl_test_executable TARGET) add_dependencies(${TARGET} ${AST_DEPENDS}) endif() - list(APPEND SDL_TEST_EXECUTABLES ${TARGET}) + set_propertY(TARGET ${TARGET} PROPERTY SDL_INSTALL "1") + set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY SDL_TEST_EXECUTABLES "${TARGET}") set_property(TARGET ${TARGET} PROPERTY SDL_NOTRACKMEM ${AST_NOTRACKMEM}) if(AST_NONINTERACTIVE) set_property(TARGET ${TARGET} PROPERTY SDL_NONINTERACTIVE 1) @@ -192,6 +191,14 @@ macro(add_sdl_test_executable TARGET) target_link_options(${TARGET} PRIVATE "SHELL:--pre-js ${CMAKE_CURRENT_SOURCE_DIR}/emscripten/pre.js") target_link_options(${TARGET} PRIVATE "-sEXIT_RUNTIME=1") set_property(TARGET ${TARGET} APPEND PROPERTY LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/emscripten/pre.js") + 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}") + if(NOT AST_MAIN_CALLBACKS) + set_property(TARGET ${TARGET} PROPERTY "EXCLUDE_FROM_ALL" "1") + set_propertY(TARGET ${TARGET} PROPERTY SDL_INSTALL "0") + endif() endif() if(OPENGL_FOUND) @@ -200,10 +207,10 @@ macro(add_sdl_test_executable TARGET) # FIXME: only add "${SDL3_BINARY_DIR}/include-config-$>" + include paths of external dependencies target_include_directories(${TARGET} PRIVATE "$") -endmacro() +endfunction() -check_include_file(signal.h HAVE_SIGNAL_H) -if(HAVE_SIGNAL_H) +check_include_file(signal.h LIBC_HAS_SIGNAL_H) +if(LIBC_HAS_SIGNAL_H) add_definitions(-DHAVE_SIGNAL_H) endif() @@ -413,7 +420,7 @@ add_sdl_test_executable(testdialog SOURCES testdialog.c) add_sdl_test_executable(testtime SOURCES testtime.c) add_sdl_test_executable(testmanymouse SOURCES testmanymouse.c) add_sdl_test_executable(testmodal SOURCES testmodal.c) -add_sdl_test_executable(testtray SOURCES testtray.c) +add_sdl_test_executable(testtray NEEDS_RESOURCES TESTUTILS SOURCES testtray.c) add_sdl_test_executable(testprocess @@ -425,6 +432,8 @@ add_sdl_test_executable(testprocess add_sdl_test_executable(childprocess SOURCES childprocess.c) add_dependencies(testprocess childprocess) +get_property(SDL_TEST_EXECUTABLES DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY SDL_TEST_EXECUTABLES) + if (HAVE_WAYLAND) # Set the GENERATED property on the protocol file, since it is first created at build time set_property(SOURCE ${SDL3_BINARY_DIR}/wayland-generated-protocols/xdg-shell-protocol.c PROPERTY GENERATED 1) @@ -654,10 +663,15 @@ if(SDL_INSTALL_TESTS) DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/installed-tests/SDL3 ) else() - install( - TARGETS ${SDL_TEST_EXECUTABLES} - DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/installed-tests/SDL3 - ) + foreach(test IN LISTS SDL_TEST_EXECUTABLES) + get_property(install_target TARGET ${test} PROPERTY "SDL_INSTALL") + if(install_target) + install( + TARGETS ${test} + DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/installed-tests/SDL3 + ) + endif() + endforeach() endif() if(MSVC) foreach(test IN LISTS SDL_TEST_EXECUTABLES) diff --git a/test/childprocess.c b/test/childprocess.c index 772d86d7..69a7cea2 100644 --- a/test/childprocess.c +++ b/test/childprocess.c @@ -2,6 +2,11 @@ #include #include +#ifdef SDL_PLATFORM_WINDOWS +#include +#include +#endif + #include #include @@ -102,6 +107,11 @@ int main(int argc, char *argv[]) { if (print_arguments) { int print_i; +#ifdef SDL_PLATFORM_WINDOWS + /* reopen stdout as binary to prevent newline conversion */ + _setmode(_fileno(stdout), _O_BINARY); +#endif + for (print_i = 0; i + print_i < argc; print_i++) { fprintf(stdout, "|%d=%s|\r\n", print_i, argv[i + print_i]); } diff --git a/test/gamepad_face_axby.bmp b/test/gamepad_face_axby.bmp new file mode 100755 index 00000000..3262729d Binary files /dev/null and b/test/gamepad_face_axby.bmp differ diff --git a/test/gamepad_face_axby.h b/test/gamepad_face_axby.h new file mode 100644 index 00000000..cf488bef --- /dev/null +++ b/test/gamepad_face_axby.h @@ -0,0 +1,2756 @@ +unsigned char gamepad_face_axby_bmp[] = { + 0x42, 0x4d, 0x06, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, + 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x50, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x03, 0x00, 0x00, 0x00, 0xc0, 0x80, + 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, + 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x26, 0x26, 0x26, 0x00, 0x29, 0x29, 0x29, 0x00, 0xc5, 0xc5, + 0xc5, 0x05, 0xc5, 0xc5, 0xc5, 0x04, 0xc5, 0xc5, 0xc5, 0x05, 0xc5, 0xc5, + 0xc5, 0x02, 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0xc8, 0xc8, + 0xc8, 0x02, 0xc5, 0xc5, 0xc5, 0x05, 0xc6, 0xc6, 0xc6, 0x06, 0xc7, 0xc7, + 0xc7, 0x07, 0x2b, 0x2b, 0x2b, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0xc7, 0xc7, 0xc7, 0x1d, 0xc7, 0xc7, 0xc7, 0x68, 0xc7, 0xc7, + 0xc7, 0x6a, 0xc7, 0xc7, 0xc7, 0x4a, 0xc7, 0xc7, 0xc7, 0x06, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0xca, 0xca, + 0xca, 0x09, 0xc7, 0xc7, 0xc7, 0x59, 0xc7, 0xc7, 0xc7, 0x6b, 0xc7, 0xc7, + 0xc7, 0x67, 0xc7, 0xc7, 0xc7, 0x2f, 0xc8, 0xc8, 0xc8, 0x0e, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x27, 0x27, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x2b, 0x2b, + 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x34, + 0x34, 0x00, 0xc6, 0xc6, 0xc6, 0x01, 0xc7, 0xc7, 0xc7, 0x54, 0xc7, 0xc7, + 0xc7, 0xf4, 0xc7, 0xc7, 0xc7, 0xff, 0xc7, 0xc7, 0xc7, 0xd7, 0xc6, 0xc6, + 0xc6, 0x21, 0xc5, 0xc5, 0xc5, 0x01, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0xc9, 0xc9, + 0xc9, 0x04, 0xc8, 0xc8, 0xc8, 0x3a, 0xc7, 0xc7, 0xc7, 0xea, 0xc7, 0xc7, + 0xc7, 0xff, 0xc7, 0xc7, 0xc7, 0xec, 0xc7, 0xc7, 0xc7, 0x3e, 0xc7, 0xc7, + 0xc7, 0x05, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x2b, 0x2b, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2b, 0x2b, + 0x2b, 0x00, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0xc3, 0xc3, 0x01, 0xc6, 0xc6, + 0xc6, 0x16, 0xc7, 0xc7, 0xc7, 0xbe, 0xc7, 0xc7, 0xc7, 0xfb, 0xc7, 0xc7, + 0xc7, 0xf3, 0xc7, 0xc7, 0xc7, 0x67, 0xc7, 0xc7, 0xc7, 0x07, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0xc7, 0xc7, 0xc7, 0x0b, 0xc7, 0xc7, 0xc7, 0x91, 0xc7, 0xc7, + 0xc7, 0xf6, 0xc7, 0xc7, 0xc7, 0xf8, 0xc7, 0xc7, 0xc7, 0xa5, 0xc6, 0xc6, + 0xc6, 0x0f, 0x24, 0x24, 0x24, 0x00, 0x27, 0x27, 0x27, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x28, 0x28, 0x28, 0x00, 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, + 0x29, 0x00, 0xc8, 0xc8, 0xc8, 0x08, 0xc8, 0xc8, 0xc8, 0x62, 0xc7, 0xc7, + 0xc7, 0xf1, 0xc7, 0xc7, 0xc7, 0xfc, 0xc6, 0xc6, 0xc6, 0xd3, 0xc7, 0xc7, + 0xc7, 0x89, 0xc7, 0xc7, 0xc7, 0x82, 0xc7, 0xc7, 0xc7, 0x82, 0xc7, 0xc7, + 0xc7, 0x82, 0xc7, 0xc7, 0xc7, 0x82, 0xc8, 0xc8, 0xc8, 0x8c, 0xc7, 0xc7, + 0xc7, 0xe8, 0xc7, 0xc7, 0xc7, 0xfe, 0xc7, 0xc7, 0xc7, 0xed, 0xc7, 0xc7, + 0xc7, 0x43, 0xc7, 0xc7, 0xc7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0xc4, 0xc4, 0x01, 0xc6, 0xc6, + 0xc6, 0x19, 0xc7, 0xc7, 0xc7, 0xcf, 0xc7, 0xc7, 0xc7, 0xfc, 0xc7, 0xc7, + 0xc7, 0xfd, 0xc7, 0xc7, 0xc7, 0xf9, 0xc7, 0xc7, 0xc7, 0xf9, 0xc7, 0xc7, + 0xc7, 0xf9, 0xc7, 0xc7, 0xc7, 0xf9, 0xc7, 0xc7, 0xc7, 0xf9, 0xc7, 0xc7, + 0xc7, 0xf9, 0xc7, 0xc7, 0xc7, 0xfe, 0xc7, 0xc7, 0xc7, 0xf9, 0xc6, 0xc6, + 0xc6, 0xb1, 0xc6, 0xc6, 0xc6, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x2b, 0x2b, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x26, + 0x26, 0x00, 0xc7, 0xc7, 0xc7, 0x09, 0xc7, 0xc7, 0xc7, 0x73, 0xc7, 0xc7, + 0xc7, 0xf4, 0xc7, 0xc7, 0xc7, 0xff, 0xc7, 0xc7, 0xc7, 0xf5, 0xc7, 0xc7, + 0xc7, 0xc6, 0xc7, 0xc7, 0xc7, 0xc1, 0xc7, 0xc7, 0xc7, 0xc2, 0xc7, 0xc7, + 0xc7, 0xd2, 0xc7, 0xc7, 0xc7, 0xfb, 0xc7, 0xc7, 0xc7, 0xff, 0xc7, 0xc7, + 0xc7, 0xed, 0xc8, 0xc8, 0xc8, 0x51, 0xc8, 0xc8, 0xc8, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x2b, 0x2b, 0x2b, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x28, 0x28, 0x28, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x28, 0x28, 0x28, 0x00, 0x27, 0x27, 0x27, 0x00, 0x27, 0x27, + 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0xc8, 0xc8, 0x02, 0xc8, 0xc8, + 0xc8, 0x24, 0xc7, 0xc7, 0xc7, 0xd8, 0xc7, 0xc7, 0xc7, 0xfc, 0xc7, 0xc7, + 0xc7, 0xe1, 0xc7, 0xc7, 0xc7, 0x2b, 0x29, 0x29, 0x29, 0x00, 0xc7, 0xc7, + 0xc7, 0x04, 0xc7, 0xc7, 0xc7, 0x67, 0xc7, 0xc7, 0xc7, 0xf2, 0xc7, 0xc7, + 0xc7, 0xfa, 0xc7, 0xc7, 0xc7, 0xbd, 0xc5, 0xc5, 0xc5, 0x13, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x28, 0x28, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2b, 0x2b, 0x2b, 0x00, 0x2d, 0x2d, + 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x28, + 0x28, 0x00, 0xc8, 0xc8, 0xc8, 0x0b, 0xc8, 0xc8, 0xc8, 0x85, 0xc7, 0xc7, + 0xc7, 0xf5, 0xc7, 0xc7, 0xc7, 0xf5, 0xc7, 0xc7, 0xc7, 0x84, 0xc7, 0xc7, + 0xc7, 0x0b, 0xc5, 0xc5, 0xc5, 0x16, 0xc7, 0xc7, 0xc7, 0xc6, 0xc7, 0xc7, + 0xc7, 0xfb, 0xc7, 0xc7, 0xc7, 0xf2, 0xc7, 0xc7, 0xc7, 0x5a, 0xc7, 0xc7, + 0xc7, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x27, 0x27, 0x27, 0x00, 0x26, 0x26, 0x26, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0xc8, 0xc8, 0x02, 0xc7, 0xc7, + 0xc7, 0x2c, 0xc7, 0xc7, 0xc7, 0xe2, 0xc7, 0xc7, 0xc7, 0xfc, 0xc7, 0xc7, + 0xc7, 0xd7, 0xc7, 0xc7, 0xc7, 0x25, 0xc7, 0xc7, 0xc7, 0x56, 0xc7, 0xc7, + 0xc7, 0xef, 0xc7, 0xc7, 0xc7, 0xfb, 0xc7, 0xc7, 0xc7, 0xc4, 0xc7, 0xc7, + 0xc7, 0x19, 0xc8, 0xc8, 0xc8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x00, 0x22, 0x22, + 0x22, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, + 0x29, 0x00, 0xc7, 0xc7, 0xc7, 0x0e, 0xc7, 0xc7, 0xc7, 0x96, 0xc7, 0xc7, + 0xc7, 0xf7, 0xc7, 0xc7, 0xc7, 0xf2, 0xc8, 0xc8, 0xc8, 0x7a, 0xc8, 0xc8, + 0xc8, 0xb9, 0xc7, 0xc7, 0xc7, 0xf9, 0xc7, 0xc7, 0xc7, 0xf1, 0xc8, 0xc8, + 0xc8, 0x68, 0xc8, 0xc8, 0xc8, 0x08, 0x00, 0x00, 0x00, 0x00, 0x23, 0x23, + 0x23, 0x00, 0x27, 0x27, 0x27, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x2c, 0x2c, 0x2c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x28, 0x28, 0x28, 0x00, 0x28, 0x28, 0x28, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2d, 0x2d, 0x2d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0xc8, 0xc8, 0x04, 0xc8, 0xc8, + 0xc8, 0x3b, 0xc7, 0xc7, 0xc7, 0xe7, 0xc7, 0xc7, 0xc7, 0xfc, 0xc6, 0xc6, + 0xc6, 0xe4, 0xc7, 0xc7, 0xc7, 0xfa, 0xc7, 0xc7, 0xc7, 0xfc, 0xc7, 0xc7, + 0xc7, 0xd1, 0xc6, 0xc6, 0xc6, 0x1b, 0xc2, 0xc2, 0xc2, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x26, + 0x26, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc8, 0xc8, 0xc8, 0x0e, 0xc8, 0xc8, 0xc8, 0xa9, 0xc7, 0xc7, + 0xc7, 0xf9, 0xc7, 0xc7, 0xc7, 0xff, 0xc7, 0xc7, 0xc7, 0xff, 0xc7, 0xc7, + 0xc7, 0xf4, 0xc7, 0xc7, 0xc7, 0x74, 0xc7, 0xc7, 0xc7, 0x09, 0x27, 0x27, + 0x27, 0x00, 0x27, 0x27, 0x27, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x27, 0x27, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x24, 0x24, + 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc5, 0xc5, 0xc5, 0x05, 0xc6, 0xc6, + 0xc6, 0x49, 0xc7, 0xc7, 0xc7, 0xf0, 0xc7, 0xc7, 0xc7, 0xff, 0xc7, 0xc7, + 0xc7, 0xff, 0xc7, 0xc7, 0xc7, 0xd8, 0xc6, 0xc6, 0xc6, 0x24, 0xc5, 0xc5, + 0xc5, 0x02, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2d, 0x2d, 0x2d, 0x00, 0x2c, 0x2c, 0x2c, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0xc6, 0xc6, 0xc6, 0x0d, 0xc7, 0xc7, 0xc7, 0x66, 0xc8, 0xc8, + 0xc8, 0x76, 0xc8, 0xc8, 0xc8, 0x74, 0xc8, 0xc8, 0xc8, 0x4f, 0xc8, 0xc8, + 0xc8, 0x06, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x27, 0x27, 0x27, 0x00, 0x26, 0x26, 0x26, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x31, 0x31, 0x31, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0xc9, 0xc9, + 0xc9, 0x06, 0xc9, 0xc9, 0xc9, 0x08, 0xca, 0xca, 0xca, 0x07, 0xcd, 0xcd, + 0xcd, 0x04, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x24, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x2b, 0x2b, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x28, 0x28, 0x28, 0x00, 0x26, 0x26, 0x26, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd7, 0xd7, 0xd7, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x01, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xcd, 0xcd, + 0xcd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, + 0xda, 0x01, 0xd9, 0xd9, 0xd9, 0x07, 0xda, 0xda, 0xda, 0x08, 0xdb, 0xdb, + 0xdb, 0x07, 0xdb, 0xdb, 0xdb, 0x07, 0xdb, 0xdb, 0xdb, 0x04, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdc, 0xdc, + 0xdc, 0x04, 0xdb, 0xdb, 0xdb, 0x07, 0xdb, 0xdb, 0xdb, 0x06, 0xdb, 0xdb, + 0xdb, 0x03, 0xda, 0xda, 0xda, 0x01, 0xdc, 0xdc, 0xdc, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xdd, 0xdd, 0xdd, 0x00, 0xe0, 0xe0, 0xe0, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x3c, 0xda, 0xda, 0xda, 0x56, 0xda, 0xda, + 0xda, 0x57, 0xda, 0xda, 0xda, 0x57, 0xda, 0xda, 0xda, 0x57, 0xda, 0xda, + 0xda, 0x57, 0xda, 0xda, 0xda, 0x56, 0xda, 0xda, 0xda, 0x53, 0xda, 0xda, + 0xda, 0x4f, 0xda, 0xda, 0xda, 0x43, 0xdb, 0xdb, 0xdb, 0x28, 0xe4, 0xe4, + 0xe4, 0x04, 0xda, 0xda, 0xda, 0x00, 0xdc, 0xdc, 0xdc, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0xc9, 0xda, 0xda, + 0xda, 0xff, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, + 0xda, 0xff, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, + 0xda, 0xff, 0xda, 0xda, 0xda, 0xfa, 0xda, 0xda, 0xda, 0xe8, 0xda, 0xda, + 0xda, 0xbe, 0xdb, 0xdb, 0xdb, 0x60, 0xe0, 0xe0, 0xe0, 0x07, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd7, 0xd7, + 0xd7, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xe5, 0xe5, 0xe5, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xce, 0xce, + 0xce, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdd, 0xdd, + 0xdd, 0x00, 0xe0, 0xe0, 0xe0, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0xc5, 0xda, 0xda, 0xda, 0xfb, 0xda, 0xda, 0xda, 0xfb, 0xda, 0xda, + 0xda, 0xcc, 0xda, 0xda, 0xda, 0xb7, 0xda, 0xda, 0xda, 0xb6, 0xda, 0xda, + 0xda, 0xb7, 0xd9, 0xd9, 0xd9, 0xbf, 0xda, 0xda, 0xda, 0xe1, 0xda, 0xda, + 0xda, 0xfe, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, 0xda, 0xdf, 0xd9, 0xd9, + 0xd9, 0x56, 0xdc, 0xdc, 0xdc, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd5, 0xd5, 0xd5, 0x00, 0xd7, 0xd7, + 0xd7, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xdd, 0xdd, 0xdd, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xda, 0xda, 0xda, 0xc5, 0xda, 0xda, 0xda, 0xfb, 0xda, 0xda, + 0xda, 0xf1, 0xd9, 0xd9, 0xd9, 0x5d, 0xdb, 0xdb, 0xdb, 0x1c, 0xdb, 0xdb, + 0xdb, 0x17, 0xd9, 0xd9, 0xd9, 0x18, 0xd9, 0xd9, 0xd9, 0x19, 0xda, 0xda, + 0xda, 0x2d, 0xd9, 0xd9, 0xd9, 0xa5, 0xda, 0xda, 0xda, 0xf8, 0xda, 0xda, + 0xda, 0xf9, 0xda, 0xda, 0xda, 0xac, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xdd, 0xdd, 0xdd, 0x00, 0xe4, 0xe4, 0xe4, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0xdc, 0xdc, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x54, 0xda, 0xda, 0xda, 0x72, 0xda, 0xda, 0xda, 0x73, 0xda, 0xda, + 0xda, 0x5d, 0xd8, 0xd8, 0xd8, 0x0d, 0xd4, 0xd4, 0xd4, 0x01, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xde, 0xde, 0xde, 0x01, 0xdc, 0xdc, + 0xdc, 0x0f, 0xda, 0xda, 0xda, 0x5f, 0xda, 0xda, 0xda, 0x73, 0xda, 0xda, + 0xda, 0x72, 0xda, 0xda, 0xda, 0x53, 0xd4, 0xd4, 0xd4, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0xc5, 0xda, 0xda, + 0xda, 0xfb, 0xda, 0xda, 0xda, 0xef, 0xd9, 0xd9, 0xd9, 0x4d, 0xd9, 0xd9, + 0xd9, 0x05, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x07, 0xd9, 0xd9, 0xd9, 0x5e, 0xda, 0xda, + 0xda, 0xf1, 0xda, 0xda, 0xda, 0xfd, 0xda, 0xda, 0xda, 0xd2, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0xe1, 0xe1, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x65, 0xda, 0xda, 0xda, 0xf0, 0xda, 0xda, + 0xda, 0xff, 0xda, 0xda, 0xda, 0xef, 0xda, 0xda, 0xda, 0x69, 0xda, 0xda, + 0xda, 0x07, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x07, 0xda, 0xda, 0xda, 0x70, 0xda, 0xda, 0xda, 0xf2, 0xda, 0xda, + 0xda, 0xff, 0xda, 0xda, 0xda, 0xee, 0xda, 0xda, 0xda, 0x62, 0xdb, 0xdb, + 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, + 0xda, 0xc5, 0xda, 0xda, 0xda, 0xfb, 0xda, 0xda, 0xda, 0xf1, 0xda, 0xda, + 0xda, 0x63, 0xd9, 0xd9, 0xd9, 0x25, 0xda, 0xda, 0xda, 0x20, 0xdb, 0xdb, + 0xdb, 0x22, 0xdb, 0xdb, 0xdb, 0x26, 0xdb, 0xdb, 0xdb, 0x3d, 0xda, 0xda, + 0xda, 0xb0, 0xda, 0xda, 0xda, 0xf9, 0xda, 0xda, 0xda, 0xf6, 0xda, 0xda, + 0xda, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0xdc, 0xdc, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdc, 0xdc, + 0xdc, 0x00, 0xdf, 0xdf, 0xdf, 0x00, 0xda, 0xda, 0xda, 0x0f, 0xda, 0xda, + 0xda, 0x85, 0xda, 0xda, 0xda, 0xf5, 0xda, 0xda, 0xda, 0xfd, 0xda, 0xda, + 0xda, 0xdc, 0xdb, 0xdb, 0xdb, 0x44, 0xdb, 0xdb, 0xdb, 0x05, 0xd9, 0xd9, + 0xd9, 0x05, 0xd9, 0xd9, 0xd9, 0x49, 0xda, 0xda, 0xda, 0xdf, 0xda, 0xda, + 0xda, 0xfe, 0xda, 0xda, 0xda, 0xf5, 0xda, 0xda, 0xda, 0x81, 0xd9, 0xd9, + 0xd9, 0x0e, 0xd9, 0xd9, 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xda, 0xda, 0xda, 0xc5, 0xda, 0xda, 0xda, 0xfb, 0xda, 0xda, + 0xda, 0xfb, 0xda, 0xda, 0xda, 0xd0, 0xda, 0xda, 0xda, 0xbc, 0xda, 0xda, + 0xda, 0xbc, 0xda, 0xda, 0xda, 0xbf, 0xda, 0xda, 0xda, 0xc8, 0xda, 0xda, + 0xda, 0xe7, 0xda, 0xda, 0xda, 0xfc, 0xda, 0xda, 0xda, 0xe3, 0xda, 0xda, + 0xda, 0xa2, 0xdb, 0xdb, 0xdb, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xd8, 0xd8, + 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd9, 0xd9, + 0xd9, 0x01, 0xda, 0xda, 0xda, 0x1a, 0xda, 0xda, 0xda, 0xae, 0xda, 0xda, + 0xda, 0xf8, 0xda, 0xda, 0xda, 0xfb, 0xda, 0xda, 0xda, 0xc6, 0xda, 0xda, + 0xda, 0x25, 0xdc, 0xdc, 0xdc, 0x29, 0xda, 0xda, 0xda, 0xcb, 0xda, 0xda, + 0xda, 0xfc, 0xda, 0xda, 0xda, 0xf8, 0xd9, 0xd9, 0xd9, 0xab, 0xda, 0xda, + 0xda, 0x19, 0xda, 0xda, 0xda, 0x01, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0xc5, 0xda, 0xda, + 0xda, 0xfb, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, + 0xda, 0xff, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, + 0xda, 0xff, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, 0xda, 0xf9, 0xda, 0xda, + 0xda, 0xb0, 0xda, 0xda, 0xda, 0x23, 0xd8, 0xd8, 0xd8, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd7, 0xd7, + 0xd7, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdd, 0xdd, 0xdd, 0x02, 0xdc, 0xdc, + 0xdc, 0x2b, 0xda, 0xda, 0xda, 0xd0, 0xda, 0xda, 0xda, 0xfc, 0xda, 0xda, + 0xda, 0xf7, 0xda, 0xda, 0xda, 0xaf, 0xda, 0xda, 0xda, 0xb4, 0xda, 0xda, + 0xda, 0xf7, 0xda, 0xda, 0xda, 0xfc, 0xda, 0xda, 0xda, 0xce, 0xda, 0xda, + 0xda, 0x29, 0xda, 0xda, 0xda, 0x02, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, + 0xda, 0xc5, 0xda, 0xda, 0xda, 0xfb, 0xda, 0xda, 0xda, 0xf3, 0xda, 0xda, + 0xda, 0x79, 0xda, 0xda, 0xda, 0x43, 0xda, 0xda, 0xda, 0x3f, 0xd9, 0xd9, + 0xd9, 0x43, 0xda, 0xda, 0xda, 0x58, 0xda, 0xda, 0xda, 0xa6, 0xda, 0xda, + 0xda, 0xf6, 0xda, 0xda, 0xda, 0xe7, 0xda, 0xda, 0xda, 0x8e, 0xda, 0xda, + 0xda, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x06, 0xdb, 0xdb, 0xdb, 0x4d, 0xda, 0xda, + 0xda, 0xe1, 0xda, 0xda, 0xda, 0xfe, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, + 0xda, 0xff, 0xda, 0xda, 0xda, 0xfe, 0xda, 0xda, 0xda, 0xe0, 0xdb, 0xdb, + 0xdb, 0x4a, 0xdb, 0xdb, 0xdb, 0x06, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xda, 0xda, 0xda, 0xc5, 0xda, 0xda, 0xda, 0xfb, 0xda, 0xda, + 0xda, 0xef, 0xd9, 0xd9, 0xd9, 0x4d, 0xd9, 0xd9, 0xd9, 0x05, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x04, 0xdc, 0xdc, + 0xdc, 0x23, 0xda, 0xda, 0xda, 0xd9, 0xda, 0xda, 0xda, 0xfd, 0xda, 0xda, + 0xda, 0xe6, 0xd9, 0xd9, 0xd9, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xdc, 0xdc, 0xdc, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, + 0xd9, 0x08, 0xda, 0xda, 0xda, 0x73, 0xda, 0xda, 0xda, 0xef, 0xda, 0xda, + 0xda, 0xff, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, 0xda, 0xef, 0xda, 0xda, + 0xda, 0x6f, 0xda, 0xda, 0xda, 0x08, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0xc5, 0xda, 0xda, + 0xda, 0xfb, 0xda, 0xda, 0xda, 0xf1, 0xd9, 0xd9, 0xd9, 0x5d, 0xda, 0xda, + 0xda, 0x1c, 0xda, 0xda, 0xda, 0x17, 0xda, 0xda, 0xda, 0x17, 0xda, 0xda, + 0xda, 0x1b, 0xdb, 0xdb, 0xdb, 0x46, 0xda, 0xda, 0xda, 0xea, 0xda, 0xda, + 0xda, 0xfe, 0xda, 0xda, 0xda, 0xe8, 0xda, 0xda, 0xda, 0x2d, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd7, 0xd7, + 0xd7, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0xdc, 0xdc, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x04, 0xda, 0xda, 0xda, 0x3b, 0xda, 0xda, + 0xda, 0xea, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, + 0xda, 0xe9, 0xda, 0xda, 0xda, 0x39, 0xda, 0xda, 0xda, 0x03, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xda, 0xda, + 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0xc5, 0xda, 0xda, 0xda, 0xfb, 0xda, 0xda, 0xda, 0xfa, 0xda, 0xda, + 0xda, 0xca, 0xda, 0xda, 0xda, 0xb5, 0xda, 0xda, 0xda, 0xb4, 0xda, 0xda, + 0xda, 0xb5, 0xda, 0xda, 0xda, 0xc1, 0xda, 0xda, 0xda, 0xf2, 0xda, 0xda, + 0xda, 0xff, 0xda, 0xda, 0xda, 0xf8, 0xda, 0xda, 0xda, 0xa6, 0xda, 0xda, + 0xda, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xd7, 0xd7, 0xd7, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xd5, 0xd5, 0xd5, 0x01, 0xd8, 0xd8, 0xd8, 0x18, 0xd9, 0xd9, + 0xd9, 0xb2, 0xda, 0xda, 0xda, 0xf9, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, + 0xda, 0xff, 0xda, 0xda, 0xda, 0xf9, 0xda, 0xda, 0xda, 0xb0, 0xd7, 0xd7, + 0xd7, 0x17, 0xd2, 0xd2, 0xd2, 0x01, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0xc9, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, + 0xda, 0xff, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, + 0xda, 0xff, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, + 0xda, 0xf4, 0xda, 0xda, 0xda, 0xd0, 0xda, 0xda, 0xda, 0x92, 0xdb, 0xdb, + 0xdb, 0x25, 0xdb, 0xdb, 0xdb, 0x02, 0xdb, 0xdb, 0xdb, 0x00, 0xdc, 0xdc, + 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x0e, 0xda, 0xda, + 0xda, 0x88, 0xda, 0xda, 0xda, 0xf2, 0xda, 0xda, 0xda, 0xfd, 0xda, 0xda, + 0xda, 0xec, 0xda, 0xda, 0xda, 0xef, 0xda, 0xda, 0xda, 0xfd, 0xda, 0xda, + 0xda, 0xf2, 0xda, 0xda, 0xda, 0x86, 0xda, 0xda, 0xda, 0x0e, 0xda, 0xda, + 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xd7, 0xd7, 0xd7, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x37, 0xda, 0xda, + 0xda, 0x4e, 0xda, 0xda, 0xda, 0x57, 0xda, 0xda, 0xda, 0x57, 0xda, 0xda, + 0xda, 0x57, 0xda, 0xda, 0xda, 0x57, 0xda, 0xda, 0xda, 0x56, 0xda, 0xda, + 0xda, 0x53, 0xda, 0xda, 0xda, 0x4b, 0xd9, 0xd9, 0xd9, 0x33, 0xd9, 0xd9, + 0xd9, 0x0f, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdc, 0xdc, 0xdc, 0x00, 0xe1, 0xe1, + 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x07, 0xd9, 0xd9, + 0xd9, 0x59, 0xda, 0xda, 0xda, 0xec, 0xda, 0xda, 0xda, 0xfe, 0xda, 0xda, + 0xda, 0xe9, 0xd9, 0xd9, 0xd9, 0x67, 0xd9, 0xd9, 0xd9, 0x6b, 0xda, 0xda, + 0xda, 0xea, 0xda, 0xda, 0xda, 0xfe, 0xda, 0xda, 0xda, 0xec, 0xdb, 0xdb, + 0xdb, 0x59, 0xdb, 0xdb, 0xdb, 0x07, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd8, 0xd8, + 0xd8, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xdc, 0xdc, 0xdc, 0x00, 0xdf, 0xdf, 0xdf, 0x00, 0xdc, 0xdc, + 0xdc, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd8, 0xd8, + 0xd8, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xdc, 0xdc, + 0xdc, 0x00, 0xdc, 0xdc, 0xdc, 0x00, 0xdc, 0xdc, 0xdc, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xe1, 0xe1, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdd, 0xdd, + 0xdd, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0x03, 0xda, 0xda, + 0xda, 0x38, 0xda, 0xda, 0xda, 0xd7, 0xda, 0xda, 0xda, 0xfd, 0xda, 0xda, + 0xda, 0xf4, 0xda, 0xda, 0xda, 0x89, 0xdb, 0xdb, 0xdb, 0x0b, 0xdc, 0xdc, + 0xdc, 0x0c, 0xda, 0xda, 0xda, 0x8e, 0xda, 0xda, 0xda, 0xf5, 0xda, 0xda, + 0xda, 0xfd, 0xda, 0xda, 0xda, 0xd8, 0xdb, 0xdb, 0xdb, 0x38, 0xdb, 0xdb, + 0xdb, 0x03, 0xd7, 0xd7, 0xd7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xdc, 0xdc, 0xdc, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xd7, 0xd7, 0xd7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, + 0xda, 0x24, 0xda, 0xda, 0xda, 0xbb, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, + 0xda, 0xfd, 0xda, 0xda, 0xda, 0xac, 0xdc, 0xdc, 0xdc, 0x1a, 0xdf, 0xdf, + 0xdf, 0x01, 0xdc, 0xdc, 0xdc, 0x01, 0xdb, 0xdb, 0xdb, 0x1c, 0xda, 0xda, + 0xda, 0xaf, 0xda, 0xda, 0xda, 0xfd, 0xda, 0xda, 0xda, 0xff, 0xda, 0xda, + 0xda, 0xbc, 0xdb, 0xdb, 0xdb, 0x25, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xd8, 0xd8, + 0xd8, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xd8, 0xd8, + 0xd8, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xdc, 0xdc, 0xdc, 0x00, 0xdc, 0xdc, + 0xdc, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xda, 0xda, 0xda, 0x20, 0xda, 0xda, 0xda, 0x6b, 0xda, 0xda, + 0xda, 0x71, 0xda, 0xda, 0xda, 0x6b, 0xdb, 0xdb, 0xdb, 0x26, 0xdc, 0xdc, + 0xdc, 0x02, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x03, 0xdb, 0xdb, 0xdb, 0x28, 0xda, 0xda, 0xda, 0x6b, 0xda, 0xda, + 0xda, 0x71, 0xda, 0xda, 0xda, 0x6b, 0xdb, 0xdb, 0xdb, 0x24, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xe4, 0xe4, 0xe4, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0xe8, + 0xe8, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd7, 0xd7, 0xd7, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0xdc, + 0xdc, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xd4, 0xd4, 0xd4, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0xdc, 0xdc, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0xe3, + 0xe3, 0x00, 0xdc, 0xdc, 0xdc, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xde, 0xde, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd1, 0xd1, 0xd1, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd6, 0xd6, + 0xd6, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0xe1, + 0xe1, 0x00, 0xde, 0xde, 0xde, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xd9, 0xd9, + 0xd9, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe5, 0xe5, + 0xe5, 0x00, 0xdc, 0xdc, 0xdc, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xd9, 0xd9, 0xd9, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, + 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xda, 0xda, 0xda, 0x00, 0xdb, 0xdb, + 0xdb, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0xcb, 0xcb, + 0xcb, 0x00, 0xcb, 0xcb, 0xcb, 0x00, 0xca, 0xca, 0xca, 0x00, 0xc9, 0xc9, + 0xc9, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, 0x28, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x31, 0x31, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x2b, + 0x2b, 0x00, 0x2b, 0x2b, 0x2b, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0xc7, 0xc7, + 0xc7, 0x00, 0xcb, 0xcb, 0xcb, 0x04, 0xcb, 0xcb, 0xcb, 0x07, 0xca, 0xca, + 0xca, 0x07, 0xc9, 0xc9, 0xc9, 0x04, 0xc7, 0xc7, 0xc7, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x26, 0x26, + 0x26, 0x00, 0x24, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x27, + 0x27, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0xc7, 0xc7, 0xc7, 0x05, 0xc7, 0xc7, 0xc7, 0x3f, 0xc8, 0xc8, + 0xc8, 0x70, 0xc8, 0xc8, 0xc8, 0x70, 0xc7, 0xc7, 0xc7, 0x3d, 0xc7, 0xc7, + 0xc7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x24, 0x24, 0x24, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x26, + 0x26, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0x0c, 0xc6, 0xc6, + 0xc6, 0x8b, 0xc7, 0xc7, 0xc7, 0xfa, 0xc7, 0xc7, 0xc7, 0xf9, 0xc6, 0xc6, + 0xc6, 0x88, 0xc6, 0xc6, 0xc6, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x28, 0x28, 0x28, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x2c, 0x2c, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, + 0xc6, 0x0c, 0xc6, 0xc6, 0xc6, 0x89, 0xc7, 0xc7, 0xc7, 0xf5, 0xc7, 0xc7, + 0xc7, 0xf5, 0xc6, 0xc6, 0xc6, 0x86, 0xc6, 0xc6, 0xc6, 0x0b, 0x2b, 0x2b, + 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x25, + 0x25, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x27, 0x27, 0x27, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0x0c, 0xc6, 0xc6, 0xc6, 0x89, 0xc7, 0xc7, + 0xc7, 0xf5, 0xc7, 0xc7, 0xc7, 0xf5, 0xc6, 0xc6, 0xc6, 0x86, 0xc6, 0xc6, + 0xc6, 0x0b, 0x2a, 0x2a, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x23, 0x23, 0x23, 0x00, 0x27, 0x27, 0x27, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x2e, 0x2e, + 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x2b, 0x2b, 0x2b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0x0c, 0xc6, 0xc6, + 0xc6, 0x89, 0xc7, 0xc7, 0xc7, 0xf5, 0xc7, 0xc7, 0xc7, 0xf5, 0xc6, 0xc6, + 0xc6, 0x86, 0xc6, 0xc6, 0xc6, 0x0b, 0x2a, 0x2a, 0x2a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x2b, + 0x2b, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0xc8, 0xc8, 0x00, 0xc6, 0xc6, + 0xc6, 0x0c, 0xc6, 0xc6, 0xc6, 0x91, 0xc7, 0xc7, 0xc7, 0xf6, 0xc7, 0xc7, + 0xc7, 0xf6, 0xc6, 0xc6, 0xc6, 0x8e, 0xc6, 0xc6, 0xc6, 0x0b, 0xc8, 0xc8, + 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0xc8, + 0xc8, 0x02, 0xc8, 0xc8, 0xc8, 0x2c, 0xc7, 0xc7, 0xc7, 0xca, 0xc7, 0xc7, + 0xc7, 0xfc, 0xc7, 0xc7, 0xc7, 0xfb, 0xc7, 0xc7, 0xc7, 0xc8, 0xc7, 0xc7, + 0xc7, 0x2a, 0xc8, 0xc8, 0xc8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x2b, + 0x2b, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x27, 0x27, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0xc7, + 0xc7, 0x00, 0xc8, 0xc8, 0xc8, 0x13, 0xc7, 0xc7, 0xc7, 0xa4, 0xc7, 0xc7, + 0xc7, 0xf9, 0xc7, 0xc7, 0xc7, 0xff, 0xc7, 0xc7, 0xc7, 0xff, 0xc7, 0xc7, + 0xc7, 0xf9, 0xc7, 0xc7, 0xc7, 0xa0, 0xc4, 0xc4, 0xc4, 0x12, 0xc6, 0xc6, + 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0xc7, + 0xc7, 0x00, 0xc7, 0xc7, 0xc7, 0x0a, 0xc7, 0xc7, 0xc7, 0x76, 0xc7, 0xc7, + 0xc7, 0xef, 0xc7, 0xc7, 0xc7, 0xfc, 0xc6, 0xc6, 0xc6, 0xde, 0xc7, 0xc7, + 0xc7, 0xc9, 0xc7, 0xc7, 0xc7, 0xf9, 0xc7, 0xc7, 0xc7, 0xee, 0xc6, 0xc6, + 0xc6, 0x72, 0xc6, 0xc6, 0xc6, 0x09, 0xc8, 0xc8, 0xc8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0xc7, + 0xc7, 0x00, 0xc7, 0xc7, 0xc7, 0x05, 0xc8, 0xc8, 0xc8, 0x45, 0xc7, 0xc7, + 0xc7, 0xe3, 0xc7, 0xc7, 0xc7, 0xfd, 0xc7, 0xc7, 0xc7, 0xec, 0xc7, 0xc7, + 0xc7, 0x59, 0xc8, 0xc8, 0xc8, 0x42, 0xc7, 0xc7, 0xc7, 0xe3, 0xc7, 0xc7, + 0xc7, 0xfd, 0xc7, 0xc7, 0xc7, 0xe3, 0xc8, 0xc8, 0xc8, 0x42, 0xc8, 0xc8, + 0xc8, 0x05, 0xc5, 0xc5, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x2b, + 0x2b, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x24, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xcc, + 0xcc, 0x00, 0xc7, 0xc7, 0xc7, 0x02, 0xc7, 0xc7, 0xc7, 0x24, 0xc7, 0xc7, + 0xc7, 0xc8, 0xc7, 0xc7, 0xc7, 0xfb, 0xc7, 0xc7, 0xc7, 0xf5, 0xc7, 0xc7, + 0xc7, 0x90, 0xc7, 0xc7, 0xc7, 0x0e, 0xc8, 0xc8, 0xc8, 0x0b, 0xc8, 0xc8, + 0xc8, 0x77, 0xc7, 0xc7, 0xc7, 0xf1, 0xc7, 0xc7, 0xc7, 0xfb, 0xc7, 0xc7, + 0xc7, 0xc5, 0xc6, 0xc6, 0xc6, 0x22, 0xc5, 0xc5, 0xc5, 0x02, 0xc4, 0xc4, + 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x2b, 0x2b, 0x2b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2c, 0x2c, 0x2c, 0x00, 0x2b, 0x2b, 0x2b, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0x01, 0xc9, 0xc9, 0xc9, 0x14, 0xc7, 0xc7, + 0xc7, 0x9b, 0xc7, 0xc7, 0xc7, 0xf7, 0xc7, 0xc7, 0xc7, 0xfb, 0xc7, 0xc7, + 0xc7, 0xc2, 0xc7, 0xc7, 0xc7, 0x20, 0xc6, 0xc6, 0xc6, 0x01, 0xbe, 0xbe, + 0xbe, 0x01, 0xc4, 0xc4, 0xc4, 0x16, 0xc6, 0xc6, 0xc6, 0xb4, 0xc7, 0xc7, + 0xc7, 0xfa, 0xc7, 0xc7, 0xc7, 0xf7, 0xc7, 0xc7, 0xc7, 0x98, 0xc6, 0xc6, + 0xc6, 0x13, 0xc4, 0xc4, 0xc4, 0x01, 0xca, 0xca, 0xca, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0xc8, 0xc8, 0x09, 0xc8, 0xc8, + 0xc8, 0x72, 0xc7, 0xc7, 0xc7, 0xf5, 0xc7, 0xc7, 0xc7, 0xff, 0xc7, 0xc7, + 0xc7, 0xe2, 0xc8, 0xc8, 0xc8, 0x46, 0xc8, 0xc8, 0xc8, 0x05, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0xc8, 0xc8, 0xc8, 0x04, 0xc7, 0xc7, + 0xc7, 0x3b, 0xc7, 0xc7, 0xc7, 0xdc, 0xc7, 0xc7, 0xc7, 0xff, 0xc7, 0xc7, + 0xc7, 0xf4, 0xc7, 0xc7, 0xc7, 0x6e, 0xc6, 0xc6, 0xc6, 0x09, 0xca, 0xca, + 0xca, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x28, 0x28, 0x28, 0x00, 0x28, 0x28, 0x28, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0xc2, 0xc2, + 0xc2, 0x05, 0xc6, 0xc6, 0xc6, 0x56, 0xc6, 0xc6, 0xc6, 0x70, 0xc6, 0xc6, + 0xc6, 0x6f, 0xc8, 0xc8, 0xc8, 0x51, 0xc9, 0xc9, 0xc9, 0x07, 0x29, 0x29, + 0x29, 0x00, 0x28, 0x28, 0x28, 0x00, 0x26, 0x26, 0x26, 0x00, 0x2b, 0x2b, + 0x2b, 0x00, 0xc9, 0xc9, 0xc9, 0x06, 0xc6, 0xc6, 0xc6, 0x4d, 0xc6, 0xc6, + 0xc6, 0x6f, 0xc6, 0xc6, 0xc6, 0x70, 0xc6, 0xc6, 0xc6, 0x55, 0xcf, 0xcf, + 0xcf, 0x08, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x28, + 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x27, 0x27, + 0x27, 0x00, 0x23, 0x23, 0x23, 0x00, 0xc5, 0xc5, 0xc5, 0x02, 0xc3, 0xc3, + 0xc3, 0x06, 0xc4, 0xc4, 0xc4, 0x06, 0xcc, 0xcc, 0xcc, 0x03, 0x29, 0x29, + 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x2b, 0x2b, 0x2b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x27, 0x27, 0x00, 0xc5, 0xc5, + 0xc5, 0x03, 0xc3, 0xc3, 0xc3, 0x06, 0xc3, 0xc3, 0xc3, 0x06, 0xc4, 0xc4, + 0xc4, 0x05, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2c, 0x2c, + 0x2c, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x2c, 0x2c, 0x2c, 0x00, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x27, 0x27, 0x27, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x25, 0x25, + 0x25, 0x00, 0x28, 0x28, 0x28, 0x00, 0xc6, 0xc6, 0xc6, 0x01, 0xd5, 0xd5, + 0xd5, 0x01, 0x29, 0x29, 0x29, 0x00, 0x2b, 0x2b, 0x2b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, 0x28, 0x00, 0x25, 0x25, + 0x25, 0x00, 0x28, 0x28, 0x28, 0x00, 0x29, 0x29, 0x29, 0x00, 0x29, 0x29, + 0x29, 0x00, 0x29, 0x29, 0x29, 0x00, 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +unsigned int gamepad_face_axby_bmp_len = 33030; diff --git a/test/gamepadutils.c b/test/gamepadutils.c index 910887fb..68d8e448 100644 --- a/test/gamepadutils.c +++ b/test/gamepadutils.c @@ -16,6 +16,7 @@ #include "gamepad_front.h" #include "gamepad_back.h" #include "gamepad_face_abxy.h" +#include "gamepad_face_axby.h" #include "gamepad_face_bayx.h" #include "gamepad_face_sony.h" #include "gamepad_battery.h" @@ -29,6 +30,217 @@ #include "gamepad_wired.h" #include "gamepad_wireless.h" +#include + +#define RAD_TO_DEG (180.0f / SDL_PI_F) + +/* Used to draw a 3D cube to represent the gyroscope orientation */ +typedef struct +{ + float x, y, z; +} Vector3; + +struct Quaternion +{ + float x, y, z, w; +}; + +static const Vector3 debug_cube_vertices[] = { + { -1.0f, -1.0f, -1.0f }, + { 1.0f, -1.0f, -1.0f }, + { 1.0f, 1.0f, -1.0f }, + { -1.0f, 1.0f, -1.0f }, + { -1.0f, -1.0f, 1.0f }, + { 1.0f, -1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { -1.0f, 1.0f, 1.0f }, +}; + +static const int debug_cube_edges[][2] = { + { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 }, /* bottom square */ + { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 4 }, /* top square */ + { 0, 4 }, { 1, 5 }, { 2, 6 }, { 3, 7 }, /* verticals */ +}; + +static Vector3 RotateVectorByQuaternion(const Vector3 *v, const Quaternion *q) { + /* v' = q * v * q^-1 */ + float x = v->x, y = v->y, z = v->z; + float qx = q->x, qy = q->y, qz = q->z, qw = q->w; + + /* Calculate quaternion *vector */ + float ix = qw * x + qy * z - qz * y; + float iy = qw * y + qz * x - qx * z; + float iz = qw * z + qx * y - qy * x; + float iw = -qx * x - qy * y - qz * z; + + /* Result = result * conjugate(q) */ + Vector3 out; + out.x = ix * qw + iw * -qx + iy * -qz - iz * -qy; + out.y = iy * qw + iw * -qy + iz * -qx - ix * -qz; + out.z = iz * qw + iw * -qz + ix * -qy - iy * -qx; + return out; +} + +#ifdef GYRO_ISOMETRIC_PROJECTION +static SDL_FPoint ProjectVec3ToRect(const Vector3 *v, const SDL_FRect *rect) +{ + SDL_FPoint out; + /* Simple orthographic projection using X and Y; scale to fit into rect */ + out.x = rect->x + (rect->w / 2.0f) + (v->x * (rect->w / 2.0f)); + out.y = rect->y + (rect->h / 2.0f) - (v->y * (rect->h / 2.0f)); /* Y inverted */ + return out; +} +#else +static SDL_FPoint ProjectVec3ToRect(const Vector3 *v, const SDL_FRect *rect) +{ + const float verticalFOV_deg = 40.0f; + const float cameraZ = 4.0f; /* Camera is at(0, 0, +4), looking toward origin */ + float aspect = rect->w / rect->h; + + float fovScaleY = SDL_tanf((verticalFOV_deg * SDL_PI_F / 180.0f) * 0.5f); + float fovScaleX = fovScaleY * aspect; + + float relZ = cameraZ - v->z; + if (relZ < 0.01f) + relZ = 0.01f; /* Prevent division by 0 or negative depth */ + + float ndc_x = (v->x / relZ) / fovScaleX; + float ndc_y = (v->y / relZ) / fovScaleY; + + /* Convert to screen space */ + SDL_FPoint out; + out.x = rect->x + (rect->w / 2.0f) + (ndc_x * rect->w / 2.0f); + out.y = rect->y + (rect->h / 2.0f) - (ndc_y * rect->h / 2.0f); /* flip Y */ + return out; +} +#endif + +void DrawGyroDebugCube(SDL_Renderer *renderer, const Quaternion *orientation, const SDL_FRect *rect) +{ + SDL_FPoint projected[8]; + int i; + for (i = 0; i < 8; ++i) { + Vector3 rotated = RotateVectorByQuaternion(&debug_cube_vertices[i], orientation); + projected[i] = ProjectVec3ToRect(&rotated, rect); + } + + for (i = 0; i < 12; ++i) { + const SDL_FPoint p0 = projected[debug_cube_edges[i][0]]; + const SDL_FPoint p1 = projected[debug_cube_edges[i][1]]; + SDL_RenderLine(renderer, p0.x, p0.y, p1.x, p1.y); + } +} + +#define CIRCLE_SEGMENTS 64 + +static Vector3 kCirclePoints3D_XY_Plane[CIRCLE_SEGMENTS]; +static Vector3 kCirclePoints3D_XZ_Plane[CIRCLE_SEGMENTS]; +static Vector3 kCirclePoints3D_YZ_Plane[CIRCLE_SEGMENTS]; + +void InitCirclePoints3D(void) +{ + int i; + for (i = 0; i < CIRCLE_SEGMENTS; ++i) { + float theta = ((float)i / CIRCLE_SEGMENTS) * SDL_PI_F * 2.0f; + kCirclePoints3D_XY_Plane[i].x = SDL_cosf(theta); + kCirclePoints3D_XY_Plane[i].y = SDL_sinf(theta); + kCirclePoints3D_XY_Plane[i].z = 0.0f; + } + + for (i = 0; i < CIRCLE_SEGMENTS; ++i) { + float theta = ((float)i / CIRCLE_SEGMENTS) * SDL_PI_F * 2.0f; + kCirclePoints3D_XZ_Plane[i].x = SDL_cosf(theta); + kCirclePoints3D_XZ_Plane[i].y = 0.0f; + kCirclePoints3D_XZ_Plane[i].z = SDL_sinf(theta); + } + + for (i = 0; i < CIRCLE_SEGMENTS; ++i) { + float theta = ((float)i / CIRCLE_SEGMENTS) * SDL_PI_F * 2.0f; + kCirclePoints3D_YZ_Plane[i].x = 0.0f; + kCirclePoints3D_YZ_Plane[i].y = SDL_cosf(theta); + kCirclePoints3D_YZ_Plane[i].z = SDL_sinf(theta); + } +} + +void DrawGyroCircle( + SDL_Renderer *renderer, + const Vector3 *circlePoints, + int numSegments, + const Quaternion *orientation, + const SDL_FRect *bounds, + Uint8 r, Uint8 g, Uint8 b, Uint8 a) +{ + SDL_SetRenderDrawColor(renderer, r, g, b, a); + + SDL_FPoint lastScreenPt = { 0 }; + bool hasLast = false; + int i; + for (i = 0; i <= numSegments; ++i) { + int index = i % numSegments; + + Vector3 rotated = RotateVectorByQuaternion(&circlePoints[index], orientation); + SDL_FPoint screenPtVec2 = ProjectVec3ToRect(&rotated, bounds); + SDL_FPoint screenPt; + screenPt.x = screenPtVec2.x; + screenPt.y = screenPtVec2.y; + + + if (hasLast) { + SDL_RenderLine(renderer, lastScreenPt.x, lastScreenPt.y, screenPt.x, screenPt.y); + } + + lastScreenPt = screenPt; + hasLast = true; + } +} + +void DrawGyroDebugCircle(SDL_Renderer *renderer, const Quaternion *orientation, const SDL_FRect *bounds) +{ + /* Store current color */ + Uint8 r, g, b, a; + SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); + DrawGyroCircle(renderer, kCirclePoints3D_YZ_Plane, CIRCLE_SEGMENTS, orientation, bounds, GYRO_COLOR_RED); /* X axis - pitch */ + DrawGyroCircle(renderer, kCirclePoints3D_XZ_Plane, CIRCLE_SEGMENTS, orientation, bounds, GYRO_COLOR_GREEN); /* Y axis - yaw */ + DrawGyroCircle(renderer, kCirclePoints3D_XY_Plane, CIRCLE_SEGMENTS, orientation, bounds, GYRO_COLOR_BLUE); /* Z axis - Roll */ + + /* Restore current color */ + SDL_SetRenderDrawColor(renderer, r, g, b, a); +} + +void DrawAccelerometerDebugArrow(SDL_Renderer *renderer, const Quaternion *gyro_quaternion, const float *accel_data, const SDL_FRect *bounds) +{ + /* Store current color */ + Uint8 r, g, b, a; + SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); + + const float flGravity = 9.81f; + Vector3 vAccel; + vAccel.x = accel_data[0] / flGravity; + vAccel.y = accel_data[1] / flGravity; + vAccel.z = accel_data[2] / flGravity; + + Vector3 origin = { 0.0f, 0.0f, 0.0f }; + Vector3 rotated_accel = RotateVectorByQuaternion(&vAccel, gyro_quaternion); + + /* Project the origin and rotated vector to screen space */ + SDL_FPoint origin_screen = ProjectVec3ToRect(&origin, bounds); + SDL_FPoint accel_screen = ProjectVec3ToRect(&rotated_accel, bounds); + + /* Draw the line from origin to the rotated accelerometer vector */ + SDL_SetRenderDrawColor(renderer, GYRO_COLOR_ORANGE); + SDL_RenderLine(renderer, origin_screen.x, origin_screen.y, accel_screen.x, accel_screen.y); + + const float head_width = 4.0f; + SDL_FRect arrow_head_rect; + arrow_head_rect.x = accel_screen.x - head_width * 0.5f; + arrow_head_rect.y = accel_screen.y - head_width * 0.5f; + arrow_head_rect.w = head_width; + arrow_head_rect.h = head_width; + SDL_RenderRect(renderer, &arrow_head_rect); + + /* Restore current color */ + SDL_SetRenderDrawColor(renderer, r, g, b, a); +} /* This is indexed by gamepad element */ static const struct @@ -95,6 +307,7 @@ struct GamepadImage SDL_Texture *front_texture; SDL_Texture *back_texture; SDL_Texture *face_abxy_texture; + SDL_Texture *face_axby_texture; SDL_Texture *face_bayx_texture; SDL_Texture *face_sony_texture; SDL_Texture *connection_texture[2]; @@ -159,6 +372,7 @@ GamepadImage *CreateGamepadImage(SDL_Renderer *renderer) SDL_GetTextureSize(ctx->front_texture, &ctx->gamepad_width, &ctx->gamepad_height); ctx->face_abxy_texture = CreateTexture(renderer, gamepad_face_abxy_bmp, gamepad_face_abxy_bmp_len); + ctx->face_axby_texture = CreateTexture(renderer, gamepad_face_axby_bmp, gamepad_face_axby_bmp_len); ctx->face_bayx_texture = CreateTexture(renderer, gamepad_face_bayx_bmp, gamepad_face_bayx_bmp_len); ctx->face_sony_texture = CreateTexture(renderer, gamepad_face_sony_bmp, gamepad_face_sony_bmp_len); SDL_GetTextureSize(ctx->face_abxy_texture, &ctx->face_width, &ctx->face_height); @@ -547,14 +761,17 @@ void RenderGamepadImage(GamepadImage *ctx) dst.w = ctx->face_width; dst.h = ctx->face_height; - switch (SDL_GetGamepadButtonLabelForType(ctx->type, SDL_GAMEPAD_BUTTON_SOUTH)) { - case SDL_GAMEPAD_BUTTON_LABEL_A: + switch (SDL_GetGamepadButtonLabelForType(ctx->type, SDL_GAMEPAD_BUTTON_EAST)) { + case SDL_GAMEPAD_BUTTON_LABEL_B: SDL_RenderTexture(ctx->renderer, ctx->face_abxy_texture, NULL, &dst); break; - case SDL_GAMEPAD_BUTTON_LABEL_B: + case SDL_GAMEPAD_BUTTON_LABEL_X: + SDL_RenderTexture(ctx->renderer, ctx->face_axby_texture, NULL, &dst); + break; + case SDL_GAMEPAD_BUTTON_LABEL_A: SDL_RenderTexture(ctx->renderer, ctx->face_bayx_texture, NULL, &dst); break; - case SDL_GAMEPAD_BUTTON_LABEL_CROSS: + case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: SDL_RenderTexture(ctx->renderer, ctx->face_sony_texture, NULL, &dst); break; default: @@ -664,6 +881,7 @@ void DestroyGamepadImage(GamepadImage *ctx) SDL_DestroyTexture(ctx->front_texture); SDL_DestroyTexture(ctx->back_texture); SDL_DestroyTexture(ctx->face_abxy_texture); + SDL_DestroyTexture(ctx->face_axby_texture); SDL_DestroyTexture(ctx->face_bayx_texture); SDL_DestroyTexture(ctx->face_sony_texture); for (i = 0; i < SDL_arraysize(ctx->battery_texture); ++i) { @@ -676,7 +894,6 @@ void DestroyGamepadImage(GamepadImage *ctx) } } - static const char *gamepad_button_names[] = { "South", "East", @@ -729,6 +946,8 @@ struct GamepadDisplay float accel_data[3]; float gyro_data[3]; + float gyro_drift_correction_data[3]; + Uint64 last_sensor_update; ControllerDisplayMode display_mode; @@ -753,10 +972,68 @@ GamepadDisplay *CreateGamepadDisplay(SDL_Renderer *renderer) ctx->element_highlighted = SDL_GAMEPAD_ELEMENT_INVALID; ctx->element_selected = SDL_GAMEPAD_ELEMENT_INVALID; + + SDL_zeroa(ctx->accel_data); + SDL_zeroa(ctx->gyro_data); + SDL_zeroa(ctx->gyro_drift_correction_data); } return ctx; } +struct GyroDisplay +{ + SDL_Renderer *renderer; + + /* Main drawing area */ + SDL_FRect area; + + /* This part displays extra info from the IMUstate in order to figure out actual polling rates. */ + float gyro_drift_solution[3]; + int reported_sensor_rate_hz; /*hz - comes from HIDsdl implementation. Could be fixed, platform time, or true sensor time*/ + int estimated_sensor_rate_hz; /*hz - our estimation of the actual polling rate by observing packets received*/ + float euler_displacement_angles[3]; /* pitch, yaw, roll */ + Quaternion gyro_quaternion; /* Rotation since startup/reset, comprised of each gyro speed packet times sensor delta time. */ + float drift_calibration_progress_frac; /* [0..1] */ + float accelerometer_noise_sq; /* Distance between last noise and new noise. Used to indicate motion.*/ + + GamepadButton *reset_gyro_button; + GamepadButton *calibrate_gyro_button; +}; + +GyroDisplay *CreateGyroDisplay(SDL_Renderer *renderer) +{ + GyroDisplay *ctx = SDL_calloc(1, sizeof(*ctx)); + { + ctx->renderer = renderer; + ctx->estimated_sensor_rate_hz = 0; + SDL_zeroa(ctx->gyro_drift_solution); + Quaternion quat_identity = { 0.0f, 0.0f, 0.0f, 1.0f }; + ctx->gyro_quaternion = quat_identity; + + ctx->reset_gyro_button = CreateGamepadButton(renderer, "Reset View"); + ctx->calibrate_gyro_button = CreateGamepadButton(renderer, "Recalibrate Drift"); + } + + return ctx; +} + +void SetGyroDisplayArea(GyroDisplay *ctx, const SDL_FRect *area) +{ + if (!ctx) { + return; + } + + SDL_copyp(&ctx->area, area); + + /* Place the reset button to the bottom right of the gyro display area.*/ + SDL_FRect reset_button_area; + reset_button_area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(ctx->reset_gyro_button) + 2 * BUTTON_PADDING); + reset_button_area.h = GetGamepadButtonLabelHeight(ctx->reset_gyro_button) + BUTTON_PADDING; + reset_button_area.x = area->x + area->w - reset_button_area.w - BUTTON_PADDING; + reset_button_area.y = area->y + area->h - reset_button_area.h - BUTTON_PADDING; + SetGamepadButtonArea(ctx->reset_gyro_button, &reset_button_area); +} + void SetGamepadDisplayDisplayMode(GamepadDisplay *ctx, ControllerDisplayMode display_mode) { if (!ctx) { @@ -774,6 +1051,16 @@ void SetGamepadDisplayArea(GamepadDisplay *ctx, const SDL_FRect *area) SDL_copyp(&ctx->area, area); } +void SetGamepadDisplayGyroDriftCorrection(GamepadDisplay *ctx, float *gyro_drift_correction) +{ + if (!ctx) { + return; + } + + ctx->gyro_drift_correction_data[0] = gyro_drift_correction[0]; + ctx->gyro_drift_correction_data[1] = gyro_drift_correction[1]; + ctx->gyro_drift_correction_data[2] = gyro_drift_correction[2]; +} static bool GetBindingString(const char *label, const char *mapping, char *text, size_t size) { @@ -1037,6 +1324,50 @@ static void RenderGamepadElementHighlight(GamepadDisplay *ctx, int element, cons } } +bool BHasCachedGyroDriftSolution(GyroDisplay *ctx) +{ + if (!ctx) { + return false; + } + return (ctx->gyro_drift_solution[0] != 0.0f || + ctx->gyro_drift_solution[1] != 0.0f || + ctx->gyro_drift_solution[2] != 0.0f); +} + +void SetGamepadDisplayIMUValues(GyroDisplay *ctx, float *gyro_drift_solution, float *euler_displacement_angles, Quaternion *gyro_quaternion, int reported_senor_rate_hz, int estimated_sensor_rate_hz, float drift_calibration_progress_frac, float accelerometer_noise_sq) +{ + if (!ctx) { + return; + } + + SDL_memcpy(ctx->gyro_drift_solution, gyro_drift_solution, sizeof(ctx->gyro_drift_solution)); + ctx->estimated_sensor_rate_hz = estimated_sensor_rate_hz; + + if (reported_senor_rate_hz != 0) + ctx->reported_sensor_rate_hz = reported_senor_rate_hz; + + SDL_memcpy(ctx->euler_displacement_angles, euler_displacement_angles, sizeof(ctx->euler_displacement_angles)); + ctx->gyro_quaternion = *gyro_quaternion; + ctx->drift_calibration_progress_frac = drift_calibration_progress_frac; + ctx->accelerometer_noise_sq = accelerometer_noise_sq; +} + +extern GamepadButton *GetGyroResetButton(GyroDisplay *ctx) +{ + if (!ctx) { + return NULL; + } + return ctx->reset_gyro_button; +} + +extern GamepadButton *GetGyroCalibrateButton(GyroDisplay *ctx) +{ + if (!ctx) { + return NULL; + } + return ctx->calibrate_gyro_button; +} + void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) { float x, y; @@ -1278,6 +1609,7 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) has_accel = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL); has_gyro = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO); + if (has_accel || has_gyro) { const int SENSOR_UPDATE_INTERVAL_MS = 100; Uint64 now = SDL_GetTicks(); @@ -1295,20 +1627,31 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) if (has_accel) { SDL_strlcpy(text, "Accelerometer:", sizeof(text)); SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); - SDL_snprintf(text, sizeof(text), "(%.2f,%.2f,%.2f)", ctx->accel_data[0], ctx->accel_data[1], ctx->accel_data[2]); + SDL_snprintf(text, sizeof(text), "[%.2f,%.2f,%.2f]m/s%s", ctx->accel_data[0], ctx->accel_data[1], ctx->accel_data[2], SQUARED_UTF8 ); SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text); - y += ctx->button_height + 2.0f; } if (has_gyro) { SDL_strlcpy(text, "Gyro:", sizeof(text)); SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); - SDL_snprintf(text, sizeof(text), "(%.2f,%.2f,%.2f)", ctx->gyro_data[0], ctx->gyro_data[1], ctx->gyro_data[2]); + SDL_snprintf(text, sizeof(text), "[%.2f,%.2f,%.2f]%s/s", ctx->gyro_data[0] * RAD_TO_DEG, ctx->gyro_data[1] * RAD_TO_DEG, ctx->gyro_data[2] * RAD_TO_DEG, DEGREE_UTF8); SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text); - y += ctx->button_height + 2.0f; + + /* Display the testcontroller tool's evaluation of drift. This is also useful to get an average rate of turn in calibrated turntable tests. */ + if (ctx->gyro_drift_correction_data[0] != 0.0f && ctx->gyro_drift_correction_data[2] != 0.0f && ctx->gyro_drift_correction_data[2] != 0.0f ) + { + y += ctx->button_height + 2.0f; + SDL_strlcpy(text, "Gyro Drift:", sizeof(text)); + SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); + SDL_snprintf(text, sizeof(text), "[%.2f,%.2f,%.2f]%s/s", ctx->gyro_drift_correction_data[0] * RAD_TO_DEG, ctx->gyro_drift_correction_data[1] * RAD_TO_DEG, ctx->gyro_drift_correction_data[2] * RAD_TO_DEG, DEGREE_UTF8); + SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text); + } + } + + } } SDL_free(mapping); @@ -1325,6 +1668,260 @@ void DestroyGamepadDisplay(GamepadDisplay *ctx) SDL_free(ctx); } +void RenderSensorTimingInfo(GyroDisplay *ctx, GamepadDisplay *gamepad_display) +{ + /* Sensor timing section */ + char text[128]; + const float new_line_height = gamepad_display->button_height + 2.0f; + const float text_offset_x = ctx->area.x + ctx->area.w / 4.0f + 40.0f; + /* Anchor to bottom left of principle rect. */ + float text_y_pos = ctx->area.y + ctx->area.h - new_line_height * 2; + /* + * Display rate of gyro as reported by the HID implementation. + * This could be based on a hardware time stamp (PS5), or it could be generated by the HID implementation. + * One should expect this to match the estimated rate below, assuming a wired connection. + */ + + SDL_strlcpy(text, "HID Sensor Time:", sizeof(text)); + SDLTest_DrawString(ctx->renderer, text_offset_x - SDL_strlen(text) * FONT_CHARACTER_SIZE, text_y_pos, text); + if (ctx->reported_sensor_rate_hz > 0) { + /* Convert to micro seconds */ + const int delta_time_us = (int)1e6 / ctx->reported_sensor_rate_hz; + SDL_snprintf(text, sizeof(text), "%d%ss %dhz", delta_time_us, MICRO_UTF8, ctx->reported_sensor_rate_hz); + } else { + SDL_snprintf(text, sizeof(text), "????%ss ???hz", MICRO_UTF8); + } + SDLTest_DrawString(ctx->renderer, text_offset_x + 2.0f, text_y_pos, text); + + /* + * Display the instrumentation's count of all sensor packets received over time. + * This may represent a more accurate polling rate for the IMU + * But only when using a wired connection. + * It does not necessarily reflect the rate at which the IMU is sampled. + */ + + text_y_pos += new_line_height; + SDL_strlcpy(text, "Est.Sensor Time:", sizeof(text)); + SDLTest_DrawString(ctx->renderer, text_offset_x - SDL_strlen(text) * FONT_CHARACTER_SIZE, text_y_pos, text); + if (ctx->estimated_sensor_rate_hz > 0) { + /* Convert to micro seconds */ + const int delta_time_us = (int)1e6 / ctx->estimated_sensor_rate_hz; + SDL_snprintf(text, sizeof(text), "%d%ss %dhz", delta_time_us, MICRO_UTF8, ctx->estimated_sensor_rate_hz); + } else { + SDL_snprintf(text, sizeof(text), "????%ss ???hz", MICRO_UTF8); + } + SDLTest_DrawString(ctx->renderer, text_offset_x + 2.0f, text_y_pos, text); +} + +void RenderGyroDriftCalibrationButton(GyroDisplay *ctx, GamepadDisplay *gamepad_display ) +{ + char label_text[128]; + float log_y = ctx->area.y + BUTTON_PADDING; + const float new_line_height = gamepad_display->button_height + 2.0f; + GamepadButton *start_calibration_button = GetGyroCalibrateButton(ctx); + bool bHasCachedDriftSolution = BHasCachedGyroDriftSolution(ctx); + + /* Show the recalibration progress bar. */ + float recalibrate_button_width = GetGamepadButtonLabelWidth(start_calibration_button) + 2 * BUTTON_PADDING; + SDL_FRect recalibrate_button_area; + recalibrate_button_area.x = ctx->area.x + ctx->area.w - recalibrate_button_width - BUTTON_PADDING; + recalibrate_button_area.y = log_y + FONT_CHARACTER_SIZE * 0.5f - gamepad_display->button_height * 0.5f; + recalibrate_button_area.w = GetGamepadButtonLabelWidth(start_calibration_button) + 2.0f * BUTTON_PADDING; + recalibrate_button_area.h = gamepad_display->button_height + BUTTON_PADDING * 2.0f; + + if (!bHasCachedDriftSolution) { + SDL_snprintf(label_text, sizeof(label_text), "Progress: %3.0f%% ", ctx->drift_calibration_progress_frac * 100.0f); + } else { + SDL_strlcpy(label_text, "Calibrate Drift", sizeof(label_text)); + } + + SetGamepadButtonLabel(start_calibration_button, label_text); + SetGamepadButtonArea(start_calibration_button, &recalibrate_button_area); + RenderGamepadButton(start_calibration_button); + + /* Above button */ + SDL_strlcpy(label_text, "Gyro Orientation:", sizeof(label_text)); + SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, recalibrate_button_area.y - new_line_height, label_text); + + if (!bHasCachedDriftSolution) { + + float flNoiseFraction = SDL_clamp(SDL_sqrtf(ctx->accelerometer_noise_sq) / ACCELEROMETER_NOISE_THRESHOLD, 0.0f, 1.0f); + bool bTooMuchNoise = (flNoiseFraction == 1.0f); + + float noise_bar_height = gamepad_display->button_height; + SDL_FRect noise_bar_rect; + noise_bar_rect.x = recalibrate_button_area.x; + noise_bar_rect.y = recalibrate_button_area.y + recalibrate_button_area.h + BUTTON_PADDING; + noise_bar_rect.w = recalibrate_button_area.w; + noise_bar_rect.h = noise_bar_height; + + /* Adjust the noise bar rectangle based on the accelerometer noise value */ + + float noise_bar_fill_width = flNoiseFraction * noise_bar_rect.w; /* Scale the width based on the noise value */ + SDL_FRect noise_bar_fill_rect; + noise_bar_fill_rect.x = noise_bar_rect.x + (noise_bar_rect.w - noise_bar_fill_width) * 0.5f; + noise_bar_fill_rect.y = noise_bar_rect.y; + noise_bar_fill_rect.w = noise_bar_fill_width; + noise_bar_fill_rect.h = noise_bar_height; + + /* Set the color based on the noise value */ + Uint8 red = (Uint8)(flNoiseFraction * 255.0f); + Uint8 green = (Uint8)((1.0f - flNoiseFraction) * 255.0f); + SDL_SetRenderDrawColor(ctx->renderer, red, green, 0, 255); /* red when high noise, green when low noise */ + SDL_RenderFillRect(ctx->renderer, &noise_bar_fill_rect); /* draw the filled rectangle */ + + SDL_SetRenderDrawColor(ctx->renderer, 100, 100, 100, 255); /* gray box */ + SDL_RenderRect(ctx->renderer, &noise_bar_rect); /* draw the outline rectangle */ + + /* Explicit warning message if we detect too much movement */ + if (bTooMuchNoise) { + SDL_strlcpy(label_text, "Place GamePad Down!", sizeof(label_text)); + SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, noise_bar_rect.y + noise_bar_rect.h + new_line_height, label_text); + } + + /* Drift progress bar */ + /* Demonstrate how far we are through the drift progress, and how it resets when there's "high noise", i.e if flNoiseFraction == 1.0f */ + SDL_FRect progress_bar_rect; + progress_bar_rect.x = recalibrate_button_area.x + BUTTON_PADDING; + progress_bar_rect.y = recalibrate_button_area.y + recalibrate_button_area.h * 0.5f + BUTTON_PADDING * 0.5f; + progress_bar_rect.w = recalibrate_button_area.w - BUTTON_PADDING * 2.0f; + progress_bar_rect.h = BUTTON_PADDING * 0.5f; + + /* Adjust the drift bar rectangle based on the drift calibration progress fraction */ + float drift_bar_fill_width = bTooMuchNoise ? 1.0f : ctx->drift_calibration_progress_frac * progress_bar_rect.w; + SDL_FRect progress_bar_fill; + progress_bar_fill.x = progress_bar_rect.x; + progress_bar_fill.y = progress_bar_rect.y; + progress_bar_fill.w = drift_bar_fill_width; + progress_bar_fill.h = progress_bar_rect.h; + + /* Set the color based on the drift calibration progress fraction */ + SDL_SetRenderDrawColor(ctx->renderer, GYRO_COLOR_GREEN); /* red when too much noise, green when low noise*/ + + /* Now draw the bars with the filled, then empty rectangles */ + SDL_RenderFillRect(ctx->renderer, &progress_bar_fill); /* draw the filled rectangle*/ + SDL_SetRenderDrawColor(ctx->renderer, 100, 100, 100, 255); /* gray box*/ + SDL_RenderRect(ctx->renderer, &progress_bar_rect); /* draw the outline rectangle*/ + + /* If there is too much movement, we are going to draw two diagonal red lines between the progress rect corners.*/ + if (bTooMuchNoise) { + SDL_SetRenderDrawColor(ctx->renderer, GYRO_COLOR_RED); /* red */ + SDL_RenderFillRect(ctx->renderer, &progress_bar_fill); /* draw the filled rectangle */ + } + } +} + +float RenderEulerReadout(GyroDisplay *ctx, GamepadDisplay *gamepad_display ) +{ + /* Get the mater button's width and base our width off that */ + GamepadButton *master_button = GetGyroCalibrateButton(ctx); + SDL_FRect gyro_calibrate_button_rect; + GetGamepadButtonArea(master_button, &gyro_calibrate_button_rect); + + char text[128]; + float log_y = gyro_calibrate_button_rect.y + gyro_calibrate_button_rect.h + BUTTON_PADDING; + const float new_line_height = gamepad_display->button_height + 2.0f; + float log_gyro_euler_text_x = gyro_calibrate_button_rect.x; + + /* Pitch Readout */ + SDL_snprintf(text, sizeof(text), "Pitch: %6.2f%s", ctx->euler_displacement_angles[0], DEGREE_UTF8); + SDLTest_DrawString(ctx->renderer, log_gyro_euler_text_x + 2.0f, log_y, text); + + /* Yaw Readout */ + log_y += new_line_height; + SDL_snprintf(text, sizeof(text), " Yaw: %6.2f%s", ctx->euler_displacement_angles[1], DEGREE_UTF8); + SDLTest_DrawString(ctx->renderer, log_gyro_euler_text_x + 2.0f, log_y, text); + + /* Roll Readout */ + log_y += new_line_height; + SDL_snprintf(text, sizeof(text), " Roll: %6.2f%s", ctx->euler_displacement_angles[2], DEGREE_UTF8); + SDLTest_DrawString(ctx->renderer, log_gyro_euler_text_x + 2.0f, log_y, text); + + return log_y + new_line_height; /* Return the next y position for further rendering */ +} + +/* Draws the 3D cube, circles and accel arrow, positioning itself relative to the calibrate button. */ +void RenderGyroGizmo(GyroDisplay *ctx, SDL_Gamepad *gamepad, float top) +{ + /* Get the calibrate button's on-screen area: */ + GamepadButton *btn = GetGyroCalibrateButton(ctx); + SDL_FRect btnArea; + GetGamepadButtonArea(btn, &btnArea); + + float gizmoSize = btnArea.w; + /* Position it centered horizontally above the button with a small gap */ + SDL_FRect gizmoRect; + gizmoRect.x = btnArea.x + (btnArea.w - gizmoSize) * 0.5f; + gizmoRect.y = top; + gizmoRect.w = gizmoSize; + gizmoRect.h = gizmoSize; + + /* Draw the rotated cube */ + DrawGyroDebugCube(ctx->renderer, &ctx->gyro_quaternion, &gizmoRect); + + /* Overlay the XYZ circles */ + DrawGyroDebugCircle(ctx->renderer, &ctx->gyro_quaternion, &gizmoRect); + + /* If we have accel, draw that arrow too */ + if (SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL)) { + float accel[3]; + SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_ACCEL, accel, SDL_arraysize(accel)); + DrawAccelerometerDebugArrow(ctx->renderer, &ctx->gyro_quaternion, accel, &gizmoRect); + } + + /* Follow the size of the main button, but position it below the gizmo */ + GamepadButton *reset_button = GetGyroResetButton(ctx); + if (reset_button) { + SDL_FRect reset_area; + GetGamepadButtonArea(reset_button, &reset_area); + /* Position the reset button below the gizmo */ + reset_area.x = btnArea.x; + reset_area.y = gizmoRect.y + gizmoRect.h + BUTTON_PADDING * 0.5f; + reset_area.w = btnArea.w; + reset_area.h = btnArea.h; + SetGamepadButtonArea(reset_button, &reset_area); + RenderGamepadButton(reset_button); + } +} + +void RenderGyroDisplay(GyroDisplay *ctx, GamepadDisplay *gamepadElements, SDL_Gamepad *gamepad) +{ + if (!ctx) + return; + + bool bHasAccelerometer = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL); + bool bHasGyroscope = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO); + bool bHasIMU = bHasAccelerometer || bHasGyroscope; + if (!bHasIMU) + return; + + Uint8 r, g, b, a; + SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); + + RenderSensorTimingInfo(ctx, gamepadElements); + + RenderGyroDriftCalibrationButton(ctx, gamepadElements); + + bool bHasCachedDriftSolution = BHasCachedGyroDriftSolution(ctx); + if (bHasCachedDriftSolution) { + float bottom = RenderEulerReadout(ctx, gamepadElements); + RenderGyroGizmo(ctx, gamepad, bottom); + + } + SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); +} + +void DestroyGyroDisplay(GyroDisplay *ctx) +{ + if (!ctx) { + return; + } + DestroyGamepadButton(ctx->reset_gyro_button); + DestroyGamepadButton(ctx->calibrate_gyro_button); + SDL_free(ctx); +} + + struct GamepadTypeDisplay { SDL_Renderer *renderer; @@ -1958,13 +2555,25 @@ GamepadButton *CreateGamepadButton(SDL_Renderer *renderer, const char *label) ctx->background = CreateTexture(renderer, gamepad_button_background_bmp, gamepad_button_background_bmp_len); SDL_GetTextureSize(ctx->background, &ctx->background_width, &ctx->background_height); - ctx->label = SDL_strdup(label); - ctx->label_width = (float)(FONT_CHARACTER_SIZE * SDL_strlen(label)); - ctx->label_height = (float)FONT_CHARACTER_SIZE; + SetGamepadButtonLabel(ctx, label); } return ctx; } +void SetGamepadButtonLabel(GamepadButton *ctx, const char *label) +{ + if (!ctx) { + return; + } + + if (ctx->label) { + SDL_free(ctx->label); + } + + ctx->label = SDL_strdup(label); + ctx->label_width = (float)(FONT_CHARACTER_SIZE * SDL_strlen(label)); + ctx->label_height = (float)FONT_CHARACTER_SIZE; +} void SetGamepadButtonArea(GamepadButton *ctx, const SDL_FRect *area) { if (!ctx) { @@ -2467,6 +3076,7 @@ static char *JoinMapping(MappingParts *parts) sort_order[i].index = i; } SDL_qsort(sort_order, parts->num_elements, sizeof(*sort_order), SortMapping); + MoveSortedEntry("face", sort_order, parts->num_elements, true); MoveSortedEntry("type", sort_order, parts->num_elements, true); MoveSortedEntry("platform", sort_order, parts->num_elements, true); MoveSortedEntry("crc", sort_order, parts->num_elements, true); @@ -2802,6 +3412,8 @@ const char *GetGamepadTypeString(SDL_GamepadType type) return "Joy-Con (R)"; case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: return "Joy-Con Pair"; + case SDL_GAMEPAD_TYPE_GAMECUBE: + return "GameCube"; default: return ""; } diff --git a/test/gamepadutils.h b/test/gamepadutils.h index c08261d1..157bc9a0 100644 --- a/test/gamepadutils.h +++ b/test/gamepadutils.h @@ -48,7 +48,19 @@ enum #define PRESSED_COLOR 175, 238, 238, SDL_ALPHA_OPAQUE #define PRESSED_TEXTURE_MOD 175, 238, 238 #define SELECTED_COLOR 224, 255, 224, SDL_ALPHA_OPAQUE +#define GYRO_COLOR_RED 255, 0, 0, SDL_ALPHA_OPAQUE +#define GYRO_COLOR_GREEN 0, 255, 0, SDL_ALPHA_OPAQUE +#define GYRO_COLOR_BLUE 0, 0, 255, SDL_ALPHA_OPAQUE +#define GYRO_COLOR_ORANGE 255, 128, 0, SDL_ALPHA_OPAQUE +/* Shared layout constants */ +#define BUTTON_PADDING 12.0f +#define MINIMUM_BUTTON_WIDTH 96.0f + +/* Symbol */ +#define DEGREE_UTF8 "\xC2\xB0" +#define SQUARED_UTF8 "\xC2\xB2" +#define MICRO_UTF8 "\xC2\xB5" /* Gamepad image display */ extern GamepadImage *CreateGamepadImage(SDL_Renderer *renderer); @@ -78,6 +90,7 @@ typedef struct GamepadDisplay GamepadDisplay; extern GamepadDisplay *CreateGamepadDisplay(SDL_Renderer *renderer); extern void SetGamepadDisplayDisplayMode(GamepadDisplay *ctx, ControllerDisplayMode display_mode); extern void SetGamepadDisplayArea(GamepadDisplay *ctx, const SDL_FRect *area); +extern void SetGamepadDisplayGyroDriftCorrection(GamepadDisplay *ctx, float *gyro_drift_correction); extern int GetGamepadDisplayElementAt(GamepadDisplay *ctx, SDL_Gamepad *gamepad, float x, float y); extern void SetGamepadDisplayHighlight(GamepadDisplay *ctx, int element, bool pressed); extern void SetGamepadDisplaySelected(GamepadDisplay *ctx, int element); @@ -118,6 +131,7 @@ extern void DestroyJoystickDisplay(JoystickDisplay *ctx); typedef struct GamepadButton GamepadButton; extern GamepadButton *CreateGamepadButton(SDL_Renderer *renderer, const char *label); +extern void SetGamepadButtonLabel(GamepadButton *ctx, const char *label); extern void SetGamepadButtonArea(GamepadButton *ctx, const SDL_FRect *area); extern void GetGamepadButtonArea(GamepadButton *ctx, SDL_FRect *area); extern void SetGamepadButtonHighlight(GamepadButton *ctx, bool highlight, bool pressed); @@ -127,6 +141,22 @@ extern bool GamepadButtonContains(GamepadButton *ctx, float x, float y); extern void RenderGamepadButton(GamepadButton *ctx); extern void DestroyGamepadButton(GamepadButton *ctx); +/* Gyro element Display */ +/* If you want to calbirate against a known rotation (i.e. a turn table test) Increase ACCELEROMETER_NOISE_THRESHOLD to about 5, or drift correction will be constantly reset.*/ +#define ACCELEROMETER_NOISE_THRESHOLD 0.5f +typedef struct Quaternion Quaternion; +typedef struct GyroDisplay GyroDisplay; + +extern void InitCirclePoints3D(); +extern GyroDisplay *CreateGyroDisplay(SDL_Renderer *renderer); +extern void SetGyroDisplayArea(GyroDisplay *ctx, const SDL_FRect *area); +extern bool BHasCachedGyroDriftSolution(GyroDisplay *ctx); +extern void SetGamepadDisplayIMUValues(GyroDisplay *ctx, float *gyro_drift_solution, float *euler_displacement_angles, Quaternion *gyro_quaternion, int reported_senor_rate_hz, int estimated_sensor_rate_hz, float drift_calibration_progress_frac, float accelerometer_noise_sq); +extern GamepadButton *GetGyroResetButton(GyroDisplay *ctx); +extern GamepadButton *GetGyroCalibrateButton(GyroDisplay *ctx); +extern void RenderGyroDisplay(GyroDisplay *ctx, GamepadDisplay *gamepadElements, SDL_Gamepad *gamepad); +extern void DestroyGyroDisplay(GyroDisplay *ctx); + /* Working with mappings and bindings */ /* Return whether a mapping has any bindings */ diff --git a/test/msdf_font.bmp b/test/msdf_font.bmp old mode 100755 new mode 100644 diff --git a/test/msdf_font.csv b/test/msdf_font.csv old mode 100755 new mode 100644 diff --git a/test/testaudio.c b/test/testaudio.c index ce9f798c..459f3e3c 100644 --- a/test/testaudio.c +++ b/test/testaudio.c @@ -1060,6 +1060,10 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { int i; + char version[32]; /* use SDL's version number, since this test program is part of SDL's sources. */ + SDL_snprintf(version, sizeof (version), "%d.%d.%d", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION); + SDL_SetAppMetadata("SDL testaudio", version, "org.libsdl.testaudio"); + state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO | SDL_INIT_AUDIO); if (!state) { return SDL_APP_FAILURE; diff --git a/test/testautomation_joystick.c b/test/testautomation_joystick.c index 274ba79f..bce6c7f2 100644 --- a/test/testautomation_joystick.c +++ b/test/testautomation_joystick.c @@ -132,6 +132,23 @@ static int SDLCALL TestVirtualJoystick(void *arg) SDL_UpdateJoysticks(); SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == false, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH) == false"); + /* Set an explicit mapping with legacy GameCube style buttons */ + SDL_SetGamepadMapping(SDL_GetJoystickID(joystick), "ff0013db5669727475616c2043007601,Virtual Nintendo GameCube,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,hint:SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,"); + { + const char *name = SDL_GetGamepadName(gamepad); + SDLTest_AssertCheck(name && SDL_strcmp(name, "Virtual Nintendo GameCube") == 0, "SDL_GetGamepadName() -> \"%s\" (expected \"%s\")", name, "Virtual Nintendo GameCube"); + } + SDLTest_AssertCheck(SDL_GetGamepadButtonLabel(gamepad, SDL_GAMEPAD_BUTTON_EAST) == SDL_GAMEPAD_BUTTON_LABEL_X, "SDL_GetGamepadButtonLabel(SDL_GAMEPAD_BUTTON_EAST) == SDL_GAMEPAD_BUTTON_LABEL_X"); + + /* Set the east button and verify that the gamepad responds, mapping "B" to the west button */ + SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_EAST, true), "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_EAST, true)"); + SDL_UpdateJoysticks(); + SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST) == true, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_WEST) == true"); + + SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_EAST, false), "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_EAST, false)"); + SDL_UpdateJoysticks(); + SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST) == false, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_WEST) == false"); + /* Set an explicit mapping with legacy Nintendo style buttons */ SDL_SetGamepadMapping(SDL_GetJoystickID(joystick), "ff0013db5669727475616c2043007601,Virtual Nintendo Gamepad,a:b1,b:b0,x:b3,y:b2,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,"); { diff --git a/test/testautomation_surface.c b/test/testautomation_surface.c index 3b840039..d3382309 100644 --- a/test/testautomation_surface.c +++ b/test/testautomation_surface.c @@ -959,6 +959,36 @@ static int SDLCALL surface_testBlitBlendMul(void *arg) return TEST_COMPLETED; } +/** + * Tests blitting invalid surfaces. + */ +static int SDLCALL surface_testBlitInvalid(void *arg) +{ + SDL_Surface *valid, *invalid; + bool result; + + valid = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA8888); + SDLTest_AssertCheck(valid != NULL, "Check surface creation"); + invalid = SDL_CreateSurface(0, 0, SDL_PIXELFORMAT_RGBA8888); + SDLTest_AssertCheck(invalid != NULL, "Check surface creation"); + SDLTest_AssertCheck(invalid->pixels == NULL, "Check surface pixels are NULL"); + + result = SDL_BlitSurface(invalid, NULL, valid, NULL); + SDLTest_AssertCheck(result == true, "SDL_BlitSurface(invalid, NULL, valid, NULL), result = %s\n", result ? "true" : "false"); + result = SDL_BlitSurface(valid, NULL, invalid, NULL); + SDLTest_AssertCheck(result == true, "SDL_BlitSurface(valid, NULL, invalid, NULL), result = %s\n", result ? "true" : "false"); + + result = SDL_BlitSurfaceScaled(invalid, NULL, valid, NULL, SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(result == false, "SDL_BlitSurfaceScaled(invalid, NULL, valid, NULL, SDL_SCALEMODE_NEAREST), result = %s\n", result ? "true" : "false"); + result = SDL_BlitSurfaceScaled(valid, NULL, invalid, NULL, SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(result == false, "SDL_BlitSurfaceScaled(valid, NULL, invalid, NULL, SDL_SCALEMODE_NEAREST), result = %s\n", result ? "true" : "false"); + + SDL_DestroySurface(valid); + SDL_DestroySurface(invalid); + + return TEST_COMPLETED; +} + static int SDLCALL surface_testOverflow(void *arg) { char buf[1024]; @@ -1632,6 +1662,10 @@ static const SDLTest_TestCaseReference surfaceTestBlitBlendMul = { surface_testBlitBlendMul, "surface_testBlitBlendMul", "Tests blitting routines with mul blending mode.", TEST_ENABLED }; +static const SDLTest_TestCaseReference surfaceTestBlitInvalid = { + surface_testBlitInvalid, "surface_testBlitInvalid", "Tests blitting routines with invalid surfaces.", TEST_ENABLED +}; + static const SDLTest_TestCaseReference surfaceTestOverflow = { surface_testOverflow, "surface_testOverflow", "Test overflow detection.", TEST_ENABLED }; @@ -1680,6 +1714,7 @@ static const SDLTest_TestCaseReference *surfaceTests[] = { &surfaceTestBlitBlendAddPremultiplied, &surfaceTestBlitBlendMod, &surfaceTestBlitBlendMul, + &surfaceTestBlitInvalid, &surfaceTestOverflow, &surfaceTestFlip, &surfaceTestPalette, diff --git a/test/testcontroller.c b/test/testcontroller.c index b717b30e..7602253f 100644 --- a/test/testcontroller.c +++ b/test/testcontroller.c @@ -32,12 +32,9 @@ #define TITLE_HEIGHT 48.0f #define PANEL_SPACING 25.0f #define PANEL_WIDTH 250.0f -#define MINIMUM_BUTTON_WIDTH 96.0f -#define BUTTON_MARGIN 16.0f -#define BUTTON_PADDING 12.0f #define GAMEPAD_WIDTH 512.0f #define GAMEPAD_HEIGHT 560.0f - +#define BUTTON_MARGIN 16.0f #define SCREEN_WIDTH (PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING + PANEL_WIDTH) #define SCREEN_HEIGHT (TITLE_HEIGHT + GAMEPAD_HEIGHT) @@ -49,6 +46,228 @@ typedef struct int m_nFarthestValue; } AxisState; +struct Quaternion +{ + float x, y, z, w; +}; + +static Quaternion quat_identity = { 0.0f, 0.0f, 0.0f, 1.0f }; + +Quaternion QuaternionFromEuler(float roll, float pitch, float yaw) +{ + Quaternion q; + float cy = SDL_cosf(yaw * 0.5f); + float sy = SDL_sinf(yaw * 0.5f); + float cp = SDL_cosf(pitch * 0.5f); + float sp = SDL_sinf(pitch * 0.5f); + float cr = SDL_cosf(roll * 0.5f); + float sr = SDL_sinf(roll * 0.5f); + + q.w = cr * cp * cy + sr * sp * sy; + q.x = sr * cp * cy - cr * sp * sy; + q.y = cr * sp * cy + sr * cp * sy; + q.z = cr * cp * sy - sr * sp * cy; + + return q; +} + +static void EulerFromQuaternion(Quaternion q, float *roll, float *pitch, float *yaw) +{ + float sinr_cosp = 2.0f * (q.w * q.x + q.y * q.z); + float cosr_cosp = 1.0f - 2.0f * (q.x * q.x + q.y * q.y); + float roll_rad = SDL_atan2f(sinr_cosp, cosr_cosp); + + float sinp = 2.0f * (q.w * q.y - q.z * q.x); + float pitch_rad; + if (SDL_fabsf(sinp) >= 1.0f) { + pitch_rad = SDL_copysignf(SDL_PI_F / 2.0f, sinp); + } else { + pitch_rad = SDL_asinf(sinp); + } + + float siny_cosp = 2.0f * (q.w * q.z + q.x * q.y); + float cosy_cosp = 1.0f - 2.0f * (q.y * q.y + q.z * q.z); + float yaw_rad = SDL_atan2f(siny_cosp, cosy_cosp); + + if (roll) + *roll = roll_rad; + if (pitch) + *pitch = pitch_rad; + if (yaw) + *yaw = yaw_rad; +} + +static void EulerDegreesFromQuaternion(Quaternion q, float *pitch, float *yaw, float *roll) +{ + float pitch_rad, yaw_rad, roll_rad; + EulerFromQuaternion(q, &pitch_rad, &yaw_rad, &roll_rad); + if (pitch) { + *pitch = pitch_rad * (180.0f / SDL_PI_F); + } + if (yaw) { + *yaw = yaw_rad * (180.0f / SDL_PI_F); + } + if (roll) { + *roll = roll_rad * (180.0f / SDL_PI_F); + } +} + +Quaternion MultiplyQuaternion(Quaternion a, Quaternion b) +{ + Quaternion q; + q.x = a.x * b.w + a.y * b.z - a.z * b.y + a.w * b.x; + q.y = -a.x * b.z + a.y * b.w + a.z * b.x + a.w * b.y; + q.z = a.x * b.y - a.y * b.x + a.z * b.w + a.w * b.z; + q.w = -a.x * b.x - a.y * b.y - a.z * b.z + a.w * b.w; + return q; +} + +void NormalizeQuaternion(Quaternion *q) +{ + float mag = SDL_sqrtf(q->x * q->x + q->y * q->y + q->z * q->z + q->w * q->w); + if (mag > 0.0f) { + q->x /= mag; + q->y /= mag; + q->z /= mag; + q->w /= mag; + } +} + +float Normalize180(float angle) +{ + angle = SDL_fmodf(angle + 180.0f, 360.0f); + if (angle < 0.0f) { + angle += 360.0f; + } + return angle - 180.0f; +} + +typedef struct +{ + Uint64 gyro_packet_number; + Uint64 accelerometer_packet_number; + /* When both gyro and accelerometer events have been processed, we can increment this and use it to calculate polling rate over time.*/ + Uint64 imu_packet_counter; + + Uint64 starting_time_stamp_ns; /* Use this to help estimate how many packets are received over a duration */ + Uint16 imu_estimated_sensor_rate; /* in Hz, used to estimate how many packets are received over a duration */ + + Uint64 last_sensor_time_stamp_ns;/* Comes from the event data/HID implementation. Official PS5/Edge gives true hardware time stamps. Others are simulated. Nanoseconds i.e. 1e9 */ + + /* Fresh data copied from sensor events. */ + float accel_data[3]; /* Meters per second squared, i.e. 9.81f means 9.81 meters per second squared */ + float gyro_data[3]; /* Degrees per second, i.e. 100.0f means 100 degrees per second */ + + float last_accel_data[3];/* Needed to detect motion (and inhibit drift calibration) */ + float accelerometer_length_squared; + float gyro_drift_accumulator[3]; + bool is_calibrating_drift; /* Starts on, but can be turned back on by the user to restart the drift calibration. */ + int gyro_drift_sample_count; + float gyro_drift_solution[3]; /* Non zero if calibration is complete. */ + + Quaternion integrated_rotation; /* Used to help test whether the time stamps and gyro degrees per second are set up correctly by the HID implementation */ +} IMUState; + +/* Reset the Drift calculation state */ +void StartGyroDriftCalibration(IMUState *imustate) +{ + imustate->is_calibrating_drift = true; + imustate->gyro_drift_sample_count = 0; + SDL_zeroa(imustate->gyro_drift_solution); + SDL_zeroa(imustate->gyro_drift_accumulator); +} +void ResetIMUState(IMUState *imustate) +{ + imustate->gyro_packet_number = 0; + imustate->accelerometer_packet_number = 0; + imustate->starting_time_stamp_ns = SDL_GetTicksNS(); + imustate->integrated_rotation = quat_identity; + imustate->accelerometer_length_squared = 0.0f; + imustate->integrated_rotation = quat_identity; + SDL_zeroa(imustate->last_accel_data); + SDL_zeroa(imustate->gyro_drift_solution); + StartGyroDriftCalibration(imustate); +} + +void ResetGyroOrientation(IMUState *imustate) +{ + imustate->integrated_rotation = quat_identity; +} + +/* More samples = more accurate drift correction, but also more time to calibrate.*/ +#define SDL_GAMEPAD_IMU_MIN_GYRO_DRIFT_SAMPLE_COUNT 1024 + +/* + * Average drift _per packet_ as opposed to _per second_ + * This reduces a small amount of overhead when applying the drift correction. + */ +void FinalizeDriftSolution(IMUState *imustate) +{ + if (imustate->gyro_drift_sample_count >= SDL_GAMEPAD_IMU_MIN_GYRO_DRIFT_SAMPLE_COUNT) { + imustate->gyro_drift_solution[0] = imustate->gyro_drift_accumulator[0] / (float)imustate->gyro_drift_sample_count; + imustate->gyro_drift_solution[1] = imustate->gyro_drift_accumulator[1] / (float)imustate->gyro_drift_sample_count; + imustate->gyro_drift_solution[2] = imustate->gyro_drift_accumulator[2] / (float)imustate->gyro_drift_sample_count; + } + + imustate->is_calibrating_drift = false; + ResetGyroOrientation(imustate); +} + +/* Sample gyro packet in order to calculate drift*/ +void SampleGyroPacketForDrift( IMUState *imustate ) +{ + if ( !imustate->is_calibrating_drift ) + return; + + /* Get the length squared difference of the last accelerometer data vs. the new one */ + float accelerometer_difference[3]; + accelerometer_difference[0] = imustate->accel_data[0] - imustate->last_accel_data[0]; + accelerometer_difference[1] = imustate->accel_data[1] - imustate->last_accel_data[1]; + accelerometer_difference[2] = imustate->accel_data[2] - imustate->last_accel_data[2]; + SDL_memcpy(imustate->last_accel_data, imustate->accel_data, sizeof(imustate->last_accel_data)); + + imustate->accelerometer_length_squared = accelerometer_difference[0] * accelerometer_difference[0] + accelerometer_difference[1] * accelerometer_difference[1] + accelerometer_difference[2] * accelerometer_difference[2]; + + /* Ideal threshold will vary considerably depending on IMU. PS5 needs a low value (0.05f). Nintendo Switch needs a higher value (0.15f). */ + const float flAccelerometerMovementThreshold = ACCELEROMETER_NOISE_THRESHOLD; + if (imustate->accelerometer_length_squared > flAccelerometerMovementThreshold * flAccelerometerMovementThreshold) { + /* Reset the drift calibration if the accelerometer has moved significantly */ + StartGyroDriftCalibration(imustate); + } else { + /* Sensor is stationary enough to evaluate for drift.*/ + ++imustate->gyro_drift_sample_count; + + imustate->gyro_drift_accumulator[0] += imustate->gyro_data[0]; + imustate->gyro_drift_accumulator[1] += imustate->gyro_data[1]; + imustate->gyro_drift_accumulator[2] += imustate->gyro_data[2]; + + if (imustate->gyro_drift_sample_count >= SDL_GAMEPAD_IMU_MIN_GYRO_DRIFT_SAMPLE_COUNT) { + FinalizeDriftSolution(imustate); + } + } +} + +void ApplyDriftSolution(float *gyro_data, const float *drift_solution) +{ + gyro_data[0] -= drift_solution[0]; + gyro_data[1] -= drift_solution[1]; + gyro_data[2] -= drift_solution[2]; +} + +void UpdateGyroRotation(IMUState *imustate, Uint64 sensorTimeStampDelta_ns) +{ + float sensorTimeDeltaTimeSeconds = SDL_NS_TO_SECONDS((float)sensorTimeStampDelta_ns); + /* Integrate speeds to get Rotational Displacement*/ + float pitch = imustate->gyro_data[0] * sensorTimeDeltaTimeSeconds; + float yaw = imustate->gyro_data[1] * sensorTimeDeltaTimeSeconds; + float roll = imustate->gyro_data[2] * sensorTimeDeltaTimeSeconds; + + /* Use quaternions to avoid gimbal lock*/ + Quaternion delta_rotation = QuaternionFromEuler(pitch, yaw, roll); + imustate->integrated_rotation = MultiplyQuaternion(imustate->integrated_rotation, delta_rotation); + NormalizeQuaternion(&imustate->integrated_rotation); +} + typedef struct { SDL_JoystickID id; @@ -56,6 +275,7 @@ typedef struct SDL_Joystick *joystick; int num_axes; AxisState *axis_state; + IMUState *imu_state; SDL_Gamepad *gamepad; char *mapping; @@ -71,6 +291,7 @@ static SDL_Renderer *screen = NULL; static ControllerDisplayMode display_mode = CONTROLLER_MODE_TESTING; static GamepadImage *image = NULL; static GamepadDisplay *gamepad_elements = NULL; +static GyroDisplay *gyro_elements = NULL; static GamepadTypeDisplay *gamepad_type = NULL; static JoystickDisplay *joystick_elements = NULL; static GamepadButton *setup_mapping_button = NULL; @@ -265,6 +486,8 @@ static void ClearButtonHighlights(void) ClearGamepadImage(image); SetGamepadDisplayHighlight(gamepad_elements, SDL_GAMEPAD_ELEMENT_INVALID, false); SetGamepadTypeDisplayHighlight(gamepad_type, SDL_GAMEPAD_TYPE_UNSELECTED, false); + SetGamepadButtonHighlight(GetGyroResetButton( gyro_elements ), false, false); + SetGamepadButtonHighlight(GetGyroCalibrateButton(gyro_elements), false, false); SetGamepadButtonHighlight(setup_mapping_button, false, false); SetGamepadButtonHighlight(done_mapping_button, false, false); SetGamepadButtonHighlight(cancel_button, false, false); @@ -276,6 +499,8 @@ static void ClearButtonHighlights(void) static void UpdateButtonHighlights(float x, float y, bool button_down) { ClearButtonHighlights(); + SetGamepadButtonHighlight(GetGyroResetButton(gyro_elements), GamepadButtonContains(GetGyroResetButton(gyro_elements), x, y), button_down); + SetGamepadButtonHighlight(GetGyroCalibrateButton(gyro_elements), GamepadButtonContains(GetGyroCalibrateButton(gyro_elements), x, y), button_down); if (display_mode == CONTROLLER_MODE_TESTING) { SetGamepadButtonHighlight(setup_mapping_button, GamepadButtonContains(setup_mapping_button, x, y), button_down); @@ -915,6 +1140,8 @@ static void AddController(SDL_JoystickID id, bool verbose) if (new_controller->joystick) { new_controller->num_axes = SDL_GetNumJoystickAxes(new_controller->joystick); new_controller->axis_state = (AxisState *)SDL_calloc(new_controller->num_axes, sizeof(*new_controller->axis_state)); + new_controller->imu_state = (IMUState *)SDL_calloc(1, sizeof(*new_controller->imu_state)); + ResetIMUState(new_controller->imu_state); } joystick = new_controller->joystick; @@ -959,6 +1186,9 @@ static void DelController(SDL_JoystickID id) if (controllers[i].axis_state) { SDL_free(controllers[i].axis_state); } + if (controllers[i].imu_state) { + SDL_free(controllers[i].imu_state); + } if (controllers[i].joystick) { SDL_CloseJoystick(controllers[i].joystick); } @@ -1133,6 +1363,99 @@ static void HandleGamepadRemoved(SDL_JoystickID id) controllers[i].gamepad = NULL; } } +static void HandleGamepadAccelerometerEvent(SDL_Event *event) +{ + controller->imu_state->accelerometer_packet_number++; + SDL_memcpy(controller->imu_state->accel_data, event->gsensor.data, sizeof(controller->imu_state->accel_data)); +} + +static void HandleGamepadGyroEvent(SDL_Event *event) +{ + controller->imu_state->gyro_packet_number++; + SDL_memcpy(controller->imu_state->gyro_data, event->gsensor.data, sizeof(controller->imu_state->gyro_data)); +} + +#define SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_COUNT 2048 +static void EstimatePacketRate() +{ + Uint64 now_ns = SDL_GetTicksNS(); + if (controller->imu_state->imu_packet_counter == 0) { + controller->imu_state->starting_time_stamp_ns = now_ns; + } + + /* Require a significant sample size before averaging rate. */ + if (controller->imu_state->imu_packet_counter >= SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_COUNT) { + Uint64 deltatime_ns = now_ns - controller->imu_state->starting_time_stamp_ns; + controller->imu_state->imu_estimated_sensor_rate = (Uint16)((controller->imu_state->imu_packet_counter * 1000000000ULL) / deltatime_ns); + } + + /* Flush sampled data after a brief period so that the imu_estimated_sensor_rate value can be read.*/ + if (controller->imu_state->imu_packet_counter >= SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_COUNT * 2) { + controller->imu_state->starting_time_stamp_ns = now_ns; + controller->imu_state->imu_packet_counter = 0; + } + ++controller->imu_state->imu_packet_counter; +} + +static void UpdateGamepadOrientation( Uint64 delta_time_ns ) +{ + if (!controller || !controller->imu_state) + return; + + SampleGyroPacketForDrift(controller->imu_state); + ApplyDriftSolution(controller->imu_state->gyro_data, controller->imu_state->gyro_drift_solution); + UpdateGyroRotation(controller->imu_state, delta_time_ns); +} + +static void HandleGamepadSensorEvent( SDL_Event* event ) +{ + if (!controller) { + return; + } + + if (controller->id != event->gsensor.which) { + return; + } + + if (event->gsensor.sensor == SDL_SENSOR_GYRO) { + HandleGamepadGyroEvent(event); + } else if (event->gsensor.sensor == SDL_SENSOR_ACCEL) { + HandleGamepadAccelerometerEvent(event); + } + + /* + This is where we can update the quaternion because we need to have a drift solution, which requires both + accelerometer and gyro events are received before progressing. + */ + if ( controller->imu_state->accelerometer_packet_number == controller->imu_state->gyro_packet_number ) { + + EstimatePacketRate(); + Uint64 sensorTimeStampDelta_ns = event->gsensor.sensor_timestamp - controller->imu_state->last_sensor_time_stamp_ns ; + UpdateGamepadOrientation(sensorTimeStampDelta_ns); + + float display_euler_angles[3]; + EulerDegreesFromQuaternion(controller->imu_state->integrated_rotation, &display_euler_angles[0], &display_euler_angles[1], &display_euler_angles[2]); + + float drift_calibration_progress_frac = controller->imu_state->gyro_drift_sample_count / (float)SDL_GAMEPAD_IMU_MIN_GYRO_DRIFT_SAMPLE_COUNT; + int reported_polling_rate_hz = sensorTimeStampDelta_ns > 0 ? (int)(SDL_NS_PER_SECOND / sensorTimeStampDelta_ns) : 0; + + /* Send the results to the frontend */ + SetGamepadDisplayIMUValues(gyro_elements, + controller->imu_state->gyro_drift_solution, + display_euler_angles, + &controller->imu_state->integrated_rotation, + reported_polling_rate_hz, + controller->imu_state->imu_estimated_sensor_rate, + drift_calibration_progress_frac, + controller->imu_state->accelerometer_length_squared + ); + + /* Also show the gyro correction next to the gyro speed - this is useful in turntable tests as you can use a turntable to calibrate for drift, and that drift correction is functionally the same as the turn table speed (ignoring drift) */ + SetGamepadDisplayGyroDriftCorrection(gamepad_elements, controller->imu_state->gyro_drift_solution); + + controller->imu_state->last_sensor_time_stamp_ns = event->gsensor.sensor_timestamp; + } +} static Uint16 ConvertAxisToRumble(Sint16 axisval) { @@ -1296,7 +1619,9 @@ static void VirtualGamepadMouseDown(float x, float y) int element = GetGamepadImageElementAt(image, x, y); if (element == SDL_GAMEPAD_ELEMENT_INVALID) { - SDL_FPoint point = { x, y }; + SDL_FPoint point; + point.x = x; + point.y = y; SDL_FRect touchpad; GetGamepadTouchpadArea(image, &touchpad); if (SDL_PointInRectFloat(&point, &touchpad)) { @@ -1738,8 +2063,9 @@ SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event) break; #endif /* VERBOSE_TOUCHPAD */ -#ifdef VERBOSE_SENSORS + case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: +#ifdef VERBOSE_SENSORS SDL_Log("Gamepad %" SDL_PRIu32 " sensor %s: %.2f, %.2f, %.2f (%" SDL_PRIu64 ")", event->gsensor.which, GetSensorName((SDL_SensorType) event->gsensor.sensor), @@ -1747,8 +2073,10 @@ SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event) event->gsensor.data[1], event->gsensor.data[2], event->gsensor.sensor_timestamp); - break; + #endif /* VERBOSE_SENSORS */ + HandleGamepadSensorEvent(event); + break; #ifdef VERBOSE_AXES case SDL_EVENT_GAMEPAD_AXIS_MOTION: @@ -1807,7 +2135,11 @@ SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event) } if (display_mode == CONTROLLER_MODE_TESTING) { - if (GamepadButtonContains(setup_mapping_button, event->button.x, event->button.y)) { + if (GamepadButtonContains(GetGyroResetButton(gyro_elements), event->button.x, event->button.y)) { + ResetGyroOrientation(controller->imu_state); + } else if (GamepadButtonContains(GetGyroCalibrateButton(gyro_elements), event->button.x, event->button.y)) { + StartGyroDriftCalibration(controller->imu_state); + } else if (GamepadButtonContains(setup_mapping_button, event->button.x, event->button.y)) { SetDisplayMode(CONTROLLER_MODE_BINDING); } } else if (display_mode == CONTROLLER_MODE_BINDING) { @@ -1886,6 +2218,10 @@ SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event) SDL_ReloadGamepadMappings(); } else if (event->key.key == SDLK_ESCAPE) { done = true; + } else if (event->key.key == SDLK_SPACE) { + if (controller && controller->imu_state) { + ResetGyroOrientation(controller->imu_state); + } } } else if (display_mode == CONTROLLER_MODE_BINDING) { if (event->key.key == SDLK_C && (event->key.mod & SDL_KMOD_CTRL)) { @@ -1994,6 +2330,7 @@ SDL_AppResult SDLCALL SDL_AppIterate(void *appstate) if (display_mode == CONTROLLER_MODE_TESTING) { RenderGamepadButton(setup_mapping_button); + RenderGyroDisplay(gyro_elements, gamepad_elements, controller->gamepad); } else if (display_mode == CONTROLLER_MODE_BINDING) { DrawBindingTips(screen); RenderGamepadButton(done_mapping_button); @@ -2148,6 +2485,17 @@ SDL_AppResult SDLCALL SDL_AppInit(void **appstate, int argc, char *argv[]) area.h = GAMEPAD_HEIGHT; SetGamepadDisplayArea(gamepad_elements, &area); + gyro_elements = CreateGyroDisplay(screen); + const float vidReservedHeight = 24.0f; + /* Bottom right of the screen */ + area.w = SCREEN_WIDTH * 0.375f; + area.h = SCREEN_HEIGHT * 0.475f; + area.x = SCREEN_WIDTH - area.w; + area.y = SCREEN_HEIGHT - area.h - vidReservedHeight; + + SetGyroDisplayArea(gyro_elements, &area); + InitCirclePoints3D(); + gamepad_type = CreateGamepadTypeDisplay(screen); area.x = 0; area.y = TITLE_HEIGHT; @@ -2227,6 +2575,7 @@ void SDLCALL SDL_AppQuit(void *appstate, SDL_AppResult result) SDL_free(controller_name); DestroyGamepadImage(image); DestroyGamepadDisplay(gamepad_elements); + DestroyGyroDisplay(gyro_elements); DestroyGamepadTypeDisplay(gamepad_type); DestroyJoystickDisplay(joystick_elements); DestroyGamepadButton(setup_mapping_button); diff --git a/test/testdialog.c b/test/testdialog.c index 3d19fb94..dcff5bd9 100644 --- a/test/testdialog.c +++ b/test/testdialog.c @@ -15,8 +15,9 @@ #include #include -const SDL_DialogFileFilter filters[3] = { +const SDL_DialogFileFilter filters[] = { { "All files", "*" }, + { "SVI Session Indexes", "index;svi-index;index.pb" }, { "JPG images", "jpg;jpeg" }, { "PNG images", "png" } }; @@ -54,7 +55,6 @@ int main(int argc, char *argv[]) const SDL_FRect open_folder_rect = { 370, 50, 220, 140 }; int i; const char *initial_path = NULL; - const int nfilters = sizeof(filters) / sizeof(*filters); /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); @@ -112,11 +112,11 @@ int main(int argc, char *argv[]) * - Nonzero if the user is allowed to choose multiple entries (not for SDL_ShowSaveFileDialog) */ if (SDL_PointInRectFloat(&p, &open_file_rect)) { - SDL_ShowOpenFileDialog(callback, NULL, w, filters, nfilters, initial_path, 1); + SDL_ShowOpenFileDialog(callback, NULL, w, filters, SDL_arraysize(filters), initial_path, 1); } else if (SDL_PointInRectFloat(&p, &open_folder_rect)) { SDL_ShowOpenFolderDialog(callback, NULL, w, initial_path, 1); } else if (SDL_PointInRectFloat(&p, &save_file_rect)) { - SDL_ShowSaveFileDialog(callback, NULL, w, filters, nfilters, initial_path); + SDL_ShowSaveFileDialog(callback, NULL, w, filters, SDL_arraysize(filters), initial_path); } } } diff --git a/test/testffmpeg_vulkan.c b/test/testffmpeg_vulkan.c index 4f2ba811..a6da6cbd 100644 --- a/test/testffmpeg_vulkan.c +++ b/test/testffmpeg_vulkan.c @@ -679,6 +679,16 @@ void SetupVulkanRenderProperties(VulkanVideoContext *context, SDL_PropertiesID p SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_GRAPHICS_QUEUE_FAMILY_INDEX_NUMBER, context->graphicsQueueFamilyIndex); } +#if LIBAVUTIL_VERSION_MAJOR >= 59 +static void AddQueueFamily(AVVulkanDeviceContext *ctx, int idx, int num, VkQueueFlagBits flags) +{ + AVVulkanDeviceQueueFamily *entry = &ctx->qf[ctx->nb_qf++]; + entry->idx = idx; + entry->num = num; + entry->flags = flags; +} +#endif /* LIBAVUTIL_VERSION_MAJOR */ + void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceContext *ctx) { ctx->get_proc_addr = context->vkGetInstanceProcAddr; @@ -690,6 +700,12 @@ void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceCon ctx->nb_enabled_inst_extensions = context->instanceExtensionsCount; ctx->enabled_dev_extensions = context->deviceExtensions; ctx->nb_enabled_dev_extensions = context->deviceExtensionsCount; +#if LIBAVUTIL_VERSION_MAJOR >= 59 + AddQueueFamily(ctx, context->graphicsQueueFamilyIndex, context->graphicsQueueCount, VK_QUEUE_GRAPHICS_BIT); + AddQueueFamily(ctx, context->transferQueueFamilyIndex, context->transferQueueCount, VK_QUEUE_TRANSFER_BIT); + AddQueueFamily(ctx, context->computeQueueFamilyIndex, context->computeQueueCount, VK_QUEUE_COMPUTE_BIT); + AddQueueFamily(ctx, context->decodeQueueFamilyIndex, context->decodeQueueCount, VK_QUEUE_VIDEO_DECODE_BIT_KHR); +#else ctx->queue_family_index = context->graphicsQueueFamilyIndex; ctx->nb_graphics_queues = context->graphicsQueueCount; ctx->queue_family_tx_index = context->transferQueueFamilyIndex; @@ -700,6 +716,7 @@ void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceCon ctx->nb_encode_queues = 0; ctx->queue_family_decode_index = context->decodeQueueFamilyIndex; ctx->nb_decode_queues = context->decodeQueueCount; +#endif /* LIBAVUTIL_VERSION_MAJOR */ } static int CreateCommandBuffers(VulkanVideoContext *context, SDL_Renderer *renderer) diff --git a/test/testhaptic.c b/test/testhaptic.c index a31d2786..d617db59 100644 --- a/test/testhaptic.c +++ b/test/testhaptic.c @@ -37,7 +37,7 @@ int main(int argc, char **argv) char *name = NULL; int index = -1; SDL_HapticEffect efx[9]; - int id[9]; + SDL_HapticEffectID id[9]; int nefx; unsigned int supported; SDL_HapticID *haptics; diff --git a/test/testmessage.c b/test/testmessage.c index 97c79608..f4e602ac 100644 --- a/test/testmessage.c +++ b/test/testmessage.c @@ -98,15 +98,16 @@ static int SDLCALL button_messagebox(void *eventNumber) } SDL_Log("Pressed button: %d, %s", button, button == -1 ? "[closed]" : text); + if (button == 2) { + continue; + } + if (eventNumber) { SDL_Event event; event.type = (Uint32)(intptr_t)eventNumber; SDL_PushEvent(&event); } - if (button == 2) { - continue; - } return 0; } } diff --git a/test/testmodal.c b/test/testmodal.c index 9e5ea63e..d898b896 100644 --- a/test/testmodal.c +++ b/test/testmodal.c @@ -97,7 +97,7 @@ int main(int argc, char *argv[]) } if (e.key.key == SDLK_M) { - if (SDL_SetWindowParent(w2, w2)) { + if (SDL_SetWindowParent(w2, w1)) { if (SDL_SetWindowModal(w2, true)) { SDL_SetWindowTitle(w2, "Modal Window"); } diff --git a/test/testpopup.c b/test/testpopup.c index 786f03c2..987eab8d 100644 --- a/test/testpopup.c +++ b/test/testpopup.c @@ -49,6 +49,9 @@ struct PopupWindow static struct PopupWindow *menus; static struct PopupWindow tooltip; +static bool no_constraints; +static bool no_grab; + /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ static void quit(int rc) { @@ -95,14 +98,27 @@ static bool create_popup(struct PopupWindow *new_popup, bool is_menu) const int w = is_menu ? MENU_WIDTH : TOOLTIP_WIDTH; const int h = is_menu ? MENU_HEIGHT : TOOLTIP_HEIGHT; const int v_off = is_menu ? 0 : 32; - const SDL_WindowFlags flags = is_menu ? SDL_WINDOW_POPUP_MENU : SDL_WINDOW_TOOLTIP; float x, y; focus = SDL_GetMouseFocus(); SDL_GetMouseState(&x, &y); - new_win = SDL_CreatePopupWindow(focus, - (int)x, (int)y + v_off, w, h, flags); + + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, focus); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_CONSTRAIN_POPUP_BOOLEAN, !no_constraints); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN, !no_grab); + if (is_menu) { + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN, true); + } else { + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_TOOLTIP_BOOLEAN, true); + } + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, w); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, h); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, (int)x); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, (int)y + v_off); + new_win = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); if (new_win) { new_renderer = SDL_CreateRenderer(new_win, state->renderdriver); @@ -249,8 +265,30 @@ int main(int argc, char *argv[]) } /* Parse commandline */ - if (!SDLTest_CommonDefaultArgs(state, argc, argv)) { - return 1; + for (i = 1; i < argc;) { + int consumed; + + consumed = SDLTest_CommonArg(state, i); + if (consumed == 0) { + consumed = -1; + if (SDL_strcasecmp(argv[i], "--no-constraints") == 0) { + no_constraints = true; + consumed = 1; + } else if (SDL_strcasecmp(argv[i], "--no-grab") == 0) { + no_grab = true; + consumed = 1; + } + } + if (consumed < 0) { + static const char *options[] = { + "[--no-constraints]", + "[--no-grab]", + NULL + }; + SDLTest_CommonLogUsage(state, argv[0], options); + return 1; + } + i += consumed; } if (!SDLTest_CommonInit(state)) { diff --git a/test/testprocess.c b/test/testprocess.c index 425b4451..4d6437d3 100644 --- a/test/testprocess.c +++ b/test/testprocess.c @@ -82,7 +82,7 @@ static int SDLCALL process_testArguments(void *arg) "", " ", "a b c", - "a\tb\tc\t", + "a\tb\tc\t\v\r\n", "\"a b\" c", "'a' 'b' 'c'", "%d%%%s", @@ -965,6 +965,165 @@ cleanup: return TEST_COMPLETED; } +static int process_testWindowsCmdline(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + const char *process_args[] = { + data->childprocess_path, + "--print-arguments", + "--", + "", + " ", + "a b c", + "a\tb\tc\t", + "\"a b\" c", + "'a' 'b' 'c'", + "%d%%%s", + "\\t\\c", + "evil\\", + "a\\b\"c\\", + "\"\\^&|<>%", /* characters with a special meaning */ + NULL + }; + /* this will have the same result as process_args, but escaped in a different way */ + const char *process_cmdline_template = + "%s " + "--print-arguments " + "-- " + "\"\" " + "\" \" " + "a\" \"b\" \"c\t" /* using tab as delimiter */ + "\"a\tb\tc\t\" " + "\"\"\"\"a b\"\"\" c\" " + "\"'a' 'b' 'c'\" " + "%%d%%%%%%s " /* will be passed to sprintf */ + "\\t\\c " + "evil\\ " + "a\\b\"\\\"\"c\\ " + "\\\"\\^&|<>%%"; + char process_cmdline[65535]; + SDL_PropertiesID props; + SDL_Process *process = NULL; + char *buffer; + int exit_code; + int i; + size_t total_read = 0; + +#ifndef SDL_PLATFORM_WINDOWS + SDLTest_AssertPass("SDL_PROP_PROCESS_CREATE_CMDLINE_STRING only works on Windows"); + return TEST_SKIPPED; +#endif + + props = SDL_CreateProperties(); + SDLTest_AssertCheck(props != 0, "SDL_CreateProperties()"); + if (!props) { + goto failed; + } + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_APP); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN, true); + + process = SDL_CreateProcessWithProperties(props); + SDLTest_AssertCheck(process == NULL, "SDL_CreateProcessWithProperties() should fail"); + + SDL_snprintf(process_cmdline, SDL_arraysize(process_cmdline), process_cmdline_template, data->childprocess_path); + SDL_SetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, process_cmdline); + + process = SDL_CreateProcessWithProperties(props); + SDLTest_AssertCheck(process != NULL, "SDL_CreateProcessWithProperties()"); + if (!process) { + goto failed; + } + + exit_code = 0xdeadbeef; + buffer = (char *)SDL_ReadProcess(process, &total_read, &exit_code); + SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()"); + SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code); + if (!buffer) { + goto failed; + } + SDLTest_LogEscapedString("stdout of process: ", buffer, total_read); + + for (i = 3; process_args[i]; i++) { + char line[64]; + SDL_snprintf(line, sizeof(line), "|%d=%s|", i - 3, process_args[i]); + SDLTest_AssertCheck(!!SDL_strstr(buffer, line), "Check %s is in output", line); + } + SDL_free(buffer); + + SDLTest_AssertPass("About to destroy process"); + SDL_DestroyProcess(process); + + return TEST_COMPLETED; + +failed: + SDL_DestroyProcess(process); + return TEST_ABORTED; +} + +static int process_testWindowsCmdlinePrecedence(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + const char *process_args[] = { + data->childprocess_path, + "--print-arguments", + "--", + "argument 1", + NULL + }; + const char *process_cmdline_template = "%s --print-arguments -- \"argument 2\""; + char process_cmdline[65535]; + SDL_PropertiesID props; + SDL_Process *process = NULL; + char *buffer; + int exit_code; + size_t total_read = 0; + +#ifndef SDL_PLATFORM_WINDOWS + SDLTest_AssertPass("SDL_PROP_PROCESS_CREATE_CMDLINE_STRING only works on Windows"); + return TEST_SKIPPED; +#endif + + props = SDL_CreateProperties(); + SDLTest_AssertCheck(props != 0, "SDL_CreateProperties()"); + if (!props) { + goto failed; + } + + SDL_snprintf(process_cmdline, SDL_arraysize(process_cmdline), process_cmdline_template, data->childprocess_path); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (void *)process_args); + SDL_SetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, (const char *)process_cmdline); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_APP); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN, true); + + process = SDL_CreateProcessWithProperties(props); + SDLTest_AssertCheck(process != NULL, "SDL_CreateProcessWithProperties()"); + if (!process) { + goto failed; + } + + exit_code = 0xdeadbeef; + buffer = (char *)SDL_ReadProcess(process, &total_read, &exit_code); + SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()"); + SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code); + if (!buffer) { + goto failed; + } + SDLTest_LogEscapedString("stdout of process: ", buffer, total_read); + SDLTest_AssertCheck(!!SDL_strstr(buffer, "|0=argument 2|"), "Check |0=argument 2| is printed"); + SDL_free(buffer); + + SDLTest_AssertPass("About to destroy process"); + SDL_DestroyProcess(process); + + return TEST_COMPLETED; + +failed: + SDL_DestroyProcess(process); + return TEST_ABORTED; +} + static const SDLTest_TestCaseReference processTestArguments = { process_testArguments, "process_testArguments", "Test passing arguments to child process", TEST_ENABLED }; @@ -1017,6 +1176,14 @@ static const SDLTest_TestCaseReference processTestFileRedirection = { process_testFileRedirection, "process_testFileRedirection", "Test redirection from/to files", TEST_ENABLED }; +static const SDLTest_TestCaseReference processTestWindowsCmdline = { + process_testWindowsCmdline, "process_testWindowsCmdline", "Test passing cmdline directly to CreateProcess", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference processTestWindowsCmdlinePrecedence = { + process_testWindowsCmdlinePrecedence, "process_testWindowsCmdlinePrecedence", "Test SDL_PROP_PROCESS_CREATE_CMDLINE_STRING precedence over SDL_PROP_PROCESS_CREATE_ARGS_POINTER", TEST_ENABLED +}; + static const SDLTest_TestCaseReference *processTests[] = { &processTestArguments, &processTestExitCode, @@ -1031,6 +1198,8 @@ static const SDLTest_TestCaseReference *processTests[] = { &processTestNonExistingExecutable, &processTestBatBadButVulnerability, &processTestFileRedirection, + &processTestWindowsCmdline, + &processTestWindowsCmdlinePrecedence, NULL }; diff --git a/test/testtray.c b/test/testtray.c index fdb12daf..8046d60c 100644 --- a/test/testtray.c +++ b/test/testtray.c @@ -1,3 +1,4 @@ +#include "testutils.h" #include #include #include @@ -520,14 +521,17 @@ int main(int argc, char **argv) goto quit; } - /* TODO: Resource paths? */ - SDL_Surface *icon = SDL_LoadBMP("../test/sdl-test_round.bmp"); + char *icon1filename = GetResourceFilename(NULL, "sdl-test_round.bmp"); + SDL_Surface *icon = SDL_LoadBMP(icon1filename); + SDL_free(icon1filename); if (!icon) { SDL_Log("Couldn't load icon 1, proceeding without: %s", SDL_GetError()); } - SDL_Surface *icon2 = SDL_LoadBMP("../test/speaker.bmp"); + char *icon2filename = GetResourceFilename(NULL, "speaker.bmp"); + SDL_Surface *icon2 = SDL_LoadBMP(icon2filename); + SDL_free(icon2filename); if (!icon2) { SDL_Log("Couldn't load icon 2, proceeding without: %s", SDL_GetError()); diff --git a/wayland-protocols/pointer-warp-v1.xml b/wayland-protocols/pointer-warp-v1.xml new file mode 100644 index 00000000..158dad83 --- /dev/null +++ b/wayland-protocols/pointer-warp-v1.xml @@ -0,0 +1,72 @@ + + + + Copyright © 2024 Neal Gompa + Copyright © 2024 Xaver Hugl + Copyright © 2024 Matthias Klumpp + Copyright © 2024 Vlad Zahorodnii + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This global interface allows applications to request the pointer to be + moved to a position relative to a wl_surface. + + Note that if the desired behavior is to constrain the pointer to an area + or lock it to a position, this protocol does not provide a reliable way + to do that. The pointer constraint and pointer lock protocols should be + used for those use cases instead. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + Destroy the pointer warp manager. + + + + + + Request the compositor to move the pointer to a surface-local position. + Whether or not the compositor honors the request is implementation defined, + but it should + - honor it if the surface has pointer focus, including + when it has an implicit pointer grab + - reject it if the enter serial is incorrect + - reject it if the requested position is outside of the surface + + Note that the enter serial is valid for any surface of the client, + and does not have to be from the surface the pointer is warped to. + + + + + + + + + +