diff --git a/CMakeLists.txt b/CMakeLists.txt index e0aeceff..6788b8f9 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,7 +227,7 @@ set(MEGA_LIBVORBIS_VER "1.3.5") set(MEGA_LIBTHEORA_VER "1.1.1") set(MEGA_FREETYPE_VER "2.13.2") set(MEGA_SDL2_VER "2.28.5") -set(MEGA_SDL3_VER "3.2.10") +set(MEGA_SDL3_VER "3.4.10") set(MEGA_OPENAL_VER "1.24.3") set(MEGA_MODPLUG_VER "0.8.8.4") diff --git a/libs/SDL3/.git-hash b/libs/SDL3/.git-hash new file mode 100644 index 00000000..f6a4cec8 --- /dev/null +++ b/libs/SDL3/.git-hash @@ -0,0 +1 @@ +8e37db5e797b6167f3a00d697d816a684bd259c7 diff --git a/libs/SDL3/.github/PULL_REQUEST_TEMPLATE.md b/libs/SDL3/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 533be857..00000000 --- a/libs/SDL3/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,7 +0,0 @@ - - -## Description - - -## Existing Issue(s) - diff --git a/libs/SDL3/.github/actions/setup-gdk-desktop/action.yml b/libs/SDL3/.github/actions/setup-gdk-desktop/action.yml deleted file mode 100644 index 10427ace..00000000 --- a/libs/SDL3/.github/actions/setup-gdk-desktop/action.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: 'Setup GDK (Game Development Kit) for Windows Desktop' -description: 'Download GDK and install into MSBuild' -inputs: - # Keep edition and ref in sync! - edition: - description: 'GDK edition' - default: '240601' # YYMMUU (Year Month Update) - ref: - description: 'Git reference' - default: 'June_2024_Update_1' - folder: - description: 'Folder where to create Directory.Build.props' - required: true - default: '${{ github.workspace }}' -runs: - using: 'composite' - steps: - - uses: actions/setup-python@main - with: - python-version: 3.x - - name: 'Calculate variables' - id: calc - shell: pwsh - run: | - $vs_folder=@(vswhere -latest -property installationPath) - echo "vs-folder=${vs_folder}" >> $Env:GITHUB_OUTPUT - - echo "gdk-path=${{ runner.temp }}\GDK-${{ inputs.edition }}" >> $Env:GITHUB_OUTPUT - - echo "cache-key=gdk-${{ inputs.ref }}-${{ inputs.edition }}" >> $Env:GITHUB_OUTPUT - - name: 'Restore cached GDK' - id: cache-restore - uses: actions/cache/restore@v4 - with: - path: '${{ steps.calc.outputs.gdk-path }}' - key: ${{ steps.calc.outputs.cache-key }} - - name: 'Download GDK' - if: ${{ !steps.cache-restore.outputs.cache-hit }} - shell: pwsh - run: | - python build-scripts/setup-gdk-desktop.py ` - --download ` - --temp-folder "${{ runner.temp }}" ` - --gdk-path="${{ steps.calc.outputs.gdk-path }}" ` - --ref-edition "${{ inputs.ref }},${{ inputs.edition }}" ` - --vs-folder="${{ steps.calc.outputs.vs-folder }}" ` - --no-user-props - - name: 'Extract GDK' - if: ${{ !steps.cache-restore.outputs.cache-hit }} - shell: pwsh - run: | - python build-scripts/setup-gdk-desktop.py ` - --extract ` - --ref-edition "${{ inputs.ref }},${{ inputs.edition }}" ` - --temp-folder "${{ runner.temp }}" ` - --gdk-path="${{ steps.calc.outputs.gdk-path }}" ` - --vs-folder="${{ steps.calc.outputs.vs-folder }}" ` - --no-user-props - - name: 'Cache GDK' - if: ${{ !steps.cache-restore.outputs.cache-hit }} - uses: actions/cache/save@v4 - with: - path: '${{ steps.calc.outputs.gdk-path }}' - key: ${{ steps.calc.outputs.cache-key }} - - name: 'Copy MSBuild files into GDK' - shell: pwsh - run: | - python build-scripts/setup-gdk-desktop.py ` - --ref-edition "${{ inputs.ref }},${{ inputs.edition }}" ` - --gdk-path="${{ steps.calc.outputs.gdk-path }}" ` - --vs-folder="${{ steps.calc.outputs.vs-folder }}" ` - --copy-msbuild ` - --no-user-props - - name: 'Write user props' - shell: pwsh - run: | - python build-scripts/setup-gdk-desktop.py ` - --ref-edition "${{ inputs.ref }},${{ inputs.edition }}" ` - --temp-folder "${{ runner.temp }}" ` - --vs-folder="${{ steps.calc.outputs.vs-folder }}" ` - --gdk-path="${{ steps.calc.outputs.gdk-path }}" ` - "--props-folder=${{ inputs.folder }}" diff --git a/libs/SDL3/.github/actions/setup-loongarch64-toolchain/action.yml b/libs/SDL3/.github/actions/setup-loongarch64-toolchain/action.yml deleted file mode 100644 index e7f9ddc7..00000000 --- a/libs/SDL3/.github/actions/setup-loongarch64-toolchain/action.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: 'Setup LoongArch64 toolchain' -description: 'Download Linux LoongArch64 toolchain and set output variables' -inputs: - version: - description: 'LoongArch64 version' - default: '2023.08.08' -outputs: - prefix: - description: "LoongArch toolchain prefix" - value: ${{ steps.final.outputs.prefix }} - cc: - description: "LoongArch C compiler" - value: ${{ steps.final.outputs.cc }} - cxx: - description: "LoongArch C++ compiler" - value: ${{ steps.final.outputs.cxx }} -runs: - using: 'composite' - steps: - - uses: actions/cache/restore@v4 - id: restore-cache - with: - path: /opt/cross-tools - key: loongarch64-${{ inputs.version }} - - - name: 'Download LoongArch64 gcc+glibc toolchain' - if: ${{ !steps.restore-cache.outputs.cache-hit }} - shell: bash - run: | - url="https://github.com/loongson/build-tools/releases/download/${{ inputs.version }}/CLFS-loongarch64-8.1-x86_64-cross-tools-gcc-glibc.tar.xz" - - wget "$url" -O /tmp/toolchain.tar.xz - - mkdir -p /opt - tar -C /opt -x -f /tmp/toolchain.tar.xz - - - uses: actions/cache/save@v4 - if: ${{ !steps.restore-cache.outputs.cache-hit }} - with: - path: /opt/cross-tools - key: loongarch64-${{ inputs.version }} - - name: 'Set output vars' - id: final - shell: bash - run: | - prefix=/opt/cross-tools - echo "prefix=${prefix}" >> $GITHUB_OUTPUT - cc="${prefix}/bin/loongarch64-unknown-linux-gnu-gcc" - cxx="${prefix}/bin/loongarch64-unknown-linux-gnu-g++" - echo "cc=${cc}" >> $GITHUB_OUTPUT - echo "cxx=${cxx}" >> $GITHUB_OUTPUT - echo "LOONGARCH64_CC=${cc}" >>$GITHUB_ENV - echo "LOONGARCH64_CXX=${cxx}" >>$GITHUB_ENV diff --git a/libs/SDL3/.github/actions/setup-msvc-libusb/action.yml b/libs/SDL3/.github/actions/setup-msvc-libusb/action.yml deleted file mode 100644 index b8732f9c..00000000 --- a/libs/SDL3/.github/actions/setup-msvc-libusb/action.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: 'Setup libusb for MSVC' -description: 'Download libusb sdk for MSVC, and set output/environment variables' -inputs: - version: - description: 'libusb version' - required: true - default: '1.0.27' - arch: - description: "libusb architecture (x86 or x64)" - rqeuired: true -outputs: - root: - description: "libusb root directory" - value: ${{ steps.final.outputs.root }} -runs: - using: 'composite' - steps: - - name: 'Restore cached libusb-${{ inputs.version }}.7z' - id: cache-restore - uses: actions/cache/restore@v4 - with: - path: 'C:\temp\libusb-${{ inputs.version }}.7z' - key: libusb-msvc-${{ inputs.version }} - - name: 'Download libusb ${{ inputs.version }}' - if: ${{ !steps.cache-restore.outputs.cache-hit }} - shell: pwsh - run: | - Invoke-WebRequest "https://github.com/libusb/libusb/releases/download/v${{ inputs.version }}/libusb-${{ inputs.version }}.7z" -OutFile "C:\temp\libusb-${{ inputs.version }}.7z" - - name: 'Cache libusb-${{ inputs.version }}.7z' - if: ${{ !steps.cache-restore.outputs.cache-hit }} - uses: actions/cache/save@v4 - with: - path: 'C:\temp\libusb-${{ inputs.version }}.7z' - key: libusb-msvc-${{ inputs.version }} - - name: 'Extract libusb' - shell: pwsh - run: | - 7z "-oC:\temp\libusb-${{ inputs.version }}" x "C:\temp\libusb-${{ inputs.version }}.7z" - - name: 'Set output vars' - id: final - shell: pwsh - run: | - if ('${{ inputs.arch }}' -eq 'x86') { - $archdir = "MS32"; - } elseif ('${{ inputs.arch }}' -eq 'x64') { - $archdir = "MS64"; - } else { - write-host "Invalid arch=${{ inputs.arch }}" - exit 1 - } - $libusb_incdir = "C:\temp\libusb-${{ inputs.version }}\include"; - $libusb_libdir = "C:\temp\libusb-${{ inputs.version }}\VS2022\${archdir}\dll"; - - $libusb_header = "${libusb_incdir}\libusb.h"; - $libusb_implib = "${libusb_libdir}\libusb-1.0.lib"; - $libusb_dll = "${libusb_libdir}\libusb-1.0.dll"; - - if (!(Test-Path "${libusb_header}")) { - write-host "${libusb_header} does not exist!" - exit 1 - } - if (!(Test-Path "${libusb_implib}")){ - write-host "${libusb_implib} does not exist!" - exit 1 - } - if (!(Test-Path "${libusb_dll}")) { - write-host "${libusb_dll} does not exist!" - exit 1 - } - echo "root=${libusb_incdir};${libusb_libdir}" >> $env:GITHUB_OUTPUT - echo "LibUSB_ROOT=${libusb_incdir};${libusb_libdir}" >> $env:GITHUB_ENV diff --git a/libs/SDL3/.github/actions/setup-ngage-sdk/action.yml b/libs/SDL3/.github/actions/setup-ngage-sdk/action.yml deleted file mode 100644 index fa83418b..00000000 --- a/libs/SDL3/.github/actions/setup-ngage-sdk/action.yml +++ /dev/null @@ -1,102 +0,0 @@ -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/libs/SDL3/.github/actions/setup-ninja/action.yml b/libs/SDL3/.github/actions/setup-ninja/action.yml deleted file mode 100644 index a1d3ad98..00000000 --- a/libs/SDL3/.github/actions/setup-ninja/action.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: 'Setup ninja' -description: 'Download ninja and add it to the PATH environment variable' -inputs: - version: - description: 'Ninja version' - default: '1.12.1' -runs: - using: 'composite' - steps: - - name: 'Calculate variables' - id: calc - shell: sh - run: | - case "${{ runner.os }}-${{ runner.arch }}" in - "Linux-X86" | "Linux-X64") - archive="ninja-linux.zip" - ;; - "Linux-ARM64") - archive="ninja-linux-aarch64.zip" - ;; - "macOS-X86" | "macOS-X64" | "macOS-ARM64") - archive="ninja-mac.zip" - ;; - "Windows-X86" | "Windows-X64") - archive="ninja-win.zip" - ;; - "Windows-ARM64") - archive="ninja-winarm64.zip" - ;; - *) - echo "Unsupported ${{ runner.os }}-${{ runner.arch }}" - exit 1; - ;; - esac - echo "archive=${archive}" >> ${GITHUB_OUTPUT} - echo "cache-key=${archive}-${{ inputs.version }}-${{ runner.os }}-${{ runner.arch }}" >> ${GITHUB_OUTPUT} - - name: 'Restore cached ${{ steps.calc.outputs.archive }}' - id: cache-restore - uses: actions/cache/restore@v4 - with: - path: '${{ runner.temp }}/${{ steps.calc.outputs.archive }}' - key: ${{ steps.calc.outputs.cache-key }} - - name: 'Download ninja ${{ inputs.version }} for ${{ runner.os }} (${{ runner.arch }})' - if: ${{ !steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false' }} - shell: pwsh - run: | - Invoke-WebRequest "https://github.com/ninja-build/ninja/releases/download/v${{ inputs.version }}/${{ steps.calc.outputs.archive }}" -OutFile "${{ runner.temp }}/${{ steps.calc.outputs.archive }}" - - 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 }}/${{ steps.calc.outputs.archive }}' - key: ${{ steps.calc.outputs.cache-key }} - - name: 'Extract ninja' - shell: pwsh - run: | - 7z "-o${{ runner.temp }}/ninja-${{ inputs.version }}-${{ runner.arch }}" x "${{ runner.temp }}/${{ steps.calc.outputs.archive }}" - - name: 'Set output variables' - id: final - shell: pwsh - run: | - echo "${{ runner.temp }}/ninja-${{ inputs.version }}-${{ runner.arch }}" >> $env:GITHUB_PATH diff --git a/libs/SDL3/.github/actions/setup-vita-gles/action.yml b/libs/SDL3/.github/actions/setup-vita-gles/action.yml deleted file mode 100644 index e263737b..00000000 --- a/libs/SDL3/.github/actions/setup-vita-gles/action.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: 'Setup GLES for PlayStation Vita' -description: 'Download GLES for VITA (PVR or PIB), and copy it into the vita sdk' -inputs: - pib-version: - description: 'PIB version' - default: '1.1.4' - pvr-version: - description: 'PVR_PSP2 version' - default: '3.9' - type: - description: '"pib" or "pvr"' - default: '' -runs: - using: 'composite' - steps: - - name: 'Calculate variables' - id: calc - shell: sh - run: | - if test "x${VITASDK}" = "x"; then - echo "VITASDK must be defined" - exit 1; - fi - case "x${{ inputs.type }}" in - "xpvr") - echo "cache-key=SDL-vita-gles-pvr-${{ inputs.pvr-version}}" >> ${GITHUB_OUTPUT} - ;; - "xpib") - echo "cache-key=SDL-vita-gles-pib-${{ inputs.pib-version}}" >> ${GITHUB_OUTPUT} - ;; - *) - echo "Invalid type. Must be 'pib' or 'pvr'." - exit 1 - ;; - esac - - uses: actions/cache/restore@v4 - id: restore-cache - with: - path: /vita/dependencies - key: '${{ steps.calc.outputs.cache-key }}' - - name: 'Download PVR_PSP2 (GLES)' - if: ${{ !steps.restore-cache.outputs.cache-hit && inputs.type == 'pvr' }} - shell: sh - run: | - pvr_psp2_version=${{ inputs.pvr-version }} - - mkdir -p /vita/dependencies/include - mkdir -p /vita/dependencies/lib - - # Configure PVR_PSP2 headers - wget https://github.com/GrapheneCt/PVR_PSP2/archive/refs/tags/v$pvr_psp2_version.zip -P/tmp - unzip /tmp/v$pvr_psp2_version.zip -d/tmp - cp -r /tmp/PVR_PSP2-$pvr_psp2_version/include/* /vita/dependencies/include - rm /tmp/v$pvr_psp2_version.zip - - # include guard of PVR_PSP2's khrplatform.h does not match the usual one - sed -i -e s/__drvkhrplatform_h_/__khrplatform_h_/ /vita/dependencies/include/KHR/khrplatform.h - - # Configure PVR_PSP2 stub libraries - wget https://github.com/GrapheneCt/PVR_PSP2/releases/download/v$pvr_psp2_version/vitasdk_stubs.zip -P/tmp - unzip /tmp/vitasdk_stubs.zip -d/tmp/pvr_psp2_stubs - find /tmp/pvr_psp2_stubs -type f -name "*.a" -exec cp {} /vita/dependencies/lib \; - rm /tmp/vitasdk_stubs.zip - rm -rf /tmp/pvr_psp2_stubs - - - name: 'Download gl4es4vita (OpenGL)' - if: ${{ !steps.restore-cache.outputs.cache-hit && inputs.type == 'pib' }} - shell: sh - run: | - gl4es4vita_version=${{ inputs.pib-version }} - - mkdir -p /vita/dependencies/include - mkdir -p /vita/dependencies/lib - - # Configure gl4es4vita headers - wget https://github.com/SonicMastr/gl4es4vita/releases/download/v$gl4es4vita_version-vita/include.zip -P/tmp - unzip -o /tmp/include.zip -d/vita/dependencies/include - rm /tmp/include.zip - - # Configure gl4es4vita stub libraries - wget https://github.com/SonicMastr/gl4es4vita/releases/download/v$gl4es4vita_version-vita/vitasdk_stubs.zip -P/tmp - unzip /tmp/vitasdk_stubs.zip -d/vita/dependencies/lib - - - uses: actions/cache/save@v4 - if: ${{ !steps.restore-cache.outputs.cache-hit }} - with: - path: /vita/dependencies - key: '${{ steps.calc.outputs.cache-key }}' - - - name: Copy PVR_PSP2 (GLES) or gl4es4vita (OpenGL) to vita toolchain dir - shell: sh - run: | - cp -rv /vita/dependencies/* ${VITASDK}/arm-vita-eabi diff --git a/libs/SDL3/.github/cmake/CMakeLists.txt b/libs/SDL3/.github/cmake/CMakeLists.txt deleted file mode 100644 index 48757010..00000000 --- a/libs/SDL3/.github/cmake/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -cmake_minimum_required(VERSION 3.0...3.5) -project(ci_utils C CXX) - -set(txt "CC=${CMAKE_C_COMPILER} -CXX=${CMAKE_CXX_COMPILER} -CFLAGS=${CMAKE_C_FLAGS} -CXXFLAGS=${CMAKE_CXX_FLAGS} -LDFLAGS=${CMAKE_EXE_LINKER_FLAGS} ${CMAKE_C_STANDARD_LIBRARIES} -") - -message("${txt}") - -set(VAR_PATH "/tmp/env.txt" CACHE PATH "Where to write environment file") -message(STATUS "Writing CC/CXX/CFLAGS/CXXFLAGS/LDFLAGS environment to ${VAR_PATH}") - -file(WRITE "${VAR_PATH}" "${txt}") diff --git a/libs/SDL3/.github/workflows/build.yml b/libs/SDL3/.github/workflows/build.yml deleted file mode 100644 index 09652e02..00000000 --- a/libs/SDL3/.github/workflows/build.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: 'Build (All)' - -on: [push, pull_request] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: true - -jobs: - controller: - name: 'Create test plan' - runs-on: 'ubuntu-latest' - outputs: - platforms-level1: ${{ steps.plan.outputs.platforms-level1 }} - platforms-others: ${{ steps.plan.outputs.platforms-others }} - steps: - - uses: actions/setup-python@main - with: - python-version: 3.x - - uses: actions/checkout@main - with: - sparse-checkout: '.github/workflows/create-test-plan.py' - - name: 'Create plan' - id: plan - run: | - # Adding [sdl-ci-filter GLOB] to the commit message will limit the jobs - # e.g. [sdl-ci-filter msvc-*] - EOF=$(openssl rand -hex 32) - cat >/tmp/commit_message.txt <<$EOF - ${{ github.event.head_commit.message }} - $EOF - - python .github/workflows/create-test-plan.py \ - --github-variable-prefix platforms \ - --github-ci \ - --verbose \ - ${{ (github.repository_owner != 'libsdl-org' && '--no-artifact') || '' }} \ - --commit-message-file /tmp/commit_message.txt - level1: - needs: [controller] - uses: './.github/workflows/generic.yml' - with: - platforms: ${{ needs.controller.outputs.platforms-level1 }} - level2: - needs: [controller, level1] - uses: './.github/workflows/generic.yml' - with: - platforms: ${{ needs.controller.outputs.platforms-others }} diff --git a/libs/SDL3/.github/workflows/create-test-plan.py b/libs/SDL3/.github/workflows/create-test-plan.py deleted file mode 100644 index 219a4de1..00000000 --- a/libs/SDL3/.github/workflows/create-test-plan.py +++ /dev/null @@ -1,883 +0,0 @@ -#!/usr/bin/env python -import argparse -import dataclasses -import fnmatch -from enum import Enum -import json -import logging -import os -import re -from typing import Optional - -logger = logging.getLogger(__name__) - - -class AppleArch(Enum): - Aarch64 = "aarch64" - X86_64 = "x86_64" - - -class MsvcArch(Enum): - X86 = "x86" - X64 = "x64" - Arm32 = "arm" - Arm64 = "arm64" - - -class JobOs(Enum): - WindowsLatest = "windows-latest" - UbuntuLatest = "ubuntu-latest" - MacosLatest = "macos-latest" - Ubuntu22_04 = "ubuntu-22.04" - Ubuntu24_04 = "ubuntu-24.04" - Ubuntu24_04_arm = "ubuntu-24.04-arm" - Macos13 = "macos-13" - - -class SdlPlatform(Enum): - Android = "android" - Emscripten = "emscripten" - Haiku = "haiku" - LoongArch64 = "loongarch64" - Msys2 = "msys2" - Linux = "linux" - MacOS = "macos" - Ios = "ios" - Tvos = "tvos" - Msvc = "msvc" - N3ds = "n3ds" - PowerPC = "powerpc" - PowerPC64 = "powerpc64" - Ps2 = "ps2" - Psp = "psp" - Vita = "vita" - Riscos = "riscos" - FreeBSD = "freebsd" - NetBSD = "netbsd" - NGage = "ngage" - - -class Msys2Platform(Enum): - Mingw32 = "mingw32" - Mingw64 = "mingw64" - Clang64 = "clang64" - Ucrt64 = "ucrt64" - - -class IntelCompiler(Enum): - Icc = "icc" - Icx = "icx" - - -class VitaGLES(Enum): - Pib = "pib" - Pvr = "pvr" - - -@dataclasses.dataclass(slots=True) -class JobSpec: - name: str - os: JobOs - platform: SdlPlatform - artifact: Optional[str] - container: Optional[str] = None - no_cmake: bool = False - xcode: bool = False - android_mk: bool = False - android_gradle: bool = False - lean: bool = False - android_arch: Optional[str] = None - android_abi: Optional[str] = None - android_platform: Optional[int] = None - msys2_platform: Optional[Msys2Platform] = None - intel: Optional[IntelCompiler] = None - apple_framework: Optional[bool] = None - apple_archs: Optional[set[AppleArch]] = None - msvc_project: Optional[str] = None - msvc_arch: Optional[MsvcArch] = None - clang_cl: bool = False - gdk: bool = False - vita_gles: Optional[VitaGLES] = None - - -JOB_SPECS = { - "msys2-mingw32": JobSpec(name="Windows (msys2, mingw32)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msys2, artifact="SDL-mingw32", msys2_platform=Msys2Platform.Mingw32, ), - "msys2-mingw64": JobSpec(name="Windows (msys2, mingw64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msys2, artifact="SDL-mingw64", msys2_platform=Msys2Platform.Mingw64, ), - "msys2-clang64": JobSpec(name="Windows (msys2, clang64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msys2, artifact="SDL-mingw64-clang", msys2_platform=Msys2Platform.Clang64, ), - "msys2-ucrt64": JobSpec(name="Windows (msys2, ucrt64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msys2, artifact="SDL-mingw64-ucrt", msys2_platform=Msys2Platform.Ucrt64, ), - "msvc-x64": JobSpec(name="Windows (MSVC, x64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-VC-x64", msvc_arch=MsvcArch.X64, msvc_project="VisualC/SDL.sln", ), - "msvc-x86": JobSpec(name="Windows (MSVC, x86)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-VC-x86", msvc_arch=MsvcArch.X86, msvc_project="VisualC/SDL.sln", ), - "msvc-clang-x64": JobSpec(name="Windows (MSVC, clang-cl x64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-clang-cl-x64", msvc_arch=MsvcArch.X64, clang_cl=True, ), - "msvc-clang-x86": JobSpec(name="Windows (MSVC, clang-cl x86)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-clang-cl-x86", msvc_arch=MsvcArch.X86, clang_cl=True, ), - "msvc-arm32": JobSpec(name="Windows (MSVC, ARM)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-VC-arm32", msvc_arch=MsvcArch.Arm32, ), - "msvc-arm64": JobSpec(name="Windows (MSVC, ARM64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-VC-arm64", msvc_arch=MsvcArch.Arm64, ), - "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", ), - "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, ), - "macos-framework-arm64": JobSpec(name="MacOS (Framework) (arm64)", os=JobOs.MacosLatest, platform=SdlPlatform.MacOS, artifact=None, apple_framework=True, apple_archs={AppleArch.Aarch64, AppleArch.X86_64, }, ), - "macos-gnu-arm64": JobSpec(name="MacOS (GNU prefix)", os=JobOs.MacosLatest, platform=SdlPlatform.MacOS, artifact="SDL-macos-arm64-gnu", apple_framework=False, apple_archs={AppleArch.Aarch64, }, ), - "ios": JobSpec(name="iOS (CMake & xcode)", os=JobOs.MacosLatest, platform=SdlPlatform.Ios, artifact="SDL-ios-arm64", xcode=True, ), - "tvos": JobSpec(name="tvOS (CMake & xcode)", os=JobOs.MacosLatest, platform=SdlPlatform.Tvos, artifact="SDL-tvos-arm64", xcode=True, ), - "android-cmake": JobSpec(name="Android (CMake)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Android, artifact="SDL-android-arm64", android_abi="arm64-v8a", android_arch="aarch64", android_platform=23, ), - "android-cmake-lean": JobSpec(name="Android (CMake, lean)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Android, artifact="SDL-lean-android-arm64", android_abi="arm64-v8a", android_arch="aarch64", android_platform=23, lean=True, ), - "android-mk": JobSpec(name="Android (Android.mk)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Android, artifact=None, no_cmake=True, android_mk=True, ), - "android-gradle": JobSpec(name="Android (Gradle)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Android, artifact=None, no_cmake=True, android_gradle=True, ), - "emscripten": JobSpec(name="Emscripten", os=JobOs.UbuntuLatest, platform=SdlPlatform.Emscripten, artifact="SDL-emscripten", ), - "haiku": JobSpec(name="Haiku", os=JobOs.UbuntuLatest, platform=SdlPlatform.Haiku, artifact="SDL-haiku-x64", container="ghcr.io/haiku/cross-compiler:x86_64-r1beta5", ), - "loongarch64": JobSpec(name="LoongArch64", os=JobOs.UbuntuLatest, platform=SdlPlatform.LoongArch64, artifact="SDL-loongarch64", ), - "n3ds": JobSpec(name="Nintendo 3DS", os=JobOs.UbuntuLatest, platform=SdlPlatform.N3ds, artifact="SDL-n3ds", container="devkitpro/devkitarm:latest", ), - "ppc": JobSpec(name="PowerPC", os=JobOs.UbuntuLatest, platform=SdlPlatform.PowerPC, artifact="SDL-ppc", container="dockcross/linux-ppc:latest", ), - "ppc64": JobSpec(name="PowerPC64", os=JobOs.UbuntuLatest, platform=SdlPlatform.PowerPC64, artifact="SDL-ppc64le", container="dockcross/linux-ppc64le:latest", ), - "ps2": JobSpec(name="Sony PlayStation 2", os=JobOs.UbuntuLatest, platform=SdlPlatform.Ps2, artifact="SDL-ps2", container="ps2dev/ps2dev:latest", ), - "psp": JobSpec(name="Sony PlayStation Portable", os=JobOs.UbuntuLatest, platform=SdlPlatform.Psp, artifact="SDL-psp", container="pspdev/pspdev:latest", ), - "vita-pib": JobSpec(name="Sony PlayStation Vita (GLES w/ pib)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Vita, artifact="SDL-vita-pib", container="vitasdk/vitasdk:latest", vita_gles=VitaGLES.Pib, ), - "vita-pvr": JobSpec(name="Sony PlayStation Vita (GLES w/ PVR_PSP2)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Vita, artifact="SDL-vita-pvr", container="vitasdk/vitasdk:latest", vita_gles=VitaGLES.Pvr, ), - "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): - STATIC_LIB = "SDL3-static.lib" - A = "libSDL3.a" - - -class SharedLibType(Enum): - WIN32 = "SDL3.dll" - SO_0 = "libSDL3.so.0" - SO = "libSDL3.so" - DYLIB = "libSDL3.0.dylib" - FRAMEWORK = "SDL3.framework/Versions/A/SDL3" - - -@dataclasses.dataclass(slots=True) -class JobDetails: - name: str - key: str - os: str - platform: str - artifact: str - no_cmake: bool - ccache: bool = False - build_tests: bool = True - container: str = "" - cmake_build_type: str = "RelWithDebInfo" - shell: str = "sh" - sudo: str = "sudo" - cmake_config_emulator: str = "" - apk_packages: list[str] = dataclasses.field(default_factory=list) - apt_packages: list[str] = dataclasses.field(default_factory=list) - brew_packages: list[str] = dataclasses.field(default_factory=list) - cmake_toolchain_file: str = "" - cmake_arguments: list[str] = dataclasses.field(default_factory=list) - cmake_build_arguments: list[str] = dataclasses.field(default_factory=list) - clang_tidy: bool = True - cppflags: list[str] = dataclasses.field(default_factory=list) - cc: str = "" - cxx: str = "" - cflags: list[str] = dataclasses.field(default_factory=list) - cxxflags: list[str] = dataclasses.field(default_factory=list) - ldflags: list[str] = dataclasses.field(default_factory=list) - pollute_directories: list[str] = dataclasses.field(default_factory=list) - use_cmake: bool = True - shared: bool = True - static: bool = True - shared_lib: Optional[SharedLibType] = None - static_lib: Optional[StaticLibType] = None - run_tests: bool = True - test_pkg_config: bool = True - cc_from_cmake: bool = False - source_cmd: str = "" - pretest_cmd: str = "" - java: bool = False - android_apks: list[str] = dataclasses.field(default_factory=list) - android_ndk: bool = False - android_mk: bool = False - android_gradle: bool = False - minidump: bool = False - intel: bool = False - msys2_msystem: str = "" - msys2_env: str = "" - msys2_no_perl: bool = False - werror: bool = True - msvc_vcvars_arch: str = "" - msvc_vcvars_sdk: str = "" - msvc_project: str = "" - msvc_project_flags: list[str] = dataclasses.field(default_factory=list) - setup_ninja: bool = False - setup_libusb_arch: str = "" - xcode_sdk: str = "" - cpactions: bool = False - setup_gdk_folder: str = "" - cpactions_os: str = "" - cpactions_version: str = "" - cpactions_arch: str = "" - cpactions_setup_cmd: str = "" - cpactions_install_cmd: str = "" - setup_vita_gles_type: str = "" - 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 = { - "name": self.name, - "key": self.key, - "os": self.os, - "ccache": self.ccache, - "container": self.container if self.container else "", - "platform": self.platform, - "artifact": self.artifact, - "enable-artifacts": enable_artifacts, - "shell": self.shell, - "msys2-msystem": self.msys2_msystem, - "msys2-env": self.msys2_env, - "msys2-no-perl": self.msys2_no_perl, - "android-ndk": self.android_ndk, - "java": self.java, - "intel": self.intel, - "apk-packages": my_shlex_join(self.apk_packages), - "apt-packages": my_shlex_join(self.apt_packages), - "test-pkg-config": self.test_pkg_config, - "brew-packages": my_shlex_join(self.brew_packages), - "pollute-directories": my_shlex_join(self.pollute_directories), - "no-cmake": self.no_cmake, - "build-tests": self.build_tests, - "source-cmd": self.source_cmd, - "pretest-cmd": self.pretest_cmd, - "cmake-config-emulator": self.cmake_config_emulator, - "cc": self.cc, - "cxx": self.cxx, - "cflags": my_shlex_join(self.cppflags + self.cflags), - "cxxflags": my_shlex_join(self.cppflags + self.cxxflags), - "ldflags": my_shlex_join(self.ldflags), - "cmake-toolchain-file": self.cmake_toolchain_file, - "clang-tidy": self.clang_tidy, - "cmake-arguments": my_shlex_join(self.cmake_arguments), - "cmake-build-arguments": my_shlex_join(self.cmake_build_arguments), - "shared": self.shared, - "static": self.static, - "shared-lib": self.shared_lib.value if self.shared_lib else None, - "static-lib": self.static_lib.value if self.static_lib else None, - "cmake-build-type": self.cmake_build_type, - "run-tests": self.run_tests, - "android-apks": my_shlex_join(self.android_apks), - "android-gradle": self.android_gradle, - "android-mk": self.android_mk, - "werror": self.werror, - "sudo": self.sudo, - "msvc-vcvars-arch": self.msvc_vcvars_arch, - "msvc-vcvars-sdk": self.msvc_vcvars_sdk, - "msvc-project": self.msvc_project, - "msvc-project-flags": my_shlex_join(self.msvc_project_flags), - "setup-ninja": self.setup_ninja, - "setup-libusb-arch": self.setup_libusb_arch, - "cc-from-cmake": self.cc_from_cmake, - "xcode-sdk": self.xcode_sdk, - "cpactions": self.cpactions, - "cpactions-os": self.cpactions_os, - "cpactions-version": self.cpactions_version, - "cpactions-arch": self.cpactions_arch, - "cpactions-setup-cmd": self.cpactions_setup_cmd, - "cpactions-install-cmd": self.cpactions_install_cmd, - "setup-vita-gles-type": self.setup_vita_gles_type, - "setup-gdk-folder": self.setup_gdk_folder, - "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 != ""} - - -def my_shlex_join(s): - def escape(s): - if s[:1] == "'" and s[-1:] == "'": - return s - if set(s).intersection(set("; \t")): - return f'"{s}"' - return s - - return " ".join(escape(s)) - - -def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDetails: - job = JobDetails( - name=spec.name, - key=key, - os=spec.os.value, - artifact=spec.artifact or "", - container=spec.container or "", - platform=spec.platform.value, - sudo="sudo", - no_cmake=spec.no_cmake, - ) - if job.os.startswith("ubuntu"): - job.apt_packages.extend([ - "ninja-build", - "pkg-config", - ]) - pretest_cmd = [] - if trackmem_symbol_names: - pretest_cmd.append("export SDL_TRACKMEM_SYMBOL_NAMES=1") - else: - pretest_cmd.append("export SDL_TRACKMEM_SYMBOL_NAMES=0") - win32 = spec.platform in (SdlPlatform.Msys2, SdlPlatform.Msvc) - fpic = None - build_parallel = True - if spec.lean: - job.cppflags.append("-DSDL_LEAN_AND_MEAN=1") - if win32: - job.cmake_arguments.append("-DSDLTEST_PROCDUMP=ON") - job.minidump = True - if spec.intel is not None: - match spec.intel: - case IntelCompiler.Icx: - job.cc = "icx" - job.cxx = "icpx" - case IntelCompiler.Icc: - job.cc = "icc" - job.cxx = "icpc" - # Disable deprecation warning - job.cppflags.append("-diag-disable=10441") - # Avoid 'Catastrophic error: cannot open precompiled header file' - job.cmake_arguments.append("-DCMAKE_DISABLE_PRECOMPILE_HEADERS:BOOL=ON") - job.clang_tidy = False - case _: - raise ValueError(f"Invalid intel={spec.intel}") - job.source_cmd = f"source /opt/intel/oneapi/setvars.sh;" - job.intel = True - job.shell = "bash" - job.cmake_arguments.extend(( - f"-DCMAKE_C_COMPILER={job.cc}", - f"-DCMAKE_CXX_COMPILER={job.cxx}", - "-DCMAKE_SYSTEM_NAME=Linux", - )) - match spec.platform: - case SdlPlatform.Msvc: - job.setup_ninja = not spec.gdk - job.clang_tidy = False # complains about \threadsafety: "unknown command tag name [clang-diagnostic-documentation-unknown-command]" - job.msvc_project = spec.msvc_project if spec.msvc_project else "" - job.msvc_project_flags.append("-p:TreatWarningsAsError=true") - job.test_pkg_config = False - job.shared_lib = SharedLibType.WIN32 - job.static_lib = StaticLibType.STATIC_LIB - job.cmake_arguments.extend(( - "-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=ProgramDatabase", - "-DCMAKE_EXE_LINKER_FLAGS=-DEBUG", - "-DCMAKE_SHARED_LINKER_FLAGS=-DEBUG", - )) - - job.cmake_arguments.append("'-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded$<$:Debug>'") - - if spec.clang_cl: - job.cmake_arguments.extend(( - "-DCMAKE_C_COMPILER=clang-cl", - "-DCMAKE_CXX_COMPILER=clang-cl", - )) - match spec.msvc_arch: - case MsvcArch.X86: - job.cflags.append("/clang:-m32") - job.cxxflags.append("/clang:-m32") - job.ldflags.append("/MACHINE:X86") - case MsvcArch.X64: - job.cflags.append("/clang:-m64") - job.cxxflags.append("/clang:-m64") - job.ldflags.append("/MACHINE:X64") - case _: - raise ValueError(f"Unsupported clang-cl architecture (arch={spec.msvc_arch})") - if spec.msvc_project: - match spec.msvc_arch: - case MsvcArch.X86: - msvc_platform = "Win32" - case MsvcArch.X64: - msvc_platform = "x64" - case _: - raise ValueError(f"Unsupported vcxproj architecture (arch={spec.msvc_arch})") - if spec.gdk: - msvc_platform = f"Gaming.Desktop.{msvc_platform}" - job.msvc_project_flags.append(f"-p:Platform={msvc_platform}") - match spec.msvc_arch: - case MsvcArch.X86: - job.msvc_vcvars_arch = "x64_x86" - case MsvcArch.X64: - job.msvc_vcvars_arch = "x64" - case MsvcArch.Arm32: - job.msvc_vcvars_arch = "x64_arm" - job.msvc_vcvars_sdk = "10.0.22621.0" # 10.0.26100.0 dropped ARM32 um and ucrt libraries - job.run_tests = False - case MsvcArch.Arm64: - job.msvc_vcvars_arch = "x64_arm64" - job.run_tests = False - if spec.gdk: - job.setup_gdk_folder = "VisualC-GDK" - else: - match spec.msvc_arch: - case MsvcArch.X86: - job.setup_libusb_arch = "x86" - case MsvcArch.X64: - job.setup_libusb_arch = "x64" - case SdlPlatform.Linux: - if spec.name.startswith("Ubuntu"): - assert spec.os.value.startswith("ubuntu-") - job.apt_packages.extend(( - "ccache", - "gnome-desktop-testing", - "libasound2-dev", - "libpulse-dev", - "libaudio-dev", - "libjack-dev", - "libsndio-dev", - "libusb-1.0-0-dev", - "libx11-dev", - "libxext-dev", - "libxrandr-dev", - "libxcursor-dev", - "libxfixes-dev", - "libxi-dev", - "libxss-dev", - "libwayland-dev", - "libxkbcommon-dev", - "libdrm-dev", - "libgbm-dev", - "libgl1-mesa-dev", - "libgles2-mesa-dev", - "libegl1-mesa-dev", - "libdbus-1-dev", - "libibus-1.0-dev", - "libudev-dev", - "fcitx-libs-dev", - )) - match = re.match(r"ubuntu-(?P[0-9]+)\.(?P[0-9]+).*", spec.os.value) - ubuntu_year, ubuntu_month = [int(match["year"]), int(match["month"])] - if ubuntu_year >= 22: - job.apt_packages.extend(("libpipewire-0.3-dev", "libdecor-0-dev")) - job.apt_packages.extend(( - "libunwind-dev", # For SDL_test memory tracking - )) - job.ccache = True - if trackmem_symbol_names: - # older libunwind is slow - job.cmake_arguments.append("-DSDLTEST_TIMEOUT_MULTIPLIER=2") - job.shared_lib = SharedLibType.SO_0 - job.static_lib = StaticLibType.A - fpic = True - case SdlPlatform.Ios | SdlPlatform.Tvos: - job.brew_packages.extend([ - "ccache", - "ninja", - ]) - job.ccache = True - job.clang_tidy = False - job.run_tests = False - job.test_pkg_config = False - job.shared_lib = SharedLibType.DYLIB - job.static_lib = StaticLibType.A - match spec.platform: - case SdlPlatform.Ios: - if spec.xcode: - job.xcode_sdk = 'iphoneos' - job.cmake_arguments.extend([ - "-DCMAKE_SYSTEM_NAME=iOS", - "-DCMAKE_OSX_ARCHITECTURES=\"arm64\"", - "-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0", - ]) - case SdlPlatform.Tvos: - if spec.xcode: - job.xcode_sdk = 'appletvos' - job.cmake_arguments.extend([ - "-DCMAKE_SYSTEM_NAME=tvOS", - "-DCMAKE_OSX_ARCHITECTURES=\"arm64\"", - "-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0", - ]) - case SdlPlatform.MacOS: - if spec.apple_framework: - job.static = False - job.clang_tidy = False - job.test_pkg_config = False - job.cmake_arguments.extend(( - "'-DCMAKE_OSX_ARCHITECTURES=x86_64;arm64'", - "-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13", - "-DSDL_FRAMEWORK=ON", - )) - job.shared_lib = SharedLibType.FRAMEWORK - else: - job.clang_tidy = True - job.cmake_arguments.extend(( - "-DCMAKE_OSX_ARCHITECTURES=arm64", - "-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13", - "-DCLANG_TIDY_BINARY=$(brew --prefix llvm)/bin/clang-tidy", - )) - job.shared_lib = SharedLibType.DYLIB - job.static_lib = StaticLibType.A - job.ccache = True - job.apt_packages = [] - job.brew_packages.extend(( - "ccache", - "ninja", - )) - if job.clang_tidy: - job.brew_packages.append("llvm") - if spec.xcode: - job.xcode_sdk = "macosx" - case SdlPlatform.Android: - job.android_gradle = spec.android_gradle - job.android_mk = spec.android_mk - job.apt_packages.append("ccache") - job.run_tests = False - job.shared_lib = SharedLibType.SO - job.static_lib = StaticLibType.A - if spec.android_mk or not spec.no_cmake: - job.android_ndk = True - if spec.android_gradle or not spec.no_cmake: - job.java = True - if spec.android_mk or spec.android_gradle: - job.apt_packages = [] - if not spec.no_cmake: - job.ccache = True - job.cmake_arguments.extend(( - f"-DANDROID_PLATFORM={spec.android_platform}", - f"-DANDROID_ABI={spec.android_abi}", - )) - job.cmake_toolchain_file = "${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake" - job.cc = f"${{ANDROID_NDK_HOME}}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang --target={spec.android_arch}-none-linux-androideabi{spec.android_platform}" - - job.android_apks = [ - "testaudiorecording-apk", - "testautomation-apk", - "testcontroller-apk", - "testmultiaudio-apk", - "testsprite-apk", - ] - case SdlPlatform.Emscripten: - job.clang_tidy = False # clang-tidy does not understand -gsource-map - job.shared = False - job.ccache = True - job.apt_packages.append("ccache") - job.cmake_config_emulator = "emcmake" - job.cmake_build_type = "Debug" - job.test_pkg_config = False - job.cmake_arguments.extend(( - "-DSDLTEST_BROWSER=chrome", - "-DSDLTEST_TIMEOUT_MULTIPLIER=4", - "-DSDLTEST_CHROME_BINARY=${CHROME_BINARY}", - )) - job.cflags.extend(( - "-gsource-map", - "-ffile-prefix-map=${PWD}=/SDL", - )) - job.ldflags.extend(( - "--source-map-base", "/", - )) - pretest_cmd.extend(( - "# Start local HTTP server", - "cmake --build build --target serve-sdl-tests --verbose &", - "chrome --version", - "chromedriver --version", - )) - job.static_lib = StaticLibType.A - job.setup_python = True - job.pypi_packages.append("selenium") - case SdlPlatform.Ps2: - job.ccache = False # actions/ccache does not work in psp container (incompatible tar of busybox) - build_parallel = False - job.shared = False - job.sudo = "" - job.apt_packages = [] - job.apk_packages = ["ccache", "cmake", "gmp", "mpc1", "mpfr4", "ninja", "pkgconf", "git", ] - job.cmake_toolchain_file = "${PS2DEV}/ps2sdk/ps2dev.cmake" - job.clang_tidy = False - job.run_tests = False - job.shared = False - job.cc = "mips64r5900el-ps2-elf-gcc" - job.ldflags = ["-L${PS2DEV}/ps2sdk/ee/lib", "-L${PS2DEV}/gsKit/lib", "-L${PS2DEV}/ps2sdk/ports/lib", ] - job.static_lib = StaticLibType.A - case SdlPlatform.Psp: - job.ccache = False # actions/ccache does not work in psp container (incompatible tar of busybox) - build_parallel = False - job.sudo = "" - job.apt_packages = [] - job.apk_packages = ["ccache", "cmake", "gmp", "mpc1", "mpfr4", "ninja", "pkgconf", ] - job.cmake_toolchain_file = "${PSPDEV}/psp/share/pspdev.cmake" - job.clang_tidy = False - job.run_tests = False - job.shared = False - job.cc = "psp-gcc" - job.ldflags = ["-L${PSPDEV}/lib", "-L${PSPDEV}/psp/lib", "-L${PSPDEV}/psp/sdk/lib", ] - job.pollute_directories = ["${PSPDEV}/include", "${PSPDEV}/psp/include", "${PSPDEV}/psp/sdk/include", ] - job.static_lib = StaticLibType.A - case SdlPlatform.Vita: - job.ccache = True - job.sudo = "" - job.apt_packages = [] - job.apk_packages = ["ccache", "cmake", "ninja", "pkgconf", "bash", "tar"] - job.cmake_toolchain_file = "${VITASDK}/share/vita.toolchain.cmake" - assert spec.vita_gles is not None - job.setup_vita_gles_type = { - VitaGLES.Pib: "pib", - VitaGLES.Pvr: "pvr", - }[spec.vita_gles] - job.cmake_arguments.extend(( - f"-DVIDEO_VITA_PIB={ 'true' if spec.vita_gles == VitaGLES.Pib else 'false' }", - f"-DVIDEO_VITA_PVR={ 'true' if spec.vita_gles == VitaGLES.Pvr else 'false' }", - "-DSDL_ARMNEON=ON", - "-DSDL_ARMSIMD=ON", - )) - # Fix vita.toolchain.cmake (https://github.com/vitasdk/vita-toolchain/pull/253) - job.source_cmd = r"""sed -i -E "s#set\\( PKG_CONFIG_EXECUTABLE \"\\$\\{VITASDK}/bin/arm-vita-eabi-pkg-config\" \\)#set\\( PKG_CONFIG_EXECUTABLE \"${VITASDK}/bin/arm-vita-eabi-pkg-config\" CACHE PATH \"Path of pkg-config executable\" \\)#" ${VITASDK}/share/vita.toolchain.cmake""" - job.clang_tidy = False - job.run_tests = False - job.shared = False - job.cc = "arm-vita-eabi-gcc" - job.static_lib = StaticLibType.A - case SdlPlatform.Haiku: - job.ccache = True - fpic = False - job.run_tests = False - job.apt_packages.append("ccache") - job.cc = "x86_64-unknown-haiku-gcc" - job.cxx = "x86_64-unknown-haiku-g++" - job.sudo = "" - job.cmake_arguments.extend(( - f"-DCMAKE_C_COMPILER={job.cc}", - f"-DCMAKE_CXX_COMPILER={job.cxx}", - "-DSDL_UNIX_CONSOLE_BUILD=ON", - )) - job.shared_lib = SharedLibType.SO_0 - job.static_lib = StaticLibType.A - case SdlPlatform.PowerPC64 | SdlPlatform.PowerPC: - job.ccache = True - # FIXME: Enable SDL_WERROR - job.werror = False - job.clang_tidy = False - job.run_tests = False - job.sudo = "" - job.apt_packages = ["ccache"] - job.shared_lib = SharedLibType.SO_0 - job.static_lib = StaticLibType.A - job.cmake_arguments.extend(( - "-DSDL_UNIX_CONSOLE_BUILD=ON", - )) - case SdlPlatform.LoongArch64: - job.ccache = True - fpic = True - job.run_tests = False - job.apt_packages.append("ccache") - job.cc = "${LOONGARCH64_CC}" - job.cxx = "${LOONGARCH64_CXX}" - job.cmake_arguments.extend(( - f"-DCMAKE_C_COMPILER={job.cc}", - f"-DCMAKE_CXX_COMPILER={job.cxx}", - "-DSDL_UNIX_CONSOLE_BUILD=ON", - "-DCMAKE_SYSTEM_NAME=Linux", - )) - job.shared_lib = SharedLibType.SO_0 - job.static_lib = StaticLibType.A - case SdlPlatform.N3ds: - job.ccache = True - job.shared = False - job.apt_packages = ["ccache", "ninja-build", "binutils"] - job.clang_tidy = False - job.run_tests = False - job.cc_from_cmake = True - job.cmake_toolchain_file = "${DEVKITPRO}/cmake/3DS.cmake" - job.static_lib = StaticLibType.A - case SdlPlatform.Msys2: - job.ccache = True - job.shell = "msys2 {0}" - assert spec.msys2_platform - job.msys2_msystem = spec.msys2_platform.value - job.msys2_env = { - "mingw32": "mingw-w64-i686", - "mingw64": "mingw-w64-x86_64", - "clang64": "mingw-w64-clang-x86_64", - "ucrt64": "mingw-w64-ucrt-x86_64", - }[spec.msys2_platform.value] - job.msys2_no_perl = spec.msys2_platform in (Msys2Platform.Mingw32, ) - job.shared_lib = SharedLibType.WIN32 - job.static_lib = StaticLibType.A - case SdlPlatform.Riscos: - job.ccache = False # FIXME: enable when container gets upgrade - # FIXME: Enable SDL_WERROR - job.werror = False - job.apt_packages = ["ccache", "cmake", "ninja-build"] - job.test_pkg_config = False - job.shared = False - job.run_tests = False - job.sudo = "" - job.cmake_arguments.extend(( - "-DRISCOS:BOOL=ON", - "-DCMAKE_DISABLE_PRECOMPILE_HEADERS:BOOL=ON", - "-DSDL_GCC_ATOMICS:BOOL=OFF", - )) - job.cmake_toolchain_file = "/home/riscos/env/toolchain-riscos.cmake" - job.static_lib = StaticLibType.A - case SdlPlatform.FreeBSD | SdlPlatform.NetBSD: - job.cpactions = True - job.no_cmake = True - job.run_tests = False - job.apt_packages = [] - job.shared_lib = SharedLibType.SO_0 - job.static_lib = StaticLibType.A - match spec.platform: - case SdlPlatform.FreeBSD: - job.cpactions_os = "freebsd" - job.cpactions_version = "14.2" - job.cpactions_arch = "x86-64" - job.cpactions_setup_cmd = "sudo pkg update" - job.cpactions_install_cmd = "sudo pkg install -y cmake ninja pkgconf libXcursor libXext libXinerama libXi libXfixes libXrandr libXScrnSaver libXxf86vm wayland wayland-protocols libxkbcommon mesa-libs libglvnd evdev-proto libinotify alsa-lib jackit pipewire pulseaudio sndio dbus zh-fcitx ibus libudev-devd" - job.cmake_arguments.extend(( - "-DSDL_CHECK_REQUIRED_INCLUDES=/usr/local/include", - "-DSDL_CHECK_REQUIRED_LINK_OPTIONS=-L/usr/local/lib", - )) - case SdlPlatform.NetBSD: - job.cpactions_os = "netbsd" - job.cpactions_version = "10.1" - 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}") - - if "ubuntu" in spec.name.lower(): - job.check_sources = True - job.setup_python = True - - if job.ccache: - job.cmake_arguments.extend(( - "-DCMAKE_C_COMPILER_LAUNCHER=ccache", - "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache", - )) - if not build_parallel: - job.cmake_build_arguments.append("-j1") - if job.cflags or job.cppflags: - job.cmake_arguments.append(f"-DCMAKE_C_FLAGS=\"{my_shlex_join(job.cflags + job.cppflags)}\"") - if job.cxxflags or job.cppflags: - job.cmake_arguments.append(f"-DCMAKE_CXX_FLAGS=\"{my_shlex_join(job.cxxflags + job.cppflags)}\"") - if job.ldflags: - job.cmake_arguments.append(f"-DCMAKE_SHARED_LINKER_FLAGS=\"{my_shlex_join(job.ldflags)}\"") - job.cmake_arguments.append(f"-DCMAKE_EXE_LINKER_FLAGS=\"{my_shlex_join(job.ldflags)}\"") - job.pretest_cmd = "\n".join(pretest_cmd) - def tf(b): - return "ON" if b else "OFF" - - if fpic is not None: - job.cmake_arguments.append(f"-DCMAKE_POSITION_INDEPENDENT_CODE={tf(fpic)}") - - if job.no_cmake: - job.cmake_arguments = [] - - return job - - -def spec_to_platform(spec: JobSpec, key: str, enable_artifacts: bool, trackmem_symbol_names: bool) -> dict[str, str|bool]: - logger.info("spec=%r", spec) - job = spec_to_job(spec, key=key, trackmem_symbol_names=trackmem_symbol_names) - logger.info("job=%r", job) - platform = job.to_workflow(enable_artifacts=enable_artifacts) - logger.info("platform=%r", platform) - return platform - - -def main(): - parser = argparse.ArgumentParser(allow_abbrev=False) - parser.add_argument("--github-variable-prefix", default="platforms") - parser.add_argument("--github-ci", action="store_true") - parser.add_argument("--verbose", action="store_true") - parser.add_argument("--commit-message-file") - parser.add_argument("--no-artifact", dest="enable_artifacts", action="store_false") - parser.add_argument("--trackmem-symbol-names", dest="trackmem_symbol_names", action="store_true") - args = parser.parse_args() - - logging.basicConfig(level=logging.INFO if args.verbose else logging.WARNING) - - remaining_keys = set(JOB_SPECS.keys()) - - all_level_keys = ( - # Level 1 - ( - "haiku", - ), - ) - - filters = [] - if args.commit_message_file: - with open(args.commit_message_file, "r") as f: - commit_message = f.read() - for m in re.finditer(r"\[sdl-ci-filter (.*)]", commit_message, flags=re.M): - filters.append(m.group(1).strip(" \t\n\r\t'\"")) - - if re.search(r"\[sdl-ci-artifacts?]", commit_message, flags=re.M): - args.enable_artifacts = True - - if re.search(r"\[sdl-ci-(full-)?trackmem(-symbol-names)?]", commit_message, flags=re.M): - args.trackmem_symbol_names = True - - if not filters: - filters.append("*") - - logger.info("filters: %r", filters) - - all_level_platforms = {} - - all_platforms = {key: spec_to_platform(spec, key=key, enable_artifacts=args.enable_artifacts, trackmem_symbol_names=args.trackmem_symbol_names) for key, spec in JOB_SPECS.items()} - - for level_i, level_keys in enumerate(all_level_keys, 1): - level_key = f"level{level_i}" - logger.info("Level %d: keys=%r", level_i, level_keys) - assert all(k in remaining_keys for k in level_keys) - level_platforms = tuple(all_platforms[key] for key in level_keys) - remaining_keys.difference_update(level_keys) - all_level_platforms[level_key] = level_platforms - logger.info("=" * 80) - - logger.info("Keys before filter: %r", remaining_keys) - - filtered_remaining_keys = set() - for filter in filters: - filtered_remaining_keys.update(fnmatch.filter(remaining_keys, filter)) - - logger.info("Keys after filter: %r", filtered_remaining_keys) - - remaining_keys = filtered_remaining_keys - - logger.info("Remaining: %r", remaining_keys) - all_level_platforms["others"] = tuple(all_platforms[key] for key in remaining_keys) - - if args.github_ci: - for level, platforms in all_level_platforms.items(): - platforms_json = json.dumps(platforms) - txt = f"{args.github_variable_prefix}-{level}={platforms_json}" - logger.info("%s", txt) - if "GITHUB_OUTPUT" in os.environ: - with open(os.environ["GITHUB_OUTPUT"], "a") as f: - f.write(txt) - f.write("\n") - else: - logger.warning("GITHUB_OUTPUT not defined") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/libs/SDL3/.github/workflows/generic.yml b/libs/SDL3/.github/workflows/generic.yml deleted file mode 100644 index 083859b3..00000000 --- a/libs/SDL3/.github/workflows/generic.yml +++ /dev/null @@ -1,436 +0,0 @@ -name: 'Build' -run-name: 'Configure, Build and Test SDL' - -on: - workflow_call: - inputs: - platforms: - description: 'JSON-encoded test properties' - type: string - required: true - -jobs: - build: - name: ${{ matrix.platform.name }} - runs-on: ${{ matrix.platform.os }} - container: ${{ matrix.platform.container }} - defaults: - run: - shell: ${{ matrix.platform.shell }} - strategy: - fail-fast: false - matrix: - platform: ${{ fromJSON(inputs.platforms) }} - steps: - - name: 'Set up MSYS2' - if: ${{ matrix.platform.platform == 'msys2' }} - uses: msys2/setup-msys2@v2 - with: - msystem: ${{ matrix.platform.msys2-msystem }} - install: >- - ${{ matrix.platform.msys2-env }}-cc - ${{ matrix.platform.msys2-env }}-cmake - ${{ matrix.platform.msys2-env }}-ninja - ${{ (!matrix.platform.msys2-no-perl && format('{0}-perl', matrix.platform.msys2-env)) || '' }} - ${{ matrix.platform.msys2-env }}-pkg-config - ${{ matrix.platform.msys2-env }}-clang-tools-extra - ${{ (matrix.platform.ccache && format('{0}-ccache', matrix.platform.msys2-env)) || '' }} - - name: 'About this job' - run: | - echo "key=${{ matrix.platform.key }}" - echo "name=${{ matrix.platform.name }}" - echo "os=${{ matrix.platform.os }}" - echo "" - echo "Add [sdl-ci-filter ${{ matrix.platform.key }}] to your commit message to reduce the number of jobs." - - uses: actions/checkout@v4 - - name: 'Set up ninja' - if: ${{ matrix.platform.setup-ninja }} - uses: ./.github/actions/setup-ninja - - name: 'Set up libusb for MSVC' - if: ${{ matrix.platform.setup-libusb-arch != '' }} - uses: ./.github/actions/setup-msvc-libusb - with: - arch: ${{ matrix.platform.setup-libusb-arch }} - - uses: mymindstorm/setup-emsdk@v14 - if: ${{ matrix.platform.platform == 'emscripten' }} - with: - version: 3.1.35 - - uses: browser-actions/setup-chrome@v1 - id: setup-chrome - if: ${{ matrix.platform.platform == 'emscripten' }} - with: - install-chromedriver: true - - name: 'Add chrome to PATH' - if: ${{ matrix.platform.platform == 'emscripten' }} - run: | - chrome_dir="$(dirname "${{ steps.setup-chrome.outputs.chrome-path }}")" - chromedriver_dir="$(dirname "${{ steps.setup-chrome.outputs.chromedriver-path }}")" - echo "CHROME_BINARY=${{ steps.setup-chrome.outputs.chrome-path }}" >>$GITHUB_ENV - echo "CHROMEDRIVER_BINARY=${{ steps.setup-chrome.outputs.chromedriver-path }}" >>$GITHUB_ENV - echo "chrome_dir=${chrome_dir}" - echo "chromedriver_dir=${chromedriver_dir}" - echo "${chrome_dir}" >>${GITHUB_PATH} - echo "${chromedriver_dir}" >>${GITHUB_PATH} - - uses: nttld/setup-ndk@v1 - if: ${{ matrix.platform.android-ndk }} - id: setup-ndk - with: - local-cache: true - ndk-version: r21e - - name: 'Configure Android NDK variables' - if: ${{ matrix.platform.android-ndk }} - shell: sh - run: | - # We cannot use GitHub expressions in the controller job - echo "ANDROID_NDK_HOME=${{ steps.setup-ndk.outputs.ndk-path }}" >>$GITHUB_ENV - - uses: actions/setup-java@v4 - if: ${{ matrix.platform.java }} - with: - distribution: 'temurin' - java-version: '17' - - uses: ilammy/msvc-dev-cmd@v1 - if: ${{ matrix.platform.platform == 'msvc' }} - 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 != '' }} - with: - folder: '${{ matrix.platform.setup-gdk-folder }}' - - name: 'Set up LoongArch64 toolchain' - uses: ./.github/actions/setup-loongarch64-toolchain - id: setup-loongarch64-toolchain - if: ${{ matrix.platform.platform == 'loongarch64' }} - - name: 'Setup Intel oneAPI toolchain' - id: intel - if: ${{ matrix.platform.intel }} - run: | - # Download the key to system keyring - wget -O- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB \ - | gpg --dearmor | sudo tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null - - # Add signed entry to apt sources and configure the APT client to use Intel repository: - echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list - - # Update package list - sudo apt-get update -y - - # Install oneAPI - sudo apt-get install -y intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic - - name: 'Install apk packages' - if: ${{ matrix.platform.apk-packages != '' }} - run: | - ${{ matrix.platform.sudo }} apk update - ${{ matrix.platform.sudo }} apk add ${{ matrix.platform.apk-packages }} - - name: 'Install apt packages' - if: ${{ matrix.platform.apt-packages != '' }} - run: | - ${{ matrix.platform.sudo }} apt-get update - ${{ matrix.platform.sudo }} apt-get install -y ${{ matrix.platform.apt-packages }} - - name: 'Install brew packages' - if: ${{ matrix.platform.brew-packages != '' }} - run: | - export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 - brew update - brew install ${{ matrix.platform.brew-packages }} - - name: 'Setup Python' - uses: 'actions/setup-python@main' - if: ${{ matrix.platform.setup-python }} - with: - python-version: '3.x' - - name: 'Install PyPI packages' - if: ${{ matrix.platform.pypi-packages != '' }} - run: | - python -m pip install --user ${{ matrix.platform.pypi-packages }} - - name: 'Set up GLES for VITA' # Must be after apk - if: ${{ matrix.platform.setup-vita-gles-type != '' }} - uses: ./.github/actions/setup-vita-gles - with: - type: ${{ matrix.platform.setup-vita-gles-type }} - - - name: 'Pollute toolchain with "bad" SDL headers' - if: ${{ matrix.platform.pollute-directories != '' }} - #shell: ${{ matrix.platform.shell }} - run: | - # Create "bad" SDL headers in the toolchain. - # SDL sources should not use these. - for include in ${{ matrix.platform.pollute-directories }}; do - toolchain_directory="${include}/SDL3" - echo "Creating directory ${toolchain_directory}" - mkdir -p "${toolchain_directory}/SDL3" - for header in include/SDL3/*.h; do - dest="${toolchain_directory}/SDL3/$(basename "${header}")" - echo "Creating ${dest}" - echo '#error "System SDL headers must not be used by build system"' >"$dest" - done - done - - - name: 'Calculate ccache key' - if: ${{ matrix.platform.ccache }} - id: prepare-restore-ccache - run: | - echo "timestamp=$(date -u "+%Y%m%d%H%M_%S")" >> "$GITHUB_OUTPUT" - - name: 'Restore ccache' - if: ${{ matrix.platform.ccache }} - uses: actions/cache/restore@v4 - id: restore-ccache - with: - path: ${{ runner.temp }}/ccache - key: ccache-${{ matrix.platform.key }}-${{ steps.prepare-restore-ccache.outputs.timestamp }} - restore-keys: | - ccache-${{matrix.platform.key}} - - name: 'Configure ccache' - if: ${{ matrix.platform.ccache }} - run: | - echo 'CCACHE_DIR=${{ runner.temp }}/ccache' >>${GITHUB_ENV} - - name: 'Prepare ccache' - if: ${{ matrix.platform.ccache && steps.restore-ccache.outputs.cache-hit }} - run: | - if [ "x${{ runner.os }}" = "xmacOS" ]; then - touch_date="2025-02-01T12:00:00Z" - else - touch_date="2025-02-01" - fi - find "${CCACHE_DIR}" -type f -exec touch -a -m -d "$touch_date" {} + - ccache -s - ccache -z - - - name: 'Configure (CMake)' - if: ${{ !matrix.platform.no-cmake }} - #shell: ${{ matrix.platform.shell }} - run: | - ${{ matrix.platform.source-cmd }} - ${{ matrix.platform.cmake-config-emulator }} cmake -S . -B build -GNinja \ - -Wdeprecated -Wdev -Werror \ - ${{ matrix.platform.cmake-toolchain-file != '' && format('-DCMAKE_TOOLCHAIN_FILE={0}', matrix.platform.cmake-toolchain-file) || '' }} \ - -DSDL_WERROR=${{ matrix.platform.werror }} \ - -DSDL_EXAMPLES=${{ matrix.platform.build-tests }} \ - -DSDL_TESTS=${{ matrix.platform.build-tests }} \ - -DSDLTEST_TRACKMEM=ON \ - -DSDL_INSTALL_TESTS=${{ matrix.platform.build-tests }} \ - -DSDL_CLANG_TIDY=${{ matrix.platform.clang-tidy }} \ - -DSDL_INSTALL_DOCS=ON \ - -DSDL_INSTALL_CPACK=ON \ - -DSDL_INSTALL_DOCS=ON \ - ${{ matrix.platform.cmake-arguments }} \ - -DSDL_SHARED=${{ matrix.platform.shared }} \ - -DSDL_STATIC=${{ matrix.platform.static }} \ - -DSDL_VENDOR_INFO="Github Workflow" \ - -DCMAKE_INSTALL_PREFIX=prefix \ - -DCMAKE_INSTALL_LIBDIR=lib \ - -DCMAKE_BUILD_TYPE=${{ matrix.platform.cmake-build-type }} - - name: 'Build (CMake)' - id: build - if: ${{ !matrix.platform.no-cmake }} -# shell: ${{ matrix.platform.shell }} - run: | - ${{ matrix.platform.source-cmd }} - cmake --build build --config ${{ matrix.platform.cmake-build-type }} --verbose -- ${{ matrix.platform.cmake-build-arguments }} - - name: 'Verify SDL_REVISION' - if: ${{ !matrix.platform.no-cmake }} - run: | - echo "This should show us the SDL_REVISION" - echo "Shared library:" - ${{ (matrix.platform.shared-lib && format('strings build/{0} | grep "Github Workflow"', matrix.platform.shared-lib)) || 'echo ""' }} - echo "Static library:" - ${{ (matrix.platform.static-lib && format('strings build/{0} | grep "Github Workflow"', matrix.platform.static-lib)) || 'echo ""' }} - - name: 'Run build-time tests (CMake)' - id: tests - if: ${{ !matrix.platform.no-cmake && matrix.platform.run-tests }} -# shell: ${{ matrix.platform.shell }} - run: | - ${{ matrix.platform.source-cmd }} - ${{ matrix.platform.pretest-cmd }} - set -eu - export SDL_TESTS_QUICK=1 - ctest -VV --test-dir build/ -j2 - - name: "Build test apk's (CMake)" - id: apks - if: ${{ always() && steps.build.outcome == 'success' && matrix.platform.android-apks != '' }} -# shell: ${{ matrix.platform.shell }} - run: | - ${{ matrix.platform.source-cmd }} - cmake --build build --config ${{ matrix.platform.cmake-build-type }} \ - --target \ - ${{ matrix.platform.android-apks }} \ - --verbose \ - -- ${{ matrix.platform.cmake-build-arguments }} - - name: 'Install (CMake)' - id: install - if: ${{ always() && steps.build.outcome == 'success' }} -# shell: ${{ matrix.platform.shell }} - run: | - ${{ matrix.platform.source-cmd }} - cmake --install build --config ${{ matrix.platform.cmake-build-type }} - echo "prefix=$(pwd)/prefix" >> $GITHUB_OUTPUT - ( cd prefix; find . ) | LC_ALL=C sort -u - - name: 'Package (CPack)' - id: package - if: ${{ always() && steps.build.outcome == 'success' }} -# shell: ${{ matrix.platform.shell }} - run: | - # DMG creation on macOS occasionally fails, so try multiple times - # https://gitlab.kitware.com/cmake/cmake/-/issues/25671 - success=0 - max_tries=10 - for i in $(seq $max_tries); do - cmake --build build/ --config ${{ matrix.platform.cmake-build-type }} --target package -- ${{ matrix.platform.cmake-build-arguments }} && success=1 - if test $success = 1; then - break - fi - echo "Package creation failed. Sleep 1 second and try again." - sleep 1 - done - if test $success = 0; then - echo "Package creation failed after $max_tries attempts." - exit 1 - fi - - name: 'Verify CMake configuration files' - if: ${{ steps.install.outcome == 'success' }} -# shell: ${{ matrix.platform.shell }} - run: | - ${{ matrix.platform.source-cmd }} - ${{ matrix.platform.cmake-config-emulator }} cmake -S cmake/test -B cmake_test_build -GNinja \ - ${{ matrix.platform.cmake-toolchain-file != '' && format('-DCMAKE_TOOLCHAIN_FILE={0}', matrix.platform.cmake-toolchain-file) || '' }} \ - -DTEST_SHARED=${{ matrix.platform.shared }} \ - -DTEST_STATIC=${{ matrix.platform.static }} \ - ${{ matrix.platform.cmake-arguments }} \ - -DCMAKE_BUILD_TYPE=${{ matrix.platform.cmake-build-type }} \ - -DCMAKE_PREFIX_PATH="${{ steps.install.outputs.prefix }}" - cmake --build cmake_test_build --verbose --config ${{ matrix.platform.cmake-build-type }} -- ${{ matrix.platform.cmake-build-arguments }} - - name: 'Extract CC/CXX/CFLAGS/CXXFLAGS from CMake toolchain' - if: ${{ steps.install.outcome == 'success' && matrix.platform.cc-from-cmake }} -# shell: ${{ matrix.platform.shell }} - run: | - cmake -S .github/cmake -B /tmp/cmake_extract \ - ${{ matrix.platform.cmake-toolchain-file != '' && format('-DCMAKE_TOOLCHAIN_FILE={0}', matrix.platform.cmake-toolchain-file) || '' }} \ - -DCMAKE_BUILD_TYPE=${{ matrix.platform.cmake-build-type }} \ - -DVAR_PATH=/tmp/env.txt - cat /tmp/env.txt >> $GITHUB_ENV - - name: 'Verify sdl3.pc' -# shell: ${{ matrix.platform.shell }} - if: ${{ steps.install.outcome == 'success' && matrix.platform.test-pkg-config }} - run: | - ${{ matrix.platform.source-cmd }} - ${{ matrix.platform.cc && format('export CC="{0}"', matrix.platform.cc) || '' }} - ${{ matrix.platform.cflags && format('export CFLAGS="{0}"', matrix.platform.cflags) || '' }} - ${{ matrix.platform.ldflags && format('export LDFLAGS="{0}"', matrix.platform.ldflags) || '' }} - export PKG_CONFIG_PATH=${{ steps.install.outputs.prefix }}/lib/pkgconfig - cmake/test/test_pkgconfig.sh - - name: 'Build (cross-platform-actions, BSD)' - id: cpactions - if: ${{ matrix.platform.cpactions }} - uses: cross-platform-actions/action@v0.27.0 - with: - operating_system: '${{ matrix.platform.cpactions-os }}' - architecture: '${{ matrix.platform.cpactions-arch }}' - version: '${{ matrix.platform.cpactions-version }}' - run: | - ${{ matrix.platform.cpactions-setup-cmd }} - ${{ matrix.platform.cpactions-install-cmd }} - cmake -S . -B build -GNinja \ - ${{ matrix.platform.cmake-toolchain-file != '' && format('-DCMAKE_TOOLCHAIN_FILE={0}', matrix.platform.cmake-toolchain-file) || '' }} \ - -Wdeprecated -Wdev -Werror \ - -DSDL_WERROR=${{ matrix.platform.werror }} \ - -DSDL_INSTALL_DOCS=ON \ - ${{ matrix.platform.cmake-arguments }} \ - -DSDL_SHARED=${{ matrix.platform.shared }} \ - -DSDL_STATIC=${{ matrix.platform.static }} \ - -DSDL_VENDOR_INFO="Github Workflow" \ - -DCMAKE_INSTALL_PREFIX=prefix \ - -DCMAKE_INSTALL_LIBDIR=lib \ - -DCMAKE_BUILD_TYPE=${{ matrix.platform.cmake-build-type }} - cmake --build build/ --config ${{ matrix.platform.cmake-build-type }} --verbose - cmake --build build/ --config ${{ matrix.platform.cmake-build-type }} --target package - - cmake --build build/ --config ${{ matrix.platform.cmake-build-type }} --target clean - rm -rf build/dist/_CPack_Packages - rm -rf build/CMakeFiles - rm -rf build/docs - - name: Add msbuild to PATH - id: setup-msbuild - if: ${{ matrix.platform.msvc-project != '' }} - uses: microsoft/setup-msbuild@v2 - - name: Build msbuild - if: ${{ matrix.platform.msvc-project != '' }} - run: | - "$(cygpath -u '${{ steps.setup-msbuild.outputs.msbuildPath }}\msbuild.exe')" ${{ matrix.platform.msvc-project }} -m -p:BuildInParallel=true -p:Configuration=Release ${{ matrix.platform.msvc-project-flags }} - - name: 'Build (Android.mk)' - if: ${{ matrix.platform.android-mk }} - run: | - ./build-scripts/androidbuildlibs.sh - - name: 'Create Gradle project (Android)' - if: ${{ matrix.platform.android-gradle }} - run: | - for folder in build-ndk-build build-cmake; do - python build-scripts/create-android-project.py \ - --output "${folder}" \ - --variant copy \ - org.libsdl.testspriteminimal \ - test/testspriteminimal.c test/icon.h - done - echo "" - echo "Project contents:" - echo "" - find "build-ndk-build/org.libsdl.testspriteminimal" - - name: 'Build Android app (Gradle & ndk-build)' - if: ${{ matrix.platform.android-gradle }} - run: | - cd build-ndk-build/org.libsdl.testspriteminimal - ./gradlew -i assembleRelease - - name: 'Build Android app (Gradle & CMake)' - if: ${{ matrix.platform.android-gradle }} - run: | - cd build-cmake/org.libsdl.testspriteminimal - ./gradlew -i assembleRelease -PBUILD_WITH_CMAKE=1 - - name: 'Build (xcode)' - if: ${{ matrix.platform.xcode-sdk != '' }} - run: | - xcodebuild -project Xcode/SDL/SDL.xcodeproj -target SDL3 -configuration Release -sdk ${{ matrix.platform.xcode-sdk }} clean build - - name: 'Prune old ccache files' - if: ${{ matrix.platform.ccache }} - run: | - ccache --evict-older-than=1d - ccache -s - - name: 'Save ccache' - if: ${{ matrix.platform.ccache }} - uses: actions/cache/save@v4 - with: - path: ${{ runner.temp }}/ccache - key: ${{ steps.restore-ccache.outputs.cache-primary-key }} - - name: 'Check Sources' - if: ${{ matrix.platform.check-sources }} - run: | - set -e - build-scripts/test-versioning.sh - python build-scripts/check_android_jni.py - python build-scripts/check_stdlib_usage.py - - name: 'Upload binary package' - uses: actions/upload-artifact@v4 - if: ${{ always() && matrix.platform.artifact != '' && (steps.package.outcome == 'success' || steps.cpactions.outcome == 'success') && (matrix.platform.enable-artifacts || steps.tests.outcome == 'failure') }} - with: - if-no-files-found: error - name: '${{ matrix.platform.artifact }}' - path: | - build/dist/SDL3* - build/include* - - name: 'Upload minidumps' - uses: actions/upload-artifact@v4 - if: ${{ always() && steps.tests.outcome == 'failure' && (matrix.platform.platform == 'msvc' || matrix.platform.platform == 'msys2') }} - with: - if-no-files-found: ignore - name: '${{ matrix.platform.artifact }}-minidumps' - path: build/**/*.dmp - - name: "Upload Android test apk's" - uses: actions/upload-artifact@v4 - if: ${{ matrix.platform.enable-artifacts && always() && matrix.platform.artifact != '' && steps.apks.outcome == 'success' }} - with: - if-no-files-found: error - name: '${{ matrix.platform.artifact }}-apks' - path: build/test/*.apk \ No newline at end of file diff --git a/libs/SDL3/.github/workflows/release.yml b/libs/SDL3/.github/workflows/release.yml deleted file mode 100644 index b0215a52..00000000 --- a/libs/SDL3/.github/workflows/release.yml +++ /dev/null @@ -1,655 +0,0 @@ -name: 'release' -run-name: 'Create SDL release artifacts for ${{ inputs.commit }}' - -on: - workflow_dispatch: - inputs: - commit: - description: 'Commit of SDL' - required: true - -jobs: - - src: - runs-on: ubuntu-latest - outputs: - project: ${{ steps.releaser.outputs.project }} - version: ${{ steps.releaser.outputs.version }} - src-tar-gz: ${{ steps.releaser.outputs.src-tar-gz }} - src-tar-xz: ${{ steps.releaser.outputs.src-tar-xz }} - src-zip: ${{ steps.releaser.outputs.src-zip }} - steps: - - name: 'Set up Python' - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - name: 'Fetch build-release.py' - uses: actions/checkout@v4 - with: - sparse-checkout: 'build-scripts/build-release.py' - - name: 'Set up SDL sources' - uses: actions/checkout@v4 - with: - path: 'SDL' - fetch-depth: 0 - - name: 'Build Source archive' - id: releaser - shell: bash - run: | - python build-scripts/build-release.py \ - --actions source \ - --commit ${{ inputs.commit }} \ - --root "${{ github.workspace }}/SDL" \ - --github \ - --debug - - name: 'Store source archives' - uses: actions/upload-artifact@v4 - with: - name: sources - path: '${{ github.workspace}}/dist' - - name: 'Generate summary' - run: | - echo "Run the following commands to download all artifacts:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "mkdir -p /tmp/${{ steps.releaser.outputs.project }}-${{ steps.releaser.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "cd /tmp/${{ steps.releaser.outputs.project }}-${{ steps.releaser.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "gh run -R ${{ github.repository }} download ${{ github.run_id }}" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - - linux-verify: - needs: [src] - runs-on: ubuntu-latest - steps: - - name: 'Download source archives' - uses: actions/download-artifact@v4 - with: - name: sources - path: '${{ github.workspace }}' - - name: 'Unzip ${{ needs.src.outputs.src-zip }}' - id: zip - run: | - mkdir /tmp/zipdir - cd /tmp/zipdir - unzip "${{ github.workspace }}/${{ needs.src.outputs.src-zip }}" - echo "path=/tmp/zipdir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT - - name: 'Untar ${{ needs.src.outputs.src-tar-gz }}' - id: tar - run: | - mkdir -p /tmp/tardir - tar -C /tmp/tardir -v -x -f "${{ github.workspace }}/${{ needs.src.outputs.src-tar-gz }}" - echo "path=/tmp/tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT - - name: 'Compare contents of ${{ needs.src.outputs.src-zip }} and ${{ needs.src.outputs.src-tar-gz }}' - run: | - diff /tmp/zipdir /tmp/tardir - - name: 'Test versioning' - shell: bash - run: | - ${{ steps.tar.outputs.path }}/build-scripts/test-versioning.sh - - name: 'Install Linux dependencies' - run: | - sudo apt-get update -y - sudo apt-get install -y \ - gnome-desktop-testing libasound2-dev libpulse-dev libaudio-dev libjack-dev libsndio-dev \ - libusb-1.0-0-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev \ - libxss-dev libwayland-dev libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \ - libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev - - name: 'CMake (configure + build + tests + examples)' - run: | - cmake -S ${{ steps.tar.outputs.path }} -B /tmp/build -DSDL_TEST_LIBRARY=TRUE -DSDL_TESTS=TRUE -DSDL_EXAMPLES=TRUE - cmake --build /tmp/build --verbose - ctest --test-dir /tmp/build --no-tests=error --output-on-failure - - dmg: - needs: [src] - runs-on: macos-latest - outputs: - dmg: ${{ steps.releaser.outputs.dmg }} - steps: - - name: 'Set up Python' - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - name: 'Fetch build-release.py' - uses: actions/checkout@v4 - with: - sparse-checkout: 'build-scripts/build-release.py' - - name: 'Download source archives' - uses: actions/download-artifact@v4 - with: - name: sources - path: '${{ github.workspace }}' - - name: 'Untar ${{ needs.src.outputs.src-tar-gz }}' - id: tar - run: | - mkdir -p "${{ github.workspace }}/tardir" - tar -C "${{ github.workspace }}/tardir" -v -x -f "${{ github.workspace }}/${{ needs.src.outputs.src-tar-gz }}" - echo "path=${{ github.workspace }}/tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT - - name: 'Build SDL3.dmg' - id: releaser - shell: bash - run: | - python build-scripts/build-release.py \ - --actions dmg \ - --commit ${{ inputs.commit }} \ - --root "${{ steps.tar.outputs.path }}" \ - --github \ - --debug - - name: 'Store DMG image file' - uses: actions/upload-artifact@v4 - with: - name: dmg - path: '${{ github.workspace }}/dist' - - dmg-verify: - needs: [dmg, src] - runs-on: macos-latest - steps: - - name: 'Download source archives' - uses: actions/download-artifact@v4 - with: - name: sources - path: '${{ github.workspace }}' - - name: 'Download ${{ needs.dmg.outputs.dmg }}' - uses: actions/download-artifact@v4 - with: - name: dmg - path: '${{ github.workspace }}' - - name: 'Untar ${{ needs.src.outputs.src-tar-gz }}' - id: src - run: | - mkdir -p /tmp/tardir - tar -C /tmp/tardir -v -x -f "${{ github.workspace }}/${{ needs.src.outputs.src-tar-gz }}" - echo "path=/tmp/tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT - - name: 'Mount ${{ needs.dmg.outputs.dmg }}' - id: mount - run: | - hdiutil attach '${{ github.workspace }}/${{ needs.dmg.outputs.dmg }}' - mount_point="/Volumes/${{ needs.src.outputs.project }}" - if [ ! -d "$mount_point/${{ needs.src.outputs.project }}.xcframework" ]; then - echo "Cannot find ${{ needs.src.outputs.project }}.xcframework!" - exit 1 - fi - echo "mount_point=$mount_point">>$GITHUB_OUTPUT - - name: 'CMake (configure + build) Darwin' - run: | - cmake -S "${{ steps.src.outputs.path }}/cmake/test" \ - -DTEST_FULL=FALSE \ - -DTEST_STATIC=FALSE \ - -DTEST_TEST=FALSE \ - -DCMAKE_PREFIX_PATH="${{ steps.mount.outputs.mount_point }}" \ - -DCMAKE_SYSTEM_NAME=Darwin \ - -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \ - -Werror=dev \ - -B build_darwin - cmake --build build_darwin --config Release --verbose - - cmake -S "${{ steps.src.outputs.path }}/cmake/test" \ - -DTEST_FULL=FALSE \ - -DTEST_STATIC=FALSE \ - -DTEST_TEST=FALSE \ - -DCMAKE_PREFIX_PATH="${{ steps.mount.outputs.mount_point }}/SDL3.xcframework/macos-arm64_x86_64" \ - -DCMAKE_SYSTEM_NAME=Darwin \ - -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \ - -Werror=dev \ - -B build_darwin_2 - cmake --build build_darwin --config Release --verbose - - name: 'CMake (configure + build) iOS' - run: | - cmake -S "${{ steps.src.outputs.path }}/cmake/test" \ - -DTEST_FULL=FALSE \ - -DTEST_STATIC=FALSE \ - -DTEST_TEST=FALSE \ - -DCMAKE_PREFIX_PATH="${{ steps.mount.outputs.mount_point }}" \ - -DCMAKE_SYSTEM_NAME=iOS \ - -DCMAKE_OSX_ARCHITECTURES="arm64" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \ - -Werror=dev \ - -B build_ios - cmake --build build_ios --config Release --verbose - - name: 'CMake (configure + build) tvOS' - run: | - cmake -S "${{ steps.src.outputs.path }}/cmake/test" \ - -DTEST_FULL=FALSE \ - -DTEST_STATIC=FALSE \ - -DTEST_TEST=FALSE \ - -DCMAKE_PREFIX_PATH="${{ steps.mount.outputs.mount_point }}" \ - -DCMAKE_SYSTEM_NAME=tvOS \ - -DCMAKE_OSX_ARCHITECTURES="arm64" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \ - -Werror=dev \ - -B build_tvos - cmake --build build_tvos --config Release --verbose - - name: 'CMake (configure + build) iOS simulator' - run: | - sysroot=$(xcodebuild -version -sdk iphonesimulator Path) - echo "sysroot=$sysroot" - cmake -S "${{ steps.src.outputs.path }}/cmake/test" \ - -DTEST_FULL=FALSE \ - -DTEST_STATIC=FALSE \ - -DTEST_TEST=FALSE \ - -DCMAKE_PREFIX_PATH="${{ steps.mount.outputs.mount_point }}" \ - -DCMAKE_SYSTEM_NAME=iOS \ - -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \ - -DCMAKE_OSX_SYSROOT="${sysroot}" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \ - -Werror=dev \ - -B build_ios_simulator - cmake --build build_ios_simulator --config Release --verbose - - name: 'CMake (configure + build) tvOS simulator' - run: | - sysroot=$(xcodebuild -version -sdk appletvsimulator Path) - echo "sysroot=$sysroot" - cmake -S "${{ steps.src.outputs.path }}/cmake/test" \ - -DTEST_FULL=FALSE \ - -DTEST_STATIC=FALSE \ - -DTEST_TEST=FALSE \ - -DCMAKE_PREFIX_PATH="${{ steps.mount.outputs.mount_point }}" \ - -DCMAKE_SYSTEM_NAME=tvOS \ - -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \ - -DCMAKE_OSX_SYSROOT="${sysroot}" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \ - -Werror=dev \ - -B build_tvos_simulator - cmake --build build_tvos_simulator --config Release --verbose - - msvc: - needs: [src] - runs-on: windows-2019 - outputs: - VC-x86: ${{ steps.releaser.outputs.VC-x86 }} - VC-x64: ${{ steps.releaser.outputs.VC-x64 }} - VC-arm64: ${{ steps.releaser.outputs.VC-arm64 }} - VC-devel: ${{ steps.releaser.outputs.VC-devel }} - steps: - - name: 'Set up Python' - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - name: 'Fetch build-release.py' - uses: actions/checkout@v4 - with: - sparse-checkout: 'build-scripts/build-release.py' - - name: 'Download source archives' - uses: actions/download-artifact@v4 - with: - name: sources - path: '${{ github.workspace }}' - - name: 'Unzip ${{ needs.src.outputs.src-zip }}' - id: zip - run: | - New-Item C:\temp -ItemType Directory -ErrorAction SilentlyContinue - cd C:\temp - unzip "${{ github.workspace }}/${{ needs.src.outputs.src-zip }}" - echo "path=C:\temp\${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$Env:GITHUB_OUTPUT - - name: 'Build MSVC binary archives' - id: releaser - run: | - python build-scripts/build-release.py ` - --actions msvc ` - --commit ${{ inputs.commit }} ` - --root "${{ steps.zip.outputs.path }}" ` - --github ` - --debug - - name: 'Store MSVC archives' - uses: actions/upload-artifact@v4 - with: - name: win32 - path: '${{ github.workspace }}/dist' - - msvc-verify: - needs: [msvc, src] - runs-on: windows-latest - steps: - - name: 'Fetch .github/actions/setup-ninja/action.yml' - uses: actions/checkout@v4 - with: - sparse-checkout: '.github/actions/setup-ninja/action.yml' - - name: 'Download source archives' - uses: actions/download-artifact@v4 - with: - name: sources - path: '${{ github.workspace }}' - - name: 'Download MSVC binaries' - uses: actions/download-artifact@v4 - with: - name: win32 - path: '${{ github.workspace }}' - - name: 'Unzip ${{ needs.src.outputs.src-zip }}' - id: src - run: | - mkdir '${{ github.workspace }}/sources' - cd '${{ github.workspace }}/sources' - unzip "${{ github.workspace }}/${{ needs.src.outputs.src-zip }}" - echo "path=${{ github.workspace }}/sources/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$env:GITHUB_OUTPUT - - name: 'Unzip ${{ needs.msvc.outputs.VC-devel }}' - id: bin - run: | - mkdir '${{ github.workspace }}/vc' - cd '${{ github.workspace }}/vc' - unzip "${{ github.workspace }}/${{ needs.msvc.outputs.VC-devel }}" - echo "path=${{ github.workspace }}/vc/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$env:GITHUB_OUTPUT - - name: Set up ninja - uses: ./.github/actions/setup-ninja - - name: 'Configure vcvars x86' - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: x64_x86 - - name: 'CMake (configure + build + tests) x86' - run: | - cmake -S "${{ steps.src.outputs.path }}/cmake/test" ` - -B build_x86 ` - -GNinja ` - -DCMAKE_BUILD_TYPE=Debug ` - -Werror=dev ` - -DTEST_FULL=TRUE ` - -DTEST_STATIC=FALSE ` - -DTEST_SHARED=TRUE ` - -DTEST_TEST=TRUE ` - -DCMAKE_SUPPRESS_REGENERATION=TRUE ` - -DCMAKE_PREFIX_PATH="${{ steps.bin.outputs.path }}" - Start-Sleep -Seconds 2 - cmake --build build_x86 --config Release --verbose - ctest --test-dir build_x86 --no-tests=error -C Release --output-on-failure - - name: 'Configure vcvars x64' - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: x64 - - name: 'CMake (configure + build + tests) x64' - run: | - cmake -S "${{ steps.src.outputs.path }}/cmake/test" ` - -B build_x64 ` - -GNinja ` - -DCMAKE_BUILD_TYPE=Debug ` - -Werror=dev ` - -DTEST_FULL=TRUE ` - -DTEST_STATIC=FALSE ` - -DTEST_SHARED=TRUE ` - -DTEST_TEST=TRUE ` - -DCMAKE_SUPPRESS_REGENERATION=TRUE ` - -DCMAKE_PREFIX_PATH="${{ steps.bin.outputs.path }}" - Start-Sleep -Seconds 2 - cmake --build build_x64 --config Release --verbose - ctest --test-dir build_x64 --no-tests=error -C Release --output-on-failure - - name: 'Configure vcvars arm64' - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: x64_arm64 - - name: 'CMake (configure + build) arm64' - run: | - cmake -S "${{ steps.src.outputs.path }}/cmake/test" ` - -B build_arm64 ` - -GNinja ` - -DCMAKE_BUILD_TYPE=Debug ` - -Werror=dev ` - -DTEST_FULL=TRUE ` - -DTEST_STATIC=FALSE ` - -DTEST_SHARED=TRUE ` - -DTEST_TEST=TRUE ` - -DCMAKE_SUPPRESS_REGENERATION=TRUE ` - -DCMAKE_PREFIX_PATH="${{ steps.bin.outputs.path }}" - Start-Sleep -Seconds 2 - cmake --build build_arm64 --config Release --verbose - - name: 'CMake (configure + build) arm64ec' - run: | - cmake -S "${{ steps.src.outputs.path }}/cmake/test" ` - -B build_arm64ec ` - -GNinja ` - -DCMAKE_BUILD_TYPE=Debug ` - -Werror=dev ` - -DTEST_FULL=TRUE ` - -DTEST_STATIC=FALSE ` - -DTEST_SHARED=TRUE ` - -DTEST_TEST=TRUE ` - -DSDL_DISABLE_AVX=TRUE ` - -DSDL_DISABLE_AVX2=TRUE ` - -DSDL_DISABLE_AVX512F=TRUE ` - -DCMAKE_SUPPRESS_REGENERATION=TRUE ` - -DCMAKE_C_FLAGS="/arm64EC" ` - -DCMAKE_CXX_FLAGS="/arm64EC" ` - -DCMAKE_EXE_LINKER_FLAGS="/MACHINE:ARM64EC" ` - -DCMAKE_SHARED_LINKER_FLAGS="/MACHINE:ARM64EC" ` - -DCMAKE_STATIC_LINKER_FLAGS="/MACHINE:ARM64EC" ` - -DCMAKE_PREFIX_PATH="${{ steps.bin.outputs.path }}" - Start-Sleep -Seconds 2 - cmake --build build_arm64ec --config Release --verbose - - mingw: - needs: [src] - runs-on: ubuntu-24.04 # FIXME: current ubuntu-latest ships an outdated mingw, replace with ubuntu-latest once 24.04 becomes the new default - outputs: - mingw-devel-tar-gz: ${{ steps.releaser.outputs.mingw-devel-tar-gz }} - mingw-devel-tar-xz: ${{ steps.releaser.outputs.mingw-devel-tar-xz }} - steps: - - name: 'Set up Python' - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - name: 'Fetch build-release.py' - uses: actions/checkout@v4 - with: - sparse-checkout: 'build-scripts/build-release.py' - - name: 'Install Mingw toolchain' - run: | - sudo apt-get update -y - sudo apt-get install -y gcc-mingw-w64 g++-mingw-w64 ninja-build - - name: 'Download source archives' - uses: actions/download-artifact@v4 - with: - name: sources - path: '${{ github.workspace }}' - - name: 'Untar ${{ needs.src.outputs.src-tar-gz }}' - id: tar - run: | - mkdir -p /tmp/tardir - tar -C /tmp/tardir -v -x -f "${{ github.workspace }}/${{ needs.src.outputs.src-tar-gz }}" - echo "path=/tmp/tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT - - name: 'Build MinGW binary archives' - id: releaser - run: | - python build-scripts/build-release.py \ - --actions mingw \ - --commit ${{ inputs.commit }} \ - --root "${{ steps.tar.outputs.path }}" \ - --github \ - --debug - - name: 'Store MinGW archives' - uses: actions/upload-artifact@v4 - with: - name: mingw - path: '${{ github.workspace }}/dist' - - mingw-verify: - needs: [mingw, src] - runs-on: ubuntu-latest - steps: - - name: 'Install Mingw toolchain' - run: | - sudo apt-get update -y - sudo apt-get install -y gcc-mingw-w64 g++-mingw-w64 ninja-build - - name: 'Download source archives' - uses: actions/download-artifact@v4 - with: - name: sources - path: '${{ github.workspace }}' - - name: 'Download MinGW binaries' - uses: actions/download-artifact@v4 - with: - name: mingw - path: '${{ github.workspace }}' - - name: 'Untar ${{ needs.src.outputs.src-tar-gz }}' - id: src - run: | - mkdir -p /tmp/tardir - tar -C /tmp/tardir -v -x -f "${{ github.workspace }}/${{ needs.src.outputs.src-tar-gz }}" - echo "path=/tmp/tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT - - name: 'Untar ${{ needs.mingw.outputs.mingw-devel-tar-gz }}' - id: bin - run: | - mkdir -p /tmp/mingw-tardir - tar -C /tmp/mingw-tardir -v -x -f "${{ github.workspace }}/${{ needs.mingw.outputs.mingw-devel-tar-gz }}" - echo "path=/tmp/mingw-tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT - - name: 'CMake (configure + build) i686' - run: | - cmake -S "${{ steps.src.outputs.path }}/cmake/test" \ - -DCMAKE_BUILD_TYPE="Release" \ - -DTEST_FULL=TRUE \ - -DTEST_STATIC=FALSE \ - -DTEST_TEST=TRUE \ - -DCMAKE_PREFIX_PATH="${{ steps.bin.outputs.path }}" \ - -DCMAKE_TOOLCHAIN_FILE="${{ steps.src.outputs.path }}/build-scripts/cmake-toolchain-mingw64-i686.cmake" \ - -DCMAKE_C_FLAGS="-DSDL_DISABLE_SSE4_2" \ - -Werror=dev \ - -B build_x86 - cmake --build build_x86 --config Release --verbose - - name: 'CMake (configure + build) x86_64' - run: | - cmake -S "${{ steps.src.outputs.path }}/cmake/test" \ - -DCMAKE_BUILD_TYPE="Release" \ - -DTEST_FULL=TRUE \ - -DTEST_STATIC=false \ - -DTEST_TEST=TRUE \ - -DCMAKE_PREFIX_PATH="${{ steps.bin.outputs.path }}" \ - -DCMAKE_TOOLCHAIN_FILE="${{ steps.src.outputs.path }}/build-scripts/cmake-toolchain-mingw64-x86_64.cmake" \ - -DCMAKE_C_FLAGS="-DSDL_DISABLE_SSE4_2" \ - -Werror=dev \ - -B build_x64 - cmake --build build_x64 --config Release --verbose - - android: - needs: [src] - runs-on: ubuntu-latest - outputs: - android-aar: ${{ steps.releaser.outputs.android-aar }} - steps: - - name: 'Set up Python' - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - name: 'Fetch build-release.py' - uses: actions/checkout@v4 - with: - sparse-checkout: 'build-scripts/build-release.py' - - name: 'Setup Android NDK' - id: setup-ndk - uses: nttld/setup-ndk@v1 - with: - local-cache: true - ndk-version: r21e - - name: 'Setup Java JDK' - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '11' - - name: 'Install ninja' - run: | - sudo apt-get update -y - sudo apt-get install -y ninja-build - - name: 'Download source archives' - uses: actions/download-artifact@v4 - with: - name: sources - path: '${{ github.workspace }}' - - name: 'Untar ${{ needs.src.outputs.src-tar-gz }}' - id: tar - run: | - mkdir -p /tmp/tardir - tar -C /tmp/tardir -v -x -f "${{ github.workspace }}/${{ needs.src.outputs.src-tar-gz }}" - echo "path=/tmp/tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT - - name: 'Build Android prefab binary archive(s)' - id: releaser - run: | - python build-scripts/build-release.py \ - --actions android \ - --android-api 23 \ - --android-ndk-home "${{ steps.setup-ndk.outputs.ndk-path }}" \ - --commit ${{ inputs.commit }} \ - --root "${{ steps.tar.outputs.path }}" \ - --github \ - --debug - - name: 'Store Android archive(s)' - uses: actions/upload-artifact@v4 - with: - name: android - path: '${{ github.workspace }}/dist' - - android-verify: - needs: [android, src] - runs-on: ubuntu-latest - steps: - - name: 'Set up Python' - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '17' - - name: 'Download source archives' - uses: actions/download-artifact@v4 - with: - name: sources - path: '${{ github.workspace }}' - - name: 'Download Android .aar archive' - uses: actions/download-artifact@v4 - with: - name: android - path: '${{ github.workspace }}' - - name: 'Untar ${{ needs.src.outputs.src-tar-gz }}' - id: src - run: | - mkdir -p /tmp/tardir - tar -C /tmp/tardir -v -x -f "${{ github.workspace }}/${{ needs.src.outputs.src-tar-gz }}" - echo "path=/tmp/tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT - - name: 'Extract Android SDK from AAR' - id: sdk - run: | - cd /tmp - unzip "${{ github.workspace }}/${{ needs.android.outputs.android-aar }}" - python "${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}.aar" -o /tmp/SDL3-android - echo "prefix=/tmp/SDL3-android" >>$GITHUB_OUTPUT - echo "sdl3-aar=/tmp/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}.aar" >>$GITHUB_OUTPUT - - name: 'CMake (configure + build) x86, x64, arm32, arm64' - run: | - android_abis="x86 x86_64 armeabi-v7a arm64-v8a" - for android_abi in ${android_abis}; do - echo "Configuring ${android_abi}..." - cmake -S "${{ steps.src.outputs.path }}/cmake/test" \ - -DTEST_FULL=TRUE \ - -DTEST_STATIC=FALSE \ - -DTEST_TEST=TRUE \ - -DCMAKE_PREFIX_PATH="${{ steps.sdk.outputs.prefix }}" \ - -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake \ - -DANDROID_ABI=${android_abi} \ - -DCMAKE_BUILD_TYPE=Release \ - -B "${android_abi}" - echo "Building ${android_abi}..." - cmake --build "${android_abi}" --config Release --verbose - done - - name: 'Create gradle project' - id: create-gradle-project - run: | - python ${{ steps.src.outputs.path }}/build-scripts/create-android-project.py \ - org.libsdl.testspriteminimal \ - ${{ steps.src.outputs.path }}/test/testspriteminimal.c \ - ${{ steps.src.outputs.path }}/test/icon.h \ - --variant aar \ - --output "/tmp/projects" - echo "path=/tmp/projects/org.libsdl.testspriteminimal" >>$GITHUB_OUTPUT - - name: 'Copy SDL3 aar into Gradle project' - run: | - cp "${{ steps.sdk.outputs.sdl3-aar }}" "${{ steps.create-gradle-project.outputs.path }}/app/libs" - - echo "" - echo "Project contents:" - echo "" - find "${{ steps.create-gradle-project.outputs.path }}" - - name: 'Build app (Gradle & CMake)' - run: | - cd "${{ steps.create-gradle-project.outputs.path }}" - ./gradlew -i assembleRelease -Pandroid.native.buildOutput=verbose -PBUILD_WITH_CMAKE=1 - - name: 'Build app (Gradle & ndk-build)' - run: | - cd "${{ steps.create-gradle-project.outputs.path }}" - ./gradlew -i assembleRelease -Pandroid.native.buildOutput=verbose diff --git a/libs/SDL3/.gitignore b/libs/SDL3/.gitignore deleted file mode 100644 index 394f4b84..00000000 --- a/libs/SDL3/.gitignore +++ /dev/null @@ -1,178 +0,0 @@ -build/ -build-*/ -!build-scripts/ -buildbot/ -/VERSION.txt -__pycache__ - -*.so -*.so.* -*.dll -*.exe -*.o -*.obj -*.res -*.lib -*.a -*.la -*.dSYM -*,e1f -*,ff8 -*.lnk -*.err -*.exp -*.map -*.orig -*~ -*.swp -*.tmp -*.rej - -# for CMake -.cmake -CMakeFiles/ -CMakeCache.txt -cmake_install.cmake -cmake_uninstall.cmake -install_manifest.txt -*Targets.cmake -*Config.cmake -*ConfigVersion.cmake -CTestTestfile.cmake -Testing -compile_commands.json -.cache/ -/include-config-* -/include-revision -/Makefile -.ninja_* -*.ninja -*.pc -test/*.test -wayland-generated-protocols - -# for CLion -.idea -cmake-build-* - -# for Xcode -*.mode1* -*.perspective* -*.pbxuser -(^|/)build($|/) -.DS_Store -xcuserdata -*.xcworkspace -Xcode/build.xcconfig - -# for Visual Studio Code -.vscode/ - -# for Visual C++ -.vs -Debug -Release -*.user -*.ncb -*.suo -*.sdf -VisualC/tests/gamepadmap/axis.bmp -VisualC/tests/gamepadmap/button.bmp -VisualC/tests/gamepadmap/gamepadmap.bmp -VisualC/tests/gamepadmap/gamepadmap_back.bmp -VisualC/tests/loopwave/sample.wav -VisualC/tests/testautomation/*.bmp -VisualC/tests/testgamepad/axis.bmp -VisualC/tests/testgamepad/button.bmp -VisualC/tests/testgamepad/gamepadmap.bmp -VisualC/tests/testgamepad/gamepadmap_back.bmp -VisualC/tests/testoverlay/moose.dat -VisualC/tests/testrendertarget/icon.bmp -VisualC/tests/testrendertarget/sample.bmp -VisualC/tests/testscale/icon.bmp -VisualC/tests/testscale/sample.bmp -VisualC/tests/testsprite/icon.bmp -VisualC/tests/testyuv/testyuv.bmp -VisualC-GDK/**/Layout -src/render/direct3d12/D3D12_*_One.h -src/render/direct3d12/D3D12_*_Series.h -src/gpu/d3d12/D3D12_*_One.h -src/gpu/d3d12/D3D12_*_Series.h - -# for Android -android-project/local.properties -android-project/.gradle/ - -test/checkkeys -test/checkkeysthreads -test/gamepadmap -test/loopwave -test/loopwavequeue -test/testatomic -test/testaudiorecording -test/testaudiohotplug -test/testaudioinfo -test/testautomation -test/testbounds -test/testcustomcursor -test/testdisplayinfo -test/testdraw -test/testdrawchessboard -test/testdropfile -test/testerror -test/testevdev -test/testfile -test/testfilesystem -test/testgamepad -test/testgeometry -test/testgesture -test/testgl -test/testgles -test/testgles2 -test/testhaptic -test/testhittesting -test/testhotplug -test/testiconv -test/testime -test/testintersections -test/testjoystick -test/testkeys -test/testloadso -test/testlocale -test/testlock -test/testmessage -test/testmouse -test/testmultiaudio -test/testnative -test/testoverlay -test/testplatform -test/testpower -test/testqsort -test/testrelative -test/testrendercopyex -test/testrendertarget -test/testresample -test/testrumble -test/testscale -test/testsem -test/testsensor -test/testshader -test/testshape -test/testsprite -test/testspriteminimal -test/teststreaming -test/testsurround -test/testthread -test/testtimer -test/testurl -test/testver -test/testviewport -test/testvulkan -test/testwm -test/testyuv -test/torturethread - -# for Doxygen -docs/output -SDL.tag -doxygen_warn.txt diff --git a/libs/SDL3/.wikiheaders-options b/libs/SDL3/.wikiheaders-options index 38a2f273..696a08c7 100644 --- a/libs/SDL3/.wikiheaders-options +++ b/libs/SDL3/.wikiheaders-options @@ -31,3 +31,11 @@ quickreftitle = SDL3 API Quick Reference quickrefurl = https://libsdl.org/ quickrefdesc = The latest version of this document can be found at https://wiki.libsdl.org/SDL3/QuickReference quickrefmacroregex = \A(SDL_PLATFORM_.*|SDL_.*_INTRINSICS|SDL_Atomic...Ref|SDL_assert.*?|SDL_COMPILE_TIME_ASSERT|SDL_arraysize|SDL_Swap[BL]E\d\d|SDL_[a-z]+_cast)\Z + +envvarenabled = 1 +envvartitle = SDL3 Environment Variables +envvardesc = SDL3 can be controlled by the user, externally, with environment variables. They are all operate exactly like the [hints you can get and set programmatically](CategoryHints), but named without the `_HINT` part (so `"SDL_HINT_A"` would be environment variable `"SDL_A"`).\n\nThis list matches the latest in SDL3's revision control. +envvarsymregex = \ASDL_HINT_(.*)\Z +envvarsymreplace = SDL_$1 + + diff --git a/libs/SDL3/AGENTS.md b/libs/SDL3/AGENTS.md new file mode 100644 index 00000000..7709baa1 --- /dev/null +++ b/libs/SDL3/AGENTS.md @@ -0,0 +1,20 @@ +AI must not be used to generate code for contributions to this project. + +"AI" in this case means a Large Language Model ("LLM"), such as ChatGPT, +Claude, Copilot, Grok, etc. + +AI-generated code is based upon sources of unknown origins and may not be +compatible with the Zlib license, or may introduce conflicting license terms +if they include code from other projects. + +AI can be used to identify issues with contributions to this project, but the +solutions to those issues should be authored by humans. + +We have found that AI will frequently hallucinate issues that are not actually +problems in practice, report incorrect information, and describe problems that +are actually not issues at all. If AI identifies a problem with this codebase, +please make sure you understand what it is saying and have independently +confirmed that the issue exists before submitting a bug report or pull request. + +Any pull request to this project will ask you to confirm that you are the +author and that you are contributing your changes under the Zlib license. diff --git a/libs/SDL3/Android.mk b/libs/SDL3/Android.mk index 3ecc631d..f4600bfa 100644 --- a/libs/SDL3/Android.mk +++ b/libs/SDL3/Android.mk @@ -112,8 +112,6 @@ ifeq ($(NDK_DEBUG),1) cmd-strip := endif -LOCAL_STATIC_LIBRARIES := cpufeatures - include $(BUILD_SHARED_LIBRARY) @@ -123,6 +121,12 @@ include $(BUILD_SHARED_LIBRARY) # ########################### +include $(CLEAR_VARS) + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/src + +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include + LOCAL_MODULE := SDL3_test LOCAL_MODULE_FILENAME := libSDL3_test @@ -139,24 +143,3 @@ LOCAL_EXPORT_LDLIBS := include $(BUILD_STATIC_LIBRARY) - -########################### -# -# SDL static library -# -########################### - -LOCAL_MODULE := SDL3_static - -LOCAL_MODULE_FILENAME := libSDL3 - -LOCAL_LDLIBS := - -LOCAL_LDFLAGS := - -LOCAL_EXPORT_LDLIBS := -ldl -lGLESv1_CM -lGLESv2 -llog -landroid - -include $(BUILD_STATIC_LIBRARY) - -$(call import-module,android/cpufeatures) - diff --git a/libs/SDL3/CLAUDE.md b/libs/SDL3/CLAUDE.md new file mode 100644 index 00000000..c8408cf2 --- /dev/null +++ b/libs/SDL3/CLAUDE.md @@ -0,0 +1,22 @@ +AI must not be used to generate code for contributions to this project. + +"AI" in this case means a Large Language Model ("LLM"), such as ChatGPT, +Claude, Copilot, Grok, etc. + +AI-generated code is based upon sources of unknown origins and may not be +compatible with the Zlib license, or may introduce conflicting license terms +if they include code from other projects. + +AI can be used to identify issues with contributions to this project, but the +solutions to those issues should be authored by humans. + +We have found that AI will frequently hallucinate issues that are not actually +problems in practice, report incorrect information, and describe problems that +are actually not issues at all. If AI identifies a problem with this codebase, +please make sure you understand what it is saying and have independently +confirmed that the issue exists before submitting a bug report or pull request. + +Any pull request to this project will ask you to confirm that you are the +author and that you are contributing your changes under the Zlib license. + + diff --git a/libs/SDL3/CMakeLists.txt b/libs/SDL3/CMakeLists.txt index fd62e985..faf6d900 100644 --- a/libs/SDL3/CMakeLists.txt +++ b/libs/SDL3/CMakeLists.txt @@ -5,7 +5,7 @@ if(NOT DEFINED CMAKE_BUILD_TYPE) endif() # See docs/release_checklist.md -project(SDL3 LANGUAGES C VERSION "3.3.0") +project(SDL3 LANGUAGES C VERSION "3.4.10") if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) set(SDL3_MAINPROJECT ON) @@ -46,6 +46,7 @@ endif() include(CheckLibraryExists) include(CheckIncludeFile) +include(CheckIncludeFiles) include(CheckLanguage) include(CheckSymbolExists) include(CheckCSourceCompiles) @@ -80,6 +81,12 @@ include("${SDL3_SOURCE_DIR}/cmake/PreseedNokiaNGageCache.cmake") SDL_DetectCompiler() SDL_DetectTargetCPUArchitectures(SDL_CPUS) +if(APPLE AND CMAKE_OSX_ARCHITECTURES) + list(LENGTH CMAKE_OSX_ARCHITECTURES _num_arches) + if(_num_arches GREATER 1) + set(APPLE_MULTIARCH TRUE) + endif() +endif() # Increment this if there is an incompatible change - but if that happens, # we should rename the library from SDL3 to SDL4, at which point this would @@ -163,12 +170,12 @@ else() endif() set(SDL_ASSEMBLY_DEFAULT OFF) -if(USE_CLANG OR USE_GCC OR USE_INTELCC OR MSVC_VERSION GREATER 1400) +if(USE_CLANG OR USE_GCC OR USE_INTELCC OR USE_TCC OR MSVC_VERSION GREATER 1400) set(SDL_ASSEMBLY_DEFAULT ON) endif() set(SDL_GCC_ATOMICS_DEFAULT OFF) -if(USE_GCC OR USE_CLANG OR USE_INTELCC OR USE_QCC) +if(USE_GCC OR USE_CLANG OR USE_INTELCC OR USE_QCC OR USE_TCC) set(SDL_GCC_ATOMICS_DEFAULT ON) endif() @@ -184,26 +191,6 @@ if(MSVC) set(SDL_RELOCATABLE_DEFAULT ON) endif() -if(MSVC) - if(NOT SDL_LIBC) - # Make sure /RTC1 is disabled, otherwise it will use functions from the CRT - foreach(flag_var - CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE - CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) - string(REGEX REPLACE "/RTC(su|[1su])" "" ${flag_var} "${${flag_var}}") - endforeach(flag_var) - endif() - - if(MSVC_CLANG) - # clang-cl treats /W4 as '-Wall -Wextra' -- we don't need -Wextra - foreach(flag_var - CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE - CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) - string(REGEX REPLACE "/W4" "/W3" ${flag_var} "${${flag_var}}") - endforeach(flag_var) - endif() -endif() - set(SDL_SHARED_DEFAULT ON) set(SDL_STATIC_DEFAULT ON) @@ -273,6 +260,7 @@ define_sdl_subsystem(Hidapi) define_sdl_subsystem(Power) define_sdl_subsystem(Sensor) define_sdl_subsystem(Dialog) +define_sdl_subsystem(Tray) cmake_dependent_option(SDL_FRAMEWORK "Build SDL libraries as Apple Framework" OFF "APPLE" OFF) if(SDL_FRAMEWORK) @@ -321,6 +309,7 @@ dep_option(SDL_ARMNEON "Use NEON assembly routines" ON "SDL_ASSEMBLY dep_option(SDL_LSX "Use LSX assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_LOONGARCH64" OFF) dep_option(SDL_LASX "Use LASX assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_LOONGARCH64" OFF) +dep_option(SDL_DLOPEN_NOTES "Record dlopen dependencies in .note.dlopen section" TRUE UNIX_SYS OFF) set_option(SDL_LIBC "Use the system C library" ${SDL_LIBC_DEFAULT}) set_option(SDL_SYSTEM_ICONV "Use iconv() from system-installed libraries" ${SDL_SYSTEM_ICONV_DEFAULT}) set_option(SDL_LIBICONV "Prefer iconv() from libiconv, if available, over libc version" OFF) @@ -359,6 +348,10 @@ dep_option(SDL_X11_XSCRNSAVER "Enable Xscrnsaver support" ON SDL_X11 OFF) dep_option(SDL_X11_XSHAPE "Enable XShape support" ON SDL_X11 OFF) dep_option(SDL_X11_XSYNC "Enable Xsync support" ON SDL_X11 OFF) dep_option(SDL_X11_XTEST "Enable XTest support" ON SDL_X11 OFF) +dep_option(SDL_FRIBIDI "Enable Fribidi support" ON SDL_X11 OFF) +dep_option(SDL_FRIBIDI_SHARED "Dynamically load Fribidi support" ON "SDL_FRIBIDI;SDL_DEPS_SHARED" OFF) +dep_option(SDL_LIBTHAI "Enable Thai support" ON SDL_X11 OFF) +dep_option(SDL_LIBTHAI_SHARED "Dynamically load Thai support" ON "SDL_LIBTHAI;SDL_DEPS_SHARED" OFF) dep_option(SDL_WAYLAND "Use Wayland video driver" ${UNIX_SYS} "SDL_VIDEO" OFF) dep_option(SDL_WAYLAND_SHARED "Dynamically load Wayland support" ON "SDL_WAYLAND;SDL_DEPS_SHARED" OFF) dep_option(SDL_WAYLAND_LIBDECOR "Use client-side window decorations on Wayland" ON "SDL_WAYLAND" OFF) @@ -372,10 +365,10 @@ dep_option(SDL_WASAPI "Use the Windows WASAPI audio driver" ON "WIN dep_option(SDL_RENDER_D3D "Enable the Direct3D 9 render driver" ON "SDL_RENDER;SDL_DIRECTX" OFF) dep_option(SDL_RENDER_D3D11 "Enable the Direct3D 11 render driver" ON "SDL_RENDER;SDL_DIRECTX" OFF) dep_option(SDL_RENDER_D3D12 "Enable the Direct3D 12 render driver" ON "SDL_RENDER;SDL_DIRECTX" OFF) -dep_option(SDL_RENDER_METAL "Enable the Metal render driver" ON "SDL_RENDER;${APPLE}" OFF) +dep_option(SDL_RENDER_METAL "Enable the Metal render driver" ON "SDL_RENDER;APPLE" OFF) dep_option(SDL_RENDER_GPU "Enable the SDL_GPU render driver" ON "SDL_RENDER;SDL_GPU" OFF) dep_option(SDL_VIVANTE "Use Vivante EGL video driver" ON "${UNIX_SYS};SDL_CPU_ARM32" OFF) -dep_option(SDL_VULKAN "Enable Vulkan support" ON "SDL_VIDEO;ANDROID OR APPLE OR LINUX OR FREEBSD OR WINDOWS" OFF) +dep_option(SDL_VULKAN "Enable Vulkan support" ON "SDL_VIDEO;ANDROID OR APPLE OR LINUX OR FREEBSD OR OPENBSD OR WINDOWS" OFF) dep_option(SDL_RENDER_VULKAN "Enable the Vulkan render driver" ON "SDL_RENDER;SDL_VULKAN" OFF) dep_option(SDL_METAL "Enable Metal support" ON "APPLE" OFF) set_option(SDL_OPENVR "Use OpenVR video driver" OFF) @@ -386,7 +379,7 @@ dep_option(SDL_DUMMYCAMERA "Support the dummy camera driver" ON SDL_CAME option_string(SDL_BACKGROUNDING_SIGNAL "number to use for magic backgrounding signal or 'OFF'" OFF) option_string(SDL_FOREGROUNDING_SIGNAL "number to use for magic foregrounding signal or 'OFF'" OFF) dep_option(SDL_HIDAPI "Enable the HIDAPI subsystem" ON "NOT VISIONOS" OFF) -dep_option(SDL_HIDAPI_LIBUSB "Use libusb for low level joystick drivers" ON SDL_HIDAPI_LIBUSB_AVAILABLE OFF) +dep_option(SDL_HIDAPI_LIBUSB "Use libusb for low level joystick drivers" ON "SDL_HIDAPI;SDL_HIDAPI_LIBUSB_AVAILABLE" OFF) dep_option(SDL_HIDAPI_LIBUSB_SHARED "Dynamically load libusb support" ON "SDL_HIDAPI_LIBUSB;SDL_DEPS_SHARED" OFF) dep_option(SDL_HIDAPI_JOYSTICK "Use HIDAPI for low level joystick drivers" ON SDL_HIDAPI OFF) dep_option(SDL_VIRTUAL_JOYSTICK "Enable the virtual-joystick driver" ON SDL_HIDAPI OFF) @@ -394,7 +387,10 @@ set_option(SDL_LIBUDEV "Enable libudev support" ON) set_option(SDL_ASAN "Use AddressSanitizer to detect memory errors" OFF) set_option(SDL_CCACHE "Use Ccache to speed up build" OFF) set_option(SDL_CLANG_TIDY "Run clang-tidy static analysis" OFF) -set_option(SDL_GPU_DXVK "Build SDL_GPU with DXVK support" OFF) + +if(EMSCRIPTEN) + option_string(SDL_EMSCRIPTEN_PERSISTENT_PATH "Path to mount Emscripten IDBFS at startup or '' to disable" "") +endif() set(SDL_VENDOR_INFO "" CACHE STRING "Vendor name and/or version to add to SDL_REVISION") @@ -402,7 +398,7 @@ cmake_dependent_option(SDL_SHARED "Build a shared version of the library" ${SDL_ cmake_dependent_option(SDL_STATIC "Build a static version of the library" ${SDL_STATIC_DEFAULT} ${SDL_STATIC_AVAILABLE} OFF) option(SDL_TEST_LIBRARY "Build the SDL3_test library" ON) -dep_option(SDL_TESTS "Build the test directory" OFF SDL_TEST_LIBRARY OFF) +dep_option(SDL_TESTS "Build the test directory" ${SDL3_MAINPROJECT} SDL_TEST_LIBRARY OFF) dep_option(SDL_INSTALL_TESTS "Install test-cases" OFF "SDL_INSTALL;NOT SDL_FRAMEWORK" OFF) dep_option(SDL_TESTS_LINK_SHARED "link tests to shared SDL library" "${SDL_SHARED}" "SDL_SHARED;SDL_STATIC" "${SDL_SHARED}") set(SDL_TESTS_TIMEOUT_MULTIPLIER "1" CACHE STRING "Timeout multiplier to account for really slow machines") @@ -430,6 +426,7 @@ if (NGAGE) set(SDL_DUMMYVIDEO OFF) set(SDL_OFFSCREEN OFF) set(SDL_RENDER_GPU OFF) + set(SDL_TRAY OFF) set(SDL_VIRTUAL_JOYSTICK OFF) endif() @@ -441,14 +438,42 @@ if(SDL_PRESEED) SDL_Preseed_CMakeCache() endif() +if(MSVC) + if(NOT SDL_LIBC) + # Make sure /RTC1 is disabled, otherwise it will use functions from the CRT + foreach(flag_var + CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + string(REGEX REPLACE "/RTC(su|[1su])" "" ${flag_var} "${${flag_var}}") + endforeach(flag_var) + set(CMAKE_MSVC_RUNTIME_CHECKS "") + endif() + + if(MSVC_CLANG) + # clang-cl treats /W4 as '-Wall -Wextra' -- we don't need -Wextra + foreach(flag_var + CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) + string(REGEX REPLACE "/W4" "/W3" ${flag_var} "${${flag_var}}") + endforeach(flag_var) + endif() +endif() + if(SDL_SHARED) add_library(SDL3-shared SHARED) add_library(SDL3::SDL3-shared ALIAS SDL3-shared) SDL_AddCommonCompilerFlags(SDL3-shared) + target_compile_definitions(SDL3-shared PRIVATE "$<$:DEBUG>") + set_property(TARGET SDL3-shared PROPERTY UNITY_BUILD OFF) if ("c_std_99" IN_LIST CMAKE_C_COMPILE_FEATURES) target_compile_features(SDL3-shared PRIVATE c_std_99) else() - message(WARNING "target_compile_features does not know c_std_99 for C compiler") + # tcc does support the subset of C99 used by SDL + if (NOT USE_TCC) + message(WARNING "target_compile_features does not know c_std_99 for C compiler") + endif() endif() endif() @@ -456,10 +481,14 @@ if(SDL_STATIC) add_library(SDL3-static STATIC) add_library(SDL3::SDL3-static ALIAS SDL3-static) SDL_AddCommonCompilerFlags(SDL3-static) + target_compile_definitions(SDL3-static PRIVATE "$<$:DEBUG>") + set_property(TARGET SDL3-static PROPERTY UNITY_BUILD OFF) if ("c_std_99" IN_LIST CMAKE_C_COMPILE_FEATURES) target_compile_features(SDL3-static PRIVATE c_std_99) else() - message(WARNING "target_compile_features does not know c_std_99 for C compiler") + if (NOT USE_TCC) + message(WARNING "target_compile_features does not know c_std_99 for C compiler") + endif() endif() endif() @@ -467,6 +496,7 @@ if(SDL_TEST_LIBRARY) add_library(SDL3_test STATIC) add_library(SDL3::SDL3_test ALIAS SDL3_test) SDL_AddCommonCompilerFlags(SDL3_test) + target_compile_definitions(SDL3_test PRIVATE "$<$:DEBUG>") endif() # Make sure SDL3::SDL3 always exists @@ -492,7 +522,10 @@ check_linker_supports_version_file(HAVE_WL_VERSION_SCRIPT) if(HAVE_WL_VERSION_SCRIPT) sdl_shared_link_options("-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/dynapi/SDL_dynapi.sym") else() - if((LINUX AND LIBC_IS_GLIBC) OR ANDROID) + # When building with tcc on Linux+glibc or Android, avoid emitting an error + # for lack of support of the version-script linker flag: the option will be + # silently ignored by the compiler and the build will still succeed. + if(((LINUX AND LIBC_IS_GLIBC) OR ANDROID) AND (NOT USE_TCC)) message(FATAL_ERROR "Linker does not support '-Wl,--version-script=xxx.sym'. This is required on the current host platform (${SDL_CMAKE_PLATFORM}).") endif() endif() @@ -624,14 +657,13 @@ if(USE_GCC OR USE_CLANG OR USE_INTELCC OR USE_QCC) if(COMPILER_SUPPORTS_WNO_ERROR_DEPRECATED_DECLARATIONS) sdl_compile_options(PRIVATE "-Wno-error=deprecated-declarations") endif() + check_c_compiler_flag(-Wno-deprecated-declarations COMPILER_SUPPORTS_WNO_DEPRECATED_DECLARATIONS) + if(COMPILER_SUPPORTS_WNO_DEPRECATED_DECLARATIONS) + sdl_compile_options(PRIVATE "-Wno-deprecated-declarations") + endif() endif() - if(APPLE) - check_linker_flag(C "-Wl,-undefined,error" LINKER_SUPPORTS_WL_UNDEFINED_ERROR) - if(LINKER_SUPPORTS_WL_UNDEFINED_ERROR) - sdl_shared_link_options("-Wl,-undefined,error") - endif() - elseif(NOT OPENBSD) + if(NOT OPENBSD) cmake_push_check_state() check_linker_flag(C "-Wl,--no-undefined" LINKER_SUPPORTS_WL_NO_UNDEFINED) #FIXME: originally this if had an additional "AND NOT (USE_CLANG AND WINDOWS)" @@ -654,6 +686,11 @@ if(MSVC) # Mark SDL3.dll as compatible with Control-flow Enforcement Technology (CET) sdl_shared_link_options("-CETCOMPAT") endif() + + # for VS >= 17.14 targeting ARM64: inline the Interlocked funcs + if(MSVC_VERSION GREATER 1943 AND SDL_CPU_ARM64 AND NOT SDL_LIBC) + sdl_compile_options(PRIVATE "/forceInterlockedFunctions-") + endif() endif() if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") @@ -670,7 +707,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -mmmx") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void ints_add(int *dest, int *a, int *b, unsigned size) { for (; size >= 2; size -= 2, dest += 2, a += 2, b += 2) { @@ -680,7 +717,7 @@ if(SDL_ASSEMBLY) int main(int argc, char *argv[]) { ints_add((int*)0, (int*)0, (int*)0, 0); return 0; - }" COMPILER_SUPPORTS_MMX) + }]==] COMPILER_SUPPORTS_MMX) cmake_pop_check_state() if(COMPILER_SUPPORTS_MMX) set(HAVE_MMX TRUE) @@ -691,7 +728,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -msse") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void floats_add(float *dest, float *a, float *b, unsigned size) { for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { @@ -701,7 +738,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { floats_add((float*)0, (float*)0, (float*)0, 0); return 0; - }" COMPILER_SUPPORTS_SSE) + }]==] COMPILER_SUPPORTS_SSE) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE) set(HAVE_SSE TRUE) @@ -712,7 +749,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -msse2") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void doubles_add(double *dest, double *a, double *b, unsigned size) { for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { @@ -722,7 +759,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { doubles_add((double*)0, (double*)0, (double*)0, 0); return 0; - }" COMPILER_SUPPORTS_SSE2) + }]==] COMPILER_SUPPORTS_SSE2) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE2) set(HAVE_SSE2 TRUE) @@ -733,7 +770,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -msse3") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void ints_add(int *dest, int *a, int *b, unsigned size) { for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { @@ -743,7 +780,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { ints_add((int*)0, (int*)0, (int*)0, 0); return 0; - }" COMPILER_SUPPORTS_SSE3) + }]==] COMPILER_SUPPORTS_SSE3) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE3) set(HAVE_SSE3 TRUE) @@ -754,7 +791,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -msse4.1") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void ints_mul(int *dest, int *a, int *b, unsigned size) { for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { @@ -764,7 +801,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { ints_mul((int*)0, (int*)0, (int*)0, 0); return 0; - }" COMPILER_SUPPORTS_SSE4_1) + }]==] COMPILER_SUPPORTS_SSE4_1) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE4_1) set(HAVE_SSE4_1 TRUE) @@ -773,21 +810,16 @@ if(SDL_ASSEMBLY) if(SDL_SSE4_2) cmake_push_check_state() if(USE_GCC OR USE_CLANG OR USE_INTELCC) - string(APPEND CMAKE_REQUIRED_FLAGS " -msse4.2 -mcrc32") + string(APPEND CMAKE_REQUIRED_FLAGS " -msse4.2") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include - unsigned calc_crc32c(const char *text, unsigned len) { - unsigned crc32c = ~0; - for (; len >= 4; len -= 4, text += 4) { - crc32c = (unsigned)_mm_crc32_u32(crc32c, *(unsigned*)text); - } - return crc32c; - } + __m128i bitmask; + char data[16]; int main(int argc, char **argv) { - calc_crc32c(\"SDL_SSE4\",8); + bitmask = _mm_cmpgt_epi64(_mm_set1_epi64x(0), _mm_loadu_si128((void*)data)); return 0; - }" COMPILER_SUPPORTS_SSE4_2) + }]==] COMPILER_SUPPORTS_SSE4_2) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE4_2) set(HAVE_SSE4_2 TRUE) @@ -798,7 +830,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -mavx") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void floats_add(float *dest, float *a, float *b, unsigned size) { for (; size >= 8; size -= 8, dest += 8, a += 8, b += 8) { @@ -808,7 +840,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { floats_add((float*)0, (float*)0, (float*)0, 0); return 0; - }" COMPILER_SUPPORTS_AVX) + }]==] COMPILER_SUPPORTS_AVX) cmake_pop_check_state() if(COMPILER_SUPPORTS_AVX) set(HAVE_AVX TRUE) @@ -819,7 +851,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -mavx2") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void ints_add(int *dest, int *a, int *b, unsigned size) { for (; size >= 8; size -= 8, dest += 8, a += 8, b += 8) { @@ -829,7 +861,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { ints_add((int*)0, (int*)0, (int*)0, 0); return 0; - }" COMPILER_SUPPORTS_AVX2) + }]==] COMPILER_SUPPORTS_AVX2) cmake_pop_check_state() if(COMPILER_SUPPORTS_AVX2) set(HAVE_AVX2 TRUE) @@ -840,7 +872,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -mavx512f") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void floats_add(float *dest, float *a, float *b, unsigned size) { for (; size >= 16; size -= 16, dest += 16, a += 16, b += 16) { @@ -850,7 +882,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { floats_add((float*)0, (float*)0, (float*)0, 0); return 0; - }" COMPILER_SUPPORTS_AVX512F) + }]==] COMPILER_SUPPORTS_AVX512F) cmake_pop_check_state() if(COMPILER_SUPPORTS_AVX512F) set(HAVE_AVX512F TRUE) @@ -858,18 +890,17 @@ if(SDL_ASSEMBLY) endif() if(SDL_ARMNEON) - check_c_source_compiles(" - #include - void floats_add(float *dest, float *a, float *b, unsigned size) { - for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { - vst1q_f32(dest, vaddq_f32(vld1q_f32(a), vld1q_f32(b))); - } + check_arm_source_compiles([==[ + #include + void floats_add(float *dest, float *a, float *b, unsigned size) { + for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { + vst1q_f32(dest, vaddq_f32(vld1q_f32(a), vld1q_f32(b))); } - int main(int argc, char *argv[]) { - floats_add((float*)0, (float*)0, (float*)0, 0); - return 0; - }" COMPILER_SUPPORTS_ARMNEON) - + } + int main(int argc, char *argv[]) { + floats_add((float*)0, (float*)0, (float*)0, 0); + return 0; + }]==] COMPILER_SUPPORTS_ARMNEON) if(COMPILER_SUPPORTS_ARMNEON) set(HAVE_ARMNEON TRUE) endif() @@ -893,6 +924,7 @@ if(SDL_ASSEMBLY) set(HAVE_ALTIVEC TRUE) set(SDL_ALTIVEC_BLITTERS 1) sdl_compile_options(PRIVATE "-maltivec") + sdl_compile_options(PRIVATE "-fno-tree-vectorize") set_property(SOURCE "${SDL3_SOURCE_DIR}/src/video/SDL_blit_N.c" APPEND PROPERTY COMPILE_DEFINITIONS "SDL_ENABLE_ALTIVEC") set_property(SOURCE "${SDL3_SOURCE_DIR}/src/video/SDL_blit_N.c" PROPERTY SKIP_PRECOMPILE_HEADERS 1) endif() @@ -910,7 +942,17 @@ if(SDL_ASSEMBLY) cmake_pop_check_state() if(COMPILER_SUPPORTS_LSX AND HAVE_LSXINTRIN_H) - set_property(SOURCE "${SDL3_SOURCE_DIR}/src/video/yuv2rgb/yuv_rgb_lsx.c" APPEND PROPERTY COMPILE_OPTIONS "-mlsx") + set_property(SOURCE + "${SDL3_SOURCE_DIR}/src/video/yuv2rgb/yuv_rgb_lsx.c" + "${SDL3_SOURCE_DIR}/src/video/SDL_blit_A.c" + "${SDL3_SOURCE_DIR}/src/video/SDL_fillrect.c" + APPEND PROPERTY COMPILE_OPTIONS "-mlsx") + + set_property(SOURCE + "${SDL3_SOURCE_DIR}/src/video/yuv2rgb/yuv_rgb_lsx.c" + "${SDL3_SOURCE_DIR}/src/video/SDL_blit_A.c" + "${SDL3_SOURCE_DIR}/src/video/SDL_fillrect.c" + PROPERTY SKIP_PRECOMPILE_HEADERS 1) set(HAVE_LSX TRUE) endif() endif() @@ -984,7 +1026,14 @@ set(SDL_DISABLE_ALLOCA 0) check_include_file("alloca.h" "HAVE_ALLOCA_H") if(MSVC) check_include_file("malloc.h" "HAVE_MALLOC_H") - check_symbol_exists("_alloca" "malloc.h" _ALLOCA_IN_MALLOC_H) + # Cannot use CheckSymbolExists for _alloca: purely intrinsic functions have no address (C7552) + if(NOT DEFINED _ALLOCA_IN_MALLOC_H) + message(STATUS "Looking for _alloca in malloc.h") + set(testsrc "${CMAKE_CURRENT_SOURCE_DIR}/test_malloc_alloca.c") + file(WRITE "${testsrc}" "#include \n\nint main(int argc, char *argv[]) { void *ptr = _alloca(argc * (int)argv[0][0]); return ptr != (void *)0; }") + try_compile(_ALLOCA_IN_MALLOC_H "${CMAKE_CURRENT_BINARY_DIR}/alloca_in_malloc_h" SOURCES "${testsrc}") + message(STATUS "Looking for _alloca in malloc.h - ${_ALLOCA_IN_MALLOC_H}") + endif() if(NOT HAVE_ALLOCA_H AND NOT _ALLOCA_IN_MALLOC_H) set(SDL_DISABLE_ALLOCA 1) endif() @@ -1048,7 +1097,7 @@ if(SDL_LIBC) ) if(WINDOWS) list(APPEND symbols_to_check - _copysign _fseeki64 _strrev _ui64toa _uitoa _ultoa _wcsdup + _copysign _fseeki64 _strrev _ui64toa _ultoa _wcsdup ) else() list(APPEND symbols_to_check @@ -1072,8 +1121,10 @@ if(SDL_LIBC) cmake_push_check_state() if(MSVC) string(APPEND CMAKE_REQUIRED_FLAGS " -we4244 -WX") # 'conversion' conversion from 'type1' to 'type2', possible loss of data - else() + elseif(HAVE_GCC_WFLOAT_CONVERSION) string(APPEND CMAKE_REQUIRED_FLAGS " -Wfloat-conversion -Werror") + else() + string(APPEND CMAKE_REQUIRED_FLAGS " -Wconversion -Werror") endif() foreach(math_fn isinf isnan) string(TOUPPER "${math_fn}" MATH_FN) @@ -1110,7 +1161,10 @@ if(SDL_LIBC) check_symbol_exists(fdatasync "unistd.h" HAVE_FDATASYNC) check_symbol_exists(gethostname "unistd.h" HAVE_GETHOSTNAME) check_symbol_exists(getpagesize "unistd.h" HAVE_GETPAGESIZE) + check_symbol_exists(getresgid "unistd.h" HAVE_GETRESGID) + check_symbol_exists(getresuid "unistd.h" HAVE_GETRESUID) check_symbol_exists(sigaction "signal.h" HAVE_SIGACTION) + check_symbol_exists(sigtimedwait "signal.h" HAVE_SIGTIMEDWAIT) check_symbol_exists(setjmp "setjmp.h" HAVE_SETJMP) check_symbol_exists(nanosleep "time.h" HAVE_NANOSLEEP) check_symbol_exists(gmtime_r "time.h" HAVE_GMTIME_R) @@ -1120,39 +1174,38 @@ if(SDL_LIBC) check_symbol_exists(sysctlbyname "sys/types.h;sys/sysctl.h" HAVE_SYSCTLBYNAME) check_symbol_exists(getauxval "sys/auxv.h" HAVE_GETAUXVAL) check_symbol_exists(elf_aux_info "sys/auxv.h" HAVE_ELF_AUX_INFO) - check_symbol_exists(poll "poll.h" HAVE_POLL) + check_symbol_exists(ppoll "poll.h" HAVE_PPOLL) check_symbol_exists(memfd_create "sys/mman.h" HAVE_MEMFD_CREATE) check_symbol_exists(posix_fallocate "fcntl.h" HAVE_POSIX_FALLOCATE) check_symbol_exists(posix_spawn_file_actions_addchdir "spawn.h" HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR) check_symbol_exists(posix_spawn_file_actions_addchdir_np "spawn.h" HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP) + endif() + if(SDL_SYSTEM_ICONV) + check_c_source_compiles(" + #define LIBICONV_PLUG 1 /* in case libiconv header is in include path */ + #include + #include + int main(int argc, char **argv) { + return !iconv_open(NULL,NULL); + }" ICONV_IN_LIBC) - if(SDL_SYSTEM_ICONV) - check_c_source_compiles(" - #define LIBICONV_PLUG 1 /* in case libiconv header is in include path */ - #include - #include - int main(int argc, char **argv) { - return !iconv_open(NULL,NULL); - }" ICONV_IN_LIBC) + cmake_push_check_state() + list(APPEND CMAKE_REQUIRED_LIBRARIES iconv) + check_c_source_compiles(" + #include + #include + int main(int argc, char **argv) { + return !iconv_open(NULL,NULL); + }" ICONV_IN_LIBICONV) + cmake_pop_check_state() - cmake_push_check_state() - list(APPEND CMAKE_REQUIRED_LIBRARIES iconv) - check_c_source_compiles(" - #include - #include - int main(int argc, char **argv) { - return !iconv_open(NULL,NULL); - }" ICONV_IN_LIBICONV) - cmake_pop_check_state() - - if(ICONV_IN_LIBC OR ICONV_IN_LIBICONV) - set(HAVE_ICONV 1) - set(HAVE_SYSTEM_ICONV TRUE) - if(ICONV_IN_LIBICONV AND (SDL_LIBICONV OR (NOT ICONV_IN_LIBC))) - sdl_link_dependency(iconv LIBS iconv) - set(SDL_USE_LIBICONV 1) - set(HAVE_LIBICONV TRUE) - endif() + if(ICONV_IN_LIBC OR ICONV_IN_LIBICONV) + set(HAVE_ICONV 1) + set(HAVE_SYSTEM_ICONV TRUE) + if(ICONV_IN_LIBICONV AND (SDL_LIBICONV OR (NOT ICONV_IN_LIBC))) + sdl_link_dependency(iconv LIBS iconv) + set(SDL_USE_LIBICONV 1) + set(HAVE_LIBICONV TRUE) endif() endif() @@ -1181,50 +1234,84 @@ endif() # General source files sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/*.c" + "${SDL3_SOURCE_DIR}/src/*.h" "${SDL3_SOURCE_DIR}/src/atomic/*.c" + "${SDL3_SOURCE_DIR}/src/atomic/*.h" "${SDL3_SOURCE_DIR}/src/audio/*.c" + "${SDL3_SOURCE_DIR}/src/audio/*.h" "${SDL3_SOURCE_DIR}/src/camera/*.c" + "${SDL3_SOURCE_DIR}/src/camera/*.h" "${SDL3_SOURCE_DIR}/src/core/*.c" + "${SDL3_SOURCE_DIR}/src/core/*.h" "${SDL3_SOURCE_DIR}/src/cpuinfo/*.c" + "${SDL3_SOURCE_DIR}/src/cpuinfo/*.h" "${SDL3_SOURCE_DIR}/src/dynapi/*.c" + "${SDL3_SOURCE_DIR}/src/dynapi/*.h" "${SDL3_SOURCE_DIR}/src/events/*.c" + "${SDL3_SOURCE_DIR}/src/events/*.h" "${SDL3_SOURCE_DIR}/src/io/*.c" + "${SDL3_SOURCE_DIR}/src/io/*.h" "${SDL3_SOURCE_DIR}/src/io/generic/*.c" + "${SDL3_SOURCE_DIR}/src/io/generic/*.h" "${SDL3_SOURCE_DIR}/src/filesystem/*.c" + "${SDL3_SOURCE_DIR}/src/filesystem/*.h" "${SDL3_SOURCE_DIR}/src/gpu/*.c" + "${SDL3_SOURCE_DIR}/src/gpu/*.h" "${SDL3_SOURCE_DIR}/src/joystick/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/*.h" "${SDL3_SOURCE_DIR}/src/haptic/*.c" + "${SDL3_SOURCE_DIR}/src/haptic/*.h" "${SDL3_SOURCE_DIR}/src/hidapi/*.c" + "${SDL3_SOURCE_DIR}/src/hidapi/*.h" "${SDL3_SOURCE_DIR}/src/locale/*.c" + "${SDL3_SOURCE_DIR}/src/locale/*.h" "${SDL3_SOURCE_DIR}/src/main/*.c" + "${SDL3_SOURCE_DIR}/src/main/*.h" "${SDL3_SOURCE_DIR}/src/misc/*.c" + "${SDL3_SOURCE_DIR}/src/misc/*.h" "${SDL3_SOURCE_DIR}/src/power/*.c" + "${SDL3_SOURCE_DIR}/src/power/*.h" "${SDL3_SOURCE_DIR}/src/render/*.c" + "${SDL3_SOURCE_DIR}/src/render/*.h" "${SDL3_SOURCE_DIR}/src/render/*/*.c" + "${SDL3_SOURCE_DIR}/src/render/*/*.h" "${SDL3_SOURCE_DIR}/src/sensor/*.c" + "${SDL3_SOURCE_DIR}/src/sensor/*.h" "${SDL3_SOURCE_DIR}/src/stdlib/*.c" + "${SDL3_SOURCE_DIR}/src/stdlib/*.h" "${SDL3_SOURCE_DIR}/src/storage/*.c" + "${SDL3_SOURCE_DIR}/src/storage/*.h" "${SDL3_SOURCE_DIR}/src/thread/*.c" + "${SDL3_SOURCE_DIR}/src/thread/*.h" "${SDL3_SOURCE_DIR}/src/time/*.c" + "${SDL3_SOURCE_DIR}/src/time/*.h" "${SDL3_SOURCE_DIR}/src/timer/*.c" + "${SDL3_SOURCE_DIR}/src/timer/*.h" "${SDL3_SOURCE_DIR}/src/video/*.c" + "${SDL3_SOURCE_DIR}/src/video/*.h" "${SDL3_SOURCE_DIR}/src/video/yuv2rgb/*.c" + "${SDL3_SOURCE_DIR}/src/video/yuv2rgb/*.h" ) -# Build uclibc as a static library such that non-used symbols don't end up in the SDL3 shared library. file(GLOB SDL_UCLIBC_SOURCES "${SDL3_SOURCE_DIR}/src/libm/*.c") -add_library(SDL_uclibc STATIC "${SDL_UCLIBC_SOURCES}") -target_compile_definitions(SDL_uclibc PRIVATE USING_GENERATED_CONFIG_H) -target_include_directories(SDL_uclibc PRIVATE "${SDL3_BINARY_DIR}/include-config-$>/build_config") -target_include_directories(SDL_uclibc PRIVATE "${SDL3_SOURCE_DIR}/src") -target_include_directories(SDL_uclibc PRIVATE "${SDL3_SOURCE_DIR}/include") -SDL_AddCommonCompilerFlags(SDL_uclibc) -sdl_sources(STATIC "$") if(TARGET SDL3-shared) + # Build uclibc as a static library such that non-used symbols don't end up in the SDL3 shared library. + add_library(SDL_uclibc STATIC ${SDL_UCLIBC_SOURCES}) + set_property(TARGET SDL_uclibc PROPERTY POSITION_INDEPENDENT_CODE TRUE) + target_compile_definitions(SDL_uclibc PRIVATE USING_GENERATED_CONFIG_H) + target_include_directories(SDL_uclibc PRIVATE "${SDL3_BINARY_DIR}/include-config-$>/build_config") + target_include_directories(SDL_uclibc PRIVATE "${SDL3_SOURCE_DIR}/src") + target_include_directories(SDL_uclibc PRIVATE "${SDL3_SOURCE_DIR}/include") + SDL_AddCommonCompilerFlags(SDL_uclibc) + target_compile_definitions(SDL_uclibc PRIVATE "$<$:DEBUG>") + set_property(TARGET SDL_uclibc PROPERTY UNITY_BUILD OFF) target_link_libraries(SDL3-shared PRIVATE SDL_uclibc) + if(HAVE_GCC_FVISIBILITY) + set_property(TARGET SDL_uclibc PROPERTY C_VISIBILITY_PRESET "hidden") + endif() endif() -if(HAVE_GCC_FVISIBILITY) - set_property(TARGET SDL_uclibc PROPERTY C_VISIBILITY_PRESET "hidden") +if(TARGET SDL3-static) + target_sources(SDL3-static PRIVATE ${SDL_UCLIBC_SOURCES}) endif() # Enable/disable various subsystems of the SDL library @@ -1247,13 +1334,19 @@ if(SDL_AUDIO) # CheckDummyAudio/CheckDiskAudio - valid for all platforms if(SDL_DUMMYAUDIO) set(SDL_AUDIO_DRIVER_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/audio/dummy/*.h" + ) set(HAVE_DUMMYAUDIO TRUE) set(HAVE_SDL_AUDIO TRUE) endif() if(SDL_DISKAUDIO) set(SDL_AUDIO_DRIVER_DISK 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/disk/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/disk/*.c" + "${SDL3_SOURCE_DIR}/src/audio/disk/*.h" + ) set(HAVE_DISKAUDIO TRUE) set(HAVE_SDL_AUDIO TRUE) endif() @@ -1263,7 +1356,10 @@ if(SDL_CAMERA) # CheckDummyCamera/CheckDiskCamera - valid for all platforms if(SDL_DUMMYCAMERA) set(SDL_CAMERA_DRIVER_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/camera/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/camera/dummy/*.h" + ) set(HAVE_DUMMYCAMERA TRUE) set(HAVE_SDL_CAMERA TRUE) endif() @@ -1282,7 +1378,10 @@ if(UNIX OR APPLE) CheckDLOPEN() if(HAVE_DLOPEN) set(SDL_LOADSO_DLOPEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/loadso/dlopen/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/loadso/dlopen/*.c" + "${SDL3_SOURCE_DIR}/src/loadso/dlopen/*.h" + ) set(HAVE_SDL_LOADSO TRUE) endif() endif() @@ -1295,14 +1394,20 @@ if(SDL_JOYSTICK) if(SDL_VIRTUAL_JOYSTICK) set(HAVE_VIRTUAL_JOYSTICK TRUE) set(SDL_JOYSTICK_VIRTUAL 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/virtual/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/virtual/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/virtual/*.h" + ) endif() endif() if(SDL_VIDEO) if(SDL_DUMMYVIDEO) set(SDL_VIDEO_DRIVER_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/video/dummy/*.h" + ) set(HAVE_DUMMYVIDEO TRUE) set(HAVE_SDL_VIDEO TRUE) endif() @@ -1312,11 +1417,15 @@ endif() if(ANDROID) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/android") - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/android/*.c") - sdl_sources("${CMAKE_ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c") - set_property(SOURCE "${CMAKE_ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c" APPEND_STRING PROPERTY COMPILE_FLAGS " -Wno-declaration-after-statement") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/core/android/*.c" + "${SDL3_SOURCE_DIR}/src/core/android/*.h" + ) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/misc/android/*.c" + "${SDL3_SOURCE_DIR}/src/misc/android/*.h" + ) set(HAVE_SDL_MISC TRUE) # SDL_spinlock.c Needs to be compiled in ARM mode. @@ -1334,18 +1443,27 @@ if(ANDROID) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_OPENSLES 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/openslES/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/openslES/*.c" + "${SDL3_SOURCE_DIR}/src/audio/openslES/*.h" + ) sdl_link_dependency(opensles LIBS ${ANDROID_DL_LIBRARY} OpenSLES) set(SDL_AUDIO_DRIVER_AAUDIO 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/aaudio/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/aaudio/*.c" + "${SDL3_SOURCE_DIR}/src/audio/aaudio/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() set(SDL_FILESYSTEM_ANDROID 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/filesystem/android/*.c" + "${SDL3_SOURCE_DIR}/src/filesystem/android/*.h" + ) set(HAVE_SDL_FILESYSTEM TRUE) set(SDL_FSOPS_POSIX 1) # !!! FIXME: this might need something else for .apk data? @@ -1354,58 +1472,84 @@ if(ANDROID) if(SDL_HAPTIC) set(SDL_HAPTIC_ANDROID 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/haptic/android/*.c" + "${SDL3_SOURCE_DIR}/src/haptic/android/*.h" + ) set(HAVE_SDL_HAPTIC TRUE) endif() - if(SDL_HIDAPI) - CheckHIDAPI() - endif() + CheckHIDAPI() if(SDL_JOYSTICK) set(SDL_JOYSTICK_ANDROID 1) sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/joystick/android/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/android/*.h" ) set(HAVE_SDL_JOYSTICK TRUE) endif() set(SDL_LOADSO_DLOPEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/loadso/dlopen/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/loadso/dlopen/*.c" + "${SDL3_SOURCE_DIR}/src/loadso/dlopen/*.h" + ) set(HAVE_SDL_LOADSO TRUE) if(SDL_POWER) set(SDL_POWER_ANDROID 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/power/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/power/android/*.c" + "${SDL3_SOURCE_DIR}/src/power/android/*.h" + ) set(HAVE_SDL_POWER TRUE) endif() - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/locale/android/*.c" + "${SDL3_SOURCE_DIR}/src/locale/android/*.h" + ) set(HAVE_SDL_LOCALE TRUE) set(SDL_TIME_UNIX 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/time/unix/*.c" + "${SDL3_SOURCE_DIR}/src/time/unix/*.h" + ) set(HAVE_SDL_TIME TRUE) set(SDL_TIMER_UNIX 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/unix/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/timer/unix/*.c" + "${SDL3_SOURCE_DIR}/src/timer/unix/*.h" + ) set(HAVE_SDL_TIMERS TRUE) if(SDL_SENSOR) set(SDL_SENSOR_ANDROID 1) set(HAVE_SDL_SENSORS TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/sensor/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/sensor/android/*.c" + "${SDL3_SOURCE_DIR}/src/sensor/android/*.h" + ) endif() if(SDL_CAMERA) set(SDL_CAMERA_DRIVER_ANDROID 1) set(HAVE_CAMERA TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/camera/android/*.c" + "${SDL3_SOURCE_DIR}/src/camera/android/*.h" + ) endif() if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_ANDROID 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/android/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/android/*.c" + "${SDL3_SOURCE_DIR}/src/video/android/*.h" + ) set(HAVE_SDL_VIDEO TRUE) # Core stuff @@ -1502,26 +1646,46 @@ if(ANDROID) endif() endif() endif() + if(TARGET SDL3-static) + target_link_options(SDL3-static INTERFACE "-Wl,-u,JNI_OnLoad") + endif() elseif(EMSCRIPTEN) # Hide noisy warnings that intend to aid mostly during initial stages of porting a new # project. Uncomment at will for verbose cross-compiling -I/../ path info. sdl_compile_options(PRIVATE "-Wno-warn-absolute-paths") - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/emscripten/*.c") + if(NOT SDL_EMSCRIPTEN_PERSISTENT_PATH STREQUAL "") + set(SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING "${SDL_EMSCRIPTEN_PERSISTENT_PATH}") + sdl_link_dependency(idbfs LIBS idbfs.js) + endif() + + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/main/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/main/emscripten/*.h" + ) set(HAVE_SDL_MAIN_CALLBACKS TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/misc/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/misc/emscripten/*.h" + ) set(HAVE_SDL_MISC TRUE) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_EMSCRIPTEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/audio/emscripten/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() set(SDL_FILESYSTEM_EMSCRIPTEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/filesystem/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/filesystem/emscripten/*.h" + ) set(HAVE_SDL_FILESYSTEM TRUE) set(SDL_FSOPS_POSIX 1) @@ -1531,39 +1695,69 @@ elseif(EMSCRIPTEN) if(SDL_CAMERA) set(SDL_CAMERA_DRIVER_EMSCRIPTEN 1) set(HAVE_CAMERA TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/camera/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/camera/emscripten/*.h" + ) endif() if(SDL_JOYSTICK) set(SDL_JOYSTICK_EMSCRIPTEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/emscripten/*.h" + ) set(HAVE_SDL_JOYSTICK TRUE) endif() if(SDL_POWER) set(SDL_POWER_EMSCRIPTEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/power/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/power/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/power/emscripten/*.h" + ) set(HAVE_SDL_POWER TRUE) endif() - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/locale/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/locale/emscripten/*.h" + ) set(HAVE_SDL_LOCALE TRUE) set(SDL_TIME_UNIX 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/time/unix/*.c" + "${SDL3_SOURCE_DIR}/src/time/unix/*.h" + ) set(HAVE_SDL_TIME TRUE) set(SDL_TIMER_UNIX 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/unix/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/timer/unix/*.c" + "${SDL3_SOURCE_DIR}/src/timer/unix/*.h" + ) set(HAVE_SDL_TIMERS TRUE) if(SDL_CLOCK_GETTIME) set(HAVE_CLOCK_GETTIME 1) endif() + if(SDL_SENSOR) + set(SDL_SENSOR_EMSCRIPTEN 1) + set(HAVE_SDL_SENSORS TRUE) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/sensor/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/sensor/emscripten/*.h" + ) + endif() + if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_EMSCRIPTEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/emscripten/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/emscripten/*.c" + "${SDL3_SOURCE_DIR}/src/video/emscripten/*.h" + ) set(HAVE_SDL_VIDEO TRUE) #enable gles @@ -1578,14 +1772,41 @@ elseif(EMSCRIPTEN) CheckLibUnwind() elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) + + set(SDL_DISABLE_DLOPEN_NOTES TRUE) + if(SDL_DLOPEN_NOTES) + set(CHECK_ELF_DLNOTES_SRC [==[ + #ifndef __ELF__ + ELF DL notes is only supported on ELF platforms + #endif + __attribute__ ((used,aligned(4),section(".note.dlopen"))) static const struct { + struct { int a; int b; int c; } hdr; char name[4]; __attribute__((aligned(4))) char json[24]; + } dlnote = { { 4, 0x407c0c0aU, 16 }, "FDO", "[\\"a\\":{\\"a\\":\\"1\\",\\"b\\":\\"2\\"}]" }; + int main(int argc, char *argv[]) { + return argc + dlnote.hdr.a; + } + ]==]) + check_c_source_compiles("${CHECK_ELF_DLNOTES_SRC}" COMPILER_SUPPORTS_ELFNOTES) + if(COMPILER_SUPPORTS_ELFNOTES) + set(SDL_DISABLE_DLOPEN_NOTES FALSE) + set(HAVE_DLOPEN_NOTES TRUE) + endif() + endif() + if(SDL_AUDIO) if(NETBSD) set(SDL_AUDIO_DRIVER_NETBSD 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/netbsd/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/netbsd/*.c" + "${SDL3_SOURCE_DIR}/src/audio/netbsd/*.h" + ) set(HAVE_SDL_AUDIO TRUE) elseif(QNX) set(SDL_AUDIO_DRIVER_QNX 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/qnx/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/qnx/*.c" + "${SDL3_SOURCE_DIR}/src/audio/qnx/*.h" + ) sdl_link_dependency(asound LIBS asound) set(HAVE_SDL_AUDIO TRUE) endif() @@ -1603,6 +1824,8 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) # Need to check for ROCKCHIP platform and get rid of "Can't window GBM/EGL surfaces on window creation." CheckROCKCHIP() CheckX11() + CheckFribidi() + CheckLibThai() # Need to check for EGL first because KMSDRM and Wayland depend on it. CheckEGL() CheckKMSDRM() @@ -1614,13 +1837,21 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) CheckVivante() CheckVulkan() CheckQNXScreen() + endif() - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/unix/*.c") + if(SDL_TRAY) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/tray/unix/*.c" + "${SDL3_SOURCE_DIR}/src/tray/unix/*.h" + ) set(HAVE_SDL_TRAY TRUE) endif() if(UNIX) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/unix/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/core/unix/*.c" + "${SDL3_SOURCE_DIR}/src/core/unix/*.h" + ) check_c_source_compiles(" #include @@ -1669,7 +1900,10 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) if(SDL_CAMERA AND HAVE_LINUX_VIDEODEV2_H) set(SDL_CAMERA_DRIVER_V4L2 1) set(HAVE_CAMERA TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/v4l2/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/camera/v4l2/*.c" + "${SDL3_SOURCE_DIR}/src/camera/v4l2/*.h" + ) endif() if(HAVE_LINUX_INPUT_H) @@ -1678,7 +1912,10 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) if(SDL_HAPTIC AND HAVE_LINUX_INPUT_H) set(SDL_HAPTIC_LINUX 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/linux/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/haptic/linux/*.c" + "${SDL3_SOURCE_DIR}/src/haptic/linux/*.h" + ) set(HAVE_SDL_HAPTIC TRUE) endif() @@ -1738,7 +1975,6 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) set(HAVE_LIBURING TRUE) endif() endif() - if((FREEBSD OR NETBSD) AND NOT HAVE_INOTIFY) set(LibInotify_PKG_CONFIG_SPEC libinotify) pkg_check_modules(PC_LIBINOTIFY IMPORTED_TARGET ${LibInotify_PKG_CONFIG_SPEC}) @@ -1754,25 +1990,40 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) if(HAVE_DBUS_DBUS_H) sdl_sources( "${SDL3_SOURCE_DIR}/src/core/linux/SDL_dbus.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_dbus.h" "${SDL3_SOURCE_DIR}/src/core/linux/SDL_system_theme.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_system_theme.h" "${SDL3_SOURCE_DIR}/src/core/linux/SDL_progressbar.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_progressbar.h" ) endif() if(SDL_USE_IME) - sdl_sources("${SDL3_SOURCE_DIR}/src/core/linux/SDL_ime.c") + sdl_sources( + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_ime.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_ime.h" + ) endif() if(HAVE_IBUS_IBUS_H) - sdl_sources("${SDL3_SOURCE_DIR}/src/core/linux/SDL_ibus.c") + sdl_sources( + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_ibus.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_ibus.h" + ) endif() if(HAVE_FCITX) - sdl_sources("${SDL3_SOURCE_DIR}/src/core/linux/SDL_fcitx.c") + sdl_sources( + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_fcitx.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_fcitx.h" + ) endif() if(HAVE_LIBUDEV_H) - sdl_sources("${SDL3_SOURCE_DIR}/src/core/linux/SDL_udev.c") + sdl_sources( + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_udev.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_udev.h" + ) endif() if(HAVE_LINUX_INPUT_H) @@ -1783,26 +2034,20 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) endif() if(HAVE_INPUT_KBIO) - sdl_sources("${SDL3_SOURCE_DIR}/src/core/freebsd/SDL_evdev_kbd_freebsd.c") + sdl_sources( + "${SDL3_SOURCE_DIR}/src/core/freebsd/SDL_evdev_kbd_freebsd.c" + "${SDL3_SOURCE_DIR}/src/core/freebsd/SDL_evdev_kbd_default_keyaccmap.h" + ) endif() if(HAVE_INPUT_WSCONS) sdl_sources( "${SDL3_SOURCE_DIR}/src/core/openbsd/SDL_wscons_kbd.c" "${SDL3_SOURCE_DIR}/src/core/openbsd/SDL_wscons_mouse.c" + "${SDL3_SOURCE_DIR}/src/core/openbsd/SDL_wscons.h" ) endif() - if(SDL_GPU AND SDL_GPU_DXVK) - if(PKG_CONFIG_FOUND) - pkg_search_module(DXVK_NATIVE dxvk-dxgi) - if(DXVK_NATIVE_FOUND) - set(HAVE_D3D11_H TRUE) - sdl_include_directories(PRIVATE SYSTEM ${DXVK_NATIVE_INCLUDE_DIRS}) - endif() - endif() - endif() - if(HAVE_LIBURING_H) sdl_sources("${SDL3_SOURCE_DIR}/src/io/io_uring/SDL_asyncio_liburing.c") endif() @@ -1810,6 +2055,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) # Always compiled for Linux, unconditionally: sdl_sources( "${SDL3_SOURCE_DIR}/src/core/linux/SDL_evdev_capabilities.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_evdev_capabilities.h" "${SDL3_SOURCE_DIR}/src/core/linux/SDL_threadprio.c" ) @@ -1828,6 +2074,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) set(SDL_JOYSTICK_LINUX 1) sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/joystick/linux/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/linux/*.h" ) set(HAVE_SDL_JOYSTICK TRUE) endif() @@ -1870,7 +2117,10 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/generic/*.c") if(LINUX) set(SDL_STORAGE_STEAM 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/steam/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/storage/steam/*.c" + "${SDL3_SOURCE_DIR}/src/storage/steam/*.h" + ) endif() set(HAVE_SDL_STORAGE 1) @@ -1918,8 +2168,11 @@ elseif(WINDOWS) #include int main(int argc, char **argv) { return 0; }" HAVE_WIN32_CC) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/windows/*.c") - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/windows/*.cpp") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/core/windows/*.c" + "${SDL3_SOURCE_DIR}/src/core/windows/*.cpp" + "${SDL3_SOURCE_DIR}/src/core/windows/*.h" + ) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/windows/*.c") sdl_glob_sources("${SDL3_SOURCE_DIR}/src/io/windows/*.c") @@ -1929,7 +2182,7 @@ elseif(WINDOWS) target_compile_options(SDL_uclibc PRIVATE $<$:/GS-> $<$:/Gs1048576>) if(SDL_CPU_X86) target_compile_options(SDL3-shared PRIVATE "/arch:SSE") - target_compile_options(SDL3-SDL_uclibc PRIVATE "/arch:SSE") + target_compile_options(SDL_uclibc PRIVATE "/arch:SSE") endif() endif() @@ -1968,7 +2221,7 @@ elseif(WINDOWS) else() set(PROCESSOR_ARCH "x86") endif() - sdl_link_directories("$") + sdl_link_directories("$") sdl_include_directories(PRIVATE SYSTEM "$") endif() endif() @@ -1979,7 +2232,8 @@ elseif(WINDOWS) check_c_source_compiles(" #include #include - int main(int argc, char **argv) { return 0; }" HAVE_XINPUT_H) + int main(int argc, char **argv) { return 0; }" HAVE_XINPUT_H + ) endif() # headers needed elsewhere @@ -1995,6 +2249,7 @@ elseif(WINDOWS) #include int main(int argc, char **argv) { return 0; }" HAVE_GAMEINPUT_H ) + check_include_file(dxgi1_5.h HAVE_DXGI1_5_H) check_include_file(dxgi1_6.h HAVE_DXGI1_6_H) check_include_file(tpcshrd.h HAVE_TPCSHRD_H) check_include_file(roapi.h HAVE_ROAPI_H) @@ -2002,12 +2257,12 @@ elseif(WINDOWS) check_include_file(audioclient.h HAVE_AUDIOCLIENT_H) check_include_file(sensorsapi.h HAVE_SENSORSAPI_H) check_include_file(shellscalingapi.h HAVE_SHELLSCALINGAPI_H) - check_include_file(shobjidl_core.h HAVE_SHOBJIDL_CORE_H) check_c_source_compiles(" #include #include #include #include + static MFVideoPrimaries primaries = MFVideoPrimaries_DCI_P3; int main(int argc, char **argv) { return 0; } " HAVE_MFAPI_H ) @@ -2015,22 +2270,31 @@ elseif(WINDOWS) if(SDL_AUDIO) if(HAVE_DSOUND_H) set(SDL_AUDIO_DRIVER_DSOUND 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/directsound/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/directsound/*.c" + "${SDL3_SOURCE_DIR}/src/audio/directsound/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() if(SDL_WASAPI AND HAVE_AUDIOCLIENT_H AND HAVE_MMDEVICEAPI_H) set(SDL_AUDIO_DRIVER_WASAPI 1) set(HAVE_WASAPI TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/wasapi/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/wasapi/*.c" + "${SDL3_SOURCE_DIR}/src/audio/wasapi/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() endif() if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_WINDOWS 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/windows/*.c") - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/windows/*.cpp") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/windows/*.c" + "${SDL3_SOURCE_DIR}/src/video/windows/*.cpp" + "${SDL3_SOURCE_DIR}/src/video/windows/*.h" + ) CheckOpenVR() @@ -2042,7 +2306,7 @@ elseif(WINDOWS) set(SDL_VIDEO_RENDER_D3D11 1) set(HAVE_RENDER_D3D11 TRUE) endif() - if(SDL_RENDER_D3D12) + if(SDL_RENDER_D3D12 AND HAVE_DXGI1_6_H) set(SDL_VIDEO_RENDER_D3D12 1) set(HAVE_RENDER_D3D12 TRUE) endif() @@ -2054,7 +2318,9 @@ elseif(WINDOWS) set(SDL_THREAD_WINDOWS 1) sdl_sources( "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock_c.h" "${SDL3_SOURCE_DIR}/src/thread/windows/SDL_syscond_cv.c" "${SDL3_SOURCE_DIR}/src/thread/windows/SDL_sysmutex.c" "${SDL3_SOURCE_DIR}/src/thread/windows/SDL_sysrwlock_srw.c" @@ -2068,7 +2334,10 @@ elseif(WINDOWS) if(SDL_SENSOR AND HAVE_SENSORSAPI_H) set(SDL_SENSOR_WINDOWS 1) set(HAVE_SDL_SENSORS TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/sensor/windows/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/sensor/windows/*.c" + "${SDL3_SOURCE_DIR}/src/sensor/windows/*.h" + ) endif() if(SDL_POWER) @@ -2090,7 +2359,10 @@ elseif(WINDOWS) set(SDL_STORAGE_GENERIC 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/generic/*.c") set(SDL_STORAGE_STEAM 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/steam/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/storage/steam/*.c" + "${SDL3_SOURCE_DIR}/src/storage/steam/*.h" + ) set(HAVE_SDL_STORAGE 1) # Libraries for Win32 native and MinGW @@ -2108,7 +2380,10 @@ elseif(WINDOWS) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/loadso/windows/*.c") set(HAVE_SDL_LOADSO TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/windows/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/core/windows/*.c" + "${SDL3_SOURCE_DIR}/src/core/windows/*.h" + ) if(SDL_VIDEO) if(SDL_OPENGL) @@ -2133,7 +2408,9 @@ elseif(WINDOWS) set(HAVE_RENDER_VULKAN TRUE) endif() endif() + endif() + if(SDL_TRAY) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/windows/*.c") set(HAVE_SDL_TRAY TRUE) endif() @@ -2143,7 +2420,10 @@ elseif(WINDOWS) endif() if(SDL_JOYSTICK) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/windows/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/windows/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/windows/*.h" + ) set(SDL_JOYSTICK_RAWINPUT 1) if(HAVE_DINPUT_H) @@ -2165,7 +2445,10 @@ elseif(WINDOWS) if(SDL_HAPTIC) if(HAVE_DINPUT_H) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/windows/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/haptic/windows/*.c" + "${SDL3_SOURCE_DIR}/src/haptic/windows/*.h" + ) set(SDL_HAPTIC_DINPUT 1) set(HAVE_SDL_HAPTIC TRUE) endif() @@ -2227,7 +2510,10 @@ elseif(APPLE) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_COREAUDIO 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/coreaudio/*.m") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/coreaudio/*.m" + "${SDL3_SOURCE_DIR}/src/audio/coreaudio/*.h" + ) set(HAVE_SDL_AUDIO TRUE) set(SDL_FRAMEWORK_COREAUDIO 1) set(SDL_FRAMEWORK_AUDIOTOOLBOX 1) @@ -2239,7 +2525,10 @@ elseif(APPLE) endif() if(SDL_JOYSTICK) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/apple/*.m") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/apple/*.m" + "${SDL3_SOURCE_DIR}/src/joystick/apple/*.h" + ) if(IOS OR TVOS OR VISIONOS OR WATCHOS) set(SDL_JOYSTICK_MFI 1) if(IOS OR VISIONOS OR WATCHOS) @@ -2248,7 +2537,10 @@ elseif(APPLE) set(SDL_FRAMEWORK_GAMECONTROLLER 1) set(SDL_FRAMEWORK_COREHAPTICS 1) else() - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/darwin/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/darwin/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/darwin/*.h" + ) set_property(SOURCE ${MFI_JOYSTICK_SOURCES} APPEND_STRING PROPERTY COMPILE_FLAGS " -fobjc-weak") check_objc_source_compiles(" #include @@ -2288,7 +2580,10 @@ elseif(APPLE) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/dummy/*.c") set(SDL_HAPTIC_DUMMY 1) else() - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/darwin/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/haptic/darwin/*.c" + "${SDL3_SOURCE_DIR}/src/haptic/darwin/*.h" + ) set(SDL_HAPTIC_IOKIT 1) set(SDL_FRAMEWORK_IOKIT 1) set(SDL_FRAMEWORK_FF 1) @@ -2298,7 +2593,10 @@ elseif(APPLE) if(SDL_POWER) if (IOS OR TVOS OR VISIONOS OR WATCHOS) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/power/uikit/*.m") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/power/uikit/*.m" + "${SDL3_SOURCE_DIR}/src/power/uikit/*.h" + ) set(SDL_POWER_UIKIT 1) else() sdl_glob_sources("${SDL3_SOURCE_DIR}/src/power/macos/*.c") @@ -2328,7 +2626,10 @@ elseif(APPLE) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/generic/*.c") if(MACOS) set(SDL_STORAGE_STEAM 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/steam/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/storage/steam/*.c" + "${SDL3_SOURCE_DIR}/src/storage/steam/*.h" + ) endif() set(HAVE_SDL_STORAGE 1) @@ -2340,7 +2641,10 @@ elseif(APPLE) if(IOS OR VISIONOS OR WATCHOS) set(SDL_SENSOR_COREMOTION 1) set(HAVE_SDL_SENSORS TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/sensor/coremotion/*.m") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/sensor/coremotion/*.m" + "${SDL3_SOURCE_DIR}/src/sensor/coremotion/*.h" + ) endif() endif() @@ -2353,7 +2657,11 @@ elseif(APPLE) set(SDL_FRAMEWORK_UIKIT 1) set(SDL_IPHONE_KEYBOARD 1) set(SDL_IPHONE_LAUNCHSCREEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/uikit/*.m") + set(SDL_FRAMEWORK_GAMECONTROLLER 1) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/uikit/*.m" + "${SDL3_SOURCE_DIR}/src/video/uikit/*.h" + ) set(HAVE_SDL_VIDEO TRUE) else() CheckCOCOA() @@ -2403,21 +2711,20 @@ elseif(APPLE) set(HAVE_METAL TRUE) endif() if(SDL_RENDER_METAL) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/render/metal/*.m") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/render/metal/*.m" + "${SDL3_SOURCE_DIR}/src/render/metal/*.h" + ) set(SDL_VIDEO_RENDER_METAL 1) set(HAVE_RENDER_METAL TRUE) endif() - if (SDL_GPU) - set(SDL_GPU_METAL 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/gpu/metal/*.m") - endif() endif() endif() + endif() - if(MACOS) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/cocoa/*.m") - set(HAVE_SDL_TRAY TRUE) - endif() + if(SDL_TRAY AND MACOS) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/cocoa/*.m") + set(HAVE_SDL_TRAY TRUE) endif() # Minimum version for $ @@ -2503,7 +2810,10 @@ elseif(HAIKU) enable_language(CXX) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_HAIKU 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/haiku/*.cc") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/haiku/*.cc" + "${SDL3_SOURCE_DIR}/src/audio/haiku/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() @@ -2518,7 +2828,10 @@ elseif(HAIKU) if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_HAIKU 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/haiku/*.cc") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/haiku/*.cc" + "${SDL3_SOURCE_DIR}/src/video/haiku/*.h" + ) set(HAVE_SDL_VIDEO TRUE) if(SDL_OPENGL) @@ -2556,7 +2869,10 @@ elseif(HAIKU) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/haiku/*.cc") set(HAVE_SDL_LOCALE TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/haiku/*.cc") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/core/haiku/*.cc" + "${SDL3_SOURCE_DIR}/src/core/haiku/*.h" + ) CheckPTHREAD() sdl_link_dependency(base LIBS root be media game device textencoding tracker) @@ -2567,7 +2883,10 @@ elseif(RISCOS) if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_RISCOS 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/riscos/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/riscos/*.c" + "${SDL3_SOURCE_DIR}/src/video/riscos/*.h" + ) set(HAVE_SDL_VIDEO TRUE) endif() @@ -2607,12 +2926,17 @@ elseif(VITA) set_property(SOURCE "${SDL3_SOURCE_DIR}/src/atomic/SDL_spinlock.c" APPEND_STRING PROPERTY COMPILE_FLAGS " -marm") endif() - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/vita/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/misc/vita/*.c" + ) set(HAVE_SDL_MISC TRUE) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_VITA 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/vita/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/vita/*.c" + "${SDL3_SOURCE_DIR}/src/audio/vita/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() @@ -2639,10 +2963,14 @@ elseif(VITA) set(SDL_THREAD_VITA 1) sdl_sources( "${SDL3_SOURCE_DIR}/src/thread/vita/SDL_sysmutex.c" + "${SDL3_SOURCE_DIR}/src/thread/vita/SDL_sysmutex_c.h" "${SDL3_SOURCE_DIR}/src/thread/vita/SDL_syssem.c" "${SDL3_SOURCE_DIR}/src/thread/vita/SDL_systhread.c" + "${SDL3_SOURCE_DIR}/src/thread/vita/SDL_systhread_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c" ) set(HAVE_SDL_THREADS TRUE) @@ -2661,7 +2989,10 @@ elseif(VITA) if(SDL_SENSOR) set(SDL_SENSOR_VITA 1) set(HAVE_SDL_SENSORS TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/sensor/vita/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/sensor/vita/*.c" + "${SDL3_SOURCE_DIR}/src/sensor/vita/*.h" + ) endif() if(SDL_CAMERA) @@ -2672,7 +3003,10 @@ elseif(VITA) if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_VITA 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/vita/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/vita/*.c" + "${SDL3_SOURCE_DIR}/src/video/vita/*.h" + ) set(HAVE_SDL_VIDEO TRUE) if(VIDEO_VITA_PIB) @@ -2761,7 +3095,10 @@ elseif(PSP) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_PSP 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/psp/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/psp/*.c" + "${SDL3_SOURCE_DIR}/src/audio/psp/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() @@ -2788,9 +3125,12 @@ elseif(PSP) set(SDL_THREAD_PSP 1) sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock_c.h" "${SDL3_SOURCE_DIR}/src/thread/psp/*.c" + "${SDL3_SOURCE_DIR}/src/thread/psp/*.h" ) set(HAVE_SDL_THREADS TRUE) @@ -2808,7 +3148,10 @@ elseif(PSP) if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_PSP 1) set(SDL_VIDEO_RENDER_PSP 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/psp/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/psp/*.c" + "${SDL3_SOURCE_DIR}/src/video/psp/*.h" + ) set(SDL_VIDEO_OPENGL 1) set(HAVE_SDL_VIDEO TRUE) endif() @@ -2832,7 +3175,10 @@ elseif(PS2) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_PS2 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/ps2/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/ps2/*.c" + "${SDL3_SOURCE_DIR}/src/audio/ps2/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() @@ -2853,10 +3199,13 @@ elseif(PS2) set(SDL_THREAD_PS2 1) sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysmutex.c" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c" "${SDL3_SOURCE_DIR}/src/thread/ps2/*.c" + "${SDL3_SOURCE_DIR}/src/thread/ps2/*.h" ) set(HAVE_SDL_THREADS TRUE) @@ -2885,13 +3234,17 @@ elseif(PS2) gskit dmakit ps2_drivers + atomic ) elseif(N3DS) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/n3ds/*.c") if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_N3DS 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/n3ds/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/n3ds/*.c" + "${SDL3_SOURCE_DIR}/src/audio/n3ds/*.h" + ) set(HAVE_SDL_AUDIO TRUE) endif() @@ -2914,11 +3267,16 @@ elseif(N3DS) endif() set(SDL_THREAD_N3DS 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/thread/n3ds/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/thread/n3ds/*.c" + "${SDL3_SOURCE_DIR}/src/thread/n3ds/*.h" + ) sdl_sources( "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond_c.h" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock_c.h" ) set(HAVE_SDL_THREADS TRUE) @@ -2942,29 +3300,45 @@ elseif(N3DS) if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_N3DS 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/n3ds/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/n3ds/*.c" + "${SDL3_SOURCE_DIR}/src/video/n3ds/*.h" + ) set(HAVE_SDL_VIDEO TRUE) endif() sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/n3ds/*.c") set(HAVE_SDL_LOCALE TRUE) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/io/n3ds/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/io/n3ds/*.c" + "${SDL3_SOURCE_DIR}/src/io/n3ds/*.h" + ) elseif(NGAGE) enable_language(CXX) set(SDL_MAIN_USE_CALLBACKS 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/ngage/*.c") - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/ngage/*.cpp") - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/ngage/*.cpp") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/main/ngage/*.c" + "${SDL3_SOURCE_DIR}/src/main/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/main/ngage/*.hpp" + ) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/core/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/core/ngage/*.h" + ) set(HAVE_SDL_MAIN_CALLBACKS TRUE) if(SDL_AUDIO) set(SDL_AUDIO_DRIVER_NGAGE 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/ngage/*.c") - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/ngage/*.cpp") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/ngage/*.c" + "${SDL3_SOURCE_DIR}/src/audio/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/audio/ngage/*.h" + "${SDL3_SOURCE_DIR}/src/audio/ngage/*.hpp" + ) set(HAVE_SDL_AUDIO TRUE) endif() @@ -2978,13 +3352,20 @@ elseif(NGAGE) if(SDL_RENDER) set(SDL_VIDEO_RENDER_NGAGE 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/render/ngage/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/render/ngage/*.c" + "${SDL3_SOURCE_DIR}/src/render/ngage/*.h" + "${SDL3_SOURCE_DIR}/src/render/ngage/*.hpp" + ) endif() sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/ngage/*.cpp") set(SDL_TIME_NGAGE 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/render/ngage/*.cpp") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/render/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/render/ngage/*.h" + ) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c") set(SDL_TIMER_NGAGE 1) @@ -2993,7 +3374,10 @@ elseif(NGAGE) set(SDL_FSOPS_POSIX 1) set(SDL_VIDEO_DRIVER_NGAGE 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/ngage/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/ngage/*.c" + "${SDL3_SOURCE_DIR}/src/video/ngage/*.h" + ) set(HAVE_SDL_TIMERS TRUE) set_option(SDL_LEAN_AND_MEAN "Enable lean and mean" ON) @@ -3036,7 +3420,9 @@ if (SDL_DIALOG) elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_unixdialog.c) sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_portaldialog.c) + sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_portaldialog.h) sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_zenitydialog.c) + sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_zenitydialog.h) set(HAVE_SDL_DIALOG TRUE) elseif(HAIKU) sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/haiku/SDL_haikudialog.cc) @@ -3049,13 +3435,20 @@ if (SDL_DIALOG) set(HAVE_SDL_DIALOG TRUE) endif() endif() +if(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) + sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_zenitymessagebox.h) + sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_zenitymessagebox.c) +endif() sdl_sources("${SDL3_SOURCE_DIR}/src/process/SDL_process.c") if(WINDOWS) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/process/windows/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/process/windows/*.c" + "${SDL3_SOURCE_DIR}/src/process/windows/*.h" + ) set(SDL_PROCESS_WINDOWS 1) set(HAVE_SDL_PROCESS TRUE) -else() +elseif(NOT ANDROID) check_c_source_compiles(" #include #include @@ -3092,8 +3485,14 @@ int main(void) return 0; } " HAVE_POSIX_SPAWN) - if(HAVE_POSIX_SPAWN) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/process/posix/*.c") + if(NOT APPLE) + check_symbol_exists(vfork "unistd.h" LIBC_HAS_VFORK) + endif() + if(HAVE_POSIX_SPAWN AND (APPLE OR LIBC_HAS_VFORK)) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/process/posix/*.c" + "${SDL3_SOURCE_DIR}/src/process/posix/*.h" + ) set(SDL_PROCESS_POSIX 1) set(HAVE_SDL_PROCESS TRUE) endif() @@ -3104,7 +3503,10 @@ endif() if(SDL_VIDEO) if(SDL_OFFSCREEN) set(SDL_VIDEO_DRIVER_OFFSCREEN 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/offscreen/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/offscreen/*.c" + "${SDL3_SOURCE_DIR}/src/video/offscreen/*.h" + ) set(HAVE_OFFSCREEN TRUE) set(HAVE_SDL_VIDEO TRUE) endif() @@ -3113,22 +3515,31 @@ endif() sdl_glob_sources(${SDL3_SOURCE_DIR}/src/tray/*.c) if(SDL_GPU) - if(HAVE_D3D11_H) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/gpu/d3d11/*.c") - set(SDL_GPU_D3D11 1) - set(HAVE_SDL_GPU TRUE) - endif() - if(WINDOWS) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/gpu/d3d12/*.c") + if(HAVE_DXGI1_6_H) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/gpu/d3d12/*.c" + "${SDL3_SOURCE_DIR}/src/gpu/d3d12/*.h" + ) set(SDL_GPU_D3D12 1) set(HAVE_SDL_GPU TRUE) endif() if(SDL_VIDEO_VULKAN) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/gpu/vulkan/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/gpu/vulkan/*.c" + "${SDL3_SOURCE_DIR}/src/gpu/vulkan/*.h" + ) set(SDL_GPU_VULKAN 1) set(HAVE_SDL_GPU TRUE) endif() - if(SDL_RENDER_GPU) + if(SDL_VIDEO_METAL) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/gpu/metal/*.m" + "${SDL3_SOURCE_DIR}/src/gpu/metal/*.h" + ) + set(SDL_GPU_METAL 1) + set(HAVE_SDL_GPU TRUE) + endif() + if(SDL_RENDER_GPU AND HAVE_SDL_GPU) set(SDL_VIDEO_RENDER_GPU 1) set(HAVE_RENDER_GPU TRUE) endif() @@ -3144,35 +3555,59 @@ endif() # src/X/*.c does not get included. if(NOT HAVE_SDL_AUDIO) set(SDL_AUDIO_DRIVER_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/audio/dummy/*.h" + ) endif() if(NOT HAVE_SDL_VIDEO) set(SDL_VIDEO_DRIVER_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/video/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/video/dummy/*.h" + ) endif() if(NOT HAVE_SDL_JOYSTICK) set(SDL_JOYSTICK_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/dummy/*.h" + ) endif() if(NOT HAVE_SDL_HAPTIC) set(SDL_HAPTIC_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/haptic/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/haptic/dummy/*.h" + ) endif() if(NOT HAVE_SDL_SENSORS) set(SDL_SENSOR_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/sensor/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/sensor/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/sensor/dummy/*.h" + ) endif() if(NOT HAVE_SDL_LOADSO) set(SDL_LOADSO_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/loadso/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/loadso/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/loadso/dummy/*.h" + ) endif() if(NOT HAVE_SDL_FILESYSTEM) set(SDL_FILESYSTEM_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/filesystem/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/filesystem/dummy/*.h" + ) endif() if(NOT HAVE_SDL_STORAGE) set(SDL_STORAGE_GENERIC 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/generic/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/storage/generic/*.c" + "${SDL3_SOURCE_DIR}/src/storage/generic/*.h" + ) endif() if(NOT HAVE_SDL_FSOPS) set(SDL_FSOPS_DUMMY 1) @@ -3180,11 +3615,17 @@ if(NOT HAVE_SDL_FSOPS) endif() if(NOT HAVE_SDL_LOCALE) set(SDL_LOCALE_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/locale/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/locale/dummy/*.h" + ) endif() if(NOT HAVE_SDL_MISC) set(SDL_MISC_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/misc/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/misc/dummy/*.h" + ) endif() if(NOT HAVE_SDL_DIALOG) set(SDL_DIALOG_DUMMY 1) @@ -3200,7 +3641,10 @@ if(NOT HAVE_SDL_TRAY) endif() if(NOT HAVE_CAMERA) set(SDL_CAMERA_DRIVER_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/dummy/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/camera/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/camera/dummy/*.h" + ) endif() # We always need to have threads and timers around @@ -3208,7 +3652,10 @@ if(NOT HAVE_SDL_THREADS) # The Emscripten and N-Gage platform has been carefully vetted to work without threads if(EMSCRIPTEN OR NGAGE) set(SDL_THREADS_DISABLED 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/thread/generic/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/thread/generic/*.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/*.h" + ) else() message(FATAL_ERROR "Threads are needed by many SDL subsystems and may not be disabled") endif() @@ -3219,7 +3666,10 @@ endif() # Most platforms use this. if(NOT HAVE_SDL_MAIN_CALLBACKS) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/generic/*.c") + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/main/generic/*.c" + "${SDL3_SOURCE_DIR}/src/main/generic/*.h" + ) endif() # config variables may contain generator expression, so we need to generate SDL_build_config.h in 2 steps: @@ -3245,14 +3695,15 @@ endforeach() if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/REVISION.txt") file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/REVISION.txt" revisions) list(GET revisions 0 revisions_0) - string(STRIP "${revisions_0}" SDL_REVISION) + string(STRIP "${revisions_0}" revisions_0_stripped) + set(SDL_REVISION "SDL-${revisions_0_stripped}") else() set(SDL_REVISION "" CACHE STRING "Custom SDL revision (only used when REVISION.txt does not exist)") endif() if(NOT SDL_REVISION) # If SDL_REVISION is not overrided, use git to describe git_describe(SDL_REVISION_GIT) - set(SDL_REVISION "SDL3-${SDL3_VERSION}-${SDL_REVISION_GIT}") + set(SDL_REVISION "SDL-${SDL3_VERSION}-${SDL_REVISION_GIT}") endif() execute_process(COMMAND "${CMAKE_COMMAND}" -E make_directory "${SDL3_BINARY_DIR}/include-revision/SDL3") @@ -3262,9 +3713,10 @@ list(APPEND SDL3_INCLUDE_FILES "${SDL3_BINARY_DIR}/include-revision/SDL3/SDL_rev if(SDL_FRAMEWORK) # With Apple frameworks, headers in the PUBLIC_HEADER property also need to be added as sources list(APPEND SDL3_INCLUDE_FILES ${SDL3_TEST_INCLUDE_FILES}) - sdl_sources(${SDL3_INCLUDE_FILES}) endif() +sdl_sources(${SDL3_INCLUDE_FILES}) + if((CMAKE_STATIC_LIBRARY_PREFIX STREQUAL "" AND CMAKE_STATIC_LIBRARY_SUFFIX STREQUAL ".lib") OR SDL_FRAMEWORK) # - Avoid conflict between the dll import library and the static library # - Create SDL3-static Apple Framework @@ -3388,10 +3840,6 @@ else() set(ARCH_64 FALSE) endif() -if(ANDROID) - sdl_include_directories(PRIVATE SYSTEM "${CMAKE_ANDROID_NDK}/sources/android/cpufeatures") -endif() - if(APPLE) cmake_push_check_state(RESET) check_c_compiler_flag(-fobjc-arc COMPILER_SUPPORTS_FOBJC_ARC) @@ -3544,6 +3992,7 @@ if(SDL_SHARED) ) endif() endif() + target_link_libraries(SDL3-shared PRIVATE ${SDL_CMAKE_DEPENDS}) target_include_directories(SDL3-shared PRIVATE @@ -3561,6 +4010,8 @@ if(SDL_SHARED) set_property(TARGET SDL3-shared PROPERTY INTERFACE_SDL3_SHARED TRUE) set_property(TARGET SDL3-shared APPEND PROPERTY COMPATIBLE_INTERFACE_STRING "SDL_VERSION") set_property(TARGET SDL3-shared PROPERTY INTERFACE_SDL_VERSION "SDL${SDL3_VERSION_MAJOR}") + set_property(TARGET SDL3-shared APPEND PROPERTY EXPORT_PROPERTIES "SDL_FULL_VERSION") + set_property(TARGET SDL3-shared PROPERTY SDL_FULL_VERSION "${PROJECT_VERSION}") if(NOT CMAKE_VERSION VERSION_LESS "3.16") target_precompile_headers(SDL3-shared PRIVATE "$<$,$>:${PROJECT_SOURCE_DIR}/src/SDL_internal.h>") endif() @@ -3585,6 +4036,8 @@ if(SDL_STATIC) set_property(TARGET SDL3-static PROPERTY INTERFACE_SDL3_SHARED FALSE) set_property(TARGET SDL3-static APPEND PROPERTY COMPATIBLE_INTERFACE_STRING "SDL_VERSION") set_property(TARGET SDL3-static PROPERTY INTERFACE_SDL_VERSION "SDL${SDL3_VERSION_MAJOR}") + set_property(TARGET SDL3-static APPEND PROPERTY EXPORT_PROPERTIES "SDL_FULL_VERSION") + set_property(TARGET SDL3-static PROPERTY SDL_FULL_VERSION "${PROJECT_VERSION}") if(NOT CMAKE_VERSION VERSION_LESS "3.16") target_precompile_headers(SDL3-static PRIVATE "$<$,$>:${PROJECT_SOURCE_DIR}/src/SDL_internal.h>") endif() @@ -3600,7 +4053,7 @@ sdl_compile_definitions( ##### Tests ##### if(SDL_TEST_LIBRARY) - file(GLOB TEST_SOURCES "${SDL3_SOURCE_DIR}/src/test/*.c") + file(GLOB TEST_SOURCES "${SDL3_SOURCE_DIR}/src/test/*.c" "${SDL3_SOURCE_DIR}/src/test/*.h") target_sources(SDL3_test PRIVATE ${TEST_SOURCES}) if(APPLE) set_target_properties(SDL3_test PROPERTIES @@ -3872,6 +4325,7 @@ endif() ##### Uninstall target ##### if(SDL_UNINSTALL) + set(HAVE_UNINSTALL ON) if(NOT TARGET uninstall) configure_file(cmake/cmake_uninstall.cmake.in cmake_uninstall.cmake IMMEDIATE @ONLY) diff --git a/libs/SDL3/LICENSE.txt b/libs/SDL3/LICENSE.txt index 23abb73f..e9adee44 100644 --- a/libs/SDL3/LICENSE.txt +++ b/libs/SDL3/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (C) 1997-2025 Sam Lantinga +Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/REVISION.txt b/libs/SDL3/REVISION.txt new file mode 100644 index 00000000..e6fa3a4b --- /dev/null +++ b/libs/SDL3/REVISION.txt @@ -0,0 +1 @@ +release-3.4.10-0-g8e37db5e7 diff --git a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj index 58194e72..a1356ab1 100644 --- a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj +++ b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj @@ -326,23 +326,25 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) - - - + + + + + + @@ -352,9 +354,11 @@ - - + + + + @@ -362,11 +366,13 @@ + + @@ -385,7 +391,6 @@ - @@ -407,7 +412,7 @@ - + @@ -424,6 +429,7 @@ + @@ -463,6 +469,7 @@ + @@ -498,7 +505,6 @@ - @@ -598,9 +604,11 @@ + + @@ -723,16 +731,21 @@ + + + + + @@ -764,6 +777,7 @@ + @@ -789,7 +803,6 @@ - @@ -799,7 +812,6 @@ - @@ -881,6 +893,7 @@ + @@ -888,7 +901,6 @@ - diff --git a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters index 8a988ace..a2f431ab 100644 --- a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -74,16 +74,21 @@ + + + + + @@ -97,6 +102,7 @@ + @@ -114,7 +120,6 @@ - @@ -124,7 +129,6 @@ - @@ -183,6 +187,7 @@ + @@ -190,7 +195,6 @@ - @@ -235,17 +239,21 @@ - - + + + + + + @@ -255,8 +263,11 @@ - + + + + @@ -264,11 +275,13 @@ + + @@ -287,10 +300,10 @@ - + @@ -305,9 +318,10 @@ + - + @@ -328,6 +342,7 @@ + @@ -358,6 +373,7 @@ + @@ -393,7 +409,6 @@ - @@ -447,9 +462,11 @@ + + diff --git a/libs/SDL3/VisualC-GDK/SDL_test/SDL_test.vcxproj b/libs/SDL3/VisualC-GDK/SDL_test/SDL_test.vcxproj index 8d5106cb..84a29b54 100644 --- a/libs/SDL3/VisualC-GDK/SDL_test/SDL_test.vcxproj +++ b/libs/SDL3/VisualC-GDK/SDL_test/SDL_test.vcxproj @@ -192,10 +192,12 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) + + + diff --git a/libs/SDL3/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj b/libs/SDL3/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj index f600ce20..5f89c242 100644 --- a/libs/SDL3/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj +++ b/libs/SDL3/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj @@ -268,7 +268,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC-GDK/tests/testgdk/src/testgdk.cpp b/libs/SDL3/VisualC-GDK/tests/testgdk/src/testgdk.cpp index 20bc03ff..53e203d9 100644 --- a/libs/SDL3/VisualC-GDK/tests/testgdk/src/testgdk.cpp +++ b/libs/SDL3/VisualC-GDK/tests/testgdk/src/testgdk.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -291,9 +291,8 @@ static void DrawSprites(SDL_Renderer * renderer, SDL_Texture * sprite) SDL_RenderPresent(renderer); } -static void loop() +static void update() { - int i; SDL_Event event; /* Check for events */ @@ -310,20 +309,39 @@ static void loop() SDLTest_CommonEvent(state, &event, &done); #endif } + fillerup(); +} + +static void draw() +{ + int i; for (i = 0; i < state->num_windows; ++i) { if (state->windows[i] == NULL) { continue; } DrawSprites(state->renderers[i], sprites[i]); } - fillerup(); +} + +static bool SDLCALL GDKEventWatch(void* userdata, SDL_Event* event) +{ + bool *suppressdraw = (bool *)userdata; + SDL_assert(suppressdraw != NULL); + if (event->type == SDL_EVENT_DID_ENTER_BACKGROUND) { + *suppressdraw = true; + SDL_GDKSuspendComplete(); + } else if (event->type == SDL_EVENT_WILL_ENTER_FOREGROUND) { + *suppressdraw = false; + } + return true; } int main(int argc, char *argv[]) { int i; - const char *icon = "icon.bmp"; + const char *icon = "icon.png"; char *soundname = NULL; + bool suppressdraw = false; /* Initialize parameters */ num_sprites = NUM_SPRITES; @@ -390,6 +408,9 @@ int main(int argc, char *argv[]) quit(2); } + /* By this point the renderers are made, so we can now add this watcher */ + SDL_AddEventWatch(GDKEventWatch, &suppressdraw); + /* Create the windows, initialize the renderers, and load the textures */ sprites = (SDL_Texture **) SDL_malloc(state->num_windows * sizeof(*sprites)); @@ -441,7 +462,10 @@ int main(int argc, char *argv[]) AddUserSilent(); while (!done) { - loop(); + update(); + if (!suppressdraw) { + draw(); + } } quit(0); diff --git a/libs/SDL3/VisualC-GDK/tests/testgdk/testgdk.vcxproj b/libs/SDL3/VisualC-GDK/tests/testgdk/testgdk.vcxproj index c16fadef..4ffc370c 100644 --- a/libs/SDL3/VisualC-GDK/tests/testgdk/testgdk.vcxproj +++ b/libs/SDL3/VisualC-GDK/tests/testgdk/testgdk.vcxproj @@ -292,7 +292,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) @@ -311,7 +310,7 @@ - + Copying %(Filename)%(Extension) Copying %(Filename)%(Extension) Copying %(Filename)%(Extension) diff --git a/libs/SDL3/VisualC-GDK/tests/testsprite/testsprite.vcxproj b/libs/SDL3/VisualC-GDK/tests/testsprite/testsprite.vcxproj index 15ea690a..65422be4 100644 --- a/libs/SDL3/VisualC-GDK/tests/testsprite/testsprite.vcxproj +++ b/libs/SDL3/VisualC-GDK/tests/testsprite/testsprite.vcxproj @@ -292,7 +292,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) @@ -311,7 +310,7 @@ - + Copying %(Filename)%(Extension) Copying %(Filename)%(Extension) Copying %(Filename)%(Extension) diff --git a/libs/SDL3/VisualC/SDL.sln b/libs/SDL3/VisualC/SDL.sln index b5cf786a..d6e89404 100644 --- a/libs/SDL3/VisualC/SDL.sln +++ b/libs/SDL3/VisualC/SDL.sln @@ -131,6 +131,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "01-joystick-polling", "exam EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "02-joystick-events", "examples\input\02-joystick-events\02-joystick-events.vcxproj", "{FCBDF2B2-1129-49AE-9406-3F219E65CA89}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testsoftwaretransparent", "tests\testsoftwaretransparent\testsoftwaretransparent.vcxproj", "{D91C45E2-274E-4C0F-89C7-9986F9A7E85A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -579,6 +581,14 @@ Global {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Release|Win32.Build.0 = Release|Win32 {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Release|x64.ActiveCfg = Release|x64 {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Release|x64.Build.0 = Release|x64 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Debug|Win32.ActiveCfg = Debug|Win32 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Debug|Win32.Build.0 = Debug|Win32 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Debug|x64.ActiveCfg = Debug|x64 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Debug|x64.Build.0 = Debug|x64 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Release|Win32.ActiveCfg = Release|Win32 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Release|Win32.Build.0 = Release|Win32 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Release|x64.ActiveCfg = Release|x64 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -644,6 +654,7 @@ Global {3DB9B219-769E-43AC-8B8B-319DB6045DCF} = {D1BF59F6-22DC-493B-BDEB-451A50DA793D} {B3852DB7-E925-4026-8B9D-D2272EFEFF3C} = {8DEAE483-FDE7-463F-9FD5-F597BBAED1F9} {FCBDF2B2-1129-49AE-9406-3F219E65CA89} = {8DEAE483-FDE7-463F-9FD5-F597BBAED1F9} + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A} = {D69D5741-611F-4E14-8541-1FEE94F50B5A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C320C9F2-1A8F-41D7-B02B-6338F872BCAD} diff --git a/libs/SDL3/VisualC/SDL/SDL.vcxproj b/libs/SDL3/VisualC/SDL/SDL.vcxproj index 843f8e61..2937791c 100644 --- a/libs/SDL3/VisualC/SDL/SDL.vcxproj +++ b/libs/SDL3/VisualC/SDL/SDL.vcxproj @@ -82,16 +82,16 @@ C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Lib\x86;$(LibraryPath) - $(ProjectDir)/../../src;$(IncludePath) + $(ProjectDir)/../../src;$(ProjectDir)/../../src/core/windows;$(IncludePath) - $(ProjectDir)/../../src;$(IncludePath) + $(ProjectDir)/../../src;$(ProjectDir)/../../src/core/windows;$(IncludePath) - $(ProjectDir)/../../src;$(IncludePath) + $(ProjectDir)/../../src;$(ProjectDir)/../../src/core/windows;$(IncludePath) - $(ProjectDir)/../../src;$(IncludePath) + $(ProjectDir)/../../src;$(ProjectDir)/../../src/core/windows;$(IncludePath) @@ -239,23 +239,25 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) - - - + + + + + + @@ -265,9 +267,11 @@ - - + + + + @@ -275,11 +279,13 @@ + + @@ -298,7 +304,6 @@ - @@ -320,6 +325,7 @@ + @@ -335,6 +341,7 @@ + @@ -376,6 +383,7 @@ + @@ -410,7 +418,6 @@ - @@ -506,9 +513,11 @@ + + @@ -613,16 +622,21 @@ + + + + + @@ -636,6 +650,7 @@ + @@ -655,7 +670,6 @@ - @@ -665,7 +679,6 @@ - @@ -736,6 +749,7 @@ + @@ -743,7 +757,6 @@ - diff --git a/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters b/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters index 375c175c..e1014918 100644 --- a/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters +++ b/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters @@ -222,42 +222,51 @@ - - API Headers - - - API Headers - - - API Headers - API Headers API Headers + + API Headers + API Headers API Headers + + API Headers + API Headers API Headers + + API Headers + API Headers + + API Headers + API Headers API Headers + + API Headers + + + API Headers + API Headers @@ -276,19 +285,28 @@ API Headers + + API Headers + API Headers API Headers - - API Headers - API Headers - + + API Headers + + + API Headers + + + API Headers + + API Headers @@ -312,15 +330,27 @@ API Headers + + API Headers + API Headers + + API Headers + + + API Headers + API Headers API Headers + + API Headers + API Headers @@ -375,9 +405,6 @@ API Headers - - API Headers - API Headers @@ -387,6 +414,9 @@ API Headers + + API Headers + API Headers @@ -423,15 +453,24 @@ API Headers + + API Headers + API Headers + + API Headers + API Headers API Headers + + API Headers + API Headers @@ -489,6 +528,9 @@ audio + + core + core\windows @@ -633,6 +675,9 @@ joystick\hidapi + + joystick\hidapi + joystick\windows @@ -651,6 +696,9 @@ video + + video + video @@ -684,12 +732,15 @@ video - + video video + + video + video\dummy @@ -840,9 +891,6 @@ render\software - - render\software - render\software @@ -1137,6 +1185,9 @@ loadso\windows + + misc + misc @@ -1215,6 +1266,9 @@ joystick\hidapi + + joystick\hidapi + joystick\hidapi @@ -1230,6 +1284,9 @@ joystick\hidapi + + joystick\hidapi + joystick\hidapi @@ -1245,9 +1302,15 @@ joystick\hidapi + + joystick\hidapi + joystick\hidapi + + joystick\hidapi + joystick\windows @@ -1323,6 +1386,9 @@ video + + video + video @@ -1353,9 +1419,6 @@ video\dummy - - video\windows - video\windows @@ -1482,9 +1545,6 @@ sensor\windows - - render - render @@ -1536,9 +1596,6 @@ render\software - - render\software - render\software @@ -1607,6 +1664,9 @@ + + joystick\hidapi + core\windows diff --git a/libs/SDL3/VisualC/SDL_test/SDL_test.vcxproj b/libs/SDL3/VisualC/SDL_test/SDL_test.vcxproj index 4313bbe1..6c159fb7 100644 --- a/libs/SDL3/VisualC/SDL_test/SDL_test.vcxproj +++ b/libs/SDL3/VisualC/SDL_test/SDL_test.vcxproj @@ -158,10 +158,12 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) + + + diff --git a/libs/SDL3/VisualC/examples/renderer/06-textures/06-textures.vcxproj b/libs/SDL3/VisualC/examples/renderer/06-textures/06-textures.vcxproj index d99b62c0..a2c43ff6 100644 --- a/libs/SDL3/VisualC/examples/renderer/06-textures/06-textures.vcxproj +++ b/libs/SDL3/VisualC/examples/renderer/06-textures/06-textures.vcxproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/08-rotating-textures/08-rotating-textures.vcxproj b/libs/SDL3/VisualC/examples/renderer/08-rotating-textures/08-rotating-textures.vcxproj index 5e14c6d0..f6a939c6 100644 --- a/libs/SDL3/VisualC/examples/renderer/08-rotating-textures/08-rotating-textures.vcxproj +++ b/libs/SDL3/VisualC/examples/renderer/08-rotating-textures/08-rotating-textures.vcxproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/10-geometry/10-geometry.vcxproj b/libs/SDL3/VisualC/examples/renderer/10-geometry/10-geometry.vcxproj index 6013514f..ba3fe991 100644 --- a/libs/SDL3/VisualC/examples/renderer/10-geometry/10-geometry.vcxproj +++ b/libs/SDL3/VisualC/examples/renderer/10-geometry/10-geometry.vcxproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/11-color-mods/11-color-mods.vcxproj b/libs/SDL3/VisualC/examples/renderer/11-color-mods/11-color-mods.vcxproj index c0af3e32..97b943c7 100644 --- a/libs/SDL3/VisualC/examples/renderer/11-color-mods/11-color-mods.vcxproj +++ b/libs/SDL3/VisualC/examples/renderer/11-color-mods/11-color-mods.vcxproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/14-viewport/14-viewport.vcxproj b/libs/SDL3/VisualC/examples/renderer/14-viewport/14-viewport.vcxproj index 1ebbda55..0e20fb8c 100644 --- a/libs/SDL3/VisualC/examples/renderer/14-viewport/14-viewport.vcxproj +++ b/libs/SDL3/VisualC/examples/renderer/14-viewport/14-viewport.vcxproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/15-cliprect/15-cliprect.vcxproj b/libs/SDL3/VisualC/examples/renderer/15-cliprect/15-cliprect.vcxproj index dbb7ca67..fb2a8fbb 100644 --- a/libs/SDL3/VisualC/examples/renderer/15-cliprect/15-cliprect.vcxproj +++ b/libs/SDL3/VisualC/examples/renderer/15-cliprect/15-cliprect.vcxproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/17-read-pixels/17-read-pixels.vcxproj b/libs/SDL3/VisualC/examples/renderer/17-read-pixels/17-read-pixels.vcxproj index 674462e8..68c6d0eb 100644 --- a/libs/SDL3/VisualC/examples/renderer/17-read-pixels/17-read-pixels.vcxproj +++ b/libs/SDL3/VisualC/examples/renderer/17-read-pixels/17-read-pixels.vcxproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/libs/SDL3/VisualC/tests/checkkeys/checkkeys.vcxproj b/libs/SDL3/VisualC/tests/checkkeys/checkkeys.vcxproj index 901cdbbd..b73120e7 100644 --- a/libs/SDL3/VisualC/tests/checkkeys/checkkeys.vcxproj +++ b/libs/SDL3/VisualC/tests/checkkeys/checkkeys.vcxproj @@ -189,7 +189,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/loopwave/loopwave.vcxproj b/libs/SDL3/VisualC/tests/loopwave/loopwave.vcxproj index 6ab91a82..271ac351 100644 --- a/libs/SDL3/VisualC/tests/loopwave/loopwave.vcxproj +++ b/libs/SDL3/VisualC/tests/loopwave/loopwave.vcxproj @@ -189,7 +189,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testatomic/testatomic.vcxproj b/libs/SDL3/VisualC/tests/testatomic/testatomic.vcxproj index abe71ff9..7123ec28 100644 --- a/libs/SDL3/VisualC/tests/testatomic/testatomic.vcxproj +++ b/libs/SDL3/VisualC/tests/testatomic/testatomic.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testautomation/testautomation.vcxproj b/libs/SDL3/VisualC/tests/testautomation/testautomation.vcxproj index d973c8dc..dbefb369 100644 --- a/libs/SDL3/VisualC/tests/testautomation/testautomation.vcxproj +++ b/libs/SDL3/VisualC/tests/testautomation/testautomation.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testcontroller/testcontroller.vcxproj b/libs/SDL3/VisualC/tests/testcontroller/testcontroller.vcxproj index fbcadfcb..9efbb6dc 100644 --- a/libs/SDL3/VisualC/tests/testcontroller/testcontroller.vcxproj +++ b/libs/SDL3/VisualC/tests/testcontroller/testcontroller.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testdialog/testdialog.vcxproj b/libs/SDL3/VisualC/tests/testdialog/testdialog.vcxproj index 29ae4469..81356ad3 100644 --- a/libs/SDL3/VisualC/tests/testdialog/testdialog.vcxproj +++ b/libs/SDL3/VisualC/tests/testdialog/testdialog.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testdraw/testdraw.vcxproj b/libs/SDL3/VisualC/tests/testdraw/testdraw.vcxproj index de8a31e2..ba957458 100644 --- a/libs/SDL3/VisualC/tests/testdraw/testdraw.vcxproj +++ b/libs/SDL3/VisualC/tests/testdraw/testdraw.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testfile/testfile.vcxproj b/libs/SDL3/VisualC/tests/testfile/testfile.vcxproj index dfcd4312..3fc59b0d 100644 --- a/libs/SDL3/VisualC/tests/testfile/testfile.vcxproj +++ b/libs/SDL3/VisualC/tests/testfile/testfile.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testgl/testgl.vcxproj b/libs/SDL3/VisualC/tests/testgl/testgl.vcxproj index 6c521252..088ce931 100644 --- a/libs/SDL3/VisualC/tests/testgl/testgl.vcxproj +++ b/libs/SDL3/VisualC/tests/testgl/testgl.vcxproj @@ -187,7 +187,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testgles2/testgles2.vcxproj b/libs/SDL3/VisualC/tests/testgles2/testgles2.vcxproj index 728bb2cc..194042f5 100644 --- a/libs/SDL3/VisualC/tests/testgles2/testgles2.vcxproj +++ b/libs/SDL3/VisualC/tests/testgles2/testgles2.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testoverlay/testoverlay.vcxproj b/libs/SDL3/VisualC/tests/testoverlay/testoverlay.vcxproj index 6d7e2ccb..112bf314 100644 --- a/libs/SDL3/VisualC/tests/testoverlay/testoverlay.vcxproj +++ b/libs/SDL3/VisualC/tests/testoverlay/testoverlay.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testpen/testpen.vcxproj b/libs/SDL3/VisualC/tests/testpen/testpen.vcxproj index 65888443..589e605b 100644 --- a/libs/SDL3/VisualC/tests/testpen/testpen.vcxproj +++ b/libs/SDL3/VisualC/tests/testpen/testpen.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testplatform/testplatform.vcxproj b/libs/SDL3/VisualC/tests/testplatform/testplatform.vcxproj index af8ae0cd..c0aba9f0 100644 --- a/libs/SDL3/VisualC/tests/testplatform/testplatform.vcxproj +++ b/libs/SDL3/VisualC/tests/testplatform/testplatform.vcxproj @@ -195,7 +195,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testpower/testpower.vcxproj b/libs/SDL3/VisualC/tests/testpower/testpower.vcxproj index cacf3621..89fc2ea9 100644 --- a/libs/SDL3/VisualC/tests/testpower/testpower.vcxproj +++ b/libs/SDL3/VisualC/tests/testpower/testpower.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testrendertarget/testrendertarget.vcxproj b/libs/SDL3/VisualC/tests/testrendertarget/testrendertarget.vcxproj index e59bb6ba..5b097c37 100644 --- a/libs/SDL3/VisualC/tests/testrendertarget/testrendertarget.vcxproj +++ b/libs/SDL3/VisualC/tests/testrendertarget/testrendertarget.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) @@ -202,7 +201,7 @@ - + Copying %(Filename)%(Extension) copy "%(FullPath)" "$(ProjectDir)\" @@ -220,7 +219,7 @@ $(ProjectDir)\%(Filename)%(Extension);%(Outputs) - + Copying %(Filename)%(Extension) copy "%(FullPath)" "$(ProjectDir)\" diff --git a/libs/SDL3/VisualC/tests/testrumble/testrumble.vcxproj b/libs/SDL3/VisualC/tests/testrumble/testrumble.vcxproj index 0ae100dc..78a666f5 100644 --- a/libs/SDL3/VisualC/tests/testrumble/testrumble.vcxproj +++ b/libs/SDL3/VisualC/tests/testrumble/testrumble.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testscale/testscale.vcxproj b/libs/SDL3/VisualC/tests/testscale/testscale.vcxproj index c8900f36..86f45ee6 100644 --- a/libs/SDL3/VisualC/tests/testscale/testscale.vcxproj +++ b/libs/SDL3/VisualC/tests/testscale/testscale.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) @@ -202,7 +201,7 @@ - + Copying %(Filename)%(Extension) copy "%(FullPath)" "$(ProjectDir)\" @@ -220,7 +219,7 @@ $(ProjectDir)\%(Filename)%(Extension);%(Outputs) - + Copying %(Filename)%(Extension) copy "%(FullPath)" "$(ProjectDir)\" diff --git a/libs/SDL3/VisualC/tests/testsensor/testsensor.vcxproj b/libs/SDL3/VisualC/tests/testsensor/testsensor.vcxproj index f0a9af8a..d7496384 100644 --- a/libs/SDL3/VisualC/tests/testsensor/testsensor.vcxproj +++ b/libs/SDL3/VisualC/tests/testsensor/testsensor.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testshape/testshape.vcxproj b/libs/SDL3/VisualC/tests/testshape/testshape.vcxproj index 14904837..97cb7bbb 100644 --- a/libs/SDL3/VisualC/tests/testshape/testshape.vcxproj +++ b/libs/SDL3/VisualC/tests/testshape/testshape.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testsoftwaretransparent/testsoftwaretransparent.vcxproj b/libs/SDL3/VisualC/tests/testsoftwaretransparent/testsoftwaretransparent.vcxproj new file mode 100644 index 00000000..1970a3b3 --- /dev/null +++ b/libs/SDL3/VisualC/tests/testsoftwaretransparent/testsoftwaretransparent.vcxproj @@ -0,0 +1,209 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A} + testsoftwaretransparent + 10.0 + + + + Application + $(DefaultPlatformToolset) + + + Application + $(DefaultPlatformToolset) + + + Application + $(DefaultPlatformToolset) + + + Application + $(DefaultPlatformToolset) + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/testsoftwaretransparent.tlb + + + %(AdditionalOptions) /utf-8 + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + Windows + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + .\Release/testsoftwaretransparent.tlb + + + %(AdditionalOptions) /utf-8 + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + Windows + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Debug/testsoftwaretransparent.tlb + + + %(AdditionalOptions) /utf-8 + Disabled + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + OldStyle + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + Windows + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + X64 + .\Debug/testsoftwaretransparent.tlb + + + %(AdditionalOptions) /utf-8 + Disabled + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + OldStyle + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + Windows + + + + + $(TreatWarningsAsError) + + + + + {81ce8daf-ebb2-4761-8e45-b71abcca8c68} + false + false + true + + + {da956fd3-e143-46f2-9fe5-c77bebc56b1a} + false + false + true + + + + + + + + + \ No newline at end of file diff --git a/libs/SDL3/VisualC/tests/testsprite/testsprite.vcxproj b/libs/SDL3/VisualC/tests/testsprite/testsprite.vcxproj index 46473ccc..65c58b1b 100644 --- a/libs/SDL3/VisualC/tests/testsprite/testsprite.vcxproj +++ b/libs/SDL3/VisualC/tests/testsprite/testsprite.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) @@ -202,7 +201,7 @@ - + Copying %(Filename)%(Extension) copy "%(FullPath)" "$(ProjectDir)\" diff --git a/libs/SDL3/VisualC/tests/testsurround/testsurround.vcxproj b/libs/SDL3/VisualC/tests/testsurround/testsurround.vcxproj index 13ded82f..9e1b9c4d 100644 --- a/libs/SDL3/VisualC/tests/testsurround/testsurround.vcxproj +++ b/libs/SDL3/VisualC/tests/testsurround/testsurround.vcxproj @@ -189,7 +189,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testvulkan/testvulkan.vcxproj b/libs/SDL3/VisualC/tests/testvulkan/testvulkan.vcxproj index 0e6645b4..f906fd0f 100644 --- a/libs/SDL3/VisualC/tests/testvulkan/testvulkan.vcxproj +++ b/libs/SDL3/VisualC/tests/testvulkan/testvulkan.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testwm/testwm.vcxproj b/libs/SDL3/VisualC/tests/testwm/testwm.vcxproj index 48786d34..9b74b039 100644 --- a/libs/SDL3/VisualC/tests/testwm/testwm.vcxproj +++ b/libs/SDL3/VisualC/tests/testwm/testwm.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/libs/SDL3/VisualC/tests/testyuv/testyuv.vcxproj b/libs/SDL3/VisualC/tests/testyuv/testyuv.vcxproj index 5adf0eee..82024b72 100644 --- a/libs/SDL3/VisualC/tests/testyuv/testyuv.vcxproj +++ b/libs/SDL3/VisualC/tests/testyuv/testyuv.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) @@ -202,7 +201,7 @@ - + Copying %(Filename)%(Extension) copy "%(FullPath)" "$(ProjectDir)\" diff --git a/libs/SDL3/WhatsNew.txt b/libs/SDL3/WhatsNew.txt index 0d10ee67..67a50be6 100644 --- a/libs/SDL3/WhatsNew.txt +++ b/libs/SDL3/WhatsNew.txt @@ -1,16 +1,134 @@ This is a list of major changes in SDL's version history. +--------------------------------------------------------------------------- +3.4.0: +--------------------------------------------------------------------------- + +General: +* Added SDL_CreateAnimatedCursor() to create animated color cursors +* Added SDL_HINT_MOUSE_DPI_SCALE_CURSORS to automatically scale cursors based on the display scale +* Added SDL_SetWindowProgressState(), SDL_SetWindowProgressValue(), SDL_GetWindowProgressState(), and SDL_GetWindowProgressValue() to show progress in the window's taskbar icon on Windows and Linux +* Added GPU device creation properties to enable the GPU API on older hardware if you're not using these features: + - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN + - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN + - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN + - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN + - SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN +* Added SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER to enable configuring Vulkan features when creating a GPU device +* Added SDL_PROP_GPU_DEVICE_CREATE_VULKAN_REQUIRE_HARDWARE_ACCELERATION_BOOLEAN to allow requiring Vulkan hardware acceleration when creating a GPU device +* Added SDL_GetGPUDeviceProperties() to query information from a GPU device: + - SDL_PROP_GPU_DEVICE_NAME_STRING + - SDL_PROP_GPU_DEVICE_DRIVER_NAME_STRING + - SDL_PROP_GPU_DEVICE_DRIVER_VERSION_STRING + - SDL_PROP_GPU_DEVICE_DRIVER_INFO_STRING +* Added SDL_GetPixelFormatFromGPUTextureFormat() and SDL_GetGPUTextureFormatFromPixelFormat() +* Added SDL_CreateGPURenderer() and SDL_GetGPURendererDevice() to create a 2D renderer for use with GPU rendering. +* Added SDL_CreateGPURenderState(), SDL_SetGPURenderStateFragmentUniforms(), SDL_SetGPURenderState(), and SDL_DestroyGPURenderState() to use fragment shaders with a GPU 2D renderer +* Added SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_POINTER to create a 2D texture from an existing GPU texture +* Added SDL_PROP_TEXTURE_GPU_TEXTURE_POINTER to get the GPU texture from a 2D texture when using the GPU 2D renderer +* Added support for YUV textures and HDR colorspaces to the GPU 2D renderer +* Added support for textures with palettes, and SDL_GetTexturePalette() and SDL_SetTexturePalette() to interact with them +* Added SDL_RenderTexture9GridTiled() to do tiled instead of stretched 9-grid texture rendering +* Added SDL_GetDefaultTextureScaleMode() and SDL_SetDefaultTextureScaleMode() to set the texture scale mode for new textures +* Added SDL_GetRenderTextureAddressMode() and SDL_SetRenderTextureAddressMode() to change the texture addressing mode +* Added SDL_TEXTURE_ADDRESS_WRAP to allow wrapping of textures if the renderer has SDL_PROP_RENDERER_TEXTURE_WRAPPING_BOOLEAN set +* The default YUV colorspace is BT.601 limited range, for compatibility with SDL2 +* Added SDL_SCALEMODE_PIXELART as an improved scaling algorithm for pixel art without introducing blurring +* Added SDL_FLIP_HORIZONTAL_AND_VERTICAL to flip a surface both horizontally and vertically +* Added SDL_LoadPNG(), SDL_LoadPNG_IO(), SDL_SavePNG(), and SDL_SavePNG_IO() to load and save PNG images +* Added SDL_LoadSurface() and SDL_LoadSurface_IO() to detect BMP and PNG formats and load them as surfaces +* Added SDL_PROP_SURFACE_ROTATION_FLOAT to indicate the rotation needed to display camera images upright +* Added SDL_RotateSurface() to create a rotated copy of a surface +* SDL_EVENT_WINDOW_EXPOSED now sets data1 to true if it is sent during live resizing +* Added SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED, which is sent when the usable desktop bounds change +* Added SDL_EVENT_SCREEN_KEYBOARD_SHOWN, which is sent when the on-screen keyboard has been shown +* Added SDL_EVENT_SCREEN_KEYBOARD_HIDDEN, which is sent when the on-screen keyboard has been hidden +* Added pinch gesture events: SDL_EVENT_PINCH_BEGIN, SDL_EVENT_PINCH_UPDATE, SDL_EVENT_PINCH_END +* SDL_EVENT_AUDIO_DEVICE_ADDED will be sent during initialization for each audio device +* SDL_GetCameraPermissionState() returns SDL_CameraPermissionState instead of int +* Added SDL_PutAudioStreamDataNoCopy() to do more efficient audio stream processing in some cases +* Added SDL_PutAudioStreamPlanarData() to add planar audio data instead of interleaved data to an audio stream +* Added SDL_HINT_AUDIO_DEVICE_RAW_STREAM to signal that the OS shouldn't do further audio processing, useful for applications that handle noise canceling, etc. +* Added SDL_PROP_AUDIOSTREAM_AUTO_CLEANUP_BOOLEAN to allow streams that persist beyond the audio subsystem lifetime. +* Added enhanced support for 8BitDo controllers +* Added enhanced support for FlyDigi controllers +* Added enhanced support for Hand Held Legend SInput controllers +* Added support for wired Nintendo Switch 2 controllers when built with libusb +* Added SDL_hid_get_properties() to associate SDL properties with HID devices +* Added SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER to query the libusb handle from an SDL_hid_device, if it's been opened with libusb +* Added SDL_SetRelativeMouseTransform() to add custom mouse input transformation +* Added SDL_GetPenDeviceType() to determine whether a pen is on the screen or on a separate touchpad +* SDL_HINT_MAIN_CALLBACK_RATE may be set to a floating point callback rate +* Added SDL_GetEventDescription() to get an English description of an event, suitable for logging +* Added SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC_POINTER to allow custom freeing of the memory used by SDL_IOFromMem() and SDL_IOFromConstMem() +* Added SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING to set the working directory for new processes +* Added verbose log output when the DEBUG_INVOCATION environment variable is set to "1" +* Added SDL_AddAtomicU32() +* Added SDL_GetSystemPageSize() to get the system page size +* Added SDL_ALIGNED() to signal that data should have a specific alignment + +Windows: +* Added SDL_HINT_RENDER_DIRECT3D11_WARP to enable D3D11 software rasterization +* Using SDL_InsertGPUDebugLabel(), SDL_PushGPUDebugGroup(), and SDL_PopGPUDebugGroup() requires WinPixEventRuntime.dll to be in your PATH or in the same directory as your executable +* Added SDL_PROP_DISPLAY_WINDOWS_HMONITOR_POINTER so you can query the HMONITOR associated with a display +* SDL_HINT_AUDIO_DEVICE_STREAM_ROLE is used by the WASAPI audio driver to set the audio stream category +* Added SDL_HINT_AUDIO_DEVICE_RAW_STREAM to signal whether the OS audio driver should do additional signal processing +* Added SDL_HINT_WINDOWS_RAW_KEYBOARD_EXCLUDE_HOTKEYS to allow disabling some system hotkeys when in raw input mode +* SDL_HINT_WINDOWS_GAMEINPUT is disabled by default + +macOS: +* Added SDL_HINT_MAC_PRESS_AND_HOLD to control whether holding down a key will repeat the pressed key or open the accents menu + +Linux: +* Added atomic support for KMSDRM +* Added SDL_HINT_KMSDRM_ATOMIC to control whether KMSDRM will use atomic functionality +* Added SDL_PROP_DISPLAY_WAYLAND_WL_OUTPUT_POINTER so you can query the wl_output associated with a display + +Emscripten: +* Added SDL_WINDOW_FILL_DOCUMENT to indicate that windows expand to fill the whole browser window +* Added SDL_SetWindowFillDocument() to change whether windows expand to fill the whole browser window +* Added SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING to allow setting the SDL canvas ID, and SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING to query it on existing windows +* Added SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING to specify where keyboard input is bound, and SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING to query it on existing windows + +iOS: +* SDL now supports window scenes, fixing the warning "CLIENT OF UIKIT REQUIRES UPDATE" +* Added SDL_PROP_WINDOW_CREATE_WINDOWSCENE_POINTER to specify the window scene for a window + +visionOS: +* The default refresh rate has been increased to 90Hz +* SDL_SetWindowSize() changes the size of the window on Vision Pro headsets + +PlayStation 2: +* Added the following hints to control the display parameters: SDL_HINT_PS2_GS_WIDTH, SDL_HINT_PS2_GS_HEIGHT, SDL_HINT_PS2_GS_PROGRESSIVE, SDL_HINT_PS2_GS_MODE + +Note: On Unix platforms SDL provides ELF notes describing its non-mandatory library dependencies in the format described by https://systemd.io/ELF_DLOPEN_METADATA/. Some of these libraries are quite important, so distribution vendors who package SDL should parse the ELF notes and consider generating dependencies at the packaging level, for example by using https://github.com/systemd/package-notes. Other libraries and games can add similar ELF notes to describe their own dependencies by using the SDL_ELF_NOTE_DLOPEN macro. + + +--------------------------------------------------------------------------- +3.2.22: +--------------------------------------------------------------------------- +* SDL_HINT_JOYSTICK_WGI is disabled by default + + +--------------------------------------------------------------------------- +3.2.16: +--------------------------------------------------------------------------- +* SDL_HINT_JOYSTICK_RAWINPUT is disabled by default + + --------------------------------------------------------------------------- 3.2.10: --------------------------------------------------------------------------- * Added SDL_HINT_VIDEO_X11_EXTERNAL_WINDOW_INPUT to control whether XSelectInput() should be called on external windows to enable input events. + --------------------------------------------------------------------------- 3.2.4: --------------------------------------------------------------------------- * Added SDL_StretchSurface() + --------------------------------------------------------------------------- 3.2.0: --------------------------------------------------------------------------- diff --git a/libs/SDL3/Xcode/SDL/Info-Framework.plist b/libs/SDL3/Xcode/SDL/Info-Framework.plist index adf5f832..7f1a985a 100644 --- a/libs/SDL3/Xcode/SDL/Info-Framework.plist +++ b/libs/SDL3/Xcode/SDL/Info-Framework.plist @@ -19,10 +19,10 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 3.3.0 + 3.4.10 CFBundleSignature SDLX CFBundleVersion - 3.3.0 + 3.4.10 diff --git a/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj index ffe8fa75..141e6372 100644 --- a/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 55; objects = { /* Begin PBXAggregateTarget section */ @@ -54,6 +54,7 @@ 00CFA89D106B4BA100758660 /* ForceFeedback.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00CFA89C106B4BA100758660 /* ForceFeedback.framework */; platformFilters = (macos, ); }; 00D0D08410675DD9004B05EF /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00D0D08310675DD9004B05EF /* CoreFoundation.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); settings = {ATTRIBUTES = (Required, ); }; }; 00D0D0D810675E46004B05EF /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 007317C10858E15000B2BC32 /* Carbon.framework */; platformFilters = (macos, ); }; + 02D6A1C228A84B8F00A7F002 /* SDL_hidapi_sinput.c in Sources */ = {isa = PBXBuildFile; fileRef = 02D6A1C128A84B8F00A7F001 /* SDL_hidapi_sinput.c */; }; 1485C3312BBA4AF30063985B /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1485C32F2BBA4A0C0063985B /* UniformTypeIdentifiers.framework */; platformFilters = (maccatalyst, macos, ); settings = {ATTRIBUTES = (Weak, ); }; }; 557D0CFA254586CA003913E3 /* CoreHaptics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F37DC5F225350EBC0002E6F7 /* CoreHaptics.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); settings = {ATTRIBUTES = (Weak, ); }; }; 557D0CFB254586D7003913E3 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A75FDABD23E28B6200529352 /* GameController.framework */; settings = {ATTRIBUTES = (Required, ); }; }; @@ -66,6 +67,7 @@ 566E26D8246274CC00718109 /* SDL_locale.c in Sources */ = {isa = PBXBuildFile; fileRef = 566E26CD246274CB00718109 /* SDL_locale.c */; }; 566E26E1246274CC00718109 /* SDL_syslocale.h in Headers */ = {isa = PBXBuildFile; fileRef = 566E26CE246274CC00718109 /* SDL_syslocale.h */; }; 56A2373329F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; }; + 63124A422E5C357500A53610 /* SDL_hidapi_zuiki.c in Sources */ = {isa = PBXBuildFile; fileRef = 63124A412E5C357500A53610 /* SDL_hidapi_zuiki.c */; }; 6312C66D2B42341400A7BB00 /* SDL_murmur3.c in Sources */ = {isa = PBXBuildFile; fileRef = 6312C66C2B42341400A7BB00 /* SDL_murmur3.c */; }; 63134A252A7902FD0021E9A6 /* SDL_pen_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 63134A232A7902FD0021E9A6 /* SDL_pen_c.h */; }; 63134A262A7902FD0021E9A6 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63134A242A7902FD0021E9A6 /* SDL_pen.c */; }; @@ -228,6 +230,7 @@ A7D8B54523E2514300DCD162 /* SDL_hidapijoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C423E2513E00DCD162 /* SDL_hidapijoystick.c */; }; A7D8B54B23E2514300DCD162 /* SDL_hidapi_xboxone.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C523E2513E00DCD162 /* SDL_hidapi_xboxone.c */; }; A7D8B55123E2514300DCD162 /* SDL_hidapi_switch.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C623E2513E00DCD162 /* SDL_hidapi_switch.c */; }; + A7D8B55123E2514300DCD163 /* SDL_hidapi_switch2.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C623E2513E00DCD163 /* SDL_hidapi_switch2.c */; }; A7D8B55723E2514300DCD162 /* SDL_hidapijoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A7C723E2513E00DCD162 /* SDL_hidapijoystick_c.h */; }; A7D8B55D23E2514300DCD162 /* SDL_hidapi_xbox360w.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C823E2513E00DCD162 /* SDL_hidapi_xbox360w.c */; }; A7D8B56323E2514300DCD162 /* SDL_hidapi_gamecube.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */; }; @@ -285,7 +288,6 @@ A7D8B9E323E2514400DCD162 /* SDL_drawline.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A8F123E2514000DCD162 /* SDL_drawline.c */; }; A7D8B9E923E2514400DCD162 /* SDL_blendline.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A8F223E2514000DCD162 /* SDL_blendline.h */; }; A7D8B9EF23E2514400DCD162 /* SDL_drawpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A8F323E2514000DCD162 /* SDL_drawpoint.h */; }; - A7D8B9F523E2514400DCD162 /* SDL_rotate.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A8F423E2514000DCD162 /* SDL_rotate.c */; }; A7D8B9FB23E2514400DCD162 /* SDL_render_sw_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A8F523E2514000DCD162 /* SDL_render_sw_c.h */; }; A7D8BA0123E2514400DCD162 /* SDL_blendfillrect.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A8F623E2514000DCD162 /* SDL_blendfillrect.h */; }; A7D8BA0723E2514400DCD162 /* SDL_drawline.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A8F723E2514000DCD162 /* SDL_drawline.h */; }; @@ -295,8 +297,6 @@ A7D8BA1F23E2514400DCD162 /* SDL_blendline.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A8FB23E2514000DCD162 /* SDL_blendline.c */; }; A7D8BA2523E2514400DCD162 /* SDL_drawpoint.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A8FC23E2514000DCD162 /* SDL_drawpoint.c */; }; A7D8BA2B23E2514400DCD162 /* SDL_blendfillrect.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A8FD23E2514000DCD162 /* SDL_blendfillrect.c */; }; - A7D8BA3123E2514400DCD162 /* SDL_rotate.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A8FE23E2514000DCD162 /* SDL_rotate.h */; }; - A7D8BA3723E2514400DCD162 /* SDL_d3dmath.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A8FF23E2514000DCD162 /* SDL_d3dmath.c */; }; A7D8BA4923E2514400DCD162 /* SDL_render_gles2.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A90423E2514000DCD162 /* SDL_render_gles2.c */; }; A7D8BA4F23E2514400DCD162 /* SDL_shaders_gles2.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A90523E2514000DCD162 /* SDL_shaders_gles2.h */; }; A7D8BA5523E2514400DCD162 /* SDL_gles2funcs.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A90623E2514000DCD162 /* SDL_gles2funcs.h */; }; @@ -414,6 +414,8 @@ F386F6F02884663E001840AA /* SDL_utils_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F386F6E52884663E001840AA /* SDL_utils_c.h */; }; F386F6F92884663E001840AA /* SDL_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = F386F6E62884663E001840AA /* SDL_utils.c */; }; F388C95528B5F6F700661ECF /* SDL_hidapi_ps3.c in Sources */ = {isa = PBXBuildFile; fileRef = F388C95428B5F6F600661ECF /* SDL_hidapi_ps3.c */; }; + F38C72492CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c in Sources */ = {isa = PBXBuildFile; fileRef = F38C72482CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c */; }; + F39344CE2E99771B0056986F /* SDL_dlopennote.h in Headers */ = {isa = PBXBuildFile; fileRef = F39344CD2E99771B0056986F /* SDL_dlopennote.h */; settings = {ATTRIBUTES = (Public, ); }; }; F395BF6525633B2400942BFF /* SDL_crc32.c in Sources */ = {isa = PBXBuildFile; fileRef = F395BF6425633B2400942BFF /* SDL_crc32.c */; }; F395C1932569C68F00942BFF /* SDL_iokitjoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F395C1912569C68E00942BFF /* SDL_iokitjoystick_c.h */; }; F395C19C2569C68F00942BFF /* SDL_iokitjoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = F395C1922569C68E00942BFF /* SDL_iokitjoystick.c */; }; @@ -520,10 +522,18 @@ F3D60A8328C16A1900788A3A /* SDL_hidapi_wii.c in Sources */ = {isa = PBXBuildFile; fileRef = F3D60A8228C16A1800788A3A /* SDL_hidapi_wii.c */; }; F3D8BDFC2D6D2C7000B22FA1 /* SDL_eventwatch_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3D8BDFB2D6D2C7000B22FA1 /* SDL_eventwatch_c.h */; }; F3D8BDFD2D6D2C7000B22FA1 /* SDL_eventwatch.c in Sources */ = {isa = PBXBuildFile; fileRef = F3D8BDFA2D6D2C7000B22FA1 /* SDL_eventwatch.c */; }; + F3DB66342EA9ACC300568044 /* SDL_rotate.c in Sources */ = {isa = PBXBuildFile; fileRef = F3DB66332EA9ACC300568044 /* SDL_rotate.c */; }; + F3DB66352EA9ACC300568044 /* SDL_rotate.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DB66322EA9ACC300568044 /* SDL_rotate.h */; }; + F3DC38C92E5FC60300CD73DE /* SDL_libusb.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DC38C72E5FC60300CD73DE /* SDL_libusb.h */; }; + F3DC38CA2E5FC60300CD73DE /* SDL_libusb.c in Sources */ = {isa = PBXBuildFile; fileRef = F3DC38C82E5FC60300CD73DE /* SDL_libusb.c */; }; F3DDCC562AFD42B600B0842B /* SDL_clipboard_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC4D2AFD42B500B0842B /* SDL_clipboard_c.h */; }; F3DDCC5B2AFD42B600B0842B /* SDL_video_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC522AFD42B600B0842B /* SDL_video_c.h */; }; F3DDCC5D2AFD42B600B0842B /* SDL_rect_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */; }; F3E5A6EB2AD5E0E600293D83 /* SDL_properties.c in Sources */ = {isa = PBXBuildFile; fileRef = F3E5A6EA2AD5E0E600293D83 /* SDL_properties.c */; }; + F3E6C3932EE9F20000A6B39E /* SDL_report_descriptor.c in Sources */ = {isa = PBXBuildFile; fileRef = F3E6C3922EE9F20000A6B39E /* SDL_report_descriptor.c */; }; + F3E6C3942EE9F20000A6B39E /* SDL_hidapi_flydigi.h in Headers */ = {isa = PBXBuildFile; fileRef = F3E6C38F2EE9F20000A6B39E /* SDL_hidapi_flydigi.h */; }; + F3E6C3952EE9F20000A6B39E /* SDL_hidapi_sinput.h in Headers */ = {isa = PBXBuildFile; fileRef = F3E6C3902EE9F20000A6B39E /* SDL_hidapi_sinput.h */; }; + F3E6C3962EE9F20000A6B39E /* SDL_report_descriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = F3E6C3912EE9F20000A6B39E /* SDL_report_descriptor.h */; }; F3EFA5ED2D5AB97300BCF22F /* SDL_stb_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3EFA5EA2D5AB97300BCF22F /* SDL_stb_c.h */; }; F3EFA5EE2D5AB97300BCF22F /* stb_image.h in Headers */ = {isa = PBXBuildFile; fileRef = F3EFA5EC2D5AB97300BCF22F /* stb_image.h */; }; F3EFA5EF2D5AB97300BCF22F /* SDL_surface_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3EFA5EB2D5AB97300BCF22F /* SDL_surface_c.h */; }; @@ -600,6 +610,7 @@ 007317C10858E15000B2BC32 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; 00CFA89C106B4BA100758660 /* ForceFeedback.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ForceFeedback.framework; path = System/Library/Frameworks/ForceFeedback.framework; sourceTree = SDKROOT; }; 00D0D08310675DD9004B05EF /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 02D6A1C128A84B8F00A7F001 /* SDL_hidapi_sinput.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_sinput.c; sourceTree = ""; }; 1485C32F2BBA4A0C0063985B /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; 5616CA49252BB2A5005D5928 /* SDL_url.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_url.c; sourceTree = ""; }; 5616CA4A252BB2A6005D5928 /* SDL_sysurl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_sysurl.h; sourceTree = ""; }; @@ -610,6 +621,7 @@ 566E26CD246274CB00718109 /* SDL_locale.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_locale.c; path = locale/SDL_locale.c; sourceTree = ""; }; 566E26CE246274CC00718109 /* SDL_syslocale.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_syslocale.h; path = locale/SDL_syslocale.h; sourceTree = ""; }; 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_sysrwlock.c; sourceTree = ""; }; + 63124A412E5C357500A53610 /* SDL_hidapi_zuiki.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_zuiki.c; sourceTree = ""; }; 6312C66C2B42341400A7BB00 /* SDL_murmur3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_murmur3.c; sourceTree = ""; }; 63134A232A7902FD0021E9A6 /* SDL_pen_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_pen_c.h; sourceTree = ""; }; 63134A242A7902FD0021E9A6 /* SDL_pen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_pen.c; sourceTree = ""; }; @@ -811,6 +823,7 @@ A7D8A7C423E2513E00DCD162 /* SDL_hidapijoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapijoystick.c; sourceTree = ""; }; A7D8A7C523E2513E00DCD162 /* SDL_hidapi_xboxone.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xboxone.c; sourceTree = ""; }; A7D8A7C623E2513E00DCD162 /* SDL_hidapi_switch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_switch.c; sourceTree = ""; }; + A7D8A7C623E2513E00DCD163 /* SDL_hidapi_switch2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_switch2.c; sourceTree = ""; }; A7D8A7C723E2513E00DCD162 /* SDL_hidapijoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hidapijoystick_c.h; sourceTree = ""; }; A7D8A7C823E2513E00DCD162 /* SDL_hidapi_xbox360w.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xbox360w.c; sourceTree = ""; }; A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_gamecube.c; sourceTree = ""; }; @@ -869,7 +882,6 @@ A7D8A8F123E2514000DCD162 /* SDL_drawline.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_drawline.c; sourceTree = ""; }; A7D8A8F223E2514000DCD162 /* SDL_blendline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_blendline.h; sourceTree = ""; }; A7D8A8F323E2514000DCD162 /* SDL_drawpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_drawpoint.h; sourceTree = ""; }; - A7D8A8F423E2514000DCD162 /* SDL_rotate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_rotate.c; sourceTree = ""; }; A7D8A8F523E2514000DCD162 /* SDL_render_sw_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_render_sw_c.h; sourceTree = ""; }; A7D8A8F623E2514000DCD162 /* SDL_blendfillrect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_blendfillrect.h; sourceTree = ""; }; A7D8A8F723E2514000DCD162 /* SDL_drawline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_drawline.h; sourceTree = ""; }; @@ -879,8 +891,6 @@ A7D8A8FB23E2514000DCD162 /* SDL_blendline.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_blendline.c; sourceTree = ""; }; A7D8A8FC23E2514000DCD162 /* SDL_drawpoint.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_drawpoint.c; sourceTree = ""; }; A7D8A8FD23E2514000DCD162 /* SDL_blendfillrect.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_blendfillrect.c; sourceTree = ""; }; - A7D8A8FE23E2514000DCD162 /* SDL_rotate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_rotate.h; sourceTree = ""; }; - A7D8A8FF23E2514000DCD162 /* SDL_d3dmath.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_d3dmath.c; sourceTree = ""; }; A7D8A90423E2514000DCD162 /* SDL_render_gles2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_render_gles2.c; sourceTree = ""; }; A7D8A90523E2514000DCD162 /* SDL_shaders_gles2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_shaders_gles2.h; sourceTree = ""; }; A7D8A90623E2514000DCD162 /* SDL_gles2funcs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_gles2funcs.h; sourceTree = ""; }; @@ -992,6 +1002,8 @@ F386F6E52884663E001840AA /* SDL_utils_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_utils_c.h; sourceTree = ""; }; F386F6E62884663E001840AA /* SDL_utils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_utils.c; sourceTree = ""; }; F388C95428B5F6F600661ECF /* SDL_hidapi_ps3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_ps3.c; sourceTree = ""; }; + F38C72482CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_steam_triton.c; sourceTree = ""; }; + F39344CD2E99771B0056986F /* SDL_dlopennote.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_dlopennote.h; sourceTree = ""; }; F395BF6425633B2400942BFF /* SDL_crc32.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_crc32.c; sourceTree = ""; }; F395C1912569C68E00942BFF /* SDL_iokitjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_iokitjoystick_c.h; sourceTree = ""; }; F395C1922569C68E00942BFF /* SDL_iokitjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_iokitjoystick.c; sourceTree = ""; }; @@ -1097,10 +1109,18 @@ F3D60A8228C16A1800788A3A /* SDL_hidapi_wii.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_wii.c; sourceTree = ""; }; F3D8BDFA2D6D2C7000B22FA1 /* SDL_eventwatch.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_eventwatch.c; sourceTree = ""; }; F3D8BDFB2D6D2C7000B22FA1 /* SDL_eventwatch_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_eventwatch_c.h; sourceTree = ""; }; + F3DB66322EA9ACC300568044 /* SDL_rotate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_rotate.h; sourceTree = ""; }; + F3DB66332EA9ACC300568044 /* SDL_rotate.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_rotate.c; sourceTree = ""; }; + F3DC38C72E5FC60300CD73DE /* SDL_libusb.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_libusb.h; sourceTree = ""; }; + F3DC38C82E5FC60300CD73DE /* SDL_libusb.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_libusb.c; sourceTree = ""; }; F3DDCC4D2AFD42B500B0842B /* SDL_clipboard_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_clipboard_c.h; sourceTree = ""; }; F3DDCC522AFD42B600B0842B /* SDL_video_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_video_c.h; sourceTree = ""; }; F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_rect_impl.h; sourceTree = ""; }; F3E5A6EA2AD5E0E600293D83 /* SDL_properties.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_properties.c; sourceTree = ""; }; + F3E6C38F2EE9F20000A6B39E /* SDL_hidapi_flydigi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_flydigi.h; sourceTree = ""; }; + F3E6C3902EE9F20000A6B39E /* SDL_hidapi_sinput.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_sinput.h; sourceTree = ""; }; + F3E6C3912EE9F20000A6B39E /* SDL_report_descriptor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_report_descriptor.h; sourceTree = ""; }; + F3E6C3922EE9F20000A6B39E /* SDL_report_descriptor.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_report_descriptor.c; sourceTree = ""; }; F3EFA5E92D5AB97300BCF22F /* SDL_stb.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_stb.c; sourceTree = ""; }; F3EFA5EA2D5AB97300BCF22F /* SDL_stb_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_stb_c.h; sourceTree = ""; }; F3EFA5EB2D5AB97300BCF22F /* SDL_surface_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_surface_c.h; sourceTree = ""; }; @@ -1257,6 +1277,7 @@ F3D46A8B2D20625800D9CBDF /* SDL_copying.h */, F3D46A8C2D20625800D9CBDF /* SDL_cpuinfo.h */, F3D46A8D2D20625800D9CBDF /* SDL_dialog.h */, + F39344CD2E99771B0056986F /* SDL_dlopennote.h */, F3D46A8E2D20625800D9CBDF /* SDL_egl.h */, F3D46A8F2D20625800D9CBDF /* SDL_endian.h */, F3D46A902D20625800D9CBDF /* SDL_error.h */, @@ -1410,6 +1431,8 @@ children = ( F3ADAB8C2576F08500A6B1D9 /* ios */, 5616CA48252BB285005D5928 /* macos */, + F3DC38C72E5FC60300CD73DE /* SDL_libusb.h */, + F3DC38C82E5FC60300CD73DE /* SDL_libusb.c */, 5616CA4A252BB2A6005D5928 /* SDL_sysurl.h */, 5616CA49252BB2A5005D5928 /* SDL_url.c */, ); @@ -1648,6 +1671,8 @@ F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */, A7D8A61523E2513D00DCD162 /* SDL_RLEaccel.c */, A7D8A76723E2513E00DCD162 /* SDL_RLEaccel_c.h */, + F3DB66322EA9ACC300568044 /* SDL_rotate.h */, + F3DB66332EA9ACC300568044 /* SDL_rotate.c */, F3EFA5E92D5AB97300BCF22F /* SDL_stb.c */, F3EFA5EA2D5AB97300BCF22F /* SDL_stb_c.h */, A7D8A60323E2513D00DCD162 /* SDL_stretch.c */, @@ -1931,6 +1956,7 @@ children = ( F3395BA72D9A5971007246C8 /* SDL_hidapi_8bitdo.c */, F32305FE28939F6400E66D30 /* SDL_hidapi_combined.c */, + F3E6C38F2EE9F20000A6B39E /* SDL_hidapi_flydigi.h */, F3395BA72D9A5971007246C9 /* SDL_hidapi_flydigi.c */, A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */, F3B6B8092DC3EA54004954FD /* SDL_hidapi_gip.c */, @@ -1943,17 +1969,24 @@ A75FDBC323EA380300529352 /* SDL_hidapi_rumble.h */, A75FDBC423EA380300529352 /* SDL_hidapi_rumble.c */, 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */, + F3E6C3902EE9F20000A6B39E /* SDL_hidapi_sinput.h */, + 02D6A1C128A84B8F00A7F001 /* SDL_hidapi_sinput.c */, F3984CCF25BCC92800374F43 /* SDL_hidapi_stadia.c */, A75FDAAC23E2795C00529352 /* SDL_hidapi_steam.c */, F3FD042D2C9B755700824C4C /* SDL_hidapi_steam_hori.c */, + F38C72482CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c */, A797456F2B2E9D39009D224A /* SDL_hidapi_steamdeck.c */, A7D8A7C623E2513E00DCD162 /* SDL_hidapi_switch.c */, + A7D8A7C623E2513E00DCD163 /* SDL_hidapi_switch2.c */, F3D60A8228C16A1800788A3A /* SDL_hidapi_wii.c */, A7D8A7C223E2513E00DCD162 /* SDL_hidapi_xbox360.c */, A7D8A7C823E2513E00DCD162 /* SDL_hidapi_xbox360w.c */, A7D8A7C523E2513E00DCD162 /* SDL_hidapi_xboxone.c */, + 63124A412E5C357500A53610 /* SDL_hidapi_zuiki.c */, A7D8A7C423E2513E00DCD162 /* SDL_hidapijoystick.c */, A7D8A7C723E2513E00DCD162 /* SDL_hidapijoystick_c.h */, + F3E6C3912EE9F20000A6B39E /* SDL_report_descriptor.h */, + F3E6C3922EE9F20000A6B39E /* SDL_report_descriptor.c */, ); path = hidapi; sourceTree = ""; @@ -2164,7 +2197,6 @@ A7D8A90C23E2514000DCD162 /* opengl */, A7D8A90323E2514000DCD162 /* opengles2 */, A7D8A8EF23E2514000DCD162 /* software */, - A7D8A8FF23E2514000DCD162 /* SDL_d3dmath.c */, A7D8A8DC23E2514000DCD162 /* SDL_d3dmath.h */, A7D8A8DB23E2514000DCD162 /* SDL_render.c */, E4F7981D2AD8D86A00669F54 /* SDL_render_unsupported.c */, @@ -2205,8 +2237,6 @@ A7D8A8F323E2514000DCD162 /* SDL_drawpoint.h */, A7D8A8F523E2514000DCD162 /* SDL_render_sw_c.h */, A7D8A8F923E2514000DCD162 /* SDL_render_sw.c */, - A7D8A8F423E2514000DCD162 /* SDL_rotate.c */, - A7D8A8FE23E2514000DCD162 /* SDL_rotate.h */, ); path = software; sourceTree = ""; @@ -2517,6 +2547,7 @@ A7D8AEEE23E2514100DCD162 /* SDL_cocoaopengles.h in Headers */, F3D46ACA2D20625800D9CBDF /* SDL_storage.h in Headers */, F3D46ACB2D20625800D9CBDF /* SDL_sensor.h in Headers */, + F3DB66352EA9ACC300568044 /* SDL_rotate.h in Headers */, F3D46ACC2D20625800D9CBDF /* SDL_properties.h in Headers */, F3D46ACD2D20625800D9CBDF /* SDL_bits.h in Headers */, F3D46ACE2D20625800D9CBDF /* SDL_keyboard.h in Headers */, @@ -2535,6 +2566,9 @@ F3D46ADB2D20625800D9CBDF /* SDL_pen.h in Headers */, F3D46ADC2D20625800D9CBDF /* SDL_render.h in Headers */, F3D46ADD2D20625800D9CBDF /* SDL_assert.h in Headers */, + F3E6C3942EE9F20000A6B39E /* SDL_hidapi_flydigi.h in Headers */, + F3E6C3952EE9F20000A6B39E /* SDL_hidapi_sinput.h in Headers */, + F3E6C3962EE9F20000A6B39E /* SDL_report_descriptor.h in Headers */, F3D46ADE2D20625800D9CBDF /* SDL_atomic.h in Headers */, F3D46ADF2D20625800D9CBDF /* SDL_begin_code.h in Headers */, F3D46AE02D20625800D9CBDF /* SDL_log.h in Headers */, @@ -2654,7 +2688,6 @@ F3DDCC5D2AFD42B600B0842B /* SDL_rect_impl.h in Headers */, A7D8B9FB23E2514400DCD162 /* SDL_render_sw_c.h in Headers */, E4F257972C81903800FCEAFC /* SDL_sysgpu.h in Headers */, - A7D8BA3123E2514400DCD162 /* SDL_rotate.h in Headers */, A7D8A98D23E2514000DCD162 /* SDL_sensor_c.h in Headers */, A7D8BA7323E2514400DCD162 /* SDL_shaders_gl.h in Headers */, A7D8BA4F23E2514400DCD162 /* SDL_shaders_gles2.h in Headers */, @@ -2742,8 +2775,10 @@ A7D8B28A23E2514200DCD162 /* vulkan_xlib_xrandr.h in Headers */, A7D8B3D423E2514300DCD162 /* yuv_rgb.h in Headers */, F3FA5A252B59ACE000FEAD97 /* yuv_rgb_common.h in Headers */, + F39344CE2E99771B0056986F /* SDL_dlopennote.h in Headers */, F3FA5A1D2B59ACE000FEAD97 /* yuv_rgb_internal.h in Headers */, F3D8BDFC2D6D2C7000B22FA1 /* SDL_eventwatch_c.h in Headers */, + F3DC38C92E5FC60300CD73DE /* SDL_libusb.h in Headers */, F3FA5A242B59ACE000FEAD97 /* yuv_rgb_lsx.h in Headers */, F3FA5A1E2B59ACE000FEAD97 /* yuv_rgb_lsx_func.h in Headers */, F3FA5A1F2B59ACE000FEAD97 /* yuv_rgb_sse.h in Headers */, @@ -2863,7 +2898,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Build an xcframework with both device and simulator files for all platforms.\n# Adapted from an answer in\n# https://developer.apple.com/forums/thread/666335?answerId=685927022#685927022\n\nif [ \"$XCODE_VERSION_ACTUAL\" -lt 1100 ]\nthen\n echo \"error: Building an xcframework requires Xcode 11 minimum.\"\n exit 1\nfi\n\nFRAMEWORK_NAME=\"SDL3\"\nPROJECT_NAME=\"SDL\"\nSCHEME=\"SDL3\"\n\nMACOS_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-macosx.xcarchive\"\nIOS_SIMULATOR_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphonesimulator.xcarchive\"\nIOS_DEVICE_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphoneos.xcarchive\"\nTVOS_SIMULATOR_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-appletvsimulator.xcarchive\"\nTVOS_DEVICE_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-appletvos.xcarchive\"\n\nOUTPUT_DIR=\"./build/\"\n\n# macOS\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${MACOS_ARCHIVE_PATH} \\\n -destination 'generic/platform=macOS,name=Any Mac' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n \n# iOS simulator\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${IOS_SIMULATOR_ARCHIVE_PATH} \\\n -destination 'generic/platform=iOS Simulator' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# iOS device\nxcodebuild archive \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${IOS_DEVICE_ARCHIVE_PATH} \\\n -destination 'generic/platform=iOS' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# tvOS simulator\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${TVOS_SIMULATOR_ARCHIVE_PATH} \\\n -destination 'generic/platform=tvOS Simulator' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# tvOS device\nxcodebuild archive \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${TVOS_DEVICE_ARCHIVE_PATH} \\\n -destination 'generic/platform=tvOS' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# Clean-up any existing instance of this xcframework from the Products directory\nrm -rf \"${OUTPUT_DIR}${FRAMEWORK_NAME}.xcframework\"\n\n# Create final xcframework\nxcodebuild -create-xcframework \\\n -framework \"${MACOS_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${IOS_DEVICE_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${IOS_SIMULATOR_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${TVOS_DEVICE_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${TVOS_SIMULATOR_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -output ${OUTPUT_DIR}/${FRAMEWORK_NAME}.xcframework\n\n# Ensure git doesn't pick up on our Products folder. \nrm -rf ${OUTPUT_DIR}/.gitignore\necho \"*\" >> ${OUTPUT_DIR}/.gitignore\n"; + shellScript = "# Build an xcframework with both device and simulator files for all platforms.\n# Adapted from an answer in\n# https://developer.apple.com/forums/thread/666335?answerId=685927022#685927022\n\nif [ \"$XCODE_VERSION_ACTUAL\" -lt 1100 ]\nthen\n echo \"error: Building an xcframework requires Xcode 11 minimum.\"\n exit 1\nfi\n\nFRAMEWORK_NAME=\"SDL3\"\nPROJECT_NAME=\"SDL\"\nSCHEME=\"SDL3\"\n\nMACOS_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-macosx.xcarchive\"\nIOS_SIMULATOR_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphonesimulator.xcarchive\"\nIOS_DEVICE_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphoneos.xcarchive\"\nTVOS_SIMULATOR_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-appletvsimulator.xcarchive\"\nTVOS_DEVICE_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-appletvos.xcarchive\"\n\nOUTPUT_DIR=\"./build/\"\n\n# macOS\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${MACOS_ARCHIVE_PATH} \\\n -destination 'generic/platform=macOS,name=Any Mac' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n \n# iOS simulator\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${IOS_SIMULATOR_ARCHIVE_PATH} \\\n -destination 'generic/platform=iOS Simulator' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# iOS device\nxcodebuild archive \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${IOS_DEVICE_ARCHIVE_PATH} \\\n -destination 'generic/platform=iOS' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# tvOS simulator\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${TVOS_SIMULATOR_ARCHIVE_PATH} \\\n -destination 'generic/platform=tvOS Simulator' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# tvOS device\nxcodebuild archive \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${TVOS_DEVICE_ARCHIVE_PATH} \\\n -destination 'generic/platform=tvOS' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# Clean-up any existing instance of this xcframework from the Products directory\nrm -rf \"${OUTPUT_DIR}${FRAMEWORK_NAME}.xcframework\"\n\n# Create final xcframework\nxcodebuild -create-xcframework \\\n -framework \"${MACOS_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -debug-symbols \"${MACOS_ARCHIVE_PATH}\"/dSYMs/$FRAMEWORK_NAME.framework.dSYM \\\n -framework \"${IOS_DEVICE_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -debug-symbols \"${IOS_DEVICE_ARCHIVE_PATH}\"/dSYMs/$FRAMEWORK_NAME.framework.dSYM \\\n -framework \"${IOS_SIMULATOR_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${TVOS_DEVICE_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -debug-symbols \"${TVOS_DEVICE_ARCHIVE_PATH}\"/dSYMs/$FRAMEWORK_NAME.framework.dSYM \\\n -framework \"${TVOS_SIMULATOR_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -output ${OUTPUT_DIR}/${FRAMEWORK_NAME}.xcframework\n\n# Ensure git doesn't pick up on our Products folder. \nrm -rf ${OUTPUT_DIR}/.gitignore\necho \"*\" >> ${OUTPUT_DIR}/.gitignore\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -2877,6 +2912,7 @@ A7D8B62F23E2514300DCD162 /* SDL_sysfilesystem.m in Sources */, A7D8B41C23E2514300DCD162 /* SDL_systls.c in Sources */, 9846B07C287A9020000C35C8 /* SDL_hidapi_shield.c in Sources */, + 02D6A1C228A84B8F00A7F002 /* SDL_hidapi_sinput.c in Sources */, F31013C72C24E98200FBE946 /* SDL_keymap.c in Sources */, F3A9AE992C8A13C100AAC390 /* SDL_render_gpu.c in Sources */, A7D8BBD923E2574800DCD162 /* SDL_uikitmessagebox.m in Sources */, @@ -2894,7 +2930,6 @@ A7D8AEC423E2514100DCD162 /* SDL_cocoaevents.m in Sources */, E479118F2BA9555500CE3B7F /* SDL_genericstorage.c in Sources */, A7D8B86623E2514400DCD162 /* SDL_audiocvt.c in Sources */, - A7D8B9F523E2514400DCD162 /* SDL_rotate.c in Sources */, A7D8BBE323E2574800DCD162 /* SDL_uikitvideo.m in Sources */, F338A1182D1B37D8007CDFDF /* SDL_tray.m in Sources */, 5616CA4E252BB2A6005D5928 /* SDL_sysurl.m in Sources */, @@ -2903,6 +2938,7 @@ F3C1BD752D1F1A3000846529 /* SDL_tray_utils.c in Sources */, F382071D284F362F004DD584 /* SDL_guid.c in Sources */, A7D8BB8D23E2514500DCD162 /* SDL_touch.c in Sources */, + F3E6C3932EE9F20000A6B39E /* SDL_report_descriptor.c in Sources */, F31A92D228D4CB39003BFD6A /* SDL_offscreenopengles.c in Sources */, A1626A3E2617006A003F1973 /* SDL_triangle.c in Sources */, A7D8B3F223E2514300DCD162 /* SDL_thread.c in Sources */, @@ -2922,6 +2958,7 @@ E4F257962C81903800FCEAFC /* SDL_gpu.c in Sources */, F3D60A8328C16A1900788A3A /* SDL_hidapi_wii.c in Sources */, A7D8B9DD23E2514400DCD162 /* SDL_blendpoint.c in Sources */, + F3DB66342EA9ACC300568044 /* SDL_rotate.c in Sources */, A7D8B4EE23E2514300DCD162 /* SDL_gamepad.c in Sources */, E4A568B62AF763940062EEC4 /* SDL_sysmain_callbacks.c in Sources */, F316ABD82B5C3185002EF551 /* SDL_memset.c in Sources */, @@ -2933,7 +2970,6 @@ A7D8BBE723E2574800DCD162 /* SDL_uikitviewcontroller.m in Sources */, A7D8ADF223E2514100DCD162 /* SDL_blit_A.c in Sources */, A7D8BBDD23E2574800DCD162 /* SDL_uikitmodes.m in Sources */, - A7D8BA3723E2514400DCD162 /* SDL_d3dmath.c in Sources */, F3A9AE9C2C8A13C100AAC390 /* SDL_pipeline_gpu.c in Sources */, 89E580232D03606400DAF6D3 /* SDL_hidapihaptic.c in Sources */, 89E580242D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c in Sources */, @@ -2992,6 +3028,7 @@ F316ABD92B5C3185002EF551 /* SDL_memcpy.c in Sources */, A7D8B97A23E2514400DCD162 /* SDL_render.c in Sources */, A7D8ABD323E2514100DCD162 /* SDL_stretch.c in Sources */, + F38C72492CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c in Sources */, A7D8AC3923E2514100DCD162 /* SDL_blit_copy.c in Sources */, A7D8B5CF23E2514300DCD162 /* SDL_syspower.m in Sources */, F3B439512C935C2400792030 /* SDL_dummyprocess.c in Sources */, @@ -3035,6 +3072,7 @@ F3FA5A222B59ACE000FEAD97 /* yuv_rgb_sse.c in Sources */, F3C2CB232C5DDDB2004D7998 /* SDL_categories.c in Sources */, A7D8B55123E2514300DCD162 /* SDL_hidapi_switch.c in Sources */, + A7D8B55123E2514300DCD163 /* SDL_hidapi_switch2.c in Sources */, A7D8B96223E2514400DCD162 /* SDL_strtokr.c in Sources */, A7D8BB7523E2514500DCD162 /* SDL_clipboardevents.c in Sources */, E4F798202AD8D87F00669F54 /* SDL_video_unsupported.c in Sources */, @@ -3093,6 +3131,7 @@ 0000481D255AF155B42C0000 /* SDL_sysfsops.c in Sources */, 0000494CC93F3E624D3C0000 /* SDL_systime.c in Sources */, 000095FA1BDE436CF3AF0000 /* SDL_time.c in Sources */, + F3DC38CA2E5FC60300CD73DE /* SDL_libusb.c in Sources */, 0000140640E77F73F1DF0000 /* SDL_dialog_utils.c in Sources */, 0000D5B526B85DE7AB1C0000 /* SDL_cocoapen.m in Sources */, 6312C66D2B42341400A7BB00 /* SDL_murmur3.c in Sources */, @@ -3100,6 +3139,7 @@ 00004D0B73767647AD550000 /* SDL_asyncio_generic.c in Sources */, 0000A03C0F32C43816F40000 /* SDL_asyncio_windows_ioring.c in Sources */, 0000A877C7DB9FA935FC0000 /* SDL_uikitpen.m in Sources */, + 63124A422E5C357500A53610 /* SDL_hidapi_zuiki.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3121,8 +3161,8 @@ CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; DEPLOYMENT_POSTPROCESSING = YES; - DYLIB_COMPATIBILITY_VERSION = 301.0.0; - DYLIB_CURRENT_VERSION = 301.0.0; + DYLIB_COMPATIBILITY_VERSION = 401.0.0; + DYLIB_CURRENT_VERSION = 401.10.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_ALTIVEC_EXTENSIONS = YES; @@ -3157,7 +3197,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 3.3.0; + MARKETING_VERSION = 3.4.10; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3; PRODUCT_NAME = SDL3; @@ -3165,7 +3205,7 @@ SUPPORTED_PLATFORMS = "xrsimulator xros macosx iphonesimulator iphoneos appletvsimulator appletvos"; SUPPORTS_MACCATALYST = YES; TVOS_DEPLOYMENT_TARGET = 11.0; - XROS_DEPLOYMENT_TARGET = 1.0; + XROS_DEPLOYMENT_TARGET = 1.3; }; name = Release; }; @@ -3174,6 +3214,8 @@ baseConfigurationReference = F3F7BE3B2CBD79D200C984AF /* config.xcconfig */; buildSettings = { CLANG_LINK_OBJC_RUNTIME = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; OTHER_LDFLAGS = "-liconv"; SUPPORTS_MACCATALYST = YES; }; @@ -3185,8 +3227,8 @@ ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; - DYLIB_COMPATIBILITY_VERSION = 301.0.0; - DYLIB_CURRENT_VERSION = 301.0.0; + DYLIB_COMPATIBILITY_VERSION = 401.0.0; + DYLIB_CURRENT_VERSION = 401.10.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -3218,7 +3260,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 3.3.0; + MARKETING_VERSION = 3.4.10; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3; @@ -3227,7 +3269,7 @@ SUPPORTED_PLATFORMS = "xrsimulator xros macosx iphonesimulator iphoneos appletvsimulator appletvos"; SUPPORTS_MACCATALYST = YES; TVOS_DEPLOYMENT_TARGET = 11.0; - XROS_DEPLOYMENT_TARGET = 1.0; + XROS_DEPLOYMENT_TARGET = 1.3; }; name = Debug; }; @@ -3236,6 +3278,8 @@ baseConfigurationReference = F3F7BE3B2CBD79D200C984AF /* config.xcconfig */; buildSettings = { CLANG_LINK_OBJC_RUNTIME = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; OTHER_LDFLAGS = "-liconv"; SUPPORTS_MACCATALYST = YES; }; diff --git a/libs/SDL3/Xcode/SDL/pkg-support/SDL.info b/libs/SDL3/Xcode/SDL/pkg-support/SDL.info index 196d9560..9353947b 100644 --- a/libs/SDL3/Xcode/SDL/pkg-support/SDL.info +++ b/libs/SDL3/Xcode/SDL/pkg-support/SDL.info @@ -1,4 +1,4 @@ -Title SDL 3.3.0 +Title SDL 3.4.10 Version 1 Description SDL Library for macOS (http://www.libsdl.org) DefaultLocation /Library/Frameworks diff --git a/libs/SDL3/Xcode/SDL/pkg-support/resources/cmake/SDL3Config.cmake b/libs/SDL3/Xcode/SDL/pkg-support/resources/cmake/SDL3Config.cmake index 784d27db..79616653 100644 --- a/libs/SDL3/Xcode/SDL/pkg-support/resources/cmake/SDL3Config.cmake +++ b/libs/SDL3/Xcode/SDL/pkg-support/resources/cmake/SDL3Config.cmake @@ -1,8 +1,9 @@ # SDL3 CMake configuration file: -# This file is meant to be placed in Resources/CMake of a SDL3 framework +# This file is meant to be placed in Resources/CMake of a SDL3 framework for macOS, +# or in the CMake directory of a SDL3 framework for iOS / tvOS / visionOS. # INTERFACE_LINK_OPTIONS needs CMake 3.12 -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.12...4.0) include(FeatureSummary) set_package_properties(SDL3 PROPERTIES @@ -31,16 +32,31 @@ endmacro() set(SDL3_FOUND TRUE) -# Compute the installation prefix relative to this file. -set(_sdl3_framework_path "${CMAKE_CURRENT_LIST_DIR}") # > /SDL3.framework/Resources/CMake/ -get_filename_component(_sdl3_framework_path "${_sdl3_framework_path}" REALPATH) # > /SDL3.framework/Versions/Current/Resources/CMake -get_filename_component(_sdl3_framework_path "${_sdl3_framework_path}" REALPATH) # > /SDL3.framework/Versions/A/Resources/CMake/ -get_filename_component(_sdl3_framework_path "${_sdl3_framework_path}" PATH) # > /SDL3.framework/Versions/A/Resources/ -get_filename_component(_sdl3_framework_path "${_sdl3_framework_path}" PATH) # > /SDL3.framework/Versions/A/ -get_filename_component(_sdl3_framework_path "${_sdl3_framework_path}" PATH) # > /SDL3.framework/Versions/ -get_filename_component(_sdl3_framework_path "${_sdl3_framework_path}" PATH) # > /SDL3.framework/ -get_filename_component(_sdl3_framework_parent_path "${_sdl3_framework_path}" PATH) # > / +# Compute the installation prefix relative to this file: +# search upwards for the .framework directory +set(_current_path "${CMAKE_CURRENT_LIST_DIR}") +get_filename_component(_current_path "${_current_path}" REALPATH) +set(_sdl3_framework_path "") +while(NOT _sdl3_framework_path) + if(IS_DIRECTORY "${_current_path}" AND "${_current_path}" MATCHES "/SDL3\\.framework$") + set(_sdl3_framework_path "${_current_path}") + break() + endif() + get_filename_component(_next_current_path "${_current_path}" DIRECTORY) + if("${_current_path}" STREQUAL "${_next_current_path}") + break() + endif() + set(_current_path "${_next_current_path}") +endwhile() +unset(_current_path) +unset(_next_current_path) + +if(NOT _sdl3_framework_path) + message(FATAL_ERROR "Could not find SDL3.framework root from ${CMAKE_CURRENT_LIST_DIR}") +endif() + +get_filename_component(_sdl3_framework_parent_path "${_sdl3_framework_path}" PATH) # All targets are created, even when some might not be requested though COMPONENTS. # This is done for compatibility with CMake generated SDL3-target.cmake files. diff --git a/libs/SDL3/Xcode/SDL/pkg-support/resources/cmake/SDL3ConfigVersion.cmake b/libs/SDL3/Xcode/SDL/pkg-support/resources/cmake/SDL3ConfigVersion.cmake index 74329c43..6f7589b1 100644 --- a/libs/SDL3/Xcode/SDL/pkg-support/resources/cmake/SDL3ConfigVersion.cmake +++ b/libs/SDL3/Xcode/SDL/pkg-support/resources/cmake/SDL3ConfigVersion.cmake @@ -1,16 +1,26 @@ # based on the files generated by CMake's write_basic_package_version_file # SDL CMake version configuration file: -# This file is meant to be placed in Resources/CMake of a SDL3 framework +# This file is meant to be placed in Resources/CMake of a SDL3 framework for macOS, +# or in the CMake directory of a SDL3 framework for iOS / tvOS / visionOS. -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.12...4.0) -if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../Headers/SDL_version.h") - message(AUTHOR_WARNING "Could not find SDL_version.h. This script is meant to be placed in the Resources/CMake directory of SDL2.framework") +# Find SDL_version.h +set(_sdl_version_h_path "") +if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../Headers/SDL_version.h") + set(_sdl_version_h_path "${CMAKE_CURRENT_LIST_DIR}/../../Headers/SDL_version.h") +elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Headers/SDL_version.h") + set(_sdl_version_h_path "${CMAKE_CURRENT_LIST_DIR}/../Headers/SDL_version.h") +endif() + +if(NOT _sdl_version_h_path) + message(AUTHOR_WARNING "Could not find SDL_version.h. This script is meant to be placed in the Resources/CMake directory or the CMake directory of SDL3.framework.") + set(PACKAGE_VERSION_UNSUITABLE TRUE) return() endif() -file(READ "${CMAKE_CURRENT_LIST_DIR}/../../Headers/SDL_version.h" _sdl_version_h) +file(READ "${_sdl_version_h_path}" _sdl_version_h) string(REGEX MATCH "#define[ \t]+SDL_MAJOR_VERSION[ \t]+([0-9]+)" _sdl_major_re "${_sdl_version_h}") set(_sdl_major "${CMAKE_MATCH_1}") string(REGEX MATCH "#define[ \t]+SDL_MINOR_VERSION[ \t]+([0-9]+)" _sdl_minor_re "${_sdl_version_h}") @@ -21,9 +31,12 @@ if(_sdl_major_re AND _sdl_minor_re AND _sdl_micro_re) set(PACKAGE_VERSION "${_sdl_major}.${_sdl_minor}.${_sdl_micro}") else() message(AUTHOR_WARNING "Could not extract version from SDL_version.h.") + set(PACKAGE_VERSION_UNSUITABLE TRUE) return() endif() +unset(_sdl_version_h) +unset(_sdl_version_h_path) unset(_sdl_major_re) unset(_sdl_major) unset(_sdl_minor_re) diff --git a/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3ConfigVersion.cmake b/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3ConfigVersion.cmake index 6c833a7f..86979cef 100644 --- a/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3ConfigVersion.cmake +++ b/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3ConfigVersion.cmake @@ -16,6 +16,7 @@ set(_sdl3_version_h "${_sdl3_framework}/Headers/SDL_version.h") if(NOT EXISTS "${_sdl3_version_h}") message(AUTHOR_WARNING "Cannot not find ${_sdl3_framework}. This script is meant to be placed in share/cmake/SDL3, next to SDL3.xcframework") + set(PACKAGE_VERSION_UNSUITABLE TRUE) return() endif() @@ -36,6 +37,7 @@ if(_sdl_major_re AND _sdl_minor_re AND _sdl_micro_re) set(PACKAGE_VERSION "${_sdl_major}.${_sdl_minor}.${_sdl_micro}") else() message(AUTHOR_WARNING "Could not extract version from SDL_version.h.") + set(PACKAGE_VERSION_UNSUITABLE TRUE) return() endif() diff --git a/libs/SDL3/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj b/libs/SDL3/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj index 3a2e59d0..ce64b09f 100644 --- a/libs/SDL3/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj +++ b/libs/SDL3/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj @@ -19,7 +19,6 @@ F35E56E72983133F00A43A5F /* PBXTargetDependency */, DB0F490517CA5249008798C5 /* PBXTargetDependency */, DB0F490717CA5249008798C5 /* PBXTargetDependency */, - DB166E9816A1D7CF00A1396C /* PBXTargetDependency */, DB166E9616A1D7CD00A1396C /* PBXTargetDependency */, DB166E6C16A1D72000A1396C /* PBXTargetDependency */, DB166E5616A1D6B800A1396C /* PBXTargetDependency */, @@ -88,7 +87,6 @@ 00794EF009D23739003FC8A1 /* utf8.txt in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E6309D20839003FC8A1 /* utf8.txt */; }; 00794EF709D237DE003FC8A1 /* moose.dat in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5E09D20839003FC8A1 /* moose.dat */; }; 453774A5120915E3002F0F45 /* testshape.c in Sources */ = {isa = PBXBuildFile; fileRef = 453774A4120915E3002F0F45 /* testshape.c */; }; - 66E88E8B203B778F0004D44E /* testyuv_cvt.c in Sources */ = {isa = PBXBuildFile; fileRef = 66E88E8A203B778F0004D44E /* testyuv_cvt.c */; }; A1A8594E2BC72FC20045DD6C /* testautomation_properties.c in Sources */ = {isa = PBXBuildFile; fileRef = A1A859482BC72FC20045DD6C /* testautomation_properties.c */; }; A1A859502BC72FC20045DD6C /* testautomation_subsystems.c in Sources */ = {isa = PBXBuildFile; fileRef = A1A859492BC72FC20045DD6C /* testautomation_subsystems.c */; }; A1A859522BC72FC20045DD6C /* testautomation_log.c in Sources */ = {isa = PBXBuildFile; fileRef = A1A8594A2BC72FC20045DD6C /* testautomation_log.c */; }; @@ -117,23 +115,21 @@ DB166D9F16A1D1A500A1396C /* SDL_test_log.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166D9016A1D1A500A1396C /* SDL_test_log.c */; }; DB166DA016A1D1A500A1396C /* SDL_test_md5.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166D9116A1D1A500A1396C /* SDL_test_md5.c */; }; DB166DD716A1D37800A1396C /* testmessage.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CBD16A1C74100A1396C /* testmessage.c */; }; - DB166DDB16A1D42F00A1396C /* icon.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; }; + DB166DDB16A1D42F00A1396C /* icon.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.png */; }; DB166DF016A1D52500A1396C /* testrelative.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CBF16A1C74100A1396C /* testrelative.c */; }; DB166E0716A1D59400A1396C /* testrendercopyex.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CC016A1C74100A1396C /* testrendercopyex.c */; }; DB166E1E16A1D5C300A1396C /* testrendertarget.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CC116A1C74100A1396C /* testrendertarget.c */; }; - DB166E2216A1D5EC00A1396C /* sample.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E6109D20839003FC8A1 /* sample.bmp */; }; - DB166E2316A1D60B00A1396C /* icon.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; }; - DB166E2516A1D61900A1396C /* icon.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; }; - DB166E2616A1D61900A1396C /* sample.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E6109D20839003FC8A1 /* sample.bmp */; }; + DB166E2216A1D5EC00A1396C /* sample.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E6109D20839003FC8A1 /* sample.png */; }; + DB166E2316A1D60B00A1396C /* icon.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.png */; }; + DB166E2516A1D61900A1396C /* icon.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.png */; }; + DB166E2616A1D61900A1396C /* sample.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E6109D20839003FC8A1 /* sample.png */; }; DB166E3C16A1D66500A1396C /* testrumble.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CC216A1C74100A1396C /* testrumble.c */; }; - DB166E4D16A1D69000A1396C /* icon.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; }; - DB166E4E16A1D69000A1396C /* sample.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E6109D20839003FC8A1 /* sample.bmp */; }; + DB166E4D16A1D69000A1396C /* icon.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.png */; }; + DB166E4E16A1D69000A1396C /* sample.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E6109D20839003FC8A1 /* sample.png */; }; DB166E5416A1D6A300A1396C /* testscale.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CC316A1C74100A1396C /* testscale.c */; }; DB166E6A16A1D70C00A1396C /* testshader.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CC416A1C74100A1396C /* testshader.c */; }; DB166E9316A1D7BC00A1396C /* testspriteminimal.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CC516A1C74100A1396C /* testspriteminimal.c */; }; - DB166E9416A1D7C700A1396C /* teststreaming.c in Sources */ = {isa = PBXBuildFile; fileRef = DB166CC616A1C74100A1396C /* teststreaming.c */; }; - DB166E9A16A1D7F700A1396C /* moose.dat in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5E09D20839003FC8A1 /* moose.dat */; }; - DB166E9C16A1D80900A1396C /* icon.bmp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; }; + DB166E9C16A1D80900A1396C /* icon.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.png */; }; DB445EFB18184BB600B306B0 /* testdropfile.c in Sources */ = {isa = PBXBuildFile; fileRef = DB445EFA18184BB600B306B0 /* testdropfile.c */; }; DB89958418A19B130092407C /* testhotplug.c in Sources */ = {isa = PBXBuildFile; fileRef = DB89958318A19B130092407C /* testhotplug.c */; }; F35E56CF2983130F00A43A5F /* testautomation_main.c in Sources */ = {isa = PBXBuildFile; fileRef = F35E56B62983130A00A43A5F /* testautomation_main.c */; }; @@ -162,6 +158,7 @@ F36C34232C0F85DB00991150 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F36C342D2C0F869B00991150 /* testcamera.c in Sources */ = {isa = PBXBuildFile; fileRef = F36C342C2C0F869B00991150 /* testcamera.c */; }; F36C342E2C0F869B00991150 /* testcamera.c in Sources */ = {isa = PBXBuildFile; fileRef = F36C342C2C0F869B00991150 /* testcamera.c */; }; + F38908B72E81276900CE01D5 /* testautomation_blit.c in Sources */ = {isa = PBXBuildFile; fileRef = F38908B42E81276900CE01D5 /* testautomation_blit.c */; }; F399C64E2A78929400C86979 /* gamepadutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F399C6492A78929400C86979 /* gamepadutils.c */; }; F399C64F2A78929400C86979 /* gamepadutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F399C6492A78929400C86979 /* gamepadutils.c */; }; F399C6512A7892D800C86979 /* testautomation_intrinsics.c in Sources */ = {isa = PBXBuildFile; fileRef = F399C6502A7892D800C86979 /* testautomation_intrinsics.c */; }; @@ -182,13 +179,12 @@ F3C17C8228E4112900E1A26D /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; F3C17C8328E4124400E1A26D /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; F3C17C8428E4126400E1A26D /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; - F3C17C8528E4127D00E1A26D /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; F3C17CEB28E4177600E1A26D /* testgeometry.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17CD628E416AC00E1A26D /* testgeometry.c */; }; F3C17CEC28E417EB00E1A26D /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; F3C17D3928E424B800E1A26D /* sample.wav in Resources */ = {isa = PBXBuildFile; fileRef = 00794E6209D20839003FC8A1 /* sample.wav */; }; - F3C17D3B28E4252900E1A26D /* icon.bmp in Resources */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; }; + F3C17D3B28E4252900E1A26D /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.png */; }; F3C2CAC62C5C8BD6004D7998 /* unifont-15.1.05.hex in Resources */ = {isa = PBXBuildFile; fileRef = F3C2CAC52C5C8BD6004D7998 /* unifont-15.1.05.hex */; }; - F3C2CB072C5D3FB2004D7998 /* icon.bmp in Resources */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; }; + F3C2CB072C5D3FB2004D7998 /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.png */; }; F3CB56892A7895F800766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; F3CB568A2A7895F800766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F3CB568C2A7896BF00766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; @@ -267,8 +263,6 @@ F3CB56FB2A78983200766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F3CB56FD2A78983C00766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; F3CB56FE2A78983C00766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - F3CB57002A78984300766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; - F3CB57012A78984300766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F3CB57032A78984A00766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; F3CB57042A78984A00766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F3CB57062A78985400766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; @@ -279,6 +273,12 @@ F3CB570D2A78986000766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F3CB570F2A78986700766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; F3CB57102A78986700766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + F3DB65DF2E9DA90000568044 /* testutils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C17C7328E40ADE00E1A26D /* testutils.c */; }; + F3DB65E12E9DA90000568044 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; }; + F3DB65E52E9DA90000568044 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + F3DB65EE2E9DA95D00568044 /* testyuv.png in Resources */ = {isa = PBXBuildFile; fileRef = F3DB65ED2E9DA95D00568044 /* testyuv.png */; }; + F3DB65F12E9DA98E00568044 /* testyuv.c in Sources */ = {isa = PBXBuildFile; fileRef = F3DB65EF2E9DA98E00568044 /* testyuv.c */; }; + F3DB65F22E9DA9B400568044 /* testyuv_cvt.c in Sources */ = {isa = PBXBuildFile; fileRef = 66E88E8A203B778F0004D44E /* testyuv_cvt.c */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -569,13 +569,6 @@ remoteGlobalIDString = DB166E6D16A1D78400A1396C; remoteInfo = testspriteminimal; }; - DB166E9716A1D7CF00A1396C /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = DB166E8016A1D78C00A1396C; - remoteInfo = teststreaming; - }; F35E56E62983133F00A43A5F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; @@ -643,7 +636,7 @@ dstPath = ""; dstSubfolderSpec = 7; files = ( - DB166DDB16A1D42F00A1396C /* icon.bmp in CopyFiles */, + DB166DDB16A1D42F00A1396C /* icon.png in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -653,8 +646,8 @@ dstPath = ""; dstSubfolderSpec = 7; files = ( - DB166E2316A1D60B00A1396C /* icon.bmp in CopyFiles */, - DB166E2216A1D5EC00A1396C /* sample.bmp in CopyFiles */, + DB166E2316A1D60B00A1396C /* icon.png in CopyFiles */, + DB166E2216A1D5EC00A1396C /* sample.png in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -664,8 +657,8 @@ dstPath = ""; dstSubfolderSpec = 7; files = ( - DB166E2516A1D61900A1396C /* icon.bmp in CopyFiles */, - DB166E2616A1D61900A1396C /* sample.bmp in CopyFiles */, + DB166E2516A1D61900A1396C /* icon.png in CopyFiles */, + DB166E2616A1D61900A1396C /* sample.png in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -675,18 +668,8 @@ dstPath = ""; dstSubfolderSpec = 7; files = ( - DB166E4D16A1D69000A1396C /* icon.bmp in CopyFiles */, - DB166E4E16A1D69000A1396C /* sample.bmp in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DB166E9916A1D7EE00A1396C /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 7; - files = ( - DB166E9A16A1D7F700A1396C /* moose.dat in CopyFiles */, + DB166E4D16A1D69000A1396C /* icon.png in CopyFiles */, + DB166E4E16A1D69000A1396C /* sample.png in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -696,7 +679,7 @@ dstPath = ""; dstSubfolderSpec = 7; files = ( - DB166E9C16A1D80900A1396C /* icon.bmp in CopyFiles */, + DB166E9C16A1D80900A1396C /* icon.png in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1160,17 +1143,6 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; - F3CB57022A78984300766177 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - F3CB57012A78984300766177 /* SDL3.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; F3CB57052A78984A00766177 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1226,6 +1198,17 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + F3DB65E42E9DA90000568044 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + F3DB65E52E9DA90000568044 /* SDL3.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -1269,10 +1252,9 @@ 002F346A09CA204F00EBEB88 /* testplatform.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testplatform.app; sourceTree = BUILT_PRODUCTS_DIR; }; 002F346F09CA20A600EBEB88 /* testplatform.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = testplatform.c; sourceTree = ""; }; 003FA63A093FFD41000C53B3 /* SDL.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SDL.xcodeproj; path = ../SDL/SDL.xcodeproj; sourceTree = SOURCE_ROOT; }; - 00794E5D09D20839003FC8A1 /* icon.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = icon.bmp; sourceTree = ""; }; + 00794E5D09D20839003FC8A1 /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; 00794E5E09D20839003FC8A1 /* moose.dat */ = {isa = PBXFileReference; lastKnownFileType = file; path = moose.dat; sourceTree = ""; }; - 00794E5F09D20839003FC8A1 /* picture.xbm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = picture.xbm; sourceTree = ""; }; - 00794E6109D20839003FC8A1 /* sample.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = sample.bmp; sourceTree = ""; }; + 00794E6109D20839003FC8A1 /* sample.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = sample.png; sourceTree = ""; }; 00794E6209D20839003FC8A1 /* sample.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = sample.wav; sourceTree = ""; }; 00794E6309D20839003FC8A1 /* utf8.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = utf8.txt; sourceTree = ""; }; 083E4872006D84C97F000001 /* loopwave.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = loopwave.c; sourceTree = ""; }; @@ -1318,7 +1300,6 @@ DB166CC316A1C74100A1396C /* testscale.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = testscale.c; sourceTree = ""; }; DB166CC416A1C74100A1396C /* testshader.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = testshader.c; sourceTree = ""; }; DB166CC516A1C74100A1396C /* testspriteminimal.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = testspriteminimal.c; sourceTree = ""; }; - DB166CC616A1C74100A1396C /* teststreaming.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = teststreaming.c; sourceTree = ""; }; DB166D7F16A1D12400A1396C /* libSDL3_test.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSDL3_test.a; sourceTree = BUILT_PRODUCTS_DIR; }; DB166D8416A1D1A500A1396C /* SDL_test_assert.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_test_assert.c; sourceTree = ""; }; DB166D8516A1D1A500A1396C /* SDL_test_common.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_test_common.c; sourceTree = ""; }; @@ -1337,7 +1318,6 @@ DB166E5216A1D69000A1396C /* testscale.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testscale.app; sourceTree = BUILT_PRODUCTS_DIR; }; DB166E6816A1D6F300A1396C /* testshader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testshader.app; sourceTree = BUILT_PRODUCTS_DIR; }; DB166E7E16A1D78400A1396C /* testspriteminimal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testspriteminimal.app; sourceTree = BUILT_PRODUCTS_DIR; }; - DB166E9116A1D78C00A1396C /* teststreaming.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = teststreaming.app; sourceTree = BUILT_PRODUCTS_DIR; }; DB445EF818184B7000B306B0 /* testdropfile.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testdropfile.app; sourceTree = BUILT_PRODUCTS_DIR; }; DB445EFA18184BB600B306B0 /* testdropfile.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testdropfile.c; sourceTree = ""; }; DB89957E18A19ABA0092407C /* testhotplug.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testhotplug.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1367,6 +1347,9 @@ F35E56CD2983130F00A43A5F /* testautomation_mouse.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_mouse.c; sourceTree = ""; }; F36C34272C0F85DB00991150 /* testcamera.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testcamera.app; sourceTree = BUILT_PRODUCTS_DIR; }; F36C342C2C0F869B00991150 /* testcamera.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testcamera.c; sourceTree = ""; }; + F38908B42E81276900CE01D5 /* testautomation_blit.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = testautomation_blit.c; sourceTree = ""; }; + F38908B52E81276900CE01D5 /* testautomation_images.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = testautomation_images.h; sourceTree = ""; }; + F38908B62E81276900CE01D5 /* testautomation_suites.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = testautomation_suites.h; sourceTree = ""; }; F399C6492A78929400C86979 /* gamepadutils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gamepadutils.c; sourceTree = ""; }; F399C6502A7892D800C86979 /* testautomation_intrinsics.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_intrinsics.c; sourceTree = ""; }; F399C6542A78933000C86979 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; @@ -1377,6 +1360,10 @@ F3C17CD628E416AC00E1A26D /* testgeometry.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testgeometry.c; sourceTree = ""; }; F3C17CDC28E416CF00E1A26D /* testgeometry.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testgeometry.app; sourceTree = BUILT_PRODUCTS_DIR; }; F3C2CAC52C5C8BD6004D7998 /* unifont-15.1.05.hex */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "unifont-15.1.05.hex"; sourceTree = ""; }; + F3DB65E92E9DA90000568044 /* testyuv.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testyuv.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F3DB65ED2E9DA95D00568044 /* testyuv.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = testyuv.png; sourceTree = ""; }; + F3DB65EF2E9DA98E00568044 /* testyuv.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = testyuv.c; sourceTree = ""; }; + F3DB65F02E9DA98E00568044 /* testyuv_cvt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = testyuv_cvt.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1708,14 +1695,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DB166E8316A1D78C00A1396C /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - F3CB57002A78984300766177 /* SDL3.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; DB445EE918184B7000B306B0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1764,6 +1743,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F3DB65E02E9DA90000568044 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F3DB65E12E9DA90000568044 /* SDL3.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -1778,10 +1765,10 @@ 00794E4609D207B4003FC8A1 /* Resources */ = { isa = PBXGroup; children = ( - 00794E5D09D20839003FC8A1 /* icon.bmp */, + F3DB65ED2E9DA95D00568044 /* testyuv.png */, + 00794E5D09D20839003FC8A1 /* icon.png */, 00794E5E09D20839003FC8A1 /* moose.dat */, - 00794E5F09D20839003FC8A1 /* picture.xbm */, - 00794E6109D20839003FC8A1 /* sample.bmp */, + 00794E6109D20839003FC8A1 /* sample.png */, 00794E6209D20839003FC8A1 /* sample.wav */, F3C2CAC52C5C8BD6004D7998 /* unifont-15.1.05.hex */, 00794E6309D20839003FC8A1 /* utf8.txt */, @@ -1815,10 +1802,12 @@ 001795B01074222D00F5D044 /* testaudioinfo.c */, F35E56CC2983130F00A43A5F /* testautomation.c */, F35E56C42983130D00A43A5F /* testautomation_audio.c */, + F38908B42E81276900CE01D5 /* testautomation_blit.c */, F35E56BC2983130B00A43A5F /* testautomation_clipboard.c */, F35E56BB2983130B00A43A5F /* testautomation_events.c */, F35E56C92983130E00A43A5F /* testautomation_guid.c */, F35E56B72983130A00A43A5F /* testautomation_hints.c */, + F38908B52E81276900CE01D5 /* testautomation_images.h */, F35E56BF2983130C00A43A5F /* testautomation_images.c */, F399C6502A7892D800C86979 /* testautomation_intrinsics.c */, F35E56B92983130B00A43A5F /* testautomation_iostream.c */, @@ -1836,6 +1825,7 @@ F35E56C82983130E00A43A5F /* testautomation_sdltest.c */, F35E56BE2983130C00A43A5F /* testautomation_stdlib.c */, A1A859492BC72FC20045DD6C /* testautomation_subsystems.c */, + F38908B62E81276900CE01D5 /* testautomation_suites.h */, F35E56CB2983130F00A43A5F /* testautomation_surface.c */, A1A8594B2BC72FC20045DD6C /* testautomation_time.c */, F35E56BD2983130B00A43A5F /* testautomation_timer.c */, @@ -1880,12 +1870,13 @@ 453774A4120915E3002F0F45 /* testshape.c */, 0017991910743F5300F5D044 /* testsprite.c */, DB166CC516A1C74100A1396C /* testspriteminimal.c */, - DB166CC616A1C74100A1396C /* teststreaming.c */, 092D6D58FFB311A97F000001 /* testthread.c */, 083E4880006D86A17F000001 /* testtimer.c */, F3C17C7328E40ADE00E1A26D /* testutils.c */, 083E4882006D86A17F000001 /* testver.c */, 0017993B10743FEF00F5D044 /* testwm.c */, + F3DB65EF2E9DA98E00568044 /* testyuv.c */, + F3DB65F02E9DA98E00568044 /* testyuv_cvt.h */, 66E88E8A203B778F0004D44E /* testyuv_cvt.c */, 083E4887006D86A17F000001 /* torturethread.c */, ); @@ -1935,7 +1926,6 @@ DB166E5216A1D69000A1396C /* testscale.app */, DB166E6816A1D6F300A1396C /* testshader.app */, DB166E7E16A1D78400A1396C /* testspriteminimal.app */, - DB166E9116A1D78C00A1396C /* teststreaming.app */, DB0F48EC17CA51E5008798C5 /* testdrawchessboard.app */, DB0F490117CA5212008798C5 /* testfilesystem.app */, DB89957E18A19ABA0092407C /* testhotplug.app */, @@ -1944,6 +1934,7 @@ F35E56AA298312CB00A43A5F /* testautomation.app */, F36C34272C0F85DB00991150 /* testcamera.app */, F3B7FD6A2D73FC630086D1D0 /* testpen.app */, + F3DB65E92E9DA90000568044 /* testyuv.app */, ); name = Products; sourceTree = ""; @@ -2000,7 +1991,6 @@ dependencies = ( ); name = testatomic; - productName = testalpha; productReference = 0017958C10741F7900F5D044 /* testatomic.app */; productType = "com.apple.product-type.application"; }; @@ -2017,7 +2007,6 @@ dependencies = ( ); name = testaudioinfo; - productName = testalpha; productReference = 001795AD107421BF00F5D044 /* testaudioinfo.app */; productType = "com.apple.product-type.application"; }; @@ -2034,7 +2023,6 @@ dependencies = ( ); name = testgl; - productName = testalpha; productReference = 0017972110742F3200F5D044 /* testgl.app */; productType = "com.apple.product-type.application"; }; @@ -2051,7 +2039,6 @@ dependencies = ( ); name = testhaptic; - productName = testalpha; productReference = 00179748107430D600F5D044 /* testhaptic.app */; productType = "com.apple.product-type.application"; }; @@ -2068,7 +2055,6 @@ dependencies = ( ); name = testdraw; - productName = testalpha; productReference = 0017976E107431B300F5D044 /* testdraw.app */; productType = "com.apple.product-type.application"; }; @@ -2086,7 +2072,6 @@ dependencies = ( ); name = testime; - productName = testalpha; productReference = 0017978E107432AE00F5D044 /* testime.app */; productType = "com.apple.product-type.application"; }; @@ -2103,7 +2088,6 @@ dependencies = ( ); name = testintersections; - productName = testalpha; productReference = 001797AE1074334C00F5D044 /* testintersections.app */; productType = "com.apple.product-type.application"; }; @@ -2120,7 +2104,6 @@ dependencies = ( ); name = testloadso; - productName = testalpha; productReference = 001797D0107433C600F5D044 /* testloadso.app */; productType = "com.apple.product-type.application"; }; @@ -2138,7 +2121,6 @@ dependencies = ( ); name = testmultiaudio; - productName = testalpha; productReference = 001798121074355200F5D044 /* testmultiaudio.app */; productType = "com.apple.product-type.application"; }; @@ -2156,7 +2138,6 @@ dependencies = ( ); name = testnative; - productName = testalpha; productReference = 001798941074392D00F5D044 /* testnative.app */; productType = "com.apple.product-type.application"; }; @@ -2173,7 +2154,6 @@ dependencies = ( ); name = testpower; - productName = testalpha; productReference = 001798B5107439DF00F5D044 /* testpower.app */; productType = "com.apple.product-type.application"; }; @@ -2190,7 +2170,6 @@ dependencies = ( ); name = testresample; - productName = testalpha; productReference = 001798F210743BEC00F5D044 /* testresample.app */; productType = "com.apple.product-type.application"; }; @@ -2208,7 +2187,6 @@ dependencies = ( ); name = testsprite; - productName = testalpha; productReference = 0017991610743F1000F5D044 /* testsprite.app */; productType = "com.apple.product-type.application"; }; @@ -2225,7 +2203,6 @@ dependencies = ( ); name = testwm; - productName = testalpha; productReference = 0017993810743FB700F5D044 /* testwm.app */; productType = "com.apple.product-type.application"; }; @@ -2242,7 +2219,6 @@ dependencies = ( ); name = testfile; - productName = testalpha; productReference = 002F341209CA1BFF00EBEB88 /* testfile.app */; productType = "com.apple.product-type.application"; }; @@ -2260,7 +2236,6 @@ dependencies = ( ); name = testiconv; - productName = testalpha; productReference = 002F343109CA1F0300EBEB88 /* testiconv.app */; productType = "com.apple.product-type.application"; }; @@ -2278,7 +2253,6 @@ dependencies = ( ); name = testoverlay; - productName = testalpha; productReference = 002F344D09CA1FB300EBEB88 /* testoverlay.app */; productType = "com.apple.product-type.application"; }; @@ -2295,7 +2269,6 @@ dependencies = ( ); name = testplatform; - productName = testalpha; productReference = 002F346A09CA204F00EBEB88 /* testplatform.app */; productType = "com.apple.product-type.application"; }; @@ -2518,7 +2491,6 @@ dependencies = ( ); name = testdrawchessboard; - productName = testalpha; productReference = DB0F48EC17CA51E5008798C5 /* testdrawchessboard.app */; productType = "com.apple.product-type.application"; }; @@ -2535,7 +2507,6 @@ dependencies = ( ); name = testfilesystem; - productName = testalpha; productReference = DB0F490117CA5212008798C5 /* testfilesystem.app */; productType = "com.apple.product-type.application"; }; @@ -2569,7 +2540,6 @@ dependencies = ( ); name = testmessage; - productName = testalpha; productReference = DB166DD516A1D36A00A1396C /* testmessage.app */; productType = "com.apple.product-type.application"; }; @@ -2586,7 +2556,6 @@ dependencies = ( ); name = testrelative; - productName = testalpha; productReference = DB166DEE16A1D50C00A1396C /* testrelative.app */; productType = "com.apple.product-type.application"; }; @@ -2604,7 +2573,6 @@ dependencies = ( ); name = testrendercopyex; - productName = testalpha; productReference = DB166E0516A1D57C00A1396C /* testrendercopyex.app */; productType = "com.apple.product-type.application"; }; @@ -2622,7 +2590,6 @@ dependencies = ( ); name = testrendertarget; - productName = testalpha; productReference = DB166E1C16A1D5AD00A1396C /* testrendertarget.app */; productType = "com.apple.product-type.application"; }; @@ -2639,7 +2606,6 @@ dependencies = ( ); name = testrumble; - productName = testalpha; productReference = DB166E3816A1D64D00A1396C /* testrumble.app */; productType = "com.apple.product-type.application"; }; @@ -2657,7 +2623,6 @@ dependencies = ( ); name = testscale; - productName = testalpha; productReference = DB166E5216A1D69000A1396C /* testscale.app */; productType = "com.apple.product-type.application"; }; @@ -2696,24 +2661,6 @@ productReference = DB166E7E16A1D78400A1396C /* testspriteminimal.app */; productType = "com.apple.product-type.application"; }; - DB166E8016A1D78C00A1396C /* teststreaming */ = { - isa = PBXNativeTarget; - buildConfigurationList = DB166E8E16A1D78C00A1396C /* Build configuration list for PBXNativeTarget "teststreaming" */; - buildPhases = ( - DB166E8116A1D78C00A1396C /* Sources */, - DB166E8316A1D78C00A1396C /* Frameworks */, - DB166E9916A1D7EE00A1396C /* CopyFiles */, - F3CB57022A78984300766177 /* Embed Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = teststreaming; - productName = teststreaming; - productReference = DB166E9116A1D78C00A1396C /* teststreaming.app */; - productType = "com.apple.product-type.application"; - }; DB445EE618184B7000B306B0 /* testdropfile */ = { isa = PBXNativeTarget; buildConfigurationList = DB445EF518184B7000B306B0 /* Build configuration list for PBXNativeTarget "testdropfile" */; @@ -2744,7 +2691,6 @@ dependencies = ( ); name = testhotplug; - productName = testalpha; productReference = DB89957E18A19ABA0092407C /* testhotplug.app */; productType = "com.apple.product-type.application"; }; @@ -2761,7 +2707,6 @@ dependencies = ( ); name = testautomation; - productName = testalpha; productReference = F35E56AA298312CB00A43A5F /* testautomation.app */; productType = "com.apple.product-type.application"; }; @@ -2778,7 +2723,6 @@ dependencies = ( ); name = testcamera; - productName = testalpha; productReference = F36C34272C0F85DB00991150 /* testcamera.app */; productType = "com.apple.product-type.application"; }; @@ -2795,7 +2739,6 @@ dependencies = ( ); name = testpen; - productName = testalpha; productReference = F3B7FD6A2D73FC630086D1D0 /* testpen.app */; productType = "com.apple.product-type.application"; }; @@ -2816,6 +2759,23 @@ productReference = F3C17CDC28E416CF00E1A26D /* testgeometry.app */; productType = "com.apple.product-type.application"; }; + F3DB65DC2E9DA90000568044 /* testyuv */ = { + isa = PBXNativeTarget; + buildConfigurationList = F3DB65E62E9DA90000568044 /* Build configuration list for PBXNativeTarget "testyuv" */; + buildPhases = ( + F3DB65DD2E9DA90000568044 /* Sources */, + F3DB65E02E9DA90000568044 /* Frameworks */, + F3DB65E22E9DA90000568044 /* Resources */, + F3DB65E42E9DA90000568044 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = testyuv; + productReference = F3DB65E92E9DA90000568044 /* testyuv.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -2945,9 +2905,6 @@ DB166E6D16A1D78400A1396C = { ProvisioningStyle = Automatic; }; - DB166E8016A1D78C00A1396C = { - ProvisioningStyle = Automatic; - }; DB445EE618184B7000B306B0 = { ProvisioningStyle = Automatic; }; @@ -3028,11 +2985,11 @@ 4537749112091504002F0F45 /* testshape */, 001798FE10743F1000F5D044 /* testsprite */, DB166E6D16A1D78400A1396C /* testspriteminimal */, - DB166E8016A1D78C00A1396C /* teststreaming */, BEC567230761D90400A33029 /* testthread */, BEC5678D0761D90500A33029 /* testtimer */, BEC567A70761D90500A33029 /* testversion */, 0017992010743FB700F5D044 /* testwm */, + F3DB65DC2E9DA90000568044 /* testyuv */, BEC567EA0761D90600A33029 /* torturethread */, ); }; @@ -3061,7 +3018,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - F3C17D3B28E4252900E1A26D /* icon.bmp in Resources */, + F3C17D3B28E4252900E1A26D /* icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3069,11 +3026,19 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - F3C2CB072C5D3FB2004D7998 /* icon.bmp in Resources */, + F3C2CB072C5D3FB2004D7998 /* icon.png in Resources */, F3C2CAC62C5C8BD6004D7998 /* unifont-15.1.05.hex in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; + F3DB65E22E9DA90000568044 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F3DB65EE2E9DA95D00568044 /* testyuv.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -3216,7 +3181,6 @@ buildActionMask = 2147483647; files = ( 002F345409CA202000EBEB88 /* testoverlay.c in Sources */, - 66E88E8B203B778F0004D44E /* testyuv_cvt.c in Sources */, F3C17C7F28E4101000E1A26D /* testutils.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3432,15 +3396,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DB166E8116A1D78C00A1396C /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - DB166E9416A1D7C700A1396C /* teststreaming.c in Sources */, - F3C17C8528E4127D00E1A26D /* testutils.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; DB445EE718184B7000B306B0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -3478,6 +3433,7 @@ F35E56DE2983130F00A43A5F /* testautomation_joystick.c in Sources */, F35E56D82983130F00A43A5F /* testautomation_images.c in Sources */, F35E56DC2983130F00A43A5F /* testautomation_audio.c in Sources */, + F38908B72E81276900CE01D5 /* testautomation_blit.c in Sources */, F35E56D32983130F00A43A5F /* testautomation_math.c in Sources */, F35E56E02983130F00A43A5F /* testautomation_sdltest.c in Sources */, F35E56D42983130F00A43A5F /* testautomation_events.c in Sources */, @@ -3516,6 +3472,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F3DB65DD2E9DA90000568044 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F3DB65DF2E9DA90000568044 /* testutils.c in Sources */, + F3DB65F22E9DA9B400568044 /* testyuv_cvt.c in Sources */, + F3DB65F12E9DA98E00568044 /* testyuv.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -3725,11 +3691,6 @@ target = DB166E6D16A1D78400A1396C /* testspriteminimal */; targetProxy = DB166E9516A1D7CD00A1396C /* PBXContainerItemProxy */; }; - DB166E9816A1D7CF00A1396C /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = DB166E8016A1D78C00A1396C /* teststreaming */; - targetProxy = DB166E9716A1D7CF00A1396C /* PBXContainerItemProxy */; - }; F35E56E72983133F00A43A5F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F35E56A2298312CB00A43A5F /* testautomation */; @@ -4053,7 +4014,7 @@ GENERATE_INFOPLIST_FILE = YES; HEADER_SEARCH_PATHS = ../../include; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.9; @@ -4061,10 +4022,11 @@ ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; PRODUCT_BUNDLE_IDENTIFIER = "org.libsdl.$(PRODUCT_NAME)"; - SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos"; - SUPPORTS_MACCATALYST = NO; + SUPPORTED_PLATFORMS = "xrsimulator xros macosx iphonesimulator iphoneos appletvsimulator appletvos"; + SUPPORTS_MACCATALYST = YES; TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 9.0; + TVOS_DEPLOYMENT_TARGET = 11.0; + XROS_DEPLOYMENT_TARGET = 1.3; }; name = Debug; }; @@ -4227,17 +4189,18 @@ GENERATE_INFOPLIST_FILE = YES; HEADER_SEARCH_PATHS = ../../include; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.9; MARKETING_VERSION = 1.0; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; PRODUCT_BUNDLE_IDENTIFIER = "org.libsdl.$(PRODUCT_NAME)"; - SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos"; - SUPPORTS_MACCATALYST = NO; + SUPPORTED_PLATFORMS = "xrsimulator xros macosx iphonesimulator iphoneos appletvsimulator appletvos"; + SUPPORTS_MACCATALYST = YES; TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 9.0; + TVOS_DEPLOYMENT_TARGET = 11.0; + XROS_DEPLOYMENT_TARGET = 1.3; }; name = Release; }; @@ -4476,7 +4439,6 @@ ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; EXECUTABLE_PREFIX = lib; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos"; SUPPORTS_MACCATALYST = YES; }; name = Debug; @@ -4487,7 +4449,6 @@ ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; EXECUTABLE_PREFIX = lib; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos"; SUPPORTS_MACCATALYST = YES; }; name = Release; @@ -4652,26 +4613,6 @@ }; name = Release; }; - DB166E8F16A1D78C00A1396C /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = teststreaming; - PROVISIONING_PROFILE_SPECIFIER = ""; - }; - name = Debug; - }; - DB166E9016A1D78C00A1396C /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = teststreaming; - PROVISIONING_PROFILE_SPECIFIER = ""; - }; - name = Release; - }; DB445EF618184B7000B306B0 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -4756,6 +4697,25 @@ }; name = Release; }; + F37E49E22EB5250B00E508F7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + F37E49E32EB5250B00E508F7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; F3B7FD682D73FC630086D1D0 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -5186,15 +5146,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; - DB166E8E16A1D78C00A1396C /* Build configuration list for PBXNativeTarget "teststreaming" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - DB166E8F16A1D78C00A1396C /* Debug */, - DB166E9016A1D78C00A1396C /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; - }; DB445EF518184B7000B306B0 /* Build configuration list for PBXNativeTarget "testdropfile" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -5249,6 +5200,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; + F3DB65E62E9DA90000568044 /* Build configuration list for PBXNativeTarget "testyuv" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F37E49E22EB5250B00E508F7 /* Debug */, + F37E49E32EB5250B00E508F7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; /* End XCConfigurationList section */ }; rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; diff --git a/libs/SDL3/Xcode/SDLTest/config.xcconfig b/libs/SDL3/Xcode/SDLTest/config.xcconfig index 64479226..4a0292e5 100644 --- a/libs/SDL3/Xcode/SDLTest/config.xcconfig +++ b/libs/SDL3/Xcode/SDLTest/config.xcconfig @@ -10,5 +10,7 @@ // This allows you to set DEVELOPMENT_TEAM for all targets, for example. #include? "build.xcconfig" +INFOPLIST_FILE = test-Info.plist + CONFIG_FRAMEWORK_LDFLAGS = -lSDL3_test diff --git a/libs/SDL3/Xcode/SDLTest/test-Info.plist b/libs/SDL3/Xcode/SDLTest/test-Info.plist new file mode 100644 index 00000000..83728a1c --- /dev/null +++ b/libs/SDL3/Xcode/SDLTest/test-Info.plist @@ -0,0 +1,20 @@ + + + + + UILaunchScreen + + UIColorName + + UIImageName + + + UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + + diff --git a/libs/SDL3/android-project/app/build.gradle b/libs/SDL3/android-project/app/build.gradle index 989ef34c..b9d75b14 100644 --- a/libs/SDL3/android-project/app/build.gradle +++ b/libs/SDL3/android-project/app/build.gradle @@ -7,6 +7,7 @@ def buildWithCMake = project.hasProperty('BUILD_WITH_CMAKE'); android { namespace = "org.libsdl.app" compileSdkVersion 35 + ndkVersion = "28.2.13676358" defaultConfig { minSdkVersion 21 targetSdkVersion 35 diff --git a/libs/SDL3/android-project/app/jni/Application.mk b/libs/SDL3/android-project/app/jni/Application.mk index 023bc20d..1f7c0c10 100644 --- a/libs/SDL3/android-project/app/jni/Application.mk +++ b/libs/SDL3/android-project/app/jni/Application.mk @@ -7,4 +7,4 @@ APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 # Min runtime API level -APP_PLATFORM=android-16 +APP_PLATFORM=android-21 diff --git a/libs/SDL3/android-project/app/jni/src/CMakeLists.txt b/libs/SDL3/android-project/app/jni/src/CMakeLists.txt index 41a82f2a..02128376 100644 --- a/libs/SDL3/android-project/app/jni/src/CMakeLists.txt +++ b/libs/SDL3/android-project/app/jni/src/CMakeLists.txt @@ -3,24 +3,7 @@ cmake_minimum_required(VERSION 3.6) project(my_app) if(NOT TARGET SDL3::SDL3) - find_package(SDL3 CONFIG) -endif() - -if(NOT TARGET SDL3::SDL3) - find_library(SDL3_LIBRARY NAMES "SDL3") - find_path(SDL3_INCLUDE_DIR NAMES "SDL3/SDL.h") - add_library(SDL3::SDL3 UNKNOWN IMPORTED) - set_property(TARGET SDL3::SDL3 PROPERTY IMPORTED_LOCATION "${SDL3_LIBRARY}") - set_property(TARGET SDL3::SDL3 PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${SDL3_INCLUDE_DIR}") -endif() - -if(NOT TARGET SDL3::SDL3) - message(FATAL_ERROR "Cannot find SDL3. - -Possible ways to fix this: -- Use a SDL3 Android aar archive, and configure gradle to use it: prefab is required. -- Add add_subdirectory(path/to/SDL) to your CMake script, and make sure a vendored SDL is present there. -") + find_package(SDL3 CONFIG REQUIRED) endif() add_library(main SHARED diff --git a/libs/SDL3/android-project/app/proguard-rules.pro b/libs/SDL3/android-project/app/proguard-rules.pro index 147cbd37..a1376896 100644 --- a/libs/SDL3/android-project/app/proguard-rules.pro +++ b/libs/SDL3/android-project/app/proguard-rules.pro @@ -23,14 +23,13 @@ void clipboardSetText(java.lang.String); int createCustomCursor(int[], int, int, int, int); void destroyCustomCursor(int); - android.content.Context getContext(); + android.app.Activity getContext(); boolean getManifestEnvironmentVariables(); android.view.Surface getNativeSurface(); void initTouch(); boolean isAndroidTV(); boolean isChromebook(); boolean isDeXMode(); - boolean isScreenKeyboardShown(); boolean isTablet(); void manualBackButton(); int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]); @@ -49,7 +48,7 @@ boolean showTextInput(int, int, int, int, int); boolean supportsRelativeMouse(); int openFileDescriptor(java.lang.String, java.lang.String); - boolean showFileDialog(java.lang.String[], boolean, boolean, int); + boolean showFileDialog(java.lang.String[], boolean, int, java.lang.String, int); java.lang.String getPreferredLocales(); java.lang.String formatLocale(java.util.Locale); } @@ -69,7 +68,9 @@ } -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager { + void joystickSetSensorsEnabled(int, boolean); void pollInputDevices(); + void joystickSetLED(int, int, int, int); void pollHapticDevices(); void hapticRun(int, float, int); void hapticRumble(int, float, float, int); diff --git a/libs/SDL3/android-project/app/src/main/AndroidManifest.xml b/libs/SDL3/android-project/app/src/main/AndroidManifest.xml index f3a7cd5c..ab43f76a 100644 --- a/libs/SDL3/android-project/app/src/main/AndroidManifest.xml +++ b/libs/SDL3/android-project/app/src/main/AndroidManifest.xml @@ -71,6 +71,7 @@ android:icon="@mipmap/ic_launcher" android:allowBackup="true" android:theme="@style/AppTheme" + android:enableOnBackInvokedCallback="false" android:hardwareAccelerated="true" > \n\n"; + print $fh "# $envvartitle\n\n"; + + if (defined $envvardesc) { + my $desc = "$envvardesc"; + $desc =~ s/\\n/\n/g; # replace "\n" strings with actual newlines. + print $fh "$desc\n\n"; + } + + print $fh "## Environment Variable List\n\n"; + + foreach (sort keys %headersyms) { + my $sym = $_; + next if $headersymstype{$sym} != 2; # not a #define? skip it. + my $hint = "$_"; + next if not $hint =~ s/$envvarsymregex/$replace/ee; + + my $brief = $$briefsref{$sym}; + if (not defined $brief) { + $brief = ''; + } else { + $brief = "$brief"; + chomp($brief); + my $thiswikitype = defined $wikitypes{$sym} ? $wikitypes{$sym} : 'md'; # default to MarkDown for new stuff. + $brief = ": " . dewikify($thiswikitype, $brief); + } + print $fh "- [$hint]($sym)$brief\n"; + } + + print $fh "\n"; + + close($fh); + + rename($tmppath, $path) or die("Can't rename '$tmppath' to '$path': $!\n"); +} + + + + my $incpath = "$srcpath"; $incpath .= "/$incsubdir" if $incsubdir ne ''; @@ -1093,7 +1165,7 @@ while (my $d = readdir(DH)) { } elsif ($ignoring_lines) { push @contents, $_; next; - } elsif (/\A\s*\#\s*ifndef\s+SDL_WIKI_DOCUMENTATION_SECTION\s*\Z/) { + } elsif (/\A\s*\#\s*ifndef\s+$wikidocsectionsym\s*\Z/) { $ignoring_lines = 1; push @contents, $_; next; @@ -1102,13 +1174,13 @@ while (my $d = readdir(DH)) { #print("CATEGORY FOR '$dent' CHANGED TO " . (defined($current_wiki_category) ? "'$current_wiki_category'" : '(undef)') . "\n"); push @contents, $_; next; - } elsif (/\A\s*extern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_|SDL_)?DECLSPEC/) { # a function declaration without a doxygen comment? + } elsif (/\A\s*extern\s+(?:$deprecatedsym\s+|)$declspecsym/) { # a function declaration without a doxygen comment? $symtype = 1; # function declaration @templines = (); $decl = $_; $str = ''; $has_doxygen = 0; - } elsif (/\A\s*SDL_FORCE_INLINE/) { # a (forced-inline) function declaration without a doxygen comment? + } elsif (/\A\s*$forceinlinesym/) { # a (forced-inline) function declaration without a doxygen comment? $symtype = 1; # function declaration @templines = (); $decl = $_; @@ -1175,9 +1247,9 @@ while (my $d = readdir(DH)) { $lineno++ if defined $decl; $decl = '' if not defined $decl; chomp($decl); - if ($decl =~ /\A\s*extern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_|SDL_)?DECLSPEC/) { + if ($decl =~ /\A\s*extern\s+(?:$deprecatedsym\s+|)$declspecsym/) { $symtype = 1; # function declaration - } elsif ($decl =~ /\A\s*SDL_FORCE_INLINE/) { + } elsif ($decl =~ /\A\s*$forceinlinesym/) { $symtype = 1; # (forced-inline) function declaration } elsif ($decl =~ /\A\s*\#\s*define\s+/) { $symtype = 2; # macro @@ -1214,7 +1286,7 @@ while (my $d = readdir(DH)) { } $headercategorydocs{$current_wiki_category} = $sym; } elsif ($symtype == 1) { # a function - my $is_forced_inline = ($decl =~ /\A\s*SDL_FORCE_INLINE/); + my $is_forced_inline = ($decl =~ /\A\s*$forceinlinesym/); if ($is_forced_inline) { if (not $decl =~ /\)\s*(\{.*|)\s*\Z/) { @@ -1251,14 +1323,14 @@ while (my $d = readdir(DH)) { my $paramsstr = undef; - if (!$is_forced_inline && $decl =~ /\A\s*extern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_|SDL_)?DECLSPEC\w*\s+(const\s+|)(unsigned\s+|)(.*?)([\*\s]+)(\*?)\s*SDLCALL\s+(.*?)\s*\((.*?)\);/) { - $sym = $8; - $rettype = "$3$4$5$6"; - $paramsstr = $9; - } elsif ($is_forced_inline && $decl =~ /\A\s*SDL_FORCE_INLINE\s+(SDL_DEPRECATED\s+|)(const\s+|)(unsigned\s+|)(.*?)([\*\s]+)(.*?)\s*\((.*?)\);/) { + if (!$is_forced_inline && $decl =~ /\A\s*extern\s+(?:$deprecatedsym\s+|)$declspecsym\w*\s+(const\s+|)(unsigned\s+|)(.*?)([\*\s]+)(\*?)\s*$callconvsym\s+(.*?)\s*\((.*?)\);/) { $sym = $6; - $rettype = "$2$3$4$5"; + $rettype = "$1$2$3$4$5"; $paramsstr = $7; + } elsif ($is_forced_inline && $decl =~ /\A\s*$forceinlinesym\s+(?:$deprecatedsym\s+|)(const\s+|)(unsigned\s+|)(.*?)([\*\s]+)(.*?)\s*\((.*?)\);/) { + $sym = $5; + $rettype = "$1$2$3$4"; + $paramsstr = $6; } else { #print "Found doxygen but no function sig:\n$str\n\n"; foreach (@templines) { @@ -1324,7 +1396,7 @@ while (my $d = readdir(DH)) { $decl = $_; $temp = $decl; - $temp =~ s/\Aextern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_|SDL_)?DECLSPEC\w*\s+(.*?)\s+(\*?)SDLCALL\s+/$3$4 /; + $temp =~ s/\Aextern\s+(?:$deprecatedsym\s+|)$declspecsym\w*\s+(.*?)\s+(\*?)$callconvsym\s+/$1$2 /; $shrink_length = length($decl) - length($temp); $decl = $temp; } else { @@ -1389,7 +1461,7 @@ while (my $d = readdir(DH)) { } $decl .= $additional_decl; } elsif ($symtype == 2) { # a macro - if ($decl =~ /\A\s*\#\s*define\s+(.*?)(\(.*?\)|)\s+/) { + if ($decl =~ /\A\s*\#\s*define\s+(.*?)(\(.*?\)|)(\s+|\Z)/) { $sym = $1; } else { #print "Found doxygen but no macro:\n$str\n\n"; @@ -2089,7 +2161,7 @@ if ($copy_direction == 1) { # --copy-to-headers 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-". + if ($dent =~ /\A(README|INTRO)\-.*?\.md\Z/) { # we only bridge Markdown files here that start with "README-" or "INTRO-". filecopy("$wikipath/$dent", "$readmepath/$dent", "\n"); } } @@ -2199,10 +2271,10 @@ if ($copy_direction == 1) { # --copy-to-headers $desc =~ s/[\s\n]+\Z//ms; - if (0) { # !!! FIXME: disabled because it's not currently suitable for general use, but for manually inspecting the output, it can be useful. - if (($desc =~ /\A[A-Z]/) && (not $desc =~ /\ASDL_/)) { - print STDERR "WARNING: $sym\'s '\\param $arg' text starts with a capital letter: '$desc'. Fixing.\n"; - $desc = lcfirst($desc); + if (0) { + if (($desc =~ /\A[a-z]/) && (not $desc =~ /$apiprefixregex/)) { + print STDERR "WARNING: $sym\'s '\\param $arg' text starts with a lowercase letter: '$desc'. Fixing.\n"; + $desc = ucfirst($desc); } } @@ -2246,8 +2318,8 @@ if ($copy_direction == 1) { # --copy-to-headers } $desc =~ s/[\s\n]+\Z//ms; - if (0) { # !!! FIXME: disabled because it's not currently suitable for general use, but for manually inspecting the output, it can be useful. - if (($desc =~ /\A[A-Z]/) && (not $desc =~ /\ASDL_/)) { + if (0) { + if (($desc =~ /\A[A-Z]/) && (not $desc =~ /$apiprefixregex/)) { print STDERR "WARNING: $sym\'s '\\returns' text starts with a capital letter: '$desc'. Fixing.\n"; $desc = lcfirst($desc); } @@ -2391,7 +2463,7 @@ if ($copy_direction == 1) { # --copy-to-headers } else { die("Unexpected symbol type $symtype!"); } - my $str = "This $symtypename is available since SDL 3.0.0."; + my $str = "This $symtypename is available since $projectshortname 3.0.0."; $sections{'Version'} = wordwrap(wikify($wikitype, $str)) . "\n"; } } @@ -2702,7 +2774,7 @@ __EOF__ 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 that start with "README-". + if ($dent =~ /\A(README|INTRO)\-.*?\.md\Z/) { # we only bridge Markdown files here that start with "README-" or "INTRO". filecopy("$readmepath/$dent", "$wikipath/$dent", "\n"); } } @@ -2712,7 +2784,7 @@ __EOF__ opendir(DH, $wikipath) or die("Can't opendir '$wikipath': $!\n"); while (my $d = readdir(DH)) { my $dent = $d; - if ($dent =~ /\A(README\-.*?)\.md\Z/) { + if ($dent =~ /\A((README|INTRO)\-.*?)\.md\Z/) { push @pages, $1; } } @@ -2733,6 +2805,11 @@ __EOF__ generate_quickref(\%briefs, "$wikipath/QuickReference.md", 0); generate_quickref(\%briefs, "$wikipath/QuickReferenceNoUnicode.md", 1); } + + if ($envvarenabled and defined $envvarsymregex and defined $envvarsymreplace) { + generate_envvar_wiki_page(\%briefs, "$wikipath/EnvironmentVariables.md"); + } + } elsif ($copy_direction == -2) { # --copy-to-manpages # This only takes from the wiki data, since it has sections we omit from the headers, like code examples. @@ -2835,6 +2912,11 @@ __EOF__ $brief = shift @briefsplit; $brief = dewikify($wikitype, $brief); + # Hack: `apropros` doesn't like escaped character things like `\[char46]` for `.`...since almost every + # manpage will end their Brief section with a period and it won't wordwrap to risk being a groff control + # character, just replace it. + $brief =~ s/\\\[char46\]/./g; + if (defined $remarks) { $remarks = dewikify($wikitype, join("\n", @briefsplit) . $remarks); } @@ -2851,7 +2933,7 @@ __EOF__ $str .= ".\\\" Please report issues in this manpage's content at:\n"; $str .= ".\\\" $bugreporturl\n"; $str .= ".\\\" Please report issues in the generation of this manpage from the wiki at:\n"; - $str .= ".\\\" https://github.com/libsdl-org/SDL/issues/new?title=Misgenerated%20manpage%20for%20$sym\n"; + $str .= ".\\\" https://github.com/libsdl-org/SDL/issues/new?title=Misgenerated%20manpage%20for%20$sym\n"; # !!! FIXME: if this becomes a problem for other projects, we'll generalize this. $str .= ".\\\" $projectshortname can be found at $projecturl\n"; # Define a .URL macro. The "www.tmac" thing decides if we're using GNU roff (which has a .URL macro already), and if so, overrides the macro we just created. diff --git a/libs/SDL3/cmake/FindLibUSB.cmake b/libs/SDL3/cmake/FindLibUSB.cmake index 24887350..45b7e387 100644 --- a/libs/SDL3/cmake/FindLibUSB.cmake +++ b/libs/SDL3/cmake/FindLibUSB.cmake @@ -10,7 +10,7 @@ if(PKG_CONFIG_FOUND) endif() find_library(LibUSB_LIBRARY - NAMES usb-1.0 libusb-1.0 + NAMES usb-1.0 libusb-1.0 usb HINTS ${PC_LibUSB_LIBRARY_DIRS} ) diff --git a/libs/SDL3/cmake/GetGitRevisionDescription.cmake b/libs/SDL3/cmake/GetGitRevisionDescription.cmake index a08895c6..70c554e3 100644 --- a/libs/SDL3/cmake/GetGitRevisionDescription.cmake +++ b/libs/SDL3/cmake/GetGitRevisionDescription.cmake @@ -164,6 +164,22 @@ function(get_git_head_revision _refspecvar _hashvar) "${GIT_DATA}/grabRef.cmake" @ONLY) include("${GIT_DATA}/grabRef.cmake") + # Fallback for reftable or other storage formats + if(NOT HEAD_HASH OR HEAD_HASH STREQUAL "") + find_package(Git QUIET) + if(GIT_FOUND) + execute_process( + COMMAND "${GIT_EXECUTABLE}" rev-parse HEAD + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE HEAD_HASH + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(HEAD_HASH "") + endif() + endif() + endif() + set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) diff --git a/libs/SDL3/cmake/GetGitRevisionDescription.cmake.in b/libs/SDL3/cmake/GetGitRevisionDescription.cmake.in index 116efc4e..81b4213e 100644 --- a/libs/SDL3/cmake/GetGitRevisionDescription.cmake.in +++ b/libs/SDL3/cmake/GetGitRevisionDescription.cmake.in @@ -25,19 +25,21 @@ if(HEAD_CONTENTS MATCHES "ref") string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") if(EXISTS "@GIT_DIR@/${HEAD_REF}") configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) - else() + elseif(EXISTS "@GIT_DIR@/packed-refs") configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") set(HEAD_HASH "${CMAKE_MATCH_1}") endif() + elseif(EXISTS "@GIT_DIR@/reftable/tables.list") + configure_file("@GIT_DIR@/reftable/tables.list" "@GIT_DATA@/reftable-tables.list" COPYONLY) endif() else() # detached HEAD configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) endif() -if(NOT HEAD_HASH) +if(NOT HEAD_HASH AND EXISTS "@GIT_DATA@/head-ref") file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) string(STRIP "${HEAD_HASH}" HEAD_HASH) endif() diff --git a/libs/SDL3/cmake/PkgConfigHelper.cmake b/libs/SDL3/cmake/PkgConfigHelper.cmake index 7070fac7..44d74310 100644 --- a/libs/SDL3/cmake/PkgConfigHelper.cmake +++ b/libs/SDL3/cmake/PkgConfigHelper.cmake @@ -1,16 +1,21 @@ # Helper for Find modules function(get_flags_from_pkg_config _library _pc_prefix _out_prefix) - if("${_library}" MATCHES "${CMAKE_STATIC_LIBRARY_SUFFIX}$") - set(_cflags ${_pc_prefix}_STATIC_CFLAGS_OTHER) - set(_link_libraries ${_pc_prefix}_STATIC_LIBRARIES) - set(_link_options ${_pc_prefix}_STATIC_LDFLAGS_OTHER) - set(_library_dirs ${_pc_prefix}_STATIC_LIBRARY_DIRS) + if(MINGW) + set(re_shared_suffix ".dll.a$") else() + set(re_shared_suffix "${CMAKE_SHARED_LIBRARY_SUFFIX}$") + endif() + if("${_library}" MATCHES "${re_shared_suffix}") set(_cflags ${_pc_prefix}_CFLAGS_OTHER) set(_link_libraries ${_pc_prefix}_LIBRARIES) set(_link_options ${_pc_prefix}_LDFLAGS_OTHER) set(_library_dirs ${_pc_prefix}_LIBRARY_DIRS) + else() + set(_cflags ${_pc_prefix}_STATIC_CFLAGS_OTHER) + set(_link_libraries ${_pc_prefix}_STATIC_LIBRARIES) + set(_link_options ${_pc_prefix}_STATIC_LDFLAGS_OTHER) + set(_library_dirs ${_pc_prefix}_STATIC_LIBRARY_DIRS) endif() # The *_LIBRARIES lists always start with the library itself diff --git a/libs/SDL3/cmake/PreseedEmscriptenCache.cmake b/libs/SDL3/cmake/PreseedEmscriptenCache.cmake index 5a34ab5a..ff6a2e20 100644 --- a/libs/SDL3/cmake/PreseedEmscriptenCache.cmake +++ b/libs/SDL3/cmake/PreseedEmscriptenCache.cmake @@ -4,6 +4,7 @@ if(EMSCRIPTEN) set(HAVE_ALLOCA_H "1" CACHE INTERNAL "Have include alloca.h") set(HAVE_LIBM "1" CACHE INTERNAL "Have library m") set(HAVE_MALLOC "1" CACHE INTERNAL "Have include malloc.h") + set(HAVE_MALLOC_H "1" CACHE INTERNAL "Have include malloc.h") set(LIBC_HAS_ABS "1" CACHE INTERNAL "Have symbol abs") set(LIBC_HAS_ACOS "1" CACHE INTERNAL "Have symbol acos") set(LIBC_HAS_ACOSF "1" CACHE INTERNAL "Have symbol acosf") @@ -131,7 +132,6 @@ if(EMSCRIPTEN) 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__") @@ -152,7 +152,7 @@ if(EMSCRIPTEN) set(HAVE_O_CLOEXEC "1" CACHE INTERNAL "Test HAVE_O_CLOEXEC") set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR") set(COMPILER_SUPPORTS_GCC_ATOMICS "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_GCC_ATOMICS") - set(LINKER_SUPPORTS_VERSION_SCRIPT "" CACHE INTERNAL "Test LINKER_SUPPORTS_VERSION_SCRIPT") + set(HAVE_WL_VERSION_SCRIPT "" CACHE INTERNAL "Test HAVE_WL_VERSION_SCRIPT") set(LINKER_SUPPORTS_WL_NO_UNDEFINED "" CACHE INTERNAL "Test LINKER_SUPPORTS_WL_NO_UNDEFINED") set(ICONV_IN_LIBC "1" CACHE INTERNAL "Test ICONV_IN_LIBC") set(ICONV_IN_LIBICONV "" CACHE INTERNAL "Test ICONV_IN_LIBICONV") @@ -174,5 +174,11 @@ if(EMSCRIPTEN) set(HAVE_MEMFD_CREATE "" CACHE INTERNAL "Have symbol memfd_create") set(HAVE_POSIX_FALLOCATE "1" CACHE INTERNAL "Have symbol posix_fallocate") set(HAVE_DLOPEN_IN_LIBC "1" CACHE INTERNAL "Have symbol dlopen") + set(HAVE_FDATASYNC "1" CACHE INTERNAL "Have symbol fdatasync") + set(HAVE_GETHOSTNAME "1" CACHE INTERNAL "Have symbol gethostname") + set(HAVE_SIGTIMEDWAIT "1" CACHE INTERNAL "Have symbol sigtimedwait") + set(HAVE_PPOLL "" CACHE INTERNAL "Have symbol ppoll") + set(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR "" CACHE INTERNAL "Have symbol posix_spawn_file_actions_addchdir") + set(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP "1" CACHE INTERNAL "Have symbol posix_spawn_file_actions_addchdir_np") endfunction() endif() diff --git a/libs/SDL3/cmake/PreseedMSVCCache.cmake b/libs/SDL3/cmake/PreseedMSVCCache.cmake index 23025eb0..f93d2b8f 100644 --- a/libs/SDL3/cmake/PreseedMSVCCache.cmake +++ b/libs/SDL3/cmake/PreseedMSVCCache.cmake @@ -1,184 +1,193 @@ if(MSVC) function(SDL_Preseed_CMakeCache) - set(COMPILER_SUPPORTS_W3 "1" CACHE INTERNAL "Test /W3") - set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS "" CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS") - set(HAVE_ALLOCA_H "" CACHE INTERNAL "Have include alloca.h") - set(HAVE_AUDIOCLIENT_H "1" CACHE INTERNAL "Have include audioclient.h") - set(HAVE_D3D11_H "1" CACHE INTERNAL "Have include d3d11_1.h") - set(HAVE_D3D9_H "1" CACHE INTERNAL "Have include d3d9.h") - set(HAVE_DDRAW_H "1" CACHE INTERNAL "Have include ddraw.h") - set(HAVE_DINPUT_H "1" CACHE INTERNAL "Have include dinput.h") - set(HAVE_DSOUND_H "1" CACHE INTERNAL "Have include dsound.h") - set(HAVE_DXGI_H "1" CACHE INTERNAL "Have include dxgi.h") - set(HAVE_LIBM "" CACHE INTERNAL "Have library m") - set(HAVE_MALLOC "1" CACHE INTERNAL "Have include malloc.h") - set(HAVE_MMDEVICEAPI_H "1" CACHE INTERNAL "Have include mmdeviceapi.h") - set(HAVE_SENSORSAPI_H "1" CACHE INTERNAL "Have include sensorsapi.h") - set(HAVE_SHELLSCALINGAPI_H "1" CACHE INTERNAL "Have include shellscalingapi.h") - set(HAVE_SHOBJIDL_CORE_H "1" CACHE INTERNAL "Have include shobjidl_core.h") - set(HAVE_TPCSHRD_H "1" CACHE INTERNAL "Have include tpcshrd.h") - set(HAVE_WIN32_CC "1" CACHE INTERNAL "Test HAVE_WIN32_CC") - set(HAVE_XINPUT_H "1" CACHE INTERNAL "Test HAVE_XINPUT_H") - set(LIBC_HAS_ABS "1" CACHE INTERNAL "Have symbol abs") - set(LIBC_HAS_ACOS "1" CACHE INTERNAL "Have symbol acos") - set(LIBC_HAS_ACOSF "1" CACHE INTERNAL "Have symbol acosf") - set(LIBC_HAS_ASIN "1" CACHE INTERNAL "Have symbol asin") - set(LIBC_HAS_ASINF "1" CACHE INTERNAL "Have symbol asinf") - set(LIBC_HAS_ATAN "1" CACHE INTERNAL "Have symbol atan") - set(LIBC_HAS_ATAN2 "1" CACHE INTERNAL "Have symbol atan2") - set(LIBC_HAS_ATAN2F "1" CACHE INTERNAL "Have symbol atan2f") - set(LIBC_HAS_ATANF "1" CACHE INTERNAL "Have symbol atanf") - set(LIBC_HAS_ATOF "1" CACHE INTERNAL "Have symbol atof") - set(LIBC_HAS_ATOI "1" CACHE INTERNAL "Have symbol atoi") - set(LIBC_HAS_BCOPY "" CACHE INTERNAL "Have symbol bcopy") - set(LIBC_HAS_CALLOC "1" CACHE INTERNAL "Have symbol calloc") - set(LIBC_HAS_CEIL "1" CACHE INTERNAL "Have symbol ceil") - set(LIBC_HAS_CEILF "1" CACHE INTERNAL "Have symbol ceilf") - set(LIBC_HAS_COPYSIGN "1" CACHE INTERNAL "Have symbol copysign") - set(LIBC_HAS_COPYSIGNF "1" CACHE INTERNAL "Have symbol copysignf") - set(LIBC_HAS_COS "1" CACHE INTERNAL "Have symbol cos") - set(LIBC_HAS_COSF "1" CACHE INTERNAL "Have symbol cosf") - set(LIBC_HAS_EXP "1" CACHE INTERNAL "Have symbol exp") - set(LIBC_HAS_EXPF "1" CACHE INTERNAL "Have symbol expf") - set(LIBC_HAS_FABS "1" CACHE INTERNAL "Have symbol fabs") - set(LIBC_HAS_FABSF "1" CACHE INTERNAL "Have symbol fabsf") - set(LIBC_HAS_FLOAT_H "1" CACHE INTERNAL "Have include float.h") - set(LIBC_HAS_FLOOR "1" CACHE INTERNAL "Have symbol floor") - set(LIBC_HAS_FLOORF "1" CACHE INTERNAL "Have symbol floorf") - set(LIBC_HAS_FMOD "1" CACHE INTERNAL "Have symbol fmod") - set(LIBC_HAS_FMODF "1" CACHE INTERNAL "Have symbol fmodf") - set(LIBC_HAS_FOPEN64 "" CACHE INTERNAL "Have symbol fopen64") - set(LIBC_HAS_FREE "1" CACHE INTERNAL "Have symbol free") - set(LIBC_HAS_FSEEKO "" CACHE INTERNAL "Have symbol fseeko") - set(LIBC_HAS_FSEEKO64 "" CACHE INTERNAL "Have symbol fseeko64") - set(LIBC_HAS_GETENV "1" CACHE INTERNAL "Have symbol getenv") - set(LIBC_HAS_ICONV_H "" CACHE INTERNAL "Have include iconv.h") - set(LIBC_HAS_INDEX "" CACHE INTERNAL "Have symbol index") - set(LIBC_HAS_INTTYPES_H "1" CACHE INTERNAL "Have include inttypes.h") - set(LIBC_HAS_ISINF "1" CACHE INTERNAL "Have include isinf(double)") - set(LIBC_ISINF_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isinf(float)") - set(LIBC_HAS_ISINFF "" CACHE INTERNAL "Have include isinff(float)") - set(LIBC_HAS_ISNAN "1" CACHE INTERNAL "Have include isnan(double)") - set(LIBC_ISNAN_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isnan(float)") - set(LIBC_HAS_ISNANF "" CACHE INTERNAL "Have include isnanf(float)") - set(LIBC_HAS_ITOA "1" CACHE INTERNAL "Have symbol itoa") - set(LIBC_HAS_LIMITS_H "1" CACHE INTERNAL "Have include limits.h") - set(LIBC_HAS_LOG "1" CACHE INTERNAL "Have symbol log") - set(LIBC_HAS_LOG10 "1" CACHE INTERNAL "Have symbol log10") - set(LIBC_HAS_LOG10F "1" CACHE INTERNAL "Have symbol log10f") - set(LIBC_HAS_LOGF "1" CACHE INTERNAL "Have symbol logf") - set(LIBC_HAS_LROUND "1" CACHE INTERNAL "Have symbol lround") - set(LIBC_HAS_LROUNDF "1" CACHE INTERNAL "Have symbol lroundf") - set(LIBC_HAS_MALLOC "1" CACHE INTERNAL "Have symbol malloc") - set(LIBC_HAS_MALLOC_H "1" CACHE INTERNAL "Have include malloc.h") - set(LIBC_HAS_MATH_H "1" CACHE INTERNAL "Have include math.h") - set(LIBC_HAS_MEMCMP "1" CACHE INTERNAL "Have symbol memcmp") - set(LIBC_HAS_MEMCPY "1" CACHE INTERNAL "Have symbol memcpy") - set(LIBC_HAS_MEMMOVE "1" CACHE INTERNAL "Have symbol memmove") - set(LIBC_HAS_MEMORY_H "1" CACHE INTERNAL "Have include memory.h") - set(LIBC_HAS_MEMSET "1" CACHE INTERNAL "Have symbol memset") - set(LIBC_HAS_MODF "1" CACHE INTERNAL "Have symbol modf") - set(LIBC_HAS_MODFF "1" CACHE INTERNAL "Have symbol modff") - set(LIBC_HAS_POW "1" CACHE INTERNAL "Have symbol pow") - set(LIBC_HAS_POWF "1" CACHE INTERNAL "Have symbol powf") - set(LIBC_HAS_PUTENV "1" CACHE INTERNAL "Have symbol putenv") - set(LIBC_HAS_REALLOC "1" CACHE INTERNAL "Have symbol realloc") - set(LIBC_HAS_RINDEX "" CACHE INTERNAL "Have symbol rindex") - set(LIBC_HAS_ROUND "1" CACHE INTERNAL "Have symbol round") - set(LIBC_HAS_ROUNDF "1" CACHE INTERNAL "Have symbol roundf") - set(LIBC_HAS_SCALBN "1" CACHE INTERNAL "Have symbol scalbn") - set(LIBC_HAS_SCALBNF "1" CACHE INTERNAL "Have symbol scalbnf") - set(LIBC_HAS_SETENV "" CACHE INTERNAL "Have symbol setenv") - set(LIBC_HAS_SIGNAL_H "1" CACHE INTERNAL "Have include signal.h") - set(LIBC_HAS_SIN "1" CACHE INTERNAL "Have symbol sin") - set(LIBC_HAS_SINF "1" CACHE INTERNAL "Have symbol sinf") - set(LIBC_HAS_SQR "" CACHE INTERNAL "Have symbol sqr") - set(LIBC_HAS_SQRT "1" CACHE INTERNAL "Have symbol sqrt") - set(LIBC_HAS_SQRTF "1" CACHE INTERNAL "Have symbol sqrtf") - set(LIBC_HAS_SSCANF "1" CACHE INTERNAL "Have symbol sscanf") - set(LIBC_HAS_STDARG_H "1" CACHE INTERNAL "Have include stdarg.h") - set(LIBC_HAS_STDBOOL_H "1" CACHE INTERNAL "Have include stdbool.h") - set(LIBC_HAS_STDDEF_H "1" CACHE INTERNAL "Have include stddef.h") - set(LIBC_HAS_STDINT_H "1" CACHE INTERNAL "Have include stdint.h") - set(LIBC_HAS_STDIO_H "1" CACHE INTERNAL "Have include stdio.h") - set(LIBC_HAS_STDLIB_H "1" CACHE INTERNAL "Have include stdlib.h") - set(LIBC_HAS_STRCHR "1" CACHE INTERNAL "Have symbol strchr") - set(LIBC_HAS_STRCMP "1" CACHE INTERNAL "Have symbol strcmp") - set(LIBC_HAS_STRINGS_H "" CACHE INTERNAL "Have include strings.h") - set(LIBC_HAS_STRING_H "1" CACHE INTERNAL "Have include string.h") - set(LIBC_HAS_STRLCAT "" CACHE INTERNAL "Have symbol strlcat") - set(LIBC_HAS_STRLCPY "" CACHE INTERNAL "Have symbol strlcpy") - set(LIBC_HAS_STRLEN "1" CACHE INTERNAL "Have symbol strlen") - set(LIBC_HAS_STRNCMP "1" CACHE INTERNAL "Have symbol strncmp") - set(LIBC_HAS_STRNLEN "1" CACHE INTERNAL "Have symbol strnlen") - set(LIBC_HAS_STRNSTR "" CACHE INTERNAL "Have symbol strnstr") - set(LIBC_HAS_STRPBRK "1" CACHE INTERNAL "Have symbol strpbrk") - set(LIBC_HAS_STRRCHR "1" CACHE INTERNAL "Have symbol strrchr") - set(LIBC_HAS_STRSTR "1" CACHE INTERNAL "Have symbol strstr") - set(LIBC_HAS_STRTOD "1" CACHE INTERNAL "Have symbol strtod") - set(LIBC_HAS_STRTOK_R "" CACHE INTERNAL "Have symbol strtok_r") - set(LIBC_HAS_STRTOL "1" CACHE INTERNAL "Have symbol strtol") - set(LIBC_HAS_STRTOLL "1" CACHE INTERNAL "Have symbol strtoll") - set(LIBC_HAS_STRTOUL "1" CACHE INTERNAL "Have symbol strtoul") - set(LIBC_HAS_STRTOULL "1" CACHE INTERNAL "Have symbol strtoull") - set(LIBC_HAS_SYS_TYPES_H "1" CACHE INTERNAL "Have include sys/types.h") - set(LIBC_HAS_TAN "1" CACHE INTERNAL "Have symbol tan") - set(LIBC_HAS_TANF "1" CACHE INTERNAL "Have symbol tanf") - set(LIBC_HAS_TIME_H "1" CACHE INTERNAL "Have include time.h") - set(LIBC_HAS_TRUNC "1" CACHE INTERNAL "Have symbol trunc") - set(LIBC_HAS_TRUNCF "1" CACHE INTERNAL "Have symbol truncf") - set(LIBC_HAS_UNSETENV "" CACHE INTERNAL "Have symbol unsetenv") - set(LIBC_HAS_VSNPRINTF "1" CACHE INTERNAL "Have symbol vsnprintf") - set(LIBC_HAS_VSSCANF "1" CACHE INTERNAL "Have symbol vsscanf") - set(LIBC_HAS_WCHAR_H "1" CACHE INTERNAL "Have include wchar.h") - set(LIBC_HAS_WCSCMP "1" CACHE INTERNAL "Have symbol wcscmp") - set(LIBC_HAS_WCSDUP "1" CACHE INTERNAL "Have symbol wcsdup") - set(LIBC_HAS_WCSLCAT "" CACHE INTERNAL "Have symbol wcslcat") - set(LIBC_HAS_WCSLCPY "" CACHE INTERNAL "Have symbol wcslcpy") - set(LIBC_HAS_WCSLEN "1" CACHE INTERNAL "Have symbol wcslen") - set(LIBC_HAS_WCSNCMP "1" CACHE INTERNAL "Have symbol wcsncmp") - set(LIBC_HAS_WCSNLEN "1" CACHE INTERNAL "Have symbol wcsnlen") - set(LIBC_HAS_WCSSTR "1" CACHE INTERNAL "Have symbol wcsstr") - set(LIBC_HAS_WCSTOL "1" CACHE INTERNAL "Have symbol wcstol") - set(LIBC_HAS__EXIT "1" CACHE INTERNAL "Have symbol _Exit") - set(LIBC_HAS__I64TOA "1" CACHE INTERNAL "Have symbol _i64toa") - set(LIBC_HAS__LTOA "1" CACHE INTERNAL "Have symbol _ltoa") - set(LIBC_HAS__STRREV "1" CACHE INTERNAL "Have symbol _strrev") - set(LIBC_HAS__UI64TOA "1" CACHE INTERNAL "Have symbol _ui64toa") - set(LIBC_HAS__UITOA "" CACHE INTERNAL "Have symbol _uitoa") - set(LIBC_HAS__ULTOA "1" CACHE INTERNAL "Have symbol _ultoa") - set(LIBC_HAS__WCSDUP "1" CACHE INTERNAL "Have symbol _wcsdup") - set(LIBC_IS_GLIBC "" CACHE INTERNAL "Have symbol __GLIBC__") - set(_ALLOCA_IN_MALLOC_H "" CACHE INTERNAL "Have symbol _alloca") + check_c_source_compiles(" + #include + #if _WIN32_WINNT < 0x0A00 + #error Preseeding is only supported for MSVC supporting Windows 10 or higher + #endif + int main(int argc, char **argv) { return 0; } + " CAN_PRESEED + ) + if(CAN_PRESEED) + set(COMPILER_SUPPORTS_W3 "1" CACHE INTERNAL "Test /W3") + set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS "" CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS") + set(HAVE_ALLOCA_H "" CACHE INTERNAL "Have include alloca.h") + set(HAVE_AUDIOCLIENT_H "1" CACHE INTERNAL "Have include audioclient.h") + set(HAVE_D3D11_H "1" CACHE INTERNAL "Have include d3d11_1.h") + set(HAVE_D3D9_H "1" CACHE INTERNAL "Have include d3d9.h") + set(HAVE_DDRAW_H "1" CACHE INTERNAL "Have include ddraw.h") + set(HAVE_DINPUT_H "1" CACHE INTERNAL "Have include dinput.h") + set(HAVE_DSOUND_H "1" CACHE INTERNAL "Have include dsound.h") + set(HAVE_DXGI_H "1" CACHE INTERNAL "Have include dxgi.h") + set(HAVE_LIBM "" CACHE INTERNAL "Have library m") + set(HAVE_MALLOC "1" CACHE INTERNAL "Have include malloc.h") + set(HAVE_MALLOC_H "1" CACHE INTERNAL "Have include malloc.h") + set(HAVE_MMDEVICEAPI_H "1" CACHE INTERNAL "Have include mmdeviceapi.h") + set(HAVE_SENSORSAPI_H "1" CACHE INTERNAL "Have include sensorsapi.h") + set(HAVE_SHELLSCALINGAPI_H "1" CACHE INTERNAL "Have include shellscalingapi.h") + set(HAVE_TPCSHRD_H "1" CACHE INTERNAL "Have include tpcshrd.h") + set(HAVE_WIN32_CC "1" CACHE INTERNAL "Test HAVE_WIN32_CC") + set(HAVE_XINPUT_H "1" CACHE INTERNAL "Test HAVE_XINPUT_H") + set(LIBC_HAS_ABS "1" CACHE INTERNAL "Have symbol abs") + set(LIBC_HAS_ACOS "1" CACHE INTERNAL "Have symbol acos") + set(LIBC_HAS_ACOSF "1" CACHE INTERNAL "Have symbol acosf") + set(LIBC_HAS_ASIN "1" CACHE INTERNAL "Have symbol asin") + set(LIBC_HAS_ASINF "1" CACHE INTERNAL "Have symbol asinf") + set(LIBC_HAS_ATAN "1" CACHE INTERNAL "Have symbol atan") + set(LIBC_HAS_ATAN2 "1" CACHE INTERNAL "Have symbol atan2") + set(LIBC_HAS_ATAN2F "1" CACHE INTERNAL "Have symbol atan2f") + set(LIBC_HAS_ATANF "1" CACHE INTERNAL "Have symbol atanf") + set(LIBC_HAS_ATOF "1" CACHE INTERNAL "Have symbol atof") + set(LIBC_HAS_ATOI "1" CACHE INTERNAL "Have symbol atoi") + set(LIBC_HAS_BCOPY "" CACHE INTERNAL "Have symbol bcopy") + set(LIBC_HAS_CALLOC "1" CACHE INTERNAL "Have symbol calloc") + set(LIBC_HAS_CEIL "1" CACHE INTERNAL "Have symbol ceil") + set(LIBC_HAS_CEILF "1" CACHE INTERNAL "Have symbol ceilf") + set(LIBC_HAS_COPYSIGN "1" CACHE INTERNAL "Have symbol copysign") + set(LIBC_HAS_COPYSIGNF "1" CACHE INTERNAL "Have symbol copysignf") + set(LIBC_HAS_COS "1" CACHE INTERNAL "Have symbol cos") + set(LIBC_HAS_COSF "1" CACHE INTERNAL "Have symbol cosf") + set(LIBC_HAS_EXP "1" CACHE INTERNAL "Have symbol exp") + set(LIBC_HAS_EXPF "1" CACHE INTERNAL "Have symbol expf") + set(LIBC_HAS_FABS "1" CACHE INTERNAL "Have symbol fabs") + set(LIBC_HAS_FABSF "1" CACHE INTERNAL "Have symbol fabsf") + set(LIBC_HAS_FLOAT_H "1" CACHE INTERNAL "Have include float.h") + set(LIBC_HAS_FLOOR "1" CACHE INTERNAL "Have symbol floor") + set(LIBC_HAS_FLOORF "1" CACHE INTERNAL "Have symbol floorf") + set(LIBC_HAS_FMOD "1" CACHE INTERNAL "Have symbol fmod") + set(LIBC_HAS_FMODF "1" CACHE INTERNAL "Have symbol fmodf") + set(LIBC_HAS_FOPEN64 "" CACHE INTERNAL "Have symbol fopen64") + set(LIBC_HAS_FREE "1" CACHE INTERNAL "Have symbol free") + set(LIBC_HAS_FSEEKO "" CACHE INTERNAL "Have symbol fseeko") + set(LIBC_HAS_FSEEKO64 "" CACHE INTERNAL "Have symbol fseeko64") + set(LIBC_HAS_GETENV "1" CACHE INTERNAL "Have symbol getenv") + set(LIBC_HAS_ICONV_H "" CACHE INTERNAL "Have include iconv.h") + set(LIBC_HAS_INDEX "" CACHE INTERNAL "Have symbol index") + set(LIBC_HAS_INTTYPES_H "1" CACHE INTERNAL "Have include inttypes.h") + set(LIBC_HAS_ISINF "1" CACHE INTERNAL "Have include isinf(double)") + set(LIBC_ISINF_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isinf(float)") + set(LIBC_HAS_ISINFF "" CACHE INTERNAL "Have include isinff(float)") + set(LIBC_HAS_ISNAN "1" CACHE INTERNAL "Have include isnan(double)") + set(LIBC_ISNAN_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isnan(float)") + set(LIBC_HAS_ISNANF "" CACHE INTERNAL "Have include isnanf(float)") + set(LIBC_HAS_ITOA "1" CACHE INTERNAL "Have symbol itoa") + set(LIBC_HAS_LIMITS_H "1" CACHE INTERNAL "Have include limits.h") + set(LIBC_HAS_LOG "1" CACHE INTERNAL "Have symbol log") + set(LIBC_HAS_LOG10 "1" CACHE INTERNAL "Have symbol log10") + set(LIBC_HAS_LOG10F "1" CACHE INTERNAL "Have symbol log10f") + set(LIBC_HAS_LOGF "1" CACHE INTERNAL "Have symbol logf") + set(LIBC_HAS_LROUND "1" CACHE INTERNAL "Have symbol lround") + set(LIBC_HAS_LROUNDF "1" CACHE INTERNAL "Have symbol lroundf") + set(LIBC_HAS_MALLOC "1" CACHE INTERNAL "Have symbol malloc") + set(LIBC_HAS_MALLOC_H "1" CACHE INTERNAL "Have include malloc.h") + set(LIBC_HAS_MATH_H "1" CACHE INTERNAL "Have include math.h") + set(LIBC_HAS_MEMCMP "1" CACHE INTERNAL "Have symbol memcmp") + set(LIBC_HAS_MEMCPY "1" CACHE INTERNAL "Have symbol memcpy") + set(LIBC_HAS_MEMMOVE "1" CACHE INTERNAL "Have symbol memmove") + set(LIBC_HAS_MEMORY_H "1" CACHE INTERNAL "Have include memory.h") + set(LIBC_HAS_MEMSET "1" CACHE INTERNAL "Have symbol memset") + set(LIBC_HAS_MODF "1" CACHE INTERNAL "Have symbol modf") + set(LIBC_HAS_MODFF "1" CACHE INTERNAL "Have symbol modff") + set(LIBC_HAS_POW "1" CACHE INTERNAL "Have symbol pow") + set(LIBC_HAS_POWF "1" CACHE INTERNAL "Have symbol powf") + set(LIBC_HAS_PUTENV "1" CACHE INTERNAL "Have symbol putenv") + set(LIBC_HAS_REALLOC "1" CACHE INTERNAL "Have symbol realloc") + set(LIBC_HAS_RINDEX "" CACHE INTERNAL "Have symbol rindex") + set(LIBC_HAS_ROUND "1" CACHE INTERNAL "Have symbol round") + set(LIBC_HAS_ROUNDF "1" CACHE INTERNAL "Have symbol roundf") + set(LIBC_HAS_SCALBN "1" CACHE INTERNAL "Have symbol scalbn") + set(LIBC_HAS_SCALBNF "1" CACHE INTERNAL "Have symbol scalbnf") + set(LIBC_HAS_SETENV "" CACHE INTERNAL "Have symbol setenv") + set(LIBC_HAS_SIGNAL_H "1" CACHE INTERNAL "Have include signal.h") + set(LIBC_HAS_SIN "1" CACHE INTERNAL "Have symbol sin") + set(LIBC_HAS_SINF "1" CACHE INTERNAL "Have symbol sinf") + set(LIBC_HAS_SQR "" CACHE INTERNAL "Have symbol sqr") + set(LIBC_HAS_SQRT "1" CACHE INTERNAL "Have symbol sqrt") + set(LIBC_HAS_SQRTF "1" CACHE INTERNAL "Have symbol sqrtf") + set(LIBC_HAS_SSCANF "1" CACHE INTERNAL "Have symbol sscanf") + set(LIBC_HAS_STDARG_H "1" CACHE INTERNAL "Have include stdarg.h") + set(LIBC_HAS_STDBOOL_H "1" CACHE INTERNAL "Have include stdbool.h") + set(LIBC_HAS_STDDEF_H "1" CACHE INTERNAL "Have include stddef.h") + set(LIBC_HAS_STDINT_H "1" CACHE INTERNAL "Have include stdint.h") + set(LIBC_HAS_STDIO_H "1" CACHE INTERNAL "Have include stdio.h") + set(LIBC_HAS_STDLIB_H "1" CACHE INTERNAL "Have include stdlib.h") + set(LIBC_HAS_STRCHR "1" CACHE INTERNAL "Have symbol strchr") + set(LIBC_HAS_STRCMP "1" CACHE INTERNAL "Have symbol strcmp") + set(LIBC_HAS_STRINGS_H "" CACHE INTERNAL "Have include strings.h") + set(LIBC_HAS_STRING_H "1" CACHE INTERNAL "Have include string.h") + set(LIBC_HAS_STRLCAT "" CACHE INTERNAL "Have symbol strlcat") + set(LIBC_HAS_STRLCPY "" CACHE INTERNAL "Have symbol strlcpy") + set(LIBC_HAS_STRLEN "1" CACHE INTERNAL "Have symbol strlen") + set(LIBC_HAS_STRNCMP "1" CACHE INTERNAL "Have symbol strncmp") + set(LIBC_HAS_STRNLEN "1" CACHE INTERNAL "Have symbol strnlen") + set(LIBC_HAS_STRNSTR "" CACHE INTERNAL "Have symbol strnstr") + set(LIBC_HAS_STRPBRK "1" CACHE INTERNAL "Have symbol strpbrk") + set(LIBC_HAS_STRRCHR "1" CACHE INTERNAL "Have symbol strrchr") + set(LIBC_HAS_STRSTR "1" CACHE INTERNAL "Have symbol strstr") + set(LIBC_HAS_STRTOD "1" CACHE INTERNAL "Have symbol strtod") + set(LIBC_HAS_STRTOK_R "" CACHE INTERNAL "Have symbol strtok_r") + set(LIBC_HAS_STRTOL "1" CACHE INTERNAL "Have symbol strtol") + set(LIBC_HAS_STRTOLL "1" CACHE INTERNAL "Have symbol strtoll") + set(LIBC_HAS_STRTOUL "1" CACHE INTERNAL "Have symbol strtoul") + set(LIBC_HAS_STRTOULL "1" CACHE INTERNAL "Have symbol strtoull") + set(LIBC_HAS_SYS_TYPES_H "1" CACHE INTERNAL "Have include sys/types.h") + set(LIBC_HAS_TAN "1" CACHE INTERNAL "Have symbol tan") + set(LIBC_HAS_TANF "1" CACHE INTERNAL "Have symbol tanf") + set(LIBC_HAS_TIME_H "1" CACHE INTERNAL "Have include time.h") + set(LIBC_HAS_TRUNC "1" CACHE INTERNAL "Have symbol trunc") + set(LIBC_HAS_TRUNCF "1" CACHE INTERNAL "Have symbol truncf") + set(LIBC_HAS_UNSETENV "" CACHE INTERNAL "Have symbol unsetenv") + set(LIBC_HAS_VSNPRINTF "1" CACHE INTERNAL "Have symbol vsnprintf") + set(LIBC_HAS_VSSCANF "1" CACHE INTERNAL "Have symbol vsscanf") + set(LIBC_HAS_WCHAR_H "1" CACHE INTERNAL "Have include wchar.h") + set(LIBC_HAS_WCSCMP "1" CACHE INTERNAL "Have symbol wcscmp") + set(LIBC_HAS_WCSDUP "1" CACHE INTERNAL "Have symbol wcsdup") + set(LIBC_HAS_WCSLCAT "" CACHE INTERNAL "Have symbol wcslcat") + set(LIBC_HAS_WCSLCPY "" CACHE INTERNAL "Have symbol wcslcpy") + set(LIBC_HAS_WCSLEN "1" CACHE INTERNAL "Have symbol wcslen") + set(LIBC_HAS_WCSNCMP "1" CACHE INTERNAL "Have symbol wcsncmp") + set(LIBC_HAS_WCSNLEN "1" CACHE INTERNAL "Have symbol wcsnlen") + set(LIBC_HAS_WCSSTR "1" CACHE INTERNAL "Have symbol wcsstr") + set(LIBC_HAS_WCSTOL "1" CACHE INTERNAL "Have symbol wcstol") + set(LIBC_HAS__EXIT "1" CACHE INTERNAL "Have symbol _Exit") + set(LIBC_HAS__I64TOA "1" CACHE INTERNAL "Have symbol _i64toa") + set(LIBC_HAS__LTOA "1" CACHE INTERNAL "Have symbol _ltoa") + set(LIBC_HAS__STRREV "1" CACHE INTERNAL "Have symbol _strrev") + set(LIBC_HAS__UI64TOA "1" CACHE INTERNAL "Have symbol _ui64toa") + set(LIBC_HAS__ULTOA "1" CACHE INTERNAL "Have symbol _ultoa") + set(LIBC_HAS__WCSDUP "1" CACHE INTERNAL "Have symbol _wcsdup") + set(LIBC_IS_GLIBC "" CACHE INTERNAL "Have symbol __GLIBC__") + set(_ALLOCA_IN_MALLOC_H "1" CACHE INTERNAL "Have symbol _alloca") - if(CHECK_CPU_ARCHITECTURE_X86) - set(COMPILER_SUPPORTS_AVX "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX") - set(COMPILER_SUPPORTS_AVX2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX2") - set(COMPILER_SUPPORTS_MMX "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_MMX") - set(COMPILER_SUPPORTS_SSE "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE") - set(COMPILER_SUPPORTS_SSE2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE2") - set(COMPILER_SUPPORTS_SSE3 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE3") - set(COMPILER_SUPPORTS_SSE4_1 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_1") - set(COMPILER_SUPPORTS_SSE4_2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_2") - endif() + if(CHECK_CPU_ARCHITECTURE_X86) + set(COMPILER_SUPPORTS_AVX "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX") + set(COMPILER_SUPPORTS_AVX2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX2") + set(COMPILER_SUPPORTS_MMX "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_MMX") + set(COMPILER_SUPPORTS_SSE "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE") + set(COMPILER_SUPPORTS_SSE2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE2") + set(COMPILER_SUPPORTS_SSE3 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE3") + set(COMPILER_SUPPORTS_SSE4_1 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_1") + set(COMPILER_SUPPORTS_SSE4_2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_2") + endif() - if(CHECK_CPU_ARCHITECTURE_X64) - set(COMPILER_SUPPORTS_AVX "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX") - set(COMPILER_SUPPORTS_AVX2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX2") - set(COMPILER_SUPPORTS_MMX "" CACHE INTERNAL "Test COMPILER_SUPPORTS_MMX") - set(COMPILER_SUPPORTS_SSE "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE") - set(COMPILER_SUPPORTS_SSE2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE2") - set(COMPILER_SUPPORTS_SSE3 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE3") - set(COMPILER_SUPPORTS_SSE4_1 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_1") - set(COMPILER_SUPPORTS_SSE4_2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_2") - endif() + if(CHECK_CPU_ARCHITECTURE_X64) + set(COMPILER_SUPPORTS_AVX "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX") + set(COMPILER_SUPPORTS_AVX2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX2") + set(COMPILER_SUPPORTS_MMX "" CACHE INTERNAL "Test COMPILER_SUPPORTS_MMX") + set(COMPILER_SUPPORTS_SSE "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE") + set(COMPILER_SUPPORTS_SSE2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE2") + set(COMPILER_SUPPORTS_SSE3 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE3") + set(COMPILER_SUPPORTS_SSE4_1 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_1") + set(COMPILER_SUPPORTS_SSE4_2 "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_2") + endif() - if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL "19.1") - set(HAVE_ROAPI_H "1" CACHE INTERNAL "Have include roapi.h") - set(HAVE_WINDOWS_GAMING_INPUT_H "1" CACHE INTERNAL "Test HAVE_WINDOWS_GAMING_INPUT_H") - else() - set(HAVE_ROAPI_H "" CACHE INTERNAL "Have include roapi.h") - set(HAVE_WINDOWS_GAMING_INPUT_H "" CACHE INTERNAL "Test HAVE_WINDOWS_GAMING_INPUT_H") + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL "19.1") + set(HAVE_ROAPI_H "1" CACHE INTERNAL "Have include roapi.h") + set(HAVE_WINDOWS_GAMING_INPUT_H "1" CACHE INTERNAL "Test HAVE_WINDOWS_GAMING_INPUT_H") + else() + set(HAVE_ROAPI_H "" CACHE INTERNAL "Have include roapi.h") + set(HAVE_WINDOWS_GAMING_INPUT_H "" CACHE INTERNAL "Test HAVE_WINDOWS_GAMING_INPUT_H") + endif() endif() endfunction() endif() diff --git a/libs/SDL3/cmake/PreseedNokiaNGageCache.cmake b/libs/SDL3/cmake/PreseedNokiaNGageCache.cmake index 298177ba..e4047853 100644 --- a/libs/SDL3/cmake/PreseedNokiaNGageCache.cmake +++ b/libs/SDL3/cmake/PreseedNokiaNGageCache.cmake @@ -135,7 +135,6 @@ if(NGAGESDK) 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__") @@ -155,7 +154,7 @@ if(NGAGESDK) #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(HAVE_WL_VERSION_SCRIPT "" CACHE INTERNAL "Test HAVE_WL_VERSION_SCRIPT") set(LINKER_SUPPORTS_WL_NO_UNDEFINED "" CACHE INTERNAL "Test LINKER_SUPPORTS_WL_NO_UNDEFINED") set(ICONV_IN_LIBC "" CACHE INTERNAL "Test ICONV_IN_LIBC") set(ICONV_IN_LIBICONV "" CACHE INTERNAL "Test ICONV_IN_LIBICONV") diff --git a/libs/SDL3/cmake/SDL3Config.cmake.in b/libs/SDL3/cmake/SDL3Config.cmake.in index 4c6f1b69..a1190aa7 100644 --- a/libs/SDL3/cmake/SDL3Config.cmake.in +++ b/libs/SDL3/cmake/SDL3Config.cmake.in @@ -34,7 +34,7 @@ endmacro() if(_sdl3_framework) set(SDL3_SDL3-static_FOUND TRUE) find_sdl3_static_dependencies() - find_package(SDL3-static CONFIG) + find_package(SDL3-static CONFIG QUIET) if(SDL3_SDL3-static_FOUND AND SDL3-static_FOUND) set(SDL3_SDL3-static_FOUND TRUE) endif() @@ -62,7 +62,7 @@ endif() # Find SDL3::SDL3_test if(_sdl3_framework) - find_package(SDL3_test CONFIG) + find_package(SDL3_test CONFIG QUIET) if(SDL3_test_FOUND) enable_language(OBJC) set(SDL3_SDL3_test_FOUND TRUE) diff --git a/libs/SDL3/cmake/macros.cmake b/libs/SDL3/cmake/macros.cmake index 3ee413c5..038dd989 100644 --- a/libs/SDL3/cmake/macros.cmake +++ b/libs/SDL3/cmake/macros.cmake @@ -21,7 +21,7 @@ macro(option_string _NAME _DESC _VALUE) add_to_alloptions(${_NAME}) set(${_NAME} ${_VALUE} CACHE STRING "${_DESC}") set(HAVE_${_NAME} ${_VALUE}) -ENDMACRO() +endmacro() macro(message_bool_option _NAME _VALUE) set(_PAD "\t") @@ -296,15 +296,15 @@ endfunction() function(check_linker_supports_version_file VAR) SDL_detect_linker() if(CMAKE_C_COMPILER_LINKER_ID MATCHES "^(MSVC)$") - set(LINKER_SUPPORTS_VERSION_SCRIPT FALSE) + set(${VAR} FALSE) else() cmake_push_check_state(RESET) file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/dummy.sym" "n_0 {\n global:\n func;\n local: *;\n};\n") list(APPEND CMAKE_REQUIRED_LINK_OPTIONS "-Wl,--version-script=${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/dummy.sym") - check_c_source_compiles("int func(void) {return 0;} int main(int argc,char*argv[]){(void)argc;(void)argv;return func();}" LINKER_SUPPORTS_VERSION_SCRIPT FAIL_REGEX "(unsupported|syntax error|unrecognized option)") + check_c_source_compiles("int func(void) {return 0;} int main(int argc,char*argv[]){(void)argc;(void)argv;return func();}" ${VAR} FAIL_REGEX "(unsupported|syntax error|unrecognized option)") cmake_pop_check_state() endif() - set(${VAR} "${LINKER_SUPPORTS_VERSION_SCRIPT}" PARENT_SCOPE) + set(${VAR} "${${VAR}}" PARENT_SCOPE) endfunction() if(CMAKE_VERSION VERSION_LESS 3.18) @@ -334,6 +334,29 @@ if(APPLE) endif() endif() +function(PrintEnabledBackends _SUBSYS _REGEXP) + get_cmake_property(_ALLVARS VARIABLES) + foreach(_VAR IN LISTS _ALLVARS) + if(_VAR AND _VAR MATCHES "${_REGEXP}") + string(TOLOWER "${CMAKE_MATCH_1}" _LOWERED) + if(NOT _LOWERED MATCHES "available|default|dynamic") # a little hack + if(${_VAR}_DYNAMIC) + list(APPEND _ENABLED_BACKENDS "${_LOWERED}(dynamic)") + else() + list(APPEND _ENABLED_BACKENDS "${_LOWERED}") + endif() + endif() + endif() + endforeach() + + if(_ENABLED_BACKENDS STREQUAL "") + set(_SPACEDOUT "(none)") + else() + string(REPLACE ";" " " _SPACEDOUT "${_ENABLED_BACKENDS}") + endif() + message(STATUS " ${_SUBSYS}: ${_SPACEDOUT}") +endfunction() + function(SDL_PrintSummary) ##### Info output ##### message(STATUS "") @@ -366,6 +389,19 @@ function(SDL_PrintSummary) message(STATUS " Build libraries as Apple Framework: ${SDL_FRAMEWORK}") endif() message(STATUS "") + + message(STATUS "Enabled backends:") + PrintEnabledBackends("Video drivers" "^SDL_VIDEO_DRIVER_([A-Z0-9]*)$") + if(SDL_VIDEO_DRIVER_X11) + PrintEnabledBackends("X11 libraries" "^SDL_VIDEO_DRIVER_X11_([A-Z0-9]*)$") + endif() + PrintEnabledBackends("Render drivers" "^SDL_VIDEO_RENDER_([A-Z0-9_]*)$") + PrintEnabledBackends("GPU drivers" "^SDL_GPU_([A-Z0-9]*)$") + PrintEnabledBackends("Audio drivers" "^SDL_AUDIO_DRIVER_([A-Z0-9]*)$") + PrintEnabledBackends("Joystick drivers" "^SDL_JOYSTICK_([A-Z0-9]*)$") + PrintEnabledBackends("Camera drivers" "^SDL_CAMERA_DRIVER_([A-Z0-9]*)$") + message(STATUS "") + if(UNIX) message(STATUS "If something was not detected, although the libraries") message(STATUS "were installed, then make sure you have set the") @@ -382,7 +418,7 @@ function(SDL_PrintSummary) "Most likely, this is not wanted." "\n" "On Linux, install the packages listed at " - "https://github.com/libsdl-org/SDL/blob/main/docs/README-linux.md#build-dependencies " + "https://wiki.libsdl.org/SDL3/README-linux#build-dependencies " "\n" "If you really don't need desktop windows, the documentation tells you how to skip this check. " "https://github.com/libsdl-org/SDL/blob/main/docs/README-cmake.md#cmake-fails-to-build-without-x11-or-wayland-support\n" @@ -392,6 +428,22 @@ function(SDL_PrintSummary) endif() endfunction() +function(SDL_missing_dependency NAME OPTION) + if(LINUX) + message( FATAL_ERROR + "Couldn't find dependency package for ${NAME}. Please install the needed packages or configure with -D${OPTION}=OFF" + "\n" + "The full set of dependencies is available at " + "https://wiki.libsdl.org/SDL3/README-linux#build-dependencies " + "\n" + ) + else() + message( FATAL_ERROR + "Couldn't find dependency package for ${NAME}. Please install the needed packages or configure with -D${OPTION}=OFF" + ) + endif() +endfunction() + function(SDL_install_pdb TARGET DIRECTORY) get_property(type TARGET ${TARGET} PROPERTY TYPE) if(type MATCHES "^(SHARED_LIBRARY|EXECUTABLE)$") diff --git a/libs/SDL3/cmake/sdlchecks.cmake b/libs/SDL3/cmake/sdlchecks.cmake index 06edce5b..bb8a97d6 100644 --- a/libs/SDL3/cmake/sdlchecks.cmake +++ b/libs/SDL3/cmake/sdlchecks.cmake @@ -390,45 +390,84 @@ macro(CheckX11) set(SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1) endif() - check_symbol_exists(XkbLookupKeySym "X11/Xlib.h;X11/XKBlib.h" SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM) + check_include_file("X11/XKBlib.h" SDL_VIDEO_DRIVER_X11_HAS_XKBLIB) - if(SDL_X11_XCURSOR AND HAVE_XCURSOR_H AND XCURSOR_LIB) - set(HAVE_X11_XCURSOR TRUE) - if(HAVE_X11_SHARED) - set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR "\"${XCURSOR_LIB_SONAME}\"") + if(SDL_X11_XCURSOR) + if (HAVE_XCURSOR_H AND XCURSOR_LIB) + set(HAVE_X11_XCURSOR TRUE) + if(HAVE_X11_SHARED) + set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR "\"${XCURSOR_LIB_SONAME}\"") + else() + sdl_link_dependency(xcursor LIBS X11::Xcursor CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xcursor_PKG_CONFIG_SPEC}) + endif() + set(SDL_VIDEO_DRIVER_X11_XCURSOR 1) else() - sdl_link_dependency(xcursor LIBS X11::Xcursor CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xcursor_PKG_CONFIG_SPEC}) + SDL_missing_dependency(XCURSOR SDL_X11_XCURSOR) endif() - set(SDL_VIDEO_DRIVER_X11_XCURSOR 1) endif() - if(SDL_X11_XDBE AND HAVE_XDBE_H) - set(HAVE_X11_XDBE TRUE) - set(SDL_VIDEO_DRIVER_X11_XDBE 1) + if(SDL_X11_XDBE) + if(HAVE_XDBE_H) + set(HAVE_X11_XDBE TRUE) + set(SDL_VIDEO_DRIVER_X11_XDBE 1) + else() + SDL_missing_dependency(XDBE SDL_X11_XDBE) + endif() endif() - if(SDL_X11_XINPUT AND HAVE_XINPUT2_H AND XI_LIB) - set(HAVE_X11_XINPUT TRUE) - if(HAVE_X11_SHARED) - set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 "\"${XI_LIB_SONAME}\"") - else() - sdl_link_dependency(xi LIBS X11::Xi CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xi_PKG_CONFIG_SPEC}) - endif() - set(SDL_VIDEO_DRIVER_X11_XINPUT2 1) + if(SDL_X11_XINPUT) + if(HAVE_XINPUT2_H AND XI_LIB) + set(HAVE_X11_XINPUT TRUE) + if(HAVE_X11_SHARED) + set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 "\"${XI_LIB_SONAME}\"") + else() + sdl_link_dependency(xi LIBS X11::Xi CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xi_PKG_CONFIG_SPEC}) + endif() + set(SDL_VIDEO_DRIVER_X11_XINPUT2 1) - # Check for multitouch - check_c_source_compiles_static(" - #include - #include - #include - int event_type = XI_TouchBegin; - XITouchClassInfo *t; - Status XIAllowTouchEvents(Display *a,int b,unsigned int c,Window d,int f) { - return (Status)0; - } - int main(int argc, char **argv) { return 0; }" HAVE_XINPUT2_MULTITOUCH) - if(HAVE_XINPUT2_MULTITOUCH) - set(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1) + # Check for scroll info + check_c_source_compiles(" + #include + #include + #include + XIScrollClassInfo *s; + int main(int argc, char **argv) {}" HAVE_XINPUT2_SCROLLINFO) + if(HAVE_XINPUT2_SCROLLINFO) + set(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 1) + endif() + + # Check for multitouch + check_c_source_compiles_static(" + #include + #include + #include + int event_type = XI_TouchBegin; + XITouchClassInfo *t; + Status XIAllowTouchEvents(Display *a,int b,unsigned int c,Window d,int f) { + return (Status)0; + } + int main(int argc, char **argv) { return 0; }" HAVE_XINPUT2_MULTITOUCH) + if(HAVE_XINPUT2_MULTITOUCH) + set(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1) + endif() + + # Check for gesture + check_c_source_compiles(" + #include + #include + #include + int event_type = XI_GesturePinchBegin; + XIGesturePinchEvent *t; + Status XIAllowTouchEvents(Display *a,int b,unsigned int c,Window d,int f) { + return (Status)0; + } + int main(int argc, char **argv) { return 0; }" HAVE_XINPUT2_GESTURE) + if(HAVE_XINPUT2_GESTURE) + set(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE 1) + endif() + + else() + SDL_missing_dependency(XINPUT SDL_X11_XINPUT) endif() endif() @@ -442,54 +481,78 @@ macro(CheckX11) BarrierEventID b; int main(int argc, char **argv) { return 0; }" HAVE_XFIXES_H) endif() - if(SDL_X11_XFIXES AND HAVE_XFIXES_H AND HAVE_XINPUT2_H AND XFIXES_LIB) - if(HAVE_X11_SHARED) - set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XFIXES "\"${XFIXES_LIB_SONAME}\"") + if(SDL_X11_XFIXES) + if (HAVE_XFIXES_H AND HAVE_XINPUT2_H AND XFIXES_LIB) + if(HAVE_X11_SHARED) + set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XFIXES "\"${XFIXES_LIB_SONAME}\"") + else() + sdl_link_dependency(xfixes LIBS X11::Xfixes CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xfixes_PKG_CONFIG_SPEC}) + endif() + set(SDL_VIDEO_DRIVER_X11_XFIXES 1) + set(HAVE_X11_XFIXES TRUE) else() - sdl_link_dependency(xfixes LIBS X11::Xfixes CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xfixes_PKG_CONFIG_SPEC}) + SDL_missing_dependency(XFIXES SDL_X11_XFIXES) endif() - set(SDL_VIDEO_DRIVER_X11_XFIXES 1) - set(HAVE_X11_XFIXES TRUE) endif() - if(SDL_X11_XSYNC AND HAVE_XSYNC_H AND XEXT_LIB) - set(SDL_VIDEO_DRIVER_X11_XSYNC 1) - set(HAVE_X11_XSYNC TRUE) - endif() - - if(SDL_X11_XRANDR AND HAVE_XRANDR_H AND XRANDR_LIB) - if(HAVE_X11_SHARED) - set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR "\"${XRANDR_LIB_SONAME}\"") + if(SDL_X11_XSYNC) + if(HAVE_XSYNC_H AND XEXT_LIB) + set(SDL_VIDEO_DRIVER_X11_XSYNC 1) + set(HAVE_X11_XSYNC TRUE) else() - sdl_link_dependency(xrandr LIBS X11::Xrandr CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xrandr_PKG_CONFIG_SPEC}) + SDL_missing_dependency(XSYNC SDL_X11_XSYNC) endif() - set(SDL_VIDEO_DRIVER_X11_XRANDR 1) - set(HAVE_X11_XRANDR TRUE) endif() - if(SDL_X11_XSCRNSAVER AND HAVE_XSS_H AND XSS_LIB) - if(HAVE_X11_SHARED) - set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS "\"${XSS_LIB_SONAME}\"") + if(SDL_X11_XRANDR) + if(HAVE_XRANDR_H AND XRANDR_LIB) + if(HAVE_X11_SHARED) + set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR "\"${XRANDR_LIB_SONAME}\"") + else() + sdl_link_dependency(xrandr LIBS X11::Xrandr CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xrandr_PKG_CONFIG_SPEC}) + endif() + set(SDL_VIDEO_DRIVER_X11_XRANDR 1) + set(HAVE_X11_XRANDR TRUE) else() - sdl_link_dependency(xss LIBS X11::Xss CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xss_PKG_CONFIG_SPEC}) + SDL_missing_dependency(XRANDR SDL_X11_XRANDR) endif() - set(SDL_VIDEO_DRIVER_X11_XSCRNSAVER 1) - set(HAVE_X11_XSCRNSAVER TRUE) endif() - if(SDL_X11_XSHAPE AND HAVE_XSHAPE_H) - set(SDL_VIDEO_DRIVER_X11_XSHAPE 1) - set(HAVE_X11_XSHAPE TRUE) - endif() - - if(SDL_X11_XTEST AND HAVE_XTEST_H AND XTST_LIB) - if(HAVE_X11_SHARED) - set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST "\"${XTST_LIB_SONAME}\"") + if(SDL_X11_XSCRNSAVER) + if(HAVE_XSS_H AND XSS_LIB) + if(HAVE_X11_SHARED) + set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS "\"${XSS_LIB_SONAME}\"") + else() + sdl_link_dependency(xss LIBS X11::Xss CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xss_PKG_CONFIG_SPEC}) + endif() + set(SDL_VIDEO_DRIVER_X11_XSCRNSAVER 1) + set(HAVE_X11_XSCRNSAVER TRUE) else() - sdl_link_dependency(xtst LIBS X11::Xtst CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xtst_PKG_CONFIG_SPEC}) + SDL_missing_dependency(XSCRNSAVER SDL_X11_XSCRNSAVER) + endif() + endif() + + if(SDL_X11_XSHAPE) + if(HAVE_XSHAPE_H) + set(SDL_VIDEO_DRIVER_X11_XSHAPE 1) + set(HAVE_X11_XSHAPE TRUE) + else() + SDL_missing_dependency(XSHAPE SDL_X11_XSHAPE) + endif() + endif() + + if(SDL_X11_XTEST) + if(HAVE_XTEST_H AND XTST_LIB) + if(HAVE_X11_SHARED) + set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST "\"${XTST_LIB_SONAME}\"") + else() + sdl_link_dependency(xtst LIBS X11::Xtst CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xtst_PKG_CONFIG_SPEC}) + endif() + set(SDL_VIDEO_DRIVER_X11_XTEST 1) + set(HAVE_X11_XTEST TRUE) + else() + SDL_missing_dependency(XTEST SDL_X11_XTEST) endif() - set(SDL_VIDEO_DRIVER_X11_XTEST 1) - set(HAVE_X11_XTEST TRUE) endif() endif() endif() @@ -500,6 +563,56 @@ macro(CheckX11) cmake_pop_check_state() endmacro() +macro(CheckFribidi) + if(SDL_FRIBIDI) + set(FRIBIDI_PKG_CONFIG_SPEC fribidi) + set(PC_FRIBIDI_FOUND FALSE) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FRIBIDI IMPORTED_TARGET ${FRIBIDI_PKG_CONFIG_SPEC}) + endif() + if(PC_FRIBIDI_FOUND) + set(HAVE_FRIBIDI TRUE) + set(HAVE_FRIBIDI_H 1) + if(SDL_FRIBIDI_SHARED AND NOT HAVE_SDL_LOADSO) + message(WARNING "You must have SDL_LoadObject() support for dynamic fribidi loading") + endif() + FindLibraryAndSONAME("fribidi" LIBDIRS ${PC_FRIBIDI_LIBRARY_DIRS}) + if(SDL_FRIBIDI_SHARED AND FRIBIDI_LIB AND HAVE_SDL_LOADSO) + set(SDL_FRIBIDI_DYNAMIC "\"${FRIBIDI_LIB_SONAME}\"") + set(HAVE_FRIBIDI_SHARED TRUE) + sdl_include_directories(PRIVATE SYSTEM $) + else() + sdl_link_dependency(fribidi LIBS PkgConfig::PC_FRIBIDI PKG_CONFIG_PREFIX PC_FRIBIDI PKG_CONFIG_SPECS ${FRIBIDI_PKG_CONFIG_SPEC}) + endif() + endif() + endif() +endmacro() + +macro(CheckLibThai) + if(SDL_LIBTHAI) + set(LIBTHAI_PKG_CONFIG_SPEC libthai) + set(PC_LIBTHAI_FOUND FALSE) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_LIBTHAI IMPORTED_TARGET ${LIBTHAI_PKG_CONFIG_SPEC}) + endif() + if(PC_LIBTHAI_FOUND) + set(HAVE_LIBTHAI TRUE) + set(HAVE_LIBTHAI_H 1) + if(SDL_LIBTHAI_SHARED AND NOT HAVE_SDL_LOADSO) + message(WARNING "You must have SDL_LoadObject() support for dynamic libthai loading") + endif() + FindLibraryAndSONAME("thai" LIBDIRS ${PC_LIBTHAI_LIBRARY_DIRS}) + if(SDL_LIBTHAI_SHARED AND THAI_LIB AND HAVE_SDL_LOADSO) + set(SDL_LIBTHAI_DYNAMIC "\"${THAI_LIB_SONAME}\"") + set(HAVE_LIBTHAI_SHARED TRUE) + sdl_include_directories(PRIVATE SYSTEM $) + else() + sdl_link_dependency(libthai LIBS PkgConfig::PC_LIBTHAI PKG_CONFIG_PREFIX PC_LIBTHAI PKG_CONFIG_SPECS ${LIBTHAI_PKG_CONFIG_SPEC}) + endif() + endif() + endif() +endmacro() + macro(WaylandProtocolGen _SCANNER _CODE_MODE _XML _PROTL) set(_WAYLAND_PROT_C_CODE "${CMAKE_CURRENT_BINARY_DIR}/wayland-generated-protocols/${_PROTL}-protocol.c") set(_WAYLAND_PROT_H_CODE "${CMAKE_CURRENT_BINARY_DIR}/wayland-generated-protocols/${_PROTL}-client-protocol.h") @@ -712,13 +825,29 @@ macro(CheckOpenVR) endif() endmacro() +# Requires +# - N/A +macro(FindOpenGLHeaders) + find_package(OpenGL MODULE) + # OPENGL_INCLUDE_DIRS is preferred over OPENGL_INCLUDE_DIR, but was only added in 3.29, + # If the CMake minimum version is changed to be >= 3.29, the second check should be removed. + if(OPENGL_INCLUDE_DIRS) + list(APPEND CMAKE_REQUIRED_INCLUDES ${OPENGL_INCLUDE_DIRS}) + elseif(OPENGL_INCLUDE_DIR) + list(APPEND CMAKE_REQUIRED_INCLUDES ${OPENGL_INCLUDE_DIR}) + endif() +endmacro() + # Requires: # - nada macro(CheckGLX) if(SDL_OPENGL) + cmake_push_check_state() + FindOpenGLHeaders() check_c_source_compiles(" #include int main(int argc, char** argv) { return 0; }" HAVE_OPENGL_GLX) + cmake_pop_check_state() if(HAVE_OPENGL_GLX AND NOT HAVE_ROCKCHIP) set(SDL_VIDEO_OPENGL_GLX 1) endif() @@ -752,10 +881,13 @@ endmacro() # - nada macro(CheckOpenGL) if(SDL_OPENGL) + cmake_push_check_state() + FindOpenGLHeaders() check_c_source_compiles(" #include #include int main(int argc, char** argv) { return 0; }" HAVE_OPENGL) + cmake_pop_check_state() if(HAVE_OPENGL) set(SDL_VIDEO_OPENGL 1) set(SDL_VIDEO_RENDER_OGL 1) @@ -768,6 +900,7 @@ endmacro() macro(CheckOpenGLES) if(SDL_OPENGLES) cmake_push_check_state() + FindOpenGLHeaders() list(APPEND CMAKE_REQUIRED_INCLUDES "${SDL3_SOURCE_DIR}/src/video/khronos") check_c_source_compiles(" #include @@ -873,6 +1006,9 @@ macro(CheckPTHREAD) set(PTHREAD_LDFLAGS "-pthread") elseif(QNX) # pthread support is baked in + elseif(HURD) + set(PTHREAD_CFLAGS "-D_REENTRANT") + set(PTHREAD_LDFLAGS "-pthread") else() set(PTHREAD_CFLAGS "-D_REENTRANT") set(PTHREAD_LDFLAGS "-lpthread") @@ -977,22 +1113,22 @@ macro(CheckUSBHID) cmake_push_check_state() check_library_exists(usbhid hid_init "" LIBUSBHID) if(LIBUSBHID) - check_include_file(usbhid.h HAVE_USBHID_H) + check_include_files("stdint.h;usbhid.h" HAVE_USBHID_H) if(HAVE_USBHID_H) set(USB_CFLAGS "-DHAVE_USBHID_H") endif() - check_include_file(libusbhid.h HAVE_LIBUSBHID_H) + check_include_files("stdint.h;libusbhid.h" HAVE_LIBUSBHID_H) if(HAVE_LIBUSBHID_H) string(APPEND USB_CFLAGS " -DHAVE_LIBUSBHID_H") endif() set(USB_LIBS ${USB_LIBS} usbhid) else() - check_include_file(usb.h HAVE_USB_H) + check_include_files("stdint.h;usb.h" HAVE_USB_H) if(HAVE_USB_H) set(USB_CFLAGS "-DHAVE_USB_H") endif() - check_include_file(libusb.h HAVE_LIBUSB_H) + check_include_files("stdint.h;libusb.h" HAVE_LIBUSB_H) if(HAVE_LIBUSB_H) string(APPEND USB_CFLAGS " -DHAVE_LIBUSB_H") endif() @@ -1005,7 +1141,7 @@ macro(CheckUSBHID) string(APPEND CMAKE_REQUIRED_FLAGS " ${USB_CFLAGS}") list(APPEND CMAKE_REQUIRED_LIBRARIES ${USB_LIBS}) check_c_source_compiles(" - #include + #include #if defined(HAVE_USB_H) #include #endif @@ -1031,7 +1167,7 @@ macro(CheckUSBHID) }" HAVE_USBHID) if(HAVE_USBHID) check_c_source_compiles(" - #include + #include #if defined(HAVE_USB_H) #include #endif @@ -1059,7 +1195,7 @@ macro(CheckUSBHID) endif() check_c_source_compiles(" - #include + #include #if defined(HAVE_USB_H) #include #endif @@ -1108,6 +1244,14 @@ endmacro() # Check for HIDAPI support macro(CheckHIDAPI) + if(ANDROID) + enable_language(CXX) + sdl_sources("${SDL3_SOURCE_DIR}/src/hidapi/android/hid.cpp") + endif() + if(IOS OR TVOS) + sdl_sources("${SDL3_SOURCE_DIR}/src/hidapi/ios/hid.m") + set(SDL_FRAMEWORK_COREBLUETOOTH 1) + endif() if(SDL_HIDAPI) set(HAVE_HIDAPI ON) if(SDL_HIDAPI_LIBUSB) @@ -1126,12 +1270,15 @@ macro(CheckHIDAPI) cmake_pop_check_state() if(HAVE_LIBUSB_H) set(HAVE_LIBUSB TRUE) - target_get_dynamic_library(dynamic_libusb LibUSB::LibUSB) - if(SDL_HIDAPI_LIBUSB_SHARED AND dynamic_libusb) - set(HAVE_HIDAPI_LIBUSB_SHARED ON) - set(SDL_LIBUSB_DYNAMIC "\"${dynamic_libusb}\"") - sdl_link_dependency(hidapi INCLUDES $) - else() + if(SDL_HIDAPI_LIBUSB_SHARED) + target_get_dynamic_library(dynamic_libusb LibUSB::LibUSB) + if(dynamic_libusb) + set(HAVE_HIDAPI_LIBUSB_SHARED ON) + set(SDL_LIBUSB_DYNAMIC "\"${dynamic_libusb}\"") + sdl_link_dependency(hidapi INCLUDES $) + endif() + endif() + if(NOT HAVE_HIDAPI_LIBUSB_SHARED) sdl_link_dependency(hidapi LIBS LibUSB::LibUSB PKG_CONFIG_SPECS "${LibUSB_PKG_CONFIG_SPEC}" CMAKE_MODULE LibUSB) endif() endif() @@ -1140,14 +1287,6 @@ macro(CheckHIDAPI) endif() if(HAVE_HIDAPI) - if(ANDROID) - enable_language(CXX) - sdl_sources("${SDL3_SOURCE_DIR}/src/hidapi/android/hid.cpp") - endif() - if(IOS OR TVOS) - sdl_sources("${SDL3_SOURCE_DIR}/src/hidapi/ios/hid.m") - set(SDL_FRAMEWORK_COREBLUETOOTH 1) - endif() set(HAVE_SDL_HIDAPI TRUE) if(SDL_JOYSTICK AND SDL_HIDAPI_JOYSTICK) diff --git a/libs/SDL3/cmake/sdlcommands.cmake b/libs/SDL3/cmake/sdlcommands.cmake index d658eb3f..58889d19 100644 --- a/libs/SDL3/cmake/sdlcommands.cmake +++ b/libs/SDL3/cmake/sdlcommands.cmake @@ -1,18 +1,40 @@ add_library(SDL3-collector INTERFACE) add_library(SDL3_test-collector INTERFACE) +function(sdl_source_group prefix_directory) + set(prefixed_list) + file(TO_CMAKE_PATH ${prefix_directory} normalized_prefix_path) + foreach(file in ${ARGN}) + file(TO_CMAKE_PATH ${file} normalized_path) + string(FIND "${normalized_path}" ${normalized_prefix_path} position) + if("${position}" EQUAL 0) + list(APPEND prefixed_list ${file}) + endif() + endforeach() + if(prefixed_list) + source_group(TREE ${prefix_directory} FILES ${prefixed_list}) + endif() +endfunction() + # Use sdl_glob_sources to add glob sources to SDL3-shared, to SDL3-static, or to both. function(sdl_glob_sources) cmake_parse_arguments(ARGS "" "" "SHARED;STATIC" ${ARGN}) - file(GLOB shared_sources ${ARGS_SHARED}) - file(GLOB static_sources ${ARGS_STATIC}) - file(GLOB both_sources ${ARGS_UNPARSED_ARGUMENTS}) + if(ARGS_SHARED) + file(GLOB shared_sources CONFIGURE_DEPENDS ${ARGS_SHARED}) + endif() + if(ARGS_STATIC) + file(GLOB static_sources CONFIGURE_DEPENDS ${ARGS_STATIC}) + endif() + if(ARGS_UNPARSED_ARGUMENTS) + file(GLOB both_sources CONFIGURE_DEPENDS ${ARGS_UNPARSED_ARGUMENTS}) + endif() if(TARGET SDL3-shared) target_sources(SDL3-shared PRIVATE ${shared_sources} ${both_sources}) endif() if(TARGET SDL3-static) target_sources(SDL3-static PRIVATE ${static_sources} ${both_sources}) endif() + sdl_source_group(${PROJECT_SOURCE_DIR} ${shared_sources} ${shared_sources} ${both_sources}) set_property(TARGET SDL3-collector APPEND PROPERTY INTERFACE_SOURCES ${shared_sources} ${static_sources} ${both_sources}) endfunction() @@ -25,6 +47,7 @@ function(sdl_sources) if(TARGET SDL3-static) target_sources(SDL3-static PRIVATE ${ARGS_STATIC} ${ARGS_UNPARSED_ARGUMENTS}) endif() + sdl_source_group(${PROJECT_SOURCE_DIR} ${ARGS_SHARED} ${ARGS_STATIC} ${ARGS_UNPARSED_ARGUMENTS}) set_property(TARGET SDL3-collector APPEND PROPERTY INTERFACE_SOURCES ${ARGS_SHARED} ${ARGS_STATIC} ${ARGS_UNPARSED_ARGUMENTS}) endfunction() @@ -131,7 +154,7 @@ function(sdl_compile_options) target_compile_options(SDL3-static ${visibility} ${escaped_opts}) endif() if(NOT ARGS_NO_EXPORT AND (ARGS_PUBLIC OR ARGS_INTERFACE)) - set_property(TARGET SDL3-collector APPEND PROPERTY INTERFACE_COMPILE_COMPILE_OPTIONS "${ARGS_UNPARSED_ARGUMENTS}") + set_property(TARGET SDL3-collector APPEND PROPERTY INTERFACE_COMPILE_OPTIONS "${ARGS_UNPARSED_ARGUMENTS}") endif() endfunction() diff --git a/libs/SDL3/cmake/sdlcompilers.cmake b/libs/SDL3/cmake/sdlcompilers.cmake index c3d8c470..cd4dde0f 100644 --- a/libs/SDL3/cmake/sdlcompilers.cmake +++ b/libs/SDL3/cmake/sdlcompilers.cmake @@ -3,6 +3,7 @@ macro(SDL_DetectCompiler) set(USE_GCC FALSE) set(USE_INTELCC FALSE) set(USE_QCC FALSE) + set(USE_TCC FALSE) if(CMAKE_C_COMPILER_ID MATCHES "Clang|IntelLLVM") set(USE_CLANG TRUE) # Visual Studio 2019 v16.2 added support for Clang/LLVM. @@ -10,12 +11,14 @@ macro(SDL_DetectCompiler) if(MSVC) set(MSVC_CLANG TRUE) endif() - elseif(CMAKE_COMPILER_IS_GNUCC) + elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU") set(USE_GCC TRUE) elseif(CMAKE_C_COMPILER_ID MATCHES "^Intel$") set(USE_INTELCC TRUE) elseif(CMAKE_C_COMPILER_ID MATCHES "QCC") set(USE_QCC TRUE) + elseif(CMAKE_C_COMPILER_ID MATCHES "TinyCC") + set(USE_TCC TRUE) endif() endmacro() @@ -39,7 +42,7 @@ function(SDL_AddCommonCompilerFlags TARGET) cmake_pop_check_state() endif() - if(USE_GCC OR USE_CLANG OR USE_INTELCC OR USE_QCC) + if(USE_GCC OR USE_CLANG OR USE_INTELCC OR USE_QCC OR USE_TCC) if(MINGW) # See if GCC's -gdwarf-4 is supported # See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101377 for why this is needed on Windows @@ -159,4 +162,68 @@ function(SDL_AddCommonCompilerFlags TARGET) sdl_target_compile_option_all_languages(${TARGET} "-fdiagnostics-color=always") endif() endif() + + if(USE_TCC) + sdl_target_compile_option_all_languages(${TARGET} "-DSTBI_NO_SIMD") + endif() +endfunction() + +function(check_x86_source_compiles BODY VAR) + if(ARGN) + message(FATAL_ERROR "Unknown arguments: ${ARGN}") + endif() + if(APPLE_MULTIARCH AND (SDL_CPU_X86 OR SDL_CPU_X64)) + set(test_conditional 1) + else() + set(test_conditional 0) + endif() + check_c_source_compiles(" + #if ${test_conditional} + # if defined(__i386__) || defined(__x86_64__) + # define test_enabled 1 + # else + # define test_enabled 0 /* feign success in Apple multi-arch configs */ + # endif + #else /* test normally */ + # define test_enabled 1 + #endif + #if test_enabled + ${BODY} + #else + int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + return 0; + } + #endif" ${VAR}) +endfunction() + +function(check_arm_source_compiles BODY VAR) + if(ARGN) + message(FATAL_ERROR "Unknown arguments: ${ARGN}") + endif() + if(APPLE_MULTIARCH AND (SDL_CPU_ARM32 OR SDL_CPU_ARM64)) + set(test_conditional 1) + else() + set(test_conditional 0) + endif() + check_c_source_compiles(" + #if ${test_conditional} + # if defined(__arm__) || defined(__aarch64__) + # define test_enabled 1 + # else + # define test_enabled 0 /* feign success in Apple multi-arch configs */ + # endif + #else /* test normally */ + # define test_enabled 1 + #endif + #if test_enabled + ${BODY} + #else + int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + return 0; + } + #endif" ${VAR}) endfunction() diff --git a/libs/SDL3/cmake/sdlcpu.cmake b/libs/SDL3/cmake/sdlcpu.cmake index 5c4b575e..fa7d4aa8 100644 --- a/libs/SDL3/cmake/sdlcpu.cmake +++ b/libs/SDL3/cmake/sdlcpu.cmake @@ -1,6 +1,6 @@ function(SDL_DetectTargetCPUArchitectures DETECTED_ARCHS) - set(known_archs EMSCRIPTEN ARM32 ARM64 ARM64EC LOONGARCH64 POWERPC32 POWERPC64 X86 X64) + set(known_archs EMSCRIPTEN ARM32 ARM64 ARM64EC LOONGARCH64 MIPS32 MIPS64 POWERPC32 POWERPC64 RISCV32 RISCV64 X86 X64) if(APPLE AND CMAKE_OSX_ARCHITECTURES) foreach(known_arch IN LISTS known_archs) @@ -37,8 +37,12 @@ function(SDL_DetectTargetCPUArchitectures DETECTED_ARCHS) set(arch_check_ARM64EC "defined(_M_ARM64EC)") set(arch_check_EMSCRIPTEN "defined(__EMSCRIPTEN__)") set(arch_check_LOONGARCH64 "defined(__loongarch64)") + set(arch_check_MIPS32 "(defined(__mips__) && !defined(__mips64))") + set(arch_check_MIPS64 "(defined(__mips__) && defined(__mips64))") set(arch_check_POWERPC32 "(defined(__PPC__) || defined(__powerpc__)) && !defined(__powerpc64__)") set(arch_check_POWERPC64 "defined(__PPC64__) || defined(__powerpc64__)") + set(arch_check_RISCV32 "defined(__riscv) && defined(__riscv_xlen) && __riscv_xlen == 32") + set(arch_check_RISCV64 "defined(__riscv) && defined(__riscv_xlen) && __riscv_xlen == 64") set(arch_check_X86 "defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) ||defined( __i386) || defined(_M_IX86)") set(arch_check_X64 "(defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64)) && !defined(_M_ARM64EC)") diff --git a/libs/SDL3/cmake/sdlplatform.cmake b/libs/SDL3/cmake/sdlplatform.cmake index 60e0e77f..f16fe3f2 100644 --- a/libs/SDL3/cmake/sdlplatform.cmake +++ b/libs/SDL3/cmake/sdlplatform.cmake @@ -26,6 +26,8 @@ function(SDL_DetectCMakePlatform) set(sdl_cmake_platform ngage) elseif(PS2) set(sdl_cmake_platform ps2) + elseif(RISCOS) + set(sdl_cmake_platform RISCOS) elseif(VITA) set(sdl_cmake_platform Vita) elseif(CMAKE_SYSTEM_NAME MATCHES ".*Linux") @@ -36,8 +38,9 @@ function(SDL_DetectCMakePlatform) set(sdl_cmake_platform NetBSD) elseif(CMAKE_SYSTEM_NAME MATCHES "kOpenBSD.*|OpenBSD.*") set(sdl_cmake_platform OpenBSD) - elseif(CMAKE_SYSTEM_NAME MATCHES ".*GNU.*") - set(sdl_cmake_platform GNU) + elseif(CMAKE_SYSTEM_NAME STREQUAL "GNU") + # GNU/Hurd must be checked AFTER RISCOS + set(sdl_cmake_platform Hurd) elseif(CMAKE_SYSTEM_NAME MATCHES ".*BSDI.*") set(sdl_cmake_platform BSDi) elseif(CMAKE_SYSTEM_NAME MATCHES "DragonFly.*|FreeBSD") diff --git a/libs/SDL3/cmake/test/test_pkgconfig.sh b/libs/SDL3/cmake/test/test_pkgconfig.sh index 5bb84df2..7362e74b 100644 --- a/libs/SDL3/cmake/test/test_pkgconfig.sh +++ b/libs/SDL3/cmake/test/test_pkgconfig.sh @@ -30,12 +30,12 @@ SDL_LDFLAGS="$( pkg-config sdl3 --libs )" SDL_STATIC_LDFLAGS="$( pkg-config sdl3 --libs --static )" compile_cmd="$CC -c "$testdir/main_gui.c" -o main_gui_pkgconfig.c.o $SDL_CFLAGS $CFLAGS" -link_cmd="$CC main_gui_pkgconfig.c.o -o ${EXEPREFIX}main_gui_pkgconfig${EXESUFFIX} $SDL_LDFLAGS $LDFLAGS" -static_link_cmd="$CC main_gui_pkgconfig.c.o -o ${EXEPREFIX}main_gui_pkgconfig_static${EXESUFFIX} $SDL_STATIC_LDFLAGS $LDFLAGS" +link_cmd="$CC main_gui_pkgconfig.c.o -o ${EXEPREFIX}main_gui_pkgconfig${EXESUFFIX} $SDL_CFLAGS $CFLAGS $SDL_LDFLAGS $LDFLAGS" +static_link_cmd="$CC main_gui_pkgconfig.c.o -o ${EXEPREFIX}main_gui_pkgconfig_static${EXESUFFIX} $SDL_CFLAGS $CFLAGS $SDL_STATIC_LDFLAGS $LDFLAGS" echo "-- CC: $CC" echo "-- CFLAGS: $CFLAGS" -echo "-- LDFLASG: $LDFLAGS" +echo "-- LDFLAGS: $LDFLAGS" echo "-- SDL_CFLAGS: $SDL_CFLAGS" echo "-- SDL_LDFLAGS: $SDL_LDFLAGS" echo "-- SDL_STATIC_LDFLAGS: $SDL_STATIC_LDFLAGS" diff --git a/libs/SDL3/docs/INTRO-emscripten.md b/libs/SDL3/docs/INTRO-emscripten.md index a0541c83..db6f5cc8 100644 --- a/libs/SDL3/docs/INTRO-emscripten.md +++ b/libs/SDL3/docs/INTRO-emscripten.md @@ -22,7 +22,7 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$") # This assumes the SDL source is available in vendored/SDL add_subdirectory(vendored/SDL EXCLUDE_FROM_ALL) -# on Web targets, we need CMake to generate a HTML webpage. +# on Web targets, we need CMake to generate a HTML webpage. if(EMSCRIPTEN) set(CMAKE_EXECUTABLE_SUFFIX ".html" CACHE INTERNAL "") endif() diff --git a/libs/SDL3/docs/INTRO-mingw.md b/libs/SDL3/docs/INTRO-mingw.md index d7c535dd..d6b425a3 100644 --- a/libs/SDL3/docs/INTRO-mingw.md +++ b/libs/SDL3/docs/INTRO-mingw.md @@ -1,6 +1,6 @@ # Introduction to SDL with MinGW -Without getting deep into the history, MinGW is a long running project that aims to bring gcc to Windows. That said, there's many distributions, versions, and forks floating around. We recommend installing [MSYS2](https://www.msys2.org/), as it's the easiest way to get a modern toolchain with a package manager to help with dependency management. This would allow you to follow the MSYS2 section below. +Without getting deep into the history, MinGW is a long running project that aims to bring gcc to Windows. That said, there's many distributions, versions, and forks floating around. We recommend installing [MSYS2](https://www.msys2.org/), as it's the easiest way to get a modern toolchain with a package manager to help with dependency management. This would allow you to follow the MSYS2 section below. Otherwise you'll want to follow the "Other Distributions" section below. @@ -89,7 +89,7 @@ This should print out which library directory we'll need to use when compiling, Now we should have everything needed to compile and run our program. You'll need to ensure to replace `` with the version of the release of SDL3 you downloaded, as well as use the `` we learned in the previous section. ```sh -gcc hello.c -o hello.exe -I SDL3-//include -L SDL3-//lib -lSDL3 -mwindows +gcc hello.c -o hello.exe -I SDL3-//include -L SDL3-//lib -lSDL3 -mwindows cp SDL3-//bin/SDL3.dll SDL3.dll ./hello.exe ``` diff --git a/libs/SDL3/docs/README-android.md b/libs/SDL3/docs/README-android.md index b2568e94..f7e2447c 100644 --- a/libs/SDL3/docs/README-android.md +++ b/libs/SDL3/docs/README-android.md @@ -168,7 +168,7 @@ build-scripts/create-android-project.py --variant aar com.yourcompany.yourapp < Customizing your application name ================================================================================ -To customize your application name, edit AndroidManifest.xml and build.gradle to replace +To customize your application name, edit build.gradle to replace "org.libsdl.app" with an identifier for your product package. Then create a Java class extending SDLActivity and place it in a directory @@ -194,6 +194,8 @@ Here's an example of a minimal class file: Then replace "SDLActivity" in AndroidManifest.xml with the name of your class, .e.g. "MyGame" +Then edit app/src/main/res/values/strings.xml and change the name there. + Customizing your application icon ================================================================================ @@ -598,7 +600,7 @@ The only caveat is that the APK's support a single architecture. When configuring the CMake project, you need to use the Android NDK CMake toolchain, and pass the Android home path through `SDL_ANDROID_HOME`. ``` -cmake .. -DCMAKE_TOOLCHAIN_FILE= -DANDROID_ABI= -DSDL_ANDROID_HOME= -DANDROID_PLATFORM=23 -DSDL_TESTS=ON +cmake .. -DCMAKE_TOOLCHAIN_FILE= -DANDROID_ABI= -DSDL_ANDROID_HOME= -DANDROID_PLATFORM=21 -DSDL_TESTS=ON ``` Remarks: diff --git a/libs/SDL3/docs/README-bsd.md b/libs/SDL3/docs/README-bsd.md index d823060d..0f94470d 100644 --- a/libs/SDL3/docs/README-bsd.md +++ b/libs/SDL3/docs/README-bsd.md @@ -4,3 +4,4 @@ SDL is fully supported on BSD platforms, and is built using [CMake](README-cmake If you want to run on the console, you can take a look at [KMSDRM support on BSD](README-kmsbsd.md) +SDL is [not designed to be used in setuid or setgid executables](README-platforms.md#setuid). diff --git a/libs/SDL3/docs/README-documentation-rules.md b/libs/SDL3/docs/README-documentation-rules.md index e6dfa25e..2a4d96f1 100644 --- a/libs/SDL3/docs/README-documentation-rules.md +++ b/libs/SDL3/docs/README-documentation-rules.md @@ -274,6 +274,23 @@ comment. So don't mention the type a second time in the documentation if possible. It looks cluttered and repetitive to do so. +## Keep `\param` and `\returns` sections short. + +These strings end up in a table that we don't want to be bulky. +Try to keep these to one sentence/phrase where possible. If you need more +detail--even extremely common details, like "you need to free the returned +pointer"--put that information in the general Remarks section, where you +can be as verbose as you like. + +(One exception for SDL: the return value almost always notes that on error, +you should call SDL_GetError() to get more information. The documentation +is so saturated with this that it's just the standard now.) + +Convention at the moment is that pointer params that are permitted to +be NULL, which is somewhat uncommon, end with terse "May be NULL." sentence +at the end, and pointers that must be non-NULL (most of them) say nothing. +This is fine. + ## Code examples go in the wiki. We don't want the headers cluttered up with code examples. These live on the @@ -446,3 +463,19 @@ Beyond stripping the initial ` * ` portion off each line, these comments are treated as pure Markdown. They don't support any Doxygen tags like `\sa` or `\since`. +## Enum/struct versioning + +If you have an enum or struct, it'll list its `\since` field as the first SDL +release it was available in. However, we might later add new values to an enum +or fields to a struct. These lines, arriving in a newer version, should have a +note about that, like this one on SDL_SCALEMODE_PIXELART: + +```c +typedef enum SDL_ScaleMode +{ + SDL_SCALEMODE_INVALID = -1, + SDL_SCALEMODE_NEAREST, /**< nearest pixel sampling */ + SDL_SCALEMODE_LINEAR, /**< linear filtering */ + SDL_SCALEMODE_PIXELART /**< nearest pixel sampling with improved scaling for pixel art (since SDL 3.3.0) */ +} SDL_ScaleMode; +``` diff --git a/libs/SDL3/docs/README-emscripten.md b/libs/SDL3/docs/README-emscripten.md index 54e7d2de..ae2af4a7 100644 --- a/libs/SDL3/docs/README-emscripten.md +++ b/libs/SDL3/docs/README-emscripten.md @@ -101,7 +101,7 @@ don't want any shutdown code that might be sitting below this code to actually run if main() were to continue on, since we're just getting started. -Another option is to use SDL' main callbacks, which handle this for you +Another option is to use SDL's main callbacks, which handle this for you without platform-specific code in your app. Please refer to [the wiki](https://wiki.libsdl.org/SDL3/README-main-functions#main-callbacks-in-sdl3) or `docs/README-main-functions.md` in the SDL source code. @@ -169,31 +169,33 @@ for several reasons, not the least of which being that no one likes when a random browser tab suddenly starts making noise and the user has to scramble to figure out which and silence it. -SDL will allow you to open the audio device for playback in this -circumstance, and your audio callback will fire, but SDL will throw the audio -data away until the user interacts with the page. This helps apps that depend -on the audio callback to make progress, and also keeps audio playback in sync +SDL will allow you to open the audio device for playback in this circumstance, +and your audio streams will consume data, but SDL will throw the audio data +away until the user interacts with the page. This helps apps that depend on +the audio callback to make progress, and also keeps audio playback in sync once the app is finally allowed to make noise. There are two reasonable ways to deal with the silence at the app level: if you are writing some sort of media player thing, where the user expects there to be a volume control when you mouseover the canvas, just default that control to a muted state; if the user clicks on the control to unmute -it, on this first click, open the audio device. This allows the media to +it, on this first click, adjust your app's volume appropriately, and SDL will +also start actually feeding the data to the browser. This allows the media to play at start, and the user can reasonably opt-in to listening. -Many games do not have this sort of UI, and are more rigid about starting -audio along with everything else at the start of the process. For these, your -best bet is to write a little Javascript that puts up a "Click here to play!" -UI, and upon the user clicking, remove that UI and then call the Emscripten -app's main() function. As far as the application knows, the audio device was -available to be opened as soon as the program started, and since this magic -happens in a little Javascript, you don't have to change your C/C++ code at -all to make it happen. +Many games do not have this sort of UI. For these, your best bet might be to +write a little Javascript that puts up a "Click here to play!" UI, and upon +the user clicking, remove that UI and then call the Emscripten app's main() +function. As far as the application knows, audio was able to play as soon as +the program started, and since this magic happens in a little Javascript, you +don't have to change your C/C++ code at all to make it happen. Please see the discussion at https://github.com/libsdl-org/SDL/issues/6385 for some Javascript code to steal for this approach. +But if a game can just do without audio until the user clicks on the page, +it will still operate correctly, as if the page was merely muted before then. + ## Rendering @@ -208,12 +210,35 @@ Calling SDL_RenderPresent (or SDL_GL_SwapWindow) will not actually present anything on the screen until your return from your mainloop function. +Note that SDL attempts to default to vsync _off_ on all platforms. You almost +certainly do _not_ want this in Emscripten, however, as it will affect the +efficiency of the mainloop. If using OpenGL directly, you should call +SDL_GL_SetSwapInterval(1) sometime near startup; if using the 2D render API, +either create the renderer with with the property +SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER set to 1, or call +SDL_SetRenderVSync(renderer, 1). If you don't explicitly set vsync, you'll get +a higher (but perhaps unstable) framerate, and use more power, but it will +still work. Choosing a vsync of 1 will use requestAnimationFrame if possible. + +If you're using the SDL main callbacks, the mainloop defaults to using +requestAnimationFrame (effectively vsync), because it calls +emscripten_set_main_loop() with a zero fps. This is almost certainly what you +want to do! Do this even if you aren't using the main callbacks! +SDL will attempt to accomodate the app if it messes with vsync settings, or +doesn't use requestAnimationFrame, but modern thinking is that this is the +most efficient, consistent, and correct way to run a game in a web browser. + ## Building SDL/emscripten +Use the latest stable Emscripten release! -SDL currently requires at least Emscripten 3.16.0 to build. Newer versions -are likely to work, as well. +It's possible to build SDL with older Emscripten releases, such as 3.x, but +several things will be silently broken, as bugs got fixed and web standards +solidified over time. At the time of this writing, Emscripten 4.0.x is the +current stable release. You're encouraged to install the latest stable release +(`emsdk install latest ; emsdk activate latest` if using Emscripten's setup +script), and make sure you're reasonably up to date as time goes on. Build: @@ -239,7 +264,7 @@ If you want to build with thread support, something like this works: ```bash mkdir build cd build -emcmake cmake -DSDL_THREADS=ON .. +emcmake cmake -DSDL_PTHREADS=ON .. # you can also do `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command. emmake make -j4 ``` @@ -321,6 +346,80 @@ all has to live in memory at runtime. [Emscripten's documentation on the matter](https://emscripten.org/docs/porting/files/packaging_files.html) gives other options and details, and is worth a read. +Please also read the next section on persistent storage, for a little help +from SDL. + + +## Automount persistent storage + +The file tree in Emscripten is provided by MEMFS by default, which stores all +files in RAM. This is often what you want, because it's fast and can be +accessed with the usual synchronous i/o functions like fopen or SDL_IOFromFile. +You can also write files to MEMFS, but when the browser tab goes away, so do +the files. But we want things like high scores, save games, etc, to still +exist if we reload the game later. + +For this, Emscripten offers IDBFS, which backs files with the browser's +[IndexedDB](https://en.wikipedia.org/wiki/IndexedDB) functionality. + +To use this, the app has to mount the IDBFS filesystem somewhere in the +virtual file tree, and then wait for it to sync up. This needs to be done in +Javascript code. The sync will not complete until at least one (but possibly +several) iterations of the mainloop have passed, which means you can not +access any saved files during main() or SDL_AppInit() by default. + +SDL can solve this problem for you: it can be built to automatically mount the +persistent files from IDBFS to a specific place in the file tree and wait +until the sync has completed before calling main() or SDL_AppInit(), so to +your C code, it looks like the files were always available. + +To use this functionality, set the CMake variable +`SDL_EMSCRIPTEN_PERSISTENT_PATH` to a path in the filetree where persistent +storage should be mounted: + +```bash +mkdir build +cd build +emcmake cmake -DSDL_EMSCRIPTEN_PERSISTENT_PATH=/storage .. +``` + +You should also link your app with `-lidbfs.js`. If your project links to SDL +using CMake's find_package(SDL3), or uses `pkg-config sdl3 --libs`, this will +be handled for you when used with an SDL built with +`-DSDL_EMSCRIPTEN_PERSISTENT_PATH`. + +Now `/storage` will be prepared when your program runs, and SDL_GetPrefPath() +will return a directory under that path. The storage is mounted with the +`autoPersist: true` option, so when you write to that tree, whether with +SDL APIs or other functions like fopen(), Emscripten will know it needs to +sync that data back to the persistent database, and will do so automatically +within the next few iterations of the mainloop. + +It's best to assume the sync will take a few frames to complete, and the +data is not safe until it does. + +To summarize how to automate this: + +- Build with `emcmake cmake -DSDL_EMSCRIPTEN_PERSISTENT_PATH=/storage` +- Link your app with `-lidbfs.js` if not handled automatically. +- Write under `/storage`, or use SDL_GetPrefPath() + + +## Customizing index.html + +You don't have to use the HTML that Emscripten produces; the above examples +use `emcc -o index.html`, but you can `-o index.js` instead to just output +code without an HTML page, and then provide your own. This is desirable for +shipping products, even though the Emscripten-provided HTML is fine for +prototyping. Certain things _must_ be in the HTML file or your program will +not function correctly (or function at all). The specifics are beyond the +scope of this document, but it's likely best to start with the Emscripten HTML +and customize it, instead of starting from scratch. + +The `` element in the HTML _must not_ have a border or padding, or +things will break in unexpected ways. This can be surprising when customizing +the page's look. Plan accordingly. + ## Debugging @@ -336,9 +435,9 @@ If you try debugging in Firefox and it doesn't work well for no apparent reason, try Chrome, and vice-versa. These tools are still relatively new, and improving all the time. -SDL_Log() (or even plain old printf) will write to the Javascript console, -and honestly I find printf-style debugging to be easier than setting up a build -for proper debugging, so use whatever tools work best for you. +SDL_Log() (or printf) will write to the Javascript console, +so printf-style debugging can be easier than setting up a build +for proper debugging. Use whatever tools work best for you. ## Questions? diff --git a/libs/SDL3/docs/README-gdk.md b/libs/SDL3/docs/README-gdk.md index acba7bc3..3af5ad46 100644 --- a/libs/SDL3/docs/README-gdk.md +++ b/libs/SDL3/docs/README-gdk.md @@ -32,7 +32,8 @@ The Windows GDK port supports the full set of Win32 APIs, renderers, controllers * Single-player games have some additional features available: * Call `SDL_GetGDKDefaultUser` to get the default XUserHandle pointer. - * `SDL_GetPrefPath` still works, but only for single-player titles. + * `SDL_GetPrefPath` and `SDL_Storage` still work, but only for single-player titles. + * To enable support for these APIs, use `SDL_SetHint` to set `SDL_GDK_SERVICE_CONFIGURATION_ID` to your SCID string at startup (it should be formatted like `"00000000-0000-0000-0000-000000000000"`) These functions mostly wrap around async APIs, and thus should be treated as synchronous alternatives. Also note that the single-player functions return on any OS errors, so be sure to validate the return values! diff --git a/libs/SDL3/docs/README-ios.md b/libs/SDL3/docs/README-ios.md index 166d1826..54cd9ed2 100644 --- a/libs/SDL3/docs/README-ios.md +++ b/libs/SDL3/docs/README-ios.md @@ -12,9 +12,44 @@ Instructions: 2. Select your desired target, and hit build. -Using the Simple DirectMedia Layer for iOS +Using the Simple DirectMedia Layer for iOS with the SDL3 xcFramework ============================================================================== +The recommended way to use SDL for iOS is by including the SDL3.xcframework which is now a build target of the SDL.xcodeproj file. An xcframework is a new (Xcode 11) uber-framework which can handle any combination of processor type and target OS platform. +You can either build the SDL3.xcframework yourself or download the latest release disk image asset (*.dmg). + +In the past, iOS devices were always an ARM variant processor, and the simulator was always i386 or x86_64, and thus libraries could be combined into a single framework for both simulator and device. With the introduction of the Apple Silicon ARM-based machines, regular frameworks would collide as CPU type was no longer sufficient to differentiate the platform. So Apple created the new xcframework library package. + +The xcframework target builds into a Products directory alongside the SDL.xcodeproj file, as SDL3.xcframework. This can be brought in to any iOS project and will function properly for both simulator and device, no matter their CPUs. Note that Intel Macs cannot cross-compile for Apple Silicon Macs. If you need AS compatibility, perform this build on an Apple Silicon Mac. + +This target requires Xcode 11 or later. The target will simply fail to build if attempted on older Xcodes. + +In addition, on Apple platforms, main() cannot be in a dynamically loaded library. +However, unlike in SDL2, in SDL3 SDL_main is implemented inline in SDL_main.h, so you don't need to link against a static libSDL3main.lib, and you don't need to copy a .c file from the SDL3 source either. +This means that iOS apps which used the statically-linked libSDL3.lib and now link with the xcframwork can just `#include ` in the source file that contains their standard `int main(int argc, char *argv[])` function to get a header-only SDL_main implementation that calls the `SDL_RunApp()` with your standard main function. + +To use the SDL3.xcframework follow these steps: + +1. Run Xcode and create a new project using the iOS Game template, selecting the Objective C language and Metal game technology. +2. In the main view, delete all files except for Assets and LaunchScreen +3. Select the project in the main view, go to the "General" tab, scroll down to "Frameworks, Libraries, and Embedded Content", and drag and drop the SDL3.xcframework +4. Still in "Frameworks, Libraries, and Embedded Content", select "Embed & Sign" for the SDL3.xcframework. +5. Add the source files that you would normally have for an SDL program, making sure to have #include at the top of the file containing your main() function. +6. Add any assets that your application needs. +7. Enjoy! + +Using an xcFramework is similar to using a regular framework. However, issues have been seen with the build system not seeing the headers in the xcFramework. To remedy this, add the path to the xcFramework in your app's target ==> Build Settings ==> Framework Search Paths and mark it recursive (this is critical). Also critical is to remove "*.framework" from Build Settings ==> Sub-Directories to Exclude in Recursive Searches. Clean the build folder, and on your next build the build system should be able to see any of these in your code, as expected: + +#include "SDL3/SDL_main.h" +#include +#include + + +Using the Simple DirectMedia Layer for iOS by adding the SDL3 Xcode project +============================================================================== + +To maintain compatibility with older Xcode versions than version 11 you can add the SDL3 Xcode project file to your project: + 1. Run Xcode and create a new project using the iOS Game template, selecting the Objective C language and Metal game technology. 2. In the main view, delete all files except for Assets and LaunchScreen 3. Right click the project in the main view, select "Add Files...", and add the SDL project, Xcode/SDL/SDL.xcodeproj @@ -26,6 +61,19 @@ Using the Simple DirectMedia Layer for iOS 9. Add any assets that your application needs. 10. Enjoy! +Notes for distributing your iOS app on the AppStore when using the embedded SDL3 Xcode project: +Embedding the SDL3 Xcode project makes SDL3.framework a target of your app, so it will be included in the archive created during the "Archive" step required for App Store submission. As this prevents successful distribution, remove the framework via a script in the Build Phase after the "Embed & Sign" step. + +1. Select the project in the main view, go to the "Build Phases" tab, click on the big '+' in this tab and click "New Run Script Phase". +2. Scroll down to "Run Script" (after the "Embed SDL3 Framework") and enter the following script: +``` + if [ -d "$INSTALL_ROOT/Library" ]; then + echo "Removing SDL3.framework from INSTALL_ROOT for archiving" + rm -rf "$INSTALL_ROOT/Library" + fi +``` +3. Below the script entry uncheck the "Run Script:" options "For install builds only" and "Based on dependency analysis" +4. Edit the Build Settings and set "User Script Sandboxing" to "No". TODO: Add information regarding App Store requirements such as icons, etc. @@ -55,6 +103,19 @@ matrix using the size in points (SDL_GetWindowSize()) can be used in order to display content at the same scale no matter whether a Retina device is used or not. +Notes -- Getting full screen resolution +============================================================================== + +Make sure that you have a Launch Screen key in your Info.plist, e.g. +``` + UILaunchScreen + +``` +If you don't specify a launch screen, then the OS will assume that your +application needs an older compatibility mode and will get a limited +resolution screen. + + Notes -- Application events ============================================================================== @@ -154,26 +215,7 @@ More information on this subject is available here: http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Introduction/Introduction.html -Notes -- xcFramework -============================================================================== -The SDL.xcodeproj file now includes a target to build SDL3.xcframework. An xcframework is a new (Xcode 11) uber-framework which can handle any combination of processor type and target OS platform. - -In the past, iOS devices were always an ARM variant processor, and the simulator was always i386 or x86_64, and thus libraries could be combined into a single framework for both simulator and device. With the introduction of the Apple Silicon ARM-based machines, regular frameworks would collide as CPU type was no longer sufficient to differentiate the platform. So Apple created the new xcframework library package. - -The xcframework target builds into a Products directory alongside the SDL.xcodeproj file, as SDL3.xcframework. This can be brought in to any iOS project and will function properly for both simulator and device, no matter their CPUs. Note that Intel Macs cannot cross-compile for Apple Silicon Macs. If you need AS compatibility, perform this build on an Apple Silicon Mac. - -This target requires Xcode 11 or later. The target will simply fail to build if attempted on older Xcodes. - -In addition, on Apple platforms, main() cannot be in a dynamically loaded library. -However, unlike in SDL2, in SDL3 SDL_main is implemented inline in SDL_main.h, so you don't need to link against a static libSDL3main.lib, and you don't need to copy a .c file from the SDL3 source either. -This means that iOS apps which used the statically-linked libSDL3.lib and now link with the xcframwork can just `#include ` in the source file that contains their standard `int main(int argc, char *argv[])` function to get a header-only SDL_main implementation that calls the `SDL_RunApp()` with your standard main function. - -Using an xcFramework is similar to using a regular framework. However, issues have been seen with the build system not seeing the headers in the xcFramework. To remedy this, add the path to the xcFramework in your app's target ==> Build Settings ==> Framework Search Paths and mark it recursive (this is critical). Also critical is to remove "*.framework" from Build Settings ==> Sub-Directories to Exclude in Recursive Searches. Clean the build folder, and on your next build the build system should be able to see any of these in your code, as expected: - -#include "SDL_main.h" -#include -#include Notes -- iPhone SDL limitations diff --git a/libs/SDL3/docs/README-linux.md b/libs/SDL3/docs/README-linux.md index 42891def..ced2ebbd 100644 --- a/libs/SDL3/docs/README-linux.md +++ b/libs/SDL3/docs/README-linux.md @@ -8,6 +8,7 @@ system does not have the XRandR libraries installed, it will be disabled at runtime, and you won't get a missing library error, at least with the default configuration parameters. +SDL is [not designed to be used in setuid or setgid executables](README-platforms.md#setuid). Build Dependencies -------------------------------------------------------------------------------- @@ -16,26 +17,28 @@ Ubuntu 18.04, all available features enabled: sudo apt-get install build-essential git make \ pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \ - libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \ + libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \ libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \ libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \ - libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev + libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev libthai-dev libusb-1.0-0-dev Ubuntu 22.04+ can also add `libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev` to that command line. Fedora 35, all available features enabled: - sudo yum install gcc git-core make cmake \ - alsa-lib-devel pulseaudio-libs-devel nas-devel pipewire-devel \ + sudo dnf install gcc git-core make cmake \ + alsa-lib-devel fribidi-devel pulseaudio-libs-devel pipewire-devel \ libX11-devel libXext-devel libXrandr-devel libXcursor-devel libXfixes-devel \ - libXi-devel libXScrnSaver-devel dbus-devel ibus-devel \ + libXi-devel libXScrnSaver-devel libXtst-devel dbus-devel ibus-devel \ systemd-devel mesa-libGL-devel libxkbcommon-devel mesa-libGLES-devel \ mesa-libEGL-devel vulkan-devel wayland-devel wayland-protocols-devel \ - libdrm-devel mesa-libgbm-devel libusb-devel libdecor-devel \ - pipewire-jack-audio-connection-kit-devel \ + libdrm-devel mesa-libgbm-devel libusb1-devel libdecor-devel \ + pipewire-jack-audio-connection-kit-devel libthai-devel Fedora 39+ can also add `liburing-devel` to that command line. +Fedora 40+ needs `zlib-ng-compat-static` to be added to that command line. + NOTES: - The sndio audio target is unavailable on Fedora (but probably not what you should want to use anyhow). @@ -43,11 +46,12 @@ NOTES: openSUSE Tumbleweed: sudo zypper in libunwind-devel libusb-1_0-devel Mesa-libGL-devel libxkbcommon-devel libdrm-devel \ - libgbm-devel pipewire-devel libpulse-devel sndio-devel Mesa-libEGL-devel + libgbm-devel pipewire-devel libpulse-devel sndio-devel Mesa-libEGL-devel alsa-devel xwayland-devel \ + wayland-devel wayland-protocols-devel libthai-devel fribidi-devel Arch: - sudo pacman -S alsa-lib cmake hidapi ibus jack libdecor libgl libpulse libusb libx11 libxcursor libxext libxinerama libxkbcommon libxrandr libxrender libxss libxtst mesa ninja pipewire sndio vulkan-driver vulkan-headers wayland wayland-protocols + sudo pacman -S alsa-lib cmake hidapi ibus jack libdecor libthai fribidi libgl libpulse libusb libx11 libxcursor libxext libxfixes libxi libxinerama libxkbcommon libxrandr libxrender libxss libxtst mesa ninja pipewire sndio vulkan-driver vulkan-headers wayland wayland-protocols Joystick does not work diff --git a/libs/SDL3/docs/README-macos.md b/libs/SDL3/docs/README-macos.md index fb0639b6..8e8453c2 100644 --- a/libs/SDL3/docs/README-macos.md +++ b/libs/SDL3/docs/README-macos.md @@ -32,6 +32,10 @@ sudo cmake --install . Please note that building SDL requires at least Xcode 12.2 and the macOS 11.0 SDK. +If you are getting errors building SDL_mfijoystick.m with Xcode 12.2, find your SDKs +directory and move MacOSX10.15.sdk out of the way so it isn't accidentally being used +by the build environment. + To use the library once it's built, you essential have two possibilities: use the traditional autoconf/automake/make method, or use Xcode. @@ -49,7 +53,7 @@ NSApplicationDelegate implementation: ```objc - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { - if (SDL_GetEventState(SDL_EVENT_QUIT) == SDL_ENABLE) { + if (SDL_EventEnabled(SDL_EVENT_QUIT)) { SDL_Event event; SDL_zero(event); event.type = SDL_EVENT_QUIT; @@ -61,7 +65,7 @@ NSApplicationDelegate implementation: - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { - if (SDL_GetEventState(SDL_EVENT_DROP_FILE) == SDL_ENABLE) { + if (SDL_EventEnabled(SDL_EVENT_DROP_FILE)) { SDL_Event event; SDL_zero(event); event.type = SDL_EVENT_DROP_FILE; @@ -73,6 +77,8 @@ NSApplicationDelegate implementation: } ``` +SDL is [not designed to be used in setuid or setgid executables](README-platforms.md#setuid). + # Using the Simple DirectMedia Layer with a traditional Makefile An existing build system for your SDL app has good chances to work almost diff --git a/libs/SDL3/docs/README-main-functions.md b/libs/SDL3/docs/README-main-functions.md index 75b9e2cc..7574cff6 100644 --- a/libs/SDL3/docs/README-main-functions.md +++ b/libs/SDL3/docs/README-main-functions.md @@ -206,6 +206,13 @@ The SDL_AppResult value that terminated the app is provided here, in case it's useful to know if this was a successful or failing run of the app. +## Using main functions from other languages + +If you're not using C/C++, using SDL's entry points is still possible but is +more complex. Please refer to https://wiki.libsdl.org/SDL3/NonstandardStartup +for the technical details. + + ## Summary and Best Practices - **Always Include SDL_main.h in One Source File:** When working with SDL, diff --git a/libs/SDL3/docs/README-migration.md b/libs/SDL3/docs/README-migration.md index 8b71b9d0..6fe62dce 100644 --- a/libs/SDL3/docs/README-migration.md +++ b/libs/SDL3/docs/README-migration.md @@ -611,6 +611,9 @@ The following enums have been renamed: The following structures have been renamed: * SDL_GameController => SDL_Gamepad +The following structures have been removed: +* SDL_GameControllerButtonBind - replaced with SDL_GamepadBinding + The following functions have been renamed: * SDL_GameControllerAddMapping() => SDL_AddGamepadMapping() * SDL_GameControllerAddMappingsFromFile() => SDL_AddGamepadMappingsFromFile() @@ -1444,7 +1447,7 @@ The following functions have been removed: * SDL_GetTextureUserData() - use SDL_GetTextureProperties() instead * SDL_RenderGetIntegerScale() * SDL_RenderSetIntegerScale() - this is now explicit with SDL_LOGICAL_PRESENTATION_INTEGER_SCALE -* SDL_RenderTargetSupported() - render targets are always supported +* SDL_RenderTargetSupported() - render targets are usually supported; just create a texture with SDL_TEXTUREACCESS_TARGET and see if it fails. * SDL_SetTextureUserData() - use SDL_GetTextureProperties() instead The following enums have been renamed: @@ -1458,7 +1461,7 @@ The following symbols have been removed: * SDL_RENDERER_ACCELERATED - all renderers except `SDL_SOFTWARE_RENDERER` are accelerated * SDL_RENDERER_PRESENTVSYNC - replaced with SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER during renderer creation and SDL_PROP_RENDERER_VSYNC_NUMBER after renderer creation * SDL_RENDERER_SOFTWARE - you can check whether the name of the renderer is `SDL_SOFTWARE_RENDERER` -* SDL_RENDERER_TARGETTEXTURE - all renderers support target texture functionality +* SDL_RENDERER_TARGETTEXTURE - most renderers support target texture functionality; just create a texture with SDL_TEXTUREACCESS_TARGET and see if it fails. * SDL_ScaleModeBest - use SDL_SCALEMODE_LINEAR instead ## SDL_rwops.h diff --git a/libs/SDL3/docs/README-ngage.md b/libs/SDL3/docs/README-ngage.md index ab5b402c..beed7af0 100644 --- a/libs/SDL3/docs/README-ngage.md +++ b/libs/SDL3/docs/README-ngage.md @@ -44,7 +44,7 @@ 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) + [SDL3 main callbacks](https://wiki.libsdl.org/SDL3/README-main-functions#main-callbacks-in-sdl3) are not optional and must be used. This is important as the callbacks are optional on other platforms. diff --git a/libs/SDL3/docs/README-platforms.md b/libs/SDL3/docs/README-platforms.md index 765cace3..fe7a51b1 100644 --- a/libs/SDL3/docs/README-platforms.md +++ b/libs/SDL3/docs/README-platforms.md @@ -2,29 +2,36 @@ ## Supported Platforms +SDL3 has been known to work on the following platforms at some point: + - [Android](README-android.md) -- [Emscripten](README-emscripten.md) +- [Emscripten](README-emscripten.md) (Web browsers) - [FreeBSD](README-bsd.md) - [Haiku OS](README-haiku.md) - [iOS](README-ios.md) - [Linux](README-linux.md) -- [macOS](README-macos.md) +- [macOS](README-macos.md) (10.14 and later) - [NetBSD](README-bsd.md) -- [Nintendo Switch](README-switch.md) -- [Nintendo 3DS](README-n3ds.md) +- [Nintendo Switch](README-switch.md) (Separate NDA-only fork) +- [Nintendo 3DS](README-n3ds.md) (Homebrew) +- [Nokia N-Gage](README-ngage.md) - [OpenBSD](README-bsd.md) -- [PlayStation 2](README-ps2.md) -- [PlayStation 4](README-ps4.md) -- [PlayStation 5](README-ps5.md) -- [PlayStation Portable](README-psp.md) -- [PlayStation Vita](README-vita.md) +- [PlayStation 2](README-ps2.md) (Homebrew) +- [PlayStation 4](README-ps4.md) (Separate NDA-only fork) +- [PlayStation 5](README-ps5.md) (Separate NDA-only fork) +- [PlayStation Portable](README-psp.md) (Homebrew) +- [PlayStation Vita](README-vita.md) (Homebrew) - [RISC OS](README-riscos.md) - [SteamOS](README-steamos.md) - [tvOS](README-ios.md) -- [Windows](README-windows.md) +- [visionOS](README-ios.md) +- [Windows](README-windows.md) (XP and later) - [Windows GDK](README-gdk.md) - [Xbox](README-gdk.md) -- [Nokia N-Gage](README-ngage.md) + +Note that the SDL maintainers do not test on all these platforms; if a less-common system breaks, [please let us know](https://github.com/libsdl-org/SDL/issues/new) and send patches if you can. + +If you'd like to port SDL to a new platform, feel free to get in touch! [A guide to porting SDL2](https://discourse.libsdl.org/t/port-sdl-2-0-to-bios/25453/2) was written a while ago, and most of it still applies to SDL3. ## Unsupported Platforms @@ -32,9 +39,24 @@ If your favorite system is listed below, we aren't working on it. However, if yo All of these still work with [SDL2](/SDL2), which is an incompatible API, but an option if you need to support these platforms still. +- QNX - Google Stadia - NaCL - OS/2 -- QNX - WinPhone - WinRT/UWP + +## General notes for Unix platforms + +Some aspects of SDL functionality are common to all Unix-based platforms. + +### Privileged processes (setuid, setgid, setcap) + +SDL is not designed to be used in programs with elevated privileges, +such as setuid (`chmod u+s`) or setgid (`chmod g+s`) executables, +or executables with file-based capabilities +(`setcap cap_sys_nice+ep` or similar). +It does not make any attempt to avoid trusting environment variables +or other aspects of the inherited execution environment. +Programs running with elevated privileges in an attacker-controlled +execution environment should not call SDL functions. diff --git a/libs/SDL3/docs/README-ps2.md b/libs/SDL3/docs/README-ps2.md index d3585653..6d3e79a5 100644 --- a/libs/SDL3/docs/README-ps2.md +++ b/libs/SDL3/docs/README-ps2.md @@ -11,11 +11,20 @@ Credit to ## Building To build SDL library for the PS2, make sure you have the latest PS2Dev status and run: ```bash -cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=$PS2DEV/ps2sdk/ps2dev.cmake +export PS2DEV=/usr/local/ps2dev # or wherever your ps2dev installation is +export PS2SDK=$PS2DEV/ps2sdk +export PATH=$PATH:$PS2DEV/bin:$PS2DEV/ee/bin:$PS2DEV/iop/bin:$PS2DEV/dvp/bin:$PS2SDK/bin +cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=$PS2DEV/share/ps2dev.cmake cmake --build build cmake --install build ``` +## Hints +- `SDL_HINT_PS2_GS_WIDTH`: Width of the framebuffer. Defaults to 640. +- `SDL_HINT_PS2_GS_HEIGHT`: Height of the framebuffer. Defaults to 448. +- `SDL_HINT_PS2_GS_PROGRESSIVE`: Whether to use progressive, instead of interlaced. Defaults to 0. +- `SDL_HINT_PS2_GS_MODE`: Regional standard of the signal. "NTSC" (60hz), "PAL" (50hz) or "" (the console's region, default). + ## Notes If you trying to debug a SDL app through [ps2client](https://github.com/ps2dev/ps2client) you need to avoid the IOP reset, otherwise you will lose the connection with your computer. So to avoid the reset of the IOP CPU, you need to call to the macro `SDL_PS2_SKIP_IOP_RESET();`. diff --git a/libs/SDL3/docs/README-switch.md b/libs/SDL3/docs/README-switch.md index 16b7acd1..bbcbd112 100644 --- a/libs/SDL3/docs/README-switch.md +++ b/libs/SDL3/docs/README-switch.md @@ -1,3 +1,3 @@ # Nintendo Switch -SDL3 runs on the Nintendo Switch! There are commercial games shipping with this port. This port is kept in a separate repository, but is available for free, under the zlib license, to anyone that is under NDA for Switch development with Nintendo. Please contact Ryan (icculus at icculus dot org) for details. +SDL3 runs on the Nintendo Switch! Both Switch and Switch 2 are supported. There are commercial games shipping with this port. This port is kept in a separate repository, but is available for free, under the zlib license, to anyone that is under the appropriate NDAs with Nintendo. Please contact Ryan (icculus at icculus dot org) for details. diff --git a/libs/SDL3/docs/README-wayland.md b/libs/SDL3/docs/README-wayland.md index a3cd06f7..72080703 100644 --- a/libs/SDL3/docs/README-wayland.md +++ b/libs/SDL3/docs/README-wayland.md @@ -47,8 +47,15 @@ encounter limitations or behavior that is different from other windowing systems ### Warping the mouse cursor to or from a point outside the window doesn't work -- The cursor can be warped only within the window with mouse focus, provided that the `zwp_pointer_confinement_v1` - protocol is supported by the compositor. +- Warping the cursor on Wayland requires that either the `wp_pointer_warp_v1` or `zwp_pointer_confinement_v1` protocol + is supported by the compositor. Compositors typically restrict pointer warps to be within the window that currently + has mouse focus. + +### Minimize/Restored window events are not sent, and the ```SDL_WINDOW_MINIMIZED``` flag is not set. + +- Wayland windows do not currently report the minimized state, aside from when it is activated programmatically via + ```SDL_MinimizeWindow()```. Minimizing a window from the window controls or a desktop shortcut will not send a + minimized event or flag the window as being minimized. ### The application icon can't be set via ```SDL_SetWindowIcon()``` diff --git a/libs/SDL3/docs/hello.c b/libs/SDL3/docs/hello.c index a825ea1c..aff0e97f 100644 --- a/libs/SDL3/docs/hello.c +++ b/libs/SDL3/docs/hello.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/examples/CMakeLists.txt b/libs/SDL3/examples/CMakeLists.txt index 1c5a40e6..19554553 100644 --- a/libs/SDL3/examples/CMakeLists.txt +++ b/libs/SDL3/examples/CMakeLists.txt @@ -16,13 +16,6 @@ else() endif() set(HAVE_EXAMPLES_LINK_SHARED "${SDL_EXAMPLES_LINK_SHARED}" PARENT_SCOPE) -# CMake incorrectly detects opengl32.lib being present on MSVC ARM64 -if(NOT (MSVC AND SDL_CPU_ARM64)) - # Prefer GLVND, if present - set(OpenGL_GL_PREFERENCE GLVND) - find_package(OpenGL) -endif() - set(SDL_EXAMPLE_EXECUTABLES) if(CMAKE_RUNTIME_OUTPUT_DIRECTORY) @@ -38,7 +31,7 @@ if(NOT CMAKE_VERSION VERSION_LESS 3.20) set(example_bin_dir "${example_bin_dir}$<$:/$>") endif() -file(GLOB RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/*.bmp ${CMAKE_CURRENT_SOURCE_DIR}/../test/*.wav ${CMAKE_CURRENT_SOURCE_DIR}/../test/*.hex) +file(GLOB RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/*.png ${CMAKE_CURRENT_SOURCE_DIR}/../test/*.wav ${CMAKE_CURRENT_SOURCE_DIR}/../test/*.hex) set(RESOURCE_FILE_NAMES) set(RESOURCE_FILES_BINDIR) @@ -64,17 +57,12 @@ macro(add_sdl_example_executable TARGET) if(NOT AST_SOURCES) message(FATAL_ERROR "add_sdl_example_executable needs at least one source") endif() - set(EXTRA_SOURCES "") - if(AST_DATAFILES) - list(APPEND EXTRA_SOURCES ${DATAFILES}) - endif() if(ANDROID) - add_library(${TARGET} SHARED ${AST_SOURCES} ${EXTRA_SOURCES}) + add_library(${TARGET} SHARED ${AST_SOURCES} ${AST_DATAFILES}) else() - add_executable(${TARGET} ${AST_SOURCES} ${EXTRA_SOURCES}) + add_executable(${TARGET} ${AST_SOURCES} ${AST_DATAFILES}) endif() SDL_AddCommonCompilerFlags(${TARGET}) - target_include_directories(${TARGET} PRIVATE "${SDL3_SOURCE_DIR}/src/video/khronos") target_link_libraries(${TARGET} PRIVATE SDL3::${sdl_name_component}) list(APPEND SDL_EXAMPLE_EXECUTABLES ${TARGET}) @@ -85,13 +73,9 @@ macro(add_sdl_example_executable TARGET) COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different ${AST_DATAFILES} $/sdl-${TARGET} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" ) - else() + elseif(NOT APPLE AND NOT N3DS) add_dependencies(${TARGET} copy-sdl-example-resources) endif() - if(APPLE) - # Make sure resource files get installed into macOS/iOS .app bundles. - set_target_properties(${TARGET} PROPERTIES RESOURCE "${AST_DATAFILES}") - endif() if(EMSCRIPTEN) foreach(res IN LISTS AST_DATAFILES) get_filename_component(res_name "${res}" NAME) @@ -101,28 +85,45 @@ macro(add_sdl_example_executable TARGET) set_property(TARGET ${TARGET} APPEND PROPERTY ADDITIONAL_CLEAN_FILES "$/$$/>") endif() - if(WINDOWS) + if(APPLE) + # Set Apple App ID / Bundle ID. This is needed to launch apps on some Apple + # platforms (iOS, for example). + set_target_properties(${TARGET} PROPERTIES + RESOURCES "${AST_DATAFILES}" + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_GUI_IDENTIFIER "org.libsdl.${TARGET}" + MACOSX_BUNDLE_BUNDLE_VERSION "${SDL3_VERSION}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${SDL3_VERSION}" + ) + set_property(SOURCE ${AST_DATAFILES} PROPERTY MACOSX_PACKAGE_LOCATION "Resources") + elseif(WINDOWS) # CET support was added in VS 16.7 if(MSVC_VERSION GREATER 1926 AND CMAKE_GENERATOR_PLATFORM MATCHES "Win32|x64") set_property(TARGET ${TARGET} APPEND_STRING PROPERTY LINK_FLAGS " -CETCOMPAT") endif() - elseif(PSP) - target_link_libraries(${TARGET} PRIVATE GL) elseif(EMSCRIPTEN) set_property(TARGET ${TARGET} PROPERTY SUFFIX ".html") target_link_options(${TARGET} PRIVATE -sALLOW_MEMORY_GROWTH=1) + elseif(N3DS) + set(ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/romfs/${TARGET}") + file(MAKE_DIRECTORY "${ROMFS_DIR}") + file(COPY ${AST_DATAFILES} DESTINATION "${ROMFS_DIR}") + ctr_generate_smdh("${TARGET}.smdh" + NAME "SDL-${TARGET}" + DESCRIPTION "SDL3 example application" + AUTHOR "SDL3 Contributors" + ICON "${CMAKE_CURRENT_SOURCE_DIR}/../test/n3ds/logo48x48.png" + ) + ctr_create_3dsx( + ${TARGET} + ROMFS "${ROMFS_DIR}" + SMDH "${TARGET}.smdh" + ) elseif(NGAGE) string(MD5 TARGET_MD5 "${TARGET}") string(SUBSTRING "${TARGET_MD5}" 0 8 TARGET_MD5_8) target_link_options(${TARGET} PRIVATE "SHELL:-s UID3=0x${TARGET_MD5_8}") endif() - - if(OPENGL_FOUND) - target_compile_definitions(${TARGET} PRIVATE HAVE_OPENGL) - endif() - - # FIXME: only add "${SDL3_BINARY_DIR}/include-config-$>" + include paths of external dependencies - target_include_directories(${TARGET} PRIVATE "$") endmacro() add_sdl_example_executable(renderer-clear SOURCES renderer/01-clear/clear.c) @@ -130,17 +131,17 @@ add_sdl_example_executable(renderer-primitives SOURCES renderer/02-primitives/pr add_sdl_example_executable(renderer-lines SOURCES renderer/03-lines/lines.c) add_sdl_example_executable(renderer-points SOURCES renderer/04-points/points.c) add_sdl_example_executable(renderer-rectangles SOURCES renderer/05-rectangles/rectangles.c) -add_sdl_example_executable(renderer-textures SOURCES renderer/06-textures/textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) +add_sdl_example_executable(renderer-textures SOURCES renderer/06-textures/textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) add_sdl_example_executable(renderer-streaming-textures SOURCES renderer/07-streaming-textures/streaming-textures.c) -add_sdl_example_executable(renderer-rotating-textures SOURCES renderer/08-rotating-textures/rotating-textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) -add_sdl_example_executable(renderer-scaling-textures SOURCES renderer/09-scaling-textures/scaling-textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) -add_sdl_example_executable(renderer-geometry SOURCES renderer/10-geometry/geometry.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) -add_sdl_example_executable(renderer-color-mods SOURCES renderer/11-color-mods/color-mods.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) -add_sdl_example_executable(renderer-viewport SOURCES renderer/14-viewport/viewport.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) -add_sdl_example_executable(renderer-cliprect SOURCES renderer/15-cliprect/cliprect.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) -add_sdl_example_executable(renderer-read-pixels SOURCES renderer/17-read-pixels/read-pixels.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) +add_sdl_example_executable(renderer-rotating-textures SOURCES renderer/08-rotating-textures/rotating-textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) +add_sdl_example_executable(renderer-scaling-textures SOURCES renderer/09-scaling-textures/scaling-textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) +add_sdl_example_executable(renderer-geometry SOURCES renderer/10-geometry/geometry.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) +add_sdl_example_executable(renderer-color-mods SOURCES renderer/11-color-mods/color-mods.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) +add_sdl_example_executable(renderer-viewport SOURCES renderer/14-viewport/viewport.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) +add_sdl_example_executable(renderer-cliprect SOURCES renderer/15-cliprect/cliprect.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) +add_sdl_example_executable(renderer-read-pixels SOURCES renderer/17-read-pixels/read-pixels.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) add_sdl_example_executable(renderer-debug-text SOURCES renderer/18-debug-text/debug-text.c) -add_sdl_example_executable(renderer-affine-textures SOURCES renderer/19-affine-textures/affine-textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) +add_sdl_example_executable(renderer-affine-textures SOURCES renderer/19-affine-textures/affine-textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png) add_sdl_example_executable(audio-simple-playback SOURCES audio/01-simple-playback/simple-playback.c) add_sdl_example_executable(audio-simple-playback-callback SOURCES audio/02-simple-playback-callback/simple-playback-callback.c) add_sdl_example_executable(audio-load-wav SOURCES audio/03-load-wav/load-wav.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.wav) @@ -148,9 +149,13 @@ add_sdl_example_executable(audio-multiple-streams SOURCES audio/04-multiple-stre add_sdl_example_executable(audio-planar-data SOURCES audio/05-planar-data/planar-data.c) add_sdl_example_executable(input-joystick-polling SOURCES input/01-joystick-polling/joystick-polling.c) add_sdl_example_executable(input-joystick-events SOURCES input/02-joystick-events/joystick-events.c) +add_sdl_example_executable(input-gamepad-polling SOURCES input/03-gamepad-polling/gamepad-polling.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/gamepad_front.png) +add_sdl_example_executable(input-gamepad-events SOURCES input/04-gamepad-events/gamepad-events.c) add_sdl_example_executable(camera-read-and-draw SOURCES camera/01-read-and-draw/read-and-draw.c) add_sdl_example_executable(pen-drawing-lines SOURCES pen/01-drawing-lines/drawing-lines.c) -add_sdl_example_executable(asyncio-load-bitmaps SOURCES asyncio/01-load-bitmaps/load-bitmaps.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp ${CMAKE_CURRENT_SOURCE_DIR}/../test/gamepad_front.bmp ${CMAKE_CURRENT_SOURCE_DIR}/../test/speaker.bmp ${CMAKE_CURRENT_SOURCE_DIR}/../test/icon2x.bmp) +add_sdl_example_executable(asyncio-load-bitmaps SOURCES asyncio/01-load-bitmaps/load-bitmaps.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.png ${CMAKE_CURRENT_SOURCE_DIR}/../test/gamepad_front.png ${CMAKE_CURRENT_SOURCE_DIR}/../test/speaker.png ${CMAKE_CURRENT_SOURCE_DIR}/../test/icon2x.png) +add_sdl_example_executable(misc-power SOURCES misc/01-power/power.c) +add_sdl_example_executable(misc-clipboard SOURCES misc/02-clipboard/clipboard.c) add_sdl_example_executable(demo-snake SOURCES demo/01-snake/snake.c) add_sdl_example_executable(demo-woodeneye-008 SOURCES demo/02-woodeneye-008/woodeneye-008.c) add_sdl_example_executable(demo-infinite-monkeys SOURCES demo/03-infinite-monkeys/infinite-monkeys.c) @@ -160,7 +165,7 @@ add_sdl_example_executable(demo-bytepusher SOURCES demo/04-bytepusher/bytepusher # - Add a new example in examples/ # - Run python VisualC/examples/generate.py # - Take note of the newly generated .vcxproj files -# - Modify the .vcxproj files if necessary (adding content such as BMP or WAV files) +# - Modify the .vcxproj files if necessary (adding content such as PNG or WAV files) # - Open VisualC/SDL.sln in Visual Studio or JetBrains Rider # - Locate the appropriate folder in the Solution Explorer # - Add the newly generated projects: Right click -> Add -> Existing project... @@ -181,28 +186,6 @@ if(PSP) endforeach() endif() -if(N3DS) - set(ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/romfs") - file(MAKE_DIRECTORY "${ROMFS_DIR}") - file(COPY ${RESOURCE_FILES} DESTINATION "${ROMFS_DIR}") - - foreach(APP ${SDL_EXAMPLE_EXECUTABLES}) - get_target_property(TARGET_BINARY_DIR ${APP} BINARY_DIR) - set(SMDH_FILE "${TARGET_BINARY_DIR}/${APP}.smdh") - ctr_generate_smdh("${SMDH_FILE}" - NAME "SDL-${APP}" - DESCRIPTION "SDL3 Test suite" - AUTHOR "SDL3 Contributors" - ICON "${CMAKE_CURRENT_SOURCE_DIR}/../test/n3ds/logo48x48.png" - ) - ctr_create_3dsx( - ${APP} - ROMFS "${ROMFS_DIR}" - SMDH "${SMDH_FILE}" - ) - endforeach() -endif() - if(RISCOS) set(SDL_EXAMPLE_EXECUTABLES_AIF) foreach(APP ${SDL_EXAMPLE_EXECUTABLES}) @@ -217,18 +200,6 @@ if(RISCOS) endforeach() endif() -# Set Apple App ID / Bundle ID. This is needed to launch apps on some Apple -# platforms (iOS, for example). -if(APPLE) - foreach(CURRENT_TARGET ${SDL_EXAMPLE_EXECUTABLES}) - set_target_properties("${CURRENT_TARGET}" PROPERTIES - MACOSX_BUNDLE_GUI_IDENTIFIER "org.libsdl.${CURRENT_TARGET}" - MACOSX_BUNDLE_BUNDLE_VERSION "${SDL3_VERSION}" - MACOSX_BUNDLE_SHORT_VERSION_STRING "${SDL3_VERSION}" - ) - endforeach() -endif() - if(SDL_INSTALL_EXAMPLES) if(RISCOS) install( diff --git a/libs/SDL3/examples/README.md b/libs/SDL3/examples/README.md index 872e0870..261e485a 100644 --- a/libs/SDL3/examples/README.md +++ b/libs/SDL3/examples/README.md @@ -65,3 +65,30 @@ If writing new examples, this is the skeleton code we start from, to keep everything consistent. You can ignore it. +## How are the thumbnails/onmouseover media created? + +(Since I have to figure this out every time.) + +This is how Ryan is doing it currently. + +- `rm -f frame*.png` +- Temporarily add `#include "../../save-rendering-to-bitmaps.h"` after any SDL + includes in the example program. +- Launch the example app, interact with it, let it run for a few seconds, quit. +- This will dump a "frameX.png" file for each frame rendered. +- Make a video in webp format from the bitmaps (this assumes the bitmaps were + stored at 60fps, you might have to tweak). + + ```bash + ffmpeg -framerate 60 -pattern_type glob -i 'frame*.png' -loop 0 -quality 40 -r 10 -frames:v 40 onmouseover.webp + ``` + + You might need to start in the middle of the video, or mess with quality or + number of frames to generate, ymmv. +- Pick a frame for the thumbnail, make it a .png, and run that png through + pngquant for massive file size reduction without any obvious loss in quality: + + ```bash + convert frame00000.png cvt.png ; pngquant cvt.png --output thumbnail.png ; rm -f cvt.png + ``` + diff --git a/libs/SDL3/examples/asyncio/01-load-bitmaps/load-bitmaps.c b/libs/SDL3/examples/asyncio/01-load-bitmaps/load-bitmaps.c index 44c2abf7..aebb96eb 100644 --- a/libs/SDL3/examples/asyncio/01-load-bitmaps/load-bitmaps.c +++ b/libs/SDL3/examples/asyncio/01-load-bitmaps/load-bitmaps.c @@ -14,7 +14,7 @@ static SDL_Renderer *renderer = NULL; static SDL_AsyncIOQueue *queue = NULL; #define TOTAL_TEXTURES 4 -static const char * const bmps[TOTAL_TEXTURES] = { "sample.bmp", "gamepad_front.bmp", "speaker.bmp", "icon2x.bmp" }; +static const char * const pngs[TOTAL_TEXTURES] = { "sample.png", "gamepad_front.png", "speaker.png", "icon2x.png" }; static SDL_Texture *textures[TOTAL_TEXTURES]; static const SDL_FRect texture_rects[TOTAL_TEXTURES] = { { 116, 156, 408, 167 }, @@ -33,10 +33,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/asyncio/load-bitmaps", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/asyncio/load-bitmaps", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create window/renderer!", SDL_GetError(), NULL); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); queue = SDL_CreateAsyncIOQueue(); if (!queue) { @@ -44,12 +45,12 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - /* Load some .bmp files asynchronously from wherever the app is being run from, put them in the same queue. */ - for (i = 0; i < SDL_arraysize(bmps); i++) { + /* Load some .png files asynchronously from wherever the app is being run from, put them in the same queue. */ + for (i = 0; i < SDL_arraysize(pngs); i++) { char *path = NULL; - SDL_asprintf(&path, "%s%s", SDL_GetBasePath(), bmps[i]); /* allocate a string of the full file path */ + SDL_asprintf(&path, "%s%s", SDL_GetBasePath(), pngs[i]); /* allocate a string of the full file path */ /* you _should) check for failure, but we'll just go on without files here. */ - SDL_LoadFileAsync(path, queue, (void *) bmps[i]); /* attach the filename as app-specific data, so we can see it later. */ + SDL_LoadFileAsync(path, queue, (void *) pngs[i]); /* attach the filename as app-specific data, so we can see it later. */ SDL_free(path); } @@ -72,18 +73,18 @@ SDL_AppResult SDL_AppIterate(void *appstate) SDL_AsyncIOOutcome outcome; int i; - if (SDL_GetAsyncIOResult(queue, &outcome)) { /* a .bmp file load has finished? */ + if (SDL_GetAsyncIOResult(queue, &outcome)) { /* a .png file load has finished? */ if (outcome.result == SDL_ASYNCIO_COMPLETE) { - /* this might be _any_ of the bmps; they might finish loading in any order. */ - for (i = 0; i < SDL_arraysize(bmps); i++) { + /* this might be _any_ of the pngs; they might finish loading in any order. */ + for (i = 0; i < SDL_arraysize(pngs); i++) { /* this doesn't need a strcmp because we gave the pointer from this array to SDL_LoadFileAsync */ - if (outcome.userdata == bmps[i]) { + if (outcome.userdata == pngs[i]) { break; } } - if (i < SDL_arraysize(bmps)) { /* (just in case.) */ - SDL_Surface *surface = SDL_LoadBMP_IO(SDL_IOFromConstMem(outcome.buffer, (size_t) outcome.bytes_transferred), true); + if (i < SDL_arraysize(pngs)) { /* (just in case.) */ + SDL_Surface *surface = SDL_LoadPNG_IO(SDL_IOFromConstMem(outcome.buffer, (size_t) outcome.bytes_transferred), true); if (surface) { /* the renderer is not multithreaded, so create the texture here once the data loads. */ textures[i] = SDL_CreateTextureFromSurface(renderer, surface); if (!textures[i]) { diff --git a/libs/SDL3/examples/audio/01-simple-playback/simple-playback.c b/libs/SDL3/examples/audio/01-simple-playback/simple-playback.c index 15126e56..69b78918 100644 --- a/libs/SDL3/examples/audio/01-simple-playback/simple-playback.c +++ b/libs/SDL3/examples/audio/01-simple-playback/simple-playback.c @@ -29,10 +29,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) } /* we don't _need_ a window for audio-only things but it's good policy to have one. */ - if (!SDL_CreateWindowAndRenderer("examples/audio/simple-playback", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/audio/simple-playback", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* We're just playing a single thing here, so we'll use the simplified option. We are always going to feed audio in as mono, float32 data at 8000Hz. diff --git a/libs/SDL3/examples/audio/02-simple-playback-callback/simple-playback-callback.c b/libs/SDL3/examples/audio/02-simple-playback-callback/simple-playback-callback.c index c011c727..186ae241 100644 --- a/libs/SDL3/examples/audio/02-simple-playback-callback/simple-playback-callback.c +++ b/libs/SDL3/examples/audio/02-simple-playback-callback/simple-playback-callback.c @@ -63,10 +63,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) } /* we don't _need_ a window for audio-only things but it's good policy to have one. */ - if (!SDL_CreateWindowAndRenderer("examples/audio/simple-playback-callback", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/audio/simple-playback-callback", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* We're just playing a single thing here, so we'll use the simplified option. We are always going to feed audio in as mono, float32 data at 8000Hz. diff --git a/libs/SDL3/examples/audio/03-load-wav/load-wav.c b/libs/SDL3/examples/audio/03-load-wav/load-wav.c index c517e5d6..a8de0715 100644 --- a/libs/SDL3/examples/audio/03-load-wav/load-wav.c +++ b/libs/SDL3/examples/audio/03-load-wav/load-wav.c @@ -39,10 +39,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) } /* we don't _need_ a window for audio-only things but it's good policy to have one. */ - if (!SDL_CreateWindowAndRenderer("examples/audio/load-wav", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/audio/load-wav", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Load the .wav file from wherever the app is being run from. */ SDL_asprintf(&wav_path, "%ssample.wav", SDL_GetBasePath()); /* allocate a string of the full file path */ diff --git a/libs/SDL3/examples/audio/04-multiple-streams/multiple-streams.c b/libs/SDL3/examples/audio/04-multiple-streams/multiple-streams.c index 188023c8..a10ae09b 100644 --- a/libs/SDL3/examples/audio/04-multiple-streams/multiple-streams.c +++ b/libs/SDL3/examples/audio/04-multiple-streams/multiple-streams.c @@ -65,10 +65,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/audio/multiple-streams", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/audio/multiple-streams", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* open the default audio device in whatever format it prefers; our audio streams will adjust to it. */ audio_device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL); diff --git a/libs/SDL3/examples/audio/05-planar-data/planar-data.c b/libs/SDL3/examples/audio/05-planar-data/planar-data.c index f06ef696..7fe291d0 100644 --- a/libs/SDL3/examples/audio/05-planar-data/planar-data.c +++ b/libs/SDL3/examples/audio/05-planar-data/planar-data.c @@ -39,10 +39,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/audio/planar-data", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/audio/planar-data", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL, NULL); if (!stream) { @@ -58,6 +59,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) /* This function runs when a new event (mouse input, keypresses, etc) occurs. */ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { + SDL_ConvertEventToRenderCoordinates(renderer, event); if (event->type == SDL_EVENT_QUIT) { return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ } else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) { diff --git a/libs/SDL3/examples/camera/01-read-and-draw/read-and-draw.c b/libs/SDL3/examples/camera/01-read-and-draw/read-and-draw.c index 989fd543..b3649f4b 100644 --- a/libs/SDL3/examples/camera/01-read-and-draw/read-and-draw.c +++ b/libs/SDL3/examples/camera/01-read-and-draw/read-and-draw.c @@ -33,7 +33,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/camera/read-and-draw", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/camera/read-and-draw", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } @@ -44,6 +44,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } else if (devcount == 0) { SDL_Log("Couldn't find any camera devices! Please connect a camera and try again."); + SDL_free(devices); return SDL_APP_FAILURE; } @@ -83,6 +84,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) the window when we get a first frame from the camera. */ if (!texture) { SDL_SetWindowSize(window, frame->w, frame->h); /* Resize the window to match */ + SDL_SetRenderLogicalPresentation(renderer, frame->w, frame->h, SDL_LOGICAL_PRESENTATION_LETTERBOX); texture = SDL_CreateTexture(renderer, frame->format, SDL_TEXTUREACCESS_STREAMING, frame->w, frame->h); } diff --git a/libs/SDL3/examples/categories.txt b/libs/SDL3/examples/categories.txt index 8a9b5ecb..bbe30f23 100644 --- a/libs/SDL3/examples/categories.txt +++ b/libs/SDL3/examples/categories.txt @@ -10,4 +10,5 @@ audio camera asyncio pen +misc demo diff --git a/libs/SDL3/examples/demo/01-snake/snake.c b/libs/SDL3/examples/demo/01-snake/snake.c index d740f2c6..5c63251d 100644 --- a/libs/SDL3/examples/demo/01-snake/snake.c +++ b/libs/SDL3/examples/demo/01-snake/snake.c @@ -18,7 +18,8 @@ #define SNAKE_GAME_HEIGHT 18U #define SNAKE_MATRIX_SIZE (SNAKE_GAME_WIDTH * SNAKE_GAME_HEIGHT) -#define THREE_BITS 0x7U /* ~CHAR_MAX >> (CHAR_BIT - SNAKE_CELL_MAX_BITS) */ +#define SNAKE_CELL_MAX_BITS 3U /* floor(log2(SNAKE_CELL_FOOD)) + 1 */ +#define SNAKE_CELL_SET_BITS (~(~0u << SNAKE_CELL_MAX_BITS)) #define SHIFT(x, y) (((x) + ((y) * SNAKE_GAME_WIDTH)) * SNAKE_CELL_MAX_BITS) static SDL_Joystick *joystick = NULL; @@ -33,8 +34,6 @@ typedef enum SNAKE_CELL_FOOD = 5U } SnakeCell; -#define SNAKE_CELL_MAX_BITS 3U /* floor(log2(SNAKE_CELL_FOOD)) + 1 */ - typedef enum { SNAKE_DIR_RIGHT, @@ -68,7 +67,7 @@ SnakeCell snake_cell_at(const SnakeContext *ctx, char x, char y) const int shift = SHIFT(x, y); unsigned short range; SDL_memcpy(&range, ctx->cells + (shift / 8), sizeof(range)); - return (SnakeCell)((range >> (shift % 8)) & THREE_BITS); + return (SnakeCell)((range >> (shift % 8)) & SNAKE_CELL_SET_BITS); } static void set_rect_xy_(SDL_FRect *r, short x, short y) @@ -84,8 +83,8 @@ static void put_cell_at_(SnakeContext *ctx, char x, char y, SnakeCell ct) unsigned char *const pos = ctx->cells + (shift / 8); unsigned short range; SDL_memcpy(&range, pos, sizeof(range)); - range &= ~(THREE_BITS << adjust); /* clear bits */ - range |= (ct & THREE_BITS) << adjust; + range &= ~(SNAKE_CELL_SET_BITS << adjust); /* clear bits */ + range |= (ct & SNAKE_CELL_SET_BITS) << adjust; SDL_memcpy(pos, &range, sizeof(range)); } @@ -341,9 +340,10 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) *appstate = as; - if (!SDL_CreateWindowAndRenderer("examples/demo/snake", SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT, 0, &as->window, &as->renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/demo/snake", SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &as->window, &as->renderer)) { return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(as->renderer, SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); snake_initialize(&as->snake_ctx); diff --git a/libs/SDL3/examples/demo/02-woodeneye-008/woodeneye-008.c b/libs/SDL3/examples/demo/02-woodeneye-008/woodeneye-008.c index b97b06ad..18431e78 100644 --- a/libs/SDL3/examples/demo/02-woodeneye-008/woodeneye-008.c +++ b/libs/SDL3/examples/demo/02-woodeneye-008/woodeneye-008.c @@ -206,7 +206,7 @@ static void draw(SDL_Renderer *renderer, const float (*edges)[6], const Player p for (i = 0; i < players_len; i++) { const Player *player = &players[i]; float mod_x = (float)(i % part_hor); - float mod_y = (float)(i / part_hor); + float mod_y = (float)i / part_hor; float hor_origin = (mod_x + 0.5f) * size_hor; float ver_origin = (mod_y + 0.5f) * size_ver; float cam_origin = (float)(0.5 * SDL_sqrt(size_hor * size_hor + size_ver * size_ver)); @@ -265,7 +265,7 @@ static void draw(SDL_Renderer *renderer, const float (*edges)[6], const Player p SDL_RenderLine(renderer, hor_origin-10, ver_origin, hor_origin+10, ver_origin); } } - SDL_SetRenderClipRect(renderer, 0); + SDL_SetRenderClipRect(renderer, NULL); SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_RenderDebugText(renderer, 0, 0, debug_string); SDL_RenderPresent(renderer); @@ -347,7 +347,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) if (!SDL_Init(SDL_INIT_VIDEO)) { return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/demo/woodeneye-008", 640, 480, 0, &as->window, &as->renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/demo/woodeneye-008", 640, 480, SDL_WINDOW_RESIZABLE, &as->window, &as->renderer)) { return SDL_APP_FAILURE; } @@ -477,4 +477,4 @@ SDL_AppResult SDL_AppIterate(void *appstate) void SDL_AppQuit(void *appstate, SDL_AppResult result) { SDL_free(appstate); // just free the memory, SDL will clean up the window/renderer for us. -} \ No newline at end of file +} diff --git a/libs/SDL3/examples/demo/04-bytepusher/bytepusher.c b/libs/SDL3/examples/demo/04-bytepusher/bytepusher.c index c7e3e059..25525735 100644 --- a/libs/SDL3/examples/demo/04-bytepusher/bytepusher.c +++ b/libs/SDL3/examples/demo/04-bytepusher/bytepusher.c @@ -31,9 +31,8 @@ typedef struct { Uint64 tick_acc; SDL_Window* window; SDL_Renderer* renderer; - SDL_Surface* screen; - SDL_Texture* screentex; - SDL_Texture* rendertarget; /* we need this render target for text to look good */ + SDL_Palette* palette; + SDL_Texture* texture; SDL_AudioStream* audiostream; char status[SCREEN_W / 8]; int status_ticks; @@ -75,6 +74,8 @@ static bool load(BytePusher* vm, SDL_IOStream* stream, bool closeio) { size_t bytes_read = 0; bool ok = true; + vm->display_help = true; // will set to false if load succeeds. + SDL_memset(vm->ram, 0, RAM_SIZE); if (!stream) { @@ -130,16 +131,12 @@ static void print(BytePusher* vm, int x, int y, const char* str) { SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { BytePusher* vm; - SDL_Palette* palette; SDL_Rect usable_bounds; SDL_AudioSpec audiospec = { SDL_AUDIO_S8, 1, SAMPLES_PER_FRAME * FRAMES_PER_SECOND }; SDL_DisplayID primary_display; - SDL_PropertiesID texprops; int zoom = 2; int i; Uint8 r, g, b; - (void)argc; - (void)argv; if (!SDL_SetAppMetadata("SDL 3 BytePusher", "1.0", "com.example.SDL3BytePusher")) { return SDL_APP_FAILURE; @@ -185,13 +182,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { return SDL_APP_FAILURE; } - if (!(vm->screen = SDL_CreateSurfaceFrom( - SCREEN_W, SCREEN_H, SDL_PIXELFORMAT_INDEX8, vm->ram, SCREEN_W - ))) { - return SDL_APP_FAILURE; - } - - if (!(palette = SDL_CreateSurfacePalette(vm->screen))) { + if (!(vm->palette = SDL_CreatePalette(256))) { return SDL_APP_FAILURE; } i = 0; @@ -199,28 +190,21 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { for (g = 0; g < 6; ++g) { for (b = 0; b < 6; ++b, ++i) { SDL_Color color = { (Uint8)(r * 0x33), (Uint8)(g * 0x33), (Uint8)(b * 0x33), SDL_ALPHA_OPAQUE }; - palette->colors[i] = color; + vm->palette->colors[i] = color; } } } for (; i < 256; ++i) { SDL_Color color = { 0, 0, 0, SDL_ALPHA_OPAQUE }; - palette->colors[i] = color; + vm->palette->colors[i] = color; } - texprops = SDL_CreateProperties(); - SDL_SetNumberProperty(texprops, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING); - SDL_SetNumberProperty(texprops, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, SCREEN_W); - SDL_SetNumberProperty(texprops, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, SCREEN_H); - vm->screentex = SDL_CreateTextureWithProperties(vm->renderer, texprops); - SDL_SetNumberProperty(texprops, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_TARGET); - vm->rendertarget = SDL_CreateTextureWithProperties(vm->renderer, texprops); - SDL_DestroyProperties(texprops); - if (!vm->screentex || !vm->rendertarget) { + vm->texture = SDL_CreateTexture(vm->renderer, SDL_PIXELFORMAT_INDEX8, SDL_TEXTUREACCESS_STREAMING, SCREEN_W, SCREEN_H); + if (!vm->texture) { return SDL_APP_FAILURE; } - SDL_SetTextureScaleMode(vm->screentex, SDL_SCALEMODE_NEAREST); - SDL_SetTextureScaleMode(vm->rendertarget, SDL_SCALEMODE_NEAREST); + SDL_SetTexturePalette(vm->texture, vm->palette); + SDL_SetTextureScaleMode(vm->texture, SDL_SCALEMODE_NEAREST); if (!(vm->audiostream = SDL_OpenAudioDeviceStream( SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audiospec, NULL, NULL @@ -235,6 +219,10 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { vm->last_tick = SDL_GetTicksNS(); vm->tick_acc = NS_PER_SECOND; + if (argc > 1) { + load_file(vm, argv[1]); + } + return SDL_APP_CONTINUE; } @@ -283,35 +271,28 @@ SDL_AppResult SDL_AppIterate(void* appstate) { } if (updated) { - SDL_Surface *tex; - - SDL_SetRenderTarget(vm->renderer, vm->rendertarget); - - if (!SDL_LockTextureToSurface(vm->screentex, NULL, &tex)) { - return SDL_APP_FAILURE; - } - vm->screen->pixels = &vm->ram[(Uint32)vm->ram[IO_SCREEN_PAGE] << 16]; - SDL_BlitSurface(vm->screen, NULL, tex, NULL); - SDL_UnlockTexture(vm->screentex); - - SDL_RenderTexture(vm->renderer, vm->screentex, NULL, NULL); - - if (vm->display_help) { - print(vm, 4, 4, "Drop a BytePusher file in this"); - print(vm, 8, 12, "window to load and run it!"); - print(vm, 4, 28, "Press ENTER to switch between"); - print(vm, 8, 36, "positional and symbolic input."); - } - - if (vm->status_ticks > 0) { - vm->status_ticks -= 1; - print(vm, 4, SCREEN_H - 12, vm->status); - } + const void *pixels = &vm->ram[(Uint32)vm->ram[IO_SCREEN_PAGE] << 16]; + SDL_UpdateTexture(vm->texture, NULL, pixels, SCREEN_W); } - SDL_SetRenderTarget(vm->renderer, NULL); SDL_RenderClear(vm->renderer); - SDL_RenderTexture(vm->renderer, vm->rendertarget, 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."); + } else { + SDL_RenderTexture(vm->renderer, vm->texture, NULL, NULL); + } + + if (vm->status_ticks > 0) { + if (updated) { + vm->status_ticks--; + } + print(vm, 4, SCREEN_H - 12, vm->status); + } + SDL_RenderPresent(vm->renderer); return SDL_APP_CONTINUE; @@ -405,9 +386,8 @@ void SDL_AppQuit(void* appstate, SDL_AppResult result) { if (appstate) { BytePusher* vm = (BytePusher*)appstate; SDL_DestroyAudioStream(vm->audiostream); - SDL_DestroyTexture(vm->rendertarget); - SDL_DestroyTexture(vm->screentex); - SDL_DestroySurface(vm->screen); + SDL_DestroyTexture(vm->texture); + SDL_DestroyPalette(vm->palette); SDL_DestroyRenderer(vm->renderer); SDL_DestroyWindow(vm->window); SDL_free(vm); diff --git a/libs/SDL3/examples/input/01-joystick-polling/joystick-polling.c b/libs/SDL3/examples/input/01-joystick-polling/joystick-polling.c index 647cdef9..2eb8466e 100644 --- a/libs/SDL3/examples/input/01-joystick-polling/joystick-polling.c +++ b/libs/SDL3/examples/input/01-joystick-polling/joystick-polling.c @@ -13,8 +13,8 @@ and knows how to map arbitrary buttons and such to look like an Xbox/PlayStation/etc gamepad. This is easier, and better, for many games, but isn't necessarily a good fit for complex apps and hardware. A flight - simulator, a realistic racing game, etc, might want this interface instead - of gamepads. */ + simulator, a realistic racing game, etc, might want the joystick interface + instead of gamepads. */ /* SDL can handle multiple joysticks, but for simplicity, this program only deals with the first stick it sees. */ diff --git a/libs/SDL3/examples/input/02-joystick-events/joystick-events.c b/libs/SDL3/examples/input/02-joystick-events/joystick-events.c index cc01d84a..2dbe0581 100644 --- a/libs/SDL3/examples/input/02-joystick-events/joystick-events.c +++ b/libs/SDL3/examples/input/02-joystick-events/joystick-events.c @@ -13,8 +13,8 @@ and knows how to map arbitrary buttons and such to look like an Xbox/PlayStation/etc gamepad. This is easier, and better, for many games, but isn't necessarily a good fit for complex apps and hardware. A flight - simulator, a realistic racing game, etc, might want this interface instead - of gamepads. */ + simulator, a realistic racing game, etc, might want the joystick interface + instead of gamepads. */ #define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ #include @@ -111,7 +111,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/input/joystick-events", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/input/joystick-events", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } diff --git a/libs/SDL3/examples/input/02-joystick-events/onmouseover.webp b/libs/SDL3/examples/input/02-joystick-events/onmouseover.webp index 05a9b426..08f40356 100644 Binary files a/libs/SDL3/examples/input/02-joystick-events/onmouseover.webp and b/libs/SDL3/examples/input/02-joystick-events/onmouseover.webp differ diff --git a/libs/SDL3/examples/input/03-gamepad-polling/README.txt b/libs/SDL3/examples/input/03-gamepad-polling/README.txt new file mode 100644 index 00000000..d7e68b94 --- /dev/null +++ b/libs/SDL3/examples/input/03-gamepad-polling/README.txt @@ -0,0 +1,11 @@ +This example code looks for the current gamepad state once per frame, +and draws a visual representation of it. See 01-joystick-polling for the +equivalent example code for the lower-level joystick API. + +Please note that on the web, gamepads don't show up until you interact with +them, so press a button to "connect" the controller. + +Also note that on the web, gamepad triggers are treated as buttons (either +pressed or not) instead of axes (pressed 0 to 100 percent). This is a web +issue, not an SDL limitation. + diff --git a/libs/SDL3/examples/input/03-gamepad-polling/gamepad-polling.c b/libs/SDL3/examples/input/03-gamepad-polling/gamepad-polling.c new file mode 100644 index 00000000..a1d22585 --- /dev/null +++ b/libs/SDL3/examples/input/03-gamepad-polling/gamepad-polling.c @@ -0,0 +1,221 @@ +/* + * This example code looks for the current gamepad state once per frame, + * and draws a visual representation of it. See 01-joystick-polling for the + * equivalent example code for the lower-level joystick API. + * + * This code is public domain. Feel free to use it for any purpose! + */ + +/* Joysticks are low-level interfaces: there's something with a bunch of + buttons, axes and hats, in no understood order or position. This is + a flexible interface, but you'll need to build some sort of configuration + UI to let people tell you what button, etc, does what. On top of this + interface, SDL offers the "gamepad" API, which works with lots of devices, + and knows how to map arbitrary buttons and such to look like an + Xbox/PlayStation/etc gamepad. This is easier, and better, for many games, + but isn't necessarily a good fit for complex apps and hardware. A flight + simulator, a realistic racing game, etc, might want the joystick interface + instead of gamepads. */ + +/* SDL can handle multiple gamepads, but for simplicity, this program only + deals with the first gamepad it sees. */ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static SDL_Texture *texture = NULL; +static SDL_Gamepad *gamepad = NULL; + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + char *png_path = NULL; + SDL_Surface *surface = NULL; + + SDL_SetAppMetadata("Example Input Gamepad Polling", "1.0", "com.example.input-gamepad-polling"); + + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_CreateWindowAndRenderer("examples/input/gamepad-polling", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_STRETCH)) { + return SDL_APP_FAILURE; + } + + /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D + engines refer to these as "sprites." We'll do a static texture (upload once, draw many + times) with data from a bitmap file. */ + + /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%sgamepad_front.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); + if (!surface) { + SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + SDL_free(png_path); /* done with this, the file is loaded. */ + + texture = SDL_CreateTextureFromSurface(renderer, surface); + if (!texture) { + SDL_Log("Couldn't create static texture: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + SDL_DestroySurface(surface); /* done with this, the texture has a copy of the pixels now. */ + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } else if (event->type == SDL_EVENT_GAMEPAD_ADDED) { + /* this event is sent for each hotplugged gamepad, but also each already-connected gamepad during SDL_Init(). */ + if (gamepad == NULL) { /* we don't have a stick yet and one was added, open it! */ + gamepad = SDL_OpenGamepad(event->gdevice.which); + if (!gamepad) { + SDL_Log("Failed to open gamepad ID %u: %s", (unsigned int) event->gdevice.which, SDL_GetError()); + } + } + } else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) { + if (gamepad && (SDL_GetGamepadID(gamepad) == event->gdevice.which)) { + SDL_CloseGamepad(gamepad); /* our controller was unplugged. */ + gamepad = NULL; + } + } + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + const char *text = "Plug in a gamepad, please."; + static Uint64 leftthumblast = 0xFFFFFFFF; + static Uint64 rightthumblast = 0xFFFFFFFF; + const Uint64 now = SDL_GetTicks(); + Sint16 axis_x, axis_y; + float x, y; + int i; + + if (gamepad) { /* we have a stick opened? */ + text = SDL_GetGamepadName(gamepad); + } + + SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); /* white */ + SDL_RenderClear(renderer); + + /* note that you can get input as events, instead of polling, which is + better since it won't miss button presses if the system is lagging, + but often times checking the current state per-frame is good enough, + and maybe better if you'd rather _drop_ inputs due to lag. */ + + if (gamepad) { /* we have a stick opened? */ + /* where to draw the buttons */ + const SDL_FRect buttons[] = { + { 497, 266, 38, 38 }, /* SDL_GAMEPAD_BUTTON_SOUTH */ + { 550, 217, 38, 38 }, /* SDL_GAMEPAD_BUTTON_EAST */ + { 445, 221, 38, 38 }, /* SDL_GAMEPAD_BUTTON_WEST */ + { 499, 173, 38, 38 }, /* SDL_GAMEPAD_BUTTON_NORTH */ + { 235, 228, 32, 29 }, /* SDL_GAMEPAD_BUTTON_BACK */ + { 287, 195, 69, 69 }, /* SDL_GAMEPAD_BUTTON_GUIDE */ + { 377, 228, 32, 29 }, /* SDL_GAMEPAD_BUTTON_START */ + { 91, 234, 63, 63 }, /* SDL_GAMEPAD_BUTTON_LEFT_STICK */ + { 381, 354, 63, 63 }, /* SDL_GAMEPAD_BUTTON_RIGHT_STICK */ + { 74, 73, 102, 29 }, /* SDL_GAMEPAD_BUTTON_LEFT_SHOULDER */ + { 468, 73, 102, 29 }, /* SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER */ + { 207, 316, 32, 32 }, /* SDL_GAMEPAD_BUTTON_DPAD_UP */ + { 207, 384, 32, 32 }, /* SDL_GAMEPAD_BUTTON_DPAD_DOWN */ + { 173, 351, 32, 32 }, /* SDL_GAMEPAD_BUTTON_DPAD_LEFT */ + { 242, 351, 32, 32 }, /* SDL_GAMEPAD_BUTTON_DPAD_RIGHT */ + { 310, 286, 23, 27 }, /* SDL_GAMEPAD_BUTTON_MISC1 */ + /* there are other buttons: paddles on the back of the gamepad, touchpads, etc, but this is good enough for now. */ + }; + + SDL_RenderTexture(renderer, texture, NULL, NULL); /* draw the gamepad picture to the whole window. */ + + /* draw green boxes over buttons that are currently pressed. */ + SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0xFF); /* green */ + for (i = 0; i < SDL_arraysize(buttons); i++) { + if (SDL_GetGamepadButton(gamepad, (SDL_GamepadButton) i)) { + SDL_RenderFillRect(renderer, &buttons[i]); + } + } + + SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0x00, 0xFF); /* yellow */ + + /* left thumb axis. */ + axis_x = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX); + axis_y = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY); + if ((SDL_abs(axis_x) > 1000) || (SDL_abs(axis_y) > 1000)) { /* zero means centered, but it might be a little off zero... */ + leftthumblast = now; /* keep drawing, we're still moving. */ + } + if ((now - leftthumblast) < 500) { /* draw if there was movement in the last half-second. */ + const SDL_FRect box = { 107 + ((axis_x / 32767.0f) * 30.0f), 252 + ((axis_y / 32767.0f) * 30.0f), 30, 30 }; + SDL_RenderFillRect(renderer, &box); + } + + /* right thumb axis. */ + axis_x = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX); + axis_y = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY); + if ((SDL_abs(axis_x) > 1000) || (SDL_abs(axis_y) > 1000)) { /* zero means centered, but it might be a little off zero... */ + rightthumblast = now; /* keep drawing, we're still moving. */ + } + if ((now - rightthumblast) < 500) { /* draw if there was movement in the last half-second. */ + const SDL_FRect box = { 397 + ((axis_x / 32767.0f) * 30.0f), 370 + ((axis_y / 32767.0f) * 30.0f), 30, 30 }; + SDL_RenderFillRect(renderer, &box); + } + + /* left trigger. */ + axis_y = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER); + if (axis_y > 1000) { /* zero means unpressed, but it might be a little off zero... */ + const float height = ((axis_y / 32767.0f) * 65.0f); + const SDL_FRect box = { 127, 1 + (65.0f - height), 37, height }; + SDL_RenderFillRect(renderer, &box); + } + + /* right trigger. */ + axis_y = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); + if (axis_y > 1000) { /* zero means unpressed, but it might be a little off zero... */ + const float height = ((axis_y / 32767.0f) * 65.0f); + const SDL_FRect box = { 481, 1 + (65.0f - height), 37, height }; + SDL_RenderFillRect(renderer, &box); + } + } + + x = (((float) WINDOW_WIDTH) - (SDL_strlen(text) * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE)) / 2.0f; + if (gamepad) { + y = (float) (WINDOW_HEIGHT - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE + 2)); + } else { + y = (((float) WINDOW_HEIGHT) - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) / 2.0f; + } + SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0xFF, 0xFF); /* blue */ + SDL_RenderDebugText(renderer, x, y, text); + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + SDL_DestroyTexture(texture); + SDL_CloseGamepad(gamepad); + /* SDL will clean up the window/renderer for us. */ +} diff --git a/libs/SDL3/examples/input/03-gamepad-polling/onmouseover.webp b/libs/SDL3/examples/input/03-gamepad-polling/onmouseover.webp new file mode 100644 index 00000000..91c7bb0d Binary files /dev/null and b/libs/SDL3/examples/input/03-gamepad-polling/onmouseover.webp differ diff --git a/libs/SDL3/examples/input/03-gamepad-polling/thumbnail.png b/libs/SDL3/examples/input/03-gamepad-polling/thumbnail.png new file mode 100644 index 00000000..c5bc6e64 Binary files /dev/null and b/libs/SDL3/examples/input/03-gamepad-polling/thumbnail.png differ diff --git a/libs/SDL3/examples/input/04-gamepad-events/README.txt b/libs/SDL3/examples/input/04-gamepad-events/README.txt new file mode 100644 index 00000000..42f62d78 --- /dev/null +++ b/libs/SDL3/examples/input/04-gamepad-events/README.txt @@ -0,0 +1,2 @@ +This example code looks for gamepad input in the event handler, and +reports any changes as a flood of info. diff --git a/libs/SDL3/examples/input/04-gamepad-events/gamepad-events.c b/libs/SDL3/examples/input/04-gamepad-events/gamepad-events.c new file mode 100644 index 00000000..bcdd5764 --- /dev/null +++ b/libs/SDL3/examples/input/04-gamepad-events/gamepad-events.c @@ -0,0 +1,212 @@ +/* + * This example code looks for gamepad input in the event handler, and + * reports any changes as a flood of info. + * + * This code is public domain. Feel free to use it for any purpose! + */ + +/* Joysticks are low-level interfaces: there's something with a bunch of + buttons, axes and hats, in no understood order or position. This is + a flexible interface, but you'll need to build some sort of configuration + UI to let people tell you what button, etc, does what. On top of this + interface, SDL offers the "gamepad" API, which works with lots of devices, + and knows how to map arbitrary buttons and such to look like an + Xbox/PlayStation/etc gamepad. This is easier, and better, for many games, + but isn't necessarily a good fit for complex apps and hardware. A flight + simulator, a realistic racing game, etc, might want the joystick interface + instead of gamepads. */ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static SDL_Color colors[64]; + +#define MOTION_EVENT_COOLDOWN 40 + +typedef struct EventMessage +{ + char *str; + SDL_Color color; + Uint64 start_ticks; + struct EventMessage *next; +} EventMessage; + +static EventMessage messages; +static EventMessage *messages_tail = &messages; + +static const char *battery_state_string(SDL_PowerState state) +{ + switch (state) { + case SDL_POWERSTATE_ERROR: return "ERROR"; + case SDL_POWERSTATE_UNKNOWN: return "UNKNOWN"; + case SDL_POWERSTATE_ON_BATTERY: return "ON BATTERY"; + case SDL_POWERSTATE_NO_BATTERY: return "NO BATTERY"; + case SDL_POWERSTATE_CHARGING: return "CHARGING"; + case SDL_POWERSTATE_CHARGED: return "CHARGED"; + default: break; + } + return "UNKNOWN"; +} + +static void add_message(SDL_JoystickID jid, const char *fmt, ...) +{ + const SDL_Color *color = &colors[((size_t) jid) % SDL_arraysize(colors)]; + EventMessage *msg = NULL; + char *str = NULL; + va_list ap; + + msg = (EventMessage *) SDL_calloc(1, sizeof (*msg)); + if (!msg) { + return; // oh well. + } + + va_start(ap, fmt); + SDL_vasprintf(&str, fmt, ap); + va_end(ap); + if (!str) { + SDL_free(msg); + return; // oh well. + } + + msg->str = str; + SDL_copyp(&msg->color, color); + msg->start_ticks = SDL_GetTicks(); + msg->next = NULL; + + messages_tail->next = msg; + messages_tail = msg; +} + + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + int i; + + SDL_SetAppMetadata("Example Input Gamepad Events", "1.0", "com.example.input-gamepad-events"); + + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_CreateWindowAndRenderer("examples/input/gamepad-events", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + colors[0].r = colors[0].g = colors[0].b = colors[0].a = 255; + for (i = 1; i < SDL_arraysize(colors); i++) { + colors[i].r = SDL_rand(255); + colors[i].g = SDL_rand(255); + colors[i].b = SDL_rand(255); + colors[i].a = 255; + } + + add_message(0, "Please plug in a gamepad."); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } else if (event->type == SDL_EVENT_GAMEPAD_ADDED) { + /* this event is sent for each hotplugged stick, but also each already-connected gamepad during SDL_Init(). */ + const SDL_JoystickID which = event->gdevice.which; + SDL_Gamepad *gamepad = SDL_OpenGamepad(which); + if (!gamepad) { + add_message(which, "Gamepad #%u add, but not opened: %s", (unsigned int) which, SDL_GetError()); + } else { + char *mapping = SDL_GetGamepadMapping(gamepad); + add_message(which, "Gamepad #%u ('%s') added", (unsigned int) which, SDL_GetGamepadName(gamepad)); + if (mapping) { + add_message(which, "Gamepad #%u mapping: %s", (unsigned int) which, mapping); + SDL_free(mapping); + } + } + } else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) { + const SDL_JoystickID which = event->gdevice.which; + SDL_Gamepad *gamepad = SDL_GetGamepadFromID(which); + if (gamepad) { + SDL_CloseGamepad(gamepad); /* the gamepad was unplugged. */ + } + add_message(which, "Gamepad #%u removed", (unsigned int) which); + } else if (event->type == SDL_EVENT_GAMEPAD_AXIS_MOTION) { + static Uint64 axis_motion_cooldown_time = 0; /* these are spammy, only show every X milliseconds. */ + const Uint64 now = SDL_GetTicks(); + if (now >= axis_motion_cooldown_time) { + const SDL_JoystickID which = event->gaxis.which; + axis_motion_cooldown_time = now + MOTION_EVENT_COOLDOWN; + add_message(which, "Gamepad #%u axis %s -> %d", (unsigned int) which, SDL_GetGamepadStringForAxis((SDL_GamepadAxis) event->gaxis.axis), (int) event->gaxis.value); + } + } else if ((event->type == SDL_EVENT_GAMEPAD_BUTTON_UP) || (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN)) { + const SDL_JoystickID which = event->gbutton.which; + add_message(which, "Gamepad #%u button %s -> %s", (unsigned int) which, SDL_GetGamepadStringForButton((SDL_GamepadButton) event->gbutton.button), event->gbutton.down ? "PRESSED" : "RELEASED"); + } else if (event->type == SDL_EVENT_JOYSTICK_BATTERY_UPDATED) { + const SDL_JoystickID which = event->jbattery.which; + if (SDL_IsGamepad(which)) { /* this is only reported for joysticks, so make sure this joystick is _actually_ a gamepad. */ + add_message(which, "Gamepad #%u battery -> %s - %d%%", (unsigned int) which, battery_state_string(event->jbattery.state), event->jbattery.percent); + } + } + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + const Uint64 now = SDL_GetTicks(); + const float msg_lifetime = 3500.0f; /* milliseconds a message lives for. */ + EventMessage *msg = messages.next; + float prev_y = 0.0f; + int winw = 640, winh = 480; + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + SDL_GetWindowSize(window, &winw, &winh); + + while (msg) { + float x, y; + const float life_percent = ((float) (now - msg->start_ticks)) / msg_lifetime; + if (life_percent >= 1.0f) { /* msg is done. */ + messages.next = msg->next; + if (messages_tail == msg) { + messages_tail = &messages; + } + SDL_free(msg->str); + SDL_free(msg); + msg = messages.next; + continue; + } + x = (((float) winw) - (SDL_strlen(msg->str) * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE)) / 2.0f; + y = ((float) winh) * life_percent; + if ((prev_y != 0.0f) && ((prev_y - y) < ((float) SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE))) { + msg->start_ticks = now; + break; // wait for the previous message to tick up a little. + } + + SDL_SetRenderDrawColor(renderer, msg->color.r, msg->color.g, msg->color.b, (Uint8) (((float) msg->color.a) * (1.0f - life_percent))); + SDL_RenderDebugText(renderer, x, y, msg->str); + + prev_y = y; + msg = msg->next; + } + + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + SDL_Quit(); + /* SDL will clean up the window/renderer for us. We let the gamepads leak. */ +} diff --git a/libs/SDL3/examples/input/04-gamepad-events/onmouseover.webp b/libs/SDL3/examples/input/04-gamepad-events/onmouseover.webp new file mode 100644 index 00000000..b4277393 Binary files /dev/null and b/libs/SDL3/examples/input/04-gamepad-events/onmouseover.webp differ diff --git a/libs/SDL3/examples/input/04-gamepad-events/thumbnail.png b/libs/SDL3/examples/input/04-gamepad-events/thumbnail.png new file mode 100644 index 00000000..1c817a55 Binary files /dev/null and b/libs/SDL3/examples/input/04-gamepad-events/thumbnail.png differ diff --git a/libs/SDL3/examples/misc/01-power/README.txt b/libs/SDL3/examples/misc/01-power/README.txt new file mode 100644 index 00000000..800153e1 --- /dev/null +++ b/libs/SDL3/examples/misc/01-power/README.txt @@ -0,0 +1,4 @@ +This example code reports power status (plugged in, battery level, etc). + +Note that only Chrome-based browsers support this API currently. Firefox and +Safari will report this as unknown, but this may change later! diff --git a/libs/SDL3/examples/misc/01-power/onmouseover.webp b/libs/SDL3/examples/misc/01-power/onmouseover.webp new file mode 100644 index 00000000..a99b8d6f Binary files /dev/null and b/libs/SDL3/examples/misc/01-power/onmouseover.webp differ diff --git a/libs/SDL3/examples/misc/01-power/power.c b/libs/SDL3/examples/misc/01-power/power.c new file mode 100644 index 00000000..44a89640 --- /dev/null +++ b/libs/SDL3/examples/misc/01-power/power.c @@ -0,0 +1,155 @@ +/* + * This example code reports power status (plugged in, battery level, etc). + * + * This code is public domain. Feel free to use it for any purpose! + */ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + SDL_SetAppMetadata("Example Misc Power", "1.0", "com.example.misc-power"); + + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_CreateWindowAndRenderer("examples/misc/power", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + const SDL_FRect frame = { 100, 200, 440, 80 }; /* the percentage bar dimensions. */ + + /* Query for battery info */ + int seconds = 0, percent = 0; + const SDL_PowerState state = SDL_GetPowerInfo(&seconds, &percent); + + /* We set up different drawing details for each power state, then + run it all through the same drawing code. */ + int clearr = 0, clearg = 0, clearb = 0; /* clear window to this color. */ + int textr = 255, textg = 255, textb = 255; /* draw messages in this color. */ + int framer = 255, frameg = 255, frameb = 255; /* draw a percentage bar frame in this color. */ + int barr = 0, barg = 0, barb = 0; /* draw a percentage bar in this color. */ + const char *msg = NULL; + const char *msg2 = NULL; + + switch (state) { + case SDL_POWERSTATE_ERROR: + msg2 = "ERROR GETTING POWER STATE"; + msg = SDL_GetError(); + clearr = 255; /* red background */ + break; + + default: /* in case this does something unexpected later, treat it as unknown. */ + case SDL_POWERSTATE_UNKNOWN: + msg = "Power state is unknown."; + clearr = clearb = clearg = 50; /* grey background */ + break; + + case SDL_POWERSTATE_ON_BATTERY: + msg = "Running on battery."; + barr = 255; /* draw in red */ + break; + + case SDL_POWERSTATE_NO_BATTERY: + msg = "Plugged in, no battery available."; + clearg = 50; /* green background */ + break; + + case SDL_POWERSTATE_CHARGING: + msg = "Charging."; + barb = barg = 255; /* draw in cyan */ + break; + + case SDL_POWERSTATE_CHARGED: + msg = "Charged."; + barg = 255; /* draw in green */ + break; + } + + SDL_SetRenderDrawColor(renderer, clearr, clearg, clearb, 255); + SDL_RenderClear(renderer); + + if (percent >= 0) { + float x, y; + SDL_FRect pctrect; + char remainstr[64]; + char msgbuf[128]; + + SDL_copyp(&pctrect, &frame); + pctrect.w *= percent / 100.0f; + + if (seconds < 0) { + SDL_strlcpy(remainstr, "unknown time", sizeof (remainstr)); + } else { + int hours, minutes; + hours = seconds / (60 * 60); + seconds -= hours * (60 * 60); + minutes = seconds / 60; + seconds -= minutes * 60; + SDL_snprintf(remainstr, sizeof (remainstr), "%02d:%02d:%02d", hours, minutes, seconds); + } + + SDL_snprintf(msgbuf, sizeof (msgbuf), "Battery: %3d percent, %s remaining", percent, remainstr); + x = frame.x + ((frame.w - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(msgbuf))) / 2.0f); + y = frame.y + frame.h + SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE; + + SDL_SetRenderDrawColor(renderer, barr, barg, barb, 255); /* draw percent bar. */ + SDL_RenderFillRect(renderer, &pctrect); + SDL_SetRenderDrawColor(renderer, framer, frameg, frameb, 255); /* draw frame on top of bar. */ + SDL_RenderRect(renderer, &frame); + SDL_SetRenderDrawColor(renderer, textr, textg, textb, 255); + SDL_RenderDebugText(renderer, x, y, msgbuf); /* draw text about battery level */ + } + + if (msg) { + const float x = frame.x + ((frame.w - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(msg))) / 2.0f); + const float y = frame.y - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * 2); + SDL_SetRenderDrawColor(renderer, textr, textg, textb, 255); + SDL_RenderDebugText(renderer, x, y, msg); + } + + if (msg2) { + const float x = frame.x + ((frame.w - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(msg2))) / 2.0f); + const float y = frame.y - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * 4); + SDL_SetRenderDrawColor(renderer, textr, textg, textb, 255); + SDL_RenderDebugText(renderer, x, y, msg2); + } + + /* put the new rendering on the screen. */ + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + /* SDL will clean up the window/renderer for us. */ +} + diff --git a/libs/SDL3/examples/misc/01-power/thumbnail.png b/libs/SDL3/examples/misc/01-power/thumbnail.png new file mode 100644 index 00000000..970bdff6 Binary files /dev/null and b/libs/SDL3/examples/misc/01-power/thumbnail.png differ diff --git a/libs/SDL3/examples/misc/02-clipboard/README.txt b/libs/SDL3/examples/misc/02-clipboard/README.txt new file mode 100644 index 00000000..b2721996 --- /dev/null +++ b/libs/SDL3/examples/misc/02-clipboard/README.txt @@ -0,0 +1,6 @@ +This example code lets the user copy and paste with the system clipboard. + +This only handles text, but SDL supports other data types, too. + +Note that only Chrome-based browsers support this API currently. This uses a +new Javascript API, so hopefully this will be available everywhere soon! diff --git a/libs/SDL3/examples/misc/02-clipboard/clipboard.c b/libs/SDL3/examples/misc/02-clipboard/clipboard.c new file mode 100644 index 00000000..a40a24f6 --- /dev/null +++ b/libs/SDL3/examples/misc/02-clipboard/clipboard.c @@ -0,0 +1,235 @@ +/* + * This example code lets the user copy and paste with the system clipboard. + * + * This only handles text, but SDL supports other data types, too. + * + * This code is public domain. Feel free to use it for any purpose! + */ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static const char *copybuttonstr = "Click here to copy!"; +static const char *pastebuttonstr = "Click here to paste!"; +static SDL_FRect currenttimerect; +static SDL_FRect copybuttonrect; +static SDL_FRect pastetextrect; +static SDL_FRect pastebuttonrect; +static bool copy_pressed = false; +static bool paste_pressed = false; +static char current_time[64]; +static char *pasted_str = NULL; + +static void CalculateCurrentTimeString(void) +{ + SDL_Time ticks = 0; + SDL_DateTime dt; + if (!SDL_GetCurrentTime(&ticks) || !SDL_TimeToDateTime(ticks, &dt, true)) { + SDL_snprintf(current_time, sizeof (current_time), "(Don't know the current time, sorry.)"); + } else { + static const char *month[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; + static const char *day[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; + SDL_snprintf(current_time, sizeof (current_time), "%s, %s %d, %d %02d:%02d:%02d", day[dt.day_of_week], month[dt.month-1], dt.day, dt.year, dt.hour, dt.minute, dt.second); + } +} + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + SDL_SetAppMetadata("Example Misc Clipboard", "1.0", "com.example.misc-clipboard"); + + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_CreateWindowAndRenderer("examples/misc/clipboard", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); + + CalculateCurrentTimeString(); + + /* set up the locations where we'll draw stuff. */ + currenttimerect.x = 30; + currenttimerect.y = 10; + currenttimerect.w = 390; + currenttimerect.h = SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE + 10; + + copybuttonrect.x = currenttimerect.x + currenttimerect.w + 30; + copybuttonrect.y = currenttimerect.y; + copybuttonrect.w = (float) ((SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(copybuttonstr)) + 10); + copybuttonrect.h = currenttimerect.h; + + pastetextrect.x = 10; + pastetextrect.y = currenttimerect.y + currenttimerect.h + 10; + pastetextrect.w = 620; + pastetextrect.h = ((480 - pastetextrect.y) - copybuttonrect.h) - 20; + + pastebuttonrect.w = (float) ((SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(pastebuttonstr)) + 10); + pastebuttonrect.x = (640 - pastebuttonrect.w) / 2.0f; + pastebuttonrect.y = pastetextrect.y + pastetextrect.h + 10; + pastebuttonrect.h = copybuttonrect.h; + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + SDL_ConvertEventToRenderCoordinates(renderer, event); + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) { + if (event->button.button == SDL_BUTTON_LEFT) { + const SDL_FPoint p = { event->button.x, event->button.y }; + copy_pressed = SDL_PointInRectFloat(&p, ©buttonrect); + paste_pressed = SDL_PointInRectFloat(&p, &pastebuttonrect); + } + } else if (event->type == SDL_EVENT_MOUSE_BUTTON_UP) { + if (event->button.button == SDL_BUTTON_LEFT) { + const SDL_FPoint p = { event->button.x, event->button.y }; + if (copy_pressed && SDL_PointInRectFloat(&p, ©buttonrect)) { + SDL_SetClipboardText(current_time); + } else if (paste_pressed && SDL_PointInRectFloat(&p, &pastebuttonrect)) { + SDL_free(pasted_str); + pasted_str = SDL_GetClipboardText(); + } + copy_pressed = paste_pressed = false; + } + } + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +static void RenderPastedText(void) +{ + char *str = pasted_str; + if (str) { + float x = pastetextrect.x + 5; + float y = pastetextrect.y + 5; + const float w = pastetextrect.w - 10; + const float h = pastetextrect.h; + const size_t max_chars_per_line = (size_t) (w / SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE); + char *newline; + size_t slen; + char ch; + + /* this doesn't wordwrap, or deal with Unicode....this is just a simple example app! */ + while ((newline = SDL_strchr(str, '\n')) != NULL) { + const bool ignore_cr = ((newline > str) && (newline[-1] == '\r')); + + if (ignore_cr) { + newline[-1] = '\0'; + } + *newline = '\0'; + + slen = SDL_strlen(str); /* length to end of line. */ + slen = SDL_min(slen, max_chars_per_line); + ch = str[slen]; + str[slen] = '\0'; + SDL_RenderDebugText(renderer, x, y, str); + str[slen] = ch; + + if (ignore_cr) { + newline[-1] = '\r'; + } + *newline = '\n'; + + str = newline + 1; + y += (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE + 2); + if ((h - y) < SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) { + break; // no space for another line, stop here. + } + } + + /* last text after newline, if there's room. */ + if ((h - y) >= SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) { + slen = SDL_strlen(str); /* length to end of line. */ + slen = SDL_min(slen, max_chars_per_line); + ch = str[slen]; + str[slen] = '\0'; + SDL_RenderDebugText(renderer, x, y, str); + str[slen] = ch; + } + } +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + float x, y; + + CalculateCurrentTimeString(); + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); /* black */ + SDL_RenderClear(renderer); + + /* draw a frame around the current time. */ + SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); + SDL_RenderFillRect(renderer, ¤ttimerect); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderRect(renderer, ¤ttimerect); + + /* draw the current time inside the frame. */ + x = currenttimerect.x + ((currenttimerect.w - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(current_time))) / 2.0f); + y = currenttimerect.y + 5; + SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255); + SDL_RenderDebugText(renderer, x, y, current_time); + + /* draw a frame for the "copy the current time to the clipboard" button. */ + if (copy_pressed) { + SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); + } else { + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); + } + SDL_RenderFillRect(renderer, ©buttonrect); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderRect(renderer, ©buttonrect); + + /* draw the "copy this text" button string. */ + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderDebugText(renderer, copybuttonrect.x + 5, copybuttonrect.y + 5, copybuttonstr); + + /* draw a frame for the pasted text area. */ + SDL_SetRenderDrawColor(renderer, 0, 53, 25, 255); + SDL_RenderFillRect(renderer, &pastetextrect); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderRect(renderer, &pastetextrect); + + /* draw pasted text. */ + SDL_SetRenderDrawColor(renderer, 0, 219, 107, 255); + RenderPastedText(); + + /* draw a frame for the "paste from the clipboard" button. */ + if (paste_pressed) { + SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); + } else { + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); + } + SDL_RenderFillRect(renderer, &pastebuttonrect); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderRect(renderer, &pastebuttonrect); + + /* draw the "paste some text" button string. */ + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderDebugText(renderer, pastebuttonrect.x + 5, pastebuttonrect.y + 5, pastebuttonstr); + + /* put the new rendering on the screen. */ + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + SDL_free(pasted_str); + /* SDL will clean up the window/renderer for us. */ +} + diff --git a/libs/SDL3/examples/misc/02-clipboard/onmouseover.webp b/libs/SDL3/examples/misc/02-clipboard/onmouseover.webp new file mode 100644 index 00000000..73476b47 Binary files /dev/null and b/libs/SDL3/examples/misc/02-clipboard/onmouseover.webp differ diff --git a/libs/SDL3/examples/misc/02-clipboard/thumbnail.png b/libs/SDL3/examples/misc/02-clipboard/thumbnail.png new file mode 100644 index 00000000..76bc9d51 Binary files /dev/null and b/libs/SDL3/examples/misc/02-clipboard/thumbnail.png differ diff --git a/libs/SDL3/examples/misc/description.txt b/libs/SDL3/examples/misc/description.txt new file mode 100644 index 00000000..bd99b89d --- /dev/null +++ b/libs/SDL3/examples/misc/description.txt @@ -0,0 +1 @@ +Various examples from smaller subsystems \ No newline at end of file diff --git a/libs/SDL3/examples/renderer/01-clear/clear.c b/libs/SDL3/examples/renderer/01-clear/clear.c index 5b2b7dc8..8a871390 100644 --- a/libs/SDL3/examples/renderer/01-clear/clear.c +++ b/libs/SDL3/examples/renderer/01-clear/clear.c @@ -24,10 +24,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/clear", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/clear", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); return SDL_APP_CONTINUE; /* carry on with the program! */ } diff --git a/libs/SDL3/examples/renderer/02-primitives/primitives.c b/libs/SDL3/examples/renderer/02-primitives/primitives.c index 5cfd7318..5afcb608 100644 --- a/libs/SDL3/examples/renderer/02-primitives/primitives.c +++ b/libs/SDL3/examples/renderer/02-primitives/primitives.c @@ -26,10 +26,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/primitives", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/primitives", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* set up some random points */ for (i = 0; i < SDL_arraysize(points); i++) { diff --git a/libs/SDL3/examples/renderer/03-lines/lines.c b/libs/SDL3/examples/renderer/03-lines/lines.c index 54f7eb1f..25af0edd 100644 --- a/libs/SDL3/examples/renderer/03-lines/lines.c +++ b/libs/SDL3/examples/renderer/03-lines/lines.c @@ -23,10 +23,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/lines", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/lines", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); return SDL_APP_CONTINUE; /* carry on with the program! */ } @@ -76,8 +77,9 @@ SDL_AppResult SDL_AppIterate(void *appstate) const float size = 30.0f; const float x = 320.0f; const float y = 95.0f - (size / 2.0f); + const float r = (float) i * (SDL_PI_F / 180.0f); SDL_SetRenderDrawColor(renderer, SDL_rand(256), SDL_rand(256), SDL_rand(256), SDL_ALPHA_OPAQUE); - SDL_RenderLine(renderer, x, y, x + SDL_sinf((float) i) * size, y + SDL_cosf((float) i) * size); + SDL_RenderLine(renderer, x, y, x + SDL_cosf(r) * size, y + SDL_sinf(r) * size); } SDL_RenderPresent(renderer); /* put it all on the screen! */ diff --git a/libs/SDL3/examples/renderer/04-points/points.c b/libs/SDL3/examples/renderer/04-points/points.c index b7b5c21d..d07b85b9 100644 --- a/libs/SDL3/examples/renderer/04-points/points.c +++ b/libs/SDL3/examples/renderer/04-points/points.c @@ -43,10 +43,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/points", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/points", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* set up the data for a bunch of points. */ for (i = 0; i < SDL_arraysize(points); i++) { diff --git a/libs/SDL3/examples/renderer/05-rectangles/rectangles.c b/libs/SDL3/examples/renderer/05-rectangles/rectangles.c index 3aa7242a..ad00b607 100644 --- a/libs/SDL3/examples/renderer/05-rectangles/rectangles.c +++ b/libs/SDL3/examples/renderer/05-rectangles/rectangles.c @@ -26,10 +26,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/rectangles", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/rectangles", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); return SDL_APP_CONTINUE; /* carry on with the program! */ } @@ -89,7 +90,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) /* ...and also fill a bunch of rectangles at once... */ for (i = 0; i < SDL_arraysize(rects); i++) { - const float w = (float) (WINDOW_WIDTH / SDL_arraysize(rects)); + const float w = ((float) WINDOW_WIDTH / SDL_arraysize(rects)); const float h = i * 8.0f; rects[i].x = i * w; rects[i].y = WINDOW_HEIGHT - h; diff --git a/libs/SDL3/examples/renderer/06-textures/README.txt b/libs/SDL3/examples/renderer/06-textures/README.txt index 21c3f0bc..8dc42b7b 100644 --- a/libs/SDL3/examples/renderer/06-textures/README.txt +++ b/libs/SDL3/examples/renderer/06-textures/README.txt @@ -1,3 +1,3 @@ This example creates an SDL window and renderer, loads a texture from a -.bmp file, and then draws it a few times each frame. +.png file, and then draws it a few times each frame. diff --git a/libs/SDL3/examples/renderer/06-textures/textures.c b/libs/SDL3/examples/renderer/06-textures/textures.c index f4ad7073..0c742bcb 100644 --- a/libs/SDL3/examples/renderer/06-textures/textures.c +++ b/libs/SDL3/examples/renderer/06-textures/textures.c @@ -23,7 +23,7 @@ static int texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Textures", "1.0", "com.example.renderer-textures"); @@ -32,25 +32,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/textures", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/textures", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many - times) with data from a bitmap file. */ + times) with data from a png file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { - SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); + SDL_Log("Couldn't load png: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/renderer/07-streaming-textures/streaming-textures.c b/libs/SDL3/examples/renderer/07-streaming-textures/streaming-textures.c index bf309bcf..27161f12 100644 --- a/libs/SDL3/examples/renderer/07-streaming-textures/streaming-textures.c +++ b/libs/SDL3/examples/renderer/07-streaming-textures/streaming-textures.c @@ -29,10 +29,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/streaming-textures", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/streaming-textures", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, TEXTURE_SIZE, TEXTURE_SIZE); if (!texture) { @@ -65,7 +66,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) /* To update a streaming texture, you need to lock it first. This gets you access to the pixels. Note that this is considered a _write-only_ operation: the buffer you get from locking - might not acutally have the existing contents of the texture, and you have to write to every + might not actually have the existing contents of the texture, and you have to write to every locked pixel! */ /* You can use SDL_LockTexture() to get an array of raw pixels, but we're going to use diff --git a/libs/SDL3/examples/renderer/08-rotating-textures/README.txt b/libs/SDL3/examples/renderer/08-rotating-textures/README.txt index 4ae46bc2..3768cd84 100644 --- a/libs/SDL3/examples/renderer/08-rotating-textures/README.txt +++ b/libs/SDL3/examples/renderer/08-rotating-textures/README.txt @@ -1,3 +1,3 @@ -This example creates an SDL window and renderer, loads a texture from a .bmp +This example creates an SDL window and renderer, loads a texture from a .png file, and then draws it, rotating around the center of the screen. diff --git a/libs/SDL3/examples/renderer/08-rotating-textures/rotating-textures.c b/libs/SDL3/examples/renderer/08-rotating-textures/rotating-textures.c index bf318efd..22bed072 100644 --- a/libs/SDL3/examples/renderer/08-rotating-textures/rotating-textures.c +++ b/libs/SDL3/examples/renderer/08-rotating-textures/rotating-textures.c @@ -23,7 +23,7 @@ static int texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Rotating Textures", "1.0", "com.example.renderer-rotating-textures"); @@ -32,25 +32,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/rotating-textures", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/rotating-textures", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/renderer/09-scaling-textures/README.txt b/libs/SDL3/examples/renderer/09-scaling-textures/README.txt index e13a6ec2..51eed0fc 100644 --- a/libs/SDL3/examples/renderer/09-scaling-textures/README.txt +++ b/libs/SDL3/examples/renderer/09-scaling-textures/README.txt @@ -1,3 +1,3 @@ -This example creates an SDL window and renderer, loads a texture from a .bmp +This example creates an SDL window and renderer, loads a texture from a .png file, and then draws it, scaling it up and down. diff --git a/libs/SDL3/examples/renderer/09-scaling-textures/scaling-textures.c b/libs/SDL3/examples/renderer/09-scaling-textures/scaling-textures.c index 66060ed7..199433f9 100644 --- a/libs/SDL3/examples/renderer/09-scaling-textures/scaling-textures.c +++ b/libs/SDL3/examples/renderer/09-scaling-textures/scaling-textures.c @@ -23,7 +23,7 @@ static int texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Scaling Textures", "1.0", "com.example.renderer-scaling-textures"); @@ -32,25 +32,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/scaling-textures", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/scaling-textures", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/renderer/10-geometry/README.txt b/libs/SDL3/examples/renderer/10-geometry/README.txt index d76d0cce..e03f0153 100644 --- a/libs/SDL3/examples/renderer/10-geometry/README.txt +++ b/libs/SDL3/examples/renderer/10-geometry/README.txt @@ -1,3 +1,3 @@ -This example creates an SDL window and renderer, loads a texture from a .bmp +This example creates an SDL window and renderer, loads a texture from a .png file, and then draws geometry (arbitrary polygons) using it. diff --git a/libs/SDL3/examples/renderer/10-geometry/geometry.c b/libs/SDL3/examples/renderer/10-geometry/geometry.c index 77ff863c..028134de 100644 --- a/libs/SDL3/examples/renderer/10-geometry/geometry.c +++ b/libs/SDL3/examples/renderer/10-geometry/geometry.c @@ -23,7 +23,7 @@ static int texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Geometry", "1.0", "com.example.renderer-geometry"); @@ -32,25 +32,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/geometry", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/geometry", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/renderer/11-color-mods/README.txt b/libs/SDL3/examples/renderer/11-color-mods/README.txt index 66f233b6..5f7e1105 100644 --- a/libs/SDL3/examples/renderer/11-color-mods/README.txt +++ b/libs/SDL3/examples/renderer/11-color-mods/README.txt @@ -1,3 +1,3 @@ This example creates an SDL window and renderer, loads a texture from a -.bmp file, and then draws it a few times each frame, adjusting the colors. +.png file, and then draws it a few times each frame, adjusting the colors. diff --git a/libs/SDL3/examples/renderer/11-color-mods/color-mods.c b/libs/SDL3/examples/renderer/11-color-mods/color-mods.c index 8877232b..8c3ed49e 100644 --- a/libs/SDL3/examples/renderer/11-color-mods/color-mods.c +++ b/libs/SDL3/examples/renderer/11-color-mods/color-mods.c @@ -23,7 +23,7 @@ static int texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Color Mods", "1.0", "com.example.renderer-color-mods"); @@ -32,25 +32,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/color-mods", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/color-mods", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/renderer/14-viewport/README.txt b/libs/SDL3/examples/renderer/14-viewport/README.txt index 9da7c7ab..ddcf2702 100644 --- a/libs/SDL3/examples/renderer/14-viewport/README.txt +++ b/libs/SDL3/examples/renderer/14-viewport/README.txt @@ -1,4 +1,4 @@ This example creates an SDL window and renderer, loads a texture -from a .bmp file, and then draws it a few times each frame, adjusting +from a .png file, and then draws it a few times each frame, adjusting the viewport before each draw. diff --git a/libs/SDL3/examples/renderer/14-viewport/viewport.c b/libs/SDL3/examples/renderer/14-viewport/viewport.c index 0a6c0151..2889fb20 100644 --- a/libs/SDL3/examples/renderer/14-viewport/viewport.c +++ b/libs/SDL3/examples/renderer/14-viewport/viewport.c @@ -23,7 +23,7 @@ static int texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Viewport", "1.0", "com.example.renderer-viewport"); @@ -32,25 +32,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/viewport", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/viewport", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/renderer/15-cliprect/README.txt b/libs/SDL3/examples/renderer/15-cliprect/README.txt index 48c9f1fe..d9a6ce15 100644 --- a/libs/SDL3/examples/renderer/15-cliprect/README.txt +++ b/libs/SDL3/examples/renderer/15-cliprect/README.txt @@ -1,5 +1,5 @@ This example creates an SDL window and renderer, loads a texture -from a .bmp file, and stretches it across the window. Each frame, we move +from a .png file, and stretches it across the window. Each frame, we move the clipping rectangle around, so only a small square of the texture is actually drawn. diff --git a/libs/SDL3/examples/renderer/15-cliprect/cliprect.c b/libs/SDL3/examples/renderer/15-cliprect/cliprect.c index 058072c3..1e7fb455 100644 --- a/libs/SDL3/examples/renderer/15-cliprect/cliprect.c +++ b/libs/SDL3/examples/renderer/15-cliprect/cliprect.c @@ -30,7 +30,7 @@ static Uint64 last_time = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Clipping Rectangle", "1.0", "com.example.renderer-cliprect"); @@ -39,10 +39,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/cliprect", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/cliprect", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); cliprect_direction.x = cliprect_direction.y = 1.0f; @@ -53,15 +54,15 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture = SDL_CreateTextureFromSurface(renderer, surface); if (!texture) { @@ -93,20 +94,20 @@ SDL_AppResult SDL_AppIterate(void *appstate) /* Set a new clipping rectangle position */ cliprect_position.x += distance * cliprect_direction.x; - if (cliprect_position.x < 0.0f) { - cliprect_position.x = 0.0f; + if (cliprect_position.x < -CLIPRECT_SIZE) { + cliprect_position.x = -CLIPRECT_SIZE; cliprect_direction.x = 1.0f; - } else if (cliprect_position.x >= (WINDOW_WIDTH - CLIPRECT_SIZE)) { - cliprect_position.x = (WINDOW_WIDTH - CLIPRECT_SIZE) - 1; + } else if (cliprect_position.x >= WINDOW_WIDTH) { + cliprect_position.x = WINDOW_WIDTH - 1; cliprect_direction.x = -1.0f; } cliprect_position.y += distance * cliprect_direction.y; - if (cliprect_position.y < 0.0f) { - cliprect_position.y = 0.0f; + if (cliprect_position.y < -CLIPRECT_SIZE) { + cliprect_position.y = -CLIPRECT_SIZE; cliprect_direction.y = 1.0f; - } else if (cliprect_position.y >= (WINDOW_HEIGHT - CLIPRECT_SIZE)) { - cliprect_position.y = (WINDOW_HEIGHT - CLIPRECT_SIZE) - 1; + } else if (cliprect_position.y >= WINDOW_HEIGHT) { + cliprect_position.y = WINDOW_HEIGHT - 1; cliprect_direction.y = -1.0f; } SDL_SetRenderClipRect(renderer, &cliprect); diff --git a/libs/SDL3/examples/renderer/17-read-pixels/read-pixels.c b/libs/SDL3/examples/renderer/17-read-pixels/read-pixels.c index 556b4903..3775069e 100644 --- a/libs/SDL3/examples/renderer/17-read-pixels/read-pixels.c +++ b/libs/SDL3/examples/renderer/17-read-pixels/read-pixels.c @@ -32,7 +32,7 @@ static int converted_texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Read Pixels", "1.0", "com.example.renderer-read-pixels"); @@ -41,25 +41,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/read-pixels", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/read-pixels", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/renderer/18-debug-text/debug-text.c b/libs/SDL3/examples/renderer/18-debug-text/debug-text.c index 62005e6b..72076983 100644 --- a/libs/SDL3/examples/renderer/18-debug-text/debug-text.c +++ b/libs/SDL3/examples/renderer/18-debug-text/debug-text.c @@ -26,10 +26,11 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/debug-text", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/debug-text", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); return SDL_APP_CONTINUE; /* carry on with the program! */ } @@ -65,7 +66,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) SDL_SetRenderScale(renderer, 1.0f, 1.0f); SDL_RenderDebugText(renderer, 64, 350, "This only does ASCII chars. So this laughing emoji won't draw: 🤣"); - SDL_RenderDebugTextFormat(renderer, (float) ((WINDOW_WIDTH - (charsize * 46)) / 2), 400, "(This program has been running for %" SDL_PRIu64 " seconds.)", SDL_GetTicks() / 1000); + SDL_RenderDebugTextFormat(renderer, ((float) (WINDOW_WIDTH - (charsize * 46)) / 2), 400, "(This program has been running for %" SDL_PRIu64 " seconds.)", SDL_GetTicks() / 1000); SDL_RenderPresent(renderer); /* put it all on the screen! */ diff --git a/libs/SDL3/examples/renderer/19-affine-textures/affine-textures.c b/libs/SDL3/examples/renderer/19-affine-textures/affine-textures.c index 6c5ccbf8..b7e0d659 100644 --- a/libs/SDL3/examples/renderer/19-affine-textures/affine-textures.c +++ b/libs/SDL3/examples/renderer/19-affine-textures/affine-textures.c @@ -25,7 +25,7 @@ static int texture_height = 0; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_Surface *surface = NULL; - char *bmp_path = NULL; + char *png_path = NULL; SDL_SetAppMetadata("Example Renderer Affine Textures", "1.0", "com.example.renderer-affine-textures"); @@ -34,25 +34,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/renderer/affine-textures", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/renderer/affine-textures", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D engines refer to these as "sprites." We'll do a static texture (upload once, draw many times) with data from a bitmap file. */ /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. - Load a .bmp into a surface, move it to a texture from there. */ - SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ - surface = SDL_LoadBMP(bmp_path); + Load a .png into a surface, move it to a texture from there. */ + SDL_asprintf(&png_path, "%ssample.png", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadPNG(png_path); if (!surface) { SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); return SDL_APP_FAILURE; } - SDL_free(bmp_path); /* done with this, the file is loaded. */ + SDL_free(png_path); /* done with this, the file is loaded. */ texture_width = surface->w; texture_height = surface->h; diff --git a/libs/SDL3/examples/save-rendering-to-bitmaps.h b/libs/SDL3/examples/save-rendering-to-bitmaps.h new file mode 100644 index 00000000..bf3eab8d --- /dev/null +++ b/libs/SDL3/examples/save-rendering-to-bitmaps.h @@ -0,0 +1,49 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* +This is for generating thumbnails and videos of examples. Just include it +temporarily and let it override SDL_RenderPresent, etc, and it'll dump each +frame rendered to a new .png file. +*/ + +static bool SAVERENDERING_SDL_RenderPresent(SDL_Renderer *renderer) +{ + static unsigned int framenum = 0; + SDL_Surface *surface = SDL_RenderReadPixels(renderer, NULL); + if (!surface) { + SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Failed to read pixels for frame #%u! (%s)", framenum, SDL_GetError()); + } else { + char fname[64]; + SDL_snprintf(fname, sizeof (fname), "frame%05u.png", framenum); + if (!SDL_SavePNG(surface, fname)) { + SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Failed to save png for frame #%u! (%s)", framenum, SDL_GetError()); + } + SDL_DestroySurface(surface); + } + + framenum++; + + return SDL_RenderPresent(renderer); +} + +#define SDL_RenderPresent SAVERENDERING_SDL_RenderPresent + diff --git a/libs/SDL3/examples/template-category.html b/libs/SDL3/examples/template-category.html index 909b2c63..21f737d3 100644 --- a/libs/SDL3/examples/template-category.html +++ b/libs/SDL3/examples/template-category.html @@ -6,6 +6,9 @@ @project_name@ Examples: @category_description@ + +@preload_images_html@ + -
@@ -31,7 +33,7 @@

@project_name@ examples: @category_description@

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

@project_name@ examples

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

@@ -241,7 +241,7 @@ return function(text) { var elem = document.getElementById('output-container'); if (elem.style['top'] == '') { - elem.style['top'] = '50%'; + elem.style['top'] = '20%'; setTimeout(function() { elem.style['top'] = ''; }, 3000); } diff --git a/libs/SDL3/include/SDL3/SDL.h b/libs/SDL3/include/SDL3/SDL.h index ebfcbd11..8fbafd55 100644 --- a/libs/SDL3/include/SDL3/SDL.h +++ b/libs/SDL3/include/SDL3/SDL.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -20,7 +20,7 @@ */ /** - * Main include header for the SDL library, version 3.3.0 + * Main include header for the SDL library, version 3.4.10 * * It is almost always best to include just this one header instead of * picking out individual headers included here. There are exceptions to @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include diff --git a/libs/SDL3/include/SDL3/SDL_assert.h b/libs/SDL3/include/SDL3/SDL_assert.h index ef5f85d0..6c8a47d0 100644 --- a/libs/SDL3/include/SDL3/SDL_assert.h +++ b/libs/SDL3/include/SDL3/SDL_assert.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -126,20 +126,17 @@ extern "C" { */ #define SDL_TriggerBreakpoint() TriggerABreakpointInAPlatformSpecificManner -#elif defined(_MSC_VER) && _MSC_VER >= 1310 +#elif defined(__MINGW32__) || (defined(_MSC_VER) && _MSC_VER >= 1310) /* Don't include intrin.h here because it contains C++ code */ extern void __cdecl __debugbreak(void); #define SDL_TriggerBreakpoint() __debugbreak() #elif defined(_MSC_VER) && defined(_M_IX86) #define SDL_TriggerBreakpoint() { _asm { int 0x03 } } -#elif defined(ANDROID) || defined(__SYMBIAN32__) - #include - #define SDL_TriggerBreakpoint() assert(0) #elif SDL_HAS_BUILTIN(__builtin_debugtrap) #define SDL_TriggerBreakpoint() __builtin_debugtrap() #elif SDL_HAS_BUILTIN(__builtin_trap) #define SDL_TriggerBreakpoint() __builtin_trap() -#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) +#elif (defined(__GNUC__) || defined(__clang__) || defined(__TINYC__) || defined(__slimcc__)) && (defined(__i386__) || defined(__x86_64__)) #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "int $3\n\t" ) #elif (defined(__GNUC__) || defined(__clang__)) && defined(__riscv) #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "ebreak\n\t" ) @@ -163,7 +160,7 @@ extern "C" { #ifdef SDL_WIKI_DOCUMENTATION_SECTION /** - * A macro that reports the current function being compiled. + * A constant that contains the current function being compiled. * * If SDL can't figure how the compiler reports this, it will use "???". * @@ -171,20 +168,58 @@ extern "C" { */ #define SDL_FUNCTION __FUNCTION__ -#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 supports __func__ as a standard. */ +#elif !defined(SDL_FUNCTION) +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 supports __func__ as a standard. */ # define SDL_FUNCTION __func__ #elif ((defined(__GNUC__) && (__GNUC__ >= 2)) || defined(_MSC_VER) || defined (__WATCOMC__)) # define SDL_FUNCTION __FUNCTION__ #else # define SDL_FUNCTION "???" #endif +#endif + +#ifdef SDL_WIKI_DOCUMENTATION_SECTION /** * A macro that reports the current file being compiled. * + * This macro is only defined if it isn't already defined, so to override it + * (perhaps with something that doesn't provide path information at all, so + * build machine information doesn't leak into public binaries), apps can + * define this macro before including SDL.h or SDL_assert.h. + * * \since This macro is available since SDL 3.2.0. */ +#define SDL_FILE __FILE_NAME__ + +#elif !defined(SDL_FILE) +#ifdef __FILE_NAME__ +#define SDL_FILE __FILE_NAME__ +#else #define SDL_FILE __FILE__ +#endif +#endif + +#ifdef SDL_WIKI_DOCUMENTATION_SECTION + +/** + * A macro that reports the current file being compiled, for use in + * assertions. + * + * This macro is only defined if it isn't already defined, so to override it + * (perhaps with something that doesn't provide path information at all, so + * build machine information doesn't leak into public binaries), apps can + * define this macro before including SDL_assert.h. For example, defining this + * to `""` will make sure no source path information is included in asserts. + * + * \since This macro is available since SDL 3.4.0. + */ +#define SDL_ASSERT_FILE SDL_FILE + +#elif !defined(SDL_ASSERT_FILE) +#define SDL_ASSERT_FILE SDL_FILE +#endif + /** * A macro that reports the current line number of the file being compiled. @@ -230,7 +265,7 @@ disable assertions. */ #define SDL_NULL_WHILE_LOOP_CONDITION (0) -#elif defined(_MSC_VER) /* Avoid /W4 warnings. */ +#elif defined(_MSC_VER) && !defined(__clang__) /* Avoid /W4 warnings. */ /* "while (0,0)" fools Microsoft's compiler's /W4 warning level into thinking this condition isn't constant. And looks like an owl's face! */ #define SDL_NULL_WHILE_LOOP_CONDITION (0,0) @@ -362,8 +397,8 @@ extern SDL_DECLSPEC SDL_AssertState SDLCALL SDL_ReportAssertion(SDL_AssertData * #define SDL_enabled_assert(condition) \ do { \ while ( !(condition) ) { \ - static struct SDL_AssertData sdl_assert_data = { 0, 0, #condition, 0, 0, 0, 0 }; \ - const SDL_AssertState sdl_assert_state = SDL_ReportAssertion(&sdl_assert_data, SDL_FUNCTION, SDL_FILE, SDL_LINE); \ + static struct SDL_AssertData sdl_assert_data = { false, 0, #condition, NULL, 0, NULL, NULL }; \ + const SDL_AssertState sdl_assert_state = SDL_ReportAssertion(&sdl_assert_data, SDL_FUNCTION, SDL_ASSERT_FILE, SDL_LINE); \ if (sdl_assert_state == SDL_ASSERTION_RETRY) { \ continue; /* go again. */ \ } else if (sdl_assert_state == SDL_ASSERTION_BREAK) { \ diff --git a/libs/SDL3/include/SDL3/SDL_asyncio.h b/libs/SDL3/include/SDL3/SDL_asyncio.h index b36cb071..3c894d69 100644 --- a/libs/SDL3/include/SDL3/SDL_asyncio.h +++ b/libs/SDL3/include/SDL3/SDL_asyncio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -213,6 +213,8 @@ typedef struct SDL_AsyncIOQueue SDL_AsyncIOQueue; * \returns a pointer to the SDL_AsyncIO structure that is created or NULL on * failure; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CloseAsyncIO @@ -531,6 +533,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_SignalAsyncIOQueue(SDL_AsyncIOQueue *queue) * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LoadFile_IO diff --git a/libs/SDL3/include/SDL3/SDL_atomic.h b/libs/SDL3/include/SDL3/SDL_atomic.h index 78b5e0fa..f1a8e063 100644 --- a/libs/SDL3/include/SDL3/SDL_atomic.h +++ b/libs/SDL3/include/SDL3/SDL_atomic.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -156,6 +156,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnlockSpinlock(SDL_SpinLock *lock); */ #define SDL_CompilerBarrier() DoCompilerSpecificReadWriteBarrier() +#elif SDL_HAS_BUILTIN(__atomic_signal_fence) || (defined(__GNUC__) && (__GNUC__ >= 5)) +#define SDL_CompilerBarrier() __atomic_signal_fence(__ATOMIC_SEQ_CST) #elif defined(_MSC_VER) && (_MSC_VER > 1200) && !defined(__clang__) void _ReadWriteBarrier(void); #pragma intrinsic(_ReadWriteBarrier) @@ -167,8 +169,9 @@ void _ReadWriteBarrier(void); extern __inline void SDL_CompilerBarrier(void); #pragma aux SDL_CompilerBarrier = "" parm [] modify exact []; #else +/* We don't unlock here to avoid possible infinite recursion */ #define SDL_CompilerBarrier() \ -{ SDL_SpinLock _tmp = 0; SDL_LockSpinlock(&_tmp); SDL_UnlockSpinlock(&_tmp); } +{ SDL_SpinLock _tmp = 0; SDL_LockSpinlock(&_tmp); } #endif /** @@ -275,12 +278,19 @@ extern SDL_DECLSPEC void SDLCALL SDL_MemoryBarrierAcquireFunction(void); */ #define SDL_MemoryBarrierAcquire() SDL_MemoryBarrierAcquireFunction() +#elif SDL_HAS_BUILTIN(__atomic_thread_fence) || (defined(__GNUC__) && (__GNUC__ >= 5)) +#define SDL_MemoryBarrierRelease() __atomic_thread_fence(__ATOMIC_RELEASE) +#define SDL_MemoryBarrierAcquire() __atomic_thread_fence(__ATOMIC_ACQUIRE) #elif defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__)) #define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("lwsync" : : : "memory") #define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("lwsync" : : : "memory") #elif defined(__GNUC__) && defined(__aarch64__) #define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory") -#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory") +#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ishld" : : : "memory") +#elif defined(_MSC_VER) && (defined(_M_ARM64) || defined(_M_ARM64EC)) +#include +#define SDL_MemoryBarrierRelease() __dmb(_ARM64_BARRIER_ISH) +#define SDL_MemoryBarrierAcquire() __dmb(_ARM64_BARRIER_ISHLD) #elif defined(__GNUC__) && defined(__arm__) #if 0 /* defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) */ /* Information from: @@ -596,6 +606,24 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_SetAtomicU32(SDL_AtomicU32 *a, Uint32 v); */ extern SDL_DECLSPEC Uint32 SDLCALL SDL_GetAtomicU32(SDL_AtomicU32 *a); +/** + * Add to an atomic variable. + * + * This function also acts as a full memory barrier. + * + * ***Note: If you don't know what this function is for, you shouldn't use + * it!*** + * + * \param a a pointer to an SDL_AtomicU32 variable to be modified. + * \param v the desired value to add or subtract. + * \returns the previous value of the atomic variable. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC Uint32 SDLCALL SDL_AddAtomicU32(SDL_AtomicU32 *a, int v); + /** * Set a pointer to a new value if it is currently an old value. * diff --git a/libs/SDL3/include/SDL3/SDL_audio.h b/libs/SDL3/include/SDL3/SDL_audio.h index b91586fd..46a73b1c 100644 --- a/libs/SDL3/include/SDL3/SDL_audio.h +++ b/libs/SDL3/include/SDL3/SDL_audio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -434,7 +434,8 @@ typedef struct SDL_AudioSpec * - It can handle incoming data in any variable size. * - It can handle input/output format changes on the fly. * - It can remap audio channels between inputs and outputs. - * - You push data as you have it, and pull it when you need it + * - You push data as you have it, and pull it when you need it; the + * stream will buffer data as needed. * - It can also function as a basic audio data queue even if you just have * sound that needs to pass from one place to another. * - You can hook callbacks up to them when more data is added or requested, @@ -577,6 +578,15 @@ extern SDL_DECLSPEC SDL_AudioDeviceID * SDLCALL SDL_GetAudioRecordingDevices(int /** * Get the human-readable name of a specific audio device. * + * **WARNING**: this function will work with SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK + * and SDL_AUDIO_DEVICE_DEFAULT_RECORDING, returning the current default + * physical devices' names. However, as the default device may change at any + * time, it is likely better to show a generic name to the user, like "System + * default audio device" or perhaps "default [currently %s]". Do not store + * this name to disk to reidentify the device in a later run of the program, + * as the default might change in general, and the string will be the name of + * a specific device and not the abstract system default. + * * \param devid the instance ID of the device to query. * \returns the name of the audio device, or NULL on failure; call * SDL_GetError() for more information. @@ -942,7 +952,10 @@ extern SDL_DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID devid); * Binding a stream to a device will set its output format for playback * devices, and its input format for recording devices, so they match the * device's settings. The caller is welcome to change the other end of the - * stream's format at any time with SDL_SetAudioStreamFormat(). + * stream's format at any time with SDL_SetAudioStreamFormat(). If the other + * end of the stream's format has never been set (the audio stream was created + * with a NULL audio spec), this function will set it to match the device + * end's format. * * \param devid an audio device to bind a stream to. * \param streams an array of audio streams to bind. @@ -1042,8 +1055,15 @@ extern SDL_DECLSPEC SDL_AudioDeviceID SDLCALL SDL_GetAudioStreamDevice(SDL_Audio /** * Create a new audio stream. * - * \param src_spec the format details of the input audio. - * \param dst_spec the format details of the output audio. + * Note that `src_spec` or `dst_spec` may be NULL, but any attempts to + * put or get data from an audio stream will fail until it has valid + * specs assigned to both ends of the stream. Specs can be assigned later + * through SDL_SetAudioStreamFormat(), or binding the stream to an audio + * device (which will set the format of only the input or output, + * depending on what kind of device the stream was bound to). + * + * \param src_spec the format details of the input audio. May be NULL. + * \param dst_spec the format details of the output audio. May be NULL. * \returns a new audio stream on success or NULL on failure; call * SDL_GetError() for more information. * @@ -1064,6 +1084,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. @@ -1074,6 +1105,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. * @@ -1111,6 +1145,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetAudioStreamFormat(SDL_AudioStream *strea * be ignored, but this will not report an error. The other side's format can * be changed. * + * `src_spec` and `dst_spec` may each be NULL; a NULL spec signals not to + * change the current format for that side of the stream. + * * \param stream the stream the format is being changed. * \param src_spec the new format of the audio input; if NULL, it is not * changed. @@ -1319,11 +1356,11 @@ extern SDL_DECLSPEC int * SDLCALL SDL_GetAudioStreamOutputChannelMap(SDL_AudioSt * \threadsafety It is safe to call this function from any thread, as it holds * a stream-specific mutex while running. Don't change the * stream's format to have a different number of channels from a - * a different thread at the same time, though! + * different thread at the same time, though! * * \since This function is available since SDL 3.2.0. * - * \sa SDL_SetAudioStreamInputChannelMap + * \sa SDL_SetAudioStreamOutputChannelMap */ extern SDL_DECLSPEC bool SDLCALL SDL_SetAudioStreamInputChannelMap(SDL_AudioStream *stream, const int *chmap, int count); @@ -2018,85 +2055,6 @@ 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/libs/SDL3/include/SDL3/SDL_begin_code.h b/libs/SDL3/include/SDL3/SDL_begin_code.h index e306a906..4f3fe2d9 100644 --- a/libs/SDL3/include/SDL3/SDL_begin_code.h +++ b/libs/SDL3/include/SDL3/SDL_begin_code.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -298,7 +298,7 @@ * // 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); + * float SDL_ALIGNED(16) position[4]; * SomeOtherStuff other_stuff; * } MyStruct; * @@ -431,7 +431,7 @@ #endif /* SDL_INLINE not defined */ #ifndef SDL_FORCE_INLINE -#ifdef _MSC_VER +#if defined(_MSC_VER) && (_MSC_VER >= 1200) #define SDL_FORCE_INLINE __forceinline #elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) #define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ @@ -469,7 +469,7 @@ #define NULL ((void *)0) #endif #endif /* NULL */ -#endif /* ! macOS - breaks precompiled headers */ +#endif /* __MACH__ */ #ifndef SDL_FALLTHROUGH #if (defined(__cplusplus) && __cplusplus >= 201703L) || \ diff --git a/libs/SDL3/include/SDL3/SDL_bits.h b/libs/SDL3/include/SDL3/SDL_bits.h index 7435ce6d..d3310ab1 100644 --- a/libs/SDL3/include/SDL3/SDL_bits.h +++ b/libs/SDL3/include/SDL3/SDL_bits.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -48,8 +48,7 @@ extern __inline int _SDL_bsr_watcom(Uint32); /** * Get the index of the most significant (set) bit in a 32-bit number. * - * Result is undefined when called with 0. This operation can also be stated - * as "count leading zeroes" and "log base 2". + * This operation can also be stated as "count leading zeroes" and "log base 2". * * Note that this is a forced-inline function in a header, and not a public * API function available in the SDL library (which is to say, the code is diff --git a/libs/SDL3/include/SDL3/SDL_blendmode.h b/libs/SDL3/include/SDL3/SDL_blendmode.h index 8f00cbca..84cf622c 100644 --- a/libs/SDL3/include/SDL3/SDL_blendmode.h +++ b/libs/SDL3/include/SDL3/SDL_blendmode.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_camera.h b/libs/SDL3/include/SDL3/SDL_camera.h index 5f3911fd..14ea647a 100644 --- a/libs/SDL3/include/SDL3/SDL_camera.h +++ b/libs/SDL3/include/SDL3/SDL_camera.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -119,7 +119,7 @@ typedef struct SDL_CameraSpec int width; /**< Frame width */ int height; /**< Frame height */ int framerate_numerator; /**< Frame rate numerator ((num / denom) == FPS, (denom / num) == duration in seconds) */ - int framerate_denominator; /**< Frame rate demoninator ((num / denom) == FPS, (denom / num) == duration in seconds) */ + int framerate_denominator; /**< Frame rate denominator ((num / denom) == FPS, (denom / num) == duration in seconds) */ } SDL_CameraSpec; /** @@ -136,6 +136,20 @@ typedef enum SDL_CameraPosition SDL_CAMERA_POSITION_BACK_FACING } SDL_CameraPosition; +/** + * The current state of a request for camera access. + * + * \since This enum is available since SDL 3.4.0. + * + * \sa SDL_GetCameraPermissionState + */ +typedef enum SDL_CameraPermissionState +{ + SDL_CAMERA_PERMISSION_STATE_DENIED = -1, + SDL_CAMERA_PERMISSION_STATE_PENDING, + SDL_CAMERA_PERMISSION_STATE_APPROVED, +} SDL_CameraPermissionState; + /** * Use this function to get the number of built-in camera drivers. @@ -346,8 +360,9 @@ extern SDL_DECLSPEC SDL_Camera * SDLCALL SDL_OpenCamera(SDL_CameraID instance_id * on others the approval might be implicit and not alert the user at all. * * This function can be used to check the status of that approval. It will - * return 0 if still waiting for user response, 1 if the camera is approved - * for use, and -1 if the user denied access. + * return SDL_CAMERA_PERMISSION_STATE_PENDING if waiting for user response, + * SDL_CAMERA_PERMISSION_STATE_APPROVED if the camera is approved for use, and + * SDL_CAMERA_PERMISSION_STATE_DENIED if the user denied access. * * Instead of polling with this function, you can wait for a * SDL_EVENT_CAMERA_DEVICE_APPROVED (or SDL_EVENT_CAMERA_DEVICE_DENIED) event @@ -358,8 +373,9 @@ extern SDL_DECLSPEC SDL_Camera * SDLCALL SDL_OpenCamera(SDL_CameraID instance_id * SDL_CloseCamera() to dispose of it. * * \param camera the opened camera device to query. - * \returns -1 if user denied access to the camera, 1 if user approved access, - * 0 if no decision has been made yet. + * \returns an SDL_CameraPermissionState value indicating if access is + * granted, or `SDL_CAMERA_PERMISSION_STATE_PENDING` if the decision + * is still pending. * * \threadsafety It is safe to call this function from any thread. * @@ -368,7 +384,7 @@ extern SDL_DECLSPEC SDL_Camera * SDLCALL SDL_OpenCamera(SDL_CameraID instance_id * \sa SDL_OpenCamera * \sa SDL_CloseCamera */ -extern SDL_DECLSPEC int SDLCALL SDL_GetCameraPermissionState(SDL_Camera *camera); +extern SDL_DECLSPEC SDL_CameraPermissionState SDLCALL SDL_GetCameraPermissionState(SDL_Camera *camera); /** * Get the instance ID of an opened camera. diff --git a/libs/SDL3/include/SDL3/SDL_clipboard.h b/libs/SDL3/include/SDL3/SDL_clipboard.h index 0d3cbb49..487f9746 100644 --- a/libs/SDL3/include/SDL3/SDL_clipboard.h +++ b/libs/SDL3/include/SDL3/SDL_clipboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -106,7 +106,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetClipboardText(const char *text); /** * Get UTF-8 text from the clipboard. * - * This functions returns an empty string if there was not enough memory left + * This function returns an empty string if there is not enough memory left * for a copy of the clipboard's content. * * \returns the clipboard text on success or an empty string on failure; call @@ -155,7 +155,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetPrimarySelectionText(const char *text); /** * Get UTF-8 text from the primary selection. * - * This functions returns an empty string if there was not enough memory left + * This function returns an empty string if there is not enough memory left * for a copy of the primary selection's content. * * \returns the primary selection text on success or an empty string on @@ -194,15 +194,14 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasPrimarySelectionText(void); * clipboard is cleared or new data is set. The clipboard is automatically * cleared in SDL_Quit(). * - * \param userdata a pointer to provided user data. + * \param userdata a pointer to the provided user data. * \param mime_type the requested mime-type. * \param size a pointer filled in with the length of the returned data. * \returns a pointer to the data for the provided mime-type. Returning NULL - * or setting length to 0 will cause no data to be sent to the - * "receiver". It is up to the receiver to handle this. Essentially - * returning no data is more or less undefined behavior and may cause - * breakage in receiving applications. The returned data will not be - * freed so it needs to be retained and dealt with internally. + * or setting the length to 0 will cause zero length data to be sent + * to the "receiver", which should be able to handle this. The + * returned data will not be freed, so it needs to be retained and + * dealt with internally. * * \since This function is available since SDL 3.2.0. * @@ -211,10 +210,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasPrimarySelectionText(void); typedef const void *(SDLCALL *SDL_ClipboardDataCallback)(void *userdata, const char *mime_type, size_t *size); /** - * Callback function that will be called when the clipboard is cleared, or new - * data is set. + * Callback function that will be called when the clipboard is cleared, or + * when new data is set. * - * \param userdata a pointer to provided user data. + * \param userdata a pointer to the provided user data. * * \since This function is available since SDL 3.2.0. * @@ -231,7 +230,7 @@ typedef void (SDLCALL *SDL_ClipboardCleanupCallback)(void *userdata); * respond with the data for the requested mime-type. * * The size of text data does not include any terminator, and the text does - * not need to be null terminated (e.g. you can directly copy a portion of a + * not need to be null-terminated (e.g., you can directly copy a portion of a * document). * * \param callback a function pointer to the function that provides the @@ -239,7 +238,8 @@ typedef void (SDLCALL *SDL_ClipboardCleanupCallback)(void *userdata); * \param cleanup a function pointer to the function that cleans up the * clipboard data. * \param userdata an opaque pointer that will be forwarded to the callbacks. - * \param mime_types a list of mime-types that are being offered. + * \param mime_types a list of mime-types that are being offered. SDL copies + * the given list. * \param num_mime_types the number of mime-types in the mime_types list. * \returns true on success or false on failure; call SDL_GetError() for more * information. @@ -252,7 +252,7 @@ typedef void (SDLCALL *SDL_ClipboardCleanupCallback)(void *userdata); * \sa SDL_GetClipboardData * \sa SDL_HasClipboardData */ -extern SDL_DECLSPEC bool SDLCALL SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardCleanupCallback cleanup, void *userdata, const char **mime_types, size_t num_mime_types); +extern SDL_DECLSPEC bool SDLCALL SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardCleanupCallback cleanup, void *userdata, const char *const *mime_types, size_t num_mime_types); /** * Clear the clipboard data. @@ -269,10 +269,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetClipboardData(SDL_ClipboardDataCallback extern SDL_DECLSPEC bool SDLCALL SDL_ClearClipboardData(void); /** - * Get the data from clipboard for a given mime type. + * Get the data from the clipboard for a given mime type. * * The size of text data does not include the terminator, but the text is - * guaranteed to be null terminated. + * guaranteed to be null-terminated. * * \param mime_type the mime type to read from the clipboard. * \param size a pointer filled in with the length of the returned data. @@ -292,8 +292,8 @@ extern SDL_DECLSPEC void * SDLCALL SDL_GetClipboardData(const char *mime_type, s /** * Query whether there is data in the clipboard for the provided mime type. * - * \param mime_type the mime type to check for data for. - * \returns true if there exists data in clipboard for the provided mime type, + * \param mime_type the mime type to check for data. + * \returns true if data exists in the clipboard for the provided mime type, * false if it does not. * * \threadsafety This function should only be called on the main thread. @@ -310,7 +310,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasClipboardData(const char *mime_type); * * \param num_mime_types a pointer filled with the number of mime types, may * be NULL. - * \returns a null terminated array of strings with mime types, or NULL on + * \returns a null-terminated array of strings with mime types, or NULL on * failure; call SDL_GetError() for more information. This should be * freed with SDL_free() when it is no longer needed. * diff --git a/libs/SDL3/include/SDL3/SDL_close_code.h b/libs/SDL3/include/SDL3/SDL_close_code.h index da1dea7f..ad9daa6d 100644 --- a/libs/SDL3/include/SDL3/SDL_close_code.h +++ b/libs/SDL3/include/SDL3/SDL_close_code.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_copying.h b/libs/SDL3/include/SDL3/SDL_copying.h index 747bd357..dd9dbc7e 100644 --- a/libs/SDL3/include/SDL3/SDL_copying.h +++ b/libs/SDL3/include/SDL3/SDL_copying.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_cpuinfo.h b/libs/SDL3/include/SDL3/SDL_cpuinfo.h index 1745bd93..5669c237 100644 --- a/libs/SDL3/include/SDL3/SDL_cpuinfo.h +++ b/libs/SDL3/include/SDL3/SDL_cpuinfo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -344,6 +344,27 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetSystemRAM(void); */ extern SDL_DECLSPEC size_t SDLCALL SDL_GetSIMDAlignment(void); +/** + * Report the size of a page of memory. + * + * Different platforms might have different memory page sizes. In current + * times, 4 kilobytes is not unusual, but newer systems are moving to larger + * page sizes, and esoteric platforms might have any unexpected size. + * + * Note that this function can return 0, which means SDL can't determine the + * page size on this platform. It will _not_ set an error string to be + * retrieved with SDL_GetError() in this case! In this case, defaulting to + * 4096 is often a reasonable option. + * + * \returns the size of a single page of memory, in bytes, or 0 if SDL can't + * determine this information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC int SDLCALL SDL_GetSystemPageSize(void); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/libs/SDL3/include/SDL3/SDL_dialog.h b/libs/SDL3/include/SDL3/SDL_dialog.h index ddb9e24d..1e01347b 100644 --- a/libs/SDL3/include/SDL3/SDL_dialog.h +++ b/libs/SDL3/include/SDL3/SDL_dialog.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -139,10 +139,12 @@ typedef void (SDLCALL *SDL_DialogFileCallback)(void *userdata, const char * cons * it will be invoked. * \param window the window that the dialog should be modal for, may be NULL. * Not all platforms support this option. - * \param filters a list of filters, may be NULL. Not all platforms support - * this option, and platforms that do support it may allow the - * user to ignore the filters. If non-NULL, it must remain - * valid at least until the callback is invoked. + * \param filters a list of filters, may be NULL. See the + * [`SDL_DialogFileFilter`](SDL_DialogFileFilter#code-examples) + * documentation for examples]. Not all platforms support this + * option, and platforms that do support it may allow the user + * to ignore the filters. If non-NULL, it must remain valid at + * least until the callback is invoked. * \param nfilters the number of filters. Ignored if filters is NULL. * \param default_location the default folder or file to start the dialog at, * may be NULL. Not all platforms support this option. diff --git a/libs/SDL3/include/SDL3/SDL_dlopennote.h b/libs/SDL3/include/SDL3/SDL_dlopennote.h new file mode 100644 index 00000000..299435a5 --- /dev/null +++ b/libs/SDL3/include/SDL3/SDL_dlopennote.h @@ -0,0 +1,234 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* WIKI CATEGORY: DlopenNotes */ + +/** + * # CategoryDlopenNotes + * + * This header allows you to annotate your code so external tools know about + * dynamic shared library dependencies. + * + * If you determine that your toolchain doesn't support dlopen notes, you can + * disable this feature by defining `SDL_DISABLE_DLOPEN_NOTES`. You can use + * this CMake snippet to check for support: + * + * ```cmake + * include(CheckCSourceCompiles) + * find_package(SDL3 REQUIRED CONFIG COMPONENTS Headers) + * list(APPEND CMAKE_REQUIRED_LIBRARIES SDL3::Headers) + * check_c_source_compiles([==[ + * #include + * SDL_ELF_NOTE_DLOPEN("sdl-video", + * "Support for video through SDL", + * SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + * "libSDL-1.2.so.0", "libSDL-2.0.so.0", "libSDL3.so.0" + * ) + * int main(int argc, char *argv[]) { + * return argc + argv[0][1]; + * } + * ]==] COMPILER_SUPPORTS_SDL_ELF_NOTE_DLOPEN) + * if(NOT COMPILER_SUPPORTS_SDL_ELF_NOTE_DLOPEN) + * add_compile_definitions(-DSDL_DISABLE_DLOPEN_NOTE) + * endif() + * ``` + */ + +#ifndef SDL_dlopennote_h +#define SDL_dlopennote_h + +/** + * Use this macro with SDL_ELF_NOTE_DLOPEN() to note that a dynamic shared + * library dependency is optional. + * + * Optional functionality uses the dependency, the binary will work and the + * dependency is only needed for full-featured installations. + * + * \since This macro is available since SDL 3.4.0. + * + * \sa SDL_ELF_NOTE_DLOPEN + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED + */ +#define SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED "suggested" + +/** + * Use this macro with SDL_ELF_NOTE_DLOPEN() to note that a dynamic shared + * library dependency is recommended. + * + * Important functionality needs the dependency, the binary will work but in + * most cases the dependency should be provided. + * + * \since This macro is available since SDL 3.4.0. + * + * \sa SDL_ELF_NOTE_DLOPEN + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED + */ +#define SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED "recommended" + +/** + * Use this macro with SDL_ELF_NOTE_DLOPEN() to note that a dynamic shared + * library dependency is required. + * + * Core functionality needs the dependency, the binary will not work if it + * cannot be found. + * + * \since This macro is available since SDL 3.4.0. + * + * \sa SDL_ELF_NOTE_DLOPEN + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED + */ +#define SDL_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED "required" + + +#if !defined(SDL_PLATFORM_UNIX) || defined(SDL_PLATFORM_ANDROID) +/* The dlopen note functionality isn't used on this platform */ +#ifndef SDL_DISABLE_DLOPEN_NOTES +#define SDL_DISABLE_DLOPEN_NOTES +#endif +#elif defined(__GNUC__) && (__GNUC__ < 3 || (__GNUC__ == 3 && __GNUC_MINOR__ < 1)) +/* gcc < 3.1 too old */ +#ifndef SDL_DISABLE_DLOPEN_NOTES +#define SDL_DISABLE_DLOPEN_NOTES +#endif +#endif /* SDL_PLATFORM_UNIX || SDL_PLATFORM_ANDROID */ + +#if defined(__ELF__) && !defined(SDL_DISABLE_DLOPEN_NOTES) + +#include + +#define SDL_ELF_NOTE_DLOPEN_VENDOR "FDO" +#define SDL_ELF_NOTE_DLOPEN_TYPE 0x407c0c0aU + +#define SDL_ELF_NOTE_INTERNAL2(json, variable_name) \ + __attribute__((aligned(4), used, section(".note.dlopen"))) \ + static const struct { \ + struct { \ + Uint32 n_namesz; \ + Uint32 n_descsz; \ + Uint32 n_type; \ + } nhdr; \ + char name[4]; \ + __attribute__((aligned(4))) char dlopen_json[sizeof(json)]; \ + } variable_name = { \ + { \ + sizeof(SDL_ELF_NOTE_DLOPEN_VENDOR), \ + sizeof(json), \ + SDL_ELF_NOTE_DLOPEN_TYPE \ + }, \ + SDL_ELF_NOTE_DLOPEN_VENDOR, \ + json \ + } + +#define SDL_ELF_NOTE_INTERNAL(json, variable_name) \ + SDL_ELF_NOTE_INTERNAL2(json, variable_name) + +#define SDL_DLNOTE_JSON_ARRAY1(N1) "[\"" N1 "\"]" +#define SDL_DLNOTE_JSON_ARRAY2(N1,N2) "[\"" N1 "\",\"" N2 "\"]" +#define SDL_DLNOTE_JSON_ARRAY3(N1,N2,N3) "[\"" N1 "\",\"" N2 "\",\"" N3 "\"]" +#define SDL_DLNOTE_JSON_ARRAY4(N1,N2,N3,N4) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\"]" +#define SDL_DLNOTE_JSON_ARRAY5(N1,N2,N3,N4,N5) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\"]" +#define SDL_DLNOTE_JSON_ARRAY6(N1,N2,N3,N4,N5,N6) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\",\"" N6 "\"]" +#define SDL_DLNOTE_JSON_ARRAY7(N1,N2,N3,N4,N5,N6,N7) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\",\"" N6 "\",\"" N7 "\"]" +#define SDL_DLNOTE_JSON_ARRAY8(N1,N2,N3,N4,N5,N6,N7,N8) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\",\"" N6 "\",\"" N7 "\",\"" N8 "\"]" +#define SDL_DLNOTE_JSON_ARRAY_GET(N1,N2,N3,N4,N5,N6,N7,N8,NAME,...) NAME +#define SDL_DLNOTE_JSON_ARRAY(...) \ + SDL_DLNOTE_JSON_ARRAY_GET( \ + __VA_ARGS__, \ + SDL_DLNOTE_JSON_ARRAY8, \ + SDL_DLNOTE_JSON_ARRAY7, \ + SDL_DLNOTE_JSON_ARRAY6, \ + SDL_DLNOTE_JSON_ARRAY5, \ + SDL_DLNOTE_JSON_ARRAY4, \ + SDL_DLNOTE_JSON_ARRAY3, \ + SDL_DLNOTE_JSON_ARRAY2, \ + SDL_DLNOTE_JSON_ARRAY1 \ + )(__VA_ARGS__) + +/* Create "unique" variable name using __LINE__, + * so creating multiple elf notes on the same line is not supported + */ +#define SDL_DLNOTE_JOIN2(A,B) A##B +#define SDL_DLNOTE_JOIN(A,B) SDL_DLNOTE_JOIN2(A,B) +#define SDL_DLNOTE_UNIQUE_NAME SDL_DLNOTE_JOIN(s_SDL_dlopen_note_, __LINE__) + +/** + * Add a note that your application has dynamic shared library dependencies. + * + * You can do this by adding the following to the global scope: + * + * ```c + * SDL_ELF_NOTE_DLOPEN( + * "png", + * "Support for loading PNG images using libpng (required for APNG)", + * SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + * "libpng12.so.0" + * ) + * ``` + * + * A trailing semicolon is not needed. + * + * Or if you support multiple versions of a library, you can list them: + * + * ```c + * // Our app supports SDL1, SDL2, and SDL3 by dynamically loading them + * SDL_ELF_NOTE_DLOPEN( + * "SDL", + * "Create windows through SDL video backend", + * SDL_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED + * "libSDL-1.2.so.0", "libSDL2-2.0.so.0", "libSDL3.so.0" + * ) + * ``` + * + * This macro is not available for compilers that do not support variadic + * macro's. + * + * \since This macro is available since SDL 3.4.0. + * + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED + * \sa SDL_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED + */ +#define SDL_ELF_NOTE_DLOPEN(feature, description, priority, ...) \ + SDL_ELF_NOTE_INTERNAL( \ + "[{\"feature\":\"" feature \ + "\",\"description\":\"" description \ + "\",\"priority\":\"" priority \ + "\",\"soname\":" SDL_DLNOTE_JSON_ARRAY(__VA_ARGS__) "}]", \ + SDL_DLNOTE_UNIQUE_NAME); + +#elif defined(__GNUC__) && __GNUC__ < 3 + +#define SDL_ELF_NOTE_DLOPEN(args...) + +#elif defined(_MSC_VER) && _MSC_VER < 1400 + +/* Variadic macros are not supported */ + +#else + +#define SDL_ELF_NOTE_DLOPEN(...) + +#endif + +#endif /* SDL_dlopennote_h */ diff --git a/libs/SDL3/include/SDL3/SDL_egl.h b/libs/SDL3/include/SDL3/SDL_egl.h index 65d4e967..1371c4d0 100644 --- a/libs/SDL3/include/SDL3/SDL_egl.h +++ b/libs/SDL3/include/SDL3/SDL_egl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_endian.h b/libs/SDL3/include/SDL3/SDL_endian.h index a34e9d4b..69f097ed 100644 --- a/libs/SDL3/include/SDL3/SDL_endian.h +++ b/libs/SDL3/include/SDL3/SDL_endian.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -46,7 +46,7 @@ #if defined(_MSC_VER) && (_MSC_VER >= 1400) /* As of Clang 11, '_m_prefetchw' is conflicting with the winnt.h's version, so we define the needed '_m_prefetch' here as a pseudo-header, until the issue is fixed. */ -#ifdef __clang__ +#if defined(__clang__) && !SDL_HAS_BUILTIN(_m_prefetch) #ifndef __PRFCHWINTRIN_H #define __PRFCHWINTRIN_H static __inline__ void __attribute__((__always_inline__, __nodebug__)) @@ -128,7 +128,7 @@ _m_prefetch(void *__P) * \sa SDL_BIG_ENDIAN */ #define SDL_BYTEORDER SDL_LIL_ENDIAN___or_maybe___SDL_BIG_ENDIAN -#elif defined(SDL_PLATFORM_LINUX) +#elif defined(SDL_PLATFORM_LINUX) || defined(__GLIBC__) #include #define SDL_BYTEORDER __BYTE_ORDER #elif defined(SDL_PLATFORM_SOLARIS) @@ -208,7 +208,7 @@ _m_prefetch(void *__P) #else /* By default, assume that floats words follow the memory system mode. */ #define SDL_FLOATWORDORDER SDL_BYTEORDER -#endif /* __FLOAT_WORD_ORDER__ */ +#endif /* SDL_WIKI_DOCUMENTATION_SECTION */ #endif /* !SDL_FLOATWORDORDER */ @@ -252,7 +252,7 @@ SDL_FORCE_INLINE Uint16 SDL_Swap16(Uint16 x) #elif defined(__x86_64__) SDL_FORCE_INLINE Uint16 SDL_Swap16(Uint16 x) { - __asm__("xchgb %b0,%h0": "=Q"(x):"0"(x)); + __asm__("xchgb %b0,%h0": "=abcd"(x):"0"(x)); return x; } #elif (defined(__powerpc__) || defined(__ppc__)) @@ -486,7 +486,7 @@ SDL_FORCE_INLINE Uint32 SDL_Swap32(Uint32 x) { return x_but_byteswapped; } * * \since This function is available since SDL 3.2.0. */ -SDL_FORCE_INLINE Uint32 SDL_Swap64(Uint64 x) { return x_but_byteswapped; } +SDL_FORCE_INLINE Uint64 SDL_Swap64(Uint64 x) { return x_but_byteswapped; } /** * Swap a 16-bit value from littleendian to native byte order. diff --git a/libs/SDL3/include/SDL3/SDL_error.h b/libs/SDL3/include/SDL3/SDL_error.h index 934967c1..3453818c 100644 --- a/libs/SDL3/include/SDL3/SDL_error.h +++ b/libs/SDL3/include/SDL3/SDL_error.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_events.h b/libs/SDL3/include/SDL3/SDL_events.h index 353ecd3e..312623e6 100644 --- a/libs/SDL3/include/SDL3/SDL_events.h +++ b/libs/SDL3/include/SDL3/SDL_events.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -30,7 +30,7 @@ * coming and going, the system changing in some way, etc. * * An app generally takes a moment, perhaps at the start of a new frame, to - * examine any events that have occured since the last time and process or + * examine any events that have occurred since the last time and process or * ignore them. This is generally done by calling SDL_PollEvent() in a loop * until it returns false (or, if using the main callbacks, events are * provided one at a time in calls to SDL_AppEvent() before the next call to @@ -127,8 +127,9 @@ typedef enum SDL_EventType SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED, /**< Display has changed desktop mode */ SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED, /**< Display has changed current mode */ SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED, /**< Display has changed content scale */ + SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED, /**< Display has changed usable bounds */ SDL_EVENT_DISPLAY_FIRST = SDL_EVENT_DISPLAY_ORIENTATION, - SDL_EVENT_DISPLAY_LAST = SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED, + SDL_EVENT_DISPLAY_LAST = SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED, /* Window events */ /* 0x200 was SDL_WINDOWEVENT, reserve the number for sdl2-compat */ @@ -175,6 +176,8 @@ typedef enum SDL_EventType SDL_EVENT_KEYBOARD_ADDED, /**< A new keyboard has been inserted into the system */ SDL_EVENT_KEYBOARD_REMOVED, /**< A keyboard has been removed */ SDL_EVENT_TEXT_EDITING_CANDIDATES, /**< Keyboard text editing candidates */ + SDL_EVENT_SCREEN_KEYBOARD_SHOWN, /**< The on-screen keyboard has been shown */ + SDL_EVENT_SCREEN_KEYBOARD_HIDDEN, /**< The on-screen keyboard has been hidden */ /* Mouse events */ SDL_EVENT_MOUSE_MOTION = 0x400, /**< Mouse moved */ @@ -215,10 +218,15 @@ typedef enum SDL_EventType SDL_EVENT_FINGER_MOTION, SDL_EVENT_FINGER_CANCELED, + /* Pinch events */ + SDL_EVENT_PINCH_BEGIN = 0x710, /**< Pinch gesture started */ + SDL_EVENT_PINCH_UPDATE, /**< Pinch gesture updated */ + SDL_EVENT_PINCH_END, /**< Pinch gesture ended */ + /* 0x800, 0x801, and 0x802 were the Gesture events from SDL2. Do not reuse these values! sdl2-compat needs them! */ /* Clipboard events */ - SDL_EVENT_CLIPBOARD_UPDATE = 0x900, /**< The clipboard or primary selection changed */ + SDL_EVENT_CLIPBOARD_UPDATE = 0x900, /**< The clipboard changed */ /* Drag and drop events */ SDL_EVENT_DROP_FILE = 0x1000, /**< The system requests a file open */ @@ -299,7 +307,7 @@ typedef struct SDL_CommonEvent */ typedef struct SDL_DisplayEvent { - SDL_EventType type; /**< SDL_DISPLAYEVENT_* */ + SDL_EventType type; /**< SDL_EVENT_DISPLAY_* */ Uint32 reserved; Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ SDL_DisplayID displayID;/**< The associated display */ @@ -448,7 +456,7 @@ typedef struct SDL_MouseMotionEvent Uint32 reserved; Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ SDL_WindowID windowID; /**< The window with mouse focus, if any */ - SDL_MouseID which; /**< The mouse instance id in relative mode, SDL_TOUCH_MOUSEID for touch events, or 0 */ + SDL_MouseID which; /**< The mouse instance id in relative mode, SDL_TOUCH_MOUSEID for touch events, SDL_PEN_MOUSEID for pen events, or 0 */ SDL_MouseButtonFlags state; /**< The current button state */ float x; /**< X coordinate, relative to window */ float y; /**< Y coordinate, relative to window */ @@ -705,6 +713,10 @@ typedef struct SDL_GamepadSensorEvent /** * Audio device event structure (event.adevice.*) * + * Note that SDL will send a SDL_EVENT_AUDIO_DEVICE_ADDED event for every + * device it discovers during initialization. After that, this event will only + * arrive when a device is hotplugged during the program's run. + * * \since This struct is available since SDL 3.2.0. */ typedef struct SDL_AudioDeviceEvent @@ -781,6 +793,18 @@ typedef struct SDL_TouchFingerEvent SDL_WindowID windowID; /**< The window underneath the finger, if any */ } SDL_TouchFingerEvent; +/** + * Pinch event structure (event.pinch.*) + */ +typedef struct SDL_PinchFingerEvent +{ + SDL_EventType type; /**< ::SDL_EVENT_PINCH_BEGIN or ::SDL_EVENT_PINCH_UPDATE or ::SDL_EVENT_PINCH_END */ + Uint32 reserved; + Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ + float scale; /**< The scale change since the last SDL_EVENT_PINCH_UPDATE. Scale < 1 is "zoom out". Scale > 1 is "zoom in". */ + SDL_WindowID windowID; /**< The window underneath the finger, if any */ +} SDL_PinchFingerEvent; + /** * Pressure-sensitive pen proximity event structure (event.pproximity.*) * @@ -794,6 +818,9 @@ typedef struct SDL_TouchFingerEvent * is there." The pen touching and lifting off from the tablet while not * leaving the area are handled by SDL_EVENT_PEN_DOWN and SDL_EVENT_PEN_UP. * + * Not all platforms have a window associated with the pen during proximity + * events. Some wait until motion/button/etc events to offer this info. + * * \since This struct is available since SDL 3.2.0. */ typedef struct SDL_PenProximityEvent @@ -968,7 +995,7 @@ typedef struct SDL_QuitEvent */ typedef struct SDL_UserEvent { - Uint32 type; /**< SDL_EVENT_USER through SDL_EVENT_LAST-1, Uint32 because these are not in the SDL_EventType enumeration */ + Uint32 type; /**< SDL_EVENT_USER through SDL_EVENT_LAST, Uint32 because these are not in the SDL_EventType enumeration */ Uint32 reserved; Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ SDL_WindowID windowID; /**< The associated window if any */ @@ -1018,6 +1045,7 @@ typedef union SDL_Event SDL_QuitEvent quit; /**< Quit request event data */ SDL_UserEvent user; /**< Custom event data */ SDL_TouchFingerEvent tfinger; /**< Touch finger event data */ + SDL_PinchFingerEvent pinch; /**< Pinch event data */ SDL_PenProximityEvent pproximity; /**< Pen proximity event data */ SDL_PenTouchEvent ptouch; /**< Pen tip touching event data */ SDL_PenMotionEvent pmotion; /**< Pen motion event data */ @@ -1044,7 +1072,7 @@ typedef union SDL_Event } SDL_Event; /* Make sure we haven't broken binary compatibility */ -SDL_COMPILE_TIME_ASSERT(SDL_Event, sizeof(SDL_Event) == sizeof(((SDL_Event *)NULL)->padding)); +SDL_COMPILE_TIME_ASSERT(SDL_Event, sizeof(SDL_Event) == sizeof((SDL_static_cast(SDL_Event *, NULL))->padding)); /* Function prototypes */ @@ -1228,15 +1256,13 @@ extern SDL_DECLSPEC void SDLCALL SDL_FlushEvents(Uint32 minType, Uint32 maxType) * Poll for currently pending events. * * If `event` is not NULL, the next event is removed from the queue and stored - * in the SDL_Event structure pointed to by `event`. The 1 returned refers to - * this event, immediately stored in the SDL Event structure -- not an event - * to follow. + * in the SDL_Event structure pointed to by `event`. * - * If `event` is NULL, it simply returns 1 if there is an event in the queue, - * but will not remove it from the queue. + * If `event` is NULL, it simply returns true if there is an event in the + * queue, but will not remove it from the queue. * * As this function may implicitly call SDL_PumpEvents(), you can only call - * this function in the thread that set the video mode. + * this function in the thread that initialized the video subsystem. * * SDL_PollEvent() is the favored way of receiving system events since it can * be done from the main loop and does not suspend the main loop while waiting @@ -1256,6 +1282,13 @@ extern SDL_DECLSPEC void SDLCALL SDL_FlushEvents(Uint32 minType, Uint32 maxType) * } * ``` * + * Note that Windows (and possibly other platforms) has a quirk about how it + * handles events while dragging/resizing a window, which can cause this + * function to block for significant amounts of time. Technical explanations + * and solutions are discussed on the wiki: + * + * https://wiki.libsdl.org/SDL3/AppFreezeDuringDrag + * * \param event the SDL_Event structure to be filled with the next event from * the queue, or NULL. * \returns true if this got an event or false if there are none available. @@ -1392,7 +1425,10 @@ typedef bool (SDLCALL *SDL_EventFilter)(void *userdata, SDL_Event *event); * allows selective filtering of dynamically arriving events. * * **WARNING**: Be very careful of what you do in the event filter function, - * as it may run in a different thread! + * as it may run in a different thread! The exception is handling of + * SDL_EVENT_WINDOW_EXPOSED, which is guaranteed to be sent from the OS on the + * main thread and you are expected to redraw your window in response to this + * event. * * On platforms that support it, if the quit event is generated by an * interrupt signal (e.g. pressing Ctrl-C), it will be delivered to the @@ -1405,7 +1441,7 @@ typedef bool (SDLCALL *SDL_EventFilter)(void *userdata, SDL_Event *event); * the event filter, but events pushed onto the queue with SDL_PeepEvents() do * not. * - * \param filter an SDL_EventFilter function to call when an event happens. + * \param filter a function to call when an event happens. * \param userdata a pointer that is passed to `filter`. * * \threadsafety It is safe to call this function from any thread. @@ -1569,7 +1605,7 @@ 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. + * Generate an English description of an event. * * This will fill `buf` with a null-terminated string that might look * something like this: diff --git a/libs/SDL3/include/SDL3/SDL_filesystem.h b/libs/SDL3/include/SDL3/SDL_filesystem.h index 031feaf9..3a077a09 100644 --- a/libs/SDL3/include/SDL3/SDL_filesystem.h +++ b/libs/SDL3/include/SDL3/SDL_filesystem.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -77,6 +77,9 @@ extern "C" { * - `parent`: the containing directory of the bundle. For example: * `/Applications/SDLApp/` * + * **Android Specific Functionality**: This function returns "./", which + * allows filesystem operations to use internal storage and the asset system. + * * **Nintendo 3DS Specific Functionality**: This function returns "romfs" * directory of the application as it is uncommon to store resources outside * the executable. As such it is not a writable directory. @@ -89,6 +92,8 @@ extern "C" { * doesn't implement this functionality, call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetPrefPath @@ -134,6 +139,12 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetBasePath(void); * - ...only use letters, numbers, and spaces. Avoid punctuation like "Game * Name 2: Bad Guy's Revenge!" ... "Game Name 2" is sufficient. * + * Due to historical mistakes, `org` is allowed to be NULL or "". In such + * cases, SDL will omit the org subdirectory, including on platforms where it + * shouldn't, and including on platforms where this would make your app fail + * certification for an app store. New apps should definitely specify a real + * string for `org`. + * * The returned path is guaranteed to end with a path separator ('\\' on * Windows, '/' on most other platforms). * @@ -144,6 +155,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetBasePath(void); * etc.). This should be freed with SDL_free() when it is no longer * needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetBasePath @@ -216,6 +229,8 @@ typedef enum SDL_Folder * \returns either a null-terminated C string containing the full path to the * folder, or NULL if an error happened. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC const char * SDLCALL SDL_GetUserFolder(SDL_Folder folder); @@ -226,9 +241,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetUserFolder(SDL_Folder folder); /** * Types of filesystem entries. * - * Note that there may be other sorts of items on a filesystem: devices, - * symlinks, named pipes, etc. They are currently reported as - * SDL_PATHTYPE_OTHER. + * Note that there may be other sorts of items on a filesystem: devices, named + * pipes, etc. They are currently reported as SDL_PATHTYPE_OTHER. * * \since This enum is available since SDL 3.2.0. * @@ -283,6 +297,8 @@ typedef Uint32 SDL_GlobFlags; * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_CreateDirectory(const char *path); @@ -346,6 +362,8 @@ typedef SDL_EnumerationResult (SDLCALL *SDL_EnumerateDirectoryCallback)(void *us * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback callback, void *userdata); @@ -360,6 +378,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_EnumerateDirectory(const char *path, SDL_En * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_RemovePath(const char *path); @@ -367,7 +387,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RemovePath(const char *path); /** * Rename a file or directory. * - * If the file at `newpath` already exists, it will replaced. + * If the file at `newpath` already exists, it will be replaced. * * Note that this will not copy files across filesystems/drives/volumes, as * that is a much more complicated (and possibly time-consuming) operation. @@ -383,6 +403,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RemovePath(const char *path); * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_RenamePath(const char *oldpath, const char *newpath); @@ -423,6 +445,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenamePath(const char *oldpath, const char * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread, but this + * operation is not atomic, so the app might need to protect + * access to specific paths from other threads if appropriate. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_CopyFile(const char *oldpath, const char *newpath); @@ -430,12 +456,18 @@ extern SDL_DECLSPEC bool SDLCALL SDL_CopyFile(const char *oldpath, const char *n /** * Get information about a filesystem path. * + * Symlinks, on filesystems that support them, are always followed, so you + * will always get information on what the symlink eventually points to, and + * not the symlink itself. + * * \param path the path to query. * \param info a pointer filled in with information about the path, or NULL to * check for the existence of a file. * \returns true on success or false if the file doesn't exist, or another * failure; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetPathInfo(const char *path, SDL_PathInfo *info); @@ -490,6 +522,8 @@ extern SDL_DECLSPEC char ** SDLCALL SDL_GlobDirectory(const char *path, const ch * platform-dependent notation. NULL if there's a problem. This * should be freed with SDL_free() when it is no longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC char * SDLCALL SDL_GetCurrentDirectory(void); diff --git a/libs/SDL3/include/SDL3/SDL_gamepad.h b/libs/SDL3/include/SDL3/SDL_gamepad.h index 460ae987..9c88a770 100644 --- a/libs/SDL3/include/SDL3/SDL_gamepad.h +++ b/libs/SDL3/include/SDL3/SDL_gamepad.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -48,6 +48,9 @@ * SDL_INIT_GAMEPAD flag. This causes SDL to scan the system for gamepads, and * load appropriate drivers. * + * If you're using SDL gamepad support in a Steam game, you must call + * SteamAPI_InitEx() before calling SDL_Init(). + * * If you would like to receive gamepad updates while the application is in * the background, you should set the following hint before calling * SDL_Init(): SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS @@ -164,15 +167,15 @@ typedef enum SDL_GamepadButton SDL_GAMEPAD_BUTTON_DPAD_DOWN, SDL_GAMEPAD_BUTTON_DPAD_LEFT, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, - SDL_GAMEPAD_BUTTON_MISC1, /**< Additional button (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button, Google Stadia capture button) */ - SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1, /**< Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1) */ - SDL_GAMEPAD_BUTTON_LEFT_PADDLE1, /**< Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3) */ - SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2, /**< Lower or secondary paddle, under your right hand (e.g. Xbox Elite paddle P2) */ - SDL_GAMEPAD_BUTTON_LEFT_PADDLE2, /**< Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4) */ + SDL_GAMEPAD_BUTTON_MISC1, /**< Additional button (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Steam Controller QAM button, Amazon Luna microphone button, Google Stadia capture button) */ + SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1, /**< Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1, DualSense Edge RB button, Right Joy-Con SR button, Steam Controller R4 button) */ + SDL_GAMEPAD_BUTTON_LEFT_PADDLE1, /**< Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3, DualSense Edge LB button, Left Joy-Con SL button, Steam Controller L4 button) */ + SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2, /**< Lower or secondary paddle, under your right hand (e.g. Xbox Elite paddle P2, DualSense Edge right Fn button, Right Joy-Con SL button, Steam Controller R5 button) */ + SDL_GAMEPAD_BUTTON_LEFT_PADDLE2, /**< Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4, DualSense Edge left Fn button, Left Joy-Con SR button, Steam Controller L5 button) */ SDL_GAMEPAD_BUTTON_TOUCHPAD, /**< PS4/PS5 touchpad button */ SDL_GAMEPAD_BUTTON_MISC2, /**< Additional button */ - SDL_GAMEPAD_BUTTON_MISC3, /**< Additional button */ - SDL_GAMEPAD_BUTTON_MISC4, /**< Additional button */ + SDL_GAMEPAD_BUTTON_MISC3, /**< Additional button (e.g. Nintendo GameCube left trigger click) */ + SDL_GAMEPAD_BUTTON_MISC4, /**< Additional button (e.g. Nintendo GameCube right trigger click) */ SDL_GAMEPAD_BUTTON_MISC5, /**< Additional button */ SDL_GAMEPAD_BUTTON_MISC6, /**< Additional button */ SDL_GAMEPAD_BUTTON_COUNT @@ -424,6 +427,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_AddGamepadMappingsFromFile(const char *file) * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_ReloadGamepadMappings(void); @@ -438,6 +443,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReloadGamepadMappings(void); * single allocation that should be freed with SDL_free() when it is * no longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC char ** SDLCALL SDL_GetGamepadMappings(int *count); @@ -450,6 +457,8 @@ extern SDL_DECLSPEC char ** SDLCALL SDL_GetGamepadMappings(int *count); * information. This should be freed with SDL_free() when it is no * longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickGUIDForID @@ -467,6 +476,8 @@ extern SDL_DECLSPEC char * SDLCALL SDL_GetGamepadMappingForGUID(SDL_GUID guid); * available; call SDL_GetError() for more information. This should * be freed with SDL_free() when it is no longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_AddGamepadMapping @@ -487,6 +498,8 @@ extern SDL_DECLSPEC char * SDLCALL SDL_GetGamepadMapping(SDL_Gamepad *gamepad); * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_AddGamepadMapping @@ -499,6 +512,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetGamepadMapping(SDL_JoystickID instance_i * * \returns true if a gamepad is connected, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepads @@ -514,6 +529,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasGamepad(void); * call SDL_GetError() for more information. This should be freed * with SDL_free() when it is no longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_HasGamepad @@ -528,6 +545,8 @@ extern SDL_DECLSPEC SDL_JoystickID * SDLCALL SDL_GetGamepads(int *count); * \returns true if the given joystick is supported by the gamepad interface, * false if it isn't or it's an invalid index. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoysticks @@ -544,6 +563,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_IsGamepad(SDL_JoystickID instance_id); * \returns the name of the selected gamepad. If no name can be found, this * function returns NULL; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadName @@ -560,6 +581,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadNameForID(SDL_JoystickID * \returns the path of the selected gamepad. If no path can be found, this * function returns NULL; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadPath @@ -575,6 +598,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadPathForID(SDL_JoystickID * \param instance_id the joystick instance ID. * \returns the player index of a gamepad, or -1 if it's not available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadPlayerIndex @@ -591,6 +616,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetGamepadPlayerIndexForID(SDL_JoystickID in * \returns the GUID of the selected gamepad. If called on an invalid index, * this function returns a zero GUID. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GUIDToString @@ -608,6 +635,8 @@ extern SDL_DECLSPEC SDL_GUID SDLCALL SDL_GetGamepadGUIDForID(SDL_JoystickID inst * \returns the USB vendor ID of the selected gamepad. If called on an invalid * index, this function returns zero. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadVendor @@ -625,6 +654,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadVendorForID(SDL_JoystickID inst * \returns the USB product ID of the selected gamepad. If called on an * invalid index, this function returns zero. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadProduct @@ -642,6 +673,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadProductForID(SDL_JoystickID ins * \returns the product version of the selected gamepad. If called on an * invalid index, this function returns zero. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadProductVersion @@ -657,6 +690,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadProductVersionForID(SDL_Joystic * \param instance_id the joystick instance ID. * \returns the gamepad type. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadType @@ -673,6 +708,8 @@ extern SDL_DECLSPEC SDL_GamepadType SDLCALL SDL_GetGamepadTypeForID(SDL_Joystick * \param instance_id the joystick instance ID. * \returns the gamepad type. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadTypeForID @@ -690,6 +727,8 @@ extern SDL_DECLSPEC SDL_GamepadType SDLCALL SDL_GetRealGamepadTypeForID(SDL_Joys * \returns the mapping string. Returns NULL if no mapping is available. This * should be freed with SDL_free() when it is no longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepads @@ -704,6 +743,8 @@ extern SDL_DECLSPEC char * SDLCALL SDL_GetGamepadMappingForID(SDL_JoystickID ins * \returns a gamepad identifier or NULL if an error occurred; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CloseGamepad @@ -719,6 +760,8 @@ extern SDL_DECLSPEC SDL_Gamepad * SDLCALL SDL_OpenGamepad(SDL_JoystickID instanc * \returns an SDL_Gamepad on success or NULL on failure or if it hasn't been * opened yet; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_Gamepad * SDLCALL SDL_GetGamepadFromID(SDL_JoystickID instance_id); @@ -729,6 +772,8 @@ extern SDL_DECLSPEC SDL_Gamepad * SDLCALL SDL_GetGamepadFromID(SDL_JoystickID in * \param player_index the player index, which different from the instance ID. * \returns the SDL_Gamepad associated with a player index. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadPlayerIndex @@ -759,6 +804,8 @@ extern SDL_DECLSPEC SDL_Gamepad * SDLCALL SDL_GetGamepadFromPlayerIndex(int play * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetGamepadProperties(SDL_Gamepad *gamepad); @@ -777,6 +824,8 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetGamepadProperties(SDL_Gamepa * \returns the instance ID of the specified gamepad on success or 0 on * failure; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_JoystickID SDLCALL SDL_GetGamepadID(SDL_Gamepad *gamepad); @@ -789,6 +838,8 @@ extern SDL_DECLSPEC SDL_JoystickID SDLCALL SDL_GetGamepadID(SDL_Gamepad *gamepad * \returns the implementation dependent name for the gamepad, or NULL if * there is no name or the identifier passed is invalid. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadNameForID @@ -803,6 +854,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadName(SDL_Gamepad *gamepad * \returns the implementation dependent path for the gamepad, or NULL if * there is no path or the identifier passed is invalid. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadPathForID @@ -816,6 +869,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadPath(SDL_Gamepad *gamepad * \returns the gamepad type, or SDL_GAMEPAD_TYPE_UNKNOWN if it's not * available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadTypeForID @@ -829,6 +884,8 @@ extern SDL_DECLSPEC SDL_GamepadType SDLCALL SDL_GetGamepadType(SDL_Gamepad *game * \returns the gamepad type, or SDL_GAMEPAD_TYPE_UNKNOWN if it's not * available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetRealGamepadTypeForID @@ -843,6 +900,8 @@ extern SDL_DECLSPEC SDL_GamepadType SDLCALL SDL_GetRealGamepadType(SDL_Gamepad * * \param gamepad the gamepad object to query. * \returns the player index for gamepad, or -1 if it's not available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetGamepadPlayerIndex @@ -858,6 +917,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetGamepadPlayerIndex(SDL_Gamepad *gamepad); * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadPlayerIndex @@ -872,6 +933,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetGamepadPlayerIndex(SDL_Gamepad *gamepad, * \param gamepad the gamepad object to query. * \returns the USB vendor ID, or zero if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadVendorForID @@ -886,6 +949,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadVendor(SDL_Gamepad *gamepad); * \param gamepad the gamepad object to query. * \returns the USB product ID, or zero if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadProductForID @@ -900,6 +965,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadProduct(SDL_Gamepad *gamepad); * \param gamepad the gamepad object to query. * \returns the USB product version, or zero if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadProductVersionForID @@ -914,6 +981,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadProductVersion(SDL_Gamepad *gam * \param gamepad the gamepad object to query. * \returns the gamepad firmware version, or zero if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadFirmwareVersion(SDL_Gamepad *gamepad); @@ -926,6 +995,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadFirmwareVersion(SDL_Gamepad *ga * \param gamepad the gamepad object to query. * \returns the serial number, or NULL if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadSerial(SDL_Gamepad *gamepad); @@ -939,6 +1010,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadSerial(SDL_Gamepad *gamep * \param gamepad the gamepad object to query. * \returns the gamepad handle, or 0 if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC Uint64 SDLCALL SDL_GetGamepadSteamHandle(SDL_Gamepad *gamepad); @@ -951,6 +1024,8 @@ extern SDL_DECLSPEC Uint64 SDLCALL SDL_GetGamepadSteamHandle(SDL_Gamepad *gamepa * `SDL_JOYSTICK_CONNECTION_INVALID` on failure; call SDL_GetError() * for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_JoystickConnectionState SDLCALL SDL_GetGamepadConnectionState(SDL_Gamepad *gamepad); @@ -971,6 +1046,8 @@ extern SDL_DECLSPEC SDL_JoystickConnectionState SDLCALL SDL_GetGamepadConnection * battery. * \returns the current battery state. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_PowerState SDLCALL SDL_GetGamepadPowerInfo(SDL_Gamepad *gamepad, int *percent); @@ -983,6 +1060,8 @@ extern SDL_DECLSPEC SDL_PowerState SDLCALL SDL_GetGamepadPowerInfo(SDL_Gamepad * * \returns true if the gamepad has been opened and is currently connected, or * false if not. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadConnected(SDL_Gamepad *gamepad); @@ -1003,6 +1082,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadConnected(SDL_Gamepad *gamepad); * \returns an SDL_Joystick object, or NULL on failure; call SDL_GetError() * for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_Joystick * SDLCALL SDL_GetGamepadJoystick(SDL_Gamepad *gamepad); @@ -1015,6 +1096,8 @@ extern SDL_DECLSPEC SDL_Joystick * SDLCALL SDL_GetGamepadJoystick(SDL_Gamepad *g * * \param enabled whether to process gamepad events or not. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GamepadEventsEnabled @@ -1030,6 +1113,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetGamepadEventsEnabled(bool enabled); * * \returns true if gamepad events are being processed, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetGamepadEventsEnabled @@ -1046,6 +1131,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadEventsEnabled(void); * single allocation that should be freed with SDL_free() when it is * no longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_GamepadBinding ** SDLCALL SDL_GetGamepadBindings(SDL_Gamepad *gamepad, int *count); @@ -1057,6 +1144,8 @@ extern SDL_DECLSPEC SDL_GamepadBinding ** SDLCALL SDL_GetGamepadBindings(SDL_Gam * enabled. Under such circumstances, it will not be necessary to call this * function. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_UpdateGamepads(void); @@ -1073,6 +1162,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UpdateGamepads(void); * \returns the SDL_GamepadType enum corresponding to the input string, or * `SDL_GAMEPAD_TYPE_UNKNOWN` if no match was found. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadStringForType @@ -1087,6 +1178,8 @@ extern SDL_DECLSPEC SDL_GamepadType SDLCALL SDL_GetGamepadTypeFromString(const c * specified. The string returned is of the format used by * SDL_Gamepad mapping strings. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadTypeFromString @@ -1109,6 +1202,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadStringForType(SDL_Gamepad * \returns the SDL_GamepadAxis enum corresponding to the input string, or * `SDL_GAMEPAD_AXIS_INVALID` if no match was found. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadStringForAxis @@ -1123,6 +1218,8 @@ extern SDL_DECLSPEC SDL_GamepadAxis SDLCALL SDL_GetGamepadAxisFromString(const c * specified. The string returned is of the format used by * SDL_Gamepad mapping strings. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadAxisFromString @@ -1139,6 +1236,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadStringForAxis(SDL_Gamepad * \param axis an axis enum value (an SDL_GamepadAxis value). * \returns true if the gamepad has this axis, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GamepadHasButton @@ -1165,6 +1264,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadHasAxis(SDL_Gamepad *gamepad, SDL_Ga * \param axis an axis index (one of the SDL_GamepadAxis values). * \returns axis state. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GamepadHasAxis @@ -1180,10 +1281,12 @@ extern SDL_DECLSPEC Sint16 SDLCALL SDL_GetGamepadAxis(SDL_Gamepad *gamepad, SDL_ * You do not normally need to call this function unless you are parsing * SDL_Gamepad mappings in your own code. * - * \param str string representing a SDL_Gamepad axis. + * \param str string representing a SDL_Gamepad button. * \returns the SDL_GamepadButton enum corresponding to the input string, or * `SDL_GAMEPAD_BUTTON_INVALID` if no match was found. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadStringForButton @@ -1198,6 +1301,8 @@ extern SDL_DECLSPEC SDL_GamepadButton SDLCALL SDL_GetGamepadButtonFromString(con * specified. The string returned is of the format used by * SDL_Gamepad mapping strings. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadButtonFromString @@ -1214,6 +1319,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadStringForButton(SDL_Gamep * \param button a button enum value (an SDL_GamepadButton value). * \returns true if the gamepad has this button, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GamepadHasAxis @@ -1227,6 +1334,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadHasButton(SDL_Gamepad *gamepad, SDL_ * \param button a button index (one of the SDL_GamepadButton values). * \returns true if the button is pressed, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GamepadHasButton @@ -1241,6 +1350,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetGamepadButton(SDL_Gamepad *gamepad, SDL_ * \param button a button index (one of the SDL_GamepadButton values). * \returns the SDL_GamepadButtonLabel enum corresponding to the button label. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadButtonLabel @@ -1254,6 +1365,8 @@ extern SDL_DECLSPEC SDL_GamepadButtonLabel SDLCALL SDL_GetGamepadButtonLabelForT * \param button a button index (one of the SDL_GamepadButton values). * \returns the SDL_GamepadButtonLabel enum corresponding to the button label. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadButtonLabelForType @@ -1266,6 +1379,8 @@ extern SDL_DECLSPEC SDL_GamepadButtonLabel SDLCALL SDL_GetGamepadButtonLabel(SDL * \param gamepad a gamepad. * \returns number of touchpads. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetNumGamepadTouchpadFingers @@ -1280,6 +1395,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumGamepadTouchpads(SDL_Gamepad *gamepad) * \param touchpad a touchpad. * \returns number of supported simultaneous fingers. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadTouchpadFinger @@ -1303,6 +1420,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumGamepadTouchpadFingers(SDL_Gamepad *ga * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetNumGamepadTouchpadFingers @@ -1316,6 +1435,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetGamepadTouchpadFinger(SDL_Gamepad *gamep * \param type the type of sensor to query. * \returns true if the sensor exists, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadSensorData @@ -1333,6 +1454,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadHasSensor(SDL_Gamepad *gamepad, SDL_ * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GamepadHasSensor @@ -1347,6 +1470,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetGamepadSensorEnabled(SDL_Gamepad *gamepa * \param type the type of sensor to query. * \returns true if the sensor is enabled, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetGamepadSensorEnabled @@ -1360,6 +1485,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadSensorEnabled(SDL_Gamepad *gamepad, * \param type the type of sensor to query. * \returns the data rate, or 0.0f if the data rate is not available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC float SDLCALL SDL_GetGamepadSensorDataRate(SDL_Gamepad *gamepad, SDL_SensorType type); @@ -1368,7 +1495,7 @@ extern SDL_DECLSPEC float SDLCALL SDL_GetGamepadSensorDataRate(SDL_Gamepad *game * Get the current state of a gamepad sensor. * * The number of values and interpretation of the data is sensor dependent. - * See SDL_sensor.h for the details for each type of sensor. + * See the remarks in SDL_SensorType for details for each type of sensor. * * \param gamepad the gamepad to query. * \param type the type of sensor to query. @@ -1377,6 +1504,8 @@ extern SDL_DECLSPEC float SDLCALL SDL_GetGamepadSensorDataRate(SDL_Gamepad *game * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetGamepadSensorData(SDL_Gamepad *gamepad, SDL_SensorType type, float *data, int num_values); @@ -1399,6 +1528,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetGamepadSensorData(SDL_Gamepad *gamepad, * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_RumbleGamepad(SDL_Gamepad *gamepad, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms); @@ -1425,6 +1556,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RumbleGamepad(SDL_Gamepad *gamepad, Uint16 * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_RumbleGamepad @@ -1447,6 +1580,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RumbleGamepadTriggers(SDL_Gamepad *gamepad, * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_SetGamepadLED(SDL_Gamepad *gamepad, Uint8 red, Uint8 green, Uint8 blue); @@ -1460,6 +1595,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetGamepadLED(SDL_Gamepad *gamepad, Uint8 r * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_SendGamepadEffect(SDL_Gamepad *gamepad, const void *data, int size); @@ -1470,6 +1607,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SendGamepadEffect(SDL_Gamepad *gamepad, con * \param gamepad a gamepad identifier previously returned by * SDL_OpenGamepad(). * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_OpenGamepad @@ -1484,6 +1623,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_CloseGamepad(SDL_Gamepad *gamepad); * \param button a button on the gamepad. * \returns the sfSymbolsName or NULL if the name can't be found. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadAppleSFSymbolsNameForAxis @@ -1497,6 +1638,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGamepadAppleSFSymbolsNameForButt * \param axis an axis on the gamepad. * \returns the sfSymbolsName or NULL if the name can't be found. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetGamepadAppleSFSymbolsNameForButton diff --git a/libs/SDL3/include/SDL3/SDL_gpu.h b/libs/SDL3/include/SDL3/SDL_gpu.h index b3ff2022..dfe5994a 100644 --- a/libs/SDL3/include/SDL3/SDL_gpu.h +++ b/libs/SDL3/include/SDL3/SDL_gpu.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -230,12 +230,28 @@ * - `drawIndirectFirstInstance` * - `sampleRateShading` * + * You can remove some of these requirements to increase compatibility with + * Android devices by using these properties when creating the GPU device with + * SDL_CreateGPUDeviceWithProperties(): + * + * - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN + * - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN + * - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN + * - SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN + * * ### D3D12 * * SDL driver name: "direct3d12" * * Supported on Windows 10 or newer, Xbox One (GDK), and Xbox Series X|S - * (GDK). Requires a GPU that supports DirectX 12 Feature Level 11_1. + * (GDK). Requires a GPU that supports DirectX 12 Feature Level 11_0 and + * Resource Binding Tier 2 or above. + * + * You can remove the Tier 2 resource binding requirement to support Intel + * Haswell and Broadwell GPUs by using this property when creating the GPU + * device with SDL_CreateGPUDeviceWithProperties(): + * + * - SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN * * ### Metal * @@ -1158,7 +1174,7 @@ typedef enum SDL_GPUCompareOp SDL_GPU_COMPAREOP_LESS_OR_EQUAL, /**< The comparison evaluates reference <= test. */ SDL_GPU_COMPAREOP_GREATER, /**< The comparison evaluates reference > test. */ SDL_GPU_COMPAREOP_NOT_EQUAL, /**< The comparison evaluates reference != test. */ - SDL_GPU_COMPAREOP_GREATER_OR_EQUAL, /**< The comparison evalutes reference >= test. */ + SDL_GPU_COMPAREOP_GREATER_OR_EQUAL, /**< The comparison evaluates reference >= test. */ SDL_GPU_COMPAREOP_ALWAYS /**< The comparison always evaluates true. */ } SDL_GPUCompareOp; @@ -1382,11 +1398,12 @@ typedef struct SDL_GPUViewport * 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. + * **WARNING**: On some older/integrated hardware, Direct3D 12 requires + * texture data row pitch to be 256 byte aligned, and offsets to be aligned to + * 512 bytes. If they are not, SDL will make a temporary copy of the data that + * is properly aligned, but this adds overhead to the transfer process. Apps + * can avoid this by aligning their data appropriately, or using a different + * GPU backend than Direct3D 12. * * \since This struct is available since SDL 3.2.0. * @@ -1627,7 +1644,7 @@ typedef struct SDL_GPUSamplerCreateInfo typedef struct SDL_GPUVertexBufferDescription { Uint32 slot; /**< The binding slot of the vertex buffer. */ - Uint32 pitch; /**< The byte pitch between consecutive elements of the vertex buffer. */ + Uint32 pitch; /**< The size of a single element + the offset between elements. */ SDL_GPUVertexInputRate input_rate; /**< Whether attribute addressing is a function of the vertex index or instance index. */ Uint32 instance_step_rate; /**< Reserved for future use. Must be set to 0. */ } SDL_GPUVertexBufferDescription; @@ -1995,6 +2012,7 @@ typedef struct SDL_GPUComputePipelineCreateInfo * \since This struct is available since SDL 3.2.0. * * \sa SDL_BeginGPURenderPass + * \sa SDL_FColor */ typedef struct SDL_GPUColorTargetInfo { @@ -2053,6 +2071,9 @@ typedef struct SDL_GPUColorTargetInfo * * Note that depth/stencil targets do not support multisample resolves. * + * Due to ABI limitations, depth textures with more than 255 layers are not + * supported. + * * \since This struct is available since SDL 3.2.0. * * \sa SDL_BeginGPURenderPass @@ -2067,8 +2088,8 @@ typedef struct SDL_GPUDepthStencilTargetInfo 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 */ Uint8 clear_stencil; /**< The value to clear the stencil component to at the beginning of the render pass. Ignored if SDL_GPU_LOADOP_CLEAR is not used. */ - Uint8 padding1; - Uint8 padding2; + Uint8 mip_level; /**< The mip level to use as the depth stencil target. */ + Uint8 layer; /**< The layer index to use as the depth stencil target. */ } SDL_GPUDepthStencilTargetInfo; /** @@ -2114,6 +2135,8 @@ typedef struct SDL_GPUBufferBinding * * \sa SDL_BindGPUVertexSamplers * \sa SDL_BindGPUFragmentSamplers + * \sa SDL_GPUTexture + * \sa SDL_GPUSampler */ typedef struct SDL_GPUTextureSamplerBinding { @@ -2235,6 +2258,27 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( * useful debug information on device creation, defaults to true. * - `SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING`: the name of the GPU driver to * use, if a specific one is desired. + * - `SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN`: Enable Vulkan + * device feature shaderClipDistance. If disabled, clip distances are not + * supported in shader code: gl_ClipDistance[] built-ins of GLSL, + * SV_ClipDistance0/1 semantics of HLSL and [[clip_distance]] attribute of + * Metal. Disabling optional features allows the application to run on some + * older Android devices. Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN`: Enable + * Vulkan device feature depthClamp. If disabled, there is no depth clamp + * support and enable_depth_clip in SDL_GPURasterizerState must always be + * set to true. Disabling optional features allows the application to run on + * some older Android devices. Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN`: + * Enable Vulkan device feature drawIndirectFirstInstance. If disabled, the + * argument first_instance of SDL_GPUIndirectDrawCommand must be set to + * zero. Disabling optional features allows the application to run on some + * older Android devices. Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN`: Enable Vulkan + * device feature samplerAnisotropy. If disabled, enable_anisotropy of + * SDL_GPUSamplerCreateInfo must be set to false. Disabling optional + * features allows the application to run on some older Android devices. + * Defaults to true. * * These are the current shader format properties: * @@ -2251,29 +2295,56 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( * - `SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN`: The app is able to * provide Metal shader libraries if applicable. * - * With the D3D12 renderer: + * With the D3D12 backend: * * - `SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING`: the prefix to * use for all vertex semantics, default is "TEXCOORD". + * - `SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN`: By + * default, Resourcing Binding Tier 2 is required for D3D12 support. + * However, an application can set this property to true to enable Tier 1 + * support, if (and only if) the application uses 8 or fewer storage + * resources across all shader stages. As of writing, this property is + * useful for targeting Intel Haswell and Broadwell GPUs; other hardware + * either supports Tier 2 Resource Binding or does not support D3D12 in any + * capacity. Defaults to false. + * - `SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_VERSION_NUMBER`: Certain + * feature checks are only possible on Windows 11 by default. By setting + * this alongside `SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_PATH_STRING` + * and vendoring D3D12Core.dll from the D3D12 Agility SDK, you can make + * those feature checks possible on older platforms. The version you provide + * must match the one given in the DLL. + * - `SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_PATH_STRING`: Certain + * feature checks are only possible on Windows 11 by default. By setting + * this alongside + * `SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_VERSION_NUMBER` and + * vendoring D3D12Core.dll from the D3D12 Agility SDK, you can make those + * feature checks possible on older platforms. The path you provide must be + * relative to the executable path of your app. Be sure not to put the DLL + * in the same directory as the exe; Microsoft strongly advises against + * this! * - * With the Vulkan renderer: + * With the Vulkan backend: * - * - `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. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_REQUIRE_HARDWARE_ACCELERATION_BOOLEAN`: + * By default, Vulkan device enumeration includes drivers of all types, + * including software renderers (for example, the Lavapipe Mesa driver). + * This can be useful if your application _requires_ SDL_GPU, but if you can + * provide your own fallback renderer (for example, an OpenGL renderer) this + * property can be set to true. Defaults to false. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER`: a pointer to an + * SDL_GPUVulkanOptions structure to be processed during device creation. + * This allows configuring a variety of Vulkan-specific options such as + * increasing the API version and opting into extensions aside from the + * minimal set SDL requires. + * + * With the Metal backend: - + * `SDL_PROP_GPU_DEVICE_CREATE_METAL_ALLOW_MACFAMILY1_BOOLEAN`: By default, + * macOS support requires what Apple calls "MTLGPUFamilyMac2" hardware or + * newer. However, an application can set this property to true to enable + * support for "MTLGPUFamilyMac1" hardware, if (and only if) the application + * does not write to sRGB textures. (For history's sake: MacFamily1 also does + * not support indirect command buffers, MSAA depth resolve, and stencil + * resolve/feedback, but these are not exposed features in SDL_GPU.) * * \param props the properties to use. * \returns a GPU context on success or NULL on failure; call SDL_GetError() @@ -2289,21 +2360,55 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDeviceWithProperties( SDL_PropertiesID props); -#define SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN "SDL.gpu.device.create.debugmode" -#define SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN "SDL.gpu.device.create.preferlowpower" -#define SDL_PROP_GPU_DEVICE_CREATE_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" +#define SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN "SDL.gpu.device.create.debugmode" +#define SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN "SDL.gpu.device.create.preferlowpower" +#define SDL_PROP_GPU_DEVICE_CREATE_VERBOSE_BOOLEAN "SDL.gpu.device.create.verbose" +#define SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING "SDL.gpu.device.create.name" +#define SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN "SDL.gpu.device.create.feature.clip_distance" +#define SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN "SDL.gpu.device.create.feature.depth_clamping" +#define SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN "SDL.gpu.device.create.feature.indirect_draw_first_instance" +#define SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN "SDL.gpu.device.create.feature.anisotropy" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOLEAN "SDL.gpu.device.create.shaders.private" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN "SDL.gpu.device.create.shaders.spirv" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN "SDL.gpu.device.create.shaders.dxbc" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN "SDL.gpu.device.create.shaders.dxil" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN "SDL.gpu.device.create.shaders.msl" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN "SDL.gpu.device.create.shaders.metallib" +#define SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN "SDL.gpu.device.create.d3d12.allowtier1resourcebinding" +#define SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING "SDL.gpu.device.create.d3d12.semantic" +#define SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_VERSION_NUMBER "SDL.gpu.device.create.d3d12.agility_sdk_version" +#define SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_PATH_STRING "SDL.gpu.device.create.d3d12.agility_sdk_path" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_REQUIRE_HARDWARE_ACCELERATION_BOOLEAN "SDL.gpu.device.create.vulkan.requirehardwareacceleration" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER "SDL.gpu.device.create.vulkan.options" +#define SDL_PROP_GPU_DEVICE_CREATE_METAL_ALLOW_MACFAMILY1_BOOLEAN "SDL.gpu.device.create.metal.allowmacfamily1" + + +/** + * A structure specifying additional options when using Vulkan. + * + * When no such structure is provided, SDL will use Vulkan API version 1.0 and + * a minimal set of features. The requested API version influences how the + * feature_list is processed by SDL. When requesting API version 1.0, the + * feature_list is ignored. Only the vulkan_10_physical_device_features and + * the extension lists are used. When requesting API version 1.1, the + * feature_list is scanned for feature structures introduced in Vulkan 1.1. + * When requesting Vulkan 1.2 or higher, the feature_list is additionally + * scanned for compound feature structs such as + * VkPhysicalDeviceVulkan11Features. The device and instance extension lists, + * as well as vulkan_10_physical_device_features, are always processed. + * + * \since This struct is available since SDL 3.4.0. + */ +typedef struct SDL_GPUVulkanOptions +{ + Uint32 vulkan_api_version; /**< The Vulkan API version to request for the instance. Use Vulkan's VK_MAKE_VERSION or VK_MAKE_API_VERSION. */ + void *feature_list; /**< Pointer to the first element of a chain of Vulkan feature structs. (Requires API version 1.1 or higher.)*/ + void *vulkan_10_physical_device_features; /**< Pointer to a VkPhysicalDeviceFeatures struct to enable additional Vulkan 1.0 features. */ + Uint32 device_extension_count; /**< Number of additional device extensions to require. */ + const char **device_extension_names; /**< Pointer to a list of additional device extensions to require. */ + Uint32 instance_extension_count; /**< Number of additional instance extensions to require. */ + const char **instance_extension_names; /**< Pointer to a list of additional instance extensions to require. */ +} SDL_GPUVulkanOptions; /** * Destroys a GPU context previously returned by SDL_CreateGPUDevice. @@ -2667,7 +2772,8 @@ extern SDL_DECLSPEC SDL_GPUShader * SDLCALL SDL_CreateGPUShader( * Creates a texture object to be used in graphics or compute workflows. * * The contents of this texture are undefined until data is written to the - * texture. + * texture, either via SDL_UploadToGPUTexture or by performing a render or + * compute pass with this texture as a target. * * Note that certain combinations of usage flags are invalid. For example, a * texture cannot have both the SAMPLER and GRAPHICS_STORAGE_READ flags. @@ -2709,6 +2815,8 @@ extern SDL_DECLSPEC SDL_GPUShader * SDLCALL SDL_CreateGPUShader( * * \sa SDL_UploadToGPUTexture * \sa SDL_DownloadFromGPUTexture + * \sa SDL_BeginGPURenderPass + * \sa SDL_BeginGPUComputePass * \sa SDL_BindGPUVertexSamplers * \sa SDL_BindGPUVertexStorageTextures * \sa SDL_BindGPUFragmentSamplers @@ -2865,6 +2973,12 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetGPUTextureName( * * Useful for debugging. * + * On Direct3D 12, using SDL_InsertGPUDebugLabel requires + * WinPixEventRuntime.dll to be in your PATH or in the same directory as your + * executable. See + * [here](https://devblogs.microsoft.com/pix/winpixeventruntime/) + * for instructions on how to obtain it. + * * \param command_buffer a command buffer. * \param text a UTF-8 string constant to insert as the label. * @@ -2875,7 +2989,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_InsertGPUDebugLabel( const char *text); /** - * Begins a debug group with an arbitary name. + * Begins a debug group with an arbitrary name. * * Used for denoting groups of calls when viewing the command buffer * callstream in a graphics debugging tool. @@ -2883,6 +2997,11 @@ extern SDL_DECLSPEC void SDLCALL SDL_InsertGPUDebugLabel( * Each call to SDL_PushGPUDebugGroup must have a corresponding call to * SDL_PopGPUDebugGroup. * + * On Direct3D 12, using SDL_PushGPUDebugGroup requires WinPixEventRuntime.dll + * to be in your PATH or in the same directory as your executable. See + * [here](https://devblogs.microsoft.com/pix/winpixeventruntime/) + * for instructions on how to obtain it. + * * On some backends (e.g. Metal), pushing a debug group during a * render/blit/compute pass will create a group that is scoped to the native * pass rather than the command buffer. For best results, if you push a debug @@ -2902,6 +3021,11 @@ extern SDL_DECLSPEC void SDLCALL SDL_PushGPUDebugGroup( /** * Ends the most-recently pushed debug group. * + * On Direct3D 12, using SDL_PopGPUDebugGroup requires WinPixEventRuntime.dll + * to be in your PATH or in the same directory as your executable. See + * [here](https://devblogs.microsoft.com/pix/winpixeventruntime/) + * for instructions on how to obtain it. + * * \param command_buffer a command buffer. * * \since This function is available since SDL 3.2.0. @@ -3043,7 +3167,7 @@ extern SDL_DECLSPEC SDL_GPUCommandBuffer * SDLCALL SDL_AcquireGPUCommandBuffer( /** * Pushes data to a vertex uniform slot on the command buffer. * - * Subsequent draw calls will use this uniform data. + * Subsequent draw calls in this command buffer will use this uniform data. * * The data being pushed must respect std140 layout conventions. In practical * terms this means you must ensure that vec3 and vec4 fields are 16-byte @@ -3068,7 +3192,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_PushGPUVertexUniformData( /** * Pushes data to a fragment uniform slot on the command buffer. * - * Subsequent draw calls will use this uniform data. + * Subsequent draw calls in this command buffer will use this uniform data. * * The data being pushed must respect std140 layout conventions. In practical * terms this means you must ensure that vec3 and vec4 fields are 16-byte @@ -3090,7 +3214,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_PushGPUFragmentUniformData( /** * Pushes data to a uniform slot on the command buffer. * - * Subsequent draw calls will use this uniform data. + * Subsequent draw calls in this command buffer will use this uniform data. * * The data being pushed must respect std140 layout conventions. In practical * terms this means you must ensure that vec3 and vec4 fields are 16-byte @@ -3122,6 +3246,14 @@ extern SDL_DECLSPEC void SDLCALL SDL_PushGPUComputeUniformData( * is called. You cannot begin another render pass, or begin a compute pass or * copy pass until you have ended the render pass. * + * Using SDL_GPU_LOADOP_LOAD before any contents have been written to the + * texture subresource will result in undefined behavior. SDL_GPU_LOADOP_CLEAR + * will set the contents of the texture subresource to a single value before + * any rendering is performed. It's fine to do an empty render pass using + * SDL_GPU_STOREOP_STORE to clear a texture, but in general it's better to + * think of clearing not as an independent operation but as something that's + * done as the beginning of a render pass. + * * \param command_buffer a command buffer. * \param color_target_infos an array of texture subresources with * corresponding clear values and load/store ops. @@ -3568,7 +3700,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputePipeline( * The textures must have been created with SDL_GPU_TEXTUREUSAGE_SAMPLER. * * Be sure your shader is set up according to the requirements documented in - * SDL_CreateGPUShader(). + * SDL_CreateGPUComputePipeline(). * * \param compute_pass a compute pass handle. * \param first_slot the compute sampler slot to begin binding from. @@ -3579,7 +3711,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputePipeline( * * \since This function is available since SDL 3.2.0. * - * \sa SDL_CreateGPUShader + * \sa SDL_CreateGPUComputePipeline */ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputeSamplers( SDL_GPUComputePass *compute_pass, @@ -3594,7 +3726,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputeSamplers( * SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ. * * Be sure your shader is set up according to the requirements documented in - * SDL_CreateGPUShader(). + * SDL_CreateGPUComputePipeline(). * * \param compute_pass a compute pass handle. * \param first_slot the compute storage texture slot to begin binding from. @@ -3603,7 +3735,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputeSamplers( * * \since This function is available since SDL 3.2.0. * - * \sa SDL_CreateGPUShader + * \sa SDL_CreateGPUComputePipeline */ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputeStorageTextures( SDL_GPUComputePass *compute_pass, @@ -3618,7 +3750,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputeStorageTextures( * SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_READ. * * Be sure your shader is set up according to the requirements documented in - * SDL_CreateGPUShader(). + * SDL_CreateGPUComputePipeline(). * * \param compute_pass a compute pass handle. * \param first_slot the compute storage buffer slot to begin binding from. @@ -3627,7 +3759,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputeStorageTextures( * * \since This function is available since SDL 3.2.0. * - * \sa SDL_CreateGPUShader + * \sa SDL_CreateGPUComputePipeline */ extern SDL_DECLSPEC void SDLCALL SDL_BindGPUComputeStorageBuffers( SDL_GPUComputePass *compute_pass, @@ -3744,6 +3876,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnmapGPUTransferBuffer( * \returns a copy pass handle. * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_EndGPUCopyPass */ extern SDL_DECLSPEC SDL_GPUCopyPass * SDLCALL SDL_BeginGPUCopyPass( SDL_GPUCommandBuffer *command_buffer); @@ -3797,6 +3931,10 @@ extern SDL_DECLSPEC void SDLCALL SDL_UploadToGPUBuffer( * This copy occurs on the GPU timeline. You may assume the copy has finished * in subsequent commands. * + * This function does not support copying between depth and color textures. + * For those, copy the texture to a buffer and then to the destination + * texture. + * * \param copy_pass a copy pass handle. * \param source a source texture region. * \param destination a destination texture region. @@ -4403,6 +4541,29 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_CalculateGPUTextureFormatSize( Uint32 height, Uint32 depth_or_layer_count); +/** + * Get the SDL pixel format corresponding to a GPU texture format. + * + * \param format a texture format. + * \returns the corresponding pixel format, or SDL_PIXELFORMAT_UNKNOWN if + * there is no corresponding pixel format. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC SDL_PixelFormat SDLCALL SDL_GetPixelFormatFromGPUTextureFormat(SDL_GPUTextureFormat format); + +/** + * Get the GPU texture format corresponding to an SDL pixel format. + * + * \param format a pixel format. + * \returns the corresponding GPU texture format, or + * SDL_GPU_TEXTUREFORMAT_INVALID if there is no corresponding GPU + * texture format. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC SDL_GPUTextureFormat SDLCALL SDL_GetGPUTextureFormatFromPixelFormat(SDL_PixelFormat format); + #ifdef SDL_PLATFORM_GDK /** @@ -4443,8 +4604,3 @@ extern SDL_DECLSPEC void SDLCALL SDL_GDKResumeGPU(SDL_GPUDevice *device); #include #endif /* SDL_gpu_h_ */ - - - - - diff --git a/libs/SDL3/include/SDL3/SDL_guid.h b/libs/SDL3/include/SDL3/SDL_guid.h index 312c42c0..cf9df31c 100644 --- a/libs/SDL3/include/SDL3/SDL_guid.h +++ b/libs/SDL3/include/SDL3/SDL_guid.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_haptic.h b/libs/SDL3/include/SDL3/SDL_haptic.h index 9a20396d..ef91904a 100644 --- a/libs/SDL3/include/SDL3/SDL_haptic.h +++ b/libs/SDL3/include/SDL3/SDL_haptic.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -1392,7 +1392,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ResumeHaptic(SDL_Haptic *haptic); * \since This function is available since SDL 3.2.0. * * \sa SDL_RunHapticEffect - * \sa SDL_StopHapticEffects + * \sa SDL_StopHapticEffect */ extern SDL_DECLSPEC bool SDLCALL SDL_StopHapticEffects(SDL_Haptic *haptic); diff --git a/libs/SDL3/include/SDL3/SDL_hidapi.h b/libs/SDL3/include/SDL3/SDL_hidapi.h index 131b0372..90e574c4 100644 --- a/libs/SDL3/include/SDL3/SDL_hidapi.h +++ b/libs/SDL3/include/SDL3/SDL_hidapi.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -55,6 +55,7 @@ #include #include +#include #include /* Set up for C function definitions, even when using C++ */ @@ -283,6 +284,24 @@ extern SDL_DECLSPEC SDL_hid_device * SDLCALL SDL_hid_open(unsigned short vendor_ */ extern SDL_DECLSPEC SDL_hid_device * SDLCALL SDL_hid_open_path(const char *path); +/** + * Get the properties associated with an SDL_hid_device. + * + * The following read-only properties are provided by SDL: + * + * - `SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER`: the libusb_device_handle + * associated with the device, if it was opened using libusb. + * + * \param dev a device handle returned from SDL_hid_open(). + * \returns a valid property ID on success or 0 on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_hid_get_properties(SDL_hid_device *dev); + +#define SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER "SDL.hidapi.libusb.device.handle" + /** * Write an Output report to a HID device. * diff --git a/libs/SDL3/include/SDL3/SDL_hints.h b/libs/SDL3/include/SDL3/SDL_hints.h index 7961157c..ba2ff351 100644 --- a/libs/SDL3/include/SDL3/SDL_hints.h +++ b/libs/SDL3/include/SDL3/SDL_hints.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -391,12 +391,51 @@ extern "C" { * concept, so it applies to a physical audio device in this case, and not an * SDL_AudioStream, nor an SDL logical audio device. * + * For Windows WASAPI audio, the following roles are supported, and map to + * `AUDIO_STREAM_CATEGORY`: + * + * - "Other" (default) + * - "Communications" - Real-time communications, such as VOIP or chat + * - "Game" - Game audio + * - "GameChat" - Game chat audio, similar to "Communications" except that + * this will not attenuate other audio streams + * - "Movie" - Music or sound with dialog + * - "Media" - Music or sound without dialog + * + * Android's AAudio target supports this hint as of SDL 3.4.4. Android does + * not support the exact same options as WASAPI, but for portability, will + * attempt to map these same strings to the `aaudio_usage_t` constants. For + * example, "Movie" and "Media" will both map to `AAUDIO_USAGE_MEDIA`, etc. + * + * If your application applies its own echo cancellation, gain control, and + * noise reduction it should also set SDL_HINT_AUDIO_DEVICE_RAW_STREAM. + * * This hint should be set before an audio device is opened. * * \since This hint is available since SDL 3.2.0. */ #define SDL_HINT_AUDIO_DEVICE_STREAM_ROLE "SDL_AUDIO_DEVICE_STREAM_ROLE" +/** + * Specify whether this audio device should do audio processing. + * + * Some operating systems perform echo cancellation, gain control, and noise + * reduction as needed. If your application already handles these, you can set + * this hint to prevent the OS from doing additional audio processing. + * + * This corresponds to the WASAPI audio option `AUDCLNT_STREAMOPTIONS_RAW`. + * + * The variable can be set to the following values: + * + * - "0": audio processing can be done by the OS. (default) + * - "1": audio processing is done by the application. + * + * This hint should be set before an audio device is opened. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_AUDIO_DEVICE_RAW_STREAM "SDL_AUDIO_DEVICE_RAW_STREAM" + /** * Specify the input file when recording audio using the disk audio driver. * @@ -595,7 +634,7 @@ extern "C" { * A variable that limits what CPU features are available. * * By default, SDL marks all features the current CPU supports as available. - * This hint allows to limit these to a subset. + * This hint allows the enabled features to be limited to a subset. * * When the hint is unset, or empty, SDL will enable all detected CPU * features. @@ -685,6 +724,21 @@ extern "C" { */ #define SDL_HINT_DISPLAY_USABLE_BOUNDS "SDL_DISPLAY_USABLE_BOUNDS" +/** + * Set the level of checking for invalid parameters passed to SDL functions. + * + * The variable can be set to the following values: + * + * - "1": Enable fast parameter error checking, e.g. quick NULL checks, etc. + * - "2": Enable full parameter error checking, e.g. validating objects are + * the correct type, etc. (default) + * + * This hint can be set anytime. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_INVALID_PARAM_CHECKS "SDL_INVALID_PARAM_CHECKS" + /** * Disable giving back control to the browser automatically when running with * asyncify. @@ -1035,6 +1089,21 @@ extern "C" { */ #define SDL_HINT_HIDAPI_LIBUSB "SDL_HIDAPI_LIBUSB" + +/** + * A variable to control whether HIDAPI uses libusb for GameCube adapters. + * + * The variable can be set to the following values: + * + * - "0": HIDAPI will not use libusb for GameCube adapters. + * - "1": HIDAPI will use libusb for GameCube adapters if available. (default) + * + * This hint should be set before SDL is initialized. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_HIDAPI_LIBUSB_GAMECUBE "SDL_HIDAPI_LIBUSB_GAMECUBE" + /** * A variable to control whether HIDAPI uses libusb only for whitelisted * devices. @@ -1141,8 +1210,8 @@ extern "C" { #define SDL_HINT_IME_IMPLEMENTED_UI "SDL_IME_IMPLEMENTED_UI" /** - * A variable controlling whether the home indicator bar on iPhone X should be - * hidden. + * A variable controlling whether the home indicator bar on iPhone X and later + * should be hidden. * * The variable can be set to the following values: * @@ -1343,6 +1412,26 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_GAMEINPUT "SDL_JOYSTICK_GAMEINPUT" +/** + * A variable controlling whether GameInput should be used for handling + * GIP devices that require raw report processing, but aren't supported + * by HIDRAW, such as Xbox One Guitars. + * + * Note that this is only supported with GameInput 3 or newer. + * + * The variable can be set to the following values: + * + * - "0": GameInput is not used to handle raw GIP devices. + * - "1": GameInput is used. + * + * The default is "1" when using GameInput 3 or newer, and is "0" otherwise. + * + * This hint should be set before SDL is initialized. + * + * \since This hint is available since SDL 3.4.4. + */ +#define SDL_HINT_JOYSTICK_GAMEINPUT_RAW "SDL_JOYSTICK_GAMEINPUT_RAW" + /** * A variable containing a list of devices known to have a GameCube form * factor. @@ -1714,10 +1803,16 @@ extern "C" { * A variable controlling whether the HIDAPI driver for HORI licensed Steam * controllers should be used. * - * This variable can be set to the following values: "0" - HIDAPI driver is - * not used "1" - HIDAPI driver is used + * The variable can be set to the following values: * - * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.2.0. */ #define SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI "SDL_JOYSTICK_HIDAPI_STEAM_HORI" @@ -1725,12 +1820,16 @@ extern "C" { * A variable controlling whether the HIDAPI driver for some Logitech wheels * should be used. * - * This variable can be set to the following values: + * The variable can be set to the following values: * - * - "0": HIDAPI driver is not used - * - "1": HIDAPI driver is used + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. * - * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. */ #define SDL_HINT_JOYSTICK_HIDAPI_LG4FF "SDL_JOYSTICK_HIDAPI_LG4FF" @@ -1738,23 +1837,69 @@ extern "C" { * A variable controlling whether the HIDAPI driver for 8BitDo controllers * should be used. * - * This variable can be set to the following values: + * The variable can be set to the following values: * - * "0" - HIDAPI driver is not used. "1" - HIDAPI driver is used. + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. * - * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. */ #define SDL_HINT_JOYSTICK_HIDAPI_8BITDO "SDL_JOYSTICK_HIDAPI_8BITDO" +/** + * A variable controlling whether the HIDAPI driver for SInput controllers + * should be used. + * + * More info - https://github.com/HandHeldLegend/SInput-HID + * + * The variable can be set to the following values: + * + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_SINPUT "SDL_JOYSTICK_HIDAPI_SINPUT" + +/** + * A variable controlling whether the HIDAPI driver for ZUIKI controllers + * should be used. + * + * The variable can be set to the following values: + * + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_ZUIKI "SDL_JOYSTICK_HIDAPI_ZUIKI" + /** * A variable controlling whether the HIDAPI driver for Flydigi controllers * should be used. * - * This variable can be set to the following values: + * The variable can be set to the following values: * - * "0" - HIDAPI driver is not used. "1" - HIDAPI driver is used. + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. * - * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. */ #define SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI "SDL_JOYSTICK_HIDAPI_FLYDIGI" @@ -1809,6 +1954,23 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED "SDL_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED" +/** + * A variable controlling whether the HIDAPI driver for Nintendo Switch 2 + * controllers should be used. + * + * The variable can be set to the following values: + * + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH2 "SDL_JOYSTICK_HIDAPI_SWITCH2" + /** * A variable controlling whether Nintendo Switch Joy-Con controllers will be * in vertical mode when using the HIDAPI driver. @@ -2196,8 +2358,8 @@ extern "C" { * * The variable can be set to the following values: * - * - "0": WGI is not used. - * - "1": WGI is used. (default) + * - "0": WGI is not used. (default) + * - "1": WGI is used. * * This hint should be set before SDL is initialized. * @@ -2298,9 +2460,9 @@ extern "C" { * pressing the 1 key would yield the keycode SDLK_1, or '1', instead of * SDLK_AMPERSAND, or '&' * - "latin_letters": For keyboards using non-Latin letters, such as Russian - * or Thai, the letter keys generate keycodes as though it had an en_US - * layout. e.g. pressing the key associated with SDL_SCANCODE_A on a Russian - * keyboard would yield 'a' instead of a Cyrillic letter. + * or Thai, the letter keys generate keycodes as though it had an English + * QWERTY layout. e.g. pressing the key associated with SDL_SCANCODE_A on a + * Russian keyboard would yield 'a' instead of a Cyrillic letter. * * The default value for this hint is "french_numbers,latin_letters" * @@ -2359,6 +2521,27 @@ extern "C" { */ #define SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER "SDL_KMSDRM_REQUIRE_DRM_MASTER" +/** + * A variable that controls whether KMSDRM will use "atomic" functionality. + * + * The KMSDRM backend can use atomic commits, if both DRM_CLIENT_CAP_ATOMIC + * and DRM_CLIENT_CAP_UNIVERSAL_PLANES is supported by the system. As of SDL + * 3.4.0, it will favor this functionality, but in case this doesn't work well + * on a given system or other surprises, this hint can be used to disable it. + * + * This hint can not enable the functionality if it isn't available. + * + * The variable can be set to the following values: + * + * - "0": SDL will not use the KMSDRM "atomic" functionality. + * - "1": SDL will allow usage of the KMSDRM "atomic" functionality. (default) + * + * This hint should be set before SDL is initialized. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_KMSDRM_ATOMIC "SDL_KMSDRM_ATOMIC" + /** * A variable controlling the default SDL log levels. * @@ -2379,6 +2562,11 @@ extern "C" { * * `app=info,assert=warn,test=verbose,*=error` * + * If the `DEBUG_INVOCATION` environment variable is set to "1", the default + * log levels are equivalent to: + * + * `assert=warn,test=verbose,*=debug` + * * This hint can be set anytime. * * \since This hint is available since SDL 3.2.0. @@ -2480,6 +2668,21 @@ extern "C" { */ #define SDL_HINT_MAC_SCROLL_MOMENTUM "SDL_MAC_SCROLL_MOMENTUM" +/** + * A variable controlling whether holding down a key will repeat the pressed + * key or open the accents menu on macOS. + * + * The variable can be set to the following values: + * + * - "0": Holding a key will repeat the pressed key. + * - "1": Holding a key will open the accents menu for that key. (default) + * + * This hint needs to be set before SDL_Init(). + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_MAC_PRESS_AND_HOLD "SDL_MAC_PRESS_AND_HOLD" + /** * Request SDL_AppIterate() be called at a specific rate. * @@ -2500,6 +2703,10 @@ extern "C" { * This defaults to 0, and specifying NULL for the hint's value will restore * the default. * + * This doesn't have to be an integer value. For example, "59.94" won't be + * rounded to an integer rate; the digits after the decimal are actually + * respected. + * * This hint can be set anytime. * * \since This hint is available since SDL 3.2.0. @@ -2555,6 +2762,24 @@ extern "C" { */ #define SDL_HINT_MOUSE_DEFAULT_SYSTEM_CURSOR "SDL_MOUSE_DEFAULT_SYSTEM_CURSOR" +/** + * A variable setting whether we should scale cursors by the current display + * scale. + * + * The variable can be set to the following values: + * + * - "0": Cursors will not change size based on the display content scale. + * (default) + * - "1": Cursors will automatically match the display content scale (e.g. a + * 2x sized cursor will be used when the window is on a monitor with 200% + * scale). This is currently implemented on Windows. + * + * This hint needs to be set before creating cursors. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_MOUSE_DPI_SCALE_CURSORS "SDL_MOUSE_DPI_SCALE_CURSORS" + /** * A variable controlling whether warping a hidden mouse cursor will activate * relative mouse mode. @@ -2563,7 +2788,7 @@ extern "C" { * the window center occur within a short time period, SDL will emulate mouse * warps using relative mouse mode. This can provide smoother and more * reliable mouse motion for some older games, which continuously calculate - * the distance travelled by the mouse pointer and warp it back to the center + * the distance traveled by the mouse pointer and warp it back to the center * of the window, rather than using relative mouse motion. * * Note that relative mouse mode may have different mouse acceleration @@ -2806,6 +3031,46 @@ extern "C" { */ #define SDL_HINT_OPENGL_ES_DRIVER "SDL_OPENGL_ES_DRIVER" +/** + * A variable controlling whether to force an sRGB-capable OpenGL context. + * + * At OpenGL context creation time, some platforms can request an sRGB-capable + * context. However, sometimes any form of the request can cause surprising + * results on some drivers, platforms, and hardware. Usually the surprise is + * in the form of rendering that is either a little darker or a little + * brighter than intended. + * + * This hint allows the user to override the app's sRGB requests and either + * force a specific value, or avoid requesting anything at all, depending on + * what makes things work correctly for their system. + * + * This is meant as a fail-safe; apps should probably not explicitly set this, + * and most users should not, either. + * + * Note that some platforms cannot make this request at all, and on all + * platforms this request can be denied by the operating system. + * + * In addition to attempting to obtain the type of sRGB-capable OpenGL context + * requested by this hint, SDL will try to force the state of + * GL_FRAMEBUFFER_SRGB on the new context, if appropriate. + * + * The variable can be set to the following values: + * + * - "0": Force a request for an OpenGL context that is _not_ sRGB-capable. + * - "1": Force a request for an OpenGL context that _is_ sRGB-capable. + * - "skip": Don't make any request for an sRGB-capable context (don't specify + * the attribute at all during context creation time). + * - any other string is undefined behavior. + * + * If unset, or set to an empty string, SDL will make a request using the + * value the app specified with the SDL_GL_FRAMEBUFFER_SRGB_CAPABLE attribute. + * + * This hint should be set before an OpenGL context is created. + * + * \since This hint is available since SDL 3.4.2. + */ +#define SDL_HINT_OPENGL_FORCE_SRGB_FRAMEBUFFER "SDL_OPENGL_FORCE_SRGB_FRAMEBUFFER" + /** * Mechanism to specify openvr_api library location * @@ -2929,6 +3194,24 @@ extern "C" { */ #define SDL_HINT_RENDER_DIRECT3D11_DEBUG "SDL_RENDER_DIRECT3D11_DEBUG" +/** + * A variable controlling whether to use the Direct3D 11 WARP software + * rasterizer. + * + * For more information, see: + * https://learn.microsoft.com/en-us/windows/win32/direct3darticles/directx-warp + * + * The variable can be set to the following values: + * + * - "0": Disable WARP rasterizer. (default) + * - "1": Enable WARP rasterizer. + * + * This hint should be set before creating a renderer. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_RENDER_DIRECT3D11_WARP "SDL_RENDER_DIRECT3D11_WARP" + /** * A variable controlling whether to enable Vulkan Validation Layers. * @@ -3111,6 +3394,49 @@ extern "C" { */ #define SDL_HINT_ROG_GAMEPAD_MICE_EXCLUDED "SDL_ROG_GAMEPAD_MICE_EXCLUDED" +/** + * A variable controlling the width of the PS2's framebuffer in pixels. + * + * By default, the variable is "640". + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_PS2_GS_WIDTH "SDL_PS2_GS_WIDTH" + +/** + * A variable controlling the height of the PS2's framebuffer in pixels. + * + * By default, the variable is "448". + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_PS2_GS_HEIGHT "SDL_PS2_GS_HEIGHT" + +/** + * A variable controlling whether the signal is interlaced or progressive. + * + * The variable can be set to the following values: + * + * - "0": Image is interlaced. (default) + * - "1": Image is progressive. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_PS2_GS_PROGRESSIVE "SDL_PS2_GS_PROGRESSIVE" + +/** + * A variable controlling the video mode of the console. + * + * The variable can be set to the following values: + * + * - "": Console-native. (default) + * - "NTSC": 60hz region. + * - "PAL": 50hz region. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_PS2_GS_MODE "SDL_PS2_GS_MODE" + /** * A variable controlling which Dispmanx layer to use on a Raspberry PI. * @@ -3337,10 +3663,12 @@ extern "C" { * prioritized in the list of displays, as exposed by calling * SDL_GetDisplays(), with the first listed becoming the primary display. The * naming convention can vary depending on the environment, but it is usually - * a connector name (e.g. 'DP-1', 'DP-2', 'HDMI-A-1',etc...). + * a connector name (e.g. 'DP-1', 'DP-2', 'HDMI-A-1', etc...). * - * On Wayland and X11 desktops, the connector names associated with displays - * can typically be found by using the `xrandr` utility. + * On Wayland desktops, the connector names associated with displays can be + * found in the `name` property of the info output from `wayland-info -i + * wl_output`. On X11 desktops, the `xrandr` utility can be used to retrieve + * the connector names associated with displays. * * This hint is currently supported on the following drivers: * @@ -3477,6 +3805,23 @@ extern "C" { */ #define SDL_HINT_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY "SDL_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY" +/** + * A variable indicating whether the metal layer drawable size should be + * updated for the SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED event on macOS. + * + * The variable can be set to the following values: + * + * - "0": the metal layer drawable size will not be updated on the + * SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED event. + * - "1": the metal layer drawable size will be updated on the + * SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED event. (default) + * + * This hint should be set before SDL_Metal_CreateView called. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_VIDEO_METAL_AUTO_RESIZE_DRAWABLE "SDL_VIDEO_METAL_AUTO_RESIZE_DRAWABLE" + /** * A variable controlling whether SDL will attempt to automatically set the * destination display to a mode most closely matching that of the previous @@ -3504,8 +3849,10 @@ extern "C" { * The variable can be set to the following values: * * - "0": Fullscreen windows will not be minimized when they lose focus. - * (default) * - "1": Fullscreen windows are minimized when they lose focus. + * - "auto": Fullscreen windows are minimized when they lose focus if they use + * exclusive fullscreen modes, so the desktop video mode is restored. + * (default) * * This hint can be set anytime. * @@ -3697,6 +4044,31 @@ extern "C" { */ #define SDL_HINT_VIDEO_WIN_D3DCOMPILER "SDL_VIDEO_WIN_D3DCOMPILER" +/** + * A variable controlling whether the X Synchronization Extension is enabled. + * + * If set, this can result in smoother window resizing when rendering using + * OpenGL, however, there are some conditions: + * + * - It is only activated on windows created with the `SDL_WINDOW_OPENGL` flag + * (windows using an SDL OpenGL renderer have this automatically set). + * - When activated, presentation must be done with `SDL_GL_SwapWindow()` + * (`SDL_RenderPresent()` calls this internally for OpenGL renderers as well). + * + * Enabling this and presenting via an external mechanism will result in sync + * requests not being acked, and hangs and other odd window behavior may result. + * + * The variable can be set to the following values: + * + * - "0": The X Synchronization Extension is disabled. (default) + * - "1": The X Synchronization Extension is enabled. + * + * This hint should be set before creating a window. + * + * \since This hint is available since SDL 3.4.10. + */ +#define SDL_HINT_VIDEO_X11_ENABLE_XSYNC_EXT "SDL_VIDEO_X11_ENABLE_XSYNC_EXT" + /** * A variable controlling whether SDL should call XSelectInput() to enable * input events on X11 windows wrapped by SDL windows. @@ -4151,15 +4523,14 @@ extern "C" { * * The variable can be set to the following values: * - * - "0": GameInput is not used for raw keyboard and mouse events. + * - "0": GameInput is not used for raw keyboard and mouse events. (default) * - "1": GameInput is used for raw keyboard and mouse events, if available. - * (default) * * This hint should be set before SDL is initialized. * * \since This hint is available since SDL 3.2.0. */ -#define SDL_HINT_WINDOWS_GAMEINPUT "SDL_WINDOWS_GAMEINPUT" +#define SDL_HINT_WINDOWS_GAMEINPUT "SDL_WINDOWS_GAMEINPUT" /** * A variable controlling whether raw keyboard events are used on Windows. @@ -4175,6 +4546,45 @@ extern "C" { */ #define SDL_HINT_WINDOWS_RAW_KEYBOARD "SDL_WINDOWS_RAW_KEYBOARD" +/** + * A variable controlling whether or not the RIDEV_NOHOTKEYS flag is set when + * enabling Windows raw keyboard events. + * + * This blocks any hotkeys that have been registered by applications from + * having any effect beyond generating raw WM_INPUT events. + * + * This flag does not affect system-hotkeys like ALT-TAB or CTRL-ALT-DEL, but + * does affect the Windows Logo key since it is a userland hotkey registered + * by explorer.exe. + * + * The variable can be set to the following values: + * + * - "0": Hotkeys are not excluded. (default) + * - "1": Hotkeys are excluded. + * + * This hint can be set anytime. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_WINDOWS_RAW_KEYBOARD_EXCLUDE_HOTKEYS "SDL_WINDOWS_RAW_KEYBOARD_EXCLUDE_HOTKEYS" + +/** + * A variable controlling whether the RIDEV_INPUTSINK flag is set when + * enabling Windows raw keyboard events. + * + * This enables the window to still receive input even if not in foreground. + * + * Focused windows that receive text input will still prevent input events from triggering. + * + * - "0": Input is not received when not in focus or foreground. (default) + * - "1": Input will be received even when not in focus or foreground. + * + * This hint can be set anytime. + * + * \since This hint is available since SDL 3.4.4. + */ +#define SDL_HINT_WINDOWS_RAW_KEYBOARD_INPUTSINK "SDL_WINDOWS_RAW_KEYBOARD_INPUTSINK" + /** * A variable controlling whether SDL uses Kernel Semaphores on Windows. * @@ -4204,7 +4614,7 @@ extern "C" { * * \since This hint is available since SDL 3.2.0. */ -#define SDL_HINT_WINDOWS_INTRESOURCE_ICON "SDL_WINDOWS_INTRESOURCE_ICON" +#define SDL_HINT_WINDOWS_INTRESOURCE_ICON "SDL_WINDOWS_INTRESOURCE_ICON" /** * A variable to specify custom icon resource id from RC file on Windows @@ -4377,7 +4787,6 @@ extern "C" { */ #define SDL_HINT_PEN_TOUCH_EVENTS "SDL_PEN_TOUCH_EVENTS" - /** * An enumeration of hint priorities. * @@ -4476,19 +4885,14 @@ extern SDL_DECLSPEC void SDLCALL SDL_ResetHints(void); * \param name the hint to query. * \returns the string value of a hint or NULL if the hint isn't set. * - * \threadsafety It is safe to call this function from any thread, however the - * return value only remains valid until the hint is changed; if - * another thread might do so, the app should supply locks - * and/or make a copy of the string. Note that using a hint - * callback instead is always thread-safe, as SDL holds a lock - * on the thread subsystem during the callback. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetHint * \sa SDL_SetHintWithPriority */ -extern SDL_DECLSPEC const char * SDLCALL SDL_GetHint(const char *name); +extern SDL_DECLSPEC const char *SDLCALL SDL_GetHint(const char *name); /** * Get the boolean value of a hint variable. @@ -4511,7 +4915,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetHintBoolean(const char *name, bool defau * A callback used to send notifications of hint value changes. * * This is called an initial time during SDL_AddHintCallback with the hint's - * current value, and then again each time the hint's value changes. + * current value, and then again each time the hint's value changes. In the + * initial call, the current value is in both `oldValue` and `newValue`. * * \param userdata what was passed as `userdata` to SDL_AddHintCallback(). * \param name what was passed as `name` to SDL_AddHintCallback(). @@ -4564,8 +4969,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_AddHintCallback(const char *name, SDL_HintC * \sa SDL_AddHintCallback */ extern SDL_DECLSPEC void SDLCALL SDL_RemoveHintCallback(const char *name, - SDL_HintCallback callback, - void *userdata); + SDL_HintCallback callback, + void *userdata); /* Ends C function definitions when using C++ */ #ifdef __cplusplus diff --git a/libs/SDL3/include/SDL3/SDL_init.h b/libs/SDL3/include/SDL3/SDL_init.h index 557f2b99..d75ccc31 100644 --- a/libs/SDL3/include/SDL3/SDL_init.h +++ b/libs/SDL3/include/SDL3/SDL_init.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -224,6 +224,8 @@ typedef void (SDLCALL *SDL_AppQuit_func)(void *appstate, SDL_AppResult result); * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetAppMetadata @@ -244,6 +246,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_Init(SDL_InitFlags flags); * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_Init @@ -260,6 +264,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_InitSubSystem(SDL_InitFlags flags); * * \param flags any of the flags used by SDL_Init(); see SDL_Init for details. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_InitSubSystem @@ -274,6 +280,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_QuitSubSystem(SDL_InitFlags flags); * \returns a mask of all initialized subsystems if `flags` is 0, otherwise it * returns the initialization status of the specified subsystems. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_Init @@ -292,6 +300,8 @@ extern SDL_DECLSPEC SDL_InitFlags SDLCALL SDL_WasInit(SDL_InitFlags flags); * application is shutdown, but it is not wise to do this from a library or * other dynamically loaded code. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_Init diff --git a/libs/SDL3/include/SDL3/SDL_intrin.h b/libs/SDL3/include/SDL3/SDL_intrin.h index bac6d7ad..a2e96808 100644 --- a/libs/SDL3/include/SDL3/SDL_intrin.h +++ b/libs/SDL3/include/SDL3/SDL_intrin.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -217,9 +217,9 @@ /* Need to do this here because intrin.h has C++ code in it */ /* Visual Studio 2005 has a bug where intrin.h conflicts with winnt.h */ #if defined(_MSC_VER) && (_MSC_VER >= 1500) && (defined(_M_IX86) || defined(_M_X64)) -#ifdef __clang__ /* As of Clang 11, '_m_prefetchw' is conflicting with the winnt.h's version, so we define the needed '_m_prefetch' here as a pseudo-header, until the issue is fixed. */ +#if defined(__clang__) && !SDL_HAS_BUILTIN(_m_prefetch) #ifndef __PRFCHWINTRIN_H #define __PRFCHWINTRIN_H static __inline__ void __attribute__((__always_inline__, __nodebug__)) @@ -268,6 +268,7 @@ _m_prefetch(void *__P) #endif /* compiler version */ #ifdef SDL_WIKI_DOCUMENTATION_SECTION + /** * A macro to decide if the compiler supports `__attribute__((target))`. * @@ -280,12 +281,14 @@ _m_prefetch(void *__P) * \sa SDL_TARGETING */ #define SDL_HAS_TARGET_ATTRIBS - +#elif defined(__loongarch64) && defined(__GNUC__) && (__GNUC__ >= 15) +/* LoongArch requires GCC 15+ for target attribute support */ +# define SDL_HAS_TARGET_ATTRIBS #elif defined(__clang__) && defined(__has_attribute) # if __has_attribute(target) # define SDL_HAS_TARGET_ATTRIBS # endif -#elif defined(__GNUC__) && (__GNUC__ + (__GNUC_MINOR__ >= 9) > 4) /* gcc >= 4.9 */ +#elif defined(__GNUC__) && !defined(__loongarch64) && (__GNUC__ + (__GNUC_MINOR__ >= 9) > 4) /* gcc >= 4.9 */ # define SDL_HAS_TARGET_ATTRIBS #elif defined(__ICC) && __ICC >= 1600 # define SDL_HAS_TARGET_ATTRIBS diff --git a/libs/SDL3/include/SDL3/SDL_iostream.h b/libs/SDL3/include/SDL3/SDL_iostream.h index d1596f91..f369fde1 100644 --- a/libs/SDL3/include/SDL3/SDL_iostream.h +++ b/libs/SDL3/include/SDL3/SDL_iostream.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -111,7 +111,7 @@ typedef struct SDL_IOStreamInterface /** * Read up to `size` bytes from the data stream to the area pointed - * at by `ptr`. + * at by `ptr`. `size` will always be > 0. * * On an incomplete read, you should set `*status` to a value from the * SDL_IOStatus enum. You do not have to explicitly set this on @@ -123,7 +123,7 @@ typedef struct SDL_IOStreamInterface /** * Write exactly `size` bytes from the area pointed at by `ptr` - * to data stream. + * to data stream. `size` will always be > 0. * * On an incomplete write, you should set `*status` to a value from the * SDL_IOStatus enum. You do not have to explicitly set this on @@ -203,6 +203,8 @@ typedef struct SDL_IOStream SDL_IOStream; * - "w": Create an empty file for writing. If a file with the same name * already exists its content is erased and the file is treated as a new * empty file. + * - "wx": Create an empty file for writing. If a file with the same name + * already exists, the call fails. * - "a": Append to a file. Writing operations append data at the end of the * file. The file is created if it does not exist. * - "r+": Open a file for update both reading and writing. The file must @@ -210,6 +212,8 @@ typedef struct SDL_IOStream SDL_IOStream; * - "w+": Create an empty file for both reading and writing. If a file with * the same name already exists its content is erased and the file is * treated as a new empty file. + * - "w+x": Create an empty file for both reading and writing. If a file with + * the same name already exists, the call fails. * - "a+": Open a file for reading and appending. All writing operations are * performed at the end of the file, protecting the previous content to be * overwritten. You can reposition (fseek, rewind) the internal pointer to @@ -260,7 +264,7 @@ typedef struct SDL_IOStream SDL_IOStream; * \returns a pointer to the SDL_IOStream structure that is created or NULL on * failure; call SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. * @@ -286,8 +290,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromFile(const char *file, cons * certain size, for both read and write access. * * This memory buffer is not copied by the SDL_IOStream; the pointer you - * provide must remain valid until you close the stream. Closing the stream - * will not free the original buffer. + * provide must remain valid until you close the stream. * * If you need to make sure the SDL_IOStream never writes to the memory * buffer, you should use SDL_IOFromConstMem() with a read-only buffer of @@ -300,6 +303,13 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromFile(const char *file, cons * - `SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER`: this will be the `size` parameter * that was passed to this function. * + * Additionally, the following properties are recognized: + * + * - `SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC_POINTER`: if this property is set to + * a non-NULL value it will be interpreted as a function of SDL_free_func + * type and called with the passed `mem` pointer when closing the stream. By + * default it is unset, i.e., the memory will not be freed. + * * \param mem a pointer to a buffer to feed an SDL_IOStream stream. * \param size the buffer size, in bytes. * \returns a pointer to a new SDL_IOStream structure or NULL on failure; call @@ -321,6 +331,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromMem(void *mem, size_t size) #define SDL_PROP_IOSTREAM_MEMORY_POINTER "SDL.iostream.memory.base" #define SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER "SDL.iostream.memory.size" +#define SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC_POINTER "SDL.iostream.memory.free" /** * Use this function to prepare a read-only memory buffer for use with @@ -333,8 +344,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromMem(void *mem, size_t size) * without writing to the memory buffer. * * This memory buffer is not copied by the SDL_IOStream; the pointer you - * provide must remain valid until you close the stream. Closing the stream - * will not free the original buffer. + * provide must remain valid until you close the stream. * * If you need to write to a memory buffer, you should use SDL_IOFromMem() * with a writable buffer of memory instead. @@ -346,6 +356,13 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromMem(void *mem, size_t size) * - `SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER`: this will be the `size` parameter * that was passed to this function. * + * Additionally, the following properties are recognized: + * + * - `SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC_POINTER`: if this property is set to + * a non-NULL value it will be interpreted as a function of SDL_free_func + * type and called with the passed `mem` pointer when closing the stream. By + * default it is unset, i.e., the memory will not be freed. + * * \param mem a pointer to a read-only buffer to feed an SDL_IOStream stream. * \param size the buffer size, in bytes. * \returns a pointer to a new SDL_IOStream structure or NULL on failure; call @@ -452,7 +469,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_OpenIO(const SDL_IOStreamInterfac * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -467,7 +484,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_CloseIO(SDL_IOStream *context); * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -487,7 +504,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetIOProperties(SDL_IOStream *c * \param context the SDL_IOStream to query. * \returns an SDL_IOStatus enum with the current state. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -501,7 +518,7 @@ extern SDL_DECLSPEC SDL_IOStatus SDLCALL SDL_GetIOStatus(SDL_IOStream *context); * negative error code on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -528,7 +545,7 @@ extern SDL_DECLSPEC Sint64 SDLCALL SDL_GetIOSize(SDL_IOStream *context); * \returns the final offset in the data stream after the seek or -1 on * failure; call SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -548,7 +565,7 @@ extern SDL_DECLSPEC Sint64 SDLCALL SDL_SeekIO(SDL_IOStream *context, Sint64 offs * \returns the current offset in the stream, or -1 if the information can not * be determined. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -567,13 +584,17 @@ extern SDL_DECLSPEC Sint64 SDLCALL SDL_TellIO(SDL_IOStream *context); * the stream is not at EOF, SDL_GetIOStatus() will return a different error * value and SDL_GetError() will offer a human-readable message. * + * A request for zero bytes on a valid stream will return zero immediately + * without accessing the stream, so the stream status (EOF, err, etc) will not + * change. + * * \param context a pointer to an SDL_IOStream structure. * \param ptr a pointer to a buffer to read data into. * \param size the number of bytes to read from the data source. * \returns the number of bytes read, or 0 on end of file or other failure; * call SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -596,13 +617,17 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_ReadIO(SDL_IOStream *context, void *ptr, * recoverable, such as a non-blocking write that can simply be retried later, * or a fatal error. * + * A request for zero bytes on a valid stream will return zero immediately + * without accessing the stream, so the stream status (EOF, err, etc) will not + * change. + * * \param context a pointer to an SDL_IOStream structure. * \param ptr a pointer to a buffer containing data to write. * \param size the number of bytes to write. * \returns the number of bytes written, which will be less than `size` on * failure; call SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -626,7 +651,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_WriteIO(SDL_IOStream *context, const void * \returns the number of bytes written or 0 on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -646,7 +671,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_IOprintf(SDL_IOStream *context, SDL_PRINT * \returns the number of bytes written or 0 on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -666,7 +691,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_IOvprintf(SDL_IOStream *context, SDL_PRIN * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -692,7 +717,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_FlushIO(SDL_IOStream *context); * \returns the data or NULL on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -715,7 +740,7 @@ extern SDL_DECLSPEC void * SDLCALL SDL_LoadFile_IO(SDL_IOStream *src, size_t *da * \returns the data or NULL on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. * @@ -736,7 +761,7 @@ extern SDL_DECLSPEC void * SDLCALL SDL_LoadFile(const char *file, size_t *datasi * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -755,7 +780,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SaveFile_IO(SDL_IOStream *src, const void * * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. * @@ -784,7 +809,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SaveFile(const char *file, const void *data * \returns true on success or false on failure or EOF; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -803,7 +828,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU8(SDL_IOStream *src, Uint8 *value); * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -826,7 +851,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS8(SDL_IOStream *src, Sint8 *value); * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -849,7 +874,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU16LE(SDL_IOStream *src, Uint16 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -872,7 +897,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS16LE(SDL_IOStream *src, Sint16 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -895,7 +920,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU16BE(SDL_IOStream *src, Uint16 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -918,7 +943,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS16BE(SDL_IOStream *src, Sint16 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -941,7 +966,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU32LE(SDL_IOStream *src, Uint32 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -964,7 +989,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS32LE(SDL_IOStream *src, Sint32 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -987,7 +1012,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU32BE(SDL_IOStream *src, Uint32 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1010,7 +1035,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS32BE(SDL_IOStream *src, Sint32 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1033,7 +1058,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU64LE(SDL_IOStream *src, Uint64 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1056,7 +1081,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS64LE(SDL_IOStream *src, Sint64 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1079,7 +1104,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU64BE(SDL_IOStream *src, Uint64 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1101,7 +1126,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS64BE(SDL_IOStream *src, Sint64 *value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1115,7 +1140,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU8(SDL_IOStream *dst, Uint8 value); * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1134,7 +1159,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS8(SDL_IOStream *dst, Sint8 value); * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1153,7 +1178,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU16LE(SDL_IOStream *dst, Uint16 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1171,7 +1196,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS16LE(SDL_IOStream *dst, Sint16 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1189,7 +1214,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU16BE(SDL_IOStream *dst, Uint16 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1208,7 +1233,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS16BE(SDL_IOStream *dst, Sint16 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1227,7 +1252,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU32LE(SDL_IOStream *dst, Uint32 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1245,7 +1270,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS32LE(SDL_IOStream *dst, Sint32 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1263,7 +1288,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU32BE(SDL_IOStream *dst, Uint32 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1282,7 +1307,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS32BE(SDL_IOStream *dst, Sint32 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1301,7 +1326,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU64LE(SDL_IOStream *dst, Uint64 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1319,7 +1344,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS64LE(SDL_IOStream *dst, Sint64 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1337,7 +1362,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU64BE(SDL_IOStream *dst, Uint64 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ diff --git a/libs/SDL3/include/SDL3/SDL_joystick.h b/libs/SDL3/include/SDL3/SDL_joystick.h index cadccf68..c93b3521 100644 --- a/libs/SDL3/include/SDL3/SDL_joystick.h +++ b/libs/SDL3/include/SDL3/SDL_joystick.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -29,8 +29,8 @@ * instead. * * The term "instance_id" is the current instantiation of a joystick device in - * the system, if the joystick is removed and then re-inserted then it will - * get a new instance_id, instance_id's are monotonically increasing + * the system. If the joystick is removed and then re-inserted then it will + * get a new instance_id. instance_id's are monotonically increasing * identifiers of a joystick plugged in. * * The term "player_index" is the number assigned to a player on a specific @@ -48,6 +48,14 @@ * If you would like to receive joystick updates while the application is in * the background, you should set the following hint before calling * SDL_Init(): SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS + * + * SDL can provide virtual joysticks as well: the app defines an imaginary + * controller with SDL_AttachVirtualJoystick(), and then can provide inputs + * for it via SDL_SetJoystickVirtualAxis(), SDL_SetJoystickVirtualButton(), + * etc. As this data is supplied, it will look like a normal joystick to SDL, + * just not backed by a hardware driver. This has been used to make unusual + * devices, like VR headset controllers, look like normal joysticks, or + * provide recording/playback of game inputs, etc. */ #ifndef SDL_joystick_h_ @@ -174,6 +182,8 @@ typedef enum SDL_JoystickConnectionState * joysticks while processing to guarantee that the joystick list won't change * and joystick and gamepad events will not be delivered. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_LockJoysticks(void) SDL_ACQUIRE(SDL_joystick_lock); @@ -181,6 +191,9 @@ extern SDL_DECLSPEC void SDLCALL SDL_LockJoysticks(void) SDL_ACQUIRE(SDL_joystic /** * Unlocking for atomic access to the joystick API. * + * \threadsafety This should be called from the same thread that called + * SDL_LockJoysticks(). + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_UnlockJoysticks(void) SDL_RELEASE(SDL_joystick_lock); @@ -190,6 +203,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnlockJoysticks(void) SDL_RELEASE(SDL_joyst * * \returns true if a joystick is connected, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoysticks @@ -205,6 +220,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasJoystick(void); * call SDL_GetError() for more information. This should be freed * with SDL_free() when it is no longer needed. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_HasJoystick @@ -221,6 +238,8 @@ extern SDL_DECLSPEC SDL_JoystickID * SDLCALL SDL_GetJoysticks(int *count); * \returns the name of the selected joystick. If no name can be found, this * function returns NULL; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickName @@ -237,6 +256,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetJoystickNameForID(SDL_JoystickID * \returns the path of the selected joystick. If no path can be found, this * function returns NULL; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickPath @@ -252,6 +273,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetJoystickPathForID(SDL_JoystickID * \param instance_id the joystick instance ID. * \returns the player index of a joystick, or -1 if it's not available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickPlayerIndex @@ -268,6 +291,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetJoystickPlayerIndexForID(SDL_JoystickID i * \returns the GUID of the selected joystick. If called with an invalid * instance_id, this function returns a zero GUID. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickGUID @@ -285,6 +310,8 @@ extern SDL_DECLSPEC SDL_GUID SDLCALL SDL_GetJoystickGUIDForID(SDL_JoystickID ins * \returns the USB vendor ID of the selected joystick. If called with an * invalid instance_id, this function returns 0. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickVendor @@ -302,6 +329,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickVendorForID(SDL_JoystickID ins * \returns the USB product ID of the selected joystick. If called with an * invalid instance_id, this function returns 0. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickProduct @@ -319,6 +348,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickProductForID(SDL_JoystickID in * \returns the product version of the selected joystick. If called with an * invalid instance_id, this function returns 0. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickProductVersion @@ -336,6 +367,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickProductVersionForID(SDL_Joysti * invalid instance_id, this function returns * `SDL_JOYSTICK_TYPE_UNKNOWN`. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickType @@ -353,6 +386,8 @@ extern SDL_DECLSPEC SDL_JoystickType SDLCALL SDL_GetJoystickTypeForID(SDL_Joysti * \returns a joystick identifier or NULL on failure; call SDL_GetError() for * more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CloseJoystick @@ -366,6 +401,8 @@ extern SDL_DECLSPEC SDL_Joystick * SDLCALL SDL_OpenJoystick(SDL_JoystickID insta * \returns an SDL_Joystick on success or NULL on failure or if it hasn't been * opened yet; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_Joystick * SDLCALL SDL_GetJoystickFromID(SDL_JoystickID instance_id); @@ -377,6 +414,8 @@ extern SDL_DECLSPEC SDL_Joystick * SDLCALL SDL_GetJoystickFromID(SDL_JoystickID * \returns an SDL_Joystick on success or NULL on failure; call SDL_GetError() * for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickPlayerIndex @@ -469,13 +508,33 @@ SDL_COMPILE_TIME_ASSERT(SDL_VirtualJoystickDesc_SIZE, /** * Attach a new virtual joystick. * + * Apps can create virtual joysticks, that exist without hardware directly + * backing them, and have program-supplied inputs. Once attached, a virtual + * joystick looks like any other joystick that SDL can access. These can be + * used to make other things look like joysticks, or provide pre-recorded + * input, etc. + * + * Once attached, the app can send joystick inputs to the new virtual joystick + * using SDL_SetJoystickVirtualAxis(), etc. + * + * When no longer needed, the virtual joystick can be removed by calling + * SDL_DetachVirtualJoystick(). + * * \param desc joystick description, initialized using SDL_INIT_INTERFACE(). * \returns the joystick instance ID, or 0 on failure; call SDL_GetError() for * more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_DetachVirtualJoystick + * \sa SDL_SetJoystickVirtualAxis + * \sa SDL_SetJoystickVirtualButton + * \sa SDL_SetJoystickVirtualBall + * \sa SDL_SetJoystickVirtualHat + * \sa SDL_SetJoystickVirtualTouchpad + * \sa SDL_SetJoystickVirtualSensorData */ extern SDL_DECLSPEC SDL_JoystickID SDLCALL SDL_AttachVirtualJoystick(const SDL_VirtualJoystickDesc *desc); @@ -487,6 +546,8 @@ extern SDL_DECLSPEC SDL_JoystickID SDLCALL SDL_AttachVirtualJoystick(const SDL_V * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_AttachVirtualJoystick @@ -499,6 +560,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_DetachVirtualJoystick(SDL_JoystickID instan * \param instance_id the joystick instance ID. * \returns true if the joystick is virtual, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_IsJoystickVirtual(SDL_JoystickID instance_id); @@ -522,7 +585,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_IsJoystickVirtual(SDL_JoystickID instance_i * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_SetJoystickVirtualButton + * \sa SDL_SetJoystickVirtualBall + * \sa SDL_SetJoystickVirtualHat + * \sa SDL_SetJoystickVirtualTouchpad + * \sa SDL_SetJoystickVirtualSensorData */ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value); @@ -542,7 +613,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualAxis(SDL_Joystick *joysti * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_SetJoystickVirtualAxis + * \sa SDL_SetJoystickVirtualButton + * \sa SDL_SetJoystickVirtualHat + * \sa SDL_SetJoystickVirtualTouchpad + * \sa SDL_SetJoystickVirtualSensorData */ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualBall(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel); @@ -561,7 +640,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualBall(SDL_Joystick *joysti * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_SetJoystickVirtualAxis + * \sa SDL_SetJoystickVirtualBall + * \sa SDL_SetJoystickVirtualHat + * \sa SDL_SetJoystickVirtualTouchpad + * \sa SDL_SetJoystickVirtualSensorData */ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, int button, bool down); @@ -580,7 +667,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualButton(SDL_Joystick *joys * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_SetJoystickVirtualAxis + * \sa SDL_SetJoystickVirtualButton + * \sa SDL_SetJoystickVirtualBall + * \sa SDL_SetJoystickVirtualTouchpad + * \sa SDL_SetJoystickVirtualSensorData */ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value); @@ -606,7 +701,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualHat(SDL_Joystick *joystic * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_SetJoystickVirtualAxis + * \sa SDL_SetJoystickVirtualButton + * \sa SDL_SetJoystickVirtualBall + * \sa SDL_SetJoystickVirtualHat + * \sa SDL_SetJoystickVirtualSensorData */ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualTouchpad(SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure); @@ -628,7 +731,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickVirtualTouchpad(SDL_Joystick *jo * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_SetJoystickVirtualAxis + * \sa SDL_SetJoystickVirtualButton + * \sa SDL_SetJoystickVirtualBall + * \sa SDL_SetJoystickVirtualHat + * \sa SDL_SetJoystickVirtualTouchpad */ extern SDL_DECLSPEC bool SDLCALL SDL_SendJoystickVirtualSensorData(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values); @@ -652,6 +763,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SendJoystickVirtualSensorData(SDL_Joystick * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetJoystickProperties(SDL_Joystick *joystick); @@ -669,6 +782,8 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetJoystickProperties(SDL_Joyst * \returns the name of the selected joystick. If no name can be found, this * function returns NULL; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickNameForID @@ -682,6 +797,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetJoystickName(SDL_Joystick *joyst * \returns the path of the selected joystick. If no path can be found, this * function returns NULL; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickPathForID @@ -697,6 +814,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetJoystickPath(SDL_Joystick *joyst * \param joystick the SDL_Joystick obtained from SDL_OpenJoystick(). * \returns the player index, or -1 if it's not available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetJoystickPlayerIndex @@ -712,6 +831,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetJoystickPlayerIndex(SDL_Joystick *joystic * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickPlayerIndex @@ -728,6 +849,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickPlayerIndex(SDL_Joystick *joysti * this function returns a zero GUID; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickGUIDForID @@ -743,6 +866,8 @@ extern SDL_DECLSPEC SDL_GUID SDLCALL SDL_GetJoystickGUID(SDL_Joystick *joystick) * \param joystick the SDL_Joystick obtained from SDL_OpenJoystick(). * \returns the USB vendor ID of the selected joystick, or 0 if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickVendorForID @@ -757,6 +882,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickVendor(SDL_Joystick *joystick) * \param joystick the SDL_Joystick obtained from SDL_OpenJoystick(). * \returns the USB product ID of the selected joystick, or 0 if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickProductForID @@ -771,6 +898,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickProduct(SDL_Joystick *joystick * \param joystick the SDL_Joystick obtained from SDL_OpenJoystick(). * \returns the product version of the selected joystick, or 0 if unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickProductVersionForID @@ -786,6 +915,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickProductVersion(SDL_Joystick *j * \returns the firmware version of the selected joystick, or 0 if * unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickFirmwareVersion(SDL_Joystick *joystick); @@ -799,6 +930,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickFirmwareVersion(SDL_Joystick * * \returns the serial number of the selected joystick, or NULL if * unavailable. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC const char * SDLCALL SDL_GetJoystickSerial(SDL_Joystick *joystick); @@ -809,6 +942,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetJoystickSerial(SDL_Joystick *joy * \param joystick the SDL_Joystick obtained from SDL_OpenJoystick(). * \returns the SDL_JoystickType of the selected joystick. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickTypeForID @@ -828,6 +963,8 @@ extern SDL_DECLSPEC SDL_JoystickType SDLCALL SDL_GetJoystickType(SDL_Joystick *j * \param crc16 a pointer filled in with a CRC used to distinguish different * products with the same VID/PID, or 0 if not available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickGUIDForID @@ -841,6 +978,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_GetJoystickGUIDInfo(SDL_GUID guid, Uint16 * * \returns true if the joystick has been opened, false if it has not; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_JoystickConnected(SDL_Joystick *joystick); @@ -852,6 +991,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_JoystickConnected(SDL_Joystick *joystick); * \returns the instance ID of the specified joystick on success or 0 on * failure; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_JoystickID SDLCALL SDL_GetJoystickID(SDL_Joystick *joystick); @@ -867,6 +1008,8 @@ extern SDL_DECLSPEC SDL_JoystickID SDLCALL SDL_GetJoystickID(SDL_Joystick *joyst * \returns the number of axis controls/number of axes on success or -1 on * failure; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickAxis @@ -888,6 +1031,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumJoystickAxes(SDL_Joystick *joystick); * \returns the number of trackballs on success or -1 on failure; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickBall @@ -904,6 +1049,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumJoystickBalls(SDL_Joystick *joystick); * \returns the number of POV hats on success or -1 on failure; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickHat @@ -920,6 +1067,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumJoystickHats(SDL_Joystick *joystick); * \returns the number of buttons on success or -1 on failure; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetJoystickButton @@ -938,6 +1087,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumJoystickButtons(SDL_Joystick *joystick * * \param enabled whether to process joystick events or not. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_JoystickEventsEnabled @@ -954,6 +1105,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetJoystickEventsEnabled(bool enabled); * * \returns true if joystick events are being processed, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetJoystickEventsEnabled @@ -966,6 +1119,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_JoystickEventsEnabled(void); * This is called automatically by the event loop if any joystick events are * enabled. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_UpdateJoysticks(void); @@ -988,6 +1143,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UpdateJoysticks(void); * \returns a 16-bit signed integer representing the current position of the * axis or 0 on failure; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetNumJoystickAxes @@ -1006,6 +1163,8 @@ extern SDL_DECLSPEC Sint16 SDLCALL SDL_GetJoystickAxis(SDL_Joystick *joystick, i * \param state upon return, the initial value is supplied here. * \returns true if this axis has any initial value, or false if not. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetJoystickAxisInitialState(SDL_Joystick *joystick, int axis, Sint16 *state); @@ -1025,6 +1184,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetJoystickAxisInitialState(SDL_Joystick *j * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetNumJoystickBalls @@ -1040,6 +1201,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetJoystickBall(SDL_Joystick *joystick, int * \param hat the hat index to get the state from; indices start at index 0. * \returns the current hat position. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetNumJoystickHats @@ -1064,6 +1227,8 @@ extern SDL_DECLSPEC Uint8 SDLCALL SDL_GetJoystickHat(SDL_Joystick *joystick, int * index 0. * \returns true if the button is pressed, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetNumJoystickButtons @@ -1087,6 +1252,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetJoystickButton(SDL_Joystick *joystick, i * \param duration_ms the duration of the rumble effect, in milliseconds. * \returns true, or false if rumble isn't supported on this joystick. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_RumbleJoystick(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms); @@ -1114,6 +1281,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RumbleJoystick(SDL_Joystick *joystick, Uint * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_RumbleJoystick @@ -1136,6 +1305,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RumbleJoystickTriggers(SDL_Joystick *joysti * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue); @@ -1149,6 +1320,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickLED(SDL_Joystick *joystick, Uint * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_SendJoystickEffect(SDL_Joystick *joystick, const void *data, int size); @@ -1158,6 +1331,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SendJoystickEffect(SDL_Joystick *joystick, * * \param joystick the joystick device to close. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_OpenJoystick @@ -1172,6 +1347,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_CloseJoystick(SDL_Joystick *joystick); * `SDL_JOYSTICK_CONNECTION_INVALID` on failure; call SDL_GetError() * for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_JoystickConnectionState SDLCALL SDL_GetJoystickConnectionState(SDL_Joystick *joystick); @@ -1193,6 +1370,8 @@ extern SDL_DECLSPEC SDL_JoystickConnectionState SDLCALL SDL_GetJoystickConnectio * \returns the current battery state or `SDL_POWERSTATE_ERROR` on failure; * call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_PowerState SDLCALL SDL_GetJoystickPowerInfo(SDL_Joystick *joystick, int *percent); diff --git a/libs/SDL3/include/SDL3/SDL_keyboard.h b/libs/SDL3/include/SDL3/SDL_keyboard.h index afa77b6c..d14ab6fb 100644 --- a/libs/SDL3/include/SDL3/SDL_keyboard.h +++ b/libs/SDL3/include/SDL3/SDL_keyboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -174,8 +174,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_ResetKeyboard(void); /** * Get the current key modifier state for the keyboard. * - * \returns an OR'd combination of the modifier keys for the keyboard. See - * SDL_Keymod for details. + * \returns an OR'd combination of the modifier keys for the keyboard. * * \threadsafety It is safe to call this function from any thread. * diff --git a/libs/SDL3/include/SDL3/SDL_keycode.h b/libs/SDL3/include/SDL3/SDL_keycode.h index 61b68e79..1818ee3e 100644 --- a/libs/SDL3/include/SDL3/SDL_keycode.h +++ b/libs/SDL3/include/SDL3/SDL_keycode.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -45,12 +45,16 @@ * `SDLK_*` constant for those keys that do not generate characters. * * A special exception is the number keys at the top of the keyboard which map - * to SDLK_0...SDLK_9 on AZERTY layouts. + * by default to SDLK_0...SDLK_9 on AZERTY layouts. * * Keys with the `SDLK_EXTENDED_MASK` bit set do not map to a scancode or - * unicode code point. + * Unicode code point. + * + * Many common keycodes are listed below, but this list is not exhaustive. * * \since This datatype is available since SDL 3.2.0. + * + * \sa SDL_HINT_KEYCODE_OPTIONS */ typedef Uint32 SDL_Keycode; diff --git a/libs/SDL3/include/SDL3/SDL_loadso.h b/libs/SDL3/include/SDL3/SDL_loadso.h index f8649d75..51d0d881 100644 --- a/libs/SDL3/include/SDL3/SDL_loadso.h +++ b/libs/SDL3/include/SDL3/SDL_loadso.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_locale.h b/libs/SDL3/include/SDL3/SDL_locale.h index 902843e8..9f03aa90 100644 --- a/libs/SDL3/include/SDL3/SDL_locale.h +++ b/libs/SDL3/include/SDL3/SDL_locale.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -102,6 +102,8 @@ typedef struct SDL_Locale * allocation that should be freed with SDL_free() when it is no * longer needed. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_Locale ** SDLCALL SDL_GetPreferredLocales(int *count); diff --git a/libs/SDL3/include/SDL3/SDL_log.h b/libs/SDL3/include/SDL3/SDL_log.h index 2019c3b4..f193abfb 100644 --- a/libs/SDL3/include/SDL3/SDL_log.h +++ b/libs/SDL3/include/SDL3/SDL_log.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -266,7 +266,6 @@ extern SDL_DECLSPEC void SDLCALL SDL_Log(SDL_PRINTF_FORMAT_STRING const char *fm * \sa SDL_LogInfo * \sa SDL_LogMessage * \sa SDL_LogMessageV - * \sa SDL_LogTrace * \sa SDL_LogVerbose * \sa SDL_LogWarn */ @@ -488,7 +487,8 @@ typedef void (SDLCALL *SDL_LogOutputFunction)(void *userdata, int category, SDL_ /** * Get the default log output function. * - * \returns the default log output callback. + * \returns the default log output callback. It should be called with NULL for + * the userdata argument. * * \threadsafety It is safe to call this function from any thread. * diff --git a/libs/SDL3/include/SDL3/SDL_main.h b/libs/SDL3/include/SDL3/SDL_main.h index 305a1a38..771572f2 100644 --- a/libs/SDL3/include/SDL3/SDL_main.h +++ b/libs/SDL3/include/SDL3/SDL_main.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -110,7 +110,8 @@ * Even if available, an app can define SDL_MAIN_HANDLED and provide their * own, if they know what they're doing. * - * This macro is used internally by SDL, and apps probably shouldn't rely on it. + * This macro is used internally by SDL, and apps probably shouldn't rely on + * it. * * \since This macro is available since SDL 3.2.0. */ @@ -125,10 +126,11 @@ * This macro is defined by `SDL_main.h`, which is not automatically included * by `SDL.h`. * - * Even if required, an app can define SDL_MAIN_HANDLED and provide their - * own, if they know what they're doing. + * Even if required, an app can define SDL_MAIN_HANDLED and provide their own, + * if they know what they're doing. * - * This macro is used internally by SDL, and apps probably shouldn't rely on it. + * This macro is used internally by SDL, and apps probably shouldn't rely on + * it. * * \since This macro is available since SDL 3.2.0. */ @@ -165,12 +167,10 @@ */ #define SDL_MAIN_NEEDED - #elif defined(SDL_PLATFORM_IOS) - /* On iOS SDL provides a main function that creates an application delegate - and starts the iOS application run loop. + #elif defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS) + /* On iOS and tvOS SDL provides a main function that creates an application delegate and starts the application run loop. - To use it, just #include SDL_main.h in the source file that contains your - main() function. + To use it, just #include in the source file that contains your main() function. See src/video/uikit/SDL_uikitappdelegate.m for more details. */ @@ -257,7 +257,7 @@ #else /* usually this is empty */ #define SDLMAIN_DECLSPEC -#endif /* SDL_MAIN_EXPORTED */ +#endif /* SDL_WIKI_DOCUMENTATION_SECTION */ #if defined(SDL_MAIN_NEEDED) || defined(SDL_MAIN_AVAILABLE) || defined(SDL_MAIN_USE_CALLBACKS) #define main SDL_main @@ -333,6 +333,9 @@ extern "C" { * \returns SDL_APP_FAILURE to terminate with an error, SDL_APP_SUCCESS to * terminate with success, SDL_APP_CONTINUE to continue. * + * \threadsafety This function is called once by SDL, at startup, on a single + * thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_AppIterate @@ -449,8 +452,8 @@ extern SDLMAIN_DECLSPEC SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_E * * This function is called once by SDL before terminating the program. * - * This function will be called no matter what, even if SDL_AppInit requests - * termination. + * This function will be called in all cases, even if SDL_AppInit requests + * termination at startup. * * This function should not go into an infinite mainloop; it should * deinitialize any resources necessary, perform whatever shutdown activities, @@ -537,6 +540,8 @@ extern SDLMAIN_DECLSPEC int SDLCALL SDL_main(int argc, char *argv[]); * will not be changed it is necessary to define SDL_MAIN_HANDLED before * including SDL.h. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_Init @@ -553,6 +558,9 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetMainReady(void); * using SDL_main (like when using SDL_MAIN_HANDLED). When using this, you do * *not* need SDL_SetMainReady(). * + * If `argv` is NULL, SDL will provide command line arguments, either by + * querying the OS for them if possible, or supplying a filler array if not. + * * \param argc the argc parameter from the application's main() function, or 0 * if the platform's main-equivalent has no argc. * \param argv the argv parameter from the application's main() function, or @@ -615,16 +623,19 @@ extern SDL_DECLSPEC int SDLCALL SDL_EnterAppMainCallbacks(int argc, char *argv[] * Most applications do not need to, and should not, call this directly; SDL * will call it when initializing the video subsystem. * + * If `name` is NULL, SDL currently uses `(CS_BYTEALIGNCLIENT | CS_OWNDC)` for + * the style, regardless of what is specified here. + * * \param name the window class name, in UTF-8 encoding. If NULL, SDL * currently uses "SDL_app" but this isn't guaranteed. - * \param style the value to use in WNDCLASSEX::style. If `name` is NULL, SDL - * currently uses `(CS_BYTEALIGNCLIENT \| CS_OWNDC)` regardless - * of what is specified here. + * \param style the value to use in WNDCLASSEX::style. * \param hInst the HINSTANCE to use in WNDCLASSEX::hInstance. If zero, SDL * will use `GetModuleHandle(NULL)` instead. * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_RegisterApp(const char *name, Uint32 style, void *hInst); @@ -642,6 +653,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RegisterApp(const char *name, Uint32 style, * deregistered when the registration counter in SDL_RegisterApp decrements to * zero through calls to this function. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_UnregisterApp(void); @@ -651,10 +664,26 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnregisterApp(void); /** * Callback from the application to let the suspend continue. * + * This should be called from an event watch in response to an + * `SDL_EVENT_DID_ENTER_BACKGROUND` event. + * + * When using SDL_Render, your event watch should be added _after_ creating + * the `SDL_Renderer`; this allows the timing of the D3D12 command queue + * suspension to execute in the correct order. + * + * When using SDL_GPU, this should be called after calling SDL_GDKSuspendGPU. + * + * If you're writing your own D3D12 renderer, this should be called after + * calling `ID3D12CommandQueue::SuspendX`. + * * This function is only needed for Xbox GDK support; all other platforms will * do nothing and set an "unsupported" error message. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_AddEventWatch */ extern SDL_DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void); diff --git a/libs/SDL3/include/SDL3/SDL_main_impl.h b/libs/SDL3/include/SDL3/SDL_main_impl.h index 14ebb429..bf5f5836 100644 --- a/libs/SDL3/include/SDL3/SDL_main_impl.h +++ b/libs/SDL3/include/SDL3/SDL_main_impl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_messagebox.h b/libs/SDL3/include/SDL3/SDL_messagebox.h index 365ae36a..af604e2c 100644 --- a/libs/SDL3/include/SDL3/SDL_messagebox.h +++ b/libs/SDL3/include/SDL3/SDL_messagebox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -168,6 +168,8 @@ typedef struct SDL_MessageBoxData * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_ShowSimpleMessageBox @@ -210,6 +212,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ShowMessageBox(const SDL_MessageBoxData *me * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_ShowMessageBox diff --git a/libs/SDL3/include/SDL3/SDL_metal.h b/libs/SDL3/include/SDL3/SDL_metal.h index 14b1bc87..6b0e171b 100644 --- a/libs/SDL3/include/SDL3/SDL_metal.h +++ b/libs/SDL3/include/SDL3/SDL_metal.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -65,6 +65,8 @@ typedef void *SDL_MetalView; * \param window the window. * \returns handle NSView or UIView. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_Metal_DestroyView @@ -80,6 +82,8 @@ extern SDL_DECLSPEC SDL_MetalView SDLCALL SDL_Metal_CreateView(SDL_Window *windo * * \param view the SDL_MetalView object. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_Metal_CreateView @@ -92,6 +96,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_Metal_DestroyView(SDL_MetalView view); * \param view the SDL_MetalView object. * \returns a pointer. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void * SDLCALL SDL_Metal_GetLayer(SDL_MetalView view); diff --git a/libs/SDL3/include/SDL3/SDL_misc.h b/libs/SDL3/include/SDL3/SDL_misc.h index 3dd6fcdd..654c0055 100644 --- a/libs/SDL3/include/SDL3/SDL_misc.h +++ b/libs/SDL3/include/SDL3/SDL_misc.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -65,6 +65,8 @@ extern "C" { * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_OpenURL(const char *url); diff --git a/libs/SDL3/include/SDL3/SDL_mouse.h b/libs/SDL3/include/SDL3/SDL_mouse.h index 4ecd1d00..fb03c017 100644 --- a/libs/SDL3/include/SDL3/SDL_mouse.h +++ b/libs/SDL3/include/SDL3/SDL_mouse.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -130,6 +130,17 @@ typedef enum SDL_MouseWheelDirection SDL_MOUSEWHEEL_FLIPPED /**< The scroll direction is flipped / natural */ } SDL_MouseWheelDirection; +/** + * Animated cursor frame info. + * + * \since This struct is available since SDL 3.4.0. + */ +typedef struct SDL_CursorFrameInfo +{ + SDL_Surface *surface; /**< The surface data for this frame */ + Uint32 duration; /**< The frame duration in milliseconds (a duration of 0 is infinite) */ +} SDL_CursorFrameInfo; + /** * A bitmask of pressed mouse buttons, as reported by SDL_GetMouseState, etc. * @@ -192,10 +203,10 @@ typedef Uint32 SDL_MouseButtonFlags; * \sa SDL_SetRelativeMouseTransform */ typedef void (SDLCALL *SDL_MouseMotionTransformCallback)( - void *userdata, - Uint64 timestamp, - SDL_Window *window, - SDL_MouseID mouseID, + void *userdata, + Uint64 timestamp, + SDL_Window *window, + SDL_MouseID mouseID, float *x, float *y ); @@ -565,6 +576,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_CaptureMouse(bool enabled); * * \since This function is available since SDL 3.2.0. * + * \sa SDL_CreateAnimatedCursor * \sa SDL_CreateColorCursor * \sa SDL_CreateSystemCursor * \sa SDL_DestroyCursor @@ -581,9 +593,10 @@ extern SDL_DECLSPEC SDL_Cursor * SDLCALL SDL_CreateCursor(const Uint8 *data, * 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 + * representations will be used for high DPI situations if + * SDL_HINT_MOUSE_DPI_SCALE_CURSORS is enabled. For example, if the original + * surface is 32x32, then on a 2x macOS display or 200% display scale on + * Windows, a 64x64 version of the image will be used, if available. If a * matching version of the image isn't available, the closest larger size * image will be downscaled to the appropriate size and be used instead, if * available. Otherwise, the closest smaller image will be upscaled and be @@ -600,6 +613,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_CreateAnimatedCursor * \sa SDL_CreateCursor * \sa SDL_CreateSystemCursor * \sa SDL_DestroyCursor @@ -609,6 +623,57 @@ extern SDL_DECLSPEC SDL_Cursor * SDLCALL SDL_CreateColorCursor(SDL_Surface *surf int hot_x, int hot_y); +/** + * Create an animated color cursor. + * + * Animated cursors are composed of a sequential array of frames, specified as + * surfaces and durations in an array of SDL_CursorFrameInfo structs. The hot + * spot coordinates are universal to all frames, and all frames must have the + * same dimensions. + * + * Frame durations are specified in milliseconds. A duration of 0 implies an + * infinite frame time, and the animation will stop on that frame. To create a + * one-shot animation, set the duration of the last frame in the sequence to + * 0. + * + * If this function is passed surfaces with alternate representations added + * with SDL_AddSurfaceAlternateImage(), the surfaces will be interpreted as + * the content to be used for 100% display scale, and the alternate + * representations will be used for high DPI situations. For example, if the + * original surfaces are 32x32, then on a 2x macOS display or 200% display + * scale on Windows, a 64x64 version of the image will be used, if available. + * If a matching version of the image isn't available, the closest larger size + * image will be downscaled to the appropriate size and be used instead, if + * available. Otherwise, the closest smaller image will be upscaled and be + * used instead. + * + * If the underlying platform does not support animated cursors, this function + * will fall back to creating a static color cursor using the first frame in + * the sequence. + * + * \param frames an array of cursor images composing the animation. + * \param frame_count the number of frames in the sequence. + * \param hot_x the x position of the cursor hot spot. + * \param hot_y the y position of the cursor hot spot. + * \returns the new cursor on success or NULL on failure; call SDL_GetError() + * for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_AddSurfaceAlternateImage + * \sa SDL_CreateCursor + * \sa SDL_CreateColorCursor + * \sa SDL_CreateSystemCursor + * \sa SDL_DestroyCursor + * \sa SDL_SetCursor + */ +extern SDL_DECLSPEC SDL_Cursor *SDLCALL SDL_CreateAnimatedCursor(SDL_CursorFrameInfo *frames, + int frame_count, + int hot_x, + int hot_y); + /** * Create a system cursor. * @@ -666,7 +731,7 @@ extern SDL_DECLSPEC SDL_Cursor * SDLCALL SDL_GetCursor(void); * You do not have to call SDL_DestroyCursor() on the return value, but it is * safe to do so. * - * \returns the default cursor on success or NULL on failuree; call + * \returns the default cursor on success or NULL on failure; call * SDL_GetError() for more information. * * \threadsafety This function should only be called on the main thread. @@ -687,6 +752,7 @@ extern SDL_DECLSPEC SDL_Cursor * SDLCALL SDL_GetDefaultCursor(void); * * \since This function is available since SDL 3.2.0. * + * \sa SDL_CreateAnimatedCursor * \sa SDL_CreateColorCursor * \sa SDL_CreateCursor * \sa SDL_CreateSystemCursor diff --git a/libs/SDL3/include/SDL3/SDL_mutex.h b/libs/SDL3/include/SDL3/SDL_mutex.h index f1b66404..29e8f1a1 100644 --- a/libs/SDL3/include/SDL3/SDL_mutex.h +++ b/libs/SDL3/include/SDL3/SDL_mutex.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -308,6 +308,8 @@ typedef struct SDL_Mutex SDL_Mutex; * \returns the initialized and unlocked mutex or NULL on failure; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_DestroyMutex @@ -334,6 +336,8 @@ extern SDL_DECLSPEC SDL_Mutex * SDLCALL SDL_CreateMutex(void); * * \param mutex the mutex to lock. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_TryLockMutex @@ -355,12 +359,14 @@ extern SDL_DECLSPEC void SDLCALL SDL_LockMutex(SDL_Mutex *mutex) SDL_ACQUIRE(mut * \param mutex the mutex to try to lock. * \returns true on success, false if the mutex would block. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LockMutex * \sa SDL_UnlockMutex */ -extern SDL_DECLSPEC bool SDLCALL SDL_TryLockMutex(SDL_Mutex *mutex) SDL_TRY_ACQUIRE(0, mutex); +extern SDL_DECLSPEC bool SDLCALL SDL_TryLockMutex(SDL_Mutex *mutex) SDL_TRY_ACQUIRE(true, mutex); /** * Unlock the mutex. @@ -374,6 +380,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_TryLockMutex(SDL_Mutex *mutex) SDL_TRY_ACQU * * \param mutex the mutex to unlock. * + * \threadsafety This call must be paired with a previous locking call on the + * same thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LockMutex @@ -392,6 +401,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnlockMutex(SDL_Mutex *mutex) SDL_RELEASE(m * * \param mutex the mutex to destroy. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CreateMutex @@ -457,6 +468,8 @@ typedef struct SDL_RWLock SDL_RWLock; * \returns the initialized and unlocked read/write lock or NULL on failure; * call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_DestroyRWLock @@ -497,6 +510,8 @@ extern SDL_DECLSPEC SDL_RWLock * SDLCALL SDL_CreateRWLock(void); * * \param rwlock the read/write lock to lock. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LockRWLockForWriting @@ -528,6 +543,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_LockRWLockForReading(SDL_RWLock *rwlock) SD * * \param rwlock the read/write lock to lock. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LockRWLockForReading @@ -553,13 +570,15 @@ extern SDL_DECLSPEC void SDLCALL SDL_LockRWLockForWriting(SDL_RWLock *rwlock) SD * \param rwlock the rwlock to try to lock. * \returns true on success, false if the lock would block. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LockRWLockForReading * \sa SDL_TryLockRWLockForWriting * \sa SDL_UnlockRWLock */ -extern SDL_DECLSPEC bool SDLCALL SDL_TryLockRWLockForReading(SDL_RWLock *rwlock) SDL_TRY_ACQUIRE_SHARED(0, rwlock); +extern SDL_DECLSPEC bool SDLCALL SDL_TryLockRWLockForReading(SDL_RWLock *rwlock) SDL_TRY_ACQUIRE_SHARED(true, rwlock); /** * Try to lock a read/write lock _for writing_ without blocking. @@ -583,13 +602,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_TryLockRWLockForReading(SDL_RWLock *rwlock) * \param rwlock the rwlock to try to lock. * \returns true on success, false if the lock would block. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LockRWLockForWriting * \sa SDL_TryLockRWLockForReading * \sa SDL_UnlockRWLock */ -extern SDL_DECLSPEC bool SDLCALL SDL_TryLockRWLockForWriting(SDL_RWLock *rwlock) SDL_TRY_ACQUIRE(0, rwlock); +extern SDL_DECLSPEC bool SDLCALL SDL_TryLockRWLockForWriting(SDL_RWLock *rwlock) SDL_TRY_ACQUIRE(true, rwlock); /** * Unlock the read/write lock. @@ -607,6 +628,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_TryLockRWLockForWriting(SDL_RWLock *rwlock) * * \param rwlock the rwlock to unlock. * + * \threadsafety This call must be paired with a previous locking call on the + * same thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_LockRWLockForReading @@ -627,6 +651,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnlockRWLock(SDL_RWLock *rwlock) SDL_RELEAS * * \param rwlock the rwlock to destroy. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CreateRWLock @@ -670,6 +696,8 @@ typedef struct SDL_Semaphore SDL_Semaphore; * \returns a new semaphore or NULL on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_DestroySemaphore @@ -689,6 +717,8 @@ extern SDL_DECLSPEC SDL_Semaphore * SDLCALL SDL_CreateSemaphore(Uint32 initial_v * * \param sem the semaphore to destroy. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CreateSemaphore @@ -707,6 +737,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroySemaphore(SDL_Semaphore *sem); * * \param sem the semaphore wait on. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SignalSemaphore @@ -726,6 +758,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_WaitSemaphore(SDL_Semaphore *sem); * \param sem the semaphore to wait on. * \returns true if the wait succeeds, false if the wait would block. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SignalSemaphore @@ -746,6 +780,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_TryWaitSemaphore(SDL_Semaphore *sem); * indefinitely. * \returns true if the wait succeeds or false if the wait times out. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SignalSemaphore @@ -759,6 +795,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WaitSemaphoreTimeout(SDL_Semaphore *sem, Si * * \param sem the semaphore to increment. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_TryWaitSemaphore @@ -773,6 +811,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_SignalSemaphore(SDL_Semaphore *sem); * \param sem the semaphore to query. * \returns the current value of the semaphore. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC Uint32 SDLCALL SDL_GetSemaphoreValue(SDL_Semaphore *sem); @@ -806,6 +846,8 @@ typedef struct SDL_Condition SDL_Condition; * \returns a new condition variable or NULL on failure; call SDL_GetError() * for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_BroadcastCondition @@ -821,6 +863,8 @@ extern SDL_DECLSPEC SDL_Condition * SDLCALL SDL_CreateCondition(void); * * \param cond the condition variable to destroy. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CreateCondition diff --git a/libs/SDL3/include/SDL3/SDL_oldnames.h b/libs/SDL3/include/SDL3/SDL_oldnames.h index c93607e3..cbf04533 100644 --- a/libs/SDL3/include/SDL3/SDL_oldnames.h +++ b/libs/SDL3/include/SDL3/SDL_oldnames.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -796,6 +796,7 @@ #define SDL_TEXTEDITING_EXT SDL_TEXTEDITING_EXT_renamed_SDL_EVENT_TEXT_EDITING_EXT #define SDL_TEXTINPUT SDL_TEXTINPUT_renamed_SDL_EVENT_TEXT_INPUT #define SDL_USEREVENT SDL_USEREVENT_renamed_SDL_EVENT_USER +#define SDL_WINDOWEVENT SDL_WINDOWEVENT_deprecated_use_SDL_EVENT_WINDOW_NAME #define SDL_WINDOWEVENT_CLOSE SDL_WINDOWEVENT_CLOSE_renamed_SDL_EVENT_WINDOW_CLOSE_REQUESTED #define SDL_WINDOWEVENT_DISPLAY_CHANGED SDL_WINDOWEVENT_DISPLAY_CHANGED_renamed_SDL_EVENT_WINDOW_DISPLAY_CHANGED #define SDL_WINDOWEVENT_ENTER SDL_WINDOWEVENT_ENTER_renamed_SDL_EVENT_WINDOW_MOUSE_ENTER @@ -870,6 +871,7 @@ #define SDL_GameControllerBindType SDL_GameControllerBindType_renamed_SDL_GamepadBindingType #define SDL_GameControllerButton SDL_GameControllerButton_renamed_SDL_GamepadButton #define SDL_GameControllerClose SDL_GameControllerClose_renamed_SDL_CloseGamepad +#define SDL_GameControllerEventState SDL_GameControllerEventState_deprecated_use_SDL_SetGamepadEventsEnabled_true_false #define SDL_GameControllerFromInstanceID SDL_GameControllerFromInstanceID_renamed_SDL_GetGamepadFromID #define SDL_GameControllerFromPlayerIndex SDL_GameControllerFromPlayerIndex_renamed_SDL_GetGamepadFromPlayerIndex #define SDL_GameControllerGetAppleSFSymbolsNameForAxis SDL_GameControllerGetAppleSFSymbolsNameForAxis_renamed_SDL_GetGamepadAppleSFSymbolsNameForAxis @@ -947,6 +949,7 @@ /* ##SDL_hints.h */ #define SDL_DelHintCallback SDL_DelHintCallback_renamed_SDL_RemoveHintCallback +#define SDL_HINT_ACCELEROMETER_AS_JOYSTICK SDL_HINT_ACCELEROMETER_AS_JOYSTICK_deprecated_use_SDL_GamepadHasSensor_and_SDL_SetGamepadSensorEnabled #define SDL_HINT_ALLOW_TOPMOST SDL_HINT_ALLOW_TOPMOST_renamed_SDL_HINT_WINDOW_ALLOW_TOPMOST #define SDL_HINT_DIRECTINPUT_ENABLED SDL_HINT_DIRECTINPUT_ENABLED_renamed_SDL_HINT_JOYSTICK_DIRECTINPUT #define SDL_HINT_GDK_TEXTINPUT_DEFAULT SDL_HINT_GDK_TEXTINPUT_DEFAULT_renamed_SDL_HINT_GDK_TEXTINPUT_DEFAULT_TEXT @@ -957,6 +960,8 @@ #define SDL_HINT_LINUX_HAT_DEADZONES SDL_HINT_LINUX_HAT_DEADZONES_renamed_SDL_HINT_JOYSTICK_LINUX_HAT_DEADZONES #define SDL_HINT_LINUX_JOYSTICK_CLASSIC SDL_HINT_LINUX_JOYSTICK_CLASSIC_renamed_SDL_HINT_JOYSTICK_LINUX_CLASSIC #define SDL_HINT_LINUX_JOYSTICK_DEADZONES SDL_HINT_LINUX_JOYSTICK_DEADZONES_renamed_SDL_HINT_JOYSTICK_LINUX_DEADZONES +#define SDL_HINT_RENDER_SCALE_QUALITY SDL_HINT_RENDER_SCALE_QUALITY_deprecated_use_SDL_SetTextureScaleMode_with_SDL_SCALEMODE_NEAREST +#define SDL_HINT_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS SDL_HINT_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS_deprecated_remove_this_line /* ##SDL_joystick.h */ #define SDL_JOYSTICK_TYPE_GAMECONTROLLER SDL_JOYSTICK_TYPE_GAMECONTROLLER_renamed_SDL_JOYSTICK_TYPE_GAMEPAD @@ -999,6 +1004,7 @@ #define SDL_JoystickSetVirtualButton SDL_JoystickSetVirtualButton_renamed_SDL_SetJoystickVirtualButton #define SDL_JoystickSetVirtualHat SDL_JoystickSetVirtualHat_renamed_SDL_SetJoystickVirtualHat #define SDL_JoystickUpdate SDL_JoystickUpdate_renamed_SDL_UpdateJoysticks +#define SDL_NumJoysticks SDL_NumJoysticks_deprecated_use_SDL_GetJoysticks /* ##SDL_keyboard.h */ #define SDL_IsScreenKeyboardShown SDL_IsScreenKeyboardShown_renamed_SDL_ScreenKeyboardShown @@ -1147,6 +1153,8 @@ /* ##SDL_render.h */ #define SDL_GetRendererOutputSize SDL_GetRendererOutputSize_renamed_SDL_GetCurrentRenderOutputSize +#define SDL_RENDERER_ACCELERATED SDL_RENDERER_ACCELERATED_deprecated_remove_this_line +#define SDL_RENDERER_PRESENTVSYNC SDL_RENDERER_PRESENTVSYNC_deprecated_use_SDL_SetRenderVSync #define SDL_RenderCopy SDL_RenderCopy_renamed_SDL_RenderTexture #define SDL_RenderCopyEx SDL_RenderCopyEx_renamed_SDL_RenderTextureRotated #define SDL_RenderCopyExF SDL_RenderCopyExF_renamed_SDL_RenderTextureRotated @@ -1291,6 +1299,7 @@ /* ##SDL_thread.h */ #define SDL_SetThreadPriority SDL_SetThreadPriority_renamed_SDL_SetCurrentThreadPriority +#define SDL_SetWindowInputFocus SDL_SetWindowInputFocus_deprecated_use_SDL_RaiseWindow #define SDL_TLSCleanup SDL_TLSCleanup_renamed_SDL_CleanupTLS #define SDL_TLSGet SDL_TLSGet_renamed_SDL_GetTLS #define SDL_TLSSet SDL_TLSSet_renamed_SDL_SetTLS @@ -1316,10 +1325,13 @@ #define SDL_GetWindowDisplayIndex SDL_GetWindowDisplayIndex_renamed_SDL_GetDisplayForWindow #define SDL_GetWindowDisplayMode SDL_GetWindowDisplayMode_renamed_SDL_GetWindowFullscreenMode #define SDL_HasWindowSurface SDL_HasWindowSurface_renamed_SDL_WindowHasSurface +#define SDL_INIT_EVERYTHING SDL_INIT_EVERYTHING_deprecated_list_flags_explicitly #define SDL_IsScreenSaverEnabled SDL_IsScreenSaverEnabled_renamed_SDL_ScreenSaverEnabled #define SDL_SetWindowDisplayMode SDL_SetWindowDisplayMode_renamed_SDL_SetWindowFullscreenMode #define SDL_WINDOW_ALLOW_HIGHDPI SDL_WINDOW_ALLOW_HIGHDPI_renamed_SDL_WINDOW_HIGH_PIXEL_DENSITY +#define SDL_WINDOW_FULLSCREEN_DESKTOP SDL_WINDOW_FULLSCREEN_DESKTOP_deprecated_use_SDL_SetWindowFullscreen_with_bool #define SDL_WINDOW_INPUT_GRABBED SDL_WINDOW_INPUT_GRABBED_renamed_SDL_WINDOW_MOUSE_GRABBED +#define SDL_WINDOW_SHOWN SDL_WINDOW_SHOWN_deprecated_windows_are_shown_by_default #define SDL_WINDOW_SKIP_TASKBAR SDL_WINDOW_SKIP_TASKBAR_renamed_SDL_WINDOW_UTILITY #endif /* SDL_ENABLE_OLD_NAMES */ diff --git a/libs/SDL3/include/SDL3/SDL_opengl.h b/libs/SDL3/include/SDL3/SDL_opengl.h index d1b8b02a..733f2b51 100644 --- a/libs/SDL3/include/SDL3/SDL_opengl.h +++ b/libs/SDL3/include/SDL3/SDL_opengl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_opengl_glext.h b/libs/SDL3/include/SDL3/SDL_opengl_glext.h index fa0f6c2a..ff6ad12c 100644 --- a/libs/SDL3/include/SDL3/SDL_opengl_glext.h +++ b/libs/SDL3/include/SDL3/SDL_opengl_glext.h @@ -9807,15 +9807,15 @@ typedef void (APIENTRYP PFNGLUPLOADGPUMASKNVXPROC) (GLbitfield mask); typedef void (APIENTRYP PFNGLMULTICASTVIEWPORTARRAYVNVXPROC) (GLuint gpu, GLuint first, GLsizei count, const GLfloat *v); typedef void (APIENTRYP PFNGLMULTICASTVIEWPORTPOSITIONWSCALENVXPROC) (GLuint gpu, GLuint index, GLfloat xcoeff, GLfloat ycoeff); typedef void (APIENTRYP PFNGLMULTICASTSCISSORARRAYVNVXPROC) (GLuint gpu, GLuint first, GLsizei count, const GLint *v); -typedef GLuint (APIENTRYP PFNGLASYNCCOPYBUFFERSUBDATANVXPROC) (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *fenceValueArray, GLuint readGPU, GLbitfield writeGPUMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); -typedef GLuint (APIENTRYP PFNGLASYNCCOPYIMAGESUBDATANVXPROC) (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *waitValueArray, GLuint srcGPU, GLbitfield dstGPUMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); +typedef GLuint (APIENTRYP PFNGLASYNCCOPYBUFFERSUBDATANVXPROC) (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *fenceValueArray, GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); +typedef GLuint (APIENTRYP PFNGLASYNCCOPYIMAGESUBDATANVXPROC) (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *waitValueArray, GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); #ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glUploadGPUMaskNVX (GLbitfield mask); +GLAPI void APIENTRY glUploadGpuMaskNVX (GLbitfield mask); GLAPI void APIENTRY glMulticastViewportArrayvNVX (GLuint gpu, GLuint first, GLsizei count, const GLfloat *v); GLAPI void APIENTRY glMulticastViewportPositionWScaleNVX (GLuint gpu, GLuint index, GLfloat xcoeff, GLfloat ycoeff); GLAPI void APIENTRY glMulticastScissorArrayvNVX (GLuint gpu, GLuint first, GLsizei count, const GLint *v); -GLAPI GLuint APIENTRY glAsyncCopyBufferSubDataNVX (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *fenceValueArray, GLuint readGPU, GLbitfield writeGPUMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); -GLAPI GLuint APIENTRY glAsyncCopyImageSubDataNVX (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *waitValueArray, GLuint srcGPU, GLbitfield dstGPUMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); +GLAPI GLuint APIENTRY glAsyncCopyBufferSubDataNVX (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *fenceValueArray, GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); +GLAPI GLuint APIENTRY glAsyncCopyImageSubDataNVX (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *waitValueArray, GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); #endif #endif /* GL_NVX_gpu_multicast2 */ @@ -9824,11 +9824,11 @@ GLAPI GLuint APIENTRY glAsyncCopyImageSubDataNVX (GLsizei waitSemaphoreCount, co #define GL_LGPU_SEPARATE_STORAGE_BIT_NVX 0x0800 #define GL_MAX_LGPU_GPUS_NVX 0x92BA typedef void (APIENTRYP PFNGLLGPUNAMEDBUFFERSUBDATANVXPROC) (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); -typedef void (APIENTRYP PFNGLLGPUCOPYIMAGESUBDATANVXPROC) (GLuint sourceGPU, GLbitfield destinationGPUMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srxY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLLGPUCOPYIMAGESUBDATANVXPROC) (GLuint sourceGpu, GLbitfield destinationGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srxY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); typedef void (APIENTRYP PFNGLLGPUINTERLOCKNVXPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glLGPUNamedBufferSubDataNVX (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); -GLAPI void APIENTRY glLGPUCopyImageSubDataNVX (GLuint sourceGPU, GLbitfield destinationGPUMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srxY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glLGPUCopyImageSubDataNVX (GLuint sourceGpu, GLbitfield destinationGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srxY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); GLAPI void APIENTRY glLGPUInterlockNVX (void); #endif #endif /* GL_NVX_linked_gpu_multicast */ @@ -9836,13 +9836,13 @@ GLAPI void APIENTRY glLGPUInterlockNVX (void); #ifndef GL_NVX_progress_fence #define GL_NVX_progress_fence 1 typedef GLuint (APIENTRYP PFNGLCREATEPROGRESSFENCENVXPROC) (void); -typedef void (APIENTRYP PFNGLSIGNALSEMAPHOREUI64NVXPROC) (GLuint signalGPU, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); -typedef void (APIENTRYP PFNGLWAITSEMAPHOREUI64NVXPROC) (GLuint waitGPU, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +typedef void (APIENTRYP PFNGLSIGNALSEMAPHOREUI64NVXPROC) (GLuint signalGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +typedef void (APIENTRYP PFNGLWAITSEMAPHOREUI64NVXPROC) (GLuint waitGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); typedef void (APIENTRYP PFNGLCLIENTWAITSEMAPHOREUI64NVXPROC) (GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLuint APIENTRY glCreateProgressFenceNVX (void); -GLAPI void APIENTRY glSignalSemaphoreui64NVX (GLuint signalGPU, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); -GLAPI void APIENTRY glWaitSemaphoreui64NVX (GLuint waitGPU, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +GLAPI void APIENTRY glSignalSemaphoreui64NVX (GLuint signalGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +GLAPI void APIENTRY glWaitSemaphoreui64NVX (GLuint waitGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); GLAPI void APIENTRY glClientWaitSemaphoreui64NVX (GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); #endif #endif /* GL_NVX_progress_fence */ @@ -10433,25 +10433,25 @@ GLAPI void APIENTRY glFramebufferTextureFaceEXT (GLenum target, GLenum attachmen #define GL_MULTICAST_PROGRAMMABLE_SAMPLE_LOCATION_NV 0x9549 typedef void (APIENTRYP PFNGLRENDERGPUMASKNVPROC) (GLbitfield mask); typedef void (APIENTRYP PFNGLMULTICASTBUFFERSUBDATANVPROC) (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); -typedef void (APIENTRYP PFNGLMULTICASTCOPYBUFFERSUBDATANVPROC) (GLuint readGPU, GLbitfield writeGPUMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); -typedef void (APIENTRYP PFNGLMULTICASTCOPYIMAGESUBDATANVPROC) (GLuint srcGPU, GLbitfield dstGPUMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); -typedef void (APIENTRYP PFNGLMULTICASTBLITFRAMEBUFFERNVPROC) (GLuint srcGPU, GLuint dstGPU, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +typedef void (APIENTRYP PFNGLMULTICASTCOPYBUFFERSUBDATANVPROC) (GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLMULTICASTCOPYIMAGESUBDATANVPROC) (GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +typedef void (APIENTRYP PFNGLMULTICASTBLITFRAMEBUFFERNVPROC) (GLuint srcGpu, GLuint dstGpu, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); typedef void (APIENTRYP PFNGLMULTICASTFRAMEBUFFERSAMPLELOCATIONSFVNVPROC) (GLuint gpu, GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); typedef void (APIENTRYP PFNGLMULTICASTBARRIERNVPROC) (void); -typedef void (APIENTRYP PFNGLMULTICASTWAITSYNCNVPROC) (GLuint signalGPU, GLbitfield waitGPUMask); +typedef void (APIENTRYP PFNGLMULTICASTWAITSYNCNVPROC) (GLuint signalGpu, GLbitfield waitGpuMask); typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTIVNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTUIVNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLuint *params); typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTI64VNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLint64 *params); typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTUI64VNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLuint64 *params); #ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glRenderGPUMaskNV (GLbitfield mask); +GLAPI void APIENTRY glRenderGpuMaskNV (GLbitfield mask); GLAPI void APIENTRY glMulticastBufferSubDataNV (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); -GLAPI void APIENTRY glMulticastCopyBufferSubDataNV (GLuint readGPU, GLbitfield writeGPUMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); -GLAPI void APIENTRY glMulticastCopyImageSubDataNV (GLuint srcGPU, GLbitfield dstGPUMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); -GLAPI void APIENTRY glMulticastBlitFramebufferNV (GLuint srcGPU, GLuint dstGPU, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +GLAPI void APIENTRY glMulticastCopyBufferSubDataNV (GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +GLAPI void APIENTRY glMulticastCopyImageSubDataNV (GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +GLAPI void APIENTRY glMulticastBlitFramebufferNV (GLuint srcGpu, GLuint dstGpu, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); GLAPI void APIENTRY glMulticastFramebufferSampleLocationsfvNV (GLuint gpu, GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); GLAPI void APIENTRY glMulticastBarrierNV (void); -GLAPI void APIENTRY glMulticastWaitSyncNV (GLuint signalGPU, GLbitfield waitGPUMask); +GLAPI void APIENTRY glMulticastWaitSyncNV (GLuint signalGpu, GLbitfield waitGpuMask); GLAPI void APIENTRY glMulticastGetQueryObjectivNV (GLuint gpu, GLuint id, GLenum pname, GLint *params); GLAPI void APIENTRY glMulticastGetQueryObjectuivNV (GLuint gpu, GLuint id, GLenum pname, GLuint *params); GLAPI void APIENTRY glMulticastGetQueryObjecti64vNV (GLuint gpu, GLuint id, GLenum pname, GLint64 *params); diff --git a/libs/SDL3/include/SDL3/SDL_opengles.h b/libs/SDL3/include/SDL3/SDL_opengles.h index 4fb9a4b7..227f51be 100644 --- a/libs/SDL3/include/SDL3/SDL_opengles.h +++ b/libs/SDL3/include/SDL3/SDL_opengles.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_opengles2.h b/libs/SDL3/include/SDL3/SDL_opengles2.h index 365898a6..2c746143 100644 --- a/libs/SDL3/include/SDL3/SDL_opengles2.h +++ b/libs/SDL3/include/SDL3/SDL_opengles2.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_pen.h b/libs/SDL3/include/SDL3/SDL_pen.h index 5182eeb0..63f32bb6 100644 --- a/libs/SDL3/include/SDL3/SDL_pen.h +++ b/libs/SDL3/include/SDL3/SDL_pen.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -28,12 +28,37 @@ * handling, e.g., for input and drawing tablets or suitably equipped mobile / * tablet devices. * - * To get started with pens, simply handle SDL_EVENT_PEN_* events. When a pen - * starts providing input, SDL will assign it a unique SDL_PenID, which will - * remain for the life of the process, as long as the pen stays connected. + * To get started with pens, simply handle pen events: + * + * - SDL_EVENT_PEN_PROXIMITY_IN, SDL_EVENT_PEN_PROXIMITY_OUT + * (SDL_PenProximityEvent) + * - SDL_EVENT_PEN_DOWN, SDL_EVENT_PEN_UP (SDL_PenTouchEvent) + * - SDL_EVENT_PEN_MOTION (SDL_PenMotionEvent) + * - SDL_EVENT_PEN_BUTTON_DOWN, SDL_EVENT_PEN_BUTTON_UP (SDL_PenButtonEvent) + * - SDL_EVENT_PEN_AXIS (SDL_PenAxisEvent) * * Pens may provide more than simple touch input; they might have other axes, * such as pressure, tilt, rotation, etc. + * + * When a pen starts providing input, SDL will assign it a unique SDL_PenID, + * which will remain for the life of the process, as long as the pen stays + * connected. A pen leaving proximity (being taken far enough away from the + * digitizer tablet that it no longer reponds) and then coming back should + * fire proximity events, but the SDL_PenID should remain consistent. + * Unplugging the digitizer and reconnecting may cause future input to have a + * new SDL_PenID, as SDL may not know that this is the same hardware. + * + * Please note that various platforms vary wildly in how (and how well) they + * support pen input. If your pen supports some piece of functionality but SDL + * doesn't seem to, it might actually be the operating system's fault. For + * example, some platforms can manage multiple devices at the same time, but + * others will make any connected pens look like a single logical device, much + * how all USB mice connected to a computer will move the same system cursor. + * Other platforms might not support pen buttons, or the distance axis, etc. + * Very few platforms can even report _what_ functionality the pen supports in + * the first place, so best practices is to either build UI to let the user + * configure their pens, or be prepared to handle new functionality for a pen + * the first time an event is reported. */ #ifndef SDL_pen_h_ @@ -43,6 +68,7 @@ #include #include +#include /* Set up for C function definitions, even when using C++ */ #ifdef __cplusplus extern "C" { @@ -55,7 +81,12 @@ extern "C" { * * These show up in pen events when SDL sees input from them. They remain * consistent as long as SDL can recognize a tool to be the same pen; but if a - * pen physically leaves the area and returns, it might get a new ID. + * pen's digitizer table is physically detached from the computer, it might + * get a new ID when reconnected, as SDL won't know it's the same device. + * + * These IDs are only stable within a single run of a program; the next time a + * program is run, the pen's ID will likely be different, even if the hardware + * hasn't been disconnected, etc. * * \since This datatype is available since SDL 3.2.0. */ @@ -75,7 +106,6 @@ typedef Uint32 SDL_PenID; */ #define SDL_PEN_TOUCHID ((SDL_TouchID)-2) - /** * Pen input flags, as reported by various pen events' `pen_state` field. * @@ -83,13 +113,14 @@ typedef Uint32 SDL_PenID; */ typedef Uint32 SDL_PenInputFlags; -#define SDL_PEN_INPUT_DOWN (1u << 0) /**< pen is pressed down */ -#define SDL_PEN_INPUT_BUTTON_1 (1u << 1) /**< button 1 is pressed */ -#define SDL_PEN_INPUT_BUTTON_2 (1u << 2) /**< button 2 is pressed */ -#define SDL_PEN_INPUT_BUTTON_3 (1u << 3) /**< button 3 is pressed */ -#define SDL_PEN_INPUT_BUTTON_4 (1u << 4) /**< button 4 is pressed */ -#define SDL_PEN_INPUT_BUTTON_5 (1u << 5) /**< button 5 is pressed */ -#define SDL_PEN_INPUT_ERASER_TIP (1u << 30) /**< eraser tip is used */ +#define SDL_PEN_INPUT_DOWN (1u << 0) /**< pen is pressed down */ +#define SDL_PEN_INPUT_BUTTON_1 (1u << 1) /**< button 1 is pressed */ +#define SDL_PEN_INPUT_BUTTON_2 (1u << 2) /**< button 2 is pressed */ +#define SDL_PEN_INPUT_BUTTON_3 (1u << 3) /**< button 3 is pressed */ +#define SDL_PEN_INPUT_BUTTON_4 (1u << 4) /**< button 4 is pressed */ +#define SDL_PEN_INPUT_BUTTON_5 (1u << 5) /**< button 5 is pressed */ +#define SDL_PEN_INPUT_ERASER_TIP (1u << 30) /**< eraser tip is used */ +#define SDL_PEN_INPUT_IN_PROXIMITY (1u << 31) /**< pen is in proximity (since SDL 3.4.0) */ /** * Pen axis indices. @@ -118,10 +149,50 @@ typedef enum SDL_PenAxis SDL_PEN_AXIS_COUNT /**< Total known pen axis types in this version of SDL. This number may grow in future releases! */ } SDL_PenAxis; +/** + * An enum that describes the type of a pen device. + * + * A "direct" device is a pen that touches a graphic display (like an Apple + * Pencil on an iPad's screen). "Indirect" devices touch an external tablet + * surface that is connected to the machine but is not a display (like a + * lower-end Wacom tablet connected over USB). + * + * Apps may use this information to decide if they should draw a cursor; if + * the pen is touching the screen directly, a cursor doesn't make sense and + * can be in the way, but becomes necessary for indirect devices to know where + * on the display they are interacting. + * + * \since This enum is available since SDL 3.4.0. + */ +typedef enum SDL_PenDeviceType +{ + SDL_PEN_DEVICE_TYPE_INVALID = -1, /**< Not a valid pen device. */ + SDL_PEN_DEVICE_TYPE_UNKNOWN, /**< Don't know specifics of this pen. */ + SDL_PEN_DEVICE_TYPE_DIRECT, /**< Pen touches display. */ + SDL_PEN_DEVICE_TYPE_INDIRECT /**< Pen touches something that isn't the display. */ +} SDL_PenDeviceType; + +/** + * Get the device type of the given pen. + * + * Many platforms do not supply this information, so an app must always be + * prepared to get an SDL_PEN_DEVICE_TYPE_UNKNOWN result. + * + * \param instance_id the pen instance ID. + * \returns the device type of the given pen, or SDL_PEN_DEVICE_TYPE_INVALID + * on failure; call SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC SDL_PenDeviceType SDLCALL SDL_GetPenDeviceType(SDL_PenID instance_id); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } #endif +#include #endif /* SDL_pen_h_ */ diff --git a/libs/SDL3/include/SDL3/SDL_pixels.h b/libs/SDL3/include/SDL3/SDL_pixels.h index f9d6e9e3..475e5f9c 100644 --- a/libs/SDL3/include/SDL3/SDL_pixels.h +++ b/libs/SDL3/include/SDL3/SDL_pixels.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -451,7 +451,7 @@ typedef enum SDL_PackedLayout * expressions with side-effects here. * * \param format an SDL_PixelFormat to check. - * \returns true if the format is 10-bit, false otherwise. + * \returns true if the format is a floating point, false otherwise. * * \threadsafety It is safe to call this macro from any thread. * @@ -1096,7 +1096,7 @@ typedef enum SDL_Colorspace SDL_CHROMA_LOCATION_LEFT), */ SDL_COLORSPACE_RGB_DEFAULT = SDL_COLORSPACE_SRGB, /**< The default colorspace for RGB surfaces if no colorspace is specified */ - SDL_COLORSPACE_YUV_DEFAULT = SDL_COLORSPACE_JPEG /**< The default colorspace for YUV surfaces if no colorspace is specified */ + SDL_COLORSPACE_YUV_DEFAULT = SDL_COLORSPACE_BT601_LIMITED /**< The default colorspace for YUV surfaces if no colorspace is specified */ } SDL_Colorspace; /** diff --git a/libs/SDL3/include/SDL3/SDL_platform.h b/libs/SDL3/include/SDL3/SDL_platform.h index e40f009b..9ad380ba 100644 --- a/libs/SDL3/include/SDL3/SDL_platform.h +++ b/libs/SDL3/include/SDL3/SDL_platform.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -51,6 +51,8 @@ extern "C" { * \returns the name of the platform. If the correct platform name is not * available, returns a string beginning with the text "Unknown". * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC const char * SDLCALL SDL_GetPlatform(void); diff --git a/libs/SDL3/include/SDL3/SDL_platform_defines.h b/libs/SDL3/include/SDL3/SDL_platform_defines.h index f7f14be0..79631491 100644 --- a/libs/SDL3/include/SDL3/SDL_platform_defines.h +++ b/libs/SDL3/include/SDL3/SDL_platform_defines.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -190,7 +190,7 @@ #if TARGET_OS_VISION /** - * A preprocessor macro that is only defined if compiling for VisionOS. + * A preprocessor macro that is only defined if compiling for visionOS. * * \since This macro is available since SDL 3.2.0. * @@ -202,7 +202,7 @@ #if TARGET_OS_IPHONE /** - * A preprocessor macro that is only defined if compiling for iOS. + * A preprocessor macro that is only defined if compiling for iOS or visionOS. * * \since This macro is available since SDL 3.2.0. * @@ -484,4 +484,14 @@ #define SDL_PLATFORM_NGAGE 1 #endif +#ifdef __GNU__ + +/** + * A preprocessor macro that is only defined if compiling for GNU/Hurd. + * + * \since This macro is available since SDL 3.4.0. + */ +#define SDL_PLATFORM_HURD 1 +#endif + #endif /* SDL_platform_defines_h_ */ diff --git a/libs/SDL3/include/SDL3/SDL_power.h b/libs/SDL3/include/SDL3/SDL_power.h index 694fb092..0616cc27 100644 --- a/libs/SDL3/include/SDL3/SDL_power.h +++ b/libs/SDL3/include/SDL3/SDL_power.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -88,11 +88,13 @@ typedef enum SDL_PowerState * can't determine a value or there is no battery. * \param percent a pointer filled in with the percentage of battery life * left, between 0 and 100, or NULL to ignore. This will be - * filled in with -1 we can't determine a value or there is no - * battery. + * filled in with -1 when we can't determine a value or there + * is no battery. * \returns the current battery state or `SDL_POWERSTATE_ERROR` on failure; * call SDL_GetError() for more information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_PowerState SDLCALL SDL_GetPowerInfo(int *seconds, int *percent); diff --git a/libs/SDL3/include/SDL3/SDL_process.h b/libs/SDL3/include/SDL3/SDL_process.h index 57e3afd9..b797890d 100644 --- a/libs/SDL3/include/SDL3/SDL_process.h +++ b/libs/SDL3/include/SDL3/SDL_process.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_properties.h b/libs/SDL3/include/SDL3/SDL_properties.h index 1f47d5f4..abd7b2aa 100644 --- a/libs/SDL3/include/SDL3/SDL_properties.h +++ b/libs/SDL3/include/SDL3/SDL_properties.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -59,7 +59,7 @@ extern "C" { #endif /** - * SDL properties ID + * An ID that represents a properties set. * * \since This datatype is available since SDL 3.2.0. */ @@ -80,12 +80,39 @@ typedef enum SDL_PropertyType SDL_PROPERTY_TYPE_BOOLEAN } SDL_PropertyType; +/** + * A generic property for naming things. + * + * This property is intended to be added to any SDL_PropertiesID that needs a + * generic name associated with the property set. It is not guaranteed that + * any property set will include this key, but it is convenient to have a + * standard key that any piece of code could reasonably agree to use. + * + * For example, the properties associated with an SDL_Texture might have a + * name string of "player sprites", or an SDL_AudioStream might have + * "background music", etc. This might also be useful for an SDL_IOStream to + * list the path to its asset. + * + * There is no format for the value set with this key; it is expected to be + * human-readable and informational in nature, possibly for logging or + * debugging purposes. + * + * SDL does not currently set this property on any objects it creates, but + * this may change in later versions; it is currently expected that apps and + * external libraries will take advantage of it, when appropriate. + * + * \since This macro is available since SDL 3.4.0. + */ +#define SDL_PROP_NAME_STRING "SDL.name" + /** * Get the global SDL properties. * * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetGlobalProperties(void); @@ -119,7 +146,9 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_CreateProperties(void); * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety It is safe to call this function from any thread. + * \threadsafety It is safe to call this function from any thread. This + * function acquires simultaneous mutex locks on both the source + * and destination property sets. * * \since This function is available since SDL 3.2.0. */ diff --git a/libs/SDL3/include/SDL3/SDL_rect.h b/libs/SDL3/include/SDL3/SDL_rect.h index e09f92a4..5f395ec4 100644 --- a/libs/SDL3/include/SDL3/SDL_rect.h +++ b/libs/SDL3/include/SDL3/SDL_rect.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -88,8 +88,11 @@ typedef struct SDL_Rect /** - * A rectangle, with the origin at the upper left (using floating point - * values). + * A rectangle stored using floating point values. + * + * The origin of the coordinate space is in the top-left, with increasing + * values moving down and right. The properties `x` and `y` represent the + * coordinates of the top-left corner of the rectangle. * * \since This struct is available since SDL 3.2.0. * @@ -125,10 +128,10 @@ typedef struct SDL_FRect */ SDL_FORCE_INLINE void SDL_RectToFRect(const SDL_Rect *rect, SDL_FRect *frect) { - frect->x = (float)rect->x; - frect->y = (float)rect->y; - frect->w = (float)rect->w; - frect->h = (float)rect->h; + frect->x = SDL_static_cast(float, rect->x); + frect->y = SDL_static_cast(float, rect->y); + frect->w = SDL_static_cast(float, rect->w); + frect->h = SDL_static_cast(float, rect->h); } /** @@ -234,6 +237,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasRectIntersection(const SDL_Rect *A, cons * rectangles `A` and `B`. * \returns true if there is an intersection, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_HasRectIntersection @@ -250,6 +255,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectIntersection(const SDL_Rect *A, cons * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectUnion(const SDL_Rect *A, const SDL_Rect *B, SDL_Rect *result); @@ -269,6 +276,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectUnion(const SDL_Rect *A, const SDL_R * \returns true if any points were enclosed or false if all the points were * outside of the clipping rectangle. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectEnclosingPoints(const SDL_Point *points, int count, const SDL_Rect *clip, SDL_Rect *result); @@ -289,6 +298,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectEnclosingPoints(const SDL_Point *poi * \param Y2 a pointer to the ending Y-coordinate of the line. * \returns true if there is an intersection, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectAndLineIntersection(const SDL_Rect *rect, int *X1, int *Y1, int *X2, int *Y2); @@ -420,9 +431,11 @@ SDL_FORCE_INLINE bool SDL_RectsEqualFloat(const SDL_FRect *a, const SDL_FRect *b * \param B an SDL_FRect structure representing the second rectangle. * \returns true if there is an intersection, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * - * \sa SDL_GetRectIntersection + * \sa SDL_GetRectIntersectionFloat */ extern SDL_DECLSPEC bool SDLCALL SDL_HasRectIntersectionFloat(const SDL_FRect *A, const SDL_FRect *B); @@ -437,6 +450,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HasRectIntersectionFloat(const SDL_FRect *A * rectangles `A` and `B`. * \returns true if there is an intersection, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_HasRectIntersectionFloat @@ -453,6 +468,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectIntersectionFloat(const SDL_FRect *A * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectUnionFloat(const SDL_FRect *A, const SDL_FRect *B, SDL_FRect *result); @@ -473,6 +490,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectUnionFloat(const SDL_FRect *A, const * \returns true if any points were enclosed or false if all the points were * outside of the clipping rectangle. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectEnclosingPointsFloat(const SDL_FPoint *points, int count, const SDL_FRect *clip, SDL_FRect *result); @@ -494,6 +513,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectEnclosingPointsFloat(const SDL_FPoin * \param Y2 a pointer to the ending Y-coordinate of the line. * \returns true if there is an intersection, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetRectAndLineIntersectionFloat(const SDL_FRect *rect, float *X1, float *Y1, float *X2, float *Y2); diff --git a/libs/SDL3/include/SDL3/SDL_render.h b/libs/SDL3/include/SDL3/SDL_render.h index e2e42750..986130ce 100644 --- a/libs/SDL3/include/SDL3/SDL_render.h +++ b/libs/SDL3/include/SDL3/SDL_render.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -39,9 +39,9 @@ * may also be stretched with linear interpolation. * * This API is designed to accelerate simple 2D operations. You may want more - * functionality such as polygons and particle effects and in that case you - * should use SDL's OpenGL/Direct3D support, the SDL3 GPU API, or one of the - * many good 3D engines. + * functionality such as 3D polygons and particle effects, and in that case + * you should use SDL's OpenGL/Direct3D support, the SDL3 GPU API, or one of + * the many good 3D engines. * * These functions must be called from the main thread. See this bug for * details: https://github.com/libsdl-org/SDL/issues/986 @@ -74,6 +74,13 @@ extern "C" { */ #define SDL_SOFTWARE_RENDERER "software" +/** + * The name of the GPU renderer. + * + * \since This macro is available since SDL 3.4.0. + */ +#define SDL_GPU_RENDERER "gpu" + /** * Vertex structure. * @@ -103,6 +110,10 @@ typedef enum SDL_TextureAccess * * This affects how texture coordinates are interpreted outside of [0, 1] * + * Texture wrapping is always supported for power of two texture sizes, and is + * supported for other texture sizes if + * SDL_PROP_RENDERER_TEXTURE_WRAPPING_BOOLEAN is set to true. + * * \since This enum is available since SDL 3.4.0. */ typedef enum SDL_TextureAddressMode @@ -110,7 +121,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; /** @@ -122,7 +133,7 @@ typedef enum SDL_RendererLogicalPresentation { SDL_LOGICAL_PRESENTATION_DISABLED, /**< There is no logical size in effect */ SDL_LOGICAL_PRESENTATION_STRETCH, /**< The rendered content is stretched to the output resolution */ - SDL_LOGICAL_PRESENTATION_LETTERBOX, /**< The rendered content is fit to the largest dimension and the other dimension is letterboxed with black bars */ + SDL_LOGICAL_PRESENTATION_LETTERBOX, /**< The rendered content is fit to the largest dimension and the other dimension is letterboxed with the clear color */ SDL_LOGICAL_PRESENTATION_OVERSCAN, /**< The rendered content is fit to the smallest dimension and the other dimension extends beyond the output bounds */ SDL_LOGICAL_PRESENTATION_INTEGER_SCALE /**< The rendered content is scaled up by integer multiples to fit the output resolution */ } SDL_RendererLogicalPresentation; @@ -283,8 +294,10 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window *window * present synchronized with the refresh rate. This property can take any * value that is supported by SDL_SetRenderVSync() for the renderer. * - * With the SDL GPU renderer: + * With the SDL GPU renderer (since SDL 3.4.0): * + * - `SDL_PROP_RENDERER_CREATE_GPU_DEVICE_POINTER`: the device to use with the + * renderer, optional. * - `SDL_PROP_RENDERER_CREATE_GPU_SHADERS_SPIRV_BOOLEAN`: the app is able to * provide SPIR-V shaders to SDL_GPURenderState, optional. * - `SDL_PROP_RENDERER_CREATE_GPU_SHADERS_DXIL_BOOLEAN`: the app is able to @@ -328,6 +341,7 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRendererWithProperties(SDL_ #define SDL_PROP_RENDERER_CREATE_SURFACE_POINTER "SDL.renderer.create.surface" #define SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER "SDL.renderer.create.output_colorspace" #define SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER "SDL.renderer.create.present_vsync" +#define SDL_PROP_RENDERER_CREATE_GPU_DEVICE_POINTER "SDL.renderer.create.gpu.device" #define SDL_PROP_RENDERER_CREATE_GPU_SHADERS_SPIRV_BOOLEAN "SDL.renderer.create.gpu.shaders_spirv" #define SDL_PROP_RENDERER_CREATE_GPU_SHADERS_DXIL_BOOLEAN "SDL.renderer.create.gpu.shaders_dxil" #define SDL_PROP_RENDERER_CREATE_GPU_SHADERS_MSL_BOOLEAN "SDL.renderer.create.gpu.shaders_msl" @@ -339,35 +353,51 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRendererWithProperties(SDL_ #define SDL_PROP_RENDERER_CREATE_VULKAN_PRESENT_QUEUE_FAMILY_INDEX_NUMBER "SDL.renderer.create.vulkan.present_queue_family_index" /** - * Create a 2D GPU rendering context for a window, with support for the - * specified shader format. + * Create a 2D GPU rendering context. * - * This is a convenience function to create a SDL GPU backed renderer, - * intended to be used with SDL_GPURenderState. The resulting renderer will - * support shaders in one of the specified shader formats. + * The GPU device to use is passed in as a parameter. If this is NULL, then a + * device will be created normally and can be retrieved using + * SDL_GetGPURendererDevice(). * - * If no available GPU driver supports any of the specified shader formats, - * this function will fail. + * The window to use is passed in as a parameter. If this is NULL, then this + * will become an offscreen renderer. In that case, you should call + * SDL_SetRenderTarget() to setup rendering to a texture, and then call + * SDL_RenderPresent() normally to complete drawing a frame. * - * \param window the window where rendering is displayed. - * \param format_flags a bitflag indicating which shader formats the app is - * able to provide. - * \param device a pointer filled with the associated GPU device, or NULL on - * error. + * \param device the GPU device to use with the renderer, or NULL to create a + * device. + * \param window the window where rendering is displayed, or NULL to create an + * offscreen renderer. * \returns a valid rendering context or NULL if there was an error; call * SDL_GetError() for more information. * - * \threadsafety This function should only be called on the main thread. + * \threadsafety If this function is called with a valid GPU device, it should + * be called on the thread that created the device. If this + * function is called with a valid window, it should be called + * on the thread that created the window. * * \since This function is available since SDL 3.4.0. * * \sa SDL_CreateRendererWithProperties - * \sa SDL_GetGPUShaderFormats + * \sa SDL_GetGPURendererDevice * \sa SDL_CreateGPUShader * \sa SDL_CreateGPURenderState - * \sa SDL_SetRenderGPUState + * \sa SDL_SetGPURenderState */ -extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateGPURenderer(SDL_Window *window, SDL_GPUShaderFormat format_flags, SDL_GPUDevice **device); +extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateGPURenderer(SDL_GPUDevice *device, SDL_Window *window); + +/** + * Return the GPU device used by a renderer. + * + * \param renderer the rendering context. + * \returns the GPU device used by the renderer, or NULL if the renderer is + * not a GPU renderer; call SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_GetGPURendererDevice(SDL_Renderer *renderer); /** * Create a 2D software rendering context for a surface. @@ -382,7 +412,7 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateGPURenderer(SDL_Window *win * \returns a valid rendering context or NULL if there was an error; call * SDL_GetError() for more information. * - * \threadsafety This function should only be called on the main thread. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. * @@ -448,6 +478,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetRendererName(SDL_Renderer *rende * - `SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER`: a (const SDL_PixelFormat *) * array of pixel formats, terminated with SDL_PIXELFORMAT_UNKNOWN, * representing the available texture formats for this renderer. + * - `SDL_PROP_RENDERER_TEXTURE_WRAPPING_BOOLEAN`: true if the renderer + * supports SDL_TEXTURE_ADDRESS_WRAP on non-power-of-two textures. * - `SDL_PROP_RENDERER_OUTPUT_COLORSPACE_NUMBER`: an SDL_Colorspace value * describing the colorspace for output to the display, defaults to * SDL_COLORSPACE_SRGB. @@ -524,6 +556,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetRendererProperties(SDL_Rende #define SDL_PROP_RENDERER_VSYNC_NUMBER "SDL.renderer.vsync" #define SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER "SDL.renderer.max_texture_size" #define SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER "SDL.renderer.texture_formats" +#define SDL_PROP_RENDERER_TEXTURE_WRAPPING_BOOLEAN "SDL.renderer.texture_wrapping" #define SDL_PROP_RENDERER_OUTPUT_COLORSPACE_NUMBER "SDL.renderer.output_colorspace" #define SDL_PROP_RENDERER_HDR_ENABLED_BOOLEAN "SDL.renderer.HDR_enabled" #define SDL_PROP_RENDERER_SDR_WHITE_POINT_FLOAT "SDL.renderer.SDR_white_point" @@ -660,6 +693,9 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureFromSurface(SDL_Rende * pixels, required * - `SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER`: the height of the texture in * pixels, required + * - `SDL_PROP_TEXTURE_CREATE_PALETTE_POINTER`: an SDL_Palette to use with + * palettized texture formats. This can be set later with + * SDL_SetTexturePalette() * - `SDL_PROP_TEXTURE_CREATE_SDR_WHITE_POINT_FLOAT`: for HDR10 and floating * point textures, this defines the value of 100% diffuse white, with higher * values being displayed in the High Dynamic Range headroom. This defaults @@ -718,8 +754,6 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureFromSurface(SDL_Rende * * - `SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER`: the GLuint texture * associated with the texture, if you want to wrap an existing texture. - * - `SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER`: the GLuint texture - * associated with the texture, if you want to wrap an existing texture. * - `SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_UV_NUMBER`: the GLuint texture * associated with the UV plane of an NV12 texture, if you want to wrap an * existing texture. @@ -732,9 +766,24 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureFromSurface(SDL_Rende * * With the vulkan renderer: * - * - `SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER`: the VkImage with layout - * VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL associated with the texture, if - * you want to wrap an existing texture. + * - `SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER`: the VkImage associated + * with the texture, if you want to wrap an existing texture. + * - `SDL_PROP_TEXTURE_CREATE_VULKAN_LAYOUT_NUMBER`: the VkImageLayout for the + * VkImage, defaults to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL. + * + * With the GPU renderer: + * + * - `SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_POINTER`: the SDL_GPUTexture + * associated with the texture, if you want to wrap an existing texture. + * - `SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_UV_NUMBER`: the SDL_GPUTexture + * associated with the UV plane of an NV12 texture, if you want to wrap an + * existing texture. + * - `SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_U_NUMBER`: the SDL_GPUTexture + * associated with the U plane of a YUV texture, if you want to wrap an + * existing texture. + * - `SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_V_NUMBER`: the SDL_GPUTexture + * associated with the V plane of a YUV texture, if you want to wrap an + * existing texture. * * \param renderer the rendering context. * \param props the properties to use. @@ -754,29 +803,35 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureFromSurface(SDL_Rende */ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_PropertiesID props); -#define SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER "SDL.texture.create.colorspace" -#define SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER "SDL.texture.create.format" -#define SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER "SDL.texture.create.access" -#define SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER "SDL.texture.create.width" -#define SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER "SDL.texture.create.height" -#define SDL_PROP_TEXTURE_CREATE_SDR_WHITE_POINT_FLOAT "SDL.texture.create.SDR_white_point" -#define SDL_PROP_TEXTURE_CREATE_HDR_HEADROOM_FLOAT "SDL.texture.create.HDR_headroom" -#define SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_POINTER "SDL.texture.create.d3d11.texture" -#define SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_U_POINTER "SDL.texture.create.d3d11.texture_u" -#define SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_V_POINTER "SDL.texture.create.d3d11.texture_v" -#define SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_POINTER "SDL.texture.create.d3d12.texture" -#define SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_U_POINTER "SDL.texture.create.d3d12.texture_u" -#define SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_V_POINTER "SDL.texture.create.d3d12.texture_v" -#define SDL_PROP_TEXTURE_CREATE_METAL_PIXELBUFFER_POINTER "SDL.texture.create.metal.pixelbuffer" -#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_NUMBER "SDL.texture.create.opengl.texture" -#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_UV_NUMBER "SDL.texture.create.opengl.texture_uv" -#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_U_NUMBER "SDL.texture.create.opengl.texture_u" -#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_V_NUMBER "SDL.texture.create.opengl.texture_v" -#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER "SDL.texture.create.opengles2.texture" -#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_UV_NUMBER "SDL.texture.create.opengles2.texture_uv" -#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_U_NUMBER "SDL.texture.create.opengles2.texture_u" -#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_V_NUMBER "SDL.texture.create.opengles2.texture_v" -#define SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER "SDL.texture.create.vulkan.texture" +#define SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER "SDL.texture.create.colorspace" +#define SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER "SDL.texture.create.format" +#define SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER "SDL.texture.create.access" +#define SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER "SDL.texture.create.width" +#define SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER "SDL.texture.create.height" +#define SDL_PROP_TEXTURE_CREATE_PALETTE_POINTER "SDL.texture.create.palette" +#define SDL_PROP_TEXTURE_CREATE_SDR_WHITE_POINT_FLOAT "SDL.texture.create.SDR_white_point" +#define SDL_PROP_TEXTURE_CREATE_HDR_HEADROOM_FLOAT "SDL.texture.create.HDR_headroom" +#define SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_POINTER "SDL.texture.create.d3d11.texture" +#define SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_U_POINTER "SDL.texture.create.d3d11.texture_u" +#define SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_V_POINTER "SDL.texture.create.d3d11.texture_v" +#define SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_POINTER "SDL.texture.create.d3d12.texture" +#define SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_U_POINTER "SDL.texture.create.d3d12.texture_u" +#define SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_V_POINTER "SDL.texture.create.d3d12.texture_v" +#define SDL_PROP_TEXTURE_CREATE_METAL_PIXELBUFFER_POINTER "SDL.texture.create.metal.pixelbuffer" +#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_NUMBER "SDL.texture.create.opengl.texture" +#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_UV_NUMBER "SDL.texture.create.opengl.texture_uv" +#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_U_NUMBER "SDL.texture.create.opengl.texture_u" +#define SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_V_NUMBER "SDL.texture.create.opengl.texture_v" +#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER "SDL.texture.create.opengles2.texture" +#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_UV_NUMBER "SDL.texture.create.opengles2.texture_uv" +#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_U_NUMBER "SDL.texture.create.opengles2.texture_u" +#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_V_NUMBER "SDL.texture.create.opengles2.texture_v" +#define SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER "SDL.texture.create.vulkan.texture" +#define SDL_PROP_TEXTURE_CREATE_VULKAN_LAYOUT_NUMBER "SDL.texture.create.vulkan.layout" +#define SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_POINTER "SDL.texture.create.gpu.texture" +#define SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_UV_POINTER "SDL.texture.create.gpu.texture_uv" +#define SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_U_POINTER "SDL.texture.create.gpu.texture_u" +#define SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_V_POINTER "SDL.texture.create.gpu.texture_v" /** * Get the properties associated with a texture. @@ -856,6 +911,17 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureWithProperties(SDL_Re * - `SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_TARGET_NUMBER`: the GLenum for the * texture target (`GL_TEXTURE_2D`, `GL_TEXTURE_EXTERNAL_OES`, etc) * + * With the gpu renderer: + * + * - `SDL_PROP_TEXTURE_GPU_TEXTURE_POINTER`: the SDL_GPUTexture associated + * with the texture + * - `SDL_PROP_TEXTURE_GPU_TEXTURE_UV_POINTER`: the SDL_GPUTexture associated + * with the UV plane of an NV12 texture + * - `SDL_PROP_TEXTURE_GPU_TEXTURE_U_POINTER`: the SDL_GPUTexture associated + * with the U plane of a YUV texture + * - `SDL_PROP_TEXTURE_GPU_TEXTURE_V_POINTER`: the SDL_GPUTexture associated + * with the V plane of a YUV texture + * * \param texture the texture to query. * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. @@ -892,6 +958,10 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetTextureProperties(SDL_Textur #define SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_V_NUMBER "SDL.texture.opengles2.texture_v" #define SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_TARGET_NUMBER "SDL.texture.opengles2.target" #define SDL_PROP_TEXTURE_VULKAN_TEXTURE_NUMBER "SDL.texture.vulkan.texture" +#define SDL_PROP_TEXTURE_GPU_TEXTURE_POINTER "SDL.texture.gpu.texture" +#define SDL_PROP_TEXTURE_GPU_TEXTURE_UV_POINTER "SDL.texture.gpu.texture_uv" +#define SDL_PROP_TEXTURE_GPU_TEXTURE_U_POINTER "SDL.texture.gpu.texture_u" +#define SDL_PROP_TEXTURE_GPU_TEXTURE_V_POINTER "SDL.texture.gpu.texture_v" /** * Get the renderer that created an SDL_Texture. @@ -923,6 +993,43 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_GetRendererFromTexture(SDL_Textur */ extern SDL_DECLSPEC bool SDLCALL SDL_GetTextureSize(SDL_Texture *texture, float *w, float *h); +/** + * Set the palette used by a texture. + * + * Setting the palette keeps an internal reference to the palette, which can + * be safely destroyed afterwards. + * + * A single palette can be shared with many textures. + * + * \param texture the texture to update. + * \param palette the SDL_Palette structure to use. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreatePalette + * \sa SDL_GetTexturePalette + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetTexturePalette(SDL_Texture *texture, SDL_Palette *palette); + +/** + * Get the palette used by a texture. + * + * \param texture the texture to query. + * \returns a pointer to the palette used by the texture, or NULL if there is + * no palette used. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_SetTexturePalette + */ +extern SDL_DECLSPEC SDL_Palette * SDLCALL SDL_GetTexturePalette(SDL_Texture *texture); + /** * Set an additional color value multiplied into render copy operations. * @@ -1445,14 +1552,6 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_GetRenderTarget(SDL_Renderer *rend * specific dimensions but to make fonts look sharp, the app turns off logical * presentation while drawing text, for example. * - * For the renderer's window, letterboxing is drawn into the framebuffer if - * logical presentation is enabled during SDL_RenderPresent; be sure to - * reenable it before presenting if you were toggling it, otherwise the - * letterbox areas might have artifacts from previous frames (or artifacts - * from external overlays, etc). Letterboxing is never drawn into texture - * render targets; be sure to call SDL_RenderClear() before drawing into the - * texture so the letterboxing areas are cleared, if appropriate. - * * You can convert coordinates in an event into rendering coordinates using * SDL_ConvertEventToRenderCoordinates(). * @@ -1477,15 +1576,16 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderLogicalPresentation(SDL_Renderer * * Get device independent resolution and presentation mode for rendering. * * This function gets the width and height of the logical rendering output, or - * the output size in pixels if a logical resolution is not enabled. + * 0 if a logical resolution is not enabled. * * Each render target has its own logical presentation state. This function * gets the state for the current render target. * * \param renderer the rendering context. - * \param w an int to be filled with the width. - * \param h an int to be filled with the height. - * \param mode the presentation mode used. + * \param w an int filled with the logical presentation width. + * \param h an int filled with the logical presentation height. + * \param mode a variable filled with the logical presentation mode being + * used. * \returns true on success or false on failure; call SDL_GetError() for more * information. * @@ -1604,8 +1704,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderCoordinatesToWindow(SDL_Renderer *ren * * \param renderer the rendering context. * \param event the event to modify. - * \returns true on success or false on failure; call SDL_GetError() for more - * information. + * \returns true if the event is converted or doesn't need conversion, or + * false on failure; call SDL_GetError() for more information. * * \threadsafety This function should only be called on the main thread. * @@ -2290,6 +2390,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTextureTiled(SDL_Renderer *renderer, * \since This function is available since SDL 3.2.0. * * \sa SDL_RenderTexture + * \sa SDL_RenderTexture9GridTiled */ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect); @@ -2327,6 +2428,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture9Grid(SDL_Renderer *renderer, * \since This function is available since SDL 3.4.0. * * \sa SDL_RenderTexture + * \sa SDL_RenderTexture9Grid */ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture9GridTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect, float tileScale); @@ -2405,6 +2507,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderGeometryRaw(SDL_Renderer *renderer, * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.4.0. * * \sa SDL_RenderGeometry @@ -2426,6 +2530,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderTextureAddressMode(SDL_Renderer *r * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.4.0. * * \sa SDL_SetRenderTextureAddressMode @@ -2712,8 +2818,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderVSync(SDL_Renderer *renderer, int * Among these limitations: * * - It accepts UTF-8 strings, but will only renders ASCII characters. - * - It has a single, tiny size (8x8 pixels). One can use logical presentation - * or scaling to adjust it, but it will be blurry. + * - It has a single, tiny size (8x8 pixels). You can use logical presentation + * or SDL_SetRenderScale() to adjust it. * - It uses a simple, hardcoded bitmap font. It does not allow different font * selections and it does not support truetype, for proper scaling. * - It does no word-wrapping and does not treat newline characters as a line @@ -2747,7 +2853,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugText(SDL_Renderer *renderer, flo * Draw debug text to an SDL_Renderer. * * This function will render a printf()-style format string to a renderer. - * Note that this is a convinence function for debugging, with severe + * Note that this is a convenience function for debugging, with severe * limitations, and is not intended to be used for production apps and games. * * For the full list of limitations and other useful information, see @@ -2808,18 +2914,14 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetDefaultTextureScaleMode(SDL_Renderer *re extern SDL_DECLSPEC bool SDLCALL SDL_GetDefaultTextureScaleMode(SDL_Renderer *renderer, SDL_ScaleMode *scale_mode); /** - * GPU render state description. - * - * This structure should be initialized using SDL_INIT_INTERFACE(). + * A structure specifying the parameters of a GPU render state. * * \since This struct is available since SDL 3.4.0. * * \sa SDL_CreateGPURenderState */ -typedef struct SDL_GPURenderStateDesc +typedef struct SDL_GPURenderStateCreateInfo { - Uint32 version; /**< the version of this interface */ - SDL_GPUShader *fragment_shader; /**< The fragment shader to use when this render state is active */ Sint32 num_sampler_bindings; /**< The number of additional fragment samplers to bind when this render state is active */ @@ -2828,19 +2930,11 @@ typedef struct SDL_GPURenderStateDesc Sint32 num_storage_textures; /**< The number of storage textures to bind when this render state is active */ SDL_GPUTexture *const *storage_textures; /**< Storage textures to bind when this render state is active */ - Sint32 num_storage_buffers; /**< The number of storage buffers to bind when this render state is active */ + Sint32 num_storage_buffers; /**< The number of storage buffers to bind when this render state is active */ SDL_GPUBuffer *const *storage_buffers; /**< Storage buffers to bind when this render state is active */ -} SDL_GPURenderStateDesc; -/* Check the size of SDL_GPURenderStateDesc - * - * If this assert fails, either the compiler is padding to an unexpected size, - * or the interface has been updated and this should be updated to match and - * the code using this interface should be updated to handle the old version. - */ -SDL_COMPILE_TIME_ASSERT(SDL_GPURenderStateDesc_SIZE, - (sizeof(void *) == 4 && sizeof(SDL_GPURenderStateDesc) == 32) || - (sizeof(void *) == 8 && sizeof(SDL_GPURenderStateDesc) == 64)); + SDL_PropertiesID props; /**< A properties ID for extensions. Should be 0 if no extensions are needed. */ +} SDL_GPURenderStateCreateInfo; /** * A custom GPU render state. @@ -2849,7 +2943,7 @@ SDL_COMPILE_TIME_ASSERT(SDL_GPURenderStateDesc_SIZE, * * \sa SDL_CreateGPURenderState * \sa SDL_SetGPURenderStateFragmentUniforms - * \sa SDL_SetRenderGPUState + * \sa SDL_SetGPURenderState * \sa SDL_DestroyGPURenderState */ typedef struct SDL_GPURenderState SDL_GPURenderState; @@ -2858,8 +2952,7 @@ typedef struct SDL_GPURenderState SDL_GPURenderState; * Create custom GPU render state. * * \param renderer the renderer to use. - * \param desc GPU render state description, initialized using - * SDL_INIT_INTERFACE(). + * \param createinfo a struct describing the GPU render state to create. * \returns a custom GPU render state or NULL on failure; call SDL_GetError() * for more information. * @@ -2869,10 +2962,10 @@ typedef struct SDL_GPURenderState SDL_GPURenderState; * \since This function is available since SDL 3.4.0. * * \sa SDL_SetGPURenderStateFragmentUniforms - * \sa SDL_SetRenderGPUState + * \sa SDL_SetGPURenderState * \sa SDL_DestroyGPURenderState */ -extern SDL_DECLSPEC SDL_GPURenderState * SDLCALL SDL_CreateGPURenderState(SDL_Renderer *renderer, SDL_GPURenderStateDesc *desc); +extern SDL_DECLSPEC SDL_GPURenderState * SDLCALL SDL_CreateGPURenderState(SDL_Renderer *renderer, const SDL_GPURenderStateCreateInfo *createinfo); /** * Set fragment shader uniform variables in a custom GPU render state. @@ -2910,7 +3003,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetGPURenderStateFragmentUniforms(SDL_GPURe * * \since This function is available since SDL 3.4.0. */ -extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderGPUState(SDL_Renderer *renderer, SDL_GPURenderState *state); +extern SDL_DECLSPEC bool SDLCALL SDL_SetGPURenderState(SDL_Renderer *renderer, SDL_GPURenderState *state); /** * Destroy custom GPU render state. diff --git a/libs/SDL3/include/SDL3/SDL_revision.h b/libs/SDL3/include/SDL3/SDL_revision.h index f0f57cd5..b79ed59b 100644 --- a/libs/SDL3/include/SDL3/SDL_revision.h +++ b/libs/SDL3/include/SDL3/SDL_revision.h @@ -1,22 +1,22 @@ /* - Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. + 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: + 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. + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. */ /* WIKI CATEGORY: Version */ @@ -44,13 +44,13 @@ * clue in debugging forensics and not something the app will parse in any * way. * - * \since This macro is available since SDL 3.2.0. + * \since This macro is available since SDL 3.0.0. */ #define SDL_REVISION "Some arbitrary string decided at SDL build time" #elif defined(SDL_VENDOR_INFO) -#define SDL_REVISION SDL_VENDOR_INFO +#define SDL_REVISION "SDL-release-3.4.10-0-g8e37db5e7 (" SDL_VENDOR_INFO ")" #else -#define SDL_REVISION "" +#define SDL_REVISION "SDL-release-3.4.10-0-g8e37db5e7" #endif #endif /* SDL_revision_h_ */ diff --git a/libs/SDL3/include/SDL3/SDL_scancode.h b/libs/SDL3/include/SDL3/SDL_scancode.h index 9650a6cf..cd5a1376 100644 --- a/libs/SDL3/include/SDL3/SDL_scancode.h +++ b/libs/SDL3/include/SDL3/SDL_scancode.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -208,7 +208,7 @@ typedef enum SDL_Scancode SDL_SCANCODE_NONUSBACKSLASH = 100, /**< This is the additional key that ISO * keyboards have over ANSI ones, - * located between left shift and Y. + * located between left shift and Z. * Produces GRAVE ACCENT and TILDE in a * US or UK Mac layout, REVERSE SOLIDUS * (backslash) and VERTICAL LINE in a diff --git a/libs/SDL3/include/SDL3/SDL_sensor.h b/libs/SDL3/include/SDL3/SDL_sensor.h index b220f053..bf890472 100644 --- a/libs/SDL3/include/SDL3/SDL_sensor.h +++ b/libs/SDL3/include/SDL3/SDL_sensor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -138,7 +138,8 @@ typedef enum SDL_SensorType SDL_SENSOR_ACCEL_L, /**< Accelerometer for left Joy-Con controller and Wii nunchuk */ SDL_SENSOR_GYRO_L, /**< Gyroscope for left Joy-Con controller */ SDL_SENSOR_ACCEL_R, /**< Accelerometer for right Joy-Con controller */ - SDL_SENSOR_GYRO_R /**< Gyroscope for right Joy-Con controller */ + SDL_SENSOR_GYRO_R, /**< Gyroscope for right Joy-Con controller */ + SDL_SENSOR_COUNT } SDL_SensorType; diff --git a/libs/SDL3/include/SDL3/SDL_stdinc.h b/libs/SDL3/include/SDL3/SDL_stdinc.h index f198de75..b6403bea 100644 --- a/libs/SDL3/include/SDL3/SDL_stdinc.h +++ b/libs/SDL3/include/SDL3/SDL_stdinc.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -49,10 +49,37 @@ #include #include -#include #include #include +/* Most everything except Visual Studio 2008 and earlier has stdint.h now */ +#if defined(_MSC_VER) && (_MSC_VER < 1600) +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; +#ifndef _INTPTR_T_DEFINED +#ifdef _WIN64 +typedef __int64 intptr_t; +#else +typedef int intptr_t; +#endif +#endif +#ifndef _UINTPTR_T_DEFINED +#ifdef _WIN64 +typedef unsigned __int64 uintptr_t; +#else +typedef unsigned int uintptr_t; +#endif +#endif +#else +#include +#endif + #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ defined(SDL_INCLUDE_INTTYPES_H) #include @@ -227,12 +254,6 @@ void *alloca(size_t); /** * Macro useful for building other macros with strings in them. * - * For example: - * - * ```c - * #define LOG_ERROR(X) OutputDebugString(SDL_STRINGIFY_ARG(__FUNCTION__) ": " X "\n")` - * ``` - * * \param arg the text to turn into a string literal. * * \since This macro is available since SDL 3.2.0. @@ -492,7 +513,7 @@ typedef uint64_t Uint64; * and SDL_SECONDS_TO_NS(), and between Windows FILETIME values with * SDL_TimeToWindows() and SDL_TimeFromWindows(). * - * \since This macro is available since SDL 3.2.0. + * \since This datatype is available since SDL 3.2.0. * * \sa SDL_MAX_SINT64 * \sa SDL_MIN_SINT64 @@ -1164,7 +1185,7 @@ typedef struct SDL_alignment_test void *b; } SDL_alignment_test; SDL_COMPILE_TIME_ASSERT(struct_alignment, sizeof(SDL_alignment_test) == (2 * sizeof(void *))); -SDL_COMPILE_TIME_ASSERT(two_s_complement, (int)~(int)0 == (int)(-1)); +SDL_COMPILE_TIME_ASSERT(two_s_complement, SDL_static_cast(int, ~SDL_static_cast(int, 0)) == SDL_static_cast(int, -1)); #endif /* DOXYGEN_SHOULD_IGNORE_THIS */ /** \endcond */ @@ -1269,8 +1290,9 @@ extern "C" { /** * Free memory previously allocated with SDL_stack_alloc. * - * If SDL used alloca() to allocate this memory, this macro does nothing and - * the allocated memory will be automatically released when the function that + * If SDL used alloca() to allocate this memory, this macro does nothing (other + * than insert `((void)(data)` so the compiler sees an expression) and the + * allocated memory will be automatically released when the function that * called SDL_stack_alloc() returns. If SDL used SDL_malloc(), it will * SDL_free the memory immediately. * @@ -1282,10 +1304,10 @@ extern "C" { * * \sa SDL_stack_alloc */ -#define SDL_stack_free(data) +#define SDL_stack_free(data) ((void)(data)) #elif !defined(SDL_DISABLE_ALLOCA) #define SDL_stack_alloc(type, count) (type*)alloca(sizeof(type)*(count)) -#define SDL_stack_free(data) +#define SDL_stack_free(data) ((void)(data)) #else #define SDL_stack_alloc(type, count) (type*)SDL_malloc(sizeof(type)*(count)) #define SDL_stack_free(data) SDL_free(data) @@ -1775,6 +1797,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroyEnvironment(SDL_Environment *env); /** * Get the value of a variable in the environment. * + * The name of the variable is case sensitive on all platforms. + * * This function uses SDL's cached copy of the environment and is thread-safe. * * \param name the name of the variable to get. @@ -1793,6 +1817,11 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_getenv(const char *name); * This function bypasses SDL's cached copy of the environment and is not * thread-safe. * + * On some platforms, this may make case-insensitive matches, while other + * platforms are case-sensitive. It is best to be precise with strings used + * for queries through this interface. SDL_getenv is always case-sensitive, + * however. + * * \param name the name of the variable to get. * \returns a pointer to the value of the variable or NULL if it can't be * found. @@ -2640,7 +2669,7 @@ extern SDL_DECLSPEC void * SDLCALL SDL_memset4(void *dst, Uint32 val, size_t dwo * \since This macro is available since SDL 3.2.0. * * \sa SDL_zero - * \sa SDL_zeroa + * \sa SDL_zerop */ #define SDL_zeroa(x) SDL_memset((x), 0, sizeof((x))) @@ -3426,7 +3455,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_utf8strnlen(const char *str, size_t bytes * Convert an integer into a string. * * This requires a radix to specified for string format. Specifying 10 - * produces a decimal number, 16 hexidecimal, etc. Must be in the range of 2 + * produces a decimal number, 16 hexadecimal, etc. Must be in the range of 2 * to 36. * * Note that this function will overflow a buffer if `str` is not large enough @@ -3454,7 +3483,7 @@ extern SDL_DECLSPEC char * SDLCALL SDL_itoa(int value, char *str, int radix); * Convert an unsigned integer into a string. * * This requires a radix to specified for string format. Specifying 10 - * produces a decimal number, 16 hexidecimal, etc. Must be in the range of 2 + * produces a decimal number, 16 hexadecimal, etc. Must be in the range of 2 * to 36. * * Note that this function will overflow a buffer if `str` is not large enough @@ -3482,7 +3511,7 @@ extern SDL_DECLSPEC char * SDLCALL SDL_uitoa(unsigned int value, char *str, int * Convert a long integer into a string. * * This requires a radix to specified for string format. Specifying 10 - * produces a decimal number, 16 hexidecimal, etc. Must be in the range of 2 + * produces a decimal number, 16 hexadecimal, etc. Must be in the range of 2 * to 36. * * Note that this function will overflow a buffer if `str` is not large enough @@ -3510,7 +3539,7 @@ extern SDL_DECLSPEC char * SDLCALL SDL_ltoa(long value, char *str, int radix); * Convert an unsigned long integer into a string. * * This requires a radix to specified for string format. Specifying 10 - * produces a decimal number, 16 hexidecimal, etc. Must be in the range of 2 + * produces a decimal number, 16 hexadecimal, etc. Must be in the range of 2 * to 36. * * Note that this function will overflow a buffer if `str` is not large enough @@ -3540,7 +3569,7 @@ extern SDL_DECLSPEC char * SDLCALL SDL_ultoa(unsigned long value, char *str, int * Convert a long long integer into a string. * * This requires a radix to specified for string format. Specifying 10 - * produces a decimal number, 16 hexidecimal, etc. Must be in the range of 2 + * produces a decimal number, 16 hexadecimal, etc. Must be in the range of 2 * to 36. * * Note that this function will overflow a buffer if `str` is not large enough @@ -3568,7 +3597,7 @@ extern SDL_DECLSPEC char * SDLCALL SDL_lltoa(long long value, char *str, int rad * Convert an unsigned long long integer into a string. * * This requires a radix to specified for string format. Specifying 10 - * produces a decimal number, 16 hexidecimal, etc. Must be in the range of 2 + * produces a decimal number, 16 hexadecimal, etc. Must be in the range of 2 * to 36. * * Note that this function will overflow a buffer if `str` is not large enough @@ -3923,7 +3952,7 @@ extern SDL_DECLSPEC int SDLCALL SDL_strcasecmp(const char *str1, const char *str extern SDL_DECLSPEC int SDLCALL SDL_strncasecmp(const char *str1, const char *str2, size_t maxlen); /** - * Searches a string for the first occurence of any character contained in a + * Searches a string for the first occurrence of any character contained in a * breakset, and returns a pointer from the string to that character. * * \param str The null-terminated string to be searched. Must not be NULL, and @@ -3931,7 +3960,7 @@ extern SDL_DECLSPEC int SDLCALL SDL_strncasecmp(const char *str1, const char *st * \param breakset A null-terminated string containing the list of characters * to look for. Must not be NULL, and must not overlap with * `str`. - * \returns A pointer to the location, in str, of the first occurence of a + * \returns A pointer to the location, in str, of the first occurrence of a * character present in the breakset, or NULL if none is found. * * \threadsafety It is safe to call this function from any thread. @@ -4722,7 +4751,7 @@ extern SDL_DECLSPEC float SDLCALL SDL_atan2f(float y, float x); /** * Compute the ceiling of `x`. * - * The ceiling of `x` is the smallest integer `y` such that `y > x`, i.e `x` + * The ceiling of `x` is the smallest integer `y` such that `y >= x`, i.e `x` * rounded up to the nearest integer. * * Domain: `-INF <= x <= INF` @@ -4750,7 +4779,7 @@ extern SDL_DECLSPEC double SDLCALL SDL_ceil(double x); /** * Compute the ceiling of `x`. * - * The ceiling of `x` is the smallest integer `y` such that `y > x`, i.e `x` + * The ceiling of `x` is the smallest integer `y` such that `y >= x`, i.e `x` * rounded up to the nearest integer. * * Domain: `-INF <= x <= INF` @@ -4992,7 +5021,7 @@ extern SDL_DECLSPEC float SDLCALL SDL_fabsf(float x); /** * Compute the floor of `x`. * - * The floor of `x` is the largest integer `y` such that `y > x`, i.e `x` + * The floor of `x` is the largest integer `y` such that `y <= x`, i.e `x` * rounded down to the nearest integer. * * Domain: `-INF <= x <= INF` @@ -5020,7 +5049,7 @@ extern SDL_DECLSPEC double SDLCALL SDL_floor(double x); /** * Compute the floor of `x`. * - * The floor of `x` is the largest integer `y` such that `y > x`, i.e `x` + * The floor of `x` is the largest integer `y` such that `y <= x`, i.e `x` * rounded down to the nearest integer. * * Domain: `-INF <= x <= INF` @@ -5794,6 +5823,8 @@ typedef struct SDL_iconv_data_t *SDL_iconv_t; * \returns a handle that must be freed with SDL_iconv_close, or * SDL_ICONV_ERROR on failure. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_iconv @@ -5809,6 +5840,8 @@ extern SDL_DECLSPEC SDL_iconv_t SDLCALL SDL_iconv_open(const char *tocode, * \param cd The character set conversion handle. * \returns 0 on success, or -1 on failure. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_iconv @@ -5821,7 +5854,7 @@ extern SDL_DECLSPEC int SDLCALL SDL_iconv_close(SDL_iconv_t cd); * This function converts text between encodings, reading from and writing to * a buffer. * - * It returns the number of succesful conversions on success. On error, + * It returns the number of successful conversions on success. On error, * SDL_ICONV_E2BIG is returned when the output buffer is too small, or * SDL_ICONV_EILSEQ is returned when an invalid input sequence is encountered, * or SDL_ICONV_EINVAL is returned when an incomplete input sequence is @@ -5847,6 +5880,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_iconv_close(SDL_iconv_t cd); * \param outbytesleft The number of bytes in the output buffer. * \returns the number of conversions on success, or a negative error code. * + * \threadsafety Do not use the same SDL_iconv_t from two threads at once. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_iconv_open @@ -5882,6 +5917,8 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_iconv(SDL_iconv_t cd, const char **inbuf, * \param inbytesleft the size of the input string _in bytes_. * \returns a new string, converted to the new encoding, or NULL on error. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_iconv_open @@ -5905,6 +5942,8 @@ extern SDL_DECLSPEC char * SDLCALL SDL_iconv_string(const char *tocode, * \param S the string to convert. * \returns a new string, converted to the new encoding, or NULL on error. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ #define SDL_iconv_utf8_locale(S) SDL_iconv_string("", "UTF-8", S, SDL_strlen(S)+1) @@ -5919,9 +5958,11 @@ extern SDL_DECLSPEC char * SDLCALL SDL_iconv_string(const char *tocode, * \param S the string to convert. * \returns a new string, converted to the new encoding, or NULL on error. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ -#define SDL_iconv_utf8_ucs2(S) (Uint16 *)SDL_iconv_string("UCS-2", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_utf8_ucs2(S) SDL_reinterpret_cast(Uint16 *, SDL_iconv_string("UCS-2", "UTF-8", S, SDL_strlen(S)+1)) /** * Convert a UTF-8 string to UCS-4. @@ -5933,9 +5974,11 @@ extern SDL_DECLSPEC char * SDLCALL SDL_iconv_string(const char *tocode, * \param S the string to convert. * \returns a new string, converted to the new encoding, or NULL on error. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ -#define SDL_iconv_utf8_ucs4(S) (Uint32 *)SDL_iconv_string("UCS-4", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_utf8_ucs4(S) SDL_reinterpret_cast(Uint32 *, SDL_iconv_string("UCS-4", "UTF-8", S, SDL_strlen(S)+1)) /** * Convert a wchar_t string to UTF-8. @@ -5947,9 +5990,11 @@ extern SDL_DECLSPEC char * SDLCALL SDL_iconv_string(const char *tocode, * \param S the string to convert. * \returns a new string, converted to the new encoding, or NULL on error. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ -#define SDL_iconv_wchar_utf8(S) SDL_iconv_string("UTF-8", "WCHAR_T", (char *)S, (SDL_wcslen(S)+1)*sizeof(wchar_t)) +#define SDL_iconv_wchar_utf8(S) SDL_iconv_string("UTF-8", "WCHAR_T", SDL_reinterpret_cast(const char *, S), (SDL_wcslen(S)+1)*sizeof(wchar_t)) /* force builds using Clang's static analysis tools to use literal C runtime @@ -5974,6 +6019,10 @@ size_t wcslcpy(wchar_t *dst, const wchar_t *src, size_t size); size_t wcslcat(wchar_t *dst, const wchar_t *src, size_t size); #endif +#if !defined(HAVE_STRTOK_R) && !defined(strtok_r) +char *strtok_r(char *str, const char *delim, char **saveptr); +#endif + #ifndef _WIN32 /* strdup is not ANSI but POSIX, and its prototype might be hidden... */ /* not for windows: might conflict with string.h where strdup may have diff --git a/libs/SDL3/include/SDL3/SDL_storage.h b/libs/SDL3/include/SDL3/SDL_storage.h index 1b20b34a..4391daad 100644 --- a/libs/SDL3/include/SDL3/SDL_storage.h +++ b/libs/SDL3/include/SDL3/SDL_storage.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -568,7 +568,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_EnumerateStorageDirectory(SDL_Storage *stor * Remove a file or an empty directory in a writable storage container. * * \param storage a storage container. - * \param path the path of the directory to enumerate. + * \param path the path to remove from the filesystem. * \returns true on success or false on failure; call SDL_GetError() for more * information. * diff --git a/libs/SDL3/include/SDL3/SDL_surface.h b/libs/SDL3/include/SDL3/SDL_surface.h index 69f4d69c..885f8f5d 100644 --- a/libs/SDL3/include/SDL3/SDL_surface.h +++ b/libs/SDL3/include/SDL3/SDL_surface.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -29,13 +29,16 @@ * provides a reasonable toolbox for transforming the data, including copying * between surfaces, filling rectangles in the image data, etc. * - * There is also a simple .bmp loader, SDL_LoadBMP(). SDL itself does not - * provide loaders for various other file formats, but there are several - * excellent external libraries that do, including its own satellite library, + * There is also a simple .bmp loader, SDL_LoadBMP(), and a simple .png + * loader, SDL_LoadPNG(). SDL itself does not provide loaders for other file + * formats, but there are several excellent external libraries that do, + * including its own satellite library, * [SDL_image](https://wiki.libsdl.org/SDL3_image) - * : + * . * - * https://github.com/libsdl-org/SDL_image + * In general these functions are thread-safe in that they can be called on + * different threads with different surfaces. You should not try to modify any + * surface from two threads simultaneously. */ #ifndef SDL_surface_h_ @@ -86,7 +89,7 @@ typedef enum SDL_ScaleMode SDL_SCALEMODE_INVALID = -1, SDL_SCALEMODE_NEAREST, /**< nearest pixel sampling */ SDL_SCALEMODE_LINEAR, /**< linear filtering */ - SDL_SCALEMODE_PIXELART /**< nearest pixel sampling with improved scaling for pixel art */ + SDL_SCALEMODE_PIXELART /**< nearest pixel sampling with improved scaling for pixel art, available since SDL 3.4.0 */ } SDL_ScaleMode; /** @@ -96,9 +99,10 @@ typedef enum SDL_ScaleMode */ typedef enum SDL_FlipMode { - SDL_FLIP_NONE, /**< Do not flip */ - SDL_FLIP_HORIZONTAL, /**< flip horizontally */ - SDL_FLIP_VERTICAL /**< flip vertically */ + SDL_FLIP_NONE, /**< Do not flip */ + SDL_FLIP_HORIZONTAL, /**< flip horizontally */ + SDL_FLIP_VERTICAL, /**< flip vertically */ + SDL_FLIP_HORIZONTAL_AND_VERTICAL = (SDL_FLIP_HORIZONTAL | SDL_FLIP_VERTICAL) /**< flip horizontally and vertically (not a diagonal flip) */ } SDL_FlipMode; #ifndef SDL_INTERNAL @@ -237,6 +241,12 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroySurface(SDL_Surface *surface); * left edge of the image, if this surface is being used as a cursor. * - `SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER`: the hotspot pixel offset from the * top edge of the image, if this surface is being used as a cursor. + * - `SDL_PROP_SURFACE_ROTATION_FLOAT`: the number of degrees a surface's data + * is meant to be rotated clockwise to make the image right-side up. Default + * 0. This is used by the camera API, if a mobile device is oriented + * differently than what its camera provides (i.e. - the camera always + * provides portrait images but the phone is being held in landscape + * orientation). Since SDL 3.4.0. * * \param surface the SDL_Surface structure to query. * \returns a valid property ID on success or 0 on failure; call @@ -253,6 +263,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetSurfaceProperties(SDL_Surfac #define SDL_PROP_SURFACE_TONEMAP_OPERATOR_STRING "SDL.surface.tonemap" #define SDL_PROP_SURFACE_HOTSPOT_X_NUMBER "SDL.surface.hotspot.x" #define SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER "SDL.surface.hotspot.y" +#define SDL_PROP_SURFACE_ROTATION_FLOAT "SDL.surface.rotation" /** * Set the colorspace used by a surface. @@ -266,7 +277,8 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetSurfaceProperties(SDL_Surfac * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -285,7 +297,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetSurfaceColorspace(SDL_Surface *surface, * \returns the colorspace used by the surface, or SDL_COLORSPACE_UNKNOWN if * the surface is NULL. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -315,7 +328,8 @@ extern SDL_DECLSPEC SDL_Colorspace SDLCALL SDL_GetSurfaceColorspace(SDL_Surface * the surface didn't have an index format); call SDL_GetError() for * more information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -326,6 +340,9 @@ extern SDL_DECLSPEC SDL_Palette * SDLCALL SDL_CreateSurfacePalette(SDL_Surface * /** * Set the palette used by a surface. * + * Setting the palette keeps an internal reference to the palette, which can + * be safely destroyed afterwards. + * * A single palette can be shared with many surfaces. * * \param surface the SDL_Surface structure to update. @@ -333,7 +350,8 @@ extern SDL_DECLSPEC SDL_Palette * SDLCALL SDL_CreateSurfacePalette(SDL_Surface * * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -374,7 +392,8 @@ extern SDL_DECLSPEC SDL_Palette * SDLCALL SDL_GetSurfacePalette(SDL_Surface *sur * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -417,7 +436,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SurfaceHasAlternateImages(SDL_Surface *surf * failure; call SDL_GetError() for more information. This should be * freed with SDL_free() when it is no longer needed. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -435,7 +455,8 @@ extern SDL_DECLSPEC SDL_Surface ** SDLCALL SDL_GetSurfaceImages(SDL_Surface *sur * * \param surface the SDL_Surface structure to update. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -461,9 +482,10 @@ extern SDL_DECLSPEC void SDLCALL SDL_RemoveSurfaceAlternateImages(SDL_Surface *s * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. The locking referred to by - * this function is making the pixels available for direct - * access, not thread-safe locking. + * \threadsafety This function can be called on different threads with + * different surfaces. The locking referred to by this function + * is making the pixels available for direct access, not + * thread-safe locking. * * \since This function is available since SDL 3.2.0. * @@ -487,6 +509,46 @@ extern SDL_DECLSPEC bool SDLCALL SDL_LockSurface(SDL_Surface *surface); */ extern SDL_DECLSPEC void SDLCALL SDL_UnlockSurface(SDL_Surface *surface); +/** + * Load a BMP or PNG image from a seekable SDL data stream. + * + * The new surface should be freed with SDL_DestroySurface(). Not doing so + * will result in a memory leak. + * + * \param src the data stream for the surface. + * \param closeio if true, calls SDL_CloseIO() on `src` before returning, even + * in the case of an error. + * \returns a pointer to a new SDL_Surface structure or NULL on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_DestroySurface + * \sa SDL_LoadSurface + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadSurface_IO(SDL_IOStream *src, bool closeio); + +/** + * Load a BMP or PNG image from a file. + * + * The new surface should be freed with SDL_DestroySurface(). Not doing so + * will result in a memory leak. + * + * \param file the file to load. + * \returns a pointer to a new SDL_Surface structure or NULL on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_DestroySurface + * \sa SDL_LoadSurface_IO + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadSurface(const char *file); + /** * Load a BMP image from a seekable SDL data stream. * @@ -545,7 +607,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadBMP(const char *file); * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -555,7 +618,7 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadBMP(const char *file); extern SDL_DECLSPEC bool SDLCALL SDL_SaveBMP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio); /** - * Save a surface to a file. + * Save a surface to a file in BMP format. * * Surfaces with a 24-bit, 32-bit and paletted 8-bit format get saved in the * BMP directly. Other RGB formats with 8-bit or higher get converted to a @@ -568,7 +631,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SaveBMP_IO(SDL_Surface *surface, SDL_IOStre * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -577,6 +641,94 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SaveBMP_IO(SDL_Surface *surface, SDL_IOStre */ extern SDL_DECLSPEC bool SDLCALL SDL_SaveBMP(SDL_Surface *surface, const char *file); +/** + * Load a PNG image from a seekable SDL data stream. + * + * This is intended as a convenience function for loading images from trusted + * sources. If you want to load arbitrary images you should use libpng or + * another image loading library designed with security in mind. + * + * The new surface should be freed with SDL_DestroySurface(). Not doing so + * will result in a memory leak. + * + * \param src the data stream for the surface. + * \param closeio if true, calls SDL_CloseIO() on `src` before returning, even + * in the case of an error. + * \returns a pointer to a new SDL_Surface structure or NULL on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_DestroySurface + * \sa SDL_LoadPNG + * \sa SDL_SavePNG_IO + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadPNG_IO(SDL_IOStream *src, bool closeio); + +/** + * Load a PNG image from a file. + * + * This is intended as a convenience function for loading images from trusted + * sources. If you want to load arbitrary images you should use libpng or + * another image loading library designed with security in mind. + * + * The new surface should be freed with SDL_DestroySurface(). Not doing so + * will result in a memory leak. + * + * \param file the PNG file to load. + * \returns a pointer to a new SDL_Surface structure or NULL on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_DestroySurface + * \sa SDL_LoadPNG_IO + * \sa SDL_SavePNG + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadPNG(const char *file); + +/** + * Save a surface to a seekable SDL data stream in PNG format. + * + * \param surface the SDL_Surface structure containing the image to be saved. + * \param dst a data stream to save to. + * \param closeio if true, calls SDL_CloseIO() on `dst` before returning, even + * in the case of an error. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function can be called on different threads with + * different surfaces. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_LoadPNG_IO + * \sa SDL_SavePNG + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio); + +/** + * Save a surface to a file in PNG format. + * + * \param surface the SDL_Surface structure containing the image to be saved. + * \param file a file to save to. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function can be called on different threads with + * different surfaces. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_LoadPNG + * \sa SDL_SavePNG_IO + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SavePNG(SDL_Surface *surface, const char *file); + /** * Set the RLE acceleration hint for a surface. * @@ -588,7 +740,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SaveBMP(SDL_Surface *surface, const char *f * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -630,7 +783,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SurfaceHasRLE(SDL_Surface *surface); * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -695,7 +849,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetSurfaceColorKey(SDL_Surface *surface, Ui * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -715,7 +870,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetSurfaceColorMod(SDL_Surface *surface, Ui * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -737,7 +893,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetSurfaceColorMod(SDL_Surface *surface, Ui * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -775,7 +932,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetSurfaceAlphaMod(SDL_Surface *surface, Ui * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -814,7 +972,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetSurfaceBlendMode(SDL_Surface *surface, S * \returns true if the rectangle intersects the surface, otherwise false and * blits will be completely clipped. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -835,7 +994,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetSurfaceClipRect(SDL_Surface *surface, co * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -851,12 +1011,42 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetSurfaceClipRect(SDL_Surface *surface, SD * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_FlipSurface(SDL_Surface *surface, SDL_FlipMode flip); +/** + * Return a copy of a surface rotated clockwise a number of degrees. + * + * The angle of rotation can be negative for counter-clockwise rotation. + * + * When the rotation isn't a multiple of 90 degrees, the resulting surface is + * larger than the original, with the background filled in with the colorkey, + * if available, or RGBA 255/255/255/0 if not. + * + * If `surface` has the SDL_PROP_SURFACE_ROTATION_FLOAT property set on it, + * the new copy will have the adjusted value set: if the rotation property is + * 90 and `angle` was 30, the new surface will have a property value of 60 + * (that is: to be upright vs gravity, this surface needs to rotate 60 more + * degrees). However, note that further rotations on the new surface in this + * example will produce unexpected results, since the image will have resized + * and padded to accommodate the not-90 degree angle. + * + * \param surface the surface to rotate. + * \param angle the rotation angle, in degrees. + * \returns a rotated copy of the surface or NULL on failure; call + * SDL_GetError() for more information. + * + * \threadsafety This function can be called on different threads with + * different surfaces. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_RotateSurface(SDL_Surface *surface, float angle); + /** * Creates a new surface identical to the existing surface. * @@ -869,7 +1059,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_FlipSurface(SDL_Surface *surface, SDL_FlipM * \returns a copy of the surface or NULL on failure; call SDL_GetError() for * more information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -890,7 +1081,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_DuplicateSurface(SDL_Surface *surf * \returns a copy of the surface or NULL on failure; call SDL_GetError() for * more information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -917,7 +1109,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_ScaleSurface(SDL_Surface *surface, * \returns the new SDL_Surface structure that is created or NULL on failure; * call SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -945,7 +1138,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_ConvertSurface(SDL_Surface *surfac * \returns the new SDL_Surface structure that is created or NULL on failure; * call SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -1048,7 +1242,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_PremultiplyAlpha(int width, int height, SDL * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. */ @@ -1060,7 +1255,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_PremultiplySurfaceAlpha(SDL_Surface *surfac * This function handles all surface formats, and ignores any clip rectangle. * * If the surface is YUV, the color is assumed to be in the sRGB colorspace, - * otherwise the color is assumed to be in the colorspace of the suface. + * otherwise the color is assumed to be in the colorspace of the surface. * * \param surface the SDL_Surface to clear. * \param r the red component of the pixel, normally in the range 0-1. @@ -1070,7 +1265,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_PremultiplySurfaceAlpha(SDL_Surface *surfac * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. */ @@ -1095,7 +1291,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ClearSurface(SDL_Surface *surface, float r, * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -1122,7 +1319,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_FillSurfaceRect(SDL_Surface *dst, const SDL * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -1413,7 +1611,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_BlitSurface9Grid(SDL_Surface *src, const SD * \param b the blue component of the pixel in the range 0-255. * \returns a pixel value. * - * \threadsafety It is safe to call this function from any thread. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -1446,7 +1645,8 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_MapSurfaceRGB(SDL_Surface *surface, Uint8 * \param a the alpha component of the pixel in the range 0-255. * \returns a pixel value. * - * \threadsafety It is safe to call this function from any thread. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. * @@ -1477,7 +1677,8 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_MapSurfaceRGBA(SDL_Surface *surface, Uint * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. */ @@ -1503,7 +1704,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadSurfacePixel(SDL_Surface *surface, int * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. */ @@ -1528,7 +1730,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadSurfacePixelFloat(SDL_Surface *surface, * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. */ @@ -1550,7 +1753,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteSurfacePixel(SDL_Surface *surface, int * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety This function can be called on different threads with + * different surfaces. * * \since This function is available since SDL 3.2.0. */ diff --git a/libs/SDL3/include/SDL3/SDL_system.h b/libs/SDL3/include/SDL3/SDL_system.h index 625db182..ec23a1fe 100644 --- a/libs/SDL3/include/SDL3/SDL_system.h +++ b/libs/SDL3/include/SDL3/SDL_system.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -86,6 +86,8 @@ typedef bool (SDLCALL *SDL_WindowsMessageHook)(void *userdata, MSG *msg); * \param callback the SDL_WindowsMessageHook function to call. * \param userdata a pointer to pass to every iteration of `callback`. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_WindowsMessageHook @@ -169,6 +171,8 @@ typedef bool (SDLCALL *SDL_X11EventHook)(void *userdata, XEvent *xevent); * \param callback the SDL_X11EventHook function to call. * \param userdata a pointer to pass to every iteration of `callback`. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata); @@ -186,6 +190,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetX11EventHook(SDL_X11EventHook callback, * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriority(Sint64 threadID, int priority); @@ -202,6 +208,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriority(Sint64 threadID, int * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy); @@ -264,6 +272,8 @@ typedef void (SDLCALL *SDL_iOSAnimationCallback)(void *userdata); * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetiOSEventPump @@ -277,6 +287,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetiOSAnimationCallback(SDL_Window *window, * * \param enabled true to enable the event pump, false to disable it. * + * \threadsafety This function should only be called on the main thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_SetiOSAnimationCallback @@ -370,6 +382,8 @@ extern SDL_DECLSPEC void * SDLCALL SDL_GetAndroidActivity(void); * * \returns the Android API level. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC int SDLCALL SDL_GetAndroidSDKVersion(void); @@ -379,6 +393,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetAndroidSDKVersion(void); * * \returns true if this is a Chromebook, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_IsChromebook(void); @@ -388,6 +404,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_IsChromebook(void); * * \returns true if this is a DeX docking station, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_IsDeXMode(void); @@ -605,6 +623,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SendAndroidMessage(Uint32 command, int para * * \returns true if the device is a tablet, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_IsTablet(void); @@ -616,6 +636,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_IsTablet(void); * * \returns true if the device is a TV, false otherwise. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_IsTV(void); diff --git a/libs/SDL3/include/SDL3/SDL_test.h b/libs/SDL3/include/SDL3/SDL_test.h index 8b285807..59a3dd84 100644 --- a/libs/SDL3/include/SDL3/SDL_test.h +++ b/libs/SDL3/include/SDL3/SDL_test.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_test_assert.h b/libs/SDL3/include/SDL3/SDL_test_assert.h index c067608a..ff41b31b 100644 --- a/libs/SDL3/include/SDL3/SDL_test_assert.h +++ b/libs/SDL3/include/SDL3/SDL_test_assert.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_test_common.h b/libs/SDL3/include/SDL3/SDL_test_common.h index 91efe8ac..6afd8577 100644 --- a/libs/SDL3/include/SDL3/SDL_test_common.h +++ b/libs/SDL3/include/SDL3/SDL_test_common.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -153,6 +153,10 @@ typedef struct SDL_Rect confine; bool hide_cursor; + /* Misc. */ + int quit_after_ms_interval; + SDL_TimerID quit_after_ms_timer; + /* Options info */ SDLTest_ArgumentParser common_argparser; SDLTest_ArgumentParser video_argparser; diff --git a/libs/SDL3/include/SDL3/SDL_test_compare.h b/libs/SDL3/include/SDL3/SDL_test_compare.h index 7ff24779..58e55134 100644 --- a/libs/SDL3/include/SDL3/SDL_test_compare.h +++ b/libs/SDL3/include/SDL3/SDL_test_compare.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -52,6 +52,7 @@ extern "C" { * \returns 0 if comparison succeeded, >0 (=number of pixels for which the comparison failed) if comparison failed, -1 if any of the surfaces were NULL, -2 if the surface sizes differ. */ int SDLCALL SDLTest_CompareSurfaces(SDL_Surface *surface, SDL_Surface *referenceSurface, int allowable_error); +int SDLCALL SDLTest_CompareSurfacesIgnoreTransparentPixels(SDL_Surface *surface, SDL_Surface *referenceSurface, int allowable_error); /** * Compares 2 memory blocks for equality diff --git a/libs/SDL3/include/SDL3/SDL_test_crc32.h b/libs/SDL3/include/SDL3/SDL_test_crc32.h index 94fe1a34..f563d5dd 100644 --- a/libs/SDL3/include/SDL3/SDL_test_crc32.h +++ b/libs/SDL3/include/SDL3/SDL_test_crc32.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_test_font.h b/libs/SDL3/include/SDL3/SDL_test_font.h index fb407949..f5b56745 100644 --- a/libs/SDL3/include/SDL3/SDL_test_font.h +++ b/libs/SDL3/include/SDL3/SDL_test_font.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_test_fuzzer.h b/libs/SDL3/include/SDL3/SDL_test_fuzzer.h index caf11f25..e8ebc45c 100644 --- a/libs/SDL3/include/SDL3/SDL_test_fuzzer.h +++ b/libs/SDL3/include/SDL3/SDL_test_fuzzer.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_test_harness.h b/libs/SDL3/include/SDL3/SDL_test_harness.h index 5d4963e7..37710455 100644 --- a/libs/SDL3/include/SDL3/SDL_test_harness.h +++ b/libs/SDL3/include/SDL3/SDL_test_harness.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_test_log.h b/libs/SDL3/include/SDL3/SDL_test_log.h index 0d48044a..13933da9 100644 --- a/libs/SDL3/include/SDL3/SDL_test_log.h +++ b/libs/SDL3/include/SDL3/SDL_test_log.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -42,6 +42,14 @@ extern "C" { #endif +/** + * Prints given message with a timestamp in the TEST category and given priority. + * + * \param priority Priority of the message + * \param fmt Message to be logged + */ +void SDLCALL SDLTest_LogMessage(SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, ...); + /** * Prints given message with a timestamp in the TEST category and INFO priority. * diff --git a/libs/SDL3/include/SDL3/SDL_test_md5.h b/libs/SDL3/include/SDL3/SDL_test_md5.h index e9d96398..7b86b05f 100644 --- a/libs/SDL3/include/SDL3/SDL_test_md5.h +++ b/libs/SDL3/include/SDL3/SDL_test_md5.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_test_memory.h b/libs/SDL3/include/SDL3/SDL_test_memory.h index 1bd94667..f07f1cba 100644 --- a/libs/SDL3/include/SDL3/SDL_test_memory.h +++ b/libs/SDL3/include/SDL3/SDL_test_memory.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_thread.h b/libs/SDL3/include/SDL3/SDL_thread.h index e981b542..1842c035 100644 --- a/libs/SDL3/include/SDL3/SDL_thread.h +++ b/libs/SDL3/include/SDL3/SDL_thread.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -37,7 +37,8 @@ * will report failure without doing anything. * * If you're going to work with threads, you almost certainly need to have a - * good understanding of [CategoryMutex](CategoryMutex) as well. + * good understanding of thread safety measures: locking and synchronization + * mechanisms are handled by the functions in SDL_mutex.h. */ #include @@ -207,6 +208,8 @@ typedef int (SDLCALL *SDL_ThreadFunction) (void *data); * new thread could not be created; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CreateThreadWithProperties @@ -273,6 +276,8 @@ extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThread(SDL_ThreadFunction fn, * new thread could not be created; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CreateThread @@ -328,6 +333,8 @@ extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThreadWithProperties(SDL_Prop * new thread could not be created; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThreadRuntime(SDL_ThreadFunction fn, const char *name, void *data, SDL_FunctionPointer pfnBeginThread, SDL_FunctionPointer pfnEndThread); @@ -342,6 +349,8 @@ extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThreadRuntime(SDL_ThreadFunct * new thread could not be created; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThreadWithPropertiesRuntime(SDL_PropertiesID props, SDL_FunctionPointer pfnBeginThread, SDL_FunctionPointer pfnEndThread); @@ -362,6 +371,8 @@ extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThreadWithPropertiesRuntime(S * \returns a pointer to a UTF-8 string that names the specified thread, or * NULL if it doesn't have a name. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC const char * SDLCALL SDL_GetThreadName(SDL_Thread *thread); @@ -378,6 +389,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetThreadName(SDL_Thread *thread); * * \returns the ID of the current thread. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetThreadID @@ -395,6 +408,8 @@ extern SDL_DECLSPEC SDL_ThreadID SDLCALL SDL_GetCurrentThreadID(void); * \returns the ID of the specified thread, or the ID of the current thread if * `thread` is NULL. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetCurrentThreadID @@ -412,6 +427,8 @@ extern SDL_DECLSPEC SDL_ThreadID SDLCALL SDL_GetThreadID(SDL_Thread *thread); * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_SetCurrentThreadPriority(SDL_ThreadPriority priority); @@ -444,6 +461,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetCurrentThreadPriority(SDL_ThreadPriority * function by its 'return', or -1 if the thread has been * detached or isn't valid, may be NULL. * + * \threadsafety It is safe to call this function from any thread, but only a + * single thread can wait any specific thread to finish. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CreateThread @@ -458,6 +478,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_WaitThread(SDL_Thread *thread, int *status) * \returns the current state of a thread, or SDL_THREAD_UNKNOWN if the thread * isn't valid. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_ThreadState @@ -493,6 +515,8 @@ extern SDL_DECLSPEC SDL_ThreadState SDLCALL SDL_GetThreadState(SDL_Thread *threa * \param thread the SDL_Thread pointer that was returned from the * SDL_CreateThread() call that started this thread. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_CreateThread diff --git a/libs/SDL3/include/SDL3/SDL_time.h b/libs/SDL3/include/SDL3/SDL_time.h index b6d3f6d0..2ae6022f 100644 --- a/libs/SDL3/include/SDL3/SDL_time.h +++ b/libs/SDL3/include/SDL3/SDL_time.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer -Copyright (C) 1997-2025 Sam Lantinga +Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -106,6 +106,8 @@ typedef enum SDL_TimeFormat * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety This function is not thread safe. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetDateTimeLocalePreferences(SDL_DateFormat *dateFormat, SDL_TimeFormat *timeFormat); @@ -118,6 +120,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetDateTimeLocalePreferences(SDL_DateFormat * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_GetCurrentTime(SDL_Time *ticks); @@ -134,6 +138,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetCurrentTime(SDL_Time *ticks); * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_TimeToDateTime(SDL_Time ticks, SDL_DateTime *dt, bool localTime); @@ -149,6 +155,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_TimeToDateTime(SDL_Time ticks, SDL_DateTime * \returns true on success or false on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC bool SDLCALL SDL_DateTimeToTime(const SDL_DateTime *dt, SDL_Time *ticks); @@ -165,6 +173,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_DateTimeToTime(const SDL_DateTime *dt, SDL_ * \param dwHighDateTime a pointer filled in with the high portion of the * Windows FILETIME value. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC void SDLCALL SDL_TimeToWindows(SDL_Time ticks, Uint32 *dwLowDateTime, Uint32 *dwHighDateTime); @@ -180,6 +190,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_TimeToWindows(SDL_Time ticks, Uint32 *dwLow * \param dwHighDateTime the high portion of the Windows FILETIME value. * \returns the converted SDL time. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC SDL_Time SDLCALL SDL_TimeFromWindows(Uint32 dwLowDateTime, Uint32 dwHighDateTime); @@ -192,6 +204,8 @@ extern SDL_DECLSPEC SDL_Time SDLCALL SDL_TimeFromWindows(Uint32 dwLowDateTime, U * \returns the number of days in the requested month or -1 on failure; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC int SDLCALL SDL_GetDaysInMonth(int year, int month); @@ -205,6 +219,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetDaysInMonth(int year, int month); * \returns the day of year [0-365] if the date is valid or -1 on failure; * call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC int SDLCALL SDL_GetDayOfYear(int year, int month, int day); @@ -218,6 +234,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetDayOfYear(int year, int month, int day); * \returns a value between 0 and 6 (0 being Sunday) if the date is valid or * -1 on failure; call SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. */ extern SDL_DECLSPEC int SDLCALL SDL_GetDayOfWeek(int year, int month, int day); diff --git a/libs/SDL3/include/SDL3/SDL_timer.h b/libs/SDL3/include/SDL3/SDL_timer.h index 9088a792..dfeec31f 100644 --- a/libs/SDL3/include/SDL3/SDL_timer.h +++ b/libs/SDL3/include/SDL3/SDL_timer.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -34,7 +34,7 @@ * This category covers measuring time elapsed (SDL_GetTicks(), * SDL_GetPerformanceCounter()), putting a thread to sleep for a certain * amount of time (SDL_Delay(), SDL_DelayNS(), SDL_DelayPrecise()), and firing - * a callback function after a certain amount of time has elasped + * a callback function after a certain amount of time has elapsed * (SDL_AddTimer(), etc). * * There are also useful macros to convert between time units, like @@ -195,6 +195,8 @@ extern "C" { * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_GetTicksNS */ extern SDL_DECLSPEC Uint64 SDLCALL SDL_GetTicks(void); diff --git a/libs/SDL3/include/SDL3/SDL_touch.h b/libs/SDL3/include/SDL3/SDL_touch.h index 64845a15..e5576a5b 100644 --- a/libs/SDL3/include/SDL3/SDL_touch.h +++ b/libs/SDL3/include/SDL3/SDL_touch.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_tray.h b/libs/SDL3/include/SDL3/SDL_tray.h index 1780b0ba..688278a1 100644 --- a/libs/SDL3/include/SDL3/SDL_tray.h +++ b/libs/SDL3/include/SDL3/SDL_tray.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/SDL3/SDL_version.h b/libs/SDL3/include/SDL3/SDL_version.h index e3fb742d..b5a30692 100644 --- a/libs/SDL3/include/SDL3/SDL_version.h +++ b/libs/SDL3/include/SDL3/SDL_version.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -53,7 +53,7 @@ extern "C" { * * \since This macro is available since SDL 3.2.0. */ -#define SDL_MINOR_VERSION 3 +#define SDL_MINOR_VERSION 4 /** * The current micro (or patchlevel) version of the SDL headers. @@ -62,7 +62,7 @@ extern "C" { * * \since This macro is available since SDL 3.2.0. */ -#define SDL_MICRO_VERSION 0 +#define SDL_MICRO_VERSION 10 /** * This macro turns the version numbers into a numeric value. @@ -85,6 +85,8 @@ extern "C" { * * \param version the version number. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ #define SDL_VERSIONNUM_MAJOR(version) ((version) / 1000000) @@ -96,6 +98,8 @@ extern "C" { * * \param version the version number. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ #define SDL_VERSIONNUM_MINOR(version) (((version) / 1000) % 1000) @@ -107,6 +111,8 @@ extern "C" { * * \param version the version number. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ #define SDL_VERSIONNUM_MICRO(version) ((version) % 1000) @@ -114,6 +120,8 @@ extern "C" { /** * This is the version number macro for the current SDL version. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. * * \sa SDL_GetVersion @@ -124,6 +132,8 @@ extern "C" { /** * This macro will evaluate to true if compiled with SDL at least X.Y.Z. * + * \threadsafety It is safe to call this macro from any thread. + * * \since This macro is available since SDL 3.2.0. */ #define SDL_VERSION_ATLEAST(X, Y, Z) \ @@ -141,6 +151,8 @@ extern "C" { * * \returns the version of the linked library. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetRevision @@ -153,7 +165,7 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetVersion(void); * * This value is the revision of the code you are linking against and may be * different from the code you are compiling with, which is found in the - * constant SDL_REVISION. + * constant SDL_REVISION if you explicitly include SDL_revision.h * * The revision is an arbitrary string (a hash value) uniquely identifying the * exact revision of the SDL library in use, and is only useful in comparing @@ -168,6 +180,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetVersion(void); * \returns an arbitrary string, uniquely identifying the exact revision of * the SDL library in use. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.2.0. * * \sa SDL_GetVersion diff --git a/libs/SDL3/include/SDL3/SDL_video.h b/libs/SDL3/include/SDL3/SDL_video.h index d5107b43..2f062959 100644 --- a/libs/SDL3/include/SDL3/SDL_video.h +++ b/libs/SDL3/include/SDL3/SDL_video.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -97,6 +97,8 @@ typedef Uint32 SDL_WindowID; * uninitialized will either return the user provided value, if one was set * prior to initialization, or NULL. See docs/README-wayland.md for more * information. + * + * \since This macro is available since SDL 3.2.0. */ #define SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER "SDL.video.wayland.wl_display" @@ -180,6 +182,12 @@ typedef struct SDL_Window SDL_Window; * changed on existing windows by the app, and some of it might be altered by * the user or system outside of the app's control. * + * When creating windows with `SDL_WINDOW_RESIZABLE`, SDL will constrain + * resizable windows to the dimensions recommended by the compositor to fit it + * within the usable desktop space, although some compositors will do this + * automatically without intervention as well. Use `SDL_SetWindowResizable` + * after creation instead if you wish to create a window with a specific size. + * * \since This datatype is available since SDL 3.2.0. * * \sa SDL_GetWindowFlags @@ -207,6 +215,7 @@ typedef Uint64 SDL_WindowFlags; #define SDL_WINDOW_TOOLTIP SDL_UINT64_C(0x0000000000040000) /**< window should be treated as a tooltip and does not get mouse or keyboard focus, requires a parent window */ #define SDL_WINDOW_POPUP_MENU SDL_UINT64_C(0x0000000000080000) /**< window should be treated as a popup menu, requires a parent window */ #define SDL_WINDOW_KEYBOARD_GRABBED SDL_UINT64_C(0x0000000000100000) /**< window has grabbed keyboard input */ +#define SDL_WINDOW_FILL_DOCUMENT SDL_UINT64_C(0x0000000000200000) /**< window is in fill-document mode (Emscripten only), since SDL 3.4.0 */ #define SDL_WINDOW_VULKAN SDL_UINT64_C(0x0000000010000000) /**< window usable for Vulkan surface */ #define SDL_WINDOW_METAL SDL_UINT64_C(0x0000000020000000) /**< window usable for Metal view */ #define SDL_WINDOW_TRANSPARENT SDL_UINT64_C(0x0000000040000000) /**< window with transparent buffer */ @@ -220,6 +229,8 @@ typedef Uint64 SDL_WindowFlags; * SDL_WINDOWPOS_UNDEFINED or SDL_WINDOWPOS_UNDEFINED_DISPLAY. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_SetWindowPosition */ #define SDL_WINDOWPOS_UNDEFINED_MASK 0x1FFF0000u @@ -232,6 +243,8 @@ typedef Uint64 SDL_WindowFlags; * \param X the SDL_DisplayID of the display to use. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_SetWindowPosition */ #define SDL_WINDOWPOS_UNDEFINED_DISPLAY(X) (SDL_WINDOWPOS_UNDEFINED_MASK|(X)) @@ -241,6 +254,8 @@ typedef Uint64 SDL_WindowFlags; * This always uses the primary display. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_SetWindowPosition */ #define SDL_WINDOWPOS_UNDEFINED SDL_WINDOWPOS_UNDEFINED_DISPLAY(0) @@ -250,6 +265,8 @@ typedef Uint64 SDL_WindowFlags; * \param X the window position value. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_SetWindowPosition */ #define SDL_WINDOWPOS_ISUNDEFINED(X) (((X)&0xFFFF0000) == SDL_WINDOWPOS_UNDEFINED_MASK) @@ -260,6 +277,8 @@ typedef Uint64 SDL_WindowFlags; * SDL_WINDOWPOS_CENTERED or SDL_WINDOWPOS_CENTERED_DISPLAY. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_SetWindowPosition */ #define SDL_WINDOWPOS_CENTERED_MASK 0x2FFF0000u @@ -272,6 +291,8 @@ typedef Uint64 SDL_WindowFlags; * \param X the SDL_DisplayID of the display to use. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_SetWindowPosition */ #define SDL_WINDOWPOS_CENTERED_DISPLAY(X) (SDL_WINDOWPOS_CENTERED_MASK|(X)) @@ -281,6 +302,8 @@ typedef Uint64 SDL_WindowFlags; * This always uses the primary display. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_SetWindowPosition */ #define SDL_WINDOWPOS_CENTERED SDL_WINDOWPOS_CENTERED_DISPLAY(0) @@ -290,6 +313,8 @@ typedef Uint64 SDL_WindowFlags; * \param X the window position value. * * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_GetWindowPosition */ #define SDL_WINDOWPOS_ISCENTERED(X) \ (((X)&0xFFFF0000) == SDL_WINDOWPOS_CENTERED_MASK) @@ -328,6 +353,9 @@ typedef enum SDL_ProgressState * \since This datatype is available since SDL 3.2.0. * * \sa SDL_GL_CreateContext + * \sa SDL_GL_SetAttribute + * \sa SDL_GL_MakeCurrent + * \sa SDL_GL_DestroyContext */ typedef struct SDL_GLContextState *SDL_GLContext; @@ -463,7 +491,7 @@ typedef enum SDL_GLAttr SDL_GL_CONTEXT_FLAGS, /**< some combination of 0 or more of elements of the SDL_GLContextFlag enumeration; defaults to 0. */ SDL_GL_CONTEXT_PROFILE_MASK, /**< type of GL context (Core, Compatibility, ES). See SDL_GLProfile; default value depends on platform. */ SDL_GL_SHARE_WITH_CURRENT_CONTEXT, /**< OpenGL context sharing; defaults to 0. */ - SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, /**< requests sRGB capable visual; defaults to 0. */ + SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, /**< requests sRGB-capable visual if 1. Defaults to -1 ("don't care"). This is a request; GL drivers might not comply! */ SDL_GL_CONTEXT_RELEASE_BEHAVIOR, /**< sets context the release behavior. See SDL_GLContextReleaseFlag; defaults to FLUSH. */ SDL_GL_CONTEXT_RESET_NOTIFICATION, /**< set context reset notification. See SDL_GLContextResetNotification; defaults to NO_NOTIFICATION. */ SDL_GL_CONTEXT_NO_ERROR, @@ -545,7 +573,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumVideoDrivers(void); * to be proper names. * * \param index the index of a video driver. - * \returns the name of the video driver with the given **index**. + * \returns the name of the video driver with the given **index**, or NULL if + * index is out of bounds. * * \threadsafety This function should only be called on the main thread. * @@ -637,6 +666,11 @@ extern SDL_DECLSPEC SDL_DisplayID SDLCALL SDL_GetPrimaryDisplay(void); * - `SDL_PROP_DISPLAY_WAYLAND_WL_OUTPUT_POINTER`: the wl_output associated * with the display * + * On Windows: + * + * - `SDL_PROP_DISPLAY_WINDOWS_HMONITOR_POINTER`: the monitor handle + * (HMONITOR) associated with the display + * * \param displayID the instance ID of the display to query. * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. @@ -650,6 +684,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetDisplayProperties(SDL_Displa #define SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN "SDL.display.HDR_enabled" #define SDL_PROP_DISPLAY_KMSDRM_PANEL_ORIENTATION_NUMBER "SDL.display.KMSDRM.panel_orientation" #define SDL_PROP_DISPLAY_WAYLAND_WL_OUTPUT_POINTER "SDL.display.wayland.wl_output" +#define SDL_PROP_DISPLAY_WINDOWS_HMONITOR_POINTER "SDL.display.windows.hmonitor" /** * Get the name of a display in UTF-8 encoding. @@ -1070,8 +1105,6 @@ extern SDL_DECLSPEC SDL_Window ** SDLCALL SDL_GetWindows(int *count); * * - `SDL_WINDOW_FULLSCREEN`: fullscreen window at desktop resolution * - `SDL_WINDOW_OPENGL`: window usable with an OpenGL context - * - `SDL_WINDOW_OCCLUDED`: window partially or completely obscured by another - * window * - `SDL_WINDOW_HIDDEN`: window is not visible * - `SDL_WINDOW_BORDERLESS`: no window decoration * - `SDL_WINDOW_RESIZABLE`: window can be resized @@ -1099,7 +1132,8 @@ extern SDL_DECLSPEC SDL_Window ** SDLCALL SDL_GetWindows(int *count); * - `SDL_WINDOW_TRANSPARENT`: window with transparent buffer * - `SDL_WINDOW_NOT_FOCUSABLE`: window should not be focusable * - * The SDL_Window is implicitly shown if SDL_WINDOW_HIDDEN is not set. + * The SDL_Window will be shown if SDL_WINDOW_HIDDEN is not set. If hidden at + * creation time, SDL_ShowWindow() can be used to show it later. * * On Apple's macOS, you **must** set the NSHighResolutionCapable Info.plist * property to YES, otherwise you will not receive a High-DPI OpenGL canvas. @@ -1298,6 +1332,12 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren * - `SDL_PROP_WINDOW_CREATE_COCOA_VIEW_POINTER`: the `(__unsafe_unretained)` * NSView associated with the window, defaults to `[window contentView]` * + * These are additional supported properties on iOS, tvOS, and visionOS: + * + * - `SDL_PROP_WINDOW_CREATE_WINDOWSCENE_POINTER`: the `(__unsafe_unretained)` + * UIWindowScene associated with the window, defaults to the active window + * scene. + * * These are additional supported properties on Wayland: * * - `SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN` - true if @@ -1397,6 +1437,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowWithProperties(SDL_Prop #define SDL_PROP_WINDOW_CREATE_Y_NUMBER "SDL.window.create.y" #define SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER "SDL.window.create.cocoa.window" #define SDL_PROP_WINDOW_CREATE_COCOA_VIEW_POINTER "SDL.window.create.cocoa.view" +#define SDL_PROP_WINDOW_CREATE_WINDOWSCENE_POINTER "SDL.window.create.uikit.windowscene" #define SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN "SDL.window.create.wayland.surface_role_custom" #define SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN "SDL.window.create.wayland.create_egl_window" #define SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER "SDL.window.create.wayland.wl_surface" @@ -1512,7 +1553,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *window) * - `SDL_PROP_WINDOW_COCOA_WINDOW_POINTER`: the `(__unsafe_unretained)` * NSWindow associated with the window * - `SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER`: the NSInteger tag - * assocated with metal views on the window + * associated with metal views on the window * * On OpenVR: * @@ -1640,6 +1681,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window * \sa SDL_MinimizeWindow * \sa SDL_SetWindowFullscreen * \sa SDL_SetWindowMouseGrab + * \sa SDL_SetWindowFillDocument * \sa SDL_ShowWindow */ extern SDL_DECLSPEC SDL_WindowFlags SDLCALL SDL_GetWindowFlags(SDL_Window *window); @@ -1827,6 +1869,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowSize(SDL_Window *window, int w, in * \sa SDL_GetRenderOutputSize * \sa SDL_GetWindowSizeInPixels * \sa SDL_SetWindowSize + * \sa SDL_EVENT_WINDOW_RESIZED */ extern SDL_DECLSPEC bool SDLCALL SDL_GetWindowSize(SDL_Window *window, int *w, int *h); @@ -1838,7 +1881,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetWindowSize(SDL_Window *window, int *w, i * notches, TV overscan, etc. This function provides the area of the window * which is safe to have interactable content. You should continue rendering * into the rest of the window, but it should not contain visually important - * or interactible content. + * or interactable content. * * \param window the window to query. * \param rect a pointer filled in with the client area that is safe for @@ -2108,6 +2151,37 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowResizable(SDL_Window *window, bool */ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowAlwaysOnTop(SDL_Window *window, bool on_top); +/** + * Set the window to fill the current document space (Emscripten only). + * + * This will add or remove the window's `SDL_WINDOW_FILL_DOCUMENT` flag. + * + * Currently this flag only applies to the Emscripten target. + * + * When enabled, the canvas element fills the entire document. Resize events + * will be generated as the browser window is resized, as that will adjust the + * canvas size as well. The canvas will cover anything else on the page, + * including any controls provided by Emscripten in its generated HTML file + * (in fact, any elements on the page that aren't the canvas will be moved + * into a hidden `div` element). + * + * Often times this is desirable for a browser-based game, but it means + * several things that we expect of an SDL window on other platforms might not + * work as expected, such as minimum window sizes and aspect ratios. + * + * \param window the window of which to change the fill-document state. + * \param fill true to set the window to fill the document, false to disable. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_GetWindowFlags + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowFillDocument(SDL_Window *window, bool fill); + /** * Show a window. * @@ -3156,8 +3230,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_GL_ResetAttributes(void); * SDL_GL_GetAttribute() to check the values after creating the OpenGL * context, since the values obtained can differ from the requested ones. * - * \param attr an SDL_GLAttr enum value specifying the OpenGL attribute to - * set. + * \param attr an enum value specifying the OpenGL attribute to set. * \param value the desired value for the attribute. * \returns true on success or false on failure; call SDL_GetError() for more * information. @@ -3166,6 +3239,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_GL_ResetAttributes(void); * * \since This function is available since SDL 3.2.0. * + * \sa SDL_GL_CreateContext * \sa SDL_GL_GetAttribute * \sa SDL_GL_ResetAttributes */ @@ -3192,6 +3266,12 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GL_GetAttribute(SDL_GLAttr attr, int *value /** * Create an OpenGL context for an OpenGL window, and make it current. * + * The OpenGL context will be created with the current states set through + * SDL_GL_SetAttribute(). + * + * The SDL_Window specified must have been created with the SDL_WINDOW_OPENGL + * flag, or context creation will fail. + * * Windows users new to OpenGL should note that, for historical reasons, GL * functions added after OpenGL version 1.1 are not available by default. * Those functions must be loaded at run-time, either with an OpenGL diff --git a/libs/SDL3/include/build_config/SDL_build_config.h b/libs/SDL3/include/build_config/SDL_build_config.h index 83031b70..a1658c2d 100644 --- a/libs/SDL3/include/build_config/SDL_build_config.h +++ b/libs/SDL3/include/build_config/SDL_build_config.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/build_config/SDL_build_config.h.cmake b/libs/SDL3/include/build_config/SDL_build_config.h.cmake index 76916aff..17cb8f25 100644 --- a/libs/SDL3/include/build_config/SDL_build_config.h.cmake +++ b/libs/SDL3/include/build_config/SDL_build_config.h.cmake @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -107,7 +107,6 @@ #cmakedefine HAVE_STRTOK_R 1 #cmakedefine HAVE_ITOA 1 #cmakedefine HAVE__LTOA 1 -#cmakedefine HAVE__UITOA 1 #cmakedefine HAVE__ULTOA 1 #cmakedefine HAVE_STRTOL 1 #cmakedefine HAVE_STRTOUL 1 @@ -180,6 +179,7 @@ #cmakedefine HAVE_MEMFD_CREATE 1 #cmakedefine HAVE_POSIX_FALLOCATE 1 #cmakedefine HAVE_SIGACTION 1 +#cmakedefine HAVE_SIGTIMEDWAIT 1 #cmakedefine HAVE_SA_SIGACTION 1 #cmakedefine HAVE_ST_MTIM 1 #cmakedefine HAVE_SETJMP 1 @@ -198,8 +198,10 @@ #cmakedefine HAVE_SEM_TIMEDWAIT 1 #cmakedefine HAVE_GETAUXVAL 1 #cmakedefine HAVE_ELF_AUX_INFO 1 -#cmakedefine HAVE_POLL 1 +#cmakedefine HAVE_PPOLL 1 #cmakedefine HAVE__EXIT 1 +#cmakedefine HAVE_GETRESUID 1 +#cmakedefine HAVE_GETRESGID 1 #endif /* HAVE_LIBC */ @@ -215,6 +217,10 @@ #cmakedefine HAVE_LIBUDEV_H 1 #cmakedefine HAVE_LIBDECOR_H 1 #cmakedefine HAVE_LIBURING_H 1 +#cmakedefine HAVE_FRIBIDI_H 1 +#cmakedefine SDL_FRIBIDI_DYNAMIC @SDL_FRIBIDI_DYNAMIC@ +#cmakedefine HAVE_LIBTHAI_H 1 +#cmakedefine SDL_LIBTHAI_DYNAMIC @SDL_LIBTHAI_DYNAMIC@ #cmakedefine HAVE_DDRAW_H 1 #cmakedefine HAVE_DSOUND_H 1 @@ -223,18 +229,20 @@ #cmakedefine HAVE_WINDOWS_GAMING_INPUT_H 1 #cmakedefine HAVE_GAMEINPUT_H 1 #cmakedefine HAVE_DXGI_H 1 +#cmakedefine HAVE_DXGI1_5_H 1 #cmakedefine HAVE_DXGI1_6_H 1 #cmakedefine HAVE_MMDEVICEAPI_H 1 #cmakedefine HAVE_TPCSHRD_H 1 #cmakedefine HAVE_ROAPI_H 1 #cmakedefine HAVE_SHELLSCALINGAPI_H 1 -#cmakedefine HAVE_SHOBJIDL_CORE_H 1 #cmakedefine USE_POSIX_SPAWN 1 #cmakedefine HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR 1 #cmakedefine HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP 1 +#cmakedefine SDL_DISABLE_DLOPEN_NOTES 1 + /* SDL internal assertion support */ #cmakedefine SDL_DEFAULT_ASSERT_LEVEL_CONFIGURED 1 #ifdef SDL_DEFAULT_ASSERT_LEVEL_CONFIGURED @@ -339,6 +347,7 @@ #cmakedefine SDL_SENSOR_DUMMY 1 #cmakedefine SDL_SENSOR_VITA 1 #cmakedefine SDL_SENSOR_N3DS 1 +#cmakedefine SDL_SENSOR_EMSCRIPTEN 1 #cmakedefine SDL_SENSOR_PRIVATE 1 @@ -423,13 +432,15 @@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR @SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS @SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST @SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST@ -#cmakedefine SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM 1 +#cmakedefine SDL_VIDEO_DRIVER_X11_HAS_XKBLIB 1 #cmakedefine SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XCURSOR 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XDBE 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XFIXES 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XINPUT2 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1 +#cmakedefine SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 1 +#cmakedefine SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE @SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE@ #cmakedefine SDL_VIDEO_DRIVER_X11_XRANDR 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XSCRNSAVER 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XSHAPE 1 @@ -539,6 +550,9 @@ /* Enable dialog subsystem */ #cmakedefine SDL_DIALOG_DUMMY 1 +/* Enable tray subsystem */ +#cmakedefine SDL_TRAY_DUMMY 1 + /* Enable assembly routines */ #cmakedefine SDL_ALTIVEC_BLITTERS 1 @@ -558,6 +572,8 @@ #cmakedefine SDL_VIDEO_VITA_PVR 1 #cmakedefine SDL_VIDEO_VITA_PVR_OGL 1 +#cmakedefine SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING "@SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING@" + /* xkbcommon version info */ #define SDL_XKBCOMMON_VERSION_MAJOR @SDL_XKBCOMMON_VERSION_MAJOR@ #define SDL_XKBCOMMON_VERSION_MINOR @SDL_XKBCOMMON_VERSION_MINOR@ diff --git a/libs/SDL3/include/build_config/SDL_build_config_android.h b/libs/SDL3/include/build_config/SDL_build_config_android.h index a0727f50..ce6f3e20 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_android.h +++ b/libs/SDL3/include/build_config/SDL_build_config_android.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -208,6 +208,9 @@ #define SDL_CAMERA_DRIVER_ANDROID 1 #endif /* SDL_CAMERA_DISABLED */ +/* Enable tray subsystem */ +#define SDL_TRAY_DUMMY 1 + /* Enable nl_langinfo and high-res file times on version 26 and higher. */ #if __ANDROID_API__ >= 26 #define HAVE_NL_LANGINFO 1 diff --git a/libs/SDL3/include/build_config/SDL_build_config_ios.h b/libs/SDL3/include/build_config/SDL_build_config_ios.h index be1ec29a..308270b5 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_ios.h +++ b/libs/SDL3/include/build_config/SDL_build_config_ios.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -214,7 +214,7 @@ #define SDL_FSOPS_POSIX 1 /* enable camera support */ -#ifndef SDL_PLATFORM_TVOS +#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) #define SDL_CAMERA_DRIVER_COREMEDIA 1 #endif @@ -223,4 +223,7 @@ /* Enable dialog subsystem */ #define SDL_DIALOG_DUMMY 1 +/* Enable tray subsystem */ +#define SDL_TRAY_DUMMY 1 + #endif /* SDL_build_config_ios_h_ */ diff --git a/libs/SDL3/include/build_config/SDL_build_config_macos.h b/libs/SDL3/include/build_config/SDL_build_config_macos.h index 1a66449a..1890c9a6 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_macos.h +++ b/libs/SDL3/include/build_config/SDL_build_config_macos.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/include/build_config/SDL_build_config_minimal.h b/libs/SDL3/include/build_config/SDL_build_config_minimal.h index d5fe736e..e557d92c 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_minimal.h +++ b/libs/SDL3/include/build_config/SDL_build_config_minimal.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -98,4 +98,7 @@ typedef unsigned int uintptr_t; /* Enable dialog subsystem */ #define SDL_DIALOG_DUMMY 1 +/* Enable tray subsystem */ +#define SDL_TRAY_DUMMY 1 + #endif /* SDL_build_config_minimal_h_ */ diff --git a/libs/SDL3/include/build_config/SDL_build_config_windows.h b/libs/SDL3/include/build_config/SDL_build_config_windows.h index 24c578b6..07eecb4d 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_windows.h +++ b/libs/SDL3/include/build_config/SDL_build_config_windows.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -85,6 +85,7 @@ typedef unsigned int uintptr_t; #define HAVE_DXGI_H 1 #define HAVE_XINPUT_H 1 #if defined(_WIN32_MAXVER) && _WIN32_MAXVER >= 0x0A00 /* Windows 10 SDK */ +#define HAVE_DXGI1_5_H 1 #define HAVE_DXGI1_6_H 1 #define HAVE_WINDOWS_GAMING_INPUT_H 1 #endif @@ -95,9 +96,6 @@ typedef unsigned int uintptr_t; #if defined(_WIN32_MAXVER) && _WIN32_MAXVER >= 0x0603 /* Windows 8.1 SDK */ #define HAVE_SHELLSCALINGAPI_H 1 #endif -#if defined(_WIN32_MAXVER) && _WIN32_MAXVER >= 0x0601 /* Windows 7 SDK */ -#define HAVE_SHOBJIDL_CORE_H 1 -#endif #define HAVE_MMDEVICEAPI_H 1 #define HAVE_AUDIOCLIENT_H 1 #define HAVE_TPCSHRD_H 1 @@ -114,6 +112,9 @@ typedef unsigned int uintptr_t; # define SDL_DISABLE_AVX 1 #endif +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 + /* This can be disabled to avoid C runtime dependencies and manifest requirements */ #ifndef HAVE_LIBC #define HAVE_LIBC 1 @@ -125,8 +126,6 @@ typedef unsigned int uintptr_t; #define HAVE_LIMITS_H 1 #define HAVE_MATH_H 1 #define HAVE_SIGNAL_H 1 -#define HAVE_STDARG_H 1 -#define HAVE_STDDEF_H 1 #define HAVE_STDIO_H 1 #define HAVE_STDLIB_H 1 #define HAVE_STRING_H 1 @@ -156,7 +155,6 @@ typedef unsigned int uintptr_t; #define HAVE_STRCMP 1 #define HAVE_STRNCMP 1 #define HAVE_STRPBRK 1 -#define HAVE_VSSCANF 1 #define HAVE_VSNPRINTF 1 #define HAVE_ACOS 1 #define HAVE_ASIN 1 @@ -214,10 +212,7 @@ typedef unsigned int uintptr_t; #if _MSC_VER >= 1400 #define HAVE__FSEEKI64 1 #endif -#endif /* _MSC_VER */ -#else -#define HAVE_STDARG_H 1 -#define HAVE_STDDEF_H 1 +#endif /* _MSC_VER */ #endif /* Enable various audio drivers */ diff --git a/libs/SDL3/include/build_config/SDL_build_config_wingdk.h b/libs/SDL3/include/build_config/SDL_build_config_wingdk.h index f51a2fff..ee06c1c6 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_wingdk.h +++ b/libs/SDL3/include/build_config/SDL_build_config_wingdk.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -37,13 +37,13 @@ #define HAVE_DSOUND_H 1 /* No SDK version checks needed for these because the SDK has to be new. */ #define HAVE_DXGI_H 1 +#define HAVE_DXGI1_5_H 1 #define HAVE_DXGI1_6_H 1 #define HAVE_XINPUT_H 1 #define HAVE_WINDOWS_GAMING_INPUT_H 1 #define HAVE_D3D11_H 1 #define HAVE_ROAPI_H 1 #define HAVE_SHELLSCALINGAPI_H 1 -#define HAVE_SHOBJIDL_CORE_H 1 #define HAVE_MMDEVICEAPI_H 1 #define HAVE_AUDIOCLIENT_H 1 #define HAVE_TPCSHRD_H 1 @@ -146,7 +146,7 @@ #define HAVE_TRUNC 1 #define HAVE_TRUNCF 1 #define HAVE__FSEEKI64 1 -#endif /* _MSC_VER */ +#endif /* _MSC_VER */ /* Enable various audio drivers */ #if defined(HAVE_MMDEVICEAPI_H) && defined(HAVE_AUDIOCLIENT_H) diff --git a/libs/SDL3/include/build_config/SDL_build_config_xbox.h b/libs/SDL3/include/build_config/SDL_build_config_xbox.h index 500468dd..0841a1cc 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_xbox.h +++ b/libs/SDL3/include/build_config/SDL_build_config_xbox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -41,7 +41,6 @@ /*#define HAVE_WINDOWS_GAMING_INPUT_H 1*/ /*#define HAVE_ROAPI_H 1*/ /*#define HAVE_SHELLSCALINGAPI_H 1*/ -/*#define HAVE_SHOBJIDL_CORE_H 1*/ #define HAVE_MMDEVICEAPI_H 1 #define HAVE_AUDIOCLIENT_H 1 /*#define HAVE_TPCSHRD_H 1*/ @@ -145,7 +144,7 @@ #define HAVE_TRUNC 1 #define HAVE_TRUNCF 1 #define HAVE__FSEEKI64 1 -#endif /* _MSC_VER */ +#endif /* _MSC_VER */ /* Enable various audio drivers */ #if defined(HAVE_MMDEVICEAPI_H) && defined(HAVE_AUDIOCLIENT_H) @@ -221,4 +220,7 @@ /* Enable dialog subsystem */ #define SDL_DIALOG_DUMMY 1 +/* Enable tray subsystem */ +#define SDL_TRAY_DUMMY 1 + #endif /* SDL_build_config_wingdk_h_ */ diff --git a/libs/SDL3/include/build_config/SDL_revision.h.cmake b/libs/SDL3/include/build_config/SDL_revision.h.cmake index 32b95bf9..e2dbf4b5 100644 --- a/libs/SDL3/include/build_config/SDL_revision.h.cmake +++ b/libs/SDL3/include/build_config/SDL_revision.h.cmake @@ -1,28 +1,30 @@ /* - Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. + 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: + 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. + 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. */ -/** - * \file SDL_revision.h - * - * Header file containing the SDL revision. +/* WIKI CATEGORY: Version */ + +/* + * SDL_revision.h contains the SDL revision, which might be defined on the + * compiler command line, or generated right into the header itself by the + * build system. */ #ifndef SDL_revision_h_ @@ -30,10 +32,10 @@ #cmakedefine SDL_VENDOR_INFO "@SDL_VENDOR_INFO@" -#ifdef SDL_VENDOR_INFO -#define SDL_REVISION "@SDL_REVISION@ (" SDL_VENDOR_INFO ")" +#if defined(SDL_VENDOR_INFO) +#define SDL_REVISION "SDL-release-3.4.10-0-g8e37db5e7 (" SDL_VENDOR_INFO ")" #else -#define SDL_REVISION "@SDL_REVISION@" +#define SDL_REVISION "SDL-release-3.4.10-0-g8e37db5e7" #endif #endif /* SDL_revision_h_ */ diff --git a/libs/SDL3/src/SDL.c b/libs/SDL3/src/SDL.c index 34e15251..3e61b8ad 100644 --- a/libs/SDL3/src/SDL.c +++ b/libs/SDL3/src/SDL.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -30,6 +30,10 @@ // this checks for HAVE_DBUS_DBUS_H internally. #include "core/linux/SDL_dbus.h" +#if defined(SDL_PLATFORM_UNIX) && !defined(SDL_PLATFORM_ANDROID) +#include "core/unix/SDL_gtk.h" +#endif + #ifdef SDL_PLATFORM_EMSCRIPTEN #include #endif @@ -61,11 +65,11 @@ #include "core/android/SDL_android.h" #endif -#define SDL_INIT_EVERYTHING ~0U +#define SDL_ALL_SUBSYSTEM_FLAGS ~0U // Initialization/Cleanup routines #include "timer/SDL_timer_c.h" -#ifdef SDL_VIDEO_DRIVER_WINDOWS +#ifdef SDL_PLATFORM_WINDOWS extern bool SDL_HelperWindowCreate(void); extern void SDL_HelperWindowDestroy(void); #endif @@ -144,7 +148,7 @@ static bool SDL_ValidMetadataProperty(const char *name) bool SDL_SetAppMetadataProperty(const char *name, const char *value) { - if (!SDL_ValidMetadataProperty(name)) { + CHECK_PARAM(!SDL_ValidMetadataProperty(name)) { return SDL_InvalidParamError("name"); } @@ -153,7 +157,7 @@ bool SDL_SetAppMetadataProperty(const char *name, const char *value) const char *SDL_GetAppMetadataProperty(const char *name) { - if (!SDL_ValidMetadataProperty(name)) { + CHECK_PARAM(!SDL_ValidMetadataProperty(name)) { SDL_InvalidParamError("name"); return NULL; } @@ -169,7 +173,10 @@ const char *SDL_GetAppMetadataProperty(const char *name) } if (!value || !*value) { if (SDL_strcmp(name, SDL_PROP_APP_METADATA_NAME_STRING) == 0) { - value = "SDL Application"; + value = SDL_GetExeName(); + if (!value) { + value = "SDL Application"; + } } else if (SDL_strcmp(name, SDL_PROP_APP_METADATA_TYPE_STRING) == 0) { value = "application"; } @@ -185,6 +192,8 @@ static bool SDL_MainIsReady = false; static bool SDL_MainIsReady = true; #endif static SDL_ThreadID SDL_MainThreadID = 0; +static SDL_ThreadID SDL_EventsThreadID = 0; +static SDL_ThreadID SDL_VideoThreadID = 0; static bool SDL_bInMainQuit = false; static Uint8 SDL_SubsystemRefCount[32]; @@ -233,6 +242,7 @@ static bool SDL_ShouldQuitSubsystem(Uint32 subsystem) return (((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 1)) || SDL_bInMainQuit); } +#if !defined(SDL_VIDEO_DISABLED) || !defined(SDL_AUDIO_DISABLED) || !defined(SDL_JOYSTICK_DISABLED) /* Private helper to either increment's existing ref counter, * or fully init a new subsystem. */ static bool SDL_InitOrIncrementSubsystem(Uint32 subsystem) @@ -248,6 +258,7 @@ static bool SDL_InitOrIncrementSubsystem(Uint32 subsystem) } return SDL_InitSubSystem(subsystem); } +#endif // !SDL_VIDEO_DISABLED || !SDL_AUDIO_DISABLED || !SDL_JOYSTICK_DISABLED void SDL_SetMainReady(void) { @@ -260,14 +271,21 @@ void SDL_SetMainReady(void) bool SDL_IsMainThread(void) { - if (SDL_MainThreadID == 0) { - // Not initialized yet? - return true; + if (SDL_VideoThreadID) { + return (SDL_GetCurrentThreadID() == SDL_VideoThreadID); } - if (SDL_MainThreadID == SDL_GetCurrentThreadID()) { - return true; + if (SDL_EventsThreadID) { + return (SDL_GetCurrentThreadID() == SDL_EventsThreadID); } - return false; + if (SDL_MainThreadID) { + return (SDL_GetCurrentThreadID() == SDL_MainThreadID); + } + return true; +} + +bool SDL_IsVideoThread(void) +{ + return (SDL_GetCurrentThreadID() == SDL_VideoThreadID); } // Initialize all the subsystems that require initialization before threads start @@ -275,6 +293,11 @@ void SDL_InitMainThread(void) { static bool done_info = false; + // If we haven't done it by now, mark this as the main thread + if (SDL_MainThreadID == 0) { + SDL_MainThreadID = SDL_GetCurrentThreadID(); + } + SDL_InitTLSData(); SDL_InitEnvironment(); SDL_InitTicks(); @@ -311,13 +334,33 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) return SDL_SetError("Application didn't initialize properly, did you include SDL_main.h in the file containing your main() function?"); } +#ifdef SDL_PLATFORM_EMSCRIPTEN + MAIN_THREAD_EM_ASM({ + // make sure this generic table to hang SDL-specific Javascript stuff is available at init time. + if (typeof(Module['SDL3']) === 'undefined') { + Module['SDL3'] = {}; + } + + var SDL3 = Module['SDL3']; + #if defined(__wasm32__) + if (typeof(SDL3.JSVarToCPtr) === 'undefined') { SDL3.JSVarToCPtr = function(v) { return v; }; } + if (typeof(SDL3.CPtrToHeap32Index) === 'undefined') { SDL3.CPtrToHeap32Index = function(ptr) { return ptr >>> 2; }; } + #elif defined(__wasm64__) + if (typeof(SDL3.JSVarToCPtr) === 'undefined') { SDL3.JSVarToCPtr = function(v) { return BigInt(v); }; } + if (typeof(SDL3.CPtrToHeap32Index) === 'undefined') { SDL3.CPtrToHeap32Index = function(ptr) { return Number(ptr / 4n); }; } + #else + #error Please define your platform. + #endif + }); +#endif + SDL_InitMainThread(); #ifdef SDL_USE_LIBDBUS SDL_DBus_Init(); #endif -#ifdef SDL_VIDEO_DRIVER_WINDOWS +#ifdef SDL_PLATFORM_WINDOWS if (flags & (SDL_INIT_HAPTIC | SDL_INIT_JOYSTICK)) { if (!SDL_HelperWindowCreate()) { goto quit_and_error; @@ -329,6 +372,11 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) if (flags & SDL_INIT_EVENTS) { if (SDL_ShouldInitSubsystem(SDL_INIT_EVENTS)) { SDL_IncrementSubsystemRefCount(SDL_INIT_EVENTS); + + // Note which thread initialized events + // This is the thread which should be pumping events + SDL_EventsThreadID = SDL_GetCurrentThreadID(); + if (!SDL_InitEvents()) { SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS); goto quit_and_error; @@ -348,12 +396,16 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) goto quit_and_error; } + SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO); + // We initialize video on the main thread // On Apple platforms this is a requirement. // On other platforms, this is the definition. - SDL_MainThreadID = SDL_GetCurrentThreadID(); + SDL_VideoThreadID = SDL_GetCurrentThreadID(); +#ifdef SDL_PLATFORM_APPLE + SDL_assert(SDL_VideoThreadID == SDL_MainThreadID); +#endif - SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO); if (!SDL_VideoInit(NULL)) { SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO); SDL_PushError(); @@ -603,6 +655,7 @@ void SDL_QuitSubSystem(SDL_InitFlags flags) if (SDL_ShouldQuitSubsystem(SDL_INIT_VIDEO)) { SDL_QuitRender(); SDL_VideoQuit(); + SDL_VideoThreadID = 0; // video implies events SDL_QuitSubSystem(SDL_INIT_EVENTS); } @@ -613,6 +666,7 @@ void SDL_QuitSubSystem(SDL_InitFlags flags) if (flags & SDL_INIT_EVENTS) { if (SDL_ShouldQuitSubsystem(SDL_INIT_EVENTS)) { SDL_QuitEvents(); + SDL_EventsThreadID = 0; } SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS); } @@ -631,7 +685,7 @@ Uint32 SDL_WasInit(SDL_InitFlags flags) } if (!flags) { - flags = SDL_INIT_EVERYTHING; + flags = SDL_ALL_SUBSYSTEM_FLAGS; } num_subsystems = SDL_min(num_subsystems, SDL_MostSignificantBitIndex32(flags) + 1); @@ -653,16 +707,20 @@ void SDL_Quit(void) SDL_bInMainQuit = true; // Quit all subsystems -#ifdef SDL_VIDEO_DRIVER_WINDOWS +#ifdef SDL_PLATFORM_WINDOWS SDL_HelperWindowDestroy(); #endif - SDL_QuitSubSystem(SDL_INIT_EVERYTHING); + SDL_QuitSubSystem(SDL_ALL_SUBSYSTEM_FLAGS); SDL_CleanupTrays(); #ifdef SDL_USE_LIBDBUS SDL_DBus_Quit(); #endif +#if defined(SDL_PLATFORM_UNIX) && !defined(SDL_PLATFORM_ANDROID) && !defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(SDL_PLATFORM_PRIVATE) + SDL_Gtk_Quit(); +#endif + SDL_QuitTimers(); SDL_QuitAsyncIO(); @@ -676,7 +734,7 @@ void SDL_Quit(void) /* Now that every subsystem has been quit, we reset the subsystem refcount * and the list of initialized subsystems. */ - SDL_memset(SDL_SubsystemRefCount, 0x0, sizeof(SDL_SubsystemRefCount)); + SDL_zeroa(SDL_SubsystemRefCount); SDL_QuitLog(); SDL_QuitHints(); @@ -750,6 +808,8 @@ const char *SDL_GetPlatform(void) return "Xbox One"; #elif defined(SDL_PLATFORM_XBOXSERIES) return "Xbox Series X|S"; +#elif defined(SDL_PLATFORM_VISIONOS) + return "visionOS"; #elif defined(SDL_PLATFORM_IOS) return "iOS"; #elif defined(SDL_PLATFORM_TVOS) @@ -762,6 +822,8 @@ const char *SDL_GetPlatform(void) return "PlayStation Vita"; #elif defined(SDL_PLATFORM_3DS) return "Nintendo 3DS"; +#elif defined(SDL_PLATFORM_HURD) + return "GNU/Hurd"; #elif defined(__managarm__) return "Managarm"; #else @@ -833,9 +895,7 @@ SDL_Sandbox SDL_GetSandbox(void) #ifdef SDL_PLATFORM_WIN32 -#if (!defined(HAVE_LIBC) || defined(__WATCOMC__)) && !defined(SDL_STATIC_LIB) -// FIXME: Still need to include DllMain() on Watcom C ? - +#if !defined(HAVE_LIBC) && !defined(SDL_STATIC_LIB) BOOL APIENTRY MINGW32_FORCEALIGN _DllMainCRTStartup(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { diff --git a/libs/SDL3/src/SDL_assert.c b/libs/SDL3/src/SDL_assert.c index 2440d04b..f1f3da33 100644 --- a/libs/SDL3/src/SDL_assert.c +++ b/libs/SDL3/src/SDL_assert.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -120,23 +120,10 @@ static void SDL_GenerateAssertionReport(void) } } -/* This is not declared in any header, although it is shared between some - parts of SDL, because we don't want anything calling it without an - extremely good reason. */ -#ifdef __WATCOMC__ -extern void SDL_ExitProcess(int exitcode); -#pragma aux SDL_ExitProcess aborts; -#endif -extern SDL_NORETURN void SDL_ExitProcess(int exitcode); - -#ifdef __WATCOMC__ -static void SDL_AbortAssertion(void); -#pragma aux SDL_AbortAssertion aborts; -#endif static SDL_NORETURN void SDL_AbortAssertion(void) { SDL_Quit(); - SDL_ExitProcess(42); + SDL_abort(); } static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata) @@ -312,7 +299,7 @@ static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, v } #else SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Assertion Failed", message, window); -#endif // HAVE_STDIO_H +#endif // SDL_PLATFORM_PRIVATE_ASSERT } // Re-enter fullscreen mode @@ -361,7 +348,7 @@ SDL_AssertState SDL_ReportAssertion(SDL_AssertData *data, const char *func, cons if (assertion_running == 2) { SDL_AbortAssertion(); } else if (assertion_running == 3) { // Abort asserted! - SDL_ExitProcess(42); + SDL_abort(); } else { while (1) { // do nothing but spin; what else can you do?! } diff --git a/libs/SDL3/src/SDL_assert_c.h b/libs/SDL3/src/SDL_assert_c.h index d892dbef..4d7a1068 100644 --- a/libs/SDL3/src/SDL_assert_c.h +++ b/libs/SDL3/src/SDL_assert_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/SDL_error.c b/libs/SDL3/src/SDL_error.c index 3f4273b4..64c1bf06 100644 --- a/libs/SDL3/src/SDL_error.c +++ b/libs/SDL3/src/SDL_error.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -43,22 +43,28 @@ bool SDL_SetErrorV(SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) if (fmt) { int result; SDL_error *error = SDL_GetErrBuf(true); + + // use the other slot for the new error, so if this does + // SDL_SetError("%s", SDL_GetError()), we don't have a problem. + const int current = error->current ? 0 : 1; + SDL_ErrorInfo *errinfo = &error->info[current]; + error->current = current; + + errinfo->error = SDL_ErrorCodeGeneric; + va_list ap2; - - error->error = SDL_ErrorCodeGeneric; - va_copy(ap2, ap); - result = SDL_vsnprintf(error->str, error->len, fmt, ap2); + result = SDL_vsnprintf(errinfo->str, errinfo->len, fmt, ap2); va_end(ap2); - if (result >= 0 && (size_t)result >= error->len && error->realloc_func) { + if (result >= 0 && (size_t)result >= errinfo->len && error->realloc_func) { size_t len = (size_t)result + 1; - char *str = (char *)error->realloc_func(error->str, len); + char *str = (char *)error->realloc_func(errinfo->str, len); if (str) { - error->str = str; - error->len = len; + errinfo->str = str; + errinfo->len = len; va_copy(ap2, ap); - (void)SDL_vsnprintf(error->str, error->len, fmt, ap2); + (void)SDL_vsnprintf(errinfo->str, errinfo->len, fmt, ap2); va_end(ap2); } } @@ -67,7 +73,7 @@ bool SDL_SetErrorV(SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) // Note that there are many recoverable errors that may happen internally and // can be safely ignored if the public API doesn't return an error code. #if 0 - SDL_LogError(SDL_LOG_CATEGORY_ERROR, "%s", error->str); + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "%s", errinfo->str); #endif } @@ -82,9 +88,10 @@ const char *SDL_GetError(void) return ""; } - switch (error->error) { + const SDL_ErrorInfo *errinfo = &error->info[error->current]; + switch (errinfo->error) { case SDL_ErrorCodeGeneric: - return error->str; + return errinfo->str; case SDL_ErrorCodeOutOfMemory: return "Out of memory"; default: @@ -97,7 +104,8 @@ bool SDL_ClearError(void) SDL_error *error = SDL_GetErrBuf(false); if (error) { - error->error = SDL_ErrorCodeNone; + SDL_ErrorInfo *errinfo = &error->info[error->current]; + errinfo->error = SDL_ErrorCodeNone; } return true; } @@ -107,7 +115,8 @@ bool SDL_OutOfMemory(void) SDL_error *error = SDL_GetErrBuf(true); if (error) { - error->error = SDL_ErrorCodeOutOfMemory; + SDL_ErrorInfo *errinfo = &error->info[error->current]; + errinfo->error = SDL_ErrorCodeOutOfMemory; } return false; } diff --git a/libs/SDL3/src/SDL_error_c.h b/libs/SDL3/src/SDL_error_c.h index ba4550ed..7d53783d 100644 --- a/libs/SDL3/src/SDL_error_c.h +++ b/libs/SDL3/src/SDL_error_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -34,11 +34,17 @@ typedef enum SDL_ErrorCodeOutOfMemory, } SDL_ErrorCode; -typedef struct SDL_error +typedef struct SDL_ErrorInfo { SDL_ErrorCode error; char *str; size_t len; +} SDL_ErrorInfo; + +typedef struct SDL_error +{ + SDL_ErrorInfo info[2]; // there are two, so you can do SDL_SetError("%s", SDL_GetError()) without stomping the buffer. + int current; SDL_realloc_func realloc_func; SDL_free_func free_func; } SDL_error; diff --git a/libs/SDL3/src/SDL_guid.c b/libs/SDL3/src/SDL_guid.c index 6c355d8d..913206a5 100644 --- a/libs/SDL3/src/SDL_guid.c +++ b/libs/SDL3/src/SDL_guid.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -77,7 +77,7 @@ SDL_GUID SDL_StringToGUID(const char *pchGUID) // Make sure it's even len = (len) & ~0x1; - SDL_memset(&guid, 0x00, sizeof(guid)); + SDL_zero(guid); p = (Uint8 *)&guid; for (i = 0; (i < len) && ((p - (Uint8 *)&guid) < maxoutputbytes); i += 2, p++) { diff --git a/libs/SDL3/src/SDL_hashtable.c b/libs/SDL3/src/SDL_hashtable.c index 3124b41a..8bc79f7e 100644 --- a/libs/SDL3/src/SDL_hashtable.c +++ b/libs/SDL3/src/SDL_hashtable.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -292,7 +292,7 @@ static bool maybe_resize(SDL_HashTable *ht) bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value, bool replace) { - if (!table) { + CHECK_PARAM(!table) { return SDL_InvalidParamError("table"); } @@ -338,7 +338,7 @@ bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void * bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **value) { - if (!table) { + CHECK_PARAM(!table) { if (value) { *value = NULL; } @@ -364,7 +364,7 @@ bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key) { - if (!table) { + CHECK_PARAM(!table) { return SDL_InvalidParamError("table"); } @@ -384,9 +384,10 @@ bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key) bool SDL_IterateHashTable(const SDL_HashTable *table, SDL_HashTableIterateCallback callback, void *userdata) { - if (!table) { + CHECK_PARAM(!table) { return SDL_InvalidParamError("table"); - } else if (!callback) { + } + CHECK_PARAM(!callback) { return SDL_InvalidParamError("callback"); } @@ -410,7 +411,7 @@ bool SDL_IterateHashTable(const SDL_HashTable *table, SDL_HashTableIterateCallba bool SDL_HashTableEmpty(SDL_HashTable *table) { - if (!table) { + CHECK_PARAM(!table) { return SDL_InvalidParamError("table"); } diff --git a/libs/SDL3/src/SDL_hashtable.h b/libs/SDL3/src/SDL_hashtable.h index 598a6d6b..e947efc2 100644 --- a/libs/SDL3/src/SDL_hashtable.h +++ b/libs/SDL3/src/SDL_hashtable.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -219,10 +219,10 @@ typedef void (SDLCALL *SDL_HashDestroyCallback)(void *userdata, const void *key, * \returns true to keep iterating, false to stop iteration. * * \threadsafety A read lock is held during iteration, so other threads can - * still access the the hash table, but threads attempting to - * make changes will be blocked until iteration completes. If - * this is a concern, do as little in the callback as possible - * and finish iteration quickly. + * still access the hash table, but threads attempting to make + * changes will be blocked until iteration completes. If this + * is a concern, do as little in the callback as possible and + * finish iteration quickly. * * \since This datatype is available since SDL 3.4.0. * @@ -248,7 +248,7 @@ typedef bool (SDLCALL *SDL_HashTableIterateCallback)(void *userdata, const SDL_H * * You can specify an estimate of the number of items expected to be stored * in the table, which can help make the table run more efficiently. The table - * will preallocate resources to accomodate this number of items, which is + * will preallocate resources to accommodate this number of items, which is * most useful if you intend to fill the table with a lot of data right after * creating it. Otherwise, it might make more sense to specify the _minimum_ * you expect the table to hold and let it grow as necessary from there. This @@ -422,7 +422,7 @@ extern bool SDL_HashTableEmpty(SDL_HashTable *table); * \param table the hash table to iterate. * \param callback the function pointer to call for each value. * \param userdata a pointer that is passed to `callback`. - * \returns true if iteration happened, false if not (bogus parameter, etc). + * \returns true if iteration happened, false if not (bogus parameter, etc.). * * \since This function is available since SDL 3.4.0. */ diff --git a/libs/SDL3/src/SDL_hints.c b/libs/SDL3/src/SDL_hints.c index 786d654a..36998c6f 100644 --- a/libs/SDL3/src/SDL_hints.c +++ b/libs/SDL3/src/SDL_hints.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -104,7 +104,7 @@ static const char *GetHintEnvironmentVariable(const char *name) bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriority priority) { - if (!name || !*name) { + CHECK_PARAM(!name || !*name) { return SDL_InvalidParamError("name"); } @@ -165,7 +165,7 @@ bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriori bool SDL_ResetHint(const char *name) { - if (!name || !*name) { + CHECK_PARAM(!name || !*name) { return SDL_InvalidParamError("name"); } @@ -316,9 +316,10 @@ bool SDL_GetHintBoolean(const char *name, bool default_value) bool SDL_AddHintCallback(const char *name, SDL_HintCallback callback, void *userdata) { - if (!name || !*name) { + CHECK_PARAM(!name || !*name) { return SDL_InvalidParamError("name"); - } else if (!callback) { + } + CHECK_PARAM(!callback) { return SDL_InvalidParamError("callback"); } diff --git a/libs/SDL3/src/SDL_hints_c.h b/libs/SDL3/src/SDL_hints_c.h index dd54cba6..d740eb77 100644 --- a/libs/SDL3/src/SDL_hints_c.h +++ b/libs/SDL3/src/SDL_hints_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/SDL_internal.h b/libs/SDL3/src/SDL_internal.h index 8fcd96a7..47500936 100644 --- a/libs/SDL3/src/SDL_internal.h +++ b/libs/SDL3/src/SDL_internal.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -191,11 +191,6 @@ #define SDL_VIDEO_RENDER_SW 1 #endif -/* STB image conversion */ -#if !defined(SDL_HAVE_STB) && !defined(SDL_LEAN_AND_MEAN) -#define SDL_HAVE_STB 1 -#endif - /* YUV formats - handling of YUV surfaces - blitting and conversion functions */ @@ -265,15 +260,55 @@ extern "C" { #include "SDL_utils_c.h" #include "SDL_hashtable.h" + +/* SDL_ExitProcess is not declared in any public header, although + it is shared between some parts of SDL, because we don't want + anything calling it without an extremely good reason. */ +extern SDL_NORETURN void SDL_ExitProcess(int exitcode); + +// Get just the process's binary name, no path. NULL if it doesn't make sense for a platform. +// Can be something not a file, like a package ID on Android. Meant to be human-readable, not appended to a path, etc. +// Calculates and caches the string on first call. String lives until SDL_Quit(). This is not a public API right now! +extern const char *SDL_GetExeName(void); + +#ifdef HAVE_LIBC +#define SDL_abort() abort() +#else +#define SDL_abort() do { \ + SDL_TriggerBreakpoint(); \ + SDL_ExitProcess(42); \ + } while (0) +#endif + #define PUSH_SDL_ERROR() \ { char *_error = SDL_strdup(SDL_GetError()); #define POP_SDL_ERROR() \ SDL_SetError("%s", _error); SDL_free(_error); } +#if defined(SDL_DISABLE_INVALID_PARAMS) +#ifdef DEBUG +// If you define SDL_DISABLE_INVALID_PARAMS, you're promising that you'll +// never pass an invalid parameter to SDL, since it may crash or lead to +// hard to diagnose bugs. Let's assert that this is true in debug builds. +#define OBJECT_VALIDATION_REQUIRED +#define CHECK_PARAM(invalid) SDL_assert_always(!(invalid)); if (false) +#else +#define CHECK_PARAM(invalid) if (false) +#endif +#elif defined(SDL_ASSERT_INVALID_PARAMS) +#define OBJECT_VALIDATION_REQUIRED +#define CHECK_PARAM(invalid) SDL_assert_always(!(invalid)); if (invalid) +#else +#define CHECK_PARAM(invalid) if (invalid) +#endif + // Do any initialization that needs to happen before threads are started extern void SDL_InitMainThread(void); +// Return true if this thread has initialized video +extern bool SDL_IsVideoThread(void); + /* The internal implementations of these functions have up to nanosecond precision. We can expose these functions as part of the API if we want to later. */ diff --git a/libs/SDL3/src/SDL_list.c b/libs/SDL3/src/SDL_list.c index 04cc0825..a17c4078 100644 --- a/libs/SDL3/src/SDL_list.c +++ b/libs/SDL3/src/SDL_list.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/SDL_list.h b/libs/SDL3/src/SDL_list.h index 83ec844b..bdfbc351 100644 --- a/libs/SDL3/src/SDL_list.h +++ b/libs/SDL3/src/SDL_list.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/SDL_log.c b/libs/SDL3/src/SDL_log.c index da55dcf1..0d1ee3f9 100644 --- a/libs/SDL3/src/SDL_log.c +++ b/libs/SDL3/src/SDL_log.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -24,6 +24,10 @@ #include "core/windows/SDL_windows.h" #endif +#if defined(SDL_PLATFORM_NGAGE) +#include "core/ngage/SDL_ngage.h" +#endif + // Simple log messages in SDL #include "SDL_log_c.h" @@ -375,6 +379,9 @@ void SDL_ResetLogPriorities(void) SDL_LockMutex(SDL_log_lock); { + const char *env = SDL_getenv("DEBUG_INVOCATION"); + bool debug = (env && *env && *env != '0'); + CleanupLogPriorities(); SDL_log_default_priority = SDL_LOG_PRIORITY_INVALID; @@ -397,7 +404,11 @@ void SDL_ResetLogPriorities(void) switch (i) { case SDL_LOG_CATEGORY_APPLICATION: - SDL_log_priorities[i] = SDL_LOG_PRIORITY_INFO; + if (debug) { + SDL_log_priorities[i] = SDL_LOG_PRIORITY_DEBUG; + } else { + SDL_log_priorities[i] = SDL_LOG_PRIORITY_INFO; + } break; case SDL_LOG_CATEGORY_ASSERT: SDL_log_priorities[i] = SDL_LOG_PRIORITY_WARN; @@ -406,7 +417,11 @@ void SDL_ResetLogPriorities(void) SDL_log_priorities[i] = SDL_LOG_PRIORITY_VERBOSE; break; default: - SDL_log_priorities[i] = SDL_LOG_PRIORITY_ERROR; + if (debug) { + SDL_log_priorities[i] = SDL_LOG_PRIORITY_DEBUG; + } else { + SDL_log_priorities[i] = SDL_LOG_PRIORITY_ERROR; + } break; } } @@ -450,7 +465,7 @@ bool SDL_SetLogPriorityPrefix(SDL_LogPriority priority, const char *prefix) { char *prefix_copy; - if (priority <= SDL_LOG_PRIORITY_INVALID || priority >= SDL_LOG_PRIORITY_COUNT) { + CHECK_PARAM(priority <= SDL_LOG_PRIORITY_INVALID || priority >= SDL_LOG_PRIORITY_COUNT) { return SDL_InvalidParamError("priority"); } @@ -587,25 +602,6 @@ 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); @@ -616,15 +612,21 @@ void SDL_LogMessageV(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_S } // If message truncated, allocate and re-render - if (len >= sizeof(stack_buf) && SDL_size_add_check_overflow(len, 1, &len_plus_term)) { - // Allocate exactly what we need, including the zero-terminator - message = (char *)SDL_malloc(len_plus_term); - if (!message) { - return; + if (len >= sizeof(stack_buf)) { + if (SDL_size_add_check_overflow(len, 1, &len_plus_term)) { + // Allocate exactly what we need, including the zero-terminator + message = (char *)SDL_malloc(len_plus_term); + if (!message) { + return; + } + va_copy(aq, ap); + len = SDL_vsnprintf(message, len_plus_term, fmt, aq); + va_end(aq); + } else { + // Allocation would overflow, use truncated message + message = stack_buf; + len = sizeof(stack_buf); } - va_copy(aq, ap); - len = SDL_vsnprintf(message, len_plus_term, fmt, aq); - va_end(aq); } else { message = stack_buf; } @@ -788,7 +790,15 @@ static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority } #elif defined(SDL_PLATFORM_NGAGE) { - /* Nothing to do here. */ + NGAGE_DebugPrintf("%s%s", GetLogPriorityPrefix(priority), message); +#ifdef ENABLE_FILE_LOG + FILE *pFile; + pFile = fopen("E:/SDL_Log.txt", "a"); + if (pFile) { + (void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message); + (void)fclose(pFile); + } +#endif } #endif #if defined(HAVE_STDIO_H) && \ diff --git a/libs/SDL3/src/SDL_log_c.h b/libs/SDL3/src/SDL_log_c.h index 887a3253..9349c348 100644 --- a/libs/SDL3/src/SDL_log_c.h +++ b/libs/SDL3/src/SDL_log_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/SDL_properties.c b/libs/SDL3/src/SDL_properties.c index 166ea0f9..6dd38ca8 100644 --- a/libs/SDL3/src/SDL_properties.c +++ b/libs/SDL3/src/SDL_properties.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -250,10 +250,10 @@ static bool SDLCALL CopyOneProperty(void *userdata, const SDL_HashTable *table, bool SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst) { - if (!src) { + CHECK_PARAM(!src) { return SDL_InvalidParamError("src"); } - if (!dst) { + CHECK_PARAM(!dst) { return SDL_InvalidParamError("dst"); } @@ -261,11 +261,11 @@ bool SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst) SDL_Properties *dst_properties = NULL; SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)src, (const void **)&src_properties); - if (!src_properties) { + CHECK_PARAM(!src_properties) { return SDL_InvalidParamError("src"); } SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)dst, (const void **)&dst_properties); - if (!dst_properties) { + CHECK_PARAM(!dst_properties) { return SDL_InvalidParamError("dst"); } @@ -287,12 +287,12 @@ bool SDL_LockProperties(SDL_PropertiesID props) { SDL_Properties *properties = NULL; - if (!props) { + CHECK_PARAM(!props) { return SDL_InvalidParamError("props"); } SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); - if (!properties) { + CHECK_PARAM(!properties) { return SDL_InvalidParamError("props"); } @@ -321,17 +321,17 @@ static bool SDL_PrivateSetProperty(SDL_PropertiesID props, const char *name, SDL SDL_Properties *properties = NULL; bool result = true; - if (!props) { + CHECK_PARAM(!props) { SDL_FreePropertyWithCleanup(NULL, property, NULL, true); return SDL_InvalidParamError("props"); } - if (!name || !*name) { + CHECK_PARAM(!name || !*name) { SDL_FreePropertyWithCleanup(NULL, property, NULL, true); return SDL_InvalidParamError("name"); } SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); - if (!properties) { + CHECK_PARAM(!properties) { SDL_FreePropertyWithCleanup(NULL, property, NULL, true); return SDL_InvalidParamError("props"); } @@ -755,15 +755,15 @@ bool SDL_EnumerateProperties(SDL_PropertiesID props, SDL_EnumeratePropertiesCall { SDL_Properties *properties = NULL; - if (!props) { + CHECK_PARAM(!props) { return SDL_InvalidParamError("props"); } - if (!callback) { + CHECK_PARAM(!callback) { return SDL_InvalidParamError("callback"); } SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); - if (!properties) { + CHECK_PARAM(!properties) { return SDL_InvalidParamError("props"); } diff --git a/libs/SDL3/src/SDL_properties_c.h b/libs/SDL3/src/SDL_properties_c.h index 8d1d39da..f48b2de7 100644 --- a/libs/SDL3/src/SDL_properties_c.h +++ b/libs/SDL3/src/SDL_properties_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/SDL_utils.c b/libs/SDL3/src/SDL_utils.c index f2090747..146d8163 100644 --- a/libs/SDL3/src/SDL_utils.c +++ b/libs/SDL3/src/SDL_utils.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -26,6 +26,14 @@ #include "joystick/SDL_joystick_c.h" // For SDL_GetGamepadTypeFromVIDPID() +#ifdef SDL_PLATFORM_EMSCRIPTEN +#include + +EMSCRIPTEN_KEEPALIVE void Emscripten_force_free(void *ptr) +{ + free(ptr); // This should NOT be SDL_free() +} +#endif // Common utility functions that aren't in the public API @@ -103,7 +111,8 @@ void SDL_CalculateFraction(float x, int *numerator, int *denominator) bool SDL_startswith(const char *string, const char *prefix) { - if (SDL_strncmp(string, prefix, SDL_strlen(prefix)) == 0) { + if (string && prefix && + SDL_strncmp(string, prefix, SDL_strlen(prefix)) == 0) { return true; } return false; @@ -137,6 +146,30 @@ Uint32 SDL_GetNextObjectID(void) static SDL_InitState SDL_objects_init; static SDL_HashTable *SDL_objects; +bool SDL_object_validation = true; + +static void SDLCALL SDL_InvalidParamChecksChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + bool validation_enabled = true; + +#ifndef OBJECT_VALIDATION_REQUIRED + if (hint) { + switch (*hint) { + case '0': + case '1': + validation_enabled = false; + break; + case '2': + validation_enabled = true; + break; + default: + break; + } + } +#endif // !OBJECT_VALIDATION_REQUIRED + + SDL_object_validation = validation_enabled; +} static Uint32 SDLCALL SDL_HashObject(void *unused, const void *key) { @@ -159,6 +192,7 @@ void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid) if (!initialized) { return; } + SDL_AddHintCallback(SDL_HINT_INVALID_PARAM_CHECKS, SDL_InvalidParamChecksChanged, NULL); } if (valid) { @@ -168,12 +202,8 @@ void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid) } } -bool SDL_ObjectValid(void *object, SDL_ObjectType type) +bool SDL_FindObject(void *object, SDL_ObjectType type) { - if (!object) { - return false; - } - const void *object_type; if (!SDL_FindInHashTable(SDL_objects, object, &object_type)) { return false; @@ -229,7 +259,7 @@ static bool SDLCALL LogOneLeakedObject(void *userdata, const SDL_HashTable *tabl #undef SDLOBJTYPECASE default: break; } - SDL_Log("Leaked %s (%p)", type, object); + SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Leaked %s (%p)", type, object); return true; // keep iterating. } @@ -238,10 +268,10 @@ void SDL_SetObjectsInvalid(void) if (SDL_ShouldQuit(&SDL_objects_init)) { // Log any leaked objects SDL_IterateHashTable(SDL_objects, LogOneLeakedObject, NULL); - SDL_assert(SDL_HashTableEmpty(SDL_objects)); SDL_DestroyHashTable(SDL_objects); SDL_objects = NULL; SDL_SetInitialized(&SDL_objects_init, false); + SDL_RemoveHintCallback(SDL_HINT_INVALID_PARAM_CHECKS, SDL_InvalidParamChecksChanged, NULL); } } @@ -355,6 +385,39 @@ int SDL_URIToLocal(const char *src, char *dst) return -1; } +bool SDL_IsURI(const char *uri) +{ + /* A valid URI begins with a letter and is followed by any sequence of + * letters, digits, '+', '.', or '-'. + */ + if (!uri) { + return false; + } + + // The first character of the scheme must be a letter. + if (!((*uri >= 'a' && *uri <= 'z') || (*uri >= 'A' && *uri <= 'Z'))) { + return false; + } + + /* If the colon is found before encountering the end of the string or + * any invalid characters, the scheme can be considered valid. + */ + while (*uri) { + if (!((*uri >= 'a' && *uri <= 'z') || + (*uri >= 'A' && *uri <= 'Z') || + (*uri >= '0' && *uri <= '9') || + *uri == '+' || *uri == '-' || *uri == '.')) { + return false; + } + + if (*++uri == ':') { + return true; + } + } + + return false; +} + // This is a set of per-thread persistent strings that we can return from the SDL API. // This is used for short strings that might persist past the lifetime of the object // they are related to. @@ -424,6 +487,7 @@ char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_nam const char *prefix; const char *replacement; } replacements[] = { + { "(Standard system devices) ", "" }, { "8BitDo Tech Ltd", "8BitDo" }, { "ASTRO Gaming", "ASTRO" }, { "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" }, @@ -552,3 +616,9 @@ char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_nam return name; } + + +void SDL_DebugLogBackend(const char *subsystem, const char *backend) +{ + SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "SDL chose %s backend '%s'", subsystem, backend); +} diff --git a/libs/SDL3/src/SDL_utils_c.h b/libs/SDL3/src/SDL_utils_c.h index 5e0e8f51..a49eaa11 100644 --- a/libs/SDL3/src/SDL_utils_c.h +++ b/libs/SDL3/src/SDL_utils_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -48,6 +48,9 @@ extern bool SDL_endswith(const char *string, const char *suffix); */ extern int SDL_URIToLocal(const char *src, char *dst); +/// Determine if a URI is valid by validating the scheme. +extern bool SDL_IsURI(const char *uri); + typedef enum { SDL_OBJECT_TYPE_UNKNOWN, @@ -67,12 +70,37 @@ typedef enum extern Uint32 SDL_GetNextObjectID(void); extern void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid); -extern bool SDL_ObjectValid(void *object, SDL_ObjectType type); +extern bool SDL_FindObject(void *object, SDL_ObjectType type); extern int SDL_GetObjects(SDL_ObjectType type, void **objects, int count); extern void SDL_SetObjectsInvalid(void); +extern bool SDL_object_validation; + +SDL_FORCE_INLINE bool SDL_ObjectValid(void *object, SDL_ObjectType type) +{ + if (!object) { + return false; + } + + if (!SDL_object_validation) { + return true; + } + + return SDL_FindObject(object, type); +} + extern const char *SDL_GetPersistentString(const char *string); extern char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name, const char *default_name); +// Log what backend a subsystem chose, if a hint was set to do so. Useful for debugging. +extern void SDL_DebugLogBackend(const char *subsystem, const char *backend); + +#ifdef SDL_PLATFORM_EMSCRIPTEN +// even though we reference the C runtime's free() in other places, it appears +// to be inlined more aggressively in Emscripten 4, so we need a reference to +// it here, too, so inlined Javascript doesn't fail to find it. +extern void Emscripten_force_free(void *ptr); +#endif + #endif // SDL_utils_h_ diff --git a/libs/SDL3/src/atomic/SDL_atomic.c b/libs/SDL3/src/atomic/SDL_atomic.c index 7a575ac2..f14bd106 100644 --- a/libs/SDL3/src/atomic/SDL_atomic.c +++ b/libs/SDL3/src/atomic/SDL_atomic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -33,50 +33,19 @@ #include #endif -// The __atomic_load_n() intrinsic showed up in different times for different compilers. -#ifdef __clang__ -#if __has_builtin(__atomic_load_n) || defined(HAVE_GCC_ATOMICS) -/* !!! FIXME: this advertises as available in the NDK but uses an external symbol we don't have. - It might be in a later NDK or we might need an extra library? --ryan. */ -#ifndef SDL_PLATFORM_ANDROID +// The __atomic intrinsics showed up in different times for different compilers. +#if (defined(__GNUC__) && (__GNUC__ >= 5)) || (defined(__clang__) && defined(HAVE_GCC_ATOMICS)) +#define HAVE_ATOMIC_LOAD_N 1 +#define HAVE_ATOMIC_EXCHANGE_N 1 +#else +#if SDL_HAS_BUILTIN(__atomic_load_n) #define HAVE_ATOMIC_LOAD_N 1 #endif -#endif -#elif defined(__GNUC__) -#if (__GNUC__ >= 5) -#define HAVE_ATOMIC_LOAD_N 1 +#if SDL_HAS_BUILTIN(__atomic_exchange_n) +#define HAVE_ATOMIC_EXCHANGE_N 1 #endif #endif -/* *INDENT-OFF* */ // clang-format off -#if defined(__WATCOMC__) && defined(__386__) -SDL_COMPILE_TIME_ASSERT(intsize, 4==sizeof(int)); -#define HAVE_WATCOM_ATOMICS -extern __inline int _SDL_xchg_watcom(volatile int *a, int v); -#pragma aux _SDL_xchg_watcom = \ - "lock xchg [ecx], eax" \ - parm [ecx] [eax] \ - value [eax] \ - modify exact [eax]; - -extern __inline unsigned char _SDL_cmpxchg_watcom(volatile int *a, int newval, int oldval); -#pragma aux _SDL_cmpxchg_watcom = \ - "lock cmpxchg [edx], ecx" \ - "setz al" \ - parm [edx] [ecx] [eax] \ - value [al] \ - modify exact [eax]; - -extern __inline int _SDL_xadd_watcom(volatile int *a, int v); -#pragma aux _SDL_xadd_watcom = \ - "lock xadd [ecx], eax" \ - parm [ecx] [eax] \ - value [eax] \ - modify exact [eax]; - -#endif // __WATCOMC__ && __386__ -/* *INDENT-ON* */ // clang-format on - /* If any of the operations are not provided then we must emulate some of them. That means we need a nice implementation of spin locks @@ -100,7 +69,7 @@ extern __inline int _SDL_xadd_watcom(volatile int *a, int v); Contributed by Bob Pendleton, bob@pendleton.com */ -#if !defined(HAVE_MSC_ATOMICS) && !defined(HAVE_GCC_ATOMICS) && !defined(SDL_PLATFORM_MACOS) && !defined(SDL_PLATFORM_SOLARIS) && !defined(HAVE_WATCOM_ATOMICS) +#if !defined(HAVE_MSC_ATOMICS) && !defined(HAVE_GCC_ATOMICS) && !defined(SDL_PLATFORM_MACOS) && !defined(SDL_PLATFORM_SOLARIS) #define EMULATE_CAS 1 #endif @@ -127,8 +96,6 @@ bool SDL_CompareAndSwapAtomicInt(SDL_AtomicInt *a, int oldval, int newval) #ifdef HAVE_MSC_ATOMICS SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(long) == sizeof(a->value)); return _InterlockedCompareExchange((long *)&a->value, (long)newval, (long)oldval) == (long)oldval; -#elif defined(HAVE_WATCOM_ATOMICS) - return _SDL_cmpxchg_watcom((volatile int *)&a->value, newval, oldval); #elif defined(HAVE_GCC_ATOMICS) return __sync_bool_compare_and_swap(&a->value, oldval, newval); #elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics. @@ -157,9 +124,6 @@ bool SDL_CompareAndSwapAtomicU32(SDL_AtomicU32 *a, Uint32 oldval, Uint32 newval) #ifdef HAVE_MSC_ATOMICS SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(long) == sizeof(a->value)); return _InterlockedCompareExchange((long *)&a->value, (long)newval, (long)oldval) == (long)oldval; -#elif defined(HAVE_WATCOM_ATOMICS) - SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(int) == sizeof(a->value)); - return _SDL_cmpxchg_watcom((volatile int *)&a->value, (int)newval, (int)oldval); #elif defined(HAVE_GCC_ATOMICS) return __sync_bool_compare_and_swap(&a->value, oldval, newval); #elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics. @@ -187,8 +151,6 @@ bool SDL_CompareAndSwapAtomicPointer(void **a, void *oldval, void *newval) { #ifdef HAVE_MSC_ATOMICS return _InterlockedCompareExchangePointer(a, newval, oldval) == oldval; -#elif defined(HAVE_WATCOM_ATOMICS) - return _SDL_cmpxchg_watcom((int *)a, (long)newval, (long)oldval); #elif defined(HAVE_GCC_ATOMICS) return __sync_bool_compare_and_swap(a, oldval, newval); #elif defined(SDL_PLATFORM_MACOS) && defined(__LP64__) // this is deprecated in 10.12 sdk; favor gcc atomics. @@ -218,9 +180,14 @@ int SDL_SetAtomicInt(SDL_AtomicInt *a, int v) #ifdef HAVE_MSC_ATOMICS SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(long) == sizeof(a->value)); return _InterlockedExchange((long *)&a->value, v); -#elif defined(HAVE_WATCOM_ATOMICS) - return _SDL_xchg_watcom(&a->value, v); +#elif defined(HAVE_ATOMIC_EXCHANGE_N) + return __atomic_exchange_n(&a->value, v, __ATOMIC_SEQ_CST); #elif defined(HAVE_GCC_ATOMICS) + // __sync_lock_test_and_set() is designed for locking rather than a + // generic atomic exchange, so it only provides an acquire barrier + // and may not store the exact value on all architectures. We prefer + // __atomic_exchange_n() instead on all modern compilers. + __sync_synchronize(); return __sync_lock_test_and_set(&a->value, v); #elif defined(SDL_PLATFORM_SOLARIS) SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(uint_t) == sizeof(a->value)); @@ -239,9 +206,14 @@ Uint32 SDL_SetAtomicU32(SDL_AtomicU32 *a, Uint32 v) #ifdef HAVE_MSC_ATOMICS SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(long) == sizeof(a->value)); return _InterlockedExchange((long *)&a->value, v); -#elif defined(HAVE_WATCOM_ATOMICS) - return _SDL_xchg_watcom(&a->value, v); +#elif defined(HAVE_ATOMIC_EXCHANGE_N) + return __atomic_exchange_n(&a->value, v, __ATOMIC_SEQ_CST); #elif defined(HAVE_GCC_ATOMICS) + // __sync_lock_test_and_set() is designed for locking rather than a + // generic atomic exchange, so it only provides an acquire barrier + // and may not store the exact value on all architectures. We prefer + // __atomic_exchange_n() instead on all modern compilers. + __sync_synchronize(); return __sync_lock_test_and_set(&a->value, v); #elif defined(SDL_PLATFORM_SOLARIS) SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(uint_t) == sizeof(a->value)); @@ -259,9 +231,14 @@ void *SDL_SetAtomicPointer(void **a, void *v) { #ifdef HAVE_MSC_ATOMICS return _InterlockedExchangePointer(a, v); -#elif defined(HAVE_WATCOM_ATOMICS) - return (void *)_SDL_xchg_watcom((int *)a, (long)v); +#elif defined(HAVE_ATOMIC_EXCHANGE_N) + return __atomic_exchange_n(a, v, __ATOMIC_SEQ_CST); #elif defined(HAVE_GCC_ATOMICS) + // __sync_lock_test_and_set() is designed for locking rather than a + // generic atomic exchange, so it only provides an acquire barrier + // and may not store the exact value on all architectures. We prefer + // __atomic_exchange_n() instead on all modern compilers. + __sync_synchronize(); return __sync_lock_test_and_set(a, v); #elif defined(SDL_PLATFORM_SOLARIS) return atomic_swap_ptr(a, v); @@ -279,9 +256,6 @@ int SDL_AddAtomicInt(SDL_AtomicInt *a, int v) #ifdef HAVE_MSC_ATOMICS SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(long) == sizeof(a->value)); return _InterlockedExchangeAdd((long *)&a->value, v); -#elif defined(HAVE_WATCOM_ATOMICS) - SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(int) == sizeof(a->value)); - return _SDL_xadd_watcom((volatile int *)&a->value, v); #elif defined(HAVE_GCC_ATOMICS) return __sync_fetch_and_add(&a->value, v); #elif defined(SDL_PLATFORM_SOLARIS) @@ -298,15 +272,40 @@ int SDL_AddAtomicInt(SDL_AtomicInt *a, int v) #endif } +Uint32 SDL_AddAtomicU32(SDL_AtomicU32 *a, int v) +{ +#ifdef HAVE_MSC_ATOMICS + SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(long) == sizeof(a->value)); + return (Uint32)_InterlockedExchangeAdd((long *)&a->value, v); +#elif defined(HAVE_GCC_ATOMICS) + return __sync_fetch_and_add(&a->value, v); +#elif defined(SDL_PLATFORM_SOLARIS) + Uint32 pv = a->value; + membar_consumer(); + atomic_add_int((volatile uint_t *)&a->value, v); + return pv; +#else + Uint32 value; + do { + value = a->value; + } while (!SDL_CompareAndSwapAtomicU32(a, value, (value + v))); + return value; +#endif +} + int SDL_GetAtomicInt(SDL_AtomicInt *a) { #ifdef HAVE_ATOMIC_LOAD_N return __atomic_load_n(&a->value, __ATOMIC_SEQ_CST); -#elif defined(HAVE_MSC_ATOMICS) - SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(long) == sizeof(a->value)); - return _InterlockedOr((long *)&a->value, 0); -#elif defined(HAVE_WATCOM_ATOMICS) - return _SDL_xadd_watcom(&a->value, 0); +#elif defined(HAVE_MSC_ATOMICS) && (defined(_M_ARM64) || defined(_M_ARM64EC)) + SDL_COMPILE_TIME_ASSERT(atomic_get_int, sizeof(__int32) == sizeof(a->value)); + return (int)__ldar32((unsigned __int32 *)&a->value); +#elif defined(HAVE_MSC_ATOMICS) && (defined(_M_X64) || defined(_M_IX86)) + SDL_COMPILE_TIME_ASSERT(atomic_get_int, sizeof(int) == sizeof(a->value)); + SDL_CompilerBarrier(); + int value = *(volatile int *)&a->value; + SDL_CompilerBarrier(); + return value; #elif defined(HAVE_GCC_ATOMICS) return __sync_or_and_fetch(&a->value, 0); #elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics. @@ -326,12 +325,15 @@ Uint32 SDL_GetAtomicU32(SDL_AtomicU32 *a) { #ifdef HAVE_ATOMIC_LOAD_N return __atomic_load_n(&a->value, __ATOMIC_SEQ_CST); -#elif defined(HAVE_MSC_ATOMICS) - SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(long) == sizeof(a->value)); - return (Uint32)_InterlockedOr((long *)&a->value, 0); -#elif defined(HAVE_WATCOM_ATOMICS) - SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(int) == sizeof(a->value)); - return (Uint32)_SDL_xadd_watcom((volatile int *)&a->value, 0); +#elif defined(HAVE_MSC_ATOMICS) && (defined(_M_ARM64) || defined(_M_ARM64EC)) + SDL_COMPILE_TIME_ASSERT(atomic_get_u32, sizeof(__int32) == sizeof(a->value)); + return __ldar32((unsigned __int32 *)&a->value); +#elif defined(HAVE_MSC_ATOMICS) && (defined(_M_X64) || defined(_M_IX86)) + SDL_COMPILE_TIME_ASSERT(atomic_get_u32, sizeof(Uint32) == sizeof(a->value)); + SDL_CompilerBarrier(); + Uint32 value = *(volatile Uint32 *)&a->value; + SDL_CompilerBarrier(); + return value; #elif defined(HAVE_GCC_ATOMICS) return __sync_or_and_fetch(&a->value, 0); #elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics. @@ -352,8 +354,14 @@ void *SDL_GetAtomicPointer(void **a) { #ifdef HAVE_ATOMIC_LOAD_N return __atomic_load_n(a, __ATOMIC_SEQ_CST); -#elif defined(HAVE_MSC_ATOMICS) - return _InterlockedCompareExchangePointer(a, NULL, NULL); +#elif defined(HAVE_MSC_ATOMICS) && (defined(_M_ARM64) || defined(_M_ARM64EC)) + SDL_COMPILE_TIME_ASSERT(atomic_get_ptr, sizeof(__int64) == sizeof(*a)); + return (void *)__ldar64((unsigned __int64 *)a); +#elif defined(HAVE_MSC_ATOMICS) && (defined(_M_X64) || defined(_M_IX86)) + SDL_CompilerBarrier(); + void *value = *(void * volatile *)a; + SDL_CompilerBarrier(); + return value; #elif defined(HAVE_GCC_ATOMICS) return __sync_val_compare_and_swap(a, (void *)0, (void *)0); #elif defined(SDL_PLATFORM_SOLARIS) diff --git a/libs/SDL3/src/atomic/SDL_spinlock.c b/libs/SDL3/src/atomic/SDL_spinlock.c index 8e35c8a7..c0488666 100644 --- a/libs/SDL3/src/atomic/SDL_spinlock.c +++ b/libs/SDL3/src/atomic/SDL_spinlock.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -44,34 +44,20 @@ #include #endif -/* *INDENT-OFF* */ // clang-format off -#if defined(__WATCOMC__) && defined(__386__) -SDL_COMPILE_TIME_ASSERT(locksize, 4==sizeof(SDL_SpinLock)); -extern __inline int _SDL_xchg_watcom(volatile int *a, int v); -#pragma aux _SDL_xchg_watcom = \ - "lock xchg [ecx], eax" \ - parm [ecx] [eax] \ - value [eax] \ - modify exact [eax]; -#endif // __WATCOMC__ && __386__ -/* *INDENT-ON* */ // clang-format on - // This function is where all the magic happens... bool SDL_TryLockSpinlock(SDL_SpinLock *lock) { #if defined(HAVE_GCC_ATOMICS) || defined(HAVE_GCC_SYNC_LOCK_TEST_AND_SET) return __sync_lock_test_and_set(lock, 1) == 0; -#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) - return _InterlockedExchange_acq(lock, 1) == 0; +#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC)) + SDL_COMPILE_TIME_ASSERT(locksize, sizeof(*lock) == sizeof(long)); + return _InterlockedExchange_acq((long *)lock, 1) == 0; #elif defined(_MSC_VER) SDL_COMPILE_TIME_ASSERT(locksize, sizeof(*lock) == sizeof(long)); return InterlockedExchange((long *)lock, 1) == 0; -#elif defined(__WATCOMC__) && defined(__386__) - return _SDL_xchg_watcom(lock, 1) == 0; - #elif defined(__GNUC__) && defined(__arm__) && \ (defined(__ARM_ARCH_3__) || defined(__ARM_ARCH_3M__) || \ defined(__ARM_ARCH_4__) || defined(__ARM_ARCH_4T__) || \ @@ -106,7 +92,7 @@ bool SDL_TryLockSpinlock(SDL_SpinLock *lock) : "cc", "memory"); return result == 0; -#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#elif (defined(__GNUC__) || defined(__TINYC__)) && (defined(__i386__) || defined(__x86_64__)) int result; __asm__ __volatile__( "lock ; xchgl %0, (%1)\n" @@ -181,23 +167,17 @@ void SDL_UnlockSpinlock(SDL_SpinLock *lock) #if defined(HAVE_GCC_ATOMICS) || defined(HAVE_GCC_SYNC_LOCK_TEST_AND_SET) __sync_lock_release(lock); -#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) - _InterlockedExchange_rel(lock, 0); - -#elif defined(_MSC_VER) - _ReadWriteBarrier(); - *lock = 0; - -#elif defined(__WATCOMC__) && defined(__386__) - SDL_CompilerBarrier(); - *lock = 0; +#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC)) + SDL_COMPILE_TIME_ASSERT(locksize, sizeof(*lock) == sizeof(long)); + _InterlockedExchange_rel((long *)lock, 0); #elif defined(SDL_PLATFORM_SOLARIS) // Used for Solaris when not using gcc. - *lock = 0; membar_producer(); + *lock = 0; #else + SDL_MemoryBarrierRelease(); *lock = 0; #endif } diff --git a/libs/SDL3/src/audio/SDL_audio.c b/libs/SDL3/src/audio/SDL_audio.c index 90d9f9bb..c1e42414 100644 --- a/libs/SDL3/src/audio/SDL_audio.c +++ b/libs/SDL3/src/audio/SDL_audio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -136,11 +136,11 @@ int SDL_GetNumAudioDrivers(void) const char *SDL_GetAudioDriver(int index) { - if (index >= 0 && index < SDL_GetNumAudioDrivers()) { - return deduped_bootstrap[index]->name; + CHECK_PARAM(index < 0 || index >= SDL_GetNumAudioDrivers()) { + SDL_InvalidParamError("index"); + return NULL; } - SDL_InvalidParamError("index"); - return NULL; + return deduped_bootstrap[index]->name; } const char *SDL_GetCurrentAudioDriver(void) @@ -171,10 +171,13 @@ int SDL_GetDefaultSampleFramesFromFreq(const int freq) int *SDL_ChannelMapDup(const int *origchmap, int channels) { - const size_t chmaplen = sizeof (*origchmap) * channels; - int *chmap = (int *)SDL_malloc(chmaplen); - if (chmap) { - SDL_memcpy(chmap, origchmap, chmaplen); + int *chmap = NULL; + if ((channels > 0) && origchmap) { + const size_t chmaplen = sizeof (*origchmap) * channels; + chmap = (int *)SDL_malloc(chmaplen); + if (chmap) { + SDL_memcpy(chmap, origchmap, chmaplen); + } } return chmap; } @@ -186,16 +189,15 @@ void OnAudioStreamCreated(SDL_AudioStream *stream) // NOTE that you can create an audio stream without initializing the audio subsystem, // but it will not be automatically destroyed during a later call to SDL_Quit! // You must explicitly destroy it yourself! - if (current_audio.device_hash_lock) { - // this isn't really part of the "device list" but it's a convenient lock to use here. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + if (current_audio.subsystem_rwlock) { + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); if (current_audio.existing_streams) { current_audio.existing_streams->prev = stream; } stream->prev = NULL; stream->next = current_audio.existing_streams; current_audio.existing_streams = stream; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -206,9 +208,8 @@ void OnAudioStreamDestroy(SDL_AudioStream *stream) // NOTE that you can create an audio stream without initializing the audio subsystem, // but it will not be automatically destroyed during a later call to SDL_Quit! // You must explicitly destroy it yourself! - if (current_audio.device_hash_lock) { - // this isn't really part of the "device list" but it's a convenient lock to use here. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + if (current_audio.subsystem_rwlock) { + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); if (stream->prev) { stream->prev->next = stream->next; } @@ -218,7 +219,7 @@ void OnAudioStreamDestroy(SDL_AudioStream *stream) if (stream == current_audio.existing_streams) { current_audio.existing_streams = stream->next; } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -369,14 +370,28 @@ static SDL_AudioDeviceID AssignAudioDeviceInstanceId(bool recording, bool islogi bool SDL_IsAudioDevicePhysical(SDL_AudioDeviceID devid) { + // bit #1 of devid is set for physical devices and unset for logical. return (devid & (1 << 1)) != 0; } +static bool SDL_IsAudioDeviceLogical(SDL_AudioDeviceID devid) +{ + // bit #1 of devid is set for physical devices and unset for logical. + return (devid & (1 << 1)) == 0; +} + bool SDL_IsAudioDevicePlayback(SDL_AudioDeviceID devid) { + // bit #0 of devid is set for playback devices and unset for recording. return (devid & (1 << 0)) != 0; } +static bool SDL_IsAudioDeviceRecording(SDL_AudioDeviceID devid) +{ + // bit #0 of devid is set for playback devices and unset for recording. + return (devid & (1 << 0)) == 0; +} + static void ObtainPhysicalAudioDeviceObj(SDL_AudioDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXMEL SDL_ACQUIRE { if (device) { @@ -407,21 +422,19 @@ static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid, SDL_AudioDevice *device = NULL; SDL_LogicalAudioDevice *logdev = NULL; - // bit #1 of devid is set for physical devices and unset for logical. - const bool islogical = !(devid & (1<<1)); - if (islogical) { // don't bother looking if it's not a logical device id value. - SDL_LockRWLockForReading(current_audio.device_hash_lock); - SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &logdev); + if (SDL_IsAudioDeviceLogical(devid)) { // don't bother looking if it's not a logical device id value. + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + SDL_FindInHashTable(current_audio.device_hash_logical, (const void *) (uintptr_t) devid, (const void **) &logdev); if (logdev) { SDL_assert(logdev->instance_id == devid); device = logdev->physical_device; SDL_assert(device != NULL); RefPhysicalAudioDevice(device); // reference it, in case the logical device migrates to a new default. } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (logdev) { - // we have to release the device_hash_lock before we take the device lock, to avoid deadlocks, so do a loop + // we have to release the subsystem_rwlock before we take the device lock, to avoid deadlocks, so do a loop // to make sure the correct physical device gets locked, in case we're in a race with the default changing. while (true) { SDL_LockMutex(device->lock); @@ -454,17 +467,15 @@ static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) // ! { SDL_AudioDevice *device = NULL; - // bit #1 of devid is set for physical devices and unset for logical. - const bool islogical = !(devid & (1<<1)); - if (islogical) { + if (SDL_IsAudioDeviceLogical(devid)) { ObtainLogicalAudioDevice(devid, &device); } else if (!SDL_GetCurrentAudioDriver()) { // (the `islogical` path, above, checks this in ObtainLogicalAudioDevice.) SDL_SetError("Audio subsystem is not initialized"); } else { - SDL_LockRWLockForReading(current_audio.device_hash_lock); - SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &device); - SDL_assert(device->instance_id == devid); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + SDL_FindInHashTable(current_audio.device_hash_physical, (const void *) (uintptr_t) devid, (const void **) &device); + SDL_assert(!device || (device->instance_id == devid)); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (!device) { SDL_SetError("Invalid audio device instance ID"); @@ -486,13 +497,13 @@ static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceI const SDL_AudioDeviceID orig_devid = devid; while (true) { - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) { devid = current_audio.default_playback_device_id; } else if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING) { devid = current_audio.default_recording_device_id; } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (devid == 0) { SDL_SetError("No default audio device available"); @@ -506,13 +517,13 @@ static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceI // make sure the default didn't change while we were waiting for the lock... bool got_it = false; - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) && (devid == current_audio.default_playback_device_id)) { got_it = true; } else if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING) && (devid == current_audio.default_recording_device_id)) { got_it = true; } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (got_it) { return device; @@ -529,10 +540,10 @@ static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceI static void DestroyLogicalAudioDevice(SDL_LogicalAudioDevice *logdev) { // Remove ourselves from the device_hash hashtable. - if (current_audio.device_hash) { // will be NULL while shutting down. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - SDL_RemoveFromHashTable(current_audio.device_hash, (const void *) (uintptr_t) logdev->instance_id); - SDL_UnlockRWLock(current_audio.device_hash_lock); + if (current_audio.device_hash_logical) { // will be NULL while shutting down. + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); + SDL_RemoveFromHashTable(current_audio.device_hash_logical, (const void *) (uintptr_t) logdev->instance_id); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } // remove ourselves from the physical device's list of logical devices. @@ -593,11 +604,11 @@ void UnrefPhysicalAudioDevice(SDL_AudioDevice *device) { if (SDL_AtomicDecRef(&device->refcount)) { // take it out of the device list. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - if (SDL_RemoveFromHashTable(current_audio.device_hash, (const void *) (uintptr_t) device->instance_id)) { + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); + if (SDL_RemoveFromHashTable(current_audio.device_hash_physical, (const void *) (uintptr_t) device->instance_id)) { SDL_AddAtomicInt(device->recording ? ¤t_audio.recording_device_count : ¤t_audio.playback_device_count, -1); } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); DestroyPhysicalAudioDevice(device); // ...and nuke it. } } @@ -611,9 +622,9 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, bool recordi { SDL_assert(name != NULL); - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); const int shutting_down = SDL_GetAtomicInt(¤t_audio.shutting_down); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (shutting_down) { return NULL; // we're shutting down, don't add any devices that are hotplugged at the last possible moment. } @@ -655,8 +666,8 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, bool recordi device->instance_id = AssignAudioDeviceInstanceId(recording, /*islogical=*/false); - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - if (SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) device->instance_id, device, false)) { + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); + if (SDL_InsertIntoHashTable(current_audio.device_hash_physical, (const void *) (uintptr_t) device->instance_id, device, false)) { SDL_AddAtomicInt(device_count, 1); } else { SDL_DestroyCondition(device->close_cond); @@ -665,7 +676,7 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, bool recordi SDL_free(device); device = NULL; } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); RefPhysicalAudioDevice(device); // unref'd on device disconnect. return device; @@ -713,12 +724,12 @@ SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_ p->type = SDL_EVENT_AUDIO_DEVICE_ADDED; p->devid = device->instance_id; p->next = NULL; - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_assert(current_audio.pending_events_tail != NULL); SDL_assert(current_audio.pending_events_tail->next == NULL); current_audio.pending_events_tail->next = p; current_audio.pending_events_tail = p; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -726,11 +737,10 @@ SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_ } // Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the audio device's thread. -void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) +static void SDLCALL SDL_AudioDeviceDisconnected_OnMainThread(void *userdata) { - if (!device) { - return; - } + SDL_AudioDevice *device = (SDL_AudioDevice *) userdata; + SDL_assert(device != NULL); // Save off removal info in a list so we can send events for each, next // time the event queue pumps, in case something tries to close a device @@ -742,10 +752,10 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) ObtainPhysicalAudioDeviceObj(device); - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); const SDL_AudioDeviceID devid = device->instance_id; const bool is_default_device = ((devid == current_audio.default_playback_device_id) || (devid == current_audio.default_recording_device_id)); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); const bool first_disconnect = SDL_CompareAndSwapAtomicInt(&device->zombie, 0, 1); if (first_disconnect) { // if already disconnected this device, don't do it twice. @@ -790,16 +800,33 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) if (first_disconnect) { if (pending.next) { // NULL if event is disabled or disaster struck. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_assert(current_audio.pending_events_tail != NULL); SDL_assert(current_audio.pending_events_tail->next == NULL); current_audio.pending_events_tail->next = pending.next; current_audio.pending_events_tail = pending_tail; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } UnrefPhysicalAudioDevice(device); } + + // We always ref this in SDL_AudioDeviceDisconnected(), so if multiple attempts + // to disconnect are queued, the pointer stays valid until the last one comes + // through. + UnrefPhysicalAudioDevice(device); +} + +void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) +{ + // lots of risk of various audio backends deadlocking because they're calling + // this while holding a backend-specific lock, which causes problems when we + // want to obtain the device lock while its audio thread is also waiting for + // that lock to be released. So just queue the work on the main thread. + if (device) { + RefPhysicalAudioDevice(device); + SDL_RunOnMainThread(SDL_AudioDeviceDisconnected_OnMainThread, device, false); + } } @@ -881,11 +908,8 @@ static bool SDLCALL FindLowestDeviceID(void *userdata, const SDL_HashTable *tabl { FindLowestDeviceIDData *data = (FindLowestDeviceIDData *) userdata; const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - // bit #0 of devid is set for playback devices and unset for recording. - // bit #1 of devid is set for physical devices and unset for logical. - const bool devid_recording = !(devid & (1 << 0)); - const bool isphysical = !!(devid & (1 << 1)); - if (isphysical && (devid_recording == data->recording) && (devid < data->highest)) { + SDL_assert(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical. + if ((SDL_IsAudioDeviceRecording(devid) == data->recording) && (devid < data->highest)) { data->highest = devid; data->result = (SDL_AudioDevice *) value; SDL_assert(data->result->instance_id == devid); @@ -899,9 +923,9 @@ static SDL_AudioDevice *GetFirstAddedAudioDevice(const bool recording) // (Device IDs increase as new devices are added, so the first device added has the lowest SDL_AudioDeviceID value.) FindLowestDeviceIDData data = { recording, highest, NULL }; - SDL_LockRWLockForReading(current_audio.device_hash_lock); - SDL_IterateHashTable(current_audio.device_hash, FindLowestDeviceID, &data); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + SDL_IterateHashTable(current_audio.device_hash_physical, FindLowestDeviceID, &data); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); return data.result; } @@ -925,14 +949,21 @@ bool SDL_InitAudio(const char *driver_name) SDL_ChooseAudioConverters(); SDL_SetupAudioResampler(); - SDL_RWLock *device_hash_lock = SDL_CreateRWLock(); // create this early, so if it fails we don't have to tear down the whole audio subsystem. - if (!device_hash_lock) { + SDL_RWLock *subsystem_rwlock = SDL_CreateRWLock(); // create this early, so if it fails we don't have to tear down the whole audio subsystem. + if (!subsystem_rwlock) { return false; } - SDL_HashTable *device_hash = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL); - if (!device_hash) { - SDL_DestroyRWLock(device_hash_lock); + SDL_HashTable *device_hash_physical = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL); + if (!device_hash_physical) { + SDL_DestroyRWLock(subsystem_rwlock); + return false; + } + + SDL_HashTable *device_hash_logical = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL); + if (!device_hash_logical) { + SDL_DestroyHashTable(device_hash_physical); + SDL_DestroyRWLock(subsystem_rwlock); return false; } @@ -949,8 +980,9 @@ bool SDL_InitAudio(const char *driver_name) const char *driver_attempt = driver_name_copy; if (!driver_name_copy) { - SDL_DestroyRWLock(device_hash_lock); - SDL_DestroyHashTable(device_hash); + SDL_DestroyRWLock(subsystem_rwlock); + SDL_DestroyHashTable(device_hash_physical); + SDL_DestroyHashTable(device_hash_logical); return false; } @@ -972,8 +1004,9 @@ bool SDL_InitAudio(const char *driver_name) tried_to_init = true; SDL_zero(current_audio); current_audio.pending_events_tail = ¤t_audio.pending_events; - current_audio.device_hash_lock = device_hash_lock; - current_audio.device_hash = device_hash; + current_audio.subsystem_rwlock = subsystem_rwlock; + current_audio.device_hash_physical = device_hash_physical; + current_audio.device_hash_logical = device_hash_logical; if (bootstrap[i]->init(¤t_audio.impl)) { current_audio.name = bootstrap[i]->name; current_audio.desc = bootstrap[i]->desc; @@ -996,8 +1029,9 @@ bool SDL_InitAudio(const char *driver_name) tried_to_init = true; SDL_zero(current_audio); current_audio.pending_events_tail = ¤t_audio.pending_events; - current_audio.device_hash_lock = device_hash_lock; - current_audio.device_hash = device_hash; + current_audio.subsystem_rwlock = subsystem_rwlock; + current_audio.device_hash_physical = device_hash_physical; + current_audio.device_hash_logical = device_hash_logical; if (bootstrap[i]->init(¤t_audio.impl)) { current_audio.name = bootstrap[i]->name; current_audio.desc = bootstrap[i]->desc; @@ -1006,7 +1040,9 @@ bool SDL_InitAudio(const char *driver_name) } } - if (!initialized) { + if (initialized) { + SDL_DebugLogBackend("audio", current_audio.name); + } else { // specific drivers will set the error message if they fail, but otherwise we do it here. if (!tried_to_init) { if (driver_name) { @@ -1016,8 +1052,9 @@ bool SDL_InitAudio(const char *driver_name) } } - SDL_DestroyRWLock(device_hash_lock); - SDL_DestroyHashTable(device_hash); + SDL_DestroyRWLock(subsystem_rwlock); + SDL_DestroyHashTable(device_hash_physical); + SDL_DestroyHashTable(device_hash_logical); SDL_zero(current_audio); return false; // No driver was available, so fail. } @@ -1053,15 +1090,11 @@ bool SDL_InitAudio(const char *driver_name) static bool SDLCALL DestroyOnePhysicalAudioDevice(void *userdata, const SDL_HashTable *table, const void *key, const void *value) { - // bit #1 of devid is set for physical devices and unset for logical. const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - const bool isphysical = !!(devid & (1<<1)); - if (isphysical) { - SDL_AudioDevice *dev = (SDL_AudioDevice *) value; - - SDL_assert(dev->instance_id == devid); - DestroyPhysicalAudioDevice(dev); - } + SDL_assert(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical. + SDL_AudioDevice *dev = (SDL_AudioDevice *) value; + SDL_assert(dev->instance_id == devid); + DestroyPhysicalAudioDevice(dev); return true; // keep iterating. } @@ -1073,20 +1106,28 @@ void SDL_QuitAudio(void) current_audio.impl.DeinitializeStart(); - // Destroy any audio streams that still exist... - while (current_audio.existing_streams) { - SDL_DestroyAudioStream(current_audio.existing_streams); + // Destroy any audio streams that still exist...unless app asked to keep it. + SDL_AudioStream *next = NULL; + for (SDL_AudioStream *i = current_audio.existing_streams; i; i = next) { + next = i->next; + if (i->simplified || SDL_GetBooleanProperty(i->props, SDL_PROP_AUDIOSTREAM_AUTO_CLEANUP_BOOLEAN, true)) { + SDL_DestroyAudioStream(i); + } else { + i->prev = NULL; + i->next = NULL; + } } - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_SetAtomicInt(¤t_audio.shutting_down, 1); - SDL_HashTable *device_hash = current_audio.device_hash; - current_audio.device_hash = NULL; + SDL_HashTable *device_hash_physical = current_audio.device_hash_physical; + SDL_HashTable *device_hash_logical = current_audio.device_hash_logical; + current_audio.device_hash_physical = current_audio.device_hash_logical = NULL; SDL_PendingAudioDeviceEvent *pending_events = current_audio.pending_events.next; current_audio.pending_events.next = NULL; SDL_SetAtomicInt(¤t_audio.playback_device_count, 0); SDL_SetAtomicInt(¤t_audio.recording_device_count, 0); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); SDL_PendingAudioDeviceEvent *pending_next = NULL; for (SDL_PendingAudioDeviceEvent *i = pending_events; i; i = pending_next) { @@ -1094,13 +1135,15 @@ void SDL_QuitAudio(void) SDL_free(i); } - SDL_IterateHashTable(device_hash, DestroyOnePhysicalAudioDevice, NULL); + SDL_IterateHashTable(device_hash_physical, DestroyOnePhysicalAudioDevice, NULL); + // device_hash_* will _not_ be empty because we nulled them out in current_audio, but all their items are now free'd pointers. Just destroy the hashes, below. // Free the driver data current_audio.impl.Deinitialize(); - SDL_DestroyRWLock(current_audio.device_hash_lock); - SDL_DestroyHashTable(device_hash); + SDL_DestroyRWLock(current_audio.subsystem_rwlock); + SDL_DestroyHashTable(device_hash_physical); + SDL_DestroyHashTable(device_hash_logical); SDL_zero(current_audio); } @@ -1155,21 +1198,9 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) // We should have updated this elsewhere if the format changed! SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &device->spec, NULL, NULL)); + SDL_assert(stream->src_spec.format != SDL_AUDIO_UNKNOWN); - 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); - } - } - + const int br = SDL_GetAtomicInt(&logdev->paused) ? 0 : SDL_GetAudioStreamDataAdjustGain(stream, device_buffer, buffer_size, logdev->gain); if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow. failed = true; SDL_memset(device_buffer, device->silence_value, buffer_size); // just supply silence to the device before we die. @@ -1207,14 +1238,12 @@ 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)); + SDL_assert(stream->src_spec.format != SDL_AUDIO_UNKNOWN); + /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams for iterating here because the binding linked list can only change while the device lock is held. (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind @@ -1233,10 +1262,6 @@ 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); @@ -1273,7 +1298,11 @@ void SDL_PlaybackAudioThreadShutdown(SDL_AudioDevice *device) const int frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec); // Wait for the audio to drain if device didn't die. if (!SDL_GetAtomicInt(&device->zombie)) { - SDL_Delay(((frames * 1000) / device->spec.freq) * 2); + int delay = ((frames * 1000) / device->spec.freq) * 2; + if (delay > 100) { + delay = 100; + } + SDL_Delay(delay); } current_audio.impl.ThreadDeinit(device); SDL_AudioThreadFinalize(device); @@ -1354,6 +1383,7 @@ bool SDL_RecordingAudioThreadIterate(SDL_AudioDevice *device) SDL_assert(stream->src_spec.format == ((logdev->postmix || (logdev->gain != 1.0f)) ? SDL_AUDIO_F32 : device->spec.format)); SDL_assert(stream->src_spec.channels == device->spec.channels); SDL_assert(stream->src_spec.freq == device->spec.freq); + SDL_assert(stream->dst_spec.format != SDL_AUDIO_UNKNOWN); void *final_buf = output_buffer; @@ -1425,13 +1455,10 @@ static bool SDLCALL CountAudioDevices(void *userdata, const SDL_HashTable *table { CountAudioDevicesData *data = (CountAudioDevicesData *) userdata; const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - // bit #0 of devid is set for playback devices and unset for recording. - // bit #1 of devid is set for physical devices and unset for logical. - const bool devid_recording = !(devid & (1<<0)); - const bool isphysical = !!(devid & (1<<1)); - if (isphysical && (devid_recording == data->recording)) { + SDL_assert(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical. + if (SDL_IsAudioDeviceRecording(devid) == data->recording) { SDL_assert(data->devs_seen < data->num_devices); - SDL_AudioDevice *device = (SDL_AudioDevice *) value; // this is normally risky, but we hold the device_hash_lock here. + SDL_AudioDevice *device = (SDL_AudioDevice *) value; // this is normally risky, but we hold the subsystem_rwlock here. const bool zombie = SDL_GetAtomicInt(&device->zombie) != 0; if (zombie) { data->devs_skipped++; @@ -1448,19 +1475,19 @@ static SDL_AudioDeviceID *GetAudioDevices(int *count, bool recording) int num_devices = 0; if (SDL_GetCurrentAudioDriver()) { - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); { num_devices = SDL_GetAtomicInt(recording ? ¤t_audio.recording_device_count : ¤t_audio.playback_device_count); result = (SDL_AudioDeviceID *) SDL_malloc((num_devices + 1) * sizeof (SDL_AudioDeviceID)); if (result) { CountAudioDevicesData data = { 0, 0, num_devices, result, recording }; - SDL_IterateHashTable(current_audio.device_hash, CountAudioDevices, &data); + SDL_IterateHashTable(current_audio.device_hash_physical, CountAudioDevices, &data); SDL_assert((data.devs_seen + data.devs_skipped) == num_devices); num_devices = data.devs_seen; // might be less if we skipped any. result[num_devices] = 0; // null-terminated. } } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } else { SDL_SetError("Audio subsystem is not initialized"); } @@ -1496,15 +1523,12 @@ static bool SDLCALL FindAudioDeviceByCallback(void *userdata, const SDL_HashTabl { FindAudioDeviceByCallbackData *data = (FindAudioDeviceByCallbackData *) userdata; const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - // bit #1 of devid is set for physical devices and unset for logical. - const bool isphysical = !!(devid & (1<<1)); - if (isphysical) { - SDL_AudioDevice *device = (SDL_AudioDevice *) value; - if (data->callback(device, data->userdata)) { // found it? - data->retval = device; - SDL_assert(data->retval->instance_id == devid); - return false; // stop iterating, we found it. - } + SDL_assert(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical. + SDL_AudioDevice *device = (SDL_AudioDevice *) value; + if (data->callback(device, data->userdata)) { // found it? + data->retval = device; + SDL_assert(data->retval->instance_id == devid); + return false; // stop iterating, we found it. } return true; // keep iterating. } @@ -1518,9 +1542,9 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(bool (*callback)(SDL_Audi } FindAudioDeviceByCallbackData data = { callback, userdata, NULL }; - SDL_LockRWLockForReading(current_audio.device_hash_lock); - SDL_IterateHashTable(current_audio.device_hash, FindAudioDeviceByCallback, &data); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + SDL_IterateHashTable(current_audio.device_hash_physical, FindAudioDeviceByCallback, &data); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (!data.retval) { SDL_SetError("Device not found"); @@ -1542,19 +1566,28 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) { // bit #1 of devid is set for physical devices and unset for logical. - const bool islogical = !(devid & (1<<1)); const char *result = NULL; - const void *vdev = NULL; if (!SDL_GetCurrentAudioDriver()) { SDL_SetError("Audio subsystem is not initialized"); } else { + const bool islogical = SDL_IsAudioDeviceLogical(devid); + const void *vdev = NULL; + // This does not call ObtainPhysicalAudioDevice() because the device's name never changes, so // it doesn't have to lock the whole device. However, just to make sure the device pointer itself // remains valid (in case the device is unplugged at the wrong moment), we hold the - // device_hash_lock while we copy the string. - SDL_LockRWLockForReading(current_audio.device_hash_lock); - SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, &vdev); + // subsystem_rwlock while we copy the string. + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + + // Allow default device IDs to be used, just return the current default physical device's name. + if (devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) { + devid = current_audio.default_playback_device_id; + } else if (devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING) { + devid = current_audio.default_recording_device_id; + } + + SDL_FindInHashTable(islogical ? current_audio.device_hash_logical : current_audio.device_hash_physical, (const void *) (uintptr_t) devid, &vdev); if (!vdev) { SDL_SetError("Invalid audio device instance ID"); } else if (islogical) { @@ -1566,7 +1599,7 @@ const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) SDL_assert(device->instance_id == devid); result = SDL_GetPersistentString(device->name); } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } return result; @@ -1574,7 +1607,7 @@ const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) bool SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec, int *sample_frames) { - if (!spec) { + CHECK_PARAM(!spec) { return SDL_InvalidParamError("spec"); } @@ -1839,8 +1872,7 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp // this will let you use a logical device to make a new logical device on the parent physical device. Could be useful? SDL_AudioDevice *device = NULL; - const bool islogical = (!wants_default && !(devid & (1<<1))); - if (!islogical) { + if ((wants_default || SDL_IsAudioDevicePhysical(devid))) { device = ObtainPhysicalAudioDeviceDefaultAllowed(devid); } else { SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); @@ -1877,9 +1909,9 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp ReleaseAudioDevice(device); if (result) { - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - const bool inserted = SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) result, logdev, false); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); + const bool inserted = SDL_InsertIntoHashTable(current_audio.device_hash_logical, (const void *) (uintptr_t) result, logdev, false); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (!inserted) { SDL_CloseAudioDevice(result); result = 0; @@ -1934,7 +1966,7 @@ float SDL_GetAudioDeviceGain(SDL_AudioDeviceID devid) bool SDL_SetAudioDeviceGain(SDL_AudioDeviceID devid, float gain) { - if (gain < 0.0f) { + CHECK_PARAM(gain < 0.0f) { return SDL_InvalidParamError("gain"); } @@ -1975,35 +2007,23 @@ 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)); SDL_AudioDevice *device = NULL; SDL_LogicalAudioDevice *logdev = NULL; bool result = true; if (num_streams == 0) { return true; // nothing to do - } else if (num_streams < 0) { + } + + CHECK_PARAM(num_streams < 0) { return SDL_InvalidParamError("num_streams"); - } else if (!streams) { + } + CHECK_PARAM(!streams) { return SDL_InvalidParamError("streams"); - } else if (!islogical) { + } + CHECK_PARAM(SDL_IsAudioDevicePhysical(devid)) { return SDL_SetError("Audio streams are bound to device ids from SDL_OpenAudioDevice, not raw physical devices"); } @@ -2013,10 +2033,6 @@ bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *stre } else if (logdev->simplified) { result = SDL_SetError("Cannot change stream bindings on device opened with SDL_OpenAudioDeviceStream"); } else { - - // !!! FIXME: We'll set the device's side's format below, but maybe we should refuse to bind a stream if the app's side doesn't have a format set yet. - // !!! FIXME: Actually, why do we allow there to be an invalid format, again? - // make sure start of list is sane. SDL_assert(!logdev->bound_streams || (logdev->bound_streams->prev_binding == NULL)); @@ -2051,9 +2067,17 @@ bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *stre if (result) { // Now that everything is verified, chain everything together. + const bool recording = device->recording; for (int i = 0; i < num_streams; i++) { SDL_AudioStream *stream = streams[i]; if (stream) { // shouldn't be NULL, but just in case... + // if the stream never had its non-device-end format set, just set it to the device end's format. + if (recording && (stream->dst_spec.format == SDL_AUDIO_UNKNOWN)) { + SDL_copyp(&stream->dst_spec, &device->spec); + } else if (!recording && (stream->src_spec.format == SDL_AUDIO_UNKNOWN)) { + SDL_copyp(&stream->src_spec, &device->spec); + } + stream->bound_device = logdev; stream->prev_binding = NULL; stream->next_binding = logdev->bound_streams; @@ -2159,7 +2183,7 @@ SDL_AudioDeviceID SDL_GetAudioStreamDevice(SDL_AudioStream *stream) { SDL_AudioDeviceID result = 0; - if (!stream) { + CHECK_PARAM(!stream) { SDL_InvalidParamError("stream"); return 0; } @@ -2340,7 +2364,7 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) const bool recording = new_default_device->recording; // change the official default over right away, so new opens will go to the new device. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); const SDL_AudioDeviceID current_devid = recording ? current_audio.default_recording_device_id : current_audio.default_playback_device_id; const bool is_already_default = (new_default_device->instance_id == current_devid); if (!is_already_default) { @@ -2350,7 +2374,7 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) current_audio.default_playback_device_id = new_default_device->instance_id; } } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (is_already_default) { return; // this is already the default. @@ -2416,8 +2440,8 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) continue; // not opened as a default, leave it on the current physical device. } - // now migrate the logical device. Hold device_hash_lock so ObtainLogicalAudioDevice doesn't get a device in the middle of transition. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + // now migrate the logical device. Hold subsystem_rwlock so ObtainLogicalAudioDevice doesn't get a device in the middle of transition. + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); if (logdev->next) { logdev->next->prev = logdev->prev; } @@ -2432,7 +2456,7 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) logdev->prev = NULL; logdev->next = new_default_device->logical_devices; new_default_device->logical_devices = logdev; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); SDL_assert(SDL_GetAtomicInt(¤t_default_device->refcount) > 1); // we should hold at least one extra reference to this device, beyond logical devices, during this phase... RefPhysicalAudioDevice(new_default_device); @@ -2474,12 +2498,12 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) } if (pending.next) { - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_assert(current_audio.pending_events_tail != NULL); SDL_assert(current_audio.pending_events_tail->next == NULL); current_audio.pending_events_tail->next = pending.next; current_audio.pending_events_tail = pending_tail; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -2556,12 +2580,12 @@ bool SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SD } if (pending.next) { - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_assert(current_audio.pending_events_tail != NULL); SDL_assert(current_audio.pending_events_tail->next == NULL); current_audio.pending_events_tail->next = pending.next; current_audio.pending_events_tail = pending_tail; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -2583,20 +2607,20 @@ bool SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec * // ("UpdateSubsystem" is the same naming that the other things that hook into PumpEvents use.) void SDL_UpdateAudio(void) { - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); SDL_PendingAudioDeviceEvent *pending_events = current_audio.pending_events.next; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (!pending_events) { return; // nothing to do, check next time. } // okay, let's take this whole list of events so we can dump the lock, and new ones can queue up for a later update. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); pending_events = current_audio.pending_events.next; // in case this changed... current_audio.pending_events.next = NULL; current_audio.pending_events_tail = ¤t_audio.pending_events; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); SDL_PendingAudioDeviceEvent *pending_next = NULL; for (SDL_PendingAudioDeviceEvent *i = pending_events; i; i = pending_next) { @@ -2606,7 +2630,7 @@ void SDL_UpdateAudio(void) SDL_zero(event); event.type = i->type; event.adevice.which = (Uint32) i->devid; - event.adevice.recording = ((i->devid & (1<<0)) == 0); // bit #0 of devid is set for playback devices and unset for recording. + event.adevice.recording = SDL_IsAudioDeviceRecording(i->devid); // bit #0 of devid is set for playback devices and unset for recording. SDL_PushEvent(&event); } SDL_free(i); diff --git a/libs/SDL3/src/audio/SDL_audio_c.h b/libs/SDL3/src/audio/SDL_audio_c.h index 0e673f1d..1068b94c 100644 --- a/libs/SDL3/src/audio/SDL_audio_c.h +++ b/libs/SDL3/src/audio/SDL_audio_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/SDL_audio_channel_converters.h b/libs/SDL3/src/audio/SDL_audio_channel_converters.h index ab682e6c..6d9cc64d 100644 --- a/libs/SDL3/src/audio/SDL_audio_channel_converters.h +++ b/libs/SDL3/src/audio/SDL_audio_channel_converters.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/SDL_audiocvt.c b/libs/SDL3/src/audio/SDL_audiocvt.c index 1d688400..1fc517ea 100644 --- a/libs/SDL3/src/audio/SDL_audiocvt.c +++ b/libs/SDL3/src/audio/SDL_audiocvt.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -474,10 +474,11 @@ SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_ SDL_PropertiesID SDL_GetAudioStreamProperties(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { SDL_InvalidParamError("stream"); return 0; } + SDL_LockMutex(stream->lock); if (stream->props == 0) { stream->props = SDL_CreateProperties(); @@ -488,9 +489,10 @@ SDL_PropertiesID SDL_GetAudioStreamProperties(SDL_AudioStream *stream) bool SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } + SDL_LockMutex(stream->lock); stream->get_callback = callback; stream->get_callback_userdata = userdata; @@ -500,9 +502,10 @@ bool SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamCallb bool SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } + SDL_LockMutex(stream->lock); stream->put_callback = callback; stream->put_callback_userdata = userdata; @@ -512,25 +515,33 @@ bool SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamCallb bool SDL_LockAudioStream(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } + SDL_LockMutex(stream->lock); return true; } bool SDL_UnlockAudioStream(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } + SDL_UnlockMutex(stream->lock); return true; } bool SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioSpec *src_spec, SDL_AudioSpec *dst_spec) { - if (!stream) { + CHECK_PARAM(!stream) { + if (src_spec) { + SDL_zerop(src_spec); + } + if (dst_spec) { + SDL_zerop(dst_spec); + } return SDL_InvalidParamError("stream"); } @@ -554,7 +565,7 @@ bool SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioSpec *src_spec, bool SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } @@ -563,21 +574,25 @@ bool SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_ // like 196608000Hz. File a bug. :P if (src_spec) { - if (!SDL_IsSupportedAudioFormat(src_spec->format)) { + CHECK_PARAM(!SDL_IsSupportedAudioFormat(src_spec->format)) { return SDL_InvalidParamError("src_spec->format"); - } else if (!SDL_IsSupportedChannelCount(src_spec->channels)) { + } + CHECK_PARAM(!SDL_IsSupportedChannelCount(src_spec->channels)) { return SDL_InvalidParamError("src_spec->channels"); - } else if (src_spec->freq <= 0) { + } + CHECK_PARAM(src_spec->freq <= 0) { return SDL_InvalidParamError("src_spec->freq"); } } if (dst_spec) { - if (!SDL_IsSupportedAudioFormat(dst_spec->format)) { + CHECK_PARAM(!SDL_IsSupportedAudioFormat(dst_spec->format)) { return SDL_InvalidParamError("dst_spec->format"); - } else if (!SDL_IsSupportedChannelCount(dst_spec->channels)) { + } + CHECK_PARAM(!SDL_IsSupportedChannelCount(dst_spec->channels)) { return SDL_InvalidParamError("dst_spec->channels"); - } else if (dst_spec->freq <= 0) { + } + CHECK_PARAM(dst_spec->freq <= 0) { return SDL_InvalidParamError("dst_spec->freq"); } } @@ -616,7 +631,7 @@ bool SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_ bool SetAudioStreamChannelMap(SDL_AudioStream *stream, const SDL_AudioSpec *spec, int **stream_chmap, const int *chmap, int channels, int isinput) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } @@ -702,7 +717,7 @@ int *SDL_GetAudioStreamOutputChannelMap(SDL_AudioStream *stream, int *count) float SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { SDL_InvalidParamError("stream"); return 0.0f; } @@ -716,7 +731,7 @@ float SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream *stream) bool SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float freq_ratio) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } @@ -739,7 +754,7 @@ bool SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float freq_ratio) float SDL_GetAudioStreamGain(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { SDL_InvalidParamError("stream"); return -1.0f; } @@ -753,9 +768,10 @@ float SDL_GetAudioStreamGain(SDL_AudioStream *stream) bool SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); - } else if (gain < 0.0f) { + } + CHECK_PARAM(gain < 0.0f) { return SDL_InvalidParamError("gain"); } @@ -841,13 +857,17 @@ static void SDLCALL FreeAllocatedAudioBuffer(void *userdata, const void *buf, in bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); - } else if (!buf) { + } + CHECK_PARAM(!buf) { return SDL_InvalidParamError("buf"); - } else if (len < 0) { + } + CHECK_PARAM(len < 0) { return SDL_InvalidParamError("len"); - } else if (len == 0) { + } + + if (len == 0) { return true; // nothing to do. } @@ -959,13 +979,17 @@ static void InterleaveAudioChannels(void *output, const void * const *channel_bu bool SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *channel_buffers, int num_channels, int num_samples) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); - } else if (!channel_buffers) { + } + CHECK_PARAM(!channel_buffers) { return SDL_InvalidParamError("channel_buffers"); - } else if (num_samples < 0) { + } + CHECK_PARAM(num_samples < 0) { return SDL_InvalidParamError("num_samples"); - } else if (num_samples == 0) { + } + + if (num_samples == 0) { return true; // nothing to do. } @@ -1033,13 +1057,17 @@ static void SDLCALL DontFreeThisAudioBuffer(void *userdata, const void *buf, int bool SDL_PutAudioStreamDataNoCopy(SDL_AudioStream *stream, const void *buf, int len, SDL_AudioStreamDataCompleteCallback callback, void *userdata) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); - } else if (!buf) { + } + CHECK_PARAM(!buf) { return SDL_InvalidParamError("buf"); - } else if (len < 0) { + } + CHECK_PARAM(len < 0) { return SDL_InvalidParamError("len"); - } else if (len == 0) { + } + + if (len == 0) { if (callback) { callback(userdata, buf, len); } @@ -1051,7 +1079,7 @@ bool SDL_PutAudioStreamDataNoCopy(SDL_AudioStream *stream, const void *buf, int bool SDL_FlushAudioStream(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } @@ -1314,16 +1342,20 @@ int SDL_GetAudioStreamDataAdjustGain(SDL_AudioStream *stream, void *voidbuf, int SDL_Log("AUDIOSTREAM: want to get %d converted bytes", len); #endif - if (!stream) { + CHECK_PARAM(!stream) { SDL_InvalidParamError("stream"); return -1; - } else if (!buf) { + } + CHECK_PARAM(!buf) { SDL_InvalidParamError("buf"); return -1; - } else if (len < 0) { + } + CHECK_PARAM(len < 0) { SDL_InvalidParamError("len"); return -1; - } else if (len == 0) { + } + + if (len == 0) { return 0; // nothing to do. } @@ -1421,7 +1453,7 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) // number of converted/resampled bytes available for output int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { SDL_InvalidParamError("stream"); return -1; } @@ -1447,7 +1479,7 @@ int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream) // number of sample frames that are currently queued as input. int SDL_GetAudioStreamQueued(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { SDL_InvalidParamError("stream"); return -1; } @@ -1464,7 +1496,7 @@ int SDL_GetAudioStreamQueued(SDL_AudioStream *stream) bool SDL_ClearAudioStream(SDL_AudioStream *stream) { - if (!stream) { + CHECK_PARAM(!stream) { return SDL_InvalidParamError("stream"); } @@ -1516,13 +1548,16 @@ bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_dat *dst_len = 0; } - if (!src_data) { + CHECK_PARAM(!src_data) { return SDL_InvalidParamError("src_data"); - } else if (src_len < 0) { + } + CHECK_PARAM(src_len < 0) { return SDL_InvalidParamError("src_len"); - } else if (!dst_data) { + } + CHECK_PARAM(!dst_data) { return SDL_InvalidParamError("dst_data"); - } else if (!dst_len) { + } + CHECK_PARAM(!dst_len) { return SDL_InvalidParamError("dst_len"); } diff --git a/libs/SDL3/src/audio/SDL_audiodev.c b/libs/SDL3/src/audio/SDL_audiodev.c index 623a3800..57bb6827 100644 --- a/libs/SDL3/src/audio/SDL_audiodev.c +++ b/libs/SDL3/src/audio/SDL_audiodev.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/SDL_audiodev_c.h b/libs/SDL3/src/audio/SDL_audiodev_c.h index ded13fc8..5c38847b 100644 --- a/libs/SDL3/src/audio/SDL_audiodev_c.h +++ b/libs/SDL3/src/audio/SDL_audiodev_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/SDL_audioqueue.c b/libs/SDL3/src/audio/SDL_audioqueue.c index df7cac88..b59e847b 100644 --- a/libs/SDL3/src/audio/SDL_audioqueue.c +++ b/libs/SDL3/src/audio/SDL_audioqueue.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/SDL_audioqueue.h b/libs/SDL3/src/audio/SDL_audioqueue.h index dc3c71c2..26542966 100644 --- a/libs/SDL3/src/audio/SDL_audioqueue.h +++ b/libs/SDL3/src/audio/SDL_audioqueue.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/SDL_audioresample.c b/libs/SDL3/src/audio/SDL_audioresample.c index 371002e3..8ec29b0e 100644 --- a/libs/SDL3/src/audio/SDL_audioresample.c +++ b/libs/SDL3/src/audio/SDL_audioresample.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/SDL_audioresample.h b/libs/SDL3/src/audio/SDL_audioresample.h index 66200738..0a3f23f5 100644 --- a/libs/SDL3/src/audio/SDL_audioresample.h +++ b/libs/SDL3/src/audio/SDL_audioresample.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/SDL_audiotypecvt.c b/libs/SDL3/src/audio/SDL_audiotypecvt.c index 78c325e5..faa223bd 100644 --- a/libs/SDL3/src/audio/SDL_audiotypecvt.c +++ b/libs/SDL3/src/audio/SDL_audiotypecvt.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -537,8 +537,10 @@ static void SDL_TARGETING("ssse3") SDL_Convert_Swap32_SSSE3(Uint32 *dst, const U // behavior. However, the compiler support for this pragma is bad. #if defined(__clang__) #if __clang_major__ >= 12 +#if defined(__aarch64__) #pragma STDC FENV_ACCESS ON #endif +#endif #elif defined(_MSC_VER) #pragma fenv_access (on) #elif defined(__GNUC__) @@ -814,8 +816,10 @@ static void SDL_Convert_Swap32_NEON(Uint32 *dst, const Uint32 *src, int num_samp #if defined(__clang__) #if __clang_major__ >= 12 +#if defined(__aarch64__) #pragma STDC FENV_ACCESS DEFAULT #endif +#endif #elif defined(_MSC_VER) #pragma fenv_access (off) #elif defined(__GNUC__) diff --git a/libs/SDL3/src/audio/SDL_mixer.c b/libs/SDL3/src/audio/SDL_mixer.c index 047c6fdf..bc8fb77f 100644 --- a/libs/SDL3/src/audio/SDL_mixer.c +++ b/libs/SDL3/src/audio/SDL_mixer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/SDL_sysaudio.h b/libs/SDL3/src/audio/SDL_sysaudio.h index 603eaab1..9104be82 100644 --- a/libs/SDL3/src/audio/SDL_sysaudio.h +++ b/libs/SDL3/src/audio/SDL_sysaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -183,8 +183,9 @@ typedef struct SDL_AudioDriver const char *name; // The name of this audio driver const char *desc; // The description of this audio driver SDL_AudioDriverImpl impl; // the backend's interface - SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash` - SDL_HashTable *device_hash; // the collection of currently-available audio devices (recording, playback, logical and physical!) + SDL_RWLock *subsystem_rwlock; // A rwlock that protects several things in the audio subsystem (device hashtables, etc). + SDL_HashTable *device_hash_physical; // the collection of currently-available audio devices (recording and playback), for mapping SDL_AudioDeviceID to an SDL_AudioDevice*. + SDL_HashTable *device_hash_logical; // the collection of currently-available audio devices (recording and playback), for mapping SDL_AudioDeviceID to an SDL_LogicalAudioDevice*. SDL_AudioStream *existing_streams; // a list of all existing SDL_AudioStreams. SDL_AudioDeviceID default_playback_device_id; SDL_AudioDeviceID default_recording_device_id; @@ -264,13 +265,6 @@ 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; diff --git a/libs/SDL3/src/audio/SDL_wave.c b/libs/SDL3/src/audio/SDL_wave.c index 1d53e79d..c819bc2e 100644 --- a/libs/SDL3/src/audio/SDL_wave.c +++ b/libs/SDL3/src/audio/SDL_wave.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -813,7 +813,7 @@ static bool IMA_ADPCM_Init(WaveFile *file, size_t datalength) if (format->formattag == EXTENSIBLE_CODE) { /* There's no specification for this, but it's basically the same - * format because the extensible header has wSampePerBlocks too. + * format because the extensible header has wSamplePerBlocks too. */ } else { // The Standards Update says there 'should' be 2 bytes for wSamplesPerBlock. @@ -1775,6 +1775,7 @@ static bool WaveLoad(SDL_IOStream *src, WaveFile *file, SDL_AudioSpec *spec, Uin int result; Uint32 chunkcount = 0; Uint32 chunkcountlimit = 10000; + const Sint64 flen = SDL_GetIOSize(src); // this might be -1 if the IOStream can't determine the total size. const char *hint; Sint64 RIFFstart, RIFFend, lastchunkpos; bool RIFFlengthknown = false; @@ -1852,7 +1853,7 @@ static bool WaveLoad(SDL_IOStream *src, WaveFile *file, SDL_AudioSpec *spec, Uin /* Step through all chunks and save information on the fmt, data, and fact * chunks. Ignore the chunks we don't know as per specification. This - * currently also ignores cue, list, and slnt chunks. + * currently also ignores cue, list, and inst chunks. */ while ((Uint64)RIFFend > (Uint64)chunk->position + chunk->length + (chunk->length & 1)) { // Abort after too many chunks or else corrupt files may waste time. @@ -1883,6 +1884,14 @@ static bool WaveLoad(SDL_IOStream *src, WaveFile *file, SDL_AudioSpec *spec, Uin fmtchunk = *chunk; } } else if (chunk->fourcc == DATA) { + /* If the data chunk is bigger than the file, it might be corrupt + or the file is truncated. Try to recover by clamping the file + size. This also means a malicious file can't allocate 4 gigabytes + for the chunks without actually supplying a 4 gigabyte file. */ + if ((flen > 0) && ((chunk->position + chunk->length) > flen)) { + chunk->length = (Uint32) (flen - chunk->position); + } + /* Only use the first data chunk. Handling the wavl list madness * may require a different approach. */ @@ -2092,16 +2101,19 @@ bool SDL_LoadWAV_IO(SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 } // Make sure we are passed a valid data source - if (!src) { + CHECK_PARAM(!src) { SDL_InvalidParamError("src"); goto done; - } else if (!spec) { + } + CHECK_PARAM(!spec) { SDL_InvalidParamError("spec"); goto done; - } else if (!audio_buf) { + } + CHECK_PARAM(!audio_buf) { SDL_InvalidParamError("audio_buf"); goto done; - } else if (!audio_len) { + } + CHECK_PARAM(!audio_len) { SDL_InvalidParamError("audio_len"); goto done; } @@ -2114,8 +2126,8 @@ bool SDL_LoadWAV_IO(SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 result = WaveLoad(src, &file, spec, audio_buf, audio_len); if (!result) { SDL_free(*audio_buf); - audio_buf = NULL; - audio_len = 0; + *audio_buf = NULL; + *audio_len = 0; } // Cleanup diff --git a/libs/SDL3/src/audio/SDL_wave.h b/libs/SDL3/src/audio/SDL_wave.h index 0ec89656..77fb9a46 100644 --- a/libs/SDL3/src/audio/SDL_wave.h +++ b/libs/SDL3/src/audio/SDL_wave.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/aaudio/SDL_aaudio.c b/libs/SDL3/src/audio/aaudio/SDL_aaudio.c index 5436be07..ea5e1c09 100644 --- a/libs/SDL3/src/audio/aaudio/SDL_aaudio.c +++ b/libs/SDL3/src/audio/aaudio/SDL_aaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -53,6 +53,13 @@ struct SDL_PrivateAudioData #define LIB_AAUDIO_SO "libaaudio.so" +SDL_ELF_NOTE_DLOPEN( + "audio-aaudio", + "Support for audio through AAudio", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + LIB_AAUDIO_SO +) + typedef struct AAUDIO_Data { SDL_SharedObject *handle; @@ -65,11 +72,16 @@ static bool AAUDIO_LoadFunctions(AAUDIO_Data *data) { #define SDL_PROC(ret, func, params) \ do { \ - data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \ + data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \ if (!data->func) { \ return SDL_SetError("Couldn't load AAUDIO function %s: %s", #func, SDL_GetError()); \ } \ } while (0); + +#define SDL_PROC_OPTIONAL(ret, func, params) \ + do { \ + data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); /* if it fails, okay. */ \ + } while (0); #include "SDL_aaudiofuncs.h" return true; } @@ -253,7 +265,7 @@ static int AAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen static void AAUDIO_CloseDevice(SDL_AudioDevice *device) { struct SDL_PrivateAudioData *hidden = device->hidden; - LOGI(__func__); + LOGI(SDL_FUNCTION); if (hidden) { if (hidden->stream) { @@ -273,6 +285,28 @@ static void AAUDIO_CloseDevice(SDL_AudioDevice *device) } } +static void SetOptionalStreamUsage(AAudioStreamBuilder *builder) +{ + if (ctx.AAudioStreamBuilder_setUsage) { // optional API: requires Android 28 + const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE); + if (hint) { + aaudio_usage_t usage = AAUDIO_USAGE_MEDIA; // covers most things, and is the system default. + if ((SDL_strcasecmp(hint, "Communications") == 0) || (SDL_strcasecmp(hint, "GameChat") == 0)) { + usage = AAUDIO_USAGE_VOICE_COMMUNICATION; + } else if (SDL_strcasecmp(hint, "Game") == 0) { + usage = AAUDIO_USAGE_GAME; + } + ctx.AAudioStreamBuilder_setUsage(builder, usage); + + // !!! FIXME: I _think_ this is okay with the current set of usages we support, but the docs + // !!! FIXME: say you need to dip down into Java to call android.app.Activity.setVolumeControlStream(usage) + // !!! FIXME: so the physical volume buttons control this stream, but that might be more for special cases + // !!! FIXME: like notification sounds, etc, and it's possible you _don't_ want to override this for those + // !!! FIXME: special cases, too! We'll revisit if there are bug reports. + } + } +} + static bool BuildAAudioStream(SDL_AudioDevice *device) { struct SDL_PrivateAudioData *hidden = device->hidden; @@ -308,12 +342,16 @@ static bool BuildAAudioStream(SDL_AudioDevice *device) ctx.AAudioStreamBuilder_setFormat(builder, format); ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq); ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels); - - // If no specific buffer size has been requested, the device will pick the optimal - if(SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES)) { - ctx.AAudioStreamBuilder_setBufferCapacityInFrames(builder, 2 * device->sample_frames); // AAudio requires that the buffer capacity is at least - ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, device->sample_frames); // twice the size of the data callback buffer size - } + + int32_t sample_frames; + if (SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES)) { + sample_frames = device->sample_frames; + } else { + // Use 20 ms for the default audio buffer size + sample_frames = (device->spec.freq / 50); + } + ctx.AAudioStreamBuilder_setBufferCapacityInFrames(builder, 2 * sample_frames); // AAudio requires that the buffer capacity is at least + ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, sample_frames); // twice the size of the data callback buffer size const aaudio_direction_t direction = (recording ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT); ctx.AAudioStreamBuilder_setDirection(builder, direction); @@ -327,6 +365,14 @@ static bool BuildAAudioStream(SDL_AudioDevice *device) SDL_Log("Low latency audio disabled"); } + SetOptionalStreamUsage(builder); + + if (recording && ctx.AAudioStreamBuilder_setInputPreset) { // optional API: requires Android 28 + // try to use a microphone that is for recording external audio. Otherwise Android might choose the mic used for talking + // on the telephone when held to the user's ear, which is often not useful at any distance from the device. + ctx.AAudioStreamBuilder_setInputPreset(builder, AAUDIO_INPUT_PRESET_CAMCORDER); + } + LOGI("AAudio Try to open %u hz %s %u channels samples %u", device->spec.freq, SDL_GetAudioFormatName(device->spec.format), device->spec.channels, device->sample_frames); @@ -335,7 +381,7 @@ static bool BuildAAudioStream(SDL_AudioDevice *device) if (res != AAUDIO_OK) { LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res); ctx.AAudioStreamBuilder_delete(builder); - return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); + return SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res)); } ctx.AAudioStreamBuilder_delete(builder); @@ -385,7 +431,7 @@ static bool BuildAAudioStream(SDL_AudioDevice *device) res = ctx.AAudioStream_requestStart(hidden->stream); if (res != AAUDIO_OK) { LOGI("SDL Failed AAudioStream_requestStart %d recording:%d", res, recording); - return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); + return SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res)); } LOGI("SDL AAudioStream_requestStart OK"); @@ -405,7 +451,7 @@ static bool AAUDIO_OpenDevice(SDL_AudioDevice *device) SDL_assert(device->handle); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero. #endif - LOGI(__func__); + LOGI(SDL_FUNCTION); if (device->recording) { // !!! FIXME: make this non-blocking! @@ -449,7 +495,7 @@ static bool PauseOneDevice(SDL_AudioDevice *device, void *userdata) if (res != AAUDIO_OK) { LOGI("SDL Failed AAudioStream_requestPause %d", res); - SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); + SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res)); } } } @@ -473,7 +519,7 @@ static bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata) aaudio_result_t res = ctx.AAudioStream_requestStart(hidden->stream); if (res != AAUDIO_OK) { LOGI("SDL Failed AAudioStream_requestStart %d", res); - SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); + SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res)); } } } @@ -491,7 +537,7 @@ static void AAUDIO_Deinitialize(void) { Android_StopAudioHotplug(); - LOGI(__func__); + LOGI(SDL_FUNCTION); if (ctx.handle) { SDL_UnloadObject(ctx.handle); } @@ -502,7 +548,7 @@ static void AAUDIO_Deinitialize(void) static bool AAUDIO_Init(SDL_AudioDriverImpl *impl) { - LOGI(__func__); + LOGI(SDL_FUNCTION); /* AAudio was introduced in Android 8.0, but has reference counting crash issues in that release, * so don't use it until 8.1. diff --git a/libs/SDL3/src/audio/aaudio/SDL_aaudio.h b/libs/SDL3/src/audio/aaudio/SDL_aaudio.h index 5326bad4..8005f775 100644 --- a/libs/SDL3/src/audio/aaudio/SDL_aaudio.h +++ b/libs/SDL3/src/audio/aaudio/SDL_aaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h b/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h index 1d9f7104..6568c12b 100644 --- a/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h +++ b/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright , (C) 1997-2025 Sam Lantinga + Copyright , (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -19,6 +19,10 @@ 3. This notice may not be removed or altered from any source distribution. */ +#ifndef SDL_PROC_OPTIONAL +#define SDL_PROC_OPTIONAL(ret, func, params) SDL_PROC(ret, func, params) +#endif + #define SDL_PROC_UNUSED(ret, func, params) SDL_PROC(const char *, AAudio_convertResultToText, (aaudio_result_t returnCode)) @@ -33,9 +37,9 @@ SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction)) 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_OPTIONAL(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) // API 28 SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) // API 28 -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) // API 28 +SDL_PROC_OPTIONAL(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) // API 28 SDL_PROC_UNUSED(void, AAudioStreamBuilder_setAllowedCapturePolicy, (AAudioStreamBuilder * builder, aaudio_allowed_capture_policy_t capturePolicy)) // API 29 SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSessionId, (AAudioStreamBuilder * builder, aaudio_session_id_t sessionId)) // API 28 SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPrivacySensitive, (AAudioStreamBuilder * builder, bool privacySensitive)) // API 30 @@ -80,3 +84,4 @@ SDL_PROC_UNUSED(bool, AAudioStream_isPrivacySensitive, (AAudioStream * stream)) #undef SDL_PROC #undef SDL_PROC_UNUSED +#undef SDL_PROC_OPTIONAL diff --git a/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c b/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c index 4ba156e3..ba9e1036 100644 --- a/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c +++ b/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -82,6 +82,7 @@ static int (*ALSA_snd_pcm_nonblock)(snd_pcm_t *, int); static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int); static int (*ALSA_snd_pcm_sw_params_set_avail_min)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t); static int (*ALSA_snd_pcm_reset)(snd_pcm_t *); +static snd_pcm_state_t (*ALSA_snd_pcm_state)(snd_pcm_t *); static int (*ALSA_snd_device_name_hint)(int, const char *, void ***); static char *(*ALSA_snd_device_name_get_hint)(const void *, const char *); static int (*ALSA_snd_device_name_free_hint)(void **); @@ -98,7 +99,6 @@ static void (*ALSA_snd_pcm_info_set_device)(snd_pcm_info_t *, unsigned int); static void (*ALSA_snd_pcm_info_set_subdevice)(snd_pcm_info_t *, unsigned int); static void (*ALSA_snd_pcm_info_set_stream)(snd_pcm_info_t *, snd_pcm_stream_t); static int (*ALSA_snd_ctl_pcm_info)(snd_ctl_t *, snd_pcm_info_t *); -static unsigned int (*ALSA_snd_pcm_info_get_subdevices_count)(const snd_pcm_info_t *); static const char *(*ALSA_snd_ctl_card_info_get_id)(const snd_ctl_card_info_t *); static const char *(*ALSA_snd_pcm_info_get_name)(const snd_pcm_info_t *); static const char *(*ALSA_snd_pcm_info_get_subdevice_name)(const snd_pcm_info_t *); @@ -172,6 +172,7 @@ static bool load_alsa_syms(void) SDL_ALSA_SYM(snd_pcm_wait); SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min); SDL_ALSA_SYM(snd_pcm_reset); + SDL_ALSA_SYM(snd_pcm_state); SDL_ALSA_SYM(snd_device_name_hint); SDL_ALSA_SYM(snd_device_name_get_hint); SDL_ALSA_SYM(snd_device_name_free_hint); @@ -208,6 +209,13 @@ static bool load_alsa_syms(void) #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libalsa", + "Support for audio through libalsa", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_ALSA_DYNAMIC +) + static void UnloadALSALibrary(void) { if (alsa_handle) { @@ -346,6 +354,20 @@ static char *get_pcm_str(void *handle) return pcm_str; } +static int RecoverALSADevice(snd_pcm_t *pcm, int errnum) +{ + const snd_pcm_state_t prerecovery = ALSA_snd_pcm_state(pcm); + const int status = ALSA_snd_pcm_recover(pcm, errnum, 0); // !!! FIXME: third parameter is non-zero to prevent libasound from printing error messages. Should we do that? + if (status == 0) { + const snd_pcm_state_t postrecovery = ALSA_snd_pcm_state(pcm); + if ((prerecovery == SND_PCM_STATE_XRUN) && (postrecovery == SND_PCM_STATE_PREPARED)) { + ALSA_snd_pcm_start(pcm); // restart the device if it stopped due to an overrun or underrun. + } + } + return status; +} + + // This function waits until it is possible to write a full sound buffer static bool ALSA_WaitDevice(SDL_AudioDevice *device) { @@ -356,7 +378,7 @@ static bool ALSA_WaitDevice(SDL_AudioDevice *device) while (!SDL_GetAtomicInt(&device->shutdown)) { 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); + const int status = RecoverALSADevice(device->hidden->pcm, rc); if (status < 0) { // Hmm, not much we can do - abort SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA wait failed (unrecoverable): %s", ALSA_snd_strerror(rc)); @@ -384,7 +406,7 @@ static bool ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int bu SDL_assert(rc != 0); // assuming this can't happen if we used snd_pcm_wait and queried for available space. if (rc < 0) { SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it! - const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0); + const int status = RecoverALSADevice(device->hidden->pcm, rc); if (status < 0) { // Hmm, not much we can do - abort SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA write failed (unrecoverable): %s", ALSA_snd_strerror(rc)); @@ -439,7 +461,7 @@ static int ALSA_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen) SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it! if (rc < 0) { - const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0); + const int status = RecoverALSADevice(device->hidden->pcm, rc); if (status < 0) { // Hmm, not much we can do - abort SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA read failed (unrecoverable): %s", ALSA_snd_strerror(rc)); @@ -462,8 +484,6 @@ static void ALSA_CloseDevice(SDL_AudioDevice *device) { if (device->hidden) { if (device->hidden->pcm) { - // Wait for the submitted audio to drain. ALSA_snd_pcm_drop() can hang, so don't use that. - SDL_Delay(((device->sample_frames * 1000) / device->spec.freq) * 2); ALSA_snd_pcm_close(device->hidden->pcm); } SDL_free(device->hidden->mixbuf); @@ -652,7 +672,7 @@ static void swizzle_map_compute_alsa_subscan(const struct ALSA_pcm_cfg_ctx *ctx, } } -// XXX: this must stay playback/recording symetric. +// XXX: this must stay playback/recording symmetric. static void swizzle_map_compute(const struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, bool *needs_swizzle) { *needs_swizzle = false; @@ -1001,7 +1021,7 @@ static int ALSA_pcm_cfg_hw_chans_n_scan(struct ALSA_pcm_cfg_ctx *ctx, unsigned i SDL_SetError("ALSA: Couldn't set the period size: %s", ALSA_snd_strerror(status)); return -1; } - // let approximate the minimun number of periods per buffer (we target a double buffer) + // let approximate the minimum number of periods per buffer (we target a double buffer) ctx->periods = 2; status = ALSA_snd_pcm_hw_params_set_periods_min(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->periods), NULL); if (status < 0) { @@ -1069,7 +1089,7 @@ static bool ALSA_pcm_cfg_hw(struct ALSA_pcm_cfg_ctx *ctx) } // Here, status == CHANS_N_NOT_CONFIGURED - return SDL_SetError("ALSA: Coudn't configure targetting any SDL supported channel number"); + return SDL_SetError("ALSA: Couldn't configure targeting any SDL supported channel number"); } #undef CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N #undef CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N @@ -1150,14 +1170,14 @@ static bool ALSA_OpenDevice(SDL_AudioDevice *device) goto err_close_pcm; } - // from here, we get only the alsa chmap queries in cfg_ctx to explicitely clean, hwparams is + // from here, we get only the alsa chmap queries in cfg_ctx to explicitly clean, hwparams is // uninstalled upon pcm closing // This is useful for debugging #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 @@ -1470,7 +1490,7 @@ static void ALSA_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, c } } -static bool ALSA_start_udev() +static bool ALSA_start_udev(void) { udev_initialized = SDL_UDEV_Init(); if (udev_initialized) { @@ -1483,7 +1503,7 @@ static bool ALSA_start_udev() return udev_initialized; } -static void ALSA_stop_udev() +static void ALSA_stop_udev(void) { if (udev_initialized) { SDL_UDEV_DelCallback(ALSA_udev_callback); @@ -1494,12 +1514,12 @@ static void ALSA_stop_udev() #else -static bool ALSA_start_udev() +static bool ALSA_start_udev(void) { return false; } -static void ALSA_stop_udev() +static void ALSA_stop_udev(void) { } diff --git a/libs/SDL3/src/audio/alsa/SDL_alsa_audio.h b/libs/SDL3/src/audio/alsa/SDL_alsa_audio.h index c68dda89..b90299d8 100644 --- a/libs/SDL3/src/audio/alsa/SDL_alsa_audio.h +++ b/libs/SDL3/src/audio/alsa/SDL_alsa_audio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.h b/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.h index f117c092..05991466 100644 --- a/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.h +++ b/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m b/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m index e7d9e58a..dda5f573 100644 --- a/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m +++ b/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -420,7 +420,8 @@ static bool UpdateAudioSession(SDL_AudioDevice *device, bool open, bool allow_pl hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY); if (hint) { - if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) { + if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0 || + SDL_strcasecmp(hint, "ambient") == 0) { category = AVAudioSessionCategoryAmbient; } else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) { category = AVAudioSessionCategorySoloAmbient; diff --git a/libs/SDL3/src/audio/directsound/SDL_directsound.c b/libs/SDL3/src/audio/directsound/SDL_directsound.c index 7b5cb11e..89480ee4 100644 --- a/libs/SDL3/src/audio/directsound/SDL_directsound.c +++ b/libs/SDL3/src/audio/directsound/SDL_directsound.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -40,16 +40,16 @@ static bool SupportsIMMDevice = false; // DirectX function pointers for audio static SDL_SharedObject *DSoundDLL = NULL; -typedef HRESULT(WINAPI *fnDirectSoundCreate8)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN); -typedef HRESULT(WINAPI *fnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID); -typedef HRESULT(WINAPI *fnDirectSoundCaptureCreate8)(LPCGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN); -typedef HRESULT(WINAPI *fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID); -typedef HRESULT(WINAPI *fnGetDeviceID)(LPCGUID, LPGUID); -static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL; -static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL; -static fnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL; -static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL; -static fnGetDeviceID pGetDeviceID = NULL; +typedef HRESULT (WINAPI *pfnDirectSoundCreate8)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN); +typedef HRESULT (WINAPI *pfnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID); +typedef HRESULT (WINAPI *pfnDirectSoundCaptureCreate8)(LPCGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN); +typedef HRESULT (WINAPI *pfnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID); +typedef HRESULT (WINAPI *pfnGetDeviceID)(LPCGUID, LPGUID); +static pfnDirectSoundCreate8 pDirectSoundCreate8 = NULL; +static pfnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL; +static pfnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL; +static pfnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL; +static pfnGetDeviceID pGetDeviceID = NULL; #include DEFINE_GUID(SDL_DSDEVID_DefaultPlayback, 0xdef00000, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03); @@ -85,7 +85,7 @@ static bool DSOUND_Load(void) // Now make sure we have DirectX 8 or better... #define DSOUNDLOAD(f) \ { \ - p##f = (fn##f)SDL_LoadFunction(DSoundDLL, #f); \ + p##f = (pfn##f)SDL_LoadFunction(DSoundDLL, #f); \ if (!p##f) \ loaded = false; \ } @@ -206,7 +206,7 @@ static void DSOUND_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDe { #ifdef HAVE_MMDEVICEAPI_H if (SupportsIMMDevice) { - SDL_IMMDevice_EnumerateEndpoints(default_playback, default_recording); + SDL_IMMDevice_EnumerateEndpoints(default_playback, default_recording, SDL_AUDIO_UNKNOWN, false); } else #endif { @@ -545,6 +545,7 @@ static bool DSOUND_OpenDevice(SDL_AudioDevice *device) tried_format = true; device->spec.format = test_format; + device->spec.freq = SDL_min(DSBFREQUENCY_MAX, device->spec.freq); // Update the fragment size as size in bytes SDL_UpdatedAudioDeviceFormat(device); diff --git a/libs/SDL3/src/audio/directsound/SDL_directsound.h b/libs/SDL3/src/audio/directsound/SDL_directsound.h index a4fa2faf..754d5a0e 100644 --- a/libs/SDL3/src/audio/directsound/SDL_directsound.h +++ b/libs/SDL3/src/audio/directsound/SDL_directsound.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/disk/SDL_diskaudio.c b/libs/SDL3/src/audio/disk/SDL_diskaudio.c index 34909666..d6c214e9 100644 --- a/libs/SDL3/src/audio/disk/SDL_diskaudio.c +++ b/libs/SDL3/src/audio/disk/SDL_diskaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/disk/SDL_diskaudio.h b/libs/SDL3/src/audio/disk/SDL_diskaudio.h index d8d99801..a5be5710 100644 --- a/libs/SDL3/src/audio/disk/SDL_diskaudio.h +++ b/libs/SDL3/src/audio/disk/SDL_diskaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/dsp/SDL_dspaudio.c b/libs/SDL3/src/audio/dsp/SDL_dspaudio.c index 62b8990a..ef274c14 100644 --- a/libs/SDL3/src/audio/dsp/SDL_dspaudio.c +++ b/libs/SDL3/src/audio/dsp/SDL_dspaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/dsp/SDL_dspaudio.h b/libs/SDL3/src/audio/dsp/SDL_dspaudio.h index 65dea601..56412840 100644 --- a/libs/SDL3/src/audio/dsp/SDL_dspaudio.h +++ b/libs/SDL3/src/audio/dsp/SDL_dspaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/dummy/SDL_dummyaudio.c b/libs/SDL3/src/audio/dummy/SDL_dummyaudio.c index d0f1a1a6..74a628bc 100644 --- a/libs/SDL3/src/audio/dummy/SDL_dummyaudio.c +++ b/libs/SDL3/src/audio/dummy/SDL_dummyaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -25,7 +25,7 @@ #include "../SDL_sysaudio.h" #include "SDL_dummyaudio.h" -#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__) +#if defined(SDL_PLATFORM_EMSCRIPTEN) #include #endif @@ -59,8 +59,8 @@ static bool DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device) } } - // on Emscripten without threads, we just fire a repeating timer to consume audio. - #if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__) + // on Emscripten, we just fire a repeating timer to consume audio. + #if defined(SDL_PLATFORM_EMSCRIPTEN) MAIN_THREAD_EM_ASM({ var a = Module['SDL3'].dummy_audio; if (a.timers[$0] !== undefined) { clearInterval(a.timers[$0]); } @@ -74,8 +74,8 @@ static bool DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device) static void DUMMYAUDIO_CloseDevice(SDL_AudioDevice *device) { if (device->hidden) { - // on Emscripten without threads, we just fire a repeating timer to consume audio. - #if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__) + // on Emscripten, we just fire a repeating timer to consume audio. + #if defined(SDL_PLATFORM_EMSCRIPTEN) MAIN_THREAD_EM_ASM({ var a = Module['SDL3'].dummy_audio; if (a.timers[$0] !== undefined) { clearInterval(a.timers[$0]); } @@ -113,12 +113,9 @@ static bool DUMMYAUDIO_Init(SDL_AudioDriverImpl *impl) impl->OnlyHasDefaultRecordingDevice = true; impl->HasRecordingSupport = true; - // on Emscripten without threads, we just fire a repeating timer to consume audio. - #if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__) + // on Emscripten, we just fire a repeating timer to consume audio. + #if defined(SDL_PLATFORM_EMSCRIPTEN) MAIN_THREAD_EM_ASM({ - if (typeof(Module['SDL3']) === 'undefined') { - Module['SDL3'] = {}; - } Module['SDL3'].dummy_audio = {}; Module['SDL3'].dummy_audio.timers = []; Module['SDL3'].dummy_audio.timers[0] = undefined; diff --git a/libs/SDL3/src/audio/dummy/SDL_dummyaudio.h b/libs/SDL3/src/audio/dummy/SDL_dummyaudio.h index d5e75c78..a122b818 100644 --- a/libs/SDL3/src/audio/dummy/SDL_dummyaudio.h +++ b/libs/SDL3/src/audio/dummy/SDL_dummyaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c b/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c index 4be91910..4087a5ef 100644 --- a/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c +++ b/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -41,13 +41,8 @@ static bool EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buf const int framelen = SDL_AUDIO_FRAMESIZE(device->spec); MAIN_THREAD_EM_ASM({ /* Convert incoming buf pointer to a HEAPF32 offset. */ - #ifdef __wasm64__ - var buf = $0 / 4; - #else - var buf = $0 >>> 2; - #endif - var SDL3 = Module['SDL3']; + var buf = SDL3.CPtrToHeap32Index($0); var numChannels = SDL3.audio_playback.currentPlaybackBuffer['numberOfChannels']; for (var c = 0; c < numChannels; ++c) { var channelData = SDL3.audio_playback.currentPlaybackBuffer['getChannelData'](c); @@ -151,13 +146,11 @@ static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device) // create context const bool result = MAIN_THREAD_EM_ASM_INT({ - if (typeof(Module['SDL3']) === 'undefined') { - Module['SDL3'] = {}; - } var SDL3 = Module['SDL3']; - if (!$0) { + if (typeof(SDL3.audio_playback) === 'undefined') { SDL3.audio_playback = {}; - } else { + } + if (typeof(SDL3.audio_recording) === 'undefined') { SDL3.audio_recording = {}; } @@ -174,7 +167,7 @@ static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device) } } return (SDL3.audioContext !== undefined); - }, device->recording); + }); if (!result) { return SDL_SetError("Web Audio API is not available!"); diff --git a/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.h b/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.h index aaf761be..a178d928 100644 --- a/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.h +++ b/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/haiku/SDL_haikuaudio.cc b/libs/SDL3/src/audio/haiku/SDL_haikuaudio.cc index 730b107d..55b6600b 100644 --- a/libs/SDL3/src/audio/haiku/SDL_haikuaudio.cc +++ b/libs/SDL3/src/audio/haiku/SDL_haikuaudio.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/haiku/SDL_haikuaudio.h b/libs/SDL3/src/audio/haiku/SDL_haikuaudio.h index 9547546a..45ab42e1 100644 --- a/libs/SDL3/src/audio/haiku/SDL_haikuaudio.h +++ b/libs/SDL3/src/audio/haiku/SDL_haikuaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/jack/SDL_jackaudio.c b/libs/SDL3/src/audio/jack/SDL_jackaudio.c index 3ae5137a..a5237da0 100644 --- a/libs/SDL3/src/audio/jack/SDL_jackaudio.c +++ b/libs/SDL3/src/audio/jack/SDL_jackaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -50,6 +50,13 @@ static bool load_jack_syms(void); #ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libjack", + "Support for audio through libjack", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_JACK_DYNAMIC +) + static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC; static SDL_SharedObject *jack_handle = NULL; diff --git a/libs/SDL3/src/audio/jack/SDL_jackaudio.h b/libs/SDL3/src/audio/jack/SDL_jackaudio.h index 4f972d59..1ef70995 100644 --- a/libs/SDL3/src/audio/jack/SDL_jackaudio.h +++ b/libs/SDL3/src/audio/jack/SDL_jackaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.c b/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.c index 780b06c4..ded98ea2 100644 --- a/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.c +++ b/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.h b/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.h index c9ae4f8e..dc93e747 100644 --- a/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.h +++ b/libs/SDL3/src/audio/n3ds/SDL_n3dsaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.c b/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.c index 26060d37..b18ab2ff 100644 --- a/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.c +++ b/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.h b/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.h index dcdc6f4a..bcb7bc7e 100644 --- a/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.h +++ b/libs/SDL3/src/audio/netbsd/SDL_netbsdaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/ngage/SDL_ngageaudio.c b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.c index 3595bd70..5b964d84 100644 --- a/libs/SDL3/src/audio/ngage/SDL_ngageaudio.c +++ b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/ngage/SDL_ngageaudio.cpp b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.cpp index 9acf030c..aff7ff0b 100644 --- a/libs/SDL3/src/audio/ngage/SDL_ngageaudio.cpp +++ b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -260,7 +260,7 @@ void CAudio::MaoscPlayComplete(TInt aError) // 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 + // The number of samples played gets reset to zero when we restart // playback after underflow. iBaseSamplesPlayed = iSamplesWritten; @@ -278,7 +278,7 @@ void CAudio::MaoscPlayComplete(TInt aError) } // We shouldn't get here. - SDL_Log("%s: %d", __FUNCTION__, aError); + SDL_Log("%s: %d", SDL_FUNCTION, aError); } static TBool gAudioRunning; diff --git a/libs/SDL3/src/audio/ngage/SDL_ngageaudio.h b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.h index dda2c91e..ee3afedb 100644 --- a/libs/SDL3/src/audio/ngage/SDL_ngageaudio.h +++ b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/ngage/SDL_ngageaudio.hpp b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.hpp index e0635f5a..68e714a8 100644 --- a/libs/SDL3/src/audio/ngage/SDL_ngageaudio.hpp +++ b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.hpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/openslES/SDL_openslES.c b/libs/SDL3/src/audio/openslES/SDL_openslES.c index 4d5b3bdf..0f49585d 100644 --- a/libs/SDL3/src/audio/openslES/SDL_openslES.c +++ b/libs/SDL3/src/audio/openslES/SDL_openslES.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -223,9 +223,7 @@ static void OPENSLES_DestroyPCMRecorder(SDL_AudioDevice *device) audiodata->playsem = NULL; } - if (audiodata->mixbuff) { - SDL_free(audiodata->mixbuff); - } + SDL_free(audiodata->mixbuff); } // !!! FIXME: make this non-blocking! @@ -419,35 +417,38 @@ static void OPENSLES_DestroyPCMPlayer(SDL_AudioDevice *device) audiodata->playsem = NULL; } - if (audiodata->mixbuff) { - SDL_free(audiodata->mixbuff); - } + SDL_free(audiodata->mixbuff); } static bool OPENSLES_CreatePCMPlayer(SDL_AudioDevice *device) { - /* If we want to add floating point audio support (requires API level 21) - it can be done as described here: - https://developer.android.com/ndk/guides/audio/opensl/android-extensions.html#floating-point - */ + /* according to https://developer.android.com/ndk/guides/audio/opensl/opensl-for-android, + Android's OpenSL ES only supports Uint8 and _littleendian_ Sint16. + (and float32, with an extension we use, below.) */ if (SDL_GetAndroidSDKVersion() >= 21) { const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); SDL_AudioFormat test_format; while ((test_format = *(closefmts++)) != 0) { - if (SDL_AUDIO_ISSIGNED(test_format)) { + switch (test_format) { + case SDL_AUDIO_U8: + case SDL_AUDIO_S16LE: + case SDL_AUDIO_F32: break; + default: + continue; } + break; } if (!test_format) { // Didn't find a compatible format : - LOGI("No compatible audio format, using signed 16-bit audio"); - test_format = SDL_AUDIO_S16; + LOGI("No compatible audio format, using signed 16-bit LE audio"); + test_format = SDL_AUDIO_S16LE; } device->spec.format = test_format; } else { // Just go with signed 16-bit audio as it's the most compatible - device->spec.format = SDL_AUDIO_S16; + device->spec.format = SDL_AUDIO_S16LE; } // Update the fragment size as size in bytes diff --git a/libs/SDL3/src/audio/openslES/SDL_openslES.h b/libs/SDL3/src/audio/openslES/SDL_openslES.h index 0ae26641..ca17c4bb 100644 --- a/libs/SDL3/src/audio/openslES/SDL_openslES.h +++ b/libs/SDL3/src/audio/openslES/SDL_openslES.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/pipewire/SDL_pipewire.c b/libs/SDL3/src/audio/pipewire/SDL_pipewire.c index a9e95a6f..b3882753 100644 --- a/libs/SDL3/src/audio/pipewire/SDL_pipewire.c +++ b/libs/SDL3/src/audio/pipewire/SDL_pipewire.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -93,6 +93,13 @@ static int (*PIPEWIRE_pw_properties_setf)(struct pw_properties *, const char *, #ifdef SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libpipewire", + "Support for audio through libpipewire", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC +) + static const char *pipewire_library = SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC; static SDL_SharedObject *pipewire_handle = NULL; @@ -628,16 +635,12 @@ static int metadata_property(void *object, Uint32 subject, const char *key, cons if (subject == PW_ID_CORE && key && value) { if (!SDL_strcmp(key, "default.audio.sink")) { - if (pipewire_default_sink_id) { - SDL_free(pipewire_default_sink_id); - } + SDL_free(pipewire_default_sink_id); pipewire_default_sink_id = get_name_from_json(value); node->persist = true; change_default_device(pipewire_default_sink_id); } else if (!SDL_strcmp(key, "default.audio.source")) { - if (pipewire_default_source_id) { - SDL_free(pipewire_default_source_id); - } + SDL_free(pipewire_default_source_id); pipewire_default_source_id = get_name_from_json(value); node->persist = true; change_default_device(pipewire_default_source_id); @@ -931,18 +934,18 @@ static void initialize_spa_info(const SDL_AudioSpec *spec, struct spa_audio_info static Uint8 *PIPEWIRE_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - // See if a buffer is available. If this returns NULL, SDL_PlaybackAudioThreadIterate will return false, but since we own the thread, it won't kill playback. - // !!! FIXME: It's not clear to me if this ever returns NULL or if this was just defensive coding. - + // See if a buffer is available. If this sets *buffer_size=0, then SDL_PlaybackAudioThreadIterate will skip this iteration but try again next time. struct pw_stream *stream = device->hidden->stream; struct pw_buffer *pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); if (pw_buf == NULL) { + *buffer_size = 0; return NULL; } struct spa_buffer *spa_buf = pw_buf->buffer; if (spa_buf->datas[0].data == NULL) { PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); + *buffer_size = 0; return NULL; } diff --git a/libs/SDL3/src/audio/pipewire/SDL_pipewire.h b/libs/SDL3/src/audio/pipewire/SDL_pipewire.h index e609e252..0e98ec4e 100644 --- a/libs/SDL3/src/audio/pipewire/SDL_pipewire.h +++ b/libs/SDL3/src/audio/pipewire/SDL_pipewire.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/ps2/SDL_ps2audio.c b/libs/SDL3/src/audio/ps2/SDL_ps2audio.c index 6579f1bd..2100bf04 100644 --- a/libs/SDL3/src/audio/ps2/SDL_ps2audio.c +++ b/libs/SDL3/src/audio/ps2/SDL_ps2audio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/ps2/SDL_ps2audio.h b/libs/SDL3/src/audio/ps2/SDL_ps2audio.h index 5a0eceae..b79e3961 100644 --- a/libs/SDL3/src/audio/ps2/SDL_ps2audio.h +++ b/libs/SDL3/src/audio/ps2/SDL_ps2audio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/psp/SDL_pspaudio.c b/libs/SDL3/src/audio/psp/SDL_pspaudio.c index 18546e88..6ead4ad6 100644 --- a/libs/SDL3/src/audio/psp/SDL_pspaudio.c +++ b/libs/SDL3/src/audio/psp/SDL_pspaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -114,7 +114,7 @@ static bool PSPAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, in } else { rc = sceAudioOutputPannedBlocking(device->hidden->channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, (void *) buffer); } - return (rc == 0); + return (rc >= 0); } static bool PSPAUDIO_WaitDevice(SDL_AudioDevice *device) diff --git a/libs/SDL3/src/audio/psp/SDL_pspaudio.h b/libs/SDL3/src/audio/psp/SDL_pspaudio.h index d7b20566..3d24cfa7 100644 --- a/libs/SDL3/src/audio/psp/SDL_pspaudio.h +++ b/libs/SDL3/src/audio/psp/SDL_pspaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c b/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c index 4d618a6e..2fcf078f 100644 --- a/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -133,6 +133,13 @@ static bool load_pulseaudio_syms(void); #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libpulseaudio", + "Support for audio through libpulseaudio", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC +) + static const char *pulseaudio_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC; static SDL_SharedObject *pulseaudio_handle = NULL; @@ -595,6 +602,71 @@ static void PulseStreamStateChangeCallback(pa_stream *stream, void *userdata) PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details. } +// Channel maps that match the order in SDL_Audio.h +static const pa_channel_position_t Pulse_map_1[] = { PA_CHANNEL_POSITION_MONO }; +static const pa_channel_position_t Pulse_map_2[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT }; + +static const pa_channel_position_t Pulse_map_3[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_LFE }; + +static const pa_channel_position_t Pulse_map_4[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }; + +static const pa_channel_position_t Pulse_map_5[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }; + +static const pa_channel_position_t Pulse_map_6[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }; + +static const pa_channel_position_t Pulse_map_7[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_REAR_CENTER, + PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT }; + +static const pa_channel_position_t Pulse_map_8[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT }; + +#define COPY_CHANNEL_MAP(c) SDL_memcpy(pacmap->map, Pulse_map_##c, sizeof(Pulse_map_##c)) + +static void PulseCreateChannelMap(pa_channel_map *pacmap, uint8_t channels) +{ + SDL_assert(channels <= PA_CHANNELS_MAX); + + pacmap->channels = channels; + + switch (channels) { + case 1: + COPY_CHANNEL_MAP(1); + break; + case 2: + COPY_CHANNEL_MAP(2); + break; + case 3: + COPY_CHANNEL_MAP(3); + break; + case 4: + COPY_CHANNEL_MAP(4); + break; + case 5: + COPY_CHANNEL_MAP(5); + break; + case 6: + COPY_CHANNEL_MAP(6); + break; + case 7: + COPY_CHANNEL_MAP(7); + break; + case 8: + COPY_CHANNEL_MAP(8); + break; + } + +} + static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) { const bool recording = device->recording; @@ -683,9 +755,8 @@ static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); const char *name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME); - // The SDL ALSA output hints us that we use Windows' channel mapping - // https://bugzilla.libsdl.org/show_bug.cgi?id=110 - PULSEAUDIO_pa_channel_map_init_auto(&pacmap, device->spec.channels, PA_CHANNEL_MAP_WAVEEX); + + PulseCreateChannelMap(&pacmap, device->spec.channels); h->stream = PULSEAUDIO_pa_stream_new( pulseaudio_context, @@ -732,7 +803,7 @@ static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) if (!actual_bufattr) { result = SDL_SetError("Could not determine connected PulseAudio stream's buffer attributes"); } else { - device->buffer_size = (int) recording ? actual_bufattr->tlength : actual_bufattr->fragsize; + device->buffer_size = (int) recording ? actual_bufattr->fragsize : actual_bufattr->tlength; device->sample_frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec); } } diff --git a/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.h b/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.h index 9ea44c0e..2c7e6b2e 100644 --- a/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.h +++ b/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/qnx/SDL_qsa_audio.c b/libs/SDL3/src/audio/qnx/SDL_qsa_audio.c index a31bea4b..be1cfa31 100644 --- a/libs/SDL3/src/audio/qnx/SDL_qsa_audio.c +++ b/libs/SDL3/src/audio/qnx/SDL_qsa_audio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -89,8 +89,8 @@ static void QSA_InitAudioParams(snd_pcm_channel_params_t * cpars) static bool QSA_WaitDevice(SDL_AudioDevice *device) { // Setup timeout for playing one fragment equal to 2 seconds - // If timeout occurred than something wrong with hardware or driver - // For example, Vortex 8820 audio driver stucks on second DAC because + // If timeout occurred then something wrong with hardware or driver + // For example, Vortex 8820 audio driver hangs on second DAC because // it doesn't exist ! const int result = SDL_IOReady(device->hidden->audio_fd, device->recording ? SDL_IOR_READ : SDL_IOR_WRITE, diff --git a/libs/SDL3/src/audio/qnx/SDL_qsa_audio.h b/libs/SDL3/src/audio/qnx/SDL_qsa_audio.h index 902752cd..ead46f72 100644 --- a/libs/SDL3/src/audio/qnx/SDL_qsa_audio.h +++ b/libs/SDL3/src/audio/qnx/SDL_qsa_audio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/sndio/SDL_sndioaudio.c b/libs/SDL3/src/audio/sndio/SDL_sndioaudio.c index a0d20209..4daab722 100644 --- a/libs/SDL3/src/audio/sndio/SDL_sndioaudio.c +++ b/libs/SDL3/src/audio/sndio/SDL_sndioaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -108,6 +108,13 @@ static bool load_sndio_syms(void) #ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libsndio", + "Support for audio through libsndio", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_SNDIO_DYNAMIC +) + static void UnloadSNDIOLibrary(void) { if (sndio_handle) { diff --git a/libs/SDL3/src/audio/sndio/SDL_sndioaudio.h b/libs/SDL3/src/audio/sndio/SDL_sndioaudio.h index d4ff7259..dfae963f 100644 --- a/libs/SDL3/src/audio/sndio/SDL_sndioaudio.h +++ b/libs/SDL3/src/audio/sndio/SDL_sndioaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/vita/SDL_vitaaudio.c b/libs/SDL3/src/audio/vita/SDL_vitaaudio.c index 86e8a691..5a963b5c 100644 --- a/libs/SDL3/src/audio/vita/SDL_vitaaudio.c +++ b/libs/SDL3/src/audio/vita/SDL_vitaaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/vita/SDL_vitaaudio.h b/libs/SDL3/src/audio/vita/SDL_vitaaudio.h index 1e974996..4bd64bfc 100644 --- a/libs/SDL3/src/audio/vita/SDL_vitaaudio.h +++ b/libs/SDL3/src/audio/vita/SDL_vitaaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/audio/wasapi/SDL_wasapi.c b/libs/SDL3/src/audio/wasapi/SDL_wasapi.c index 4b782d34..c28e797e 100644 --- a/libs/SDL3/src/audio/wasapi/SDL_wasapi.c +++ b/libs/SDL3/src/audio/wasapi/SDL_wasapi.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -45,8 +45,8 @@ // handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency). static HMODULE libavrt = NULL; -typedef HANDLE(WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD); -typedef BOOL(WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE); +typedef HANDLE (WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD); +typedef BOOL (WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE); static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL; static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL; @@ -54,11 +54,31 @@ static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NUL static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } }; static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } }; static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } }; +#ifdef __IAudioClient2_INTERFACE_DEFINED__ +static const IID SDL_IID_IAudioClient2 = { 0x726778cd, 0xf60a, 0x4EDA, { 0x82, 0xde, 0xe4, 0x76, 0x10, 0xcd, 0x78, 0xaa } }; +#endif // #ifdef __IAudioClient3_INTERFACE_DEFINED__ static const IID SDL_IID_IAudioClient3 = { 0x7ed4ee07, 0x8e67, 0x4cd4, { 0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42 } }; #endif // static bool immdevice_initialized = false; +static bool supports_recording_on_playback_devices = false; + +#ifdef __IAudioClient2_INTERFACE_DEFINED__ +// AUDCLNT_STREAMOPTIONS and AudioClientProperties->Options were +// added in Windows 8.1: This ugliness is here to make sure that +// we can build against older SDK versions. +#define SDL_AUDCLNT_STREAMOPTIONS_RAW 0x1 +typedef union SDL_AudioClientProperties { + AudioClientProperties a; + struct _SDL_AudioClientProperties { + UINT32 cbSize; + BOOL bIsOffload; + AUDIO_STREAM_CATEGORY eCategory; + int Options; // AUDCLNT_STREAMOPTIONS + } s; +} SDL_AudioClientProperties; +#endif // // WASAPI is _really_ particular about various things happening on the same thread, for COM and such, // so we proxy various stuff to a single background thread to manage. @@ -164,21 +184,10 @@ bool WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, b return true; // successfully added (and possibly executed)! } -static bool mgmtthrtask_AudioDeviceDisconnected(void *userdata) -{ - SDL_AudioDevice *device = (SDL_AudioDevice *) userdata; - SDL_AudioDeviceDisconnected(device); - UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes. - return true; -} static void AudioDeviceDisconnected(SDL_AudioDevice *device) { - // don't wait on this, IMMDevice's own thread needs to return or everything will deadlock. - if (device) { - RefPhysicalAudioDevice(device); // make sure this lives until the task completes. - WASAPI_ProxyToManagementThread(mgmtthrtask_AudioDeviceDisconnected, device, NULL); - } + WASAPI_DisconnectDevice(device); } static bool mgmtthrtask_DefaultAudioDeviceChanged(void *userdata) @@ -337,7 +346,7 @@ typedef struct static bool mgmtthrtask_DetectDevices(void *userdata) { mgmtthrtask_DetectDevicesData *data = (mgmtthrtask_DetectDevicesData *)userdata; - SDL_IMMDevice_EnumerateEndpoints(data->default_playback, data->default_recording); + SDL_IMMDevice_EnumerateEndpoints(data->default_playback, data->default_recording, SDL_AUDIO_F32, supports_recording_on_playback_devices); return true; } @@ -351,19 +360,11 @@ static void WASAPI_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDe WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc); } -static bool mgmtthrtask_DisconnectDevice(void *userdata) -{ - SDL_AudioDevice *device = (SDL_AudioDevice *) userdata; - SDL_AudioDeviceDisconnected(device); - UnrefPhysicalAudioDevice(device); - return true; -} - void WASAPI_DisconnectDevice(SDL_AudioDevice *device) { - if (SDL_CompareAndSwapAtomicInt(&device->hidden->device_disconnecting, 0, 1)) { - RefPhysicalAudioDevice(device); // will unref when the task ends. - WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, NULL); + // don't block in here; IMMDevice's own thread needs to return or everything will deadlock. + if (device && (!device->hidden || SDL_CompareAndSwapAtomicInt(&device->hidden->device_disconnecting, 0, 1))) { + SDL_AudioDeviceDisconnected(device); // this proxies the work to the main thread now, so no point in proxying to the management thread. } } @@ -462,6 +463,8 @@ static bool mgmtthrtask_ActivateDevice(void *userdata) return false; // This is already set by SDL_IMMDevice_Get } + device->hidden->isplayback = !SDL_IMMDevice_GetIsCapture(immdevice); + // this is _not_ async in standard win32, yay! HRESULT ret = IMMDevice_Activate(immdevice, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&device->hidden->client); IMMDevice_Release(immdevice); @@ -605,7 +608,7 @@ static int WASAPI_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen UINT32 frames = 0; DWORD flags = 0; - while (device->hidden->capture) { + while (device->hidden->capture && !SDL_GetAtomicInt(&device->hidden->device_disconnecting)) { const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL); if (ret == AUDCLNT_S_BUFFER_EMPTY) { return 0; // in theory we should have waited until there was data, but oh well, we'll go back to waiting. Returning 0 is safe in SDL3. @@ -741,11 +744,60 @@ static bool mgmtthrtask_PrepDevice(void *userdata) newspec.freq = waveformat->nSamplesPerSec; + if (device->recording && device->hidden->isplayback) { + streamflags |= AUDCLNT_STREAMFLAGS_LOOPBACK; + } + streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK; int new_sample_frames = 0; bool iaudioclient3_initialized = false; +#ifdef __IAudioClient2_INTERFACE_DEFINED__ + IAudioClient2 *client2 = NULL; + ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient2, (void **)&client2); + if (SUCCEEDED(ret)) { + SDL_AudioClientProperties audioProps; + + SDL_zero(audioProps); + audioProps.a.cbSize = sizeof(audioProps); + +// Setting AudioCategory_GameChat breaks audio on several devices, including Behringer U-PHORIA UM2 and RODE NT-USB Mini. +// We'll disable this for now until we understand more about what's happening. +#if 0 + const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE); + if (hint && *hint) { + if (SDL_strcasecmp(hint, "Communications") == 0) { + audioProps.a.eCategory = AudioCategory_Communications; + } else if (SDL_strcasecmp(hint, "Game") == 0) { + // We'll add support for GameEffects as distinct from GameMedia later when we add stream roles + audioProps.a.eCategory = AudioCategory_GameEffects; + } else if (SDL_strcasecmp(hint, "GameChat") == 0) { + audioProps.a.eCategory = AudioCategory_GameChat; + } else if (SDL_strcasecmp(hint, "Movie") == 0) { + audioProps.a.eCategory = AudioCategory_Movie; + } else if (SDL_strcasecmp(hint, "Media") == 0) { + audioProps.a.eCategory = AudioCategory_Media; + } + } +#endif + + if (WIN_IsWindows81OrGreater() && + SDL_GetHintBoolean(SDL_HINT_AUDIO_DEVICE_RAW_STREAM, false)) { + audioProps.s.Options = SDL_AUDCLNT_STREAMOPTIONS_RAW; + } else { + audioProps.a.cbSize = sizeof (AudioClientProperties); + } + + ret = IAudioClient2_SetClientProperties(client2, (AudioClientProperties *)&audioProps); + if (FAILED(ret)) { + // This isn't fatal, let's log it instead of failing + SDL_LogWarn(SDL_LOG_CATEGORY_AUDIO, "IAudioClient2_SetClientProperties failed: 0x%lx", ret); + } + IAudioClient2_Release(client2); + } +#endif + #ifdef __IAudioClient3_INTERFACE_DEFINED__ // Try querying IAudioClient3 if sharemode is AUDCLNT_SHAREMODE_SHARED if (sharemode == AUDCLNT_SHAREMODE_SHARED) { @@ -952,6 +1004,7 @@ static bool WASAPI_Init(SDL_AudioDriverImpl *impl) impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle; impl->HasRecordingSupport = true; + supports_recording_on_playback_devices = SDL_GetHintBoolean(SDL_HINT_AUDIO_INCLUDE_MONITORS, false); return true; } diff --git a/libs/SDL3/src/audio/wasapi/SDL_wasapi.h b/libs/SDL3/src/audio/wasapi/SDL_wasapi.h index 5e528dc7..202a0cb7 100644 --- a/libs/SDL3/src/audio/wasapi/SDL_wasapi.h +++ b/libs/SDL3/src/audio/wasapi/SDL_wasapi.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -43,6 +43,7 @@ struct SDL_PrivateAudioData SDL_AtomicInt device_disconnecting; bool device_lost; bool device_dead; + bool isplayback; }; // win32 implementation calls into these. diff --git a/libs/SDL3/src/camera/SDL_camera.c b/libs/SDL3/src/camera/SDL_camera.c index 9f71cea0..323be59a 100644 --- a/libs/SDL3/src/camera/SDL_camera.c +++ b/libs/SDL3/src/camera/SDL_camera.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -69,11 +69,11 @@ int SDL_GetNumCameraDrivers(void) const char *SDL_GetCameraDriver(int index) { - if (index >= 0 && index < SDL_GetNumCameraDrivers()) { - return bootstrap[index]->name; + CHECK_PARAM(index < 0 || index >= SDL_GetNumCameraDrivers()) { + SDL_InvalidParamError("index"); + return NULL; } - SDL_InvalidParamError("index"); - return NULL; + return bootstrap[index]->name; } const char *SDL_GetCurrentCameraDriver(void) @@ -150,7 +150,7 @@ static size_t GetFrameBufLen(const SDL_CameraSpec *spec) return wxh * SDL_BYTESPERPIXEL(fmt); } -static SDL_CameraFrameResult ZombieAcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult ZombieAcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { const SDL_CameraSpec *spec = &device->actual_spec; @@ -229,10 +229,15 @@ static void ZombieReleaseFrame(SDL_Camera *device, SDL_Surface *frame) // Reclai // we just leave zombie_pixels alone, as we'll reuse it for every new frame until the camera is closed. } + +static void ObtainPhysicalCameraObj(SDL_Camera *device); +static void ReleaseCamera(SDL_Camera *device); + + static void ClosePhysicalCamera(SDL_Camera *device) { - if (!device) { - return; + if (!device || (device->hidden == NULL)) { + return; // device is not open. } SDL_SetAtomicInt(&device->shutdown, 1); @@ -244,6 +249,8 @@ static void ClosePhysicalCamera(SDL_Camera *device) device->thread = NULL; } + ObtainPhysicalCameraObj(device); + // release frames that are queued up somewhere... if (!device->needs_conversion && !device->needs_scaling) { for (SurfaceList *i = device->filled_output_surfaces.next; i != NULL; i = i->next) { @@ -255,6 +262,7 @@ static void ClosePhysicalCamera(SDL_Camera *device) } camera_driver.impl.CloseDevice(device); + device->hidden = NULL; // just in case backend didn't reset this. SDL_DestroyProperties(device->props); @@ -270,7 +278,7 @@ static void ClosePhysicalCamera(SDL_Camera *device) SDL_aligned_free(device->zombie_pixels); - device->permission = 0; + device->permission = SDL_CAMERA_PERMISSION_STATE_PENDING; device->zombie_pixels = NULL; device->filled_output_surfaces.next = NULL; device->empty_output_surfaces.next = NULL; @@ -280,13 +288,16 @@ static void ClosePhysicalCamera(SDL_Camera *device) device->adjust_timestamp = 0; SDL_zero(device->spec); + UnrefPhysicalCamera(device); // we're closed, release a reference. + + ReleaseCamera(device); } // Don't hold the device lock when calling this, as we may destroy the device! void UnrefPhysicalCamera(SDL_Camera *device) { if (SDL_AtomicDecRef(&device->refcount)) { - // take it out of the device list. + // take it out of the device list. This will call DestroyCameraHashItem to clean up the object. SDL_LockRWLockForWriting(camera_driver.device_hash_lock); if (SDL_RemoveFromHashTable(camera_driver.device_hash, (const void *) (uintptr_t) device->instance_id)) { SDL_AddAtomicInt(&camera_driver.device_count, -1); @@ -555,6 +566,8 @@ void SDL_CameraDisconnected(SDL_Camera *device) pending_tail->next = p; pending_tail = p; } + + UnrefPhysicalCamera(device); // camera is disconnected, drop its reference } ReleaseCamera(device); @@ -581,7 +594,7 @@ void SDL_CameraPermissionOutcome(SDL_Camera *device, bool approved) pending.next = NULL; SDL_PendingCameraEvent *pending_tail = &pending; - const int permission = approved ? 1 : -1; + const SDL_CameraPermissionState permission = approved ? SDL_CAMERA_PERMISSION_STATE_APPROVED : SDL_CAMERA_PERMISSION_STATE_DENIED; ObtainPhysicalCameraObj(device); if (device->permission != permission) { @@ -657,15 +670,16 @@ bool SDL_GetCameraFormat(SDL_Camera *camera, SDL_CameraSpec *spec) { bool result; - if (!camera) { + CHECK_PARAM(!camera) { return SDL_InvalidParamError("camera"); - } else if (!spec) { + } + CHECK_PARAM(!spec) { return SDL_InvalidParamError("spec"); } SDL_Camera *device = camera; // currently there's no separation between physical and logical device. ObtainPhysicalCameraObj(device); - if (device->permission > 0) { + if (device->permission > SDL_CAMERA_PERMISSION_STATE_PENDING) { SDL_copyp(spec, &device->spec); result = true; } else { @@ -808,9 +822,9 @@ bool SDL_CameraThreadIterate(SDL_Camera *device) } const int permission = device->permission; - if (permission <= 0) { + if (permission <= SDL_CAMERA_PERMISSION_STATE_PENDING) { SDL_UnlockMutex(device->lock); - return (permission < 0) ? false : true; // if permission was denied, shut it down. if undecided, we're done for now. + return (permission < SDL_CAMERA_PERMISSION_STATE_PENDING) ? false : true; // if permission was denied, shut it down. if undecided, we're done for now. } bool failed = false; // set to true if disaster worthy of treating the device as lost has happened. @@ -818,9 +832,10 @@ bool SDL_CameraThreadIterate(SDL_Camera *device) SDL_Surface *output_surface = NULL; SurfaceList *slist = NULL; Uint64 timestampNS = 0; + float rotation = 0.0f; // AcquireFrame SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice instead! - const SDL_CameraFrameResult rc = device->AcquireFrame(device, device->acquire_surface, ×tampNS); + const SDL_CameraFrameResult rc = device->AcquireFrame(device, device->acquire_surface, ×tampNS, &rotation); if (rc == SDL_CAMERA_FRAME_READY) { // new frame acquired! #if DEBUG_CAMERA @@ -914,6 +929,8 @@ bool SDL_CameraThreadIterate(SDL_Camera *device) acquired->pixels = NULL; acquired->pitch = 0; + SDL_SetFloatProperty(SDL_GetSurfaceProperties(output_surface), SDL_PROP_SURFACE_ROTATION_FLOAT, rotation); + // make the filled output surface available to the app. SDL_LockMutex(device->lock); slist->next = device->filled_output_surfaces.next; @@ -940,6 +957,8 @@ static int SDLCALL CameraThread(void *devicep) SDL_Log("CAMERA: dev[%p] Start thread 'CameraThread'", devicep); #endif + RefPhysicalCamera(device); // this thread holds a reference. + SDL_assert(device != NULL); SDL_CameraThreadSetup(device); @@ -951,6 +970,8 @@ static int SDLCALL CameraThread(void *devicep) SDL_CameraThreadShutdown(device); + UnrefPhysicalCamera(device); // this thread no longer holds a reference. + #if DEBUG_CAMERA SDL_Log("CAMERA: dev[%p] End thread 'CameraThread'", devicep); #endif @@ -1075,7 +1096,6 @@ static void ChooseBestCameraSpec(SDL_Camera *device, const SDL_CameraSpec *spec, // that. SDL_zerop(closest); - SDL_assert(((Uint32) SDL_PIXELFORMAT_UNKNOWN) == 0); // since we SDL_zerop'd to this value. if (device->num_specs == 0) { // device listed no specs! You get whatever you want! if (spec) { @@ -1244,6 +1264,8 @@ SDL_Camera *SDL_OpenCamera(SDL_CameraID instance_id, const SDL_CameraSpec *spec) } } + RefPhysicalCamera(device); // we're open, hold a reference. + ReleaseCamera(device); // unlock, we're good to go! return device; // currently there's no separation between physical and logical device. @@ -1255,7 +1277,7 @@ SDL_Surface *SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS) *timestampNS = 0; } - if (!camera) { + CHECK_PARAM(!camera) { SDL_InvalidParamError("camera"); return NULL; } @@ -1264,7 +1286,7 @@ SDL_Surface *SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS) ObtainPhysicalCameraObj(device); - if (device->permission <= 0) { + if (device->permission <= SDL_CAMERA_PERMISSION_STATE_PENDING) { ReleaseCamera(device); SDL_SetError("Camera permission has not been granted"); return NULL; @@ -1340,49 +1362,55 @@ void SDL_ReleaseCameraFrame(SDL_Camera *camera, SDL_Surface *frame) SDL_CameraID SDL_GetCameraID(SDL_Camera *camera) { - SDL_CameraID result = 0; - if (!camera) { + SDL_CameraID result; + + CHECK_PARAM(!camera) { SDL_InvalidParamError("camera"); - } else { - SDL_Camera *device = camera; // currently there's no separation between physical and logical device. - ObtainPhysicalCameraObj(device); - result = device->instance_id; - ReleaseCamera(device); + return 0; } + SDL_Camera *device = camera; // currently there's no separation between physical and logical device. + ObtainPhysicalCameraObj(device); + result = device->instance_id; + ReleaseCamera(device); + return result; } SDL_PropertiesID SDL_GetCameraProperties(SDL_Camera *camera) { - SDL_PropertiesID result = 0; - if (!camera) { + SDL_PropertiesID result; + + CHECK_PARAM(!camera) { SDL_InvalidParamError("camera"); - } else { - SDL_Camera *device = camera; // currently there's no separation between physical and logical device. - ObtainPhysicalCameraObj(device); - if (device->props == 0) { - device->props = SDL_CreateProperties(); - } - result = device->props; - ReleaseCamera(device); + return 0; } + SDL_Camera *device = camera; // currently there's no separation between physical and logical device. + ObtainPhysicalCameraObj(device); + if (device->props == 0) { + device->props = SDL_CreateProperties(); + } + result = device->props; + ReleaseCamera(device); + return result; } -int SDL_GetCameraPermissionState(SDL_Camera *camera) +SDL_CameraPermissionState SDL_GetCameraPermissionState(SDL_Camera *camera) { - int result; - if (!camera) { + SDL_CameraPermissionState result; + + CHECK_PARAM(!camera) { SDL_InvalidParamError("camera"); - result = -1; - } else { - SDL_Camera *device = camera; // currently there's no separation between physical and logical device. - ObtainPhysicalCameraObj(device); - result = device->permission; - ReleaseCamera(device); + return SDL_CAMERA_PERMISSION_STATE_DENIED; } + + SDL_Camera *device = camera; // currently there's no separation between physical and logical device. + ObtainPhysicalCameraObj(device); + result = device->permission; + ReleaseCamera(device); + return result; } @@ -1436,6 +1464,11 @@ void SDL_QuitCamera(void) static void SDLCALL DestroyCameraHashItem(void *userdata, const void *key, const void *value) { SDL_Camera *device = (SDL_Camera *) value; + + #if DEBUG_CAMERA + SDL_Log("DESTROYING CAMERA '%s'", device->name); + #endif + ClosePhysicalCamera(device); camera_driver.impl.FreeDeviceHandle(device); SDL_DestroyMutex(device->lock); @@ -1524,7 +1557,9 @@ bool SDL_CameraInit(const char *driver_name) } } - if (!initialized) { + if (initialized) { + SDL_DebugLogBackend("camera", camera_driver.name); + } else { // specific drivers will set the error message if they fail, but otherwise we do it here. if (!tried_to_init) { if (driver_name) { diff --git a/libs/SDL3/src/camera/SDL_camera_c.h b/libs/SDL3/src/camera/SDL_camera_c.h index 316ae7d7..3d1f21aa 100644 --- a/libs/SDL3/src/camera/SDL_camera_c.h +++ b/libs/SDL3/src/camera/SDL_camera_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/camera/SDL_syscamera.h b/libs/SDL3/src/camera/SDL_syscamera.h index 30a02f39..40c4ae9f 100644 --- a/libs/SDL3/src/camera/SDL_syscamera.h +++ b/libs/SDL3/src/camera/SDL_syscamera.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -99,7 +99,7 @@ struct SDL_Camera // These are, initially, set from camera_driver, but we might swap them out with Zombie versions on disconnect/failure. bool (*WaitDevice)(SDL_Camera *device); - SDL_CameraFrameResult (*AcquireFrame)(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS); + SDL_CameraFrameResult (*AcquireFrame)(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation); void (*ReleaseFrame)(SDL_Camera *device, SDL_Surface *frame); // All supported formats/dimensions for this device. @@ -160,20 +160,25 @@ struct SDL_Camera // Optional properties. SDL_PropertiesID props; - // -1: user denied permission, 0: waiting for user response, 1: user approved permission. - int permission; + // Current state of user permission check. + SDL_CameraPermissionState permission; // Data private to this driver, used when device is opened and running. struct SDL_PrivateCameraData *hidden; }; + +// Note that for AcquireFrame, `rotation` is degrees, with positive values rotating clockwise. This is the amount to rotate an image so it would be right-side up. +// Rotations should be in 90 degree increments at this time (landscape to portrait, or upside down to right side up, etc). +// Most platforms won't care about this, but mobile devices might need to deal with the device itself being physically rotated, causing the fixed-orientation camera to be presenting sideways images. + typedef struct SDL_CameraDriverImpl { void (*DetectDevices)(void); bool (*OpenDevice)(SDL_Camera *device, const SDL_CameraSpec *spec); void (*CloseDevice)(SDL_Camera *device); bool (*WaitDevice)(SDL_Camera *device); - SDL_CameraFrameResult (*AcquireFrame)(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS); // set frame->pixels, frame->pitch, and *timestampNS! + SDL_CameraFrameResult (*AcquireFrame)(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation); // set frame->pixels, frame->pitch, *timestampNS, and *rotation! void (*ReleaseFrame)(SDL_Camera *device, SDL_Surface *frame); // Reclaim frame->pixels and frame->pitch! void (*FreeDeviceHandle)(SDL_Camera *device); // SDL is done with this device; free the handle from SDL_AddCamera() void (*Deinitialize)(void); diff --git a/libs/SDL3/src/camera/android/SDL_camera_android.c b/libs/SDL3/src/camera/android/SDL_camera_android.c index 54b539a1..ab6fb9da 100644 --- a/libs/SDL3/src/camera/android/SDL_camera_android.c +++ b/libs/SDL3/src/camera/android/SDL_camera_android.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -149,6 +149,7 @@ struct SDL_PrivateCameraData ACaptureRequest *request; ACameraCaptureSession *session; SDL_CameraSpec requested_spec; + int rotation; // degrees to rotate clockwise to get from camera's static orientation to device's native orientation. Apply this plus current phone rotation to get upright image! }; static bool SetErrorStr(const char *what, const char *errstr, const int rc) @@ -295,7 +296,7 @@ static bool ANDROIDCAMERA_WaitDevice(SDL_Camera *device) return true; // this isn't used atm, since we run our own thread via onImageAvailable callbacks. } -static SDL_CameraFrameResult ANDROIDCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult ANDROIDCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY; media_status_t res; @@ -366,6 +367,21 @@ static SDL_CameraFrameResult ANDROIDCAMERA_AcquireFrame(SDL_Camera *device, SDL_ pAImage_delete(image); + int dev_rotation = 0; + switch (Android_JNI_GetDisplayCurrentOrientation()) { + case SDL_ORIENTATION_PORTRAIT: dev_rotation = 0; break; + case SDL_ORIENTATION_LANDSCAPE: dev_rotation = 90; break; + case SDL_ORIENTATION_PORTRAIT_FLIPPED: dev_rotation = 180; break; + case SDL_ORIENTATION_LANDSCAPE_FLIPPED: dev_rotation = 270; break; + default: SDL_assert(!"Unexpected device rotation!"); dev_rotation = 0; break; + } + + if (device->position == SDL_CAMERA_POSITION_BACK_FACING) { + dev_rotation = -dev_rotation; // we want to subtract this value, instead of add, if back-facing. + } + + *rotation = (float) (dev_rotation + device->hidden->rotation); // current phone orientation, static camera orientation in relation to phone. + return result; } @@ -494,10 +510,23 @@ static bool PrepareCamera(SDL_Camera *device) imglistener.context = device; imglistener.onImageAvailable = onImageAvailable; + + const char *devid = (const char *) device->handle; + + device->hidden->rotation = 0; + ACameraMetadata *metadata = NULL; + ACameraMetadata_const_entry orientationentry; + if (pACameraManager_getCameraCharacteristics(cameraMgr, devid, &metadata) == ACAMERA_OK) { + if (pACameraMetadata_getConstEntry(metadata, ACAMERA_SENSOR_ORIENTATION, &orientationentry) == ACAMERA_OK) { + device->hidden->rotation = (int) (*orientationentry.data.i32 % 360); + } + pACameraMetadata_free(metadata); + } + // just in case SDL_OpenCamera is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy. const SDL_CameraSpec *spec = &device->hidden->requested_spec; - if ((res = pACameraManager_openCamera(cameraMgr, (const char *) device->handle, &dev_callbacks, &device->hidden->device)) != ACAMERA_OK) { + if ((res = pACameraManager_openCamera(cameraMgr, devid, &dev_callbacks, &device->hidden->device)) != ACAMERA_OK) { return SetCameraError("Failed to open camera", res); } else if ((res2 = pAImageReader_new(spec->width, spec->height, format_sdl_to_android(spec->format), 10 /* nb buffers */, &device->hidden->reader)) != AMEDIA_OK) { return SetMediaError("Error AImageReader_new", res2); @@ -729,7 +758,7 @@ static void onCameraAvailable(void *context, const char *cameraId) static void onCameraUnavailable(void *context, const char *cameraId) { #if DEBUG_CAMERA - SDL_Log("CAMERA: CB onCameraUnvailable('%s')", cameraId); + SDL_Log("CAMERA: CB onCameraUnavailable('%s')", cameraId); #endif SDL_assert(cameraId != NULL); @@ -814,7 +843,7 @@ static void ANDROIDCAMERA_Deinitialize(void) static bool ANDROIDCAMERA_Init(SDL_CameraDriverImpl *impl) { // !!! FIXME: slide this off into a subroutine - // system libraries are in android-24 and later; we currently target android-16 and later, so check if they exist at runtime. + // system libraries are in android-24 and later; we currently target android-21 and later, so check if they exist at runtime. void *libcamera2 = dlopen("libcamera2ndk.so", RTLD_NOW | RTLD_LOCAL); if (!libcamera2) { SDL_Log("CAMERA: libcamera2ndk.so can't be loaded: %s", dlerror()); diff --git a/libs/SDL3/src/camera/coremedia/SDL_camera_coremedia.m b/libs/SDL3/src/camera/coremedia/SDL_camera_coremedia.m index f58fb927..ef4d9a93 100644 --- a/libs/SDL3/src/camera/coremedia/SDL_camera_coremedia.m +++ b/libs/SDL3/src/camera/coremedia/SDL_camera_coremedia.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -29,6 +29,14 @@ #import #import +#if defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_TVOS) +#define USE_UIKIT_DEVICE_ROTATION +#endif + +#ifdef USE_UIKIT_DEVICE_ROTATION +#import +#endif + /* * Need to link with:: CoreMedia CoreVideo * @@ -77,6 +85,9 @@ static void CoreMediaFormatToSDL(FourCharCode fmt, SDL_PixelFormat *pixel_format @property(nonatomic, retain) AVCaptureSession *session; @property(nonatomic, retain) SDLCaptureVideoDataOutputSampleBufferDelegate *delegate; @property(nonatomic, assign) CMSampleBufferRef current_sample; +#ifdef USE_UIKIT_DEVICE_ROTATION +@property(nonatomic, assign) UIDeviceOrientation last_device_orientation; +#endif @end @implementation SDLPrivateCameraData @@ -85,7 +96,7 @@ static void CoreMediaFormatToSDL(FourCharCode fmt, SDL_PixelFormat *pixel_format static bool CheckCameraPermissions(SDL_Camera *device) { - if (device->permission == 0) { // still expecting a permission result. + if (device->permission == SDL_CAMERA_PERMISSION_STATE_PENDING) { // still expecting a permission result. if (@available(macOS 14, *)) { const AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; if (status != AVAuthorizationStatusNotDetermined) { // NotDetermined == still waiting for an answer from the user. @@ -96,7 +107,7 @@ static bool CheckCameraPermissions(SDL_Camera *device) } } - return (device->permission > 0); + return (device->permission > SDL_CAMERA_PERMISSION_STATE_PENDING); } // this delegate just receives new video frames on a Grand Central Dispatch queue, and fires off the @@ -146,7 +157,7 @@ static bool COREMEDIA_WaitDevice(SDL_Camera *device) return true; // this isn't used atm, since we run our own thread out of Grand Central Dispatch. } -static SDL_CameraFrameResult COREMEDIA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult COREMEDIA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY; SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden; @@ -219,6 +230,37 @@ static SDL_CameraFrameResult COREMEDIA_AcquireFrame(SDL_Camera *device, SDL_Surf CVPixelBufferUnlockBaseAddress(image, 0); + #ifdef USE_UIKIT_DEVICE_ROTATION + UIDeviceOrientation device_orientation = [[UIDevice currentDevice] orientation]; + if (!UIDeviceOrientationIsValidInterfaceOrientation(device_orientation)) { + device_orientation = hidden.last_device_orientation; // possible the phone is laying flat or something went wrong, just stay with the last known-good orientation. + } else { + hidden.last_device_orientation = device_orientation; // update the last known-good orientation for later. + } + + const UIInterfaceOrientation ui_orientation = [UIApplication sharedApplication].statusBarOrientation; + + // there is probably math for this, but this is easy to slap into a table. + // rotation = rotations[uiorientation-1][devorientation-1]; + if (device->position == SDL_CAMERA_POSITION_BACK_FACING) { + static const Uint16 back_rotations[4][4] = { + { 90, 90, 90, 90 }, // ui portrait + { 270, 270, 270, 270 }, // ui portrait upside down + { 0, 0, 0, 0 }, // ui landscape left + { 180, 180, 180, 180 } // ui landscape right + }; + *rotation = (float) back_rotations[ui_orientation - 1][device_orientation - 1]; + } else { + static const Uint16 front_rotations[4][4] = { + { 90, 90, 270, 270 }, // ui portrait + { 270, 270, 90, 90 }, // ui portrait upside down + { 0, 0, 180, 180 }, // ui landscape left + { 180, 180, 0, 0 } // ui landscape right + }; + *rotation = (float) front_rotations[ui_orientation - 1][device_orientation - 1]; + } + #endif + return result; } @@ -231,6 +273,10 @@ static void COREMEDIA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame) static void COREMEDIA_CloseDevice(SDL_Camera *device) { if (device && device->hidden) { + #ifdef USE_UIKIT_DEVICE_ROTATION + [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; + #endif + SDLPrivateCameraData *hidden = (SDLPrivateCameraData *) CFBridgingRelease(device->hidden); device->hidden = NULL; @@ -274,10 +320,37 @@ static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) const float FRAMERATE_EPSILON = 0.01f; for (AVFrameRateRange *framerate in format.videoSupportedFrameRateRanges) { - if (rate > (framerate.minFrameRate - FRAMERATE_EPSILON) && - rate < (framerate.maxFrameRate + FRAMERATE_EPSILON)) { - spec_format = format; - break; + // Check if the requested rate is within the supported range + if (rate >= (framerate.minFrameRate - FRAMERATE_EPSILON) && + rate <= (framerate.maxFrameRate + FRAMERATE_EPSILON)) { + + // Prefer formats with narrower frame rate ranges that are closer to our target + // This helps avoid formats that support a wide range (like 10-60 FPS) + // when we want a specific rate (like 30 FPS) + bool should_select = false; + if (spec_format == nil) { + should_select = true; + } else { + AVFrameRateRange *current_range = spec_format.videoSupportedFrameRateRanges.firstObject; + float current_range_width = current_range.maxFrameRate - current_range.minFrameRate; + float new_range_width = framerate.maxFrameRate - framerate.minFrameRate; + + // Prefer formats with narrower ranges, or if ranges are similar, prefer closer to target + if (new_range_width < current_range_width) { + should_select = true; + } else if (SDL_fabsf(new_range_width - current_range_width) < 0.1f) { + // Similar range width, prefer the one closer to our target rate + float current_distance = SDL_fabsf(rate - current_range.minFrameRate); + float new_distance = SDL_fabsf(rate - framerate.minFrameRate); + if (new_distance < current_distance) { + should_select = true; + } + } + } + + if (should_select) { + spec_format = format; + } } } @@ -293,6 +366,21 @@ static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) } avdevice.activeFormat = spec_format; + + // Try to set the frame duration to enforce the requested frame rate + const CMTime frameDuration = CMTimeMake(spec->framerate_denominator, spec->framerate_numerator); + + // Check if the device supports setting frame duration + if ([avdevice respondsToSelector:@selector(setActiveVideoMinFrameDuration:)] && + [avdevice respondsToSelector:@selector(setActiveVideoMaxFrameDuration:)]) { + @try { + avdevice.activeVideoMinFrameDuration = frameDuration; + avdevice.activeVideoMaxFrameDuration = frameDuration; + } @catch (NSException *exception) { + // Some devices don't support setting frame duration, that's okay + } + } + [avdevice unlockForConfiguration]; AVCaptureSession *session = [[AVCaptureSession alloc] init]; @@ -348,6 +436,17 @@ static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) } [session addOutput:output]; + // Try to set the frame rate on the device (preferred modern approach) + if ([avdevice lockForConfiguration:nil]) { + @try { + avdevice.activeVideoMinFrameDuration = frameDuration; + avdevice.activeVideoMaxFrameDuration = frameDuration; + } @catch (NSException *exception) { + // Some devices don't support setting frame duration, that's okay + } + [avdevice unlockForConfiguration]; + } + [session commitConfiguration]; SDLPrivateCameraData *hidden = [[SDLPrivateCameraData alloc] init]; @@ -358,6 +457,28 @@ static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) hidden.session = session; hidden.delegate = delegate; hidden.current_sample = NULL; + + #ifdef USE_UIKIT_DEVICE_ROTATION + // When using a camera, we turn on device orientation tracking. The docs note that this turns on + // the device's accelerometer, so I assume this burns power, so we don't leave this running all + // the time. These calls nest, so we just need to call the matching `end` message when we close. + // You _can_ get an actual events through this mechanism, but we just want to be able to call + // -[UIDevice orientation], which will update with real info while notifications are enabled. + UIDevice *uidevice = [UIDevice currentDevice]; + [uidevice beginGeneratingDeviceOrientationNotifications]; + hidden.last_device_orientation = uidevice.orientation; + if (!UIDeviceOrientationIsValidInterfaceOrientation(hidden.last_device_orientation)) { + // accelerometer isn't ready yet or the phone is laying flat or something. Just try to guess from how the UI is oriented at the moment. + switch ([UIApplication sharedApplication].statusBarOrientation) { + case UIInterfaceOrientationPortrait: hidden.last_device_orientation = UIDeviceOrientationPortrait; break; + case UIInterfaceOrientationPortraitUpsideDown: hidden.last_device_orientation = UIDeviceOrientationPortraitUpsideDown; break; + case UIInterfaceOrientationLandscapeLeft: hidden.last_device_orientation = UIDeviceOrientationLandscapeRight; break; // Apple docs say UI and device orientations are reversed in landscape. + case UIInterfaceOrientationLandscapeRight: hidden.last_device_orientation = UIDeviceOrientationLandscapeLeft; break; + default: hidden.last_device_orientation = UIDeviceOrientationPortrait; break; // oh well. + } + } + #endif + device->hidden = (struct SDL_PrivateCameraData *)CFBridgingRetain(hidden); [session startRunning]; // !!! FIXME: docs say this can block while camera warms up and shouldn't be done on main thread. Maybe push through `queue`? diff --git a/libs/SDL3/src/camera/dummy/SDL_camera_dummy.c b/libs/SDL3/src/camera/dummy/SDL_camera_dummy.c index b2a4dc13..30ccffa0 100644 --- a/libs/SDL3/src/camera/dummy/SDL_camera_dummy.c +++ b/libs/SDL3/src/camera/dummy/SDL_camera_dummy.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -38,7 +38,7 @@ static bool DUMMYCAMERA_WaitDevice(SDL_Camera *device) return SDL_Unsupported(); } -static SDL_CameraFrameResult DUMMYCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult DUMMYCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { SDL_Unsupported(); return SDL_CAMERA_FRAME_ERROR; diff --git a/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c b/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c index 36418c57..85ff587d 100644 --- a/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c +++ b/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -33,15 +33,13 @@ // each EM_ASM section is ugly. /* *INDENT-OFF* */ // clang-format off -EM_JS_DEPS(sdlcamera, "$dynCall"); - static bool EMSCRIPTENCAMERA_WaitDevice(SDL_Camera *device) { SDL_assert(!"This shouldn't be called"); // we aren't using SDL's internal thread. return false; } -static SDL_CameraFrameResult EMSCRIPTENCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult EMSCRIPTENCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { void *rgba = SDL_malloc(device->actual_spec.width * device->actual_spec.height * 4); if (!rgba) { @@ -98,7 +96,7 @@ static void EMSCRIPTENCAMERA_CloseDevice(SDL_Camera *device) } } -static int SDLEmscriptenCameraPermissionOutcome(SDL_Camera *device, int approved, int w, int h, int fps) +EMSCRIPTEN_KEEPALIVE int SDLEmscriptenCameraPermissionOutcome(SDL_Camera *device, int approved, int w, int h, int fps) { if (approved) { device->actual_spec.format = SDL_PIXELFORMAT_RGBA32; @@ -118,6 +116,10 @@ static int SDLEmscriptenCameraPermissionOutcome(SDL_Camera *device, int approved return approved; } +EMSCRIPTEN_KEEPALIVE bool SDLEmscriptenThreadIterate(SDL_Camera *device) { + return SDL_CameraThreadIterate(device); +} + static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) { MAIN_THREAD_EM_ASM({ @@ -128,8 +130,8 @@ static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec const h = $2; const framerate_numerator = $3; const framerate_denominator = $4; - const outcome = $5; - const iterate = $6; + const outcome = Module._SDLEmscriptenCameraPermissionOutcome; + const iterate = Module._SDLEmscriptenThreadIterate; const constraints = {}; if ((w <= 0) || (h <= 0)) { @@ -155,7 +157,7 @@ static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec const nextframems = SDL3.camera.next_frame_time; const now = performance.now(); if (now >= nextframems) { - dynCall('vi', iterate, [device]); // calls SDL_CameraThreadIterate, which will call our AcquireFrame implementation. + iterate(device); // calls SDL_CameraThreadIterate, which will call our AcquireFrame implementation. // bump ahead but try to stay consistent on timing, in case we dropped frames. while (SDL3.camera.next_frame_time < now) { @@ -174,7 +176,7 @@ static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec const actualfps = settings.frameRate; console.log("Camera is opened! Actual spec: (" + actualw + "x" + actualh + "), fps=" + actualfps); - if (dynCall('iiiiii', outcome, [device, 1, actualw, actualh, actualfps])) { + if (outcome(device, 1, actualw, actualh, actualfps)) { const video = document.createElement("video"); video.width = actualw; video.height = actualh; @@ -207,9 +209,9 @@ static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec }) .catch((err) => { console.error("Tried to open camera but it threw an error! " + err.name + ": " + err.message); - dynCall('iiiiii', outcome, [device, 0, 0, 0, 0]); // we call this a permission error, because it probably is. + outcome(device, 0, 0, 0, 0); // we call this a permission error, because it probably is. }); - }, device, spec->width, spec->height, spec->framerate_numerator, spec->framerate_denominator, SDLEmscriptenCameraPermissionOutcome, SDL_CameraThreadIterate); + }, device, spec->width, spec->height, spec->framerate_numerator, spec->framerate_denominator); return true; // the real work waits until the user approves a camera. } @@ -245,9 +247,6 @@ static void EMSCRIPTENCAMERA_DetectDevices(void) static bool EMSCRIPTENCAMERA_Init(SDL_CameraDriverImpl *impl) { MAIN_THREAD_EM_ASM({ - if (typeof(Module['SDL3']) === 'undefined') { - Module['SDL3'] = {}; - } Module['SDL3'].camera = {}; }); diff --git a/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c b/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c index 433be4d8..f04b77b5 100644 --- a/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c +++ b/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -324,18 +324,18 @@ static const GUID *SDLFmtToMFVidFmtGuid(SDL_PixelFormat format) // mf.dll ... static HMODULE libmf = NULL; -typedef HRESULT(WINAPI *pfnMFEnumDeviceSources)(IMFAttributes *,IMFActivate ***,UINT32 *); -typedef HRESULT(WINAPI *pfnMFCreateDeviceSource)(IMFAttributes *, IMFMediaSource **); +typedef HRESULT (WINAPI *pfnMFEnumDeviceSources)(IMFAttributes *,IMFActivate ***,UINT32 *); +typedef HRESULT (WINAPI *pfnMFCreateDeviceSource)(IMFAttributes *, IMFMediaSource **); static pfnMFEnumDeviceSources pMFEnumDeviceSources = NULL; static pfnMFCreateDeviceSource pMFCreateDeviceSource = NULL; // mfplat.dll ... static HMODULE libmfplat = NULL; -typedef HRESULT(WINAPI *pfnMFStartup)(ULONG, DWORD); -typedef HRESULT(WINAPI *pfnMFShutdown)(void); -typedef HRESULT(WINAPI *pfnMFCreateAttributes)(IMFAttributes **, UINT32); -typedef HRESULT(WINAPI *pfnMFCreateMediaType)(IMFMediaType **); -typedef HRESULT(WINAPI *pfnMFGetStrideForBitmapInfoHeader)(DWORD, DWORD, LONG *); +typedef HRESULT (WINAPI *pfnMFStartup)(ULONG, DWORD); +typedef HRESULT (WINAPI *pfnMFShutdown)(void); +typedef HRESULT (WINAPI *pfnMFCreateAttributes)(IMFAttributes **, UINT32); +typedef HRESULT (WINAPI *pfnMFCreateMediaType)(IMFMediaType **); +typedef HRESULT (WINAPI *pfnMFGetStrideForBitmapInfoHeader)(DWORD, DWORD, LONG *); static pfnMFStartup pMFStartup = NULL; static pfnMFShutdown pMFShutdown = NULL; @@ -345,7 +345,7 @@ static pfnMFGetStrideForBitmapInfoHeader pMFGetStrideForBitmapInfoHeader = NULL; // mfreadwrite.dll ... static HMODULE libmfreadwrite = NULL; -typedef HRESULT(WINAPI *pfnMFCreateSourceReaderFromMediaSource)(IMFMediaSource *, IMFAttributes *, IMFSourceReader **); +typedef HRESULT (WINAPI *pfnMFCreateSourceReaderFromMediaSource)(IMFMediaSource *, IMFAttributes *, IMFSourceReader **); static pfnMFCreateSourceReaderFromMediaSource pMFCreateSourceReaderFromMediaSource = NULL; @@ -430,7 +430,7 @@ static void SDLCALL CleanupIMFMediaBuffer(void *userdata, void *value) SDL_free(objs); } -static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { SDL_assert(device->hidden->current_sample != NULL); @@ -562,7 +562,7 @@ static SDL_CameraFrameResult MEDIAFOUNDATION_CopyFrame(SDL_Surface *frame, const return SDL_CAMERA_FRAME_READY; } -static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { SDL_assert(device->hidden->current_sample != NULL); diff --git a/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c b/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c index 056ed4f7..909aefab 100644 --- a/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c +++ b/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga Copyright (C) 2024 Wim Taymans This software is provided 'as-is', without any express or implied @@ -109,6 +109,13 @@ static int (*PIPEWIRE_pw_properties_setf)(struct pw_properties *, const char *, #ifdef SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "camera-libpipewire", + "Support for camera through libpipewire", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC +) + static const char *pipewire_library = SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC; static SDL_SharedObject *pipewire_handle = NULL; @@ -323,7 +330,7 @@ static struct param *param_add(struct spa_list *params, id = SPA_POD_OBJECT_ID(param); } - p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0)); + p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0)); // This should NOT be SDL_malloc() if (p == NULL) return NULL; @@ -570,7 +577,7 @@ static bool PIPEWIRECAMERA_WaitDevice(SDL_Camera *device) return true; } -static SDL_CameraFrameResult PIPEWIRECAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult PIPEWIRECAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { struct pw_buffer *b; @@ -943,7 +950,7 @@ static void hotplug_registry_global_callback(void *object, uint32_t id, g->permissions = permissions; g->props = props ? PIPEWIRE_pw_properties_new_dict(props) : NULL; g->proxy = proxy; - g->name = strdup(name); + g->name = strdup(name); // This should NOT be SDL_strdup() spa_list_init(&g->pending_list); spa_list_init(&g->param_list); spa_list_append(&hotplug.global_list, &g->link); diff --git a/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c b/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c index 664d4418..938f5a3b 100644 --- a/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c +++ b/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -32,6 +32,9 @@ #include #include +#ifndef V4L2_PIX_FMT_RGBX32 +#define V4L2_PIX_FMT_RGBX32 v4l2_fourcc('X','B','2','4') +#endif #ifndef V4L2_CAP_DEVICE_CAPS // device_caps was added to struct v4l2_capability as of kernel 3.4. #define device_caps reserved[0] @@ -122,7 +125,7 @@ static bool V4L2_WaitDevice(SDL_Camera *device) return false; } -static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { const int fd = device->hidden->fd; const io_method io = device->hidden->io; @@ -418,6 +421,7 @@ static void format_v4l2_to_sdl(Uint32 fmt, SDL_PixelFormat *format, SDL_Colorspa #define CASE(x, y, z) case x: *format = y; *colorspace = z; return CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED); CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_SRGB); + CASE(V4L2_PIX_FMT_RGBX32, SDL_PIXELFORMAT_RGBX32, SDL_COLORSPACE_SRGB); #undef CASE default: #if DEBUG_CAMERA @@ -439,6 +443,7 @@ static Uint32 format_sdl_to_v4l2(SDL_PixelFormat fmt) #define CASE(y, x) case x: return y CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2); CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_MJPG); + CASE(V4L2_PIX_FMT_RGBX32, SDL_PIXELFORMAT_RGBX32); #undef CASE default: return 0; @@ -570,7 +575,7 @@ static bool V4L2_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (xioctl(fd, VIDIOC_G_PARM, &setfps) == 0) { if ( (setfps.parm.capture.timeperframe.denominator != spec->framerate_numerator) || - (setfps.parm.capture.timeperframe.numerator = spec->framerate_denominator) ) { + (setfps.parm.capture.timeperframe.numerator != spec->framerate_denominator) ) { setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; setfps.parm.capture.timeperframe.numerator = spec->framerate_denominator; setfps.parm.capture.timeperframe.denominator = spec->framerate_numerator; @@ -693,7 +698,7 @@ static bool AddCameraFormat(const int fd, CameraFormatAddData *data, SDL_PixelFo return false; // Probably out of memory; we'll go with what we have, if anything. } frmivalenum.index++; // set up for the next one. - } else if ((frmivalenum.type == V4L2_FRMIVAL_TYPE_STEPWISE) || (frmivalenum.type == V4L2_FRMIVAL_TYPE_CONTINUOUS)) { + } else if (frmivalenum.type == V4L2_FRMIVAL_TYPE_STEPWISE) { int d = frmivalenum.stepwise.min.denominator; // !!! FIXME: should we step by the numerator...? for (int n = (int) frmivalenum.stepwise.min.numerator; n <= (int) frmivalenum.stepwise.max.numerator; n += (int) frmivalenum.stepwise.step.numerator) { @@ -708,6 +713,50 @@ static bool AddCameraFormat(const int fd, CameraFormatAddData *data, SDL_PixelFo d += (int) frmivalenum.stepwise.step.denominator; } break; + } else if (frmivalenum.type == V4L2_FRMIVAL_TYPE_CONTINUOUS) { + // FIXME: The current API does not enable exposing continuous ranges, so for now let's expose some common values that are within the range + const int min_numer = frmivalenum.stepwise.min.numerator; + const int min_denom = frmivalenum.stepwise.min.denominator; + const int max_numer = frmivalenum.stepwise.max.numerator; + const int max_denom = frmivalenum.stepwise.max.denominator; + const float minrate = (float) min_numer / (float) min_denom; + const float maxrate = (float) max_numer / (float) max_denom; + if (minrate <= 1.001 / 24 && maxrate >= 1.001 / 24) { + if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, 24000, 1001)) { + return false; + } + } + if (minrate <= 1.000 / 24 && maxrate >= 1.000 / 24) { + if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, 24000, 1000)) { + return false; + } + } + if (minrate <= 1.001 / 30 && maxrate >= 1.001 / 30) { + if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, 30000, 1001)) { + return false; + } + } + if (minrate <= 1.000 / 30 && maxrate >= 1.000 / 30) { + if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, 30000, 1000)) { + return false; + } + } + if (minrate <= 1.000 / 50 && maxrate >= 1.000 / 50) { + if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, 50000, 1000)) { + return false; + } + } + if (minrate <= 1.001 / 60 && maxrate >= 1.001 / 60) { + if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, 60000, 1001)) { + return false; + } + } + if (minrate <= 1.000 / 60 && maxrate >= 1.000 / 60) { + if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, 60000, 1000)) { + return false; + } + } + break; } } @@ -796,7 +845,7 @@ static void MaybeAddDevice(const char *path) const int stepw = (int) frmsizeenum.stepwise.step_width; const int steph = (int) frmsizeenum.stepwise.step_height; for (int w = minw; w <= maxw; w += stepw) { - for (int h = minh; w <= maxh; w += steph) { + for (int h = minh; h <= maxh; h += steph) { #if DEBUG_CAMERA SDL_Log("CAMERA: * Has %s size %dx%d", (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) ? "stepwise" : "continuous", w, h); #endif diff --git a/libs/SDL3/src/camera/vita/SDL_camera_vita.c b/libs/SDL3/src/camera/vita/SDL_camera_vita.c index 874c6d8d..36b75b52 100644 --- a/libs/SDL3/src/camera/vita/SDL_camera_vita.c +++ b/libs/SDL3/src/camera/vita/SDL_camera_vita.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -139,7 +139,7 @@ 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); @@ -190,7 +190,7 @@ static bool VITACAMERA_WaitDevice(SDL_Camera *device) return true; } -static SDL_CameraFrameResult VITACAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) +static SDL_CameraFrameResult VITACAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) { SceCameraRead read = {0}; read.size = sizeof(SceCameraRead); diff --git a/libs/SDL3/src/core/SDL_core_unsupported.c b/libs/SDL3/src/core/SDL_core_unsupported.c index 9ace0e0b..5a70b5b4 100644 --- a/libs/SDL3/src/core/SDL_core_unsupported.c +++ b/libs/SDL3/src/core/SDL_core_unsupported.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -20,18 +20,18 @@ */ #include "SDL_internal.h" +#include "SDL_core_unsupported.h" + #ifndef SDL_VIDEO_DRIVER_X11 -SDL_DECLSPEC void SDLCALL SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata); void SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata) { } -#endif +#endif /* !SDL_VIDEO_DRIVER_X11 */ #ifndef SDL_PLATFORM_LINUX -SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriority(Sint64 threadID, int priority); bool SDL_SetLinuxThreadPriority(Sint64 threadID, int priority) { (void)threadID; @@ -39,7 +39,6 @@ bool SDL_SetLinuxThreadPriority(Sint64 threadID, int priority) return SDL_Unsupported(); } -SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy); bool SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy) { (void)threadID; @@ -48,37 +47,32 @@ bool SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int s return SDL_Unsupported(); } -#endif +#endif /* !SDL_PLATFORM_LINUX */ #ifndef SDL_PLATFORM_GDK -SDL_DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void); void SDL_GDKSuspendComplete(void) { SDL_Unsupported(); } -SDL_DECLSPEC bool SDLCALL SDL_GetGDKDefaultUser(void *outUserHandle); /* XUserHandle *outUserHandle */ -bool SDL_GetGDKDefaultUser(void *outUserHandle) +bool SDL_GetGDKDefaultUser(XUserHandle *outUserHandle) { return SDL_Unsupported(); } -SDL_DECLSPEC void SDLCALL SDL_GDKSuspendGPU(SDL_GPUDevice *device); void SDL_GDKSuspendGPU(SDL_GPUDevice *device) { } -SDL_DECLSPEC void SDLCALL SDL_GDKResumeGPU(SDL_GPUDevice *device); void SDL_GDKResumeGPU(SDL_GPUDevice *device) { } -#endif +#endif /* !SDL_PLATFORM_GDK */ #if !defined(SDL_PLATFORM_WINDOWS) -SDL_DECLSPEC bool SDLCALL SDL_RegisterApp(const char *name, Uint32 style, void *hInst); bool SDL_RegisterApp(const char *name, Uint32 style, void *hInst) { (void)name; @@ -87,7 +81,6 @@ bool SDL_RegisterApp(const char *name, Uint32 style, void *hInst) return SDL_Unsupported(); } -SDL_DECLSPEC void SDLCALL SDL_SetWindowsMessageHook(void *callback, void *userdata); // SDL_WindowsMessageHook callback void SDL_SetWindowsMessageHook(void *callback, void *userdata) { (void)callback; @@ -95,30 +88,26 @@ void SDL_SetWindowsMessageHook(void *callback, void *userdata) SDL_Unsupported(); } -SDL_DECLSPEC void SDLCALL SDL_UnregisterApp(void); void SDL_UnregisterApp(void) { SDL_Unsupported(); } -#endif +#endif /* !SDL_PLATFORM_WINDOWS */ #ifndef SDL_PLATFORM_ANDROID -SDL_DECLSPEC void SDLCALL SDL_SendAndroidBackButton(void); void SDL_SendAndroidBackButton(void) { SDL_Unsupported(); } -SDL_DECLSPEC void * SDLCALL SDL_GetAndroidActivity(void); void *SDL_GetAndroidActivity(void) { SDL_Unsupported(); return NULL; } -SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidCachePath(void); const char *SDL_GetAndroidCachePath(void) { SDL_Unsupported(); @@ -126,35 +115,29 @@ const char *SDL_GetAndroidCachePath(void) } -SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidExternalStoragePath(void); const char *SDL_GetAndroidExternalStoragePath(void) { SDL_Unsupported(); return NULL; } -SDL_DECLSPEC Uint32 SDLCALL SDL_GetAndroidExternalStorageState(void); Uint32 SDL_GetAndroidExternalStorageState(void) { SDL_Unsupported(); return 0; } -SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidInternalStoragePath(void); const char *SDL_GetAndroidInternalStoragePath(void) { SDL_Unsupported(); return NULL; } -SDL_DECLSPEC void * SDLCALL SDL_GetAndroidJNIEnv(void); void *SDL_GetAndroidJNIEnv(void) { SDL_Unsupported(); return NULL; } -typedef void (SDLCALL *SDL_RequestAndroidPermissionCallback)(void *userdata, const char *permission, bool granted); -SDL_DECLSPEC bool SDLCALL SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPermissionCallback cb, void *userdata); bool SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPermissionCallback cb, void *userdata) { (void)permission; @@ -163,7 +146,6 @@ bool SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPerm return SDL_Unsupported(); } -SDL_DECLSPEC bool SDLCALL SDL_SendAndroidMessage(Uint32 command, int param); bool SDL_SendAndroidMessage(Uint32 command, int param) { (void)command; @@ -171,7 +153,6 @@ bool SDL_SendAndroidMessage(Uint32 command, int param) return SDL_Unsupported(); } -SDL_DECLSPEC bool SDLCALL SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xoffset, int yoffset); bool SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xoffset, int yoffset) { (void)message; @@ -182,32 +163,27 @@ bool SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xo return SDL_Unsupported(); } -SDL_DECLSPEC int SDLCALL SDL_GetAndroidSDKVersion(void); int SDL_GetAndroidSDKVersion(void) { return SDL_Unsupported(); } -SDL_DECLSPEC bool SDLCALL SDL_IsChromebook(void); bool SDL_IsChromebook(void) { SDL_Unsupported(); return false; } -SDL_DECLSPEC bool SDLCALL SDL_IsDeXMode(void); bool SDL_IsDeXMode(void) { SDL_Unsupported(); return false; } -SDL_DECLSPEC Sint32 SDLCALL JNI_OnLoad(void *vm, void *reserved); -Sint32 JNI_OnLoad(void *vm, void *reserved) +Sint32 JNI_OnLoad(JavaVM *vm, void *reserved) { (void)vm; (void)reserved; - SDL_Unsupported(); - return -1; // JNI_ERR + return 0x00010004; // JNI_VERSION_1_4 } #endif diff --git a/libs/SDL3/src/core/SDL_core_unsupported.h b/libs/SDL3/src/core/SDL_core_unsupported.h new file mode 100644 index 00000000..3a942877 --- /dev/null +++ b/libs/SDL3/src/core/SDL_core_unsupported.h @@ -0,0 +1,65 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_VIDEO_DRIVER_X11 +extern SDL_DECLSPEC void SDLCALL SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata); +#endif + +#ifndef SDL_PLATFORM_LINUX +extern SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriority(Sint64 threadID, int priority); +extern SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy); +#endif + +#if !defined(SDL_PLATFORM_GDK) +typedef struct XUserHandle XUserHandle; + +extern SDL_DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void); +extern SDL_DECLSPEC bool SDLCALL SDL_GetGDKDefaultUser(XUserHandle *outUserHandle); +extern SDL_DECLSPEC void SDLCALL SDL_GDKSuspendGPU(SDL_GPUDevice *device); +extern SDL_DECLSPEC void SDLCALL SDL_GDKResumeGPU(SDL_GPUDevice *device); +#endif /* !SDL_PLATFORM_GDK */ + +#if !defined(SDL_PLATFORM_WINDOWS) +extern SDL_DECLSPEC bool SDLCALL SDL_RegisterApp(const char *name, Uint32 style, void *hInst); +extern SDL_DECLSPEC void SDLCALL SDL_SetWindowsMessageHook(void *callback, void *userdata); +extern SDL_DECLSPEC void SDLCALL SDL_UnregisterApp(void); +#endif /* !SDL_PLATFORM_WINDOWS */ + +#if !defined(SDL_PLATFORM_ANDROID) + +typedef void *SDL_RequestAndroidPermissionCallback; +typedef void *JavaVM; + +extern SDL_DECLSPEC void SDLCALL SDL_SendAndroidBackButton(void); +extern SDL_DECLSPEC void * SDLCALL SDL_GetAndroidActivity(void); +extern SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidCachePath(void); +extern SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidExternalStoragePath(void); +extern SDL_DECLSPEC Uint32 SDLCALL SDL_GetAndroidExternalStorageState(void); +extern SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidInternalStoragePath(void); +extern SDL_DECLSPEC void * SDLCALL SDL_GetAndroidJNIEnv(void); +extern SDL_DECLSPEC bool SDLCALL SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPermissionCallback cb, void *userdata); +extern SDL_DECLSPEC bool SDLCALL SDL_SendAndroidMessage(Uint32 command, int param); +extern SDL_DECLSPEC bool SDLCALL SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xoffset, int yoffset); +extern SDL_DECLSPEC int SDLCALL SDL_GetAndroidSDKVersion(void); +extern SDL_DECLSPEC bool SDLCALL SDL_IsChromebook(void); +extern SDL_DECLSPEC bool SDLCALL SDL_IsDeXMode(void); +extern SDL_DECLSPEC Sint32 SDLCALL JNI_OnLoad(JavaVM *vm, void *reserved); +#endif /* !SDL_PLATFORM_ANDROID */ diff --git a/libs/SDL3/src/core/android/SDL_android.c b/libs/SDL3/src/core/android/SDL_android.c index 6cdffef2..59ea5574 100644 --- a/libs/SDL3/src/core/android/SDL_android.c +++ b/libs/SDL3/src/core/android/SDL_android.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -44,6 +44,7 @@ #include #include #include +#include #define SDL_JAVA_PREFIX org_libsdl_app #define CONCAT1(prefix, class, function) CONCAT2(prefix, class, function) @@ -96,6 +97,12 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)( JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)( JNIEnv *env, jclass jcls); +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeScreenKeyboardShown)( + JNIEnv *env, jclass jcls); + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeScreenKeyboardHidden)( + JNIEnv *env, jclass jcls); + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)( JNIEnv *env, jclass jcls, jint keycode); @@ -115,13 +122,23 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)( jint touch_device_id_in, jint pointer_finger_id_in, jint action, jfloat x, jfloat y, jfloat p); +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchStart)( + JNIEnv *env, jclass jcls); + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchUpdate)( + JNIEnv *env, jclass jcls, + jfloat scale); + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchEnd)( + JNIEnv *env, jclass jcls); + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)( JNIEnv *env, jclass jcls, jint button, jint action, jfloat x, jfloat y, jboolean relative); JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePen)( JNIEnv *env, jclass jcls, - jint pen_id_in, jint button, jint action, jfloat x, jfloat y, jfloat p); + jint pen_id_in, jint device_type, jint button, jint action, jfloat x, jfloat y, jfloat p); JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)( JNIEnv *env, jclass jcls, @@ -198,7 +215,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)( static JNINativeMethod SDLActivity_tab[] = { { "nativeGetVersion", "()Ljava/lang/String;", SDL_JAVA_INTERFACE(nativeGetVersion) }, - { "nativeSetupJNI", "()I", SDL_JAVA_INTERFACE(nativeSetupJNI) }, + { "nativeSetupJNI", "()V", SDL_JAVA_INTERFACE(nativeSetupJNI) }, { "nativeInitMainThread", "()V", SDL_JAVA_INTERFACE(nativeInitMainThread) }, { "nativeCleanupMainThread", "()V", SDL_JAVA_INTERFACE(nativeCleanupMainThread) }, { "nativeRunMain", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)I", SDL_JAVA_INTERFACE(nativeRunMain) }, @@ -208,13 +225,18 @@ static JNINativeMethod SDLActivity_tab[] = { { "onNativeSurfaceCreated", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceCreated) }, { "onNativeSurfaceChanged", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceChanged) }, { "onNativeSurfaceDestroyed", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed) }, + { "onNativeScreenKeyboardShown", "()V", SDL_JAVA_INTERFACE(onNativeScreenKeyboardShown) }, + { "onNativeScreenKeyboardHidden", "()V", SDL_JAVA_INTERFACE(onNativeScreenKeyboardHidden) }, { "onNativeKeyDown", "(I)V", SDL_JAVA_INTERFACE(onNativeKeyDown) }, { "onNativeKeyUp", "(I)V", SDL_JAVA_INTERFACE(onNativeKeyUp) }, { "onNativeSoftReturnKey", "()Z", SDL_JAVA_INTERFACE(onNativeSoftReturnKey) }, { "onNativeKeyboardFocusLost", "()V", SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost) }, { "onNativeTouch", "(IIIFFF)V", SDL_JAVA_INTERFACE(onNativeTouch) }, + { "onNativePinchStart", "()V", SDL_JAVA_INTERFACE(onNativePinchStart) }, + { "onNativePinchUpdate", "(F)V", SDL_JAVA_INTERFACE(onNativePinchUpdate) }, + { "onNativePinchEnd", "()V", SDL_JAVA_INTERFACE(onNativePinchEnd) }, { "onNativeMouse", "(IIFFZ)V", SDL_JAVA_INTERFACE(onNativeMouse) }, - { "onNativePen", "(IIIFFF)V", SDL_JAVA_INTERFACE(onNativePen) }, + { "onNativePen", "(IIIIFFF)V", SDL_JAVA_INTERFACE(onNativePen) }, { "onNativeAccel", "(FFF)V", SDL_JAVA_INTERFACE(onNativeAccel) }, { "onNativeClipboardChanged", "()V", SDL_JAVA_INTERFACE(onNativeClipboardChanged) }, { "nativeLowMemory", "()V", SDL_JAVA_INTERFACE(nativeLowMemory) }, @@ -265,7 +287,7 @@ JNIEXPORT void JNICALL jint device_id); static JNINativeMethod SDLAudioManager_tab[] = { - { "nativeSetupJNI", "()I", SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI) }, + { "nativeSetupJNI", "()V", SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI) }, { "nativeAddAudioDevice", "(ZLjava/lang/String;I)V", SDL_JAVA_AUDIO_INTERFACE(nativeAddAudioDevice) }, { "nativeRemoveAudioDevice", "(ZI)V", SDL_JAVA_AUDIO_INTERFACE(nativeRemoveAudioDevice) } }; @@ -276,11 +298,11 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)( JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)( JNIEnv *env, jclass jcls, - jint device_id, jint keycode); + jint device_id, jint keycode, jint scancode); JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)( JNIEnv *env, jclass jcls, - jint device_id, jint keycode); + jint device_id, jint keycode, jint scancode); JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)( JNIEnv *env, jclass jcls, @@ -290,10 +312,15 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)( JNIEnv *env, jclass jcls, jint device_id, jint hat_id, jint x, jint y); +JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoySensor)( + JNIEnv *env, jclass jcls, + jint device_id, jint sensor_type, jlong sensor_timestamp, jfloat x, jfloat y, jfloat z); + JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( JNIEnv *env, jclass jcls, jint device_id, jstring device_name, jstring device_desc, jint vendor_id, jint product_id, - jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble); + jint button_mask, jint naxes, jint axis_mask, jint nhats, + jboolean can_rumble, jboolean has_rgb_led, jboolean has_accelerometer, jboolean has_gyroscope); JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)( JNIEnv *env, jclass jcls, @@ -308,12 +335,13 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)( jint device_id); static JNINativeMethod SDLControllerManager_tab[] = { - { "nativeSetupJNI", "()I", SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI) }, - { "onNativePadDown", "(II)Z", SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown) }, - { "onNativePadUp", "(II)Z", SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp) }, + { "nativeSetupJNI", "()V", SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI) }, + { "onNativePadDown", "(III)Z", SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown) }, + { "onNativePadUp", "(III)Z", SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp) }, { "onNativeJoy", "(IIF)V", SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy) }, { "onNativeHat", "(IIII)V", SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat) }, - { "nativeAddJoystick", "(ILjava/lang/String;Ljava/lang/String;IIIIIIZ)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick) }, + { "onNativeJoySensor", "(IIJFFF)V", SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoySensor) }, + { "nativeAddJoystick", "(ILjava/lang/String;Ljava/lang/String;IIIIIIZZZZ)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick) }, { "nativeRemoveJoystick", "(I)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick) }, { "nativeAddHaptic", "(ILjava/lang/String;)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic) }, { "nativeRemoveHaptic", "(I)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic) } @@ -352,7 +380,6 @@ static jmethodID midInitTouch; static jmethodID midIsAndroidTV; static jmethodID midIsChromebook; static jmethodID midIsDeXMode; -static jmethodID midIsScreenKeyboardShown; static jmethodID midIsTablet; static jmethodID midManualBackButton; static jmethodID midMinimizeWindow; @@ -386,6 +413,8 @@ static jclass mControllerManagerClass; // method signatures static jmethodID midPollInputDevices; +static jmethodID midJoystickSetLED; +static jmethodID midJoystickSetSensorsEnabled; static jmethodID midPollHapticDevices; static jmethodID midHapticRun; static jmethodID midHapticRumble; @@ -635,14 +664,13 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl midClipboardSetText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardSetText", "(Ljava/lang/String;)V"); midCreateCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "createCustomCursor", "([IIIII)I"); midDestroyCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "destroyCustomCursor", "(I)V"); - midGetContext = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;"); + midGetContext = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/app/Activity;"); midGetManifestEnvironmentVariables = (*env)->GetStaticMethodID(env, mActivityClass, "getManifestEnvironmentVariables", "()Z"); midGetNativeSurface = (*env)->GetStaticMethodID(env, mActivityClass, "getNativeSurface", "()Landroid/view/Surface;"); midInitTouch = (*env)->GetStaticMethodID(env, mActivityClass, "initTouch", "()V"); midIsAndroidTV = (*env)->GetStaticMethodID(env, mActivityClass, "isAndroidTV", "()Z"); midIsChromebook = (*env)->GetStaticMethodID(env, mActivityClass, "isChromebook", "()Z"); midIsDeXMode = (*env)->GetStaticMethodID(env, mActivityClass, "isDeXMode", "()Z"); - midIsScreenKeyboardShown = (*env)->GetStaticMethodID(env, mActivityClass, "isScreenKeyboardShown", "()Z"); midIsTablet = (*env)->GetStaticMethodID(env, mActivityClass, "isTablet", "()Z"); midManualBackButton = (*env)->GetStaticMethodID(env, mActivityClass, "manualBackButton", "()V"); midMinimizeWindow = (*env)->GetStaticMethodID(env, mActivityClass, "minimizeWindow", "()V"); @@ -675,7 +703,6 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl !midIsAndroidTV || !midIsChromebook || !midIsDeXMode || - !midIsScreenKeyboardShown || !midIsTablet || !midManualBackButton || !midMinimizeWindow || @@ -734,6 +761,10 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env midPollInputDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass, "pollInputDevices", "()V"); + midJoystickSetLED = (*env)->GetStaticMethodID(env, mControllerManagerClass, + "joystickSetLED", "(IIII)V"); + midJoystickSetSensorsEnabled = (*env)->GetStaticMethodID(env, mControllerManagerClass, + "joystickSetSensorsEnabled", "(IZ)V"); midPollHapticDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass, "pollHapticDevices", "()V"); midHapticRun = (*env)->GetStaticMethodID(env, mControllerManagerClass, @@ -743,16 +774,13 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env midHapticStop = (*env)->GetStaticMethodID(env, mControllerManagerClass, "hapticStop", "(I)V"); - if (!midPollInputDevices || !midPollHapticDevices || !midHapticRun || !midHapticRumble || !midHapticStop) { + if (!midPollInputDevices || !midJoystickSetLED || !midJoystickSetSensorsEnabled || !midPollHapticDevices || !midHapticRun || !midHapticRumble || !midHapticStop) { __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLControllerManager.java?"); } checkJNIReady(); } -// SDL main function prototype -typedef int (*SDL_main_func)(int argc, char *argv[]); - static int run_count = 0; static bool allow_recreate_activity; static bool allow_recreate_activity_set; @@ -822,47 +850,61 @@ JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv *env, jclass cls, function_name = (*env)->GetStringUTFChars(env, function, NULL); SDL_main = (SDL_main_func)dlsym(library_handle, function_name); if (SDL_main) { - int i; - int argc; - int len; - char **argv; - bool isstack; + // Use the name "app_process" for argv[0] so PHYSFS_platformCalcBaseDir() works. + // https://github.com/love2d/love-android/issues/24 + // (note that PhysicsFS hasn't used argv on Android in a long time, but we'll keep this for compat at least for SDL3's lifetime. --ryan.) + const char *argv0 = "app_process"; + const int len = (*env)->GetArrayLength(env, array); // argv elements, not counting argv[0]. - // Prepare the arguments. - len = (*env)->GetArrayLength(env, array); - argv = SDL_small_alloc(char *, 1 + len + 1, &isstack); // !!! FIXME: check for NULL - argc = 0; - /* Use the name "app_process" so PHYSFS_platformCalcBaseDir() works. - https://github.com/love2d/love-android/issues/24 - */ - argv[argc++] = SDL_strdup("app_process"); - for (i = 0; i < len; ++i) { - char *arg = NULL; + size_t total_alloc_len = (SDL_strlen(argv0) + 1) + ((len + 2) * sizeof (char *)); // len+2 to allocate an array that also holds argv0 and a NULL terminator. + for (int i = 0; i < len; ++i) { + total_alloc_len++; // null terminator. jstring string = (*env)->GetObjectArrayElement(env, array, i); if (string) { const char *utf = (*env)->GetStringUTFChars(env, string, 0); if (utf) { - arg = SDL_strdup(utf); + total_alloc_len += SDL_strlen(utf) + 1; (*env)->ReleaseStringUTFChars(env, string, utf); } (*env)->DeleteLocalRef(env, string); } - if (arg == NULL) { - arg = SDL_strdup(""); + } + + void *args = malloc(total_alloc_len); // This should NOT be SDL_malloc() + if (!args) { // uhoh. + __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Out of memory parsing command line!"); + } else { + size_t remain = total_alloc_len - (sizeof (char *) * (len + 2)); + int argc = 0; + char **argv = (char **) args; + char *ptr = (char *) &argv[len + 2]; + size_t cpy = SDL_strlcpy(ptr, argv0, remain) + 1; + argv[argc++] = ptr; + SDL_assert(cpy <= remain); remain -= cpy; ptr += cpy; + for (int i = 0; i < len; ++i) { + jstring string = (*env)->GetObjectArrayElement(env, array, i); + const char *utf = string ? (*env)->GetStringUTFChars(env, string, 0) : NULL; + cpy = SDL_strlcpy(ptr, utf ? utf : "", remain) + 1; + if (cpy < remain) { + argv[argc++] = ptr; + remain -= cpy; + ptr += cpy; + } + if (utf) { + (*env)->ReleaseStringUTFChars(env, string, utf); + } + if (string) { + (*env)->DeleteLocalRef(env, string); + } } - argv[argc++] = arg; + argv[argc] = NULL; + + // Run the application. + status = SDL_RunApp(argc, argv, SDL_main, NULL); + + // Release the arguments. + free(args); // This should NOT be SDL_free() } - argv[argc] = NULL; - - // Run the application. - status = SDL_main(argc, argv); - - // Release the arguments. - for (i = 0; i < argc; ++i) { - SDL_free(argv[i]); - } - SDL_small_free(argv, isstack); - } else { __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't find function %s in library %s", function_name, library_file); } @@ -1080,7 +1122,8 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)( { const char *utfname = (*env)->GetStringUTFChars(env, name, NULL); - SDL_AddTouch((SDL_TouchID)touchId, SDL_TOUCH_DEVICE_DIRECT, utfname); + SDL_AddTouch(Android_ConvertJavaTouchID(touchId), + SDL_TOUCH_DEVICE_DIRECT, utfname); (*env)->ReleaseStringUTFChars(env, name, utfname); } @@ -1116,10 +1159,10 @@ SDL_JAVA_AUDIO_INTERFACE(nativeRemoveAudioDevice)(JNIEnv *env, jclass jcls, jboo // Paddown JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)( JNIEnv *env, jclass jcls, - jint device_id, jint keycode) + jint device_id, jint keycode, jint scancode) { #ifdef SDL_JOYSTICK_ANDROID - return Android_OnPadDown(device_id, keycode); + return Android_OnPadDown(device_id, keycode, scancode); #else return false; #endif // SDL_JOYSTICK_ANDROID @@ -1128,10 +1171,10 @@ JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)( // Padup JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)( JNIEnv *env, jclass jcls, - jint device_id, jint keycode) + jint device_id, jint keycode, jint scancode) { #ifdef SDL_JOYSTICK_ANDROID - return Android_OnPadUp(device_id, keycode); + return Android_OnPadUp(device_id, keycode, scancode); #else return false; #endif // SDL_JOYSTICK_ANDROID @@ -1157,17 +1200,29 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)( #endif // SDL_JOYSTICK_ANDROID } +JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoySensor)( + JNIEnv *env, jclass jcls, + jint device_id, jint sensor_type, jlong sensor_timestamp, jfloat x, jfloat y, jfloat z) +{ +#ifdef SDL_JOYSTICK_ANDROID + // In Java there's no Uint64 type, so pass Sint64 as if it was Uint64. + Android_OnJoySensor(device_id, sensor_type, sensor_timestamp, x, y, z); +#endif // SDL_JOYSTICK_ANDROID +} + JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( JNIEnv *env, jclass jcls, jint device_id, jstring device_name, jstring device_desc, jint vendor_id, jint product_id, - jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble) + jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble, jboolean has_rgb_led, + jboolean has_accelerometer, jboolean has_gyroscope) { #ifdef SDL_JOYSTICK_ANDROID const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL); - Android_AddJoystick(device_id, name, desc, vendor_id, product_id, button_mask, naxes, axis_mask, nhats, can_rumble); + Android_AddJoystick(device_id, name, desc, vendor_id, product_id, button_mask, naxes, axis_mask, nhats, + can_rumble, has_rgb_led, has_accelerometer, has_gyroscope); (*env)->ReleaseStringUTFChars(env, device_name, name); (*env)->ReleaseStringUTFChars(env, device_desc, desc); @@ -1241,6 +1296,10 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(JNIEnv *env, j } #endif + if (Android_Window) { + Android_RestoreScreenKeyboard(SDL_GetVideoDevice(), Android_Window); + } + SDL_UnlockMutex(Android_ActivityMutex); } @@ -1286,6 +1345,16 @@ retry: SDL_UnlockMutex(Android_ActivityMutex); } +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeScreenKeyboardShown)(JNIEnv *env, jclass jcls) +{ + SDL_SendScreenKeyboardShown(); +} + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeScreenKeyboardHidden)(JNIEnv *env, jclass jcls) +{ + SDL_SendScreenKeyboardHidden(); +} + // Keydown JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)( JNIEnv *env, jclass jcls, @@ -1346,6 +1415,43 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)( SDL_UnlockMutex(Android_ActivityMutex); } +// Pinch +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchStart)( + JNIEnv *env, jclass jcls) +{ + SDL_LockMutex(Android_ActivityMutex); + + if (Android_Window) { + SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, Android_Window, 0); + } + + SDL_UnlockMutex(Android_ActivityMutex); +} + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchUpdate)( + JNIEnv *env, jclass jcls, jfloat scale) +{ + SDL_LockMutex(Android_ActivityMutex); + + if (Android_Window) { + SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, Android_Window, scale); + } + + SDL_UnlockMutex(Android_ActivityMutex); +} + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchEnd)( + JNIEnv *env, jclass jcls) +{ + SDL_LockMutex(Android_ActivityMutex); + + if (Android_Window) { + SDL_SendPinch(SDL_EVENT_PINCH_END, 0, Android_Window, 0); + } + + SDL_UnlockMutex(Android_ActivityMutex); +} + // Mouse JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)( JNIEnv *env, jclass jcls, @@ -1361,11 +1467,11 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)( // Pen JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePen)( JNIEnv *env, jclass jcls, - jint pen_id_in, jint button, jint action, jfloat x, jfloat y, jfloat p) + jint pen_id_in, jint device_type, jint button, jint action, jfloat x, jfloat y, jfloat p) { SDL_LockMutex(Android_ActivityMutex); - Android_OnPen(Android_Window, pen_id_in, button, action, x, y, p); + Android_OnPen(Android_Window, pen_id_in, device_type, button, action, x, y, p); SDL_UnlockMutex(Android_ActivityMutex); } @@ -1644,7 +1750,7 @@ SDL_DisplayOrientation Android_JNI_GetDisplayCurrentOrientation(void) return displayCurrentOrientation; } -void Android_JNI_MinizeWindow(void) +void Android_JNI_MinimizeWindow(void) { JNIEnv *env = Android_JNI_GetEnv(); (*env)->CallStaticVoidMethod(env, mActivityClass, midMinimizeWindow); @@ -1728,18 +1834,24 @@ static bool Android_JNI_ExceptionOccurred(bool silent) exceptionName = (jstring)(*env)->CallObjectMethod(env, exceptionClass, mid); exceptionNameUTF8 = (*env)->GetStringUTFChars(env, exceptionName, 0); + (*env)->DeleteLocalRef(env, classClass); + mid = (*env)->GetMethodID(env, exceptionClass, "getMessage", "()Ljava/lang/String;"); exceptionMessage = (jstring)(*env)->CallObjectMethod(env, exception, mid); + (*env)->DeleteLocalRef(env, exceptionClass); + if (exceptionMessage != NULL) { const char *exceptionMessageUTF8 = (*env)->GetStringUTFChars(env, exceptionMessage, 0); SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8); (*env)->ReleaseStringUTFChars(env, exceptionMessage, exceptionMessageUTF8); + (*env)->DeleteLocalRef(env, exceptionMessage); } else { SDL_SetError("%s", exceptionNameUTF8); } (*env)->ReleaseStringUTFChars(env, exceptionName, exceptionNameUTF8); + (*env)->DeleteLocalRef(env, exceptionName); } return true; @@ -1748,10 +1860,456 @@ static bool Android_JNI_ExceptionOccurred(bool silent) return false; } + +// APK file tree discovery... + +// APK files are just .zip files, so try to find parse out the file tree from it. We'll still +// use Android's system APIs to actually access data, but those APIs aren't reliable for +// enumerating the tree for various reasons. + +// we only care about the directories; Android's AAssetManager can enumerate all files in any +// known directory, but it won't enumerate subdirectories, so we track that by ourselves. +typedef struct APKNode +{ + char *name; + SDL_PathInfo info; + struct APKNode *children; + struct APKNode *next_sibling; +} APKNode; + +static APKNode *APKRootNode = NULL; + +static void FreeAPKNode(APKNode *node) +{ + if (node) { + FreeAPKNode(node->next_sibling); + FreeAPKNode(node->children); + SDL_free(node->name); + SDL_free(node); + } +} + +static APKNode *FindAPKChildNode(APKNode *parent, const char *child) +{ + for (APKNode *node = parent->children; node != NULL; node = node->next_sibling) { + if (SDL_strcmp(child, node->name) == 0) { + return node; + } + } + return NULL; +} + +static const APKNode *FindAPKNode(const char *constpath) +{ + APKNode *parent = APKRootNode; + if (!parent) { + return NULL; + } + + const size_t pathlen = SDL_strlen(constpath); + bool isstack = false; + char *alloc_path = SDL_small_alloc(char, pathlen + 1, &isstack); + if (!alloc_path) { + return NULL; + } + char *path = alloc_path; + SDL_strlcpy(path, constpath, pathlen + 1); + + while (parent) { + while (*path == '/') { + path++; // just in case there are absolute paths or double-slashes, drop them. + } + + if (*path == '\0') { // ended with a '/'? We're done. + break; + } + + char *ptr = SDL_strchr(path, '/'); + if (ptr) { + *ptr = '\0'; // terminate on the end of this subdir's name. + } + + APKNode *node = FindAPKChildNode(parent, path); + if (!node) { + SDL_SetError("No such file or directory"); + parent = NULL; + } else if ((node->info.type == SDL_PATHTYPE_FILE) && ptr) { // file where we want a directory? + SDL_SetError("%s is not a directory", alloc_path); + parent = NULL; + } else { + parent = node; + if (!ptr) { + break; + } + *ptr = '/'; + path = ptr + 1; + } + } + + SDL_small_free(alloc_path, isstack); + return parent; +} + +static APKNode *AddAPKChildNode(APKNode *parent, const char *child) +{ + APKNode *node = FindAPKChildNode(parent, child); + if (!node) { // don't have this one yet, make a new node. + node = (APKNode *) SDL_calloc(1, sizeof (*node)); + if (!node) { + return NULL; // uhoh. + } + + node->name = SDL_strdup(child); + if (!node->name) { + SDL_free(node); + return NULL; // uhoh. + } + + SDL_copyp(&node->info, &parent->info); // you probably need to update this afterwards. + + node->next_sibling = parent->children; + parent->children = node; + } + return node; +} + +static APKNode *AddAPKDirs(char *path, APKNode *parent) +{ + // zip files specify explicit directories by just having a path that ends with a dir separator, + // which works nicely for our needs here; if the last segment of the path doesn't end with a + // '/', it's a file and we can drop it, or we filled in the final subdirectory and the '/' at + // the end will put us at an empty string to be dropped. + // + // directories do not need to be explicitly specified if something uses some deeper path, they + // may need to be inferred from those references, so we build out the tree by looking at all + // files and filling in nodes they mention. + + SDL_assert(parent->info.type == SDL_PATHTYPE_DIRECTORY); + SDL_assert(parent->info.size == 0); + + while (true) { // while still subdirectories to handle... + while (*path == '/') { + path++; // just in case there are absolute paths or double-slashes, drop them. + } + + char *ptr = SDL_strchr(path, '/'); + if (!ptr) { + break; // last thing is either an empty string (we ended with a '/'), or an actual file's name, so drop it. + } + + *ptr = '\0'; // terminate on the end of this subdir's name. + APKNode *node = AddAPKChildNode(parent, path); + *ptr = '/'; + + if (!node) { + return NULL; // uhoh. + } + + parent = node; + path = ptr + 1; // point to start of next section. + } + + return parent; +} + + +static SDL_Time ZipDosTimeToSDLTime(Uint32 dostime) +{ + Uint32 dosdate; + struct tm unixtime; + SDL_zero(unixtime); + + dosdate = (Uint32) ((dostime >> 16) & 0xFFFF); + dostime &= 0xFFFF; + + /* dissect date */ + unixtime.tm_year = ((dosdate >> 9) & 0x7F) + 80; + unixtime.tm_mon = ((dosdate >> 5) & 0x0F) - 1; + unixtime.tm_mday = ((dosdate ) & 0x1F); + + /* dissect time */ + unixtime.tm_hour = ((dostime >> 11) & 0x1F); + unixtime.tm_min = ((dostime >> 5) & 0x3F); + unixtime.tm_sec = ((dostime << 1) & 0x3E); + + /* let mktime calculate daylight savings time. */ + unixtime.tm_isdst = -1; + + return ((SDL_Time) mktime(&unixtime)); +} + + + +#define ZIP_CENTRAL_DIR_SIG 0x02014b50 +#define ZIP_END_OF_CENTRAL_DIR_SIG 0x06054b50 +#define ZIP64_END_OF_CENTRAL_DIR_SIG 0x06064b50 +#define ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG 0x07064b50 +#define ZIP64_EXTENDED_INFO_EXTRA_FIELD_SIG 0x0001 + +static bool ProcessZip(SDL_IOStream *io, APKNode *root) +{ + // There is a record at the end of a .zip file we can use to find the central directory; + // unfortunately it's before the variable-length comment field, so we might need to read + // from the end of the file until we see a magic signature. For now, we assume APKs don't + // have a comment, so we don't have to search backwards for the signature and it's always + // 22 bytes from EOF. + const Sint64 eocd = SDL_GetIOSize(io) - 22; + if (eocd < 0) { + SDL_Log("ANDROID: Couldn't find End Of Central Directory in APK (%s). Filesystem enumeration will fail.", SDL_GetError()); + return false; + } + + bool zip64 = false; + Sint64 centraldir = -1; + Uint64 num_entries = 0; + Uint16 val16 = 0; + Uint32 val32 = 0; + Uint64 val64 = 0; + + // First, check if this is actually zip64 format instead. The zip64 magic is 20 bytes back. + if (eocd < 20) { // presumably we always _are_ > 20, but let's be defensive here. + goto corrupterr; + } else if (SDL_SeekIO(io, eocd - 20, SDL_IO_SEEK_SET) < 0) { + goto ioerr; + } else if (!SDL_ReadU32LE(io, &val32)) { + goto ioerr; + } else if (val32 == ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG) { // this is a zip64 archive? + if (!SDL_ReadU32LE(io, &val32)) { // disk number with start of central directory. + goto ioerr; + } else if (val32 != 0) { + goto corrupterr; + } else if (!SDL_ReadU64LE(io, &val64)) { // file offset of zip64 end-of-central-dir record + goto ioerr; + + // note that this gets significantly more complex if there is data prepended to the .zip file + // (like a self-extracting .exe, etc), but until that happens, we're keeping this as simple + // as possible and assuming the file offset in val64 is correct. + + } else if (SDL_SeekIO(io, (Sint64) val64, SDL_IO_SEEK_SET) < 0) { + goto ioerr; + } else if (!SDL_ReadU32LE(io, &val32)) { // zip64 end-of-central-dir signature. + goto ioerr; + } else if (val32 != ZIP64_END_OF_CENTRAL_DIR_SIG) { + goto corrupterr; + } else if (SDL_SeekIO(io, 28, SDL_IO_SEEK_CUR) < 0) { // we don't care about several of the fields, skip over them. + goto ioerr; + } else if (!SDL_ReadU64LE(io, &num_entries)) { // total entries in the central dir. + goto ioerr; + } else if (!SDL_ReadU64LE(io, &val64)) { // size of the central dir. + goto ioerr; + } else if (!SDL_ReadU64LE(io, &val64)) { // offset of the central dir. + goto ioerr; + } + + zip64 = true; + centraldir = (Sint64) val64; + } else if (SDL_SeekIO(io, eocd + 4 + 6, SDL_IO_SEEK_SET) < 0) { // skip back to where we were, plus skip some fields we don't care about. + goto ioerr; + } else if (!SDL_ReadU16LE(io, &val16)) { + goto ioerr; + } else if (!SDL_ReadU32LE(io, &val32)) { // size of the central dir. + goto ioerr; + } else if (!SDL_ReadU32LE(io, &val32)) { // offset of the central dir. + goto ioerr; + } else { + num_entries = (Uint64) val16; + centraldir = (Sint64) val32; + } + + // okay, we know where the central dir is now, go there and start reading entries. + SDL_assert(centraldir > 0); // negative means we failed, zero is impossible since there should be something else there. + + for (Uint64 i = 0; i < num_entries; i++) { + Uint16 fnamelen = 0; + Uint16 extralen = 0; + Uint16 commentlen = 0; + Uint32 dosmodtime = 0; + Uint32 uncompressed32 = 0; + Uint64 uncompressed64 = 0; + + // we don't care about most of this information, just parse through it to get what we need. + if (SDL_SeekIO(io, centraldir, SDL_IO_SEEK_SET) < 0) { + goto ioerr; + } else if (!SDL_ReadU32LE(io, &val32)) { // central dir item signature. + goto ioerr; + } else if (val32 != ZIP_CENTRAL_DIR_SIG) { + goto corrupterr; + } else if (!SDL_ReadU16LE(io, &val16)) { // version made by + goto ioerr; + } else if (!SDL_ReadU16LE(io, &val16)) { // version needed + goto ioerr; + } else if (!SDL_ReadU16LE(io, &val16)) { // general bits + goto ioerr; + } else if (!SDL_ReadU16LE(io, &val16)) { // compression method + goto ioerr; + } else if (!SDL_ReadU32LE(io, &dosmodtime)) { // last mod date/time + goto ioerr; + } else if (!SDL_ReadU32LE(io, &val32)) { // CRC-32 + goto ioerr; + } else if (!SDL_ReadU32LE(io, &val32)) { // compressed size + goto ioerr; + } else if (!SDL_ReadU32LE(io, &uncompressed32)) { // uncompressed size + goto ioerr; + } else if (!SDL_ReadU16LE(io, &fnamelen)) { // filename length + goto ioerr; + } else if (!SDL_ReadU16LE(io, &extralen)) { // extra length + goto ioerr; + } else if (!SDL_ReadU16LE(io, &commentlen)) { // comment length + goto ioerr; + } else if (!SDL_ReadU16LE(io, &val16)) { // disk number start + goto ioerr; + } else if (!SDL_ReadU16LE(io, &val16)) { // internal file attributes + goto ioerr; + } else if (!SDL_ReadU32LE(io, &val32)) { // external file attributes + goto ioerr; + } else if (!SDL_ReadU32LE(io, &val32)) { // relative offset of local header + goto ioerr; + } + + char fnamebuf[0xFFFF+1]; // just eat 64k of stack like a boss until someone complains. + if (SDL_ReadIO(io, fnamebuf, (size_t) fnamelen) != ((size_t) fnamelen)) { + goto ioerr; + } + + // technically zip files might have '\\' dir separators, but these were mostly old DOS files and not Android APKs, I think. Revisit if necessary. + + fnamebuf[fnamelen] = '\0'; // make sure the string is null-terminated. + + //SDL_Log("ANDROID: Saw ZIP entry '%s'", fnamebuf); + + uncompressed64 = (Uint64) uncompressed32; + if (zip64 && (uncompressed32 == 0xFFFFFFFF)) { // file is larger than 4gig, find the zip64 extended info field in the extra section. + bool found = false; + Uint16 remaining = extralen; + while (remaining > 4) { // Two 16-bit values at a minimum, tag and len. + Uint16 tag, len; + if (!SDL_ReadU16LE(io, &tag) || !SDL_ReadU16LE(io, &len)) { + goto ioerr; + } else if (remaining < (len + 4)) { + goto corrupterr; + } else if (tag != ZIP64_EXTENDED_INFO_EXTRA_FIELD_SIG) { // not the field we need, skip over it. + if (SDL_SeekIO(io, (Sint64) len, SDL_IO_SEEK_CUR) < 0) { + goto ioerr; + } + } else if (len < 8) { + goto corrupterr; + } else if ((uncompressed32 == 0xFFFFFFFF) && !SDL_ReadU64LE(io, &uncompressed64)) { + goto ioerr; + + // there are other values in here, but we don't care about them and we're done, so don't try to skip over them. + + } else { + found = true; + break; // got what we need, drop out. + } + remaining -= len + 4; + } + + if (!found) { + goto corrupterr; + } + } + + char *ptr = fnamebuf; + while (*ptr == '/') { // drop absolute paths. + ptr++; + } + + if (SDL_strncmp(ptr, "assets/", 7) == 0) { // we only care about things under 'assets' for now. Drop everything else. + ptr += 6; // keep the '/' so strrchr never returns NULL. + APKNode *node = AddAPKDirs(ptr, root); // this builds out any missing subdirs, returns parent dir's node. + if (!node) { + goto ioerr; // (probably out of memory.) + } + + const SDL_Time modtime = ZipDosTimeToSDLTime(dosmodtime); + ptr = SDL_strrchr(ptr, '/'); + SDL_assert(ptr != NULL); + if (*(++ptr) == '\0') { // explicit directory entry paths end with '/' ...`node` is the new node. + node->info.type = SDL_PATHTYPE_DIRECTORY; + node->info.size = 0; + } else { + node = AddAPKChildNode(node, ptr); + if (!node) { + goto ioerr; // (probably out of memory.) + } + node->info.type = SDL_PATHTYPE_FILE; + node->info.size = uncompressed64; + } + node->info.create_time = node->info.modify_time = node->info.access_time = modtime; + } + + centraldir += (Sint64) (46 + fnamelen + extralen + commentlen); // will seek to next file entry. + } + + return true; + +corrupterr: + SDL_Log("ANDROID: Unexpected or corrupt data in APK. Filesystem enumeration will fail."); + return false; + +ioerr: + SDL_Log("ANDROID: i/o error in APK (%s). Filesystem enumeration will fail.", SDL_GetError()); + return false; +} + +static bool CreateAPKNodes(const char *path) +{ + SDL_Log("ANDROID: Parsing APK file '%s' ...", path); + + SDL_IOStream *io = SDL_IOFromFile(path, "rb"); + if (!io) { + SDL_Log("ANDROID: Can't open APK '%s' for reading (%s). Filesystem enumeration will fail.", path, SDL_GetError()); + } else { + ProcessZip(io, APKRootNode); + SDL_CloseIO(io); + } + return true; +} + +static bool PrepareAPK(void) +{ + // the assetmanager isn't useful for enumerating directories, so parse the APK directly for that info upfront. + bool retval = (APKRootNode != NULL); + if (!retval) { + // allocate this upfront, so if there's a failure, we'll not try again and just have an empty file tree. + APKRootNode = (APKNode *) SDL_calloc(1, sizeof (*APKRootNode)); + if (!APKRootNode) { + return false; // oh well. + } + APKRootNode->info.type = SDL_PATHTYPE_DIRECTORY; + + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); + JNIEnv *env = Android_JNI_GetEnv(); + if (LocalReferenceHolder_Init(&refs, env)) { + jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); + jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), "getPackageResourcePath", "()Ljava/lang/String;"); + jstring jstr = (jstring)(*env)->CallObjectMethod(env, context, mid); + jthrowable jexception = (*env)->ExceptionOccurred(env); + if (jexception != NULL) { + (*env)->ExceptionClear(env); // oh well + } else { + const char *apkpath = (*env)->GetStringUTFChars(env, jstr, NULL); + SDL_PathInfo apkinfo; + SDL_assert(apkpath[0] == '/'); // So SDL_GetPathInfo goes through the `stat` path and doesn't try to dig into the APK. + if (SDL_GetPathInfo(apkpath, &apkinfo)) { // we just want the file times here, so oh well if it fails. + APKRootNode->info.create_time = apkinfo.create_time; + APKRootNode->info.modify_time = apkinfo.modify_time; + APKRootNode->info.access_time = apkinfo.access_time; + } + CreateAPKNodes(apkpath); + (*env)->ReleaseStringUTFChars(env, jstr, apkpath); + retval = true; + } + } + LocalReferenceHolder_Cleanup(&refs); + } + return retval; // even on failure, leave an empty root node so we have zero files and don't try to load the .zip again. +} + static void Internal_Android_Create_AssetManager(void) { - - struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); JNIEnv *env = Android_JNI_GetEnv(); jmethodID mid; jobject context; @@ -1795,6 +2353,22 @@ static void Internal_Android_Destroy_AssetManager(void) (*env)->DeleteGlobalRef(env, javaAssetManagerRef); asset_manager = NULL; } + + if (APKRootNode) { + FreeAPKNode(APKRootNode); + APKRootNode = NULL; + } +} + +static const char *GetAssetPath(const char *path) +{ + if (path && path[0] == '.' && path[1] == '/') { + path += 2; + while (*path == '/') { + ++path; + } + } + return path; } bool Android_JNI_FileOpen(void **puserdata, const char *fileName, const char *mode) @@ -1806,11 +2380,12 @@ bool Android_JNI_FileOpen(void **puserdata, const char *fileName, const char *mo if (!asset_manager) { Internal_Android_Create_AssetManager(); + if (!asset_manager) { + return SDL_SetError("Couldn't create asset manager"); + } } - if (!asset_manager) { - return SDL_SetError("Couldn't create asset manager"); - } + fileName = GetAssetPath(fileName); asset = AAssetManager_open(asset_manager, fileName, AASSET_MODE_UNKNOWN); if (!asset) { @@ -1826,7 +2401,10 @@ size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOSta const int bytes = AAsset_read((AAsset *)userdata, buffer, size); if (bytes < 0) { SDL_SetError("AAsset_read() failed"); + *status = SDL_IO_STATUS_ERROR; return 0; + } else if (bytes < size) { + *status = SDL_IO_STATUS_EOF; } return (size_t)bytes; } @@ -1834,6 +2412,7 @@ size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOSta size_t Android_JNI_FileWrite(void *userdata, const void *buffer, size_t size, SDL_IOStatus *status) { SDL_SetError("Cannot write to Android package filesystem"); + *status = SDL_IO_STATUS_ERROR; return 0; } @@ -1853,6 +2432,48 @@ bool Android_JNI_FileClose(void *userdata) return true; } +bool Android_JNI_EnumerateAssetDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata) +{ + SDL_assert(path != NULL); + + if (!PrepareAPK()) { + return false; + } + + SDL_EnumerationResult result = SDL_ENUM_CONTINUE; + const char *asset_path = GetAssetPath(path); + + const APKNode *apknode = FindAPKNode(asset_path); + if (!apknode) { + return SDL_SetError("No such directory"); + } else if (apknode->info.type != SDL_PATHTYPE_DIRECTORY) { + return SDL_SetError("Not a directory"); + } else { + for (const APKNode *node = apknode->children; node && (result == SDL_ENUM_CONTINUE); node = node->next_sibling) { + result = cb(userdata, path, node->name); + } + } + + return (result != SDL_ENUM_FAILURE); +} + +bool Android_JNI_GetAssetPathInfo(const char *path, SDL_PathInfo *info) +{ + SDL_assert(path != NULL); + + if (!PrepareAPK()) { + return false; + } + + path = GetAssetPath(path); + const APKNode *apknode = FindAPKNode(path); + if (!apknode) { + return SDL_SetError("No such file or directory"); + } + SDL_copyp(info, &apknode->info); + return true; +} + bool Android_JNI_SetClipboardText(const char *text) { JNIEnv *env = Android_JNI_GetEnv(); @@ -1893,7 +2514,7 @@ bool Android_JNI_HasClipboardText(void) */ int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seconds, int *percent) { - struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); JNIEnv *env = Android_JNI_GetEnv(); jmethodID mid; jobject context; @@ -2020,6 +2641,18 @@ void Android_JNI_PollInputDevices(void) (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollInputDevices); } +void Android_JNI_JoystickSetLED(int device_id, int red, int green, int blue) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midJoystickSetLED, device_id, red, green, blue); +} + +void Android_JNI_JoystickSetSensorsEnabled(int device_id, bool enabled) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midJoystickSetSensorsEnabled, device_id, (enabled == 1)); +} + void Android_JNI_PollHapticDevices(void) { JNIEnv *env = Android_JNI_GetEnv(); @@ -2049,7 +2682,7 @@ void Android_JNI_HapticStop(int device_id) bool SDL_SendAndroidMessage(Uint32 command, int param) { - if (command < 0x8000) { + CHECK_PARAM(command < 0x8000) { return SDL_InvalidParamError("command"); } return Android_JNI_SendMessage(command, param); @@ -2085,14 +2718,6 @@ void Android_JNI_HideScreenKeyboard(void) Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0); } -bool Android_JNI_IsScreenKeyboardShown(void) -{ - JNIEnv *env = Android_JNI_GetEnv(); - jboolean is_shown = 0; - is_shown = (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsScreenKeyboardShown); - return is_shown; -} - bool Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) { JNIEnv *env; @@ -2163,7 +2788,7 @@ bool Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *b mid = (*env)->GetMethodID(env, clazz, "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I"); *buttonID = (*env)->CallIntMethod(env, context, mid, - messageboxdata->flags, + (jint)messageboxdata->flags, title, message, button_flags, @@ -2223,6 +2848,43 @@ int SDL_GetAndroidSDKVersion(void) return sdk_version; } +char *SDL_GetAndroidPackageName(void) +{ + // this doesn't currently cache this, because it's only used by SDL_GetExeName, which _does_ cache it. + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); + + JNIEnv *env = Android_JNI_GetEnv(); + if (!LocalReferenceHolder_Init(&refs, env)) { + LocalReferenceHolder_Cleanup(&refs); + return NULL; + } + + // context = SDLActivity.getContext(); + jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); + if (!context) { + SDL_SetError("Couldn't get Android context!"); + LocalReferenceHolder_Cleanup(&refs); + return NULL; + } + + // fileObj = context.getFilesDir(); + jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), "getPackageName", "()Ljava/lang/String;"); + jstring jstr = (jstring)(*env)->CallObjectMethod(env, context, mid); + if (Android_JNI_ExceptionOccurred(false)) { + LocalReferenceHolder_Cleanup(&refs); + return NULL; + } + + const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL); + char *retval = cstr ? SDL_strdup(cstr) : NULL; + (*env)->ReleaseStringUTFChars(env, jstr, cstr); + + LocalReferenceHolder_Cleanup(&refs); + + return retval; +} + + bool SDL_IsAndroidTablet(void) { JNIEnv *env = Android_JNI_GetEnv(); @@ -2258,7 +2920,7 @@ const char *SDL_GetAndroidInternalStoragePath(void) static char *s_AndroidInternalFilesPath = NULL; if (!s_AndroidInternalFilesPath) { - struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); jmethodID mid; jobject context; jobject fileObject; @@ -2309,7 +2971,7 @@ const char *SDL_GetAndroidInternalStoragePath(void) Uint32 SDL_GetAndroidExternalStorageState(void) { - struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); jmethodID mid; jclass cls; jstring stateString; @@ -2352,7 +3014,7 @@ const char *SDL_GetAndroidExternalStoragePath(void) static char *s_AndroidExternalFilesPath = NULL; if (!s_AndroidExternalFilesPath) { - struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); jmethodID mid; jobject context; jobject fileObject; @@ -2398,7 +3060,7 @@ const char *SDL_GetAndroidCachePath(void) static char *s_AndroidCachePath = NULL; if (!s_AndroidCachePath) { - struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); jmethodID mid; jobject context; jobject fileObject; @@ -2417,7 +3079,7 @@ const char *SDL_GetAndroidCachePath(void) // fileObj = context.getExternalFilesDir(); mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), "getCacheDir", "()Ljava/io/File;"); - fileObject = (*env)->CallObjectMethod(env, context, mid, NULL); + fileObject = (*env)->CallObjectMethod(env, context, mid); if (!fileObject) { SDL_SetError("Couldn't get cache directory"); LocalReferenceHolder_Cleanup(&refs); @@ -2767,6 +3429,7 @@ bool Android_JNI_OpenFileDialog( if (filters) { jclass stringClass = (*env)->FindClass(env, "java/lang/String"); filtersArray = (*env)->NewObjectArray(env, nfilters, stringClass, NULL); + (*env)->DeleteLocalRef(env, stringClass); // Convert to string for (int i = 0; i < nfilters; i++) { diff --git a/libs/SDL3/src/core/android/SDL_android.h b/libs/SDL3/src/core/android/SDL_android.h index 925cc199..eedb6617 100644 --- a/libs/SDL3/src/core/android/SDL_android.h +++ b/libs/SDL3/src/core/android/SDL_android.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -61,13 +61,12 @@ void Android_SetAllowRecreateActivity(bool enabled); extern void Android_JNI_SetActivityTitle(const char *title); extern void Android_JNI_SetWindowStyle(bool fullscreen); extern void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint); -extern void Android_JNI_MinizeWindow(void); +extern void Android_JNI_MinimizeWindow(void); extern bool Android_JNI_ShouldMinimizeOnFocusLoss(void); extern bool Android_JNI_GetAccelerometerValues(float values[3]); extern void Android_JNI_ShowScreenKeyboard(int input_type, SDL_Rect *inputRect); extern void Android_JNI_HideScreenKeyboard(void); -extern bool Android_JNI_IsScreenKeyboardShown(void); extern ANativeWindow *Android_JNI_GetNativeWindow(void); extern SDL_DisplayOrientation Android_JNI_GetDisplayNaturalOrientation(void); @@ -88,6 +87,8 @@ Sint64 Android_JNI_FileSeek(void *userdata, Sint64 offset, SDL_IOWhence whence); size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOStatus *status); size_t Android_JNI_FileWrite(void *userdata, const void *buffer, size_t size, SDL_IOStatus *status); bool Android_JNI_FileClose(void *userdata); +bool Android_JNI_EnumerateAssetDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata); +bool Android_JNI_GetAssetPathInfo(const char *path, SDL_PathInfo *info); // Environment support void Android_JNI_GetManifestEnvironmentVariables(void); @@ -103,6 +104,8 @@ int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seco // Joystick support void Android_JNI_PollInputDevices(void); +void Android_JNI_JoystickSetLED(int device_id, int red, int green, int blue); +void Android_JNI_JoystickSetSensorsEnabled(int device_id, bool enabled); // Haptic support void Android_JNI_PollHapticDevices(void); @@ -150,6 +153,8 @@ int SDL_GetAndroidSDKVersion(void); bool SDL_IsAndroidTablet(void); bool SDL_IsAndroidTV(void); +char *SDL_GetAndroidPackageName(void); // this is a SDL_malloc'd string the caller will own. + // File Dialogs bool Android_JNI_OpenFileDialog(SDL_DialogFileCallback callback, void *userdata, const SDL_DialogFileFilter *filters, int nfilters, bool forwrite, diff --git a/libs/SDL3/src/core/freebsd/SDL_evdev_kbd_freebsd.c b/libs/SDL3/src/core/freebsd/SDL_evdev_kbd_freebsd.c index afd3ad05..17c8ddff 100644 --- a/libs/SDL3/src/core/freebsd/SDL_evdev_kbd_freebsd.c +++ b/libs/SDL3/src/core/freebsd/SDL_evdev_kbd_freebsd.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -27,19 +27,26 @@ // This logic is adapted from drivers/tty/vt/keyboard.c in the Linux kernel source, slightly modified to work with FreeBSD +#include #include #include #include #include #include +#include #include #include "../../events/SDL_events_c.h" #include "SDL_evdev_kbd_default_keyaccmap.h" +#ifndef K_OFF +#define K_OFF 0x04 +#endif + typedef void(fn_handler_fn)(SDL_EVDEV_keyboard_state *kbd); + /* * Keyboard State */ @@ -48,6 +55,7 @@ struct SDL_EVDEV_keyboard_state { int console_fd; int keyboard_fd; + bool muted; unsigned long old_kbd_mode; unsigned short **key_maps; keymap_t *key_map; @@ -63,6 +71,10 @@ struct SDL_EVDEV_keyboard_state char shift_state; char text[128]; unsigned int text_len; + void (*vt_release_callback)(void *); + void *vt_release_callback_data; + void (*vt_acquire_callback)(void *); + void *vt_acquire_callback_data; }; static bool SDL_EVDEV_kbd_load_keymaps(SDL_EVDEV_keyboard_state *kbd) @@ -83,22 +95,28 @@ static int fatal_signals[] = { SIGSYS }; -static void kbd_cleanup(void) +static void vt_update_mouse(SDL_EVDEV_keyboard_state *kbd, int operation) { struct mouse_info mData; + + SDL_zero(mData); + mData.operation = operation; + ioctl(kbd->console_fd, CONS_MOUSECTL, &mData); +} + +static void kbd_cleanup(void) +{ SDL_EVDEV_keyboard_state *kbd = kbd_cleanup_state; if (!kbd) { return; } kbd_cleanup_state = NULL; - SDL_zero(mData); - mData.operation = MOUSE_SHOW; ioctl(kbd->keyboard_fd, KDSKBMODE, kbd->old_kbd_mode); if (kbd->keyboard_fd != kbd->console_fd) { close(kbd->keyboard_fd); } ioctl(kbd->console_fd, CONS_SETKBD, (unsigned long)(kbd->kbInfo->kb_index)); - ioctl(kbd->console_fd, CONS_MOUSECTL, &mData); + vt_update_mouse(kbd, true); } void SDL_EVDEV_kbd_reraise_signal(int sig) @@ -144,7 +162,7 @@ static void kbd_unregister_emerg_cleanup(void) } kbd_cleanup_sigactions_installed = 0; - for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) { + for (tabidx = 0; tabidx < SDL_arraysize(fatal_signals); ++tabidx) { struct sigaction *old_action_p; struct sigaction cur_action; int signum = fatal_signals[tabidx]; @@ -197,7 +215,7 @@ static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state *kbd) } kbd_cleanup_sigactions_installed = 1; - for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) { + for (tabidx = 0; tabidx < SDL_arraysize(fatal_signals); ++tabidx) { struct sigaction *old_action_p; struct sigaction new_action; int signum = fatal_signals[tabidx]; @@ -220,15 +238,108 @@ static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state *kbd) } } +enum { + VT_SIGNAL_NONE, + VT_SIGNAL_RELEASE, + VT_SIGNAL_ACQUIRE, +}; +static int vt_release_signal; +static int vt_acquire_signal; +static SDL_AtomicInt vt_signal_pending; +SDL_AtomicInt vt_current; + +typedef void (*signal_handler)(int signum); + + +static void kbd_vt_release_signal_action(int signum) +{ + SDL_SetAtomicInt(&vt_signal_pending, VT_SIGNAL_RELEASE); + SDL_SetAtomicInt(&vt_current, VT_THEIRS); +} + +static void kbd_vt_acquire_signal_action(int signum) +{ + SDL_SetAtomicInt(&vt_signal_pending, VT_SIGNAL_ACQUIRE); + SDL_SetAtomicInt(&vt_current, VT_OURS); +} + +static bool setup_vt_signal(int signum, signal_handler handler) +{ + struct sigaction *old_action_p; + struct sigaction new_action; + old_action_p = &(old_sigaction[signum]); + SDL_zero(new_action); + new_action.sa_handler = handler; + new_action.sa_flags = SA_RESTART; + if (sigaction(signum, &new_action, old_action_p) < 0) { + return false; + } + if (old_action_p->sa_handler != SIG_DFL) { + // This signal is already in use + if (signum == SIGUSR1 || signum == SIGUSR2) { + /* + * For the moment we have no other options in FreeBSD because + * the vt(4) will not accept signal numbers over 32. + */ + return true; + } + sigaction(signum, old_action_p, NULL); + return false; + } + return true; +} + +static void kbd_vt_quit(int console_fd) +{ + struct vt_mode mode; + + if (vt_release_signal) { + sigaction(vt_release_signal, &old_sigaction[vt_release_signal], NULL); + vt_release_signal = 0; + } + if (vt_acquire_signal) { + sigaction(vt_acquire_signal, &old_sigaction[vt_acquire_signal], NULL); + vt_acquire_signal = 0; + } + + SDL_zero(mode); + mode.mode = VT_AUTO; + ioctl(console_fd, VT_SETMODE, &mode); +} + +static bool kbd_vt_init(int console_fd) +{ + struct vt_mode mode; + + if (setup_vt_signal(SIGUSR1, kbd_vt_release_signal_action)) { + vt_release_signal = SIGUSR1; + } + if (setup_vt_signal(SIGUSR2, kbd_vt_acquire_signal_action)) { + vt_acquire_signal = SIGUSR2; + } + if (!vt_release_signal || !vt_acquire_signal ) { + kbd_vt_quit(console_fd); + return false; + } + + SDL_zero(mode); + mode.mode = VT_PROCESS; + mode.relsig = vt_release_signal; + mode.acqsig = vt_acquire_signal; + mode.frsig = SIGIO; + if (ioctl(console_fd, VT_SETMODE, &mode) < 0) { + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed VT_SETMODE ioctl: %s", strerror(errno)); + kbd_vt_quit(console_fd); + return false; + } + return true; +} + SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void) { SDL_EVDEV_keyboard_state *kbd; - struct mouse_info mData; char flag_state; - char *devicePath; - SDL_zero(mData); - mData.operation = MOUSE_HIDE; kbd = (SDL_EVDEV_keyboard_state *)SDL_calloc(1, sizeof(SDL_EVDEV_keyboard_state)); if (!kbd) { return NULL; @@ -246,7 +357,6 @@ SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void) kbd->kbInfo = SDL_calloc(1, sizeof(keyboard_info_t)); ioctl(kbd->console_fd, KDGKBINFO, kbd->kbInfo); - ioctl(kbd->console_fd, CONS_MOUSECTL, &mData); if (ioctl(kbd->console_fd, KDGKBSTATE, &flag_state) == 0) { kbd->ledflagstate = flag_state; @@ -265,43 +375,31 @@ SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void) kbd->key_map = &keymap_default_us_acc; } - if (SDL_GetHintBoolean(SDL_HINT_MUTE_CONSOLE_KEYBOARD, true)) { - /* Take keyboard from console and open the actual keyboard device. - * Ensures that the keystrokes do not leak through to the console. - */ - ioctl(kbd->console_fd, CONS_RELKBD, 1ul); - SDL_asprintf(&devicePath, "/dev/kbd%d", kbd->kbInfo->kb_index); - kbd->keyboard_fd = open(devicePath, O_WRONLY | O_CLOEXEC); - if (kbd->keyboard_fd == -1) { - // Give keyboard back. - ioctl(kbd->console_fd, CONS_SETKBD, (unsigned long)(kbd->kbInfo->kb_index)); - kbd->keyboard_fd = kbd->console_fd; - } + if (!kbd_vt_init(kbd->console_fd)) { + SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "kbd_vt_init failed"); + } - /* Make sure to restore keyboard if application fails to call - * SDL_Quit before exit or fatal signal is raised. - */ - if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, false)) { - kbd_register_emerg_cleanup(kbd); - } - SDL_free(devicePath); - } else - kbd->keyboard_fd = kbd->console_fd; + kbd->keyboard_fd = kbd->console_fd; + + if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, false)) { + kbd_register_emerg_cleanup(kbd); + } } + vt_update_mouse(kbd, MOUSE_HIDE); + return kbd; } void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *kbd) { - struct mouse_info mData; - if (!kbd) { return; } - SDL_zero(mData); - mData.operation = MOUSE_SHOW; - ioctl(kbd->console_fd, CONS_MOUSECTL, &mData); + + kbd_vt_quit(kbd->console_fd); + + vt_update_mouse(kbd, MOUSE_SHOW); kbd_unregister_emerg_cleanup(); @@ -322,14 +420,83 @@ void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *kbd) void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted) { + struct termios tios; + + SDL_zero(tios); + + if (!state) { + return; + } + + if (muted == state->muted) { + return; + } + + if (tcgetattr(state->console_fd, &tios) == -1) { + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Could not get terminal mode: %s", strerror(errno)); + return; + } + + if (muted) { + if (SDL_GetHintBoolean(SDL_HINT_MUTE_CONSOLE_KEYBOARD, true)) { + ioctl(state->console_fd, KDSKBMODE, K_OFF); + cfmakeraw(&tios); + + if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, false)) { + kbd_register_emerg_cleanup(state); + } + } + } else { + kbd_unregister_emerg_cleanup(); + + cfmakesane(&tios); + ioctl(state->console_fd, KDSKBMODE, state->old_kbd_mode); + } + + if (tcsetattr(state->console_fd, TCSAFLUSH, &tios) == -1) { + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Could not set terminal mode to %s: %s", muted ? "raw" : "sane", strerror(errno)); + return; + } + + state->muted = muted; } void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void *), void *release_callback_data, void (*acquire_callback)(void *), void *acquire_callback_data) { + if (state == NULL) { + return; + } + + state->vt_release_callback = release_callback; + state->vt_release_callback_data = release_callback_data; + state->vt_acquire_callback = acquire_callback; + state->vt_acquire_callback_data = acquire_callback_data; } void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state) { + if (!state) { + return; + } + + int signal_pending = SDL_GetAtomicInt(&vt_signal_pending); + + if (signal_pending != VT_SIGNAL_NONE) { + if (signal_pending == VT_SIGNAL_RELEASE) { + if (state->vt_release_callback) { + vt_update_mouse(state, MOUSE_SHOW); + state->vt_release_callback(state->vt_release_callback_data); + } + ioctl(state->console_fd, VT_RELDISP, 1); + } else { + if (state->vt_acquire_callback) { + state->vt_acquire_callback(state->vt_acquire_callback_data); + vt_update_mouse(state, MOUSE_HIDE); + } + ioctl(state->console_fd, VT_RELDISP, VT_ACKACQ); + } + SDL_CompareAndSwapAtomicInt(&vt_signal_pending, signal_pending, VT_SIGNAL_NONE); + } } /* diff --git a/libs/SDL3/src/core/gdk/SDL_gdk.cpp b/libs/SDL3/src/core/gdk/SDL_gdk.cpp index 4e792ef2..c4495271 100644 --- a/libs/SDL3/src/core/gdk/SDL_gdk.cpp +++ b/libs/SDL3/src/core/gdk/SDL_gdk.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/gdk/SDL_gdk.h b/libs/SDL3/src/core/gdk/SDL_gdk.h index 14f4e6f1..37d470c0 100644 --- a/libs/SDL3/src/core/gdk/SDL_gdk.h +++ b/libs/SDL3/src/core/gdk/SDL_gdk.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/haiku/SDL_BApp.h b/libs/SDL3/src/core/haiku/SDL_BApp.h index d540c36d..4e6e9c9f 100644 --- a/libs/SDL3/src/core/haiku/SDL_BApp.h +++ b/libs/SDL3/src/core/haiku/SDL_BApp.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/haiku/SDL_BeApp.cc b/libs/SDL3/src/core/haiku/SDL_BeApp.cc index 4737f48e..e1f7d5d2 100644 --- a/libs/SDL3/src/core/haiku/SDL_BeApp.cc +++ b/libs/SDL3/src/core/haiku/SDL_BeApp.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/haiku/SDL_BeApp.h b/libs/SDL3/src/core/haiku/SDL_BeApp.h index de5bb6bf..178adab4 100644 --- a/libs/SDL3/src/core/haiku/SDL_BeApp.h +++ b/libs/SDL3/src/core/haiku/SDL_BeApp.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/linux/SDL_dbus.c b/libs/SDL3/src/core/linux/SDL_dbus.c index b61a1cd9..cd7f77a1 100644 --- a/libs/SDL3/src/core/linux/SDL_dbus.c +++ b/libs/SDL3/src/core/linux/SDL_dbus.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -22,14 +22,25 @@ #include "SDL_dbus.h" #include "../../stdlib/SDL_vacopy.h" +#include +#include + #ifdef SDL_USE_LIBDBUS // we never link directly to libdbus. -static const char *dbus_library = "libdbus-1.so.3"; +#define SDL_DRIVER_DBUS_DYNAMIC "libdbus-1.so.3" +static const char *dbus_library = SDL_DRIVER_DBUS_DYNAMIC; static SDL_SharedObject *dbus_handle = NULL; static char *inhibit_handle = NULL; static unsigned int screensaver_cookie = 0; static SDL_DBusContext dbus; +SDL_ELF_NOTE_DLOPEN( + "core-libdbus", + "Support for D-Bus IPC", + SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SDL_DRIVER_DBUS_DYNAMIC +) + static bool LoadDBUSSyms(void) { #define SDL_DBUS_SYM2_OPTIONAL(TYPE, x, y) \ @@ -65,10 +76,12 @@ static bool LoadDBUSSyms(void) SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write); SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write_dispatch); SDL_DBUS_SYM(DBusDispatchStatus (*)(DBusConnection *), connection_dispatch); + SDL_DBUS_SYM(dbus_bool_t (*)(int), type_is_fixed); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_signal); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *), message_has_path); SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *, const char *), message_new_method_call); SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *), message_new_signal); + SDL_DBUS_SYM(void (*)(DBusMessage *, dbus_bool_t), message_set_no_reply); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, ...), message_append_args); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, va_list), message_append_args_valist); SDL_DBUS_SYM(void (*)(DBusMessage *, DBusMessageIter *), message_iter_init_append); @@ -221,7 +234,7 @@ SDL_DBusContext *SDL_DBus_GetContext(void) return (dbus_handle && dbus.session_conn) ? &dbus : NULL; } -static bool SDL_DBus_CallMethodInternal(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, va_list ap) +static bool SDL_DBus_CallMethodInternal(DBusConnection *conn, DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *method, va_list ap) { bool result = false; @@ -253,7 +266,11 @@ static bool SDL_DBus_CallMethodInternal(DBusConnection *conn, const char *node, if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_get_args_valist(reply, NULL, firstarg, ap_reply)) { result = true; } - dbus.message_unref(reply); + if (save_reply) { + *save_reply = reply; + } else { + dbus.message_unref(reply); + } } } va_end(ap_reply); @@ -264,22 +281,22 @@ static bool SDL_DBus_CallMethodInternal(DBusConnection *conn, const char *node, return result; } -bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...) +bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *method, ...) { bool result; va_list ap; va_start(ap, method); - result = SDL_DBus_CallMethodInternal(conn, node, path, interface, method, ap); + result = SDL_DBus_CallMethodInternal(conn, save_reply, node, path, interface, method, ap); va_end(ap); return result; } -bool SDL_DBus_CallMethod(const char *node, const char *path, const char *interface, const char *method, ...) +bool SDL_DBus_CallMethod(DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *method, ...) { bool result; va_list ap; va_start(ap, method); - result = SDL_DBus_CallMethodInternal(dbus.session_conn, node, path, interface, method, ap); + result = SDL_DBus_CallMethodInternal(dbus.session_conn, save_reply, node, path, interface, method, ap); va_end(ap); return result; } @@ -293,6 +310,7 @@ static bool SDL_DBus_CallVoidMethodInternal(DBusConnection *conn, const char *no if (msg) { int firstarg = va_arg(ap, int); if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_append_args_valist(msg, firstarg, ap)) { + dbus.message_set_no_reply(msg, true); if (dbus.connection_send(conn, msg, NULL)) { dbus.connection_flush(conn); result = true; @@ -306,31 +324,6 @@ static bool SDL_DBus_CallVoidMethodInternal(DBusConnection *conn, const char *no return result; } -static bool SDL_DBus_CallWithBasicReply(DBusConnection *conn, DBusMessage *msg, const int expectedtype, void *result) -{ - bool retval = false; - - DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL); - if (reply) { - DBusMessageIter iter, actual_iter; - dbus.message_iter_init(reply, &iter); - if (dbus.message_iter_get_arg_type(&iter) == DBUS_TYPE_VARIANT) { - dbus.message_iter_recurse(&iter, &actual_iter); - } else { - actual_iter = iter; - } - - if (dbus.message_iter_get_arg_type(&actual_iter) == expectedtype) { - dbus.message_iter_get_basic(&actual_iter, result); - retval = true; - } - - dbus.message_unref(reply); - } - - return retval; -} - bool SDL_DBus_CallVoidMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...) { bool result; @@ -351,7 +344,40 @@ bool SDL_DBus_CallVoidMethod(const char *node, const char *path, const char *int return result; } -bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result) +static bool SDL_DBus_CallWithBasicReply(DBusConnection *conn, DBusMessage **save_reply, DBusMessage *msg, const int expectedtype, void *result) +{ + bool retval = false; + + // Make sure we're not looking up strings here, otherwise we'd have to save and return the reply + SDL_assert(save_reply == NULL || *save_reply == NULL); + SDL_assert(save_reply != NULL || dbus.type_is_fixed(expectedtype)); + + DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL); + if (reply) { + DBusMessageIter iter, actual_iter; + dbus.message_iter_init(reply, &iter); + if (dbus.message_iter_get_arg_type(&iter) == DBUS_TYPE_VARIANT) { + dbus.message_iter_recurse(&iter, &actual_iter); + } else { + actual_iter = iter; + } + + if (dbus.message_iter_get_arg_type(&actual_iter) == expectedtype) { + dbus.message_iter_get_basic(&actual_iter, result); + retval = true; + } + + if (save_reply) { + *save_reply = reply; + } else { + dbus.message_unref(reply); + } + } + + return retval; +} + +bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result) { bool retval = false; @@ -359,7 +385,7 @@ bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, DBusMessage *msg = dbus.message_new_method_call(node, path, "org.freedesktop.DBus.Properties", "Get"); if (msg) { if (dbus.message_append_args(msg, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) { - retval = SDL_DBus_CallWithBasicReply(conn, msg, expectedtype, result); + retval = SDL_DBus_CallWithBasicReply(conn, save_reply, msg, expectedtype, result); } dbus.message_unref(msg); } @@ -368,9 +394,18 @@ bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, return retval; } -bool SDL_DBus_QueryProperty(const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result) +bool SDL_DBus_QueryProperty(DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result) { - return SDL_DBus_QueryPropertyOnConnection(dbus.session_conn, node, path, interface, property, expectedtype, result); + return SDL_DBus_QueryPropertyOnConnection(dbus.session_conn, save_reply, node, path, interface, property, expectedtype, result); +} + +void SDL_DBus_FreeReply(DBusMessage **saved_reply) +{ + DBusMessage *reply = *saved_reply; + if (reply) { + dbus.message_unref(reply); + *saved_reply = NULL; + } } void SDL_DBus_ScreensaverTickle(void) @@ -439,10 +474,119 @@ static bool SDL_DBus_AppendDictWithKeyValue(DBusMessageIter *iterInit, const cha return SDL_DBus_AppendDictWithKeysAndValues(iterInit, keys, values, 1); } +bool SDL_DBus_OpenURI(const char *uri, const char *window_id, const char *activation_token) +{ + static const char *bus_name = "org.freedesktop.portal.Desktop"; + static const char *path = "/org/freedesktop/portal/desktop"; + static const char *interface = "org.freedesktop.portal.OpenURI"; + + if (!dbus.session_conn) { + /* We either lost connection to the session bus or were not able to + * load the D-Bus library at all. + */ + return false; + } + + DBusMessageIter iterInit; + DBusMessage *msg = NULL; + int fd = -1; + bool ret = false; + const bool has_file_scheme = SDL_strncasecmp(uri, "file:/", 6) == 0; + + // The OpenURI method can't open 'file://' URIs or local paths, so OpenFile must be used instead. + if (has_file_scheme || !SDL_IsURI(uri)) { + char *decoded_path = NULL; + + // Decode the path if it is a URI. + if (has_file_scheme) { + const size_t len = SDL_strlen(uri) + 1; + decoded_path = SDL_malloc(len); + if (!decoded_path) { + goto done; + } + if (SDL_URIToLocal(uri, decoded_path) < 0) { + SDL_free(decoded_path); + goto done; + } + uri = decoded_path; + } + fd = open(uri, O_RDWR | O_CLOEXEC); + SDL_free(decoded_path); + if (fd >= 0) { + msg = dbus.message_new_method_call(bus_name, path, interface, "OpenFile"); + } + } else { + msg = dbus.message_new_method_call(bus_name, path, interface, "OpenURI"); + } + if (!msg) { + goto done; + } + + dbus.message_iter_init_append(msg, &iterInit); + + if (!window_id) { + window_id = ""; + } + if (!dbus.message_iter_append_basic(&iterInit, DBUS_TYPE_STRING, &window_id)) { + goto done; + } + + if (fd >= 0) { + if (!dbus.message_iter_append_basic(&iterInit, DBUS_TYPE_UNIX_FD, &fd)) { + goto done; + } + } else { + if (!dbus.message_iter_append_basic(&iterInit, DBUS_TYPE_STRING, &uri)) { + goto done; + } + } + + if (activation_token) { + if (!SDL_DBus_AppendDictWithKeyValue(&iterInit, "activation_token", activation_token)) { + goto done; + } + } else { + // The array must be in the parameter list, even if empty. + DBusMessageIter iterArray; + if (!dbus.message_iter_open_container(&iterInit, DBUS_TYPE_ARRAY, "{sv}", &iterArray)) { + goto done; + } + if (!dbus.message_iter_close_container(&iterInit, &iterArray)) { + goto done; + } + } + + { + DBusMessage *reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, -1, NULL); + if (reply) { + ret = true; + dbus.message_unref(reply); + } + } + +done: + if (msg) { + dbus.message_unref(msg); + } + + // The file descriptor is duplicated by D-Bus, so it can be closed on this end. + if (fd >= 0) { + close(fd); + } + + return ret; +} + bool SDL_DBus_ScreensaverInhibit(bool inhibit) { + static bool interface_unavailable = false; const char *default_inhibit_reason = "Playing a game"; + // If the interface was previously queried and is unavailable, return false. + if (interface_unavailable) { + return false; + } + if ((inhibit && (screensaver_cookie != 0 || inhibit_handle)) || (!inhibit && (screensaver_cookie == 0 && !inhibit_handle))) { return true; } @@ -465,7 +609,7 @@ bool SDL_DBus_ScreensaverInhibit(bool inhibit) DBusMessage *msg; bool result = false; const char *key = "reason"; - const char *reply = NULL; + const char *reply_path = NULL; const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME); if (!reason || !reason[0]) { reason = default_inhibit_reason; @@ -489,11 +633,14 @@ bool SDL_DBus_ScreensaverInhibit(bool inhibit) return false; } - if (SDL_DBus_CallWithBasicReply(dbus.session_conn, msg, DBUS_TYPE_OBJECT_PATH, &reply)) { - inhibit_handle = SDL_strdup(reply); + DBusMessage *reply = NULL; + if (SDL_DBus_CallWithBasicReply(dbus.session_conn, &reply, msg, DBUS_TYPE_OBJECT_PATH, &reply_path)) { + inhibit_handle = SDL_strdup(reply_path); result = true; + } else { + interface_unavailable = true; } - + SDL_DBus_FreeReply(&reply); dbus.message_unref(msg); return result; } else { @@ -515,9 +662,10 @@ bool SDL_DBus_ScreensaverInhibit(bool inhibit) reason = default_inhibit_reason; } - if (!SDL_DBus_CallMethod(bus_name, path, interface, "Inhibit", + if (!SDL_DBus_CallMethod(NULL, bus_name, path, interface, "Inhibit", DBUS_TYPE_STRING, &app, DBUS_TYPE_STRING, &reason, DBUS_TYPE_INVALID, DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) { + interface_unavailable = true; return false; } return (screensaver_cookie != 0); diff --git a/libs/SDL3/src/core/linux/SDL_dbus.h b/libs/SDL3/src/core/linux/SDL_dbus.h index 230b20fd..568dd74b 100644 --- a/libs/SDL3/src/core/linux/SDL_dbus.h +++ b/libs/SDL3/src/core/linux/SDL_dbus.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -64,10 +64,12 @@ typedef struct SDL_DBusContext dbus_bool_t (*connection_read_write)(DBusConnection *, int); dbus_bool_t (*connection_read_write_dispatch)(DBusConnection *, int); DBusDispatchStatus (*connection_dispatch)(DBusConnection *); + dbus_bool_t (*type_is_fixed)(int); dbus_bool_t (*message_is_signal)(DBusMessage *, const char *, const char *); dbus_bool_t (*message_has_path)(DBusMessage *, const char *); DBusMessage *(*message_new_method_call)(const char *, const char *, const char *, const char *); DBusMessage *(*message_new_signal)(const char *, const char *, const char *); + void (*message_set_no_reply)(DBusMessage *, dbus_bool_t); dbus_bool_t (*message_append_args)(DBusMessage *, int, ...); dbus_bool_t (*message_append_args_valist)(DBusMessage *, int, va_list); void (*message_iter_init_append)(DBusMessage *, DBusMessageIter *); @@ -100,18 +102,25 @@ extern void SDL_DBus_Quit(void); extern SDL_DBusContext *SDL_DBus_GetContext(void); // These use the built-in Session connection. -extern bool SDL_DBus_CallMethod(const char *node, const char *path, const char *interface, const char *method, ...); +extern bool SDL_DBus_CallMethod(DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *method, ...); extern bool SDL_DBus_CallVoidMethod(const char *node, const char *path, const char *interface, const char *method, ...); -extern bool SDL_DBus_QueryProperty(const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result); +// save_reply must be non-NULL if it's a string property +extern bool SDL_DBus_QueryProperty(DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result); // These use whatever connection you like. -extern bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...); +extern bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *method, ...); extern bool SDL_DBus_CallVoidMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...); -extern bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result); +// save_reply must be non-NULL if it's a string property +extern bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, DBusMessage **save_reply, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result); + +// Used to free any reply returned from SDL_DBus_CallMethod() and SDL_DBus_QueryProperty() +extern void SDL_DBus_FreeReply(DBusMessage **saved_reply); extern void SDL_DBus_ScreensaverTickle(void); extern bool SDL_DBus_ScreensaverInhibit(bool inhibit); +extern bool SDL_DBus_OpenURI(const char *uri, const char *window_id, const char *activation_token); + extern void SDL_DBus_PumpEvents(void); extern char *SDL_DBus_GetLocalMachineId(void); diff --git a/libs/SDL3/src/core/linux/SDL_evdev.c b/libs/SDL3/src/core/linux/SDL_evdev.c index 902f7e69..a6ef26e5 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev.c +++ b/libs/SDL3/src/core/linux/SDL_evdev.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -32,10 +32,11 @@ #include "SDL_evdev.h" #include "SDL_evdev_kbd.h" -#include -#include +#include #include +#include #include +#include #include #include "../../events/SDL_events_c.h" @@ -322,16 +323,17 @@ void SDL_EVDEV_Poll(void) return; } -#ifdef SDL_USE_LIBUDEV - SDL_UDEV_Poll(); -#endif - SDL_EVDEV_kbd_update(_this->kbd); mouse = SDL_GetMouse(); for (item = _this->first; item; item = item->next) { while ((len = read(item->fd, events, sizeof(events))) > 0) { +#ifdef SDL_INPUT_FBSDKBIO + if (SDL_GetAtomicInt(&vt_current) == VT_THEIRS) { + continue; + } +#endif len /= sizeof(events[0]); for (i = 0; i < len; ++i) { struct input_event *event = &events[i]; @@ -546,16 +548,16 @@ void SDL_EVDEV_Poll(void) * be window-relative in that case. */ switch (item->touchscreen_data->slots[j].delta) { case EVDEV_TOUCH_SLOTDELTA_DOWN: - SDL_SendTouch(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, NULL, SDL_EVENT_FINGER_DOWN, norm_x, norm_y, norm_pressure); + SDL_SendTouch(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, mouse->focus, SDL_EVENT_FINGER_DOWN, norm_x, norm_y, norm_pressure); item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE; break; case EVDEV_TOUCH_SLOTDELTA_UP: - SDL_SendTouch(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, NULL, SDL_EVENT_FINGER_UP, norm_x, norm_y, norm_pressure); + SDL_SendTouch(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, mouse->focus, SDL_EVENT_FINGER_UP, norm_x, norm_y, norm_pressure); item->touchscreen_data->slots[j].tracking_id = 0; item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE; break; case EVDEV_TOUCH_SLOTDELTA_MOVE: - SDL_SendTouchMotion(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, NULL, norm_x, norm_y, norm_pressure); + SDL_SendTouchMotion(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, mouse->focus, norm_x, norm_y, norm_pressure); item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE; break; default: @@ -612,14 +614,14 @@ static bool SDL_EVDEV_init_keyboard(SDL_evdevlist_item *item, int udev_class) name[0] = '\0'; ioctl(item->fd, EVIOCGNAME(sizeof(name)), name); - SDL_AddKeyboard((SDL_KeyboardID)item->fd, name, true); + SDL_AddKeyboard((SDL_KeyboardID)item->fd, name); return true; } static void SDL_EVDEV_destroy_keyboard(SDL_evdevlist_item *item) { - SDL_RemoveKeyboard((SDL_KeyboardID)item->fd, true); + SDL_RemoveKeyboard((SDL_KeyboardID)item->fd); } static bool SDL_EVDEV_init_mouse(SDL_evdevlist_item *item, int udev_class) @@ -631,7 +633,7 @@ static bool SDL_EVDEV_init_mouse(SDL_evdevlist_item *item, int udev_class) name[0] = '\0'; ioctl(item->fd, EVIOCGNAME(sizeof(name)), name); - SDL_AddMouse((SDL_MouseID)item->fd, name, true); + SDL_AddMouse((SDL_MouseID)item->fd, name); ret = ioctl(item->fd, EVIOCGABS(ABS_X), &abs_info); if (ret < 0) { @@ -656,7 +658,7 @@ static bool SDL_EVDEV_init_mouse(SDL_evdevlist_item *item, int udev_class) static void SDL_EVDEV_destroy_mouse(SDL_evdevlist_item *item) { - SDL_RemoveMouse((SDL_MouseID)item->fd, true); + SDL_RemoveMouse((SDL_MouseID)item->fd); } static bool SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) @@ -678,7 +680,8 @@ static bool SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) ret = ioctl(item->fd, EVIOCGNAME(sizeof(name)), name); if (ret < 0) { SDL_free(item->touchscreen_data); - return SDL_SetError("Failed to get evdev touchscreen name"); + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed to get evdev touchscreen name"); + return false; } item->touchscreen_data->name = SDL_strdup(name); @@ -691,7 +694,8 @@ static bool SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) if (ret < 0) { SDL_free(item->touchscreen_data->name); SDL_free(item->touchscreen_data); - return SDL_SetError("Failed to get evdev touchscreen limits"); + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed to get evdev touchscreen limits"); + return false; } if (abs_info.maximum == 0) { @@ -708,7 +712,8 @@ static bool SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) if (ret < 0) { SDL_free(item->touchscreen_data->name); SDL_free(item->touchscreen_data); - return SDL_SetError("Failed to get evdev touchscreen limits"); + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed to get evdev touchscreen limits"); + return false; } item->touchscreen_data->min_x = abs_info.minimum; item->touchscreen_data->max_x = abs_info.maximum; @@ -718,7 +723,8 @@ static bool SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) if (ret < 0) { SDL_free(item->touchscreen_data->name); SDL_free(item->touchscreen_data); - return SDL_SetError("Failed to get evdev touchscreen limits"); + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed to get evdev touchscreen limits"); + return false; } item->touchscreen_data->min_y = abs_info.minimum; item->touchscreen_data->max_y = abs_info.maximum; @@ -728,7 +734,8 @@ static bool SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) if (ret < 0) { SDL_free(item->touchscreen_data->name); SDL_free(item->touchscreen_data); - return SDL_SetError("Failed to get evdev touchscreen limits"); + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed to get evdev touchscreen limits"); + return false; } item->touchscreen_data->min_pressure = abs_info.minimum; item->touchscreen_data->max_pressure = abs_info.maximum; @@ -912,8 +919,9 @@ static bool SDL_EVDEV_device_added(const char *dev_path, int udev_class) item->fd = open(dev_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (item->fd < 0) { + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Couldn't open %s: %s", dev_path, strerror(errno)); SDL_free(item); - return SDL_SetError("Unable to open %s", dev_path); + return false; } item->path = SDL_strdup(dev_path); diff --git a/libs/SDL3/src/core/linux/SDL_evdev.h b/libs/SDL3/src/core/linux/SDL_evdev.h index c3de16fe..8ddf4769 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev.h +++ b/libs/SDL3/src/core/linux/SDL_evdev.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/linux/SDL_evdev_capabilities.c b/libs/SDL3/src/core/linux/SDL_evdev_capabilities.c index 61e240e7..38c891ce 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev_capabilities.c +++ b/libs/SDL3/src/core/linux/SDL_evdev_capabilities.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga Copyright (C) 2020 Collabora Ltd. This software is provided 'as-is', without any express or implied diff --git a/libs/SDL3/src/core/linux/SDL_evdev_capabilities.h b/libs/SDL3/src/core/linux/SDL_evdev_capabilities.h index 5a70bdd6..e7f081e5 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev_capabilities.h +++ b/libs/SDL3/src/core/linux/SDL_evdev_capabilities.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga Copyright (C) 2020 Collabora Ltd. This software is provided 'as-is', without any express or implied diff --git a/libs/SDL3/src/core/linux/SDL_evdev_kbd.c b/libs/SDL3/src/core/linux/SDL_evdev_kbd.c index 32340bff..679f901a 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev_kbd.c +++ b/libs/SDL3/src/core/linux/SDL_evdev_kbd.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -224,7 +224,7 @@ static void kbd_unregister_emerg_cleanup(void) } kbd_cleanup_sigactions_installed = 0; - for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) { + for (tabidx = 0; tabidx < SDL_arraysize(fatal_signals); ++tabidx) { struct sigaction *old_action_p; struct sigaction cur_action; int signum = fatal_signals[tabidx]; @@ -277,7 +277,7 @@ static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state *kbd) } kbd_cleanup_sigactions_installed = 1; - for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) { + for (tabidx = 0; tabidx < SDL_arraysize(fatal_signals); ++tabidx) { struct sigaction *old_action_p; struct sigaction new_action; int signum = fatal_signals[tabidx]; @@ -534,9 +534,7 @@ void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state) if (state->key_maps && state->key_maps != default_key_maps) { int i; for (i = 0; i < MAX_NR_KEYMAPS; ++i) { - if (state->key_maps[i]) { - SDL_free(state->key_maps[i]); - } + SDL_free(state->key_maps[i]); } SDL_free(state->key_maps); } diff --git a/libs/SDL3/src/core/linux/SDL_evdev_kbd.h b/libs/SDL3/src/core/linux/SDL_evdev_kbd.h index b7a2834c..488548ae 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev_kbd.h +++ b/libs/SDL3/src/core/linux/SDL_evdev_kbd.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -22,6 +22,14 @@ #ifndef SDL_evdev_kbd_h_ #define SDL_evdev_kbd_h_ +#ifdef SDL_INPUT_FBSDKBIO +enum { + VT_OURS, + VT_THEIRS, +}; +extern SDL_AtomicInt vt_current; +#endif + struct SDL_EVDEV_keyboard_state; typedef struct SDL_EVDEV_keyboard_state SDL_EVDEV_keyboard_state; diff --git a/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_accents.h b/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_accents.h index b6d034c4..7e3bb293 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_accents.h +++ b/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_accents.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_keymap.h b/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_keymap.h index 05294546..9a23e52b 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_keymap.h +++ b/libs/SDL3/src/core/linux/SDL_evdev_kbd_default_keymap.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/linux/SDL_fcitx.c b/libs/SDL3/src/core/linux/SDL_fcitx.c index d7a9ed66..a13b640d 100644 --- a/libs/SDL3/src/core/linux/SDL_fcitx.c +++ b/libs/SDL3/src/core/linux/SDL_fcitx.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -25,6 +25,7 @@ #include "SDL_fcitx.h" #include "../../video/SDL_sysvideo.h" #include "../../events/SDL_keyboard_c.h" +#include "../../core/unix/SDL_appid.h" #include "SDL_dbus.h" #ifdef SDL_VIDEO_DRIVER_X11 @@ -53,32 +54,14 @@ typedef struct FcitxClient static FcitxClient fcitx_client; -static char *GetAppName(void) +// !!! FIXME: should this just be dumped for src/core/unix's SDL_GetAppID()? +static const char *GetAppName(void) { -#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_FREEBSD) - char *spot; - char procfile[1024]; - char linkfile[1024]; - int linksize; - -#ifdef SDL_PLATFORM_LINUX - (void)SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/exe", getpid()); -#elif defined(SDL_PLATFORM_FREEBSD) - (void)SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/file", getpid()); -#endif - linksize = readlink(procfile, linkfile, sizeof(linkfile) - 1); - if (linksize > 0) { - linkfile[linksize] = '\0'; - spot = SDL_strrchr(linkfile, '/'); - if (spot) { - return SDL_strdup(spot + 1); - } else { - return SDL_strdup(linkfile); - } + const char *exe_name = SDL_GetExeName(); + if (exe_name) { + return exe_name; } -#endif // SDL_PLATFORM_LINUX || SDL_PLATFORM_FREEBSD - - return SDL_strdup("SDL_App"); + return "SDL_App"; } static size_t Fcitx_GetPreeditString(SDL_DBusContext *dbus, @@ -261,7 +244,7 @@ static bool FcitxCreateInputContext(SDL_DBusContext *dbus, const char *appname, DBusMessageIter args, array, sub; dbus->message_iter_init_append(msg, &args); dbus->message_iter_open_container(&args, DBUS_TYPE_ARRAY, "(ss)", &array); - dbus->message_iter_open_container(&array, DBUS_TYPE_STRUCT, 0, &sub); + dbus->message_iter_open_container(&array, DBUS_TYPE_STRUCT, NULL, &sub); dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &program); dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &appname); dbus->message_iter_close_container(&array, &sub); @@ -281,7 +264,7 @@ static bool FcitxCreateInputContext(SDL_DBusContext *dbus, const char *appname, static bool FcitxClientCreateIC(FcitxClient *client) { - char *appname = GetAppName(); + const char *appname = GetAppName(); char *ic_path = NULL; SDL_DBusContext *dbus = client->dbus; @@ -290,8 +273,6 @@ static bool FcitxClientCreateIC(FcitxClient *client) ic_path = NULL; // just in case. } - SDL_free(appname); - if (ic_path) { SDL_free(client->ic_path); client->ic_path = SDL_strdup(ic_path); @@ -390,7 +371,7 @@ bool SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down) return false; } - if (SDL_DBus_CallMethod(FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "ProcessKeyEvent", + if (SDL_DBus_CallMethod(NULL, FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "ProcessKeyEvent", DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &mod_state, DBUS_TYPE_BOOLEAN, &is_release, DBUS_TYPE_UINT32, &event_time, DBUS_TYPE_INVALID, DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID)) { if (handled) { diff --git a/libs/SDL3/src/core/linux/SDL_fcitx.h b/libs/SDL3/src/core/linux/SDL_fcitx.h index 82c544e8..9ea11411 100644 --- a/libs/SDL3/src/core/linux/SDL_fcitx.h +++ b/libs/SDL3/src/core/linux/SDL_fcitx.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/linux/SDL_ibus.c b/libs/SDL3/src/core/linux/SDL_ibus.c index ea58ed57..b0d96eaa 100644 --- a/libs/SDL3/src/core/linux/SDL_ibus.c +++ b/libs/SDL3/src/core/linux/SDL_ibus.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -371,7 +371,7 @@ static char *IBus_GetDBusAddressFilename(void) } } - SDL_memset(config_dir, 0, sizeof(config_dir)); + SDL_zeroa(config_dir); conf_env = SDL_getenv("XDG_CONFIG_HOME"); if (conf_env && *conf_env) { @@ -392,7 +392,7 @@ static char *IBus_GetDBusAddressFilename(void) return NULL; } - SDL_memset(file_path, 0, sizeof(file_path)); + SDL_zeroa(file_path); (void)SDL_snprintf(file_path, sizeof(file_path), "%s/ibus/bus/%s-%s-%s", config_dir, key, host, disp_num); dbus->free(key); @@ -426,6 +426,7 @@ static void SDLCALL IBus_SetCapabilities(void *data, const char *name, const cha static bool IBus_SetupConnection(SDL_DBusContext *dbus, const char *addr) { const char *client_name = "SDL3_Application"; + DBusMessage *reply = NULL; const char *path = NULL; bool result = false; DBusObjectPathVTable ibus_vtable; @@ -442,7 +443,7 @@ static bool IBus_SetupConnection(SDL_DBusContext *dbus, const char *addr) ibus_input_interface = IBUS_PORTAL_INPUT_INTERFACE; ibus_conn = dbus->session_conn; - result = SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, IBUS_PATH, ibus_interface, "CreateInputContext", + result = SDL_DBus_CallMethodOnConnection(ibus_conn, &reply, ibus_service, IBUS_PATH, ibus_interface, "CreateInputContext", DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); if (!result) { @@ -465,7 +466,7 @@ static bool IBus_SetupConnection(SDL_DBusContext *dbus, const char *addr) dbus->connection_flush(ibus_conn); - result = SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, IBUS_PATH, ibus_interface, "CreateInputContext", + result = SDL_DBus_CallMethodOnConnection(ibus_conn, &reply, ibus_service, IBUS_PATH, ibus_interface, "CreateInputContext", DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); } else { @@ -483,6 +484,7 @@ static bool IBus_SetupConnection(SDL_DBusContext *dbus, const char *addr) dbus->connection_try_register_object_path(ibus_conn, input_ctx_path, &ibus_vtable, dbus, NULL); dbus->connection_flush(ibus_conn); } + SDL_DBus_FreeReply(&reply); SDL_Window *window = SDL_GetKeyboardFocus(); if (SDL_TextInputActive(window)) { @@ -563,9 +565,7 @@ bool SDL_IBus_Init(void) return false; } - if (ibus_addr_file) { - SDL_free(ibus_addr_file); - } + SDL_free(ibus_addr_file); ibus_addr_file = SDL_strdup(addr_file); if (inotify_fd < 0) { @@ -637,7 +637,7 @@ void SDL_IBus_Quit(void) SDL_RemoveHintCallback(SDL_HINT_IME_IMPLEMENTED_UI, IBus_SetCapabilities, NULL); - SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect)); + SDL_zero(ibus_cursor_rect); } static void IBus_SimpleMessage(const char *method) @@ -671,7 +671,7 @@ bool SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down) if (!down) { mods |= (1 << 30); // IBUS_RELEASE_MASK } - if (!SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, input_ctx_path, ibus_input_interface, "ProcessKeyEvent", + if (!SDL_DBus_CallMethodOnConnection(ibus_conn, NULL, ibus_service, input_ctx_path, ibus_input_interface, "ProcessKeyEvent", DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &ibus_keycode, DBUS_TYPE_UINT32, &mods, DBUS_TYPE_INVALID, DBUS_TYPE_BOOLEAN, &result, DBUS_TYPE_INVALID)) { result = 0; diff --git a/libs/SDL3/src/core/linux/SDL_ibus.h b/libs/SDL3/src/core/linux/SDL_ibus.h index ba08be69..ceeea6f1 100644 --- a/libs/SDL3/src/core/linux/SDL_ibus.h +++ b/libs/SDL3/src/core/linux/SDL_ibus.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/linux/SDL_ime.c b/libs/SDL3/src/core/linux/SDL_ime.c index c15defb8..9c971b58 100644 --- a/libs/SDL3/src/core/linux/SDL_ime.c +++ b/libs/SDL3/src/core/linux/SDL_ime.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/linux/SDL_ime.h b/libs/SDL3/src/core/linux/SDL_ime.h index ac192fa0..386c7846 100644 --- a/libs/SDL3/src/core/linux/SDL_ime.h +++ b/libs/SDL3/src/core/linux/SDL_ime.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/linux/SDL_progressbar.c b/libs/SDL3/src/core/linux/SDL_progressbar.c index e50f8361..ca66d967 100644 --- a/libs/SDL3/src/core/linux/SDL_progressbar.c +++ b/libs/SDL3/src/core/linux/SDL_progressbar.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -32,7 +32,7 @@ #define UnityLauncherAPI_DBUS_INTERFACE "com.canonical.Unity.LauncherEntry" #define UnityLauncherAPI_DBUS_SIGNAL "Update" -static char *GetDBUSObjectPath() +static char *GetDBUSObjectPath(void) { char *app_id = SDL_strdup(SDL_GetAppID()); @@ -49,20 +49,26 @@ static char *GetDBUSObjectPath() // Ensure it starts with a letter or underscore if (!SDL_isalpha(app_id[0]) && app_id[0] != '_') { + app_id = SDL_realloc(app_id, SDL_strlen(app_id) + 2); + if (!app_id) { + return NULL; + } SDL_memmove(app_id + 1, app_id, SDL_strlen(app_id) + 1); app_id[0] = '_'; } // Create full path - char path[1024]; - SDL_snprintf(path, sizeof(path), "/org/libsdl/%s_%d", app_id, getpid()); + char *path; + if (SDL_asprintf(&path, "/org/libsdl/%s_%d", app_id, getpid()) < 0) { + path = NULL; + } SDL_free(app_id); - return SDL_strdup(path); + return path; } -static char *GetAppDesktopPath() +static char *GetAppDesktopPath(void) { const char *desktop_suffix = ".desktop"; const char *app_id = SDL_GetAppID(); @@ -120,28 +126,26 @@ bool DBUS_ApplyWindowProgress(SDL_VideoDevice *_this, SDL_Window *window) 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_append_basic(&args, DBUS_TYPE_STRING, &desktop_path); // Setup app_uri parameter dbus->message_iter_open_container(&args, DBUS_TYPE_ARRAY, "{sv}", &props); // Setup properties parameter DBusMessageIter key_it, value_it; // Set progress visible property dbus->message_iter_open_container(&props, DBUS_TYPE_DICT_ENTRY, NULL, &key_it); dbus->message_iter_append_basic(&key_it, DBUS_TYPE_STRING, &progress_visible_str); // Append progress-visible key data - dbus->message_iter_open_container(&key_it, DBUS_TYPE_VARIANT, (const char *)&dbus_type_boolean_str, &value_it); + dbus->message_iter_open_container(&key_it, DBUS_TYPE_VARIANT, "b", &value_it); dbus->message_iter_append_basic(&value_it, DBUS_TYPE_BOOLEAN, &progress_visible); // Append progress-visible value data dbus->message_iter_close_container(&key_it, &value_it); dbus->message_iter_close_container(&props, &key_it); // Set progress value property dbus->message_iter_open_container(&props, DBUS_TYPE_DICT_ENTRY, NULL, &key_it); dbus->message_iter_append_basic(&key_it, DBUS_TYPE_STRING, &progress_str); // Append progress key data - dbus->message_iter_open_container(&key_it, DBUS_TYPE_VARIANT, (const char *)&dbus_type_double_str, &value_it); + dbus->message_iter_open_container(&key_it, DBUS_TYPE_VARIANT, "d", &value_it); dbus->message_iter_append_basic(&value_it, DBUS_TYPE_DOUBLE, &progress); // Append progress value data dbus->message_iter_close_container(&key_it, &value_it); dbus->message_iter_close_container(&props, &key_it); diff --git a/libs/SDL3/src/core/linux/SDL_progressbar.h b/libs/SDL3/src/core/linux/SDL_progressbar.h index da9b815f..186e46ab 100644 --- a/libs/SDL3/src/core/linux/SDL_progressbar.h +++ b/libs/SDL3/src/core/linux/SDL_progressbar.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/linux/SDL_system_theme.c b/libs/SDL3/src/core/linux/SDL_system_theme.c index 6d6087db..100661d6 100644 --- a/libs/SDL3/src/core/linux/SDL_system_theme.c +++ b/libs/SDL3/src/core/linux/SDL_system_theme.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/linux/SDL_system_theme.h b/libs/SDL3/src/core/linux/SDL_system_theme.h index 2cd55cd6..683c21c4 100644 --- a/libs/SDL3/src/core/linux/SDL_system_theme.h +++ b/libs/SDL3/src/core/linux/SDL_system_theme.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/linux/SDL_threadprio.c b/libs/SDL3/src/core/linux/SDL_threadprio.c index 1a3e22fa..68b11911 100644 --- a/libs/SDL3/src/core/linux/SDL_threadprio.c +++ b/libs/SDL3/src/core/linux/SDL_threadprio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -70,7 +70,7 @@ static Sint64 rtkit_max_rttime_usec = 200000; static bool realtime_portal_supported(DBusConnection *conn) { Sint64 res; - return SDL_DBus_QueryPropertyOnConnection(conn, XDG_PORTAL_DBUS_NODE, XDG_PORTAL_DBUS_PATH, XDG_PORTAL_DBUS_INTERFACE, + return SDL_DBus_QueryPropertyOnConnection(conn, NULL, XDG_PORTAL_DBUS_NODE, XDG_PORTAL_DBUS_PATH, XDG_PORTAL_DBUS_INTERFACE, "RTTimeUSecMax", DBUS_TYPE_INT64, &res); } @@ -111,19 +111,19 @@ static void rtkit_initialize(void) dbus_conn = get_rtkit_dbus_connection(); // Try getting minimum nice level: this is often greater than PRIO_MIN (-20). - if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MinNiceLevel", + if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, NULL, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MinNiceLevel", DBUS_TYPE_INT32, &rtkit_min_nice_level)) { rtkit_min_nice_level = -20; } // Try getting maximum realtime priority: this can be less than the POSIX default (99). - if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MaxRealtimePriority", + if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, NULL, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MaxRealtimePriority", DBUS_TYPE_INT32, &rtkit_max_realtime_priority)) { rtkit_max_realtime_priority = 99; } // Try getting maximum rttime allowed by rtkit: exceeding this value will result in SIGKILL - if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "RTTimeUSecMax", + if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, NULL, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "RTTimeUSecMax", DBUS_TYPE_INT64, &rtkit_max_rttime_usec)) { rtkit_max_rttime_usec = 200000; } @@ -202,10 +202,10 @@ static bool rtkit_setpriority_nice(pid_t thread, int nice_level) nice = rtkit_min_nice_level; } - if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(dbus_conn, - rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MakeThreadHighPriorityWithPID", - DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &tid, DBUS_TYPE_INT32, &nice, DBUS_TYPE_INVALID, - DBUS_TYPE_INVALID)) { + if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(dbus_conn, NULL, + rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MakeThreadHighPriorityWithPID", + DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &tid, DBUS_TYPE_INT32, &nice, DBUS_TYPE_INVALID, + DBUS_TYPE_INVALID)) { return false; } return true; @@ -233,10 +233,10 @@ static bool rtkit_setpriority_realtime(pid_t thread, int rt_priority) // go through to determine whether it really needs to fail or not. rtkit_initialize_realtime_thread(); - if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(dbus_conn, - rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MakeThreadRealtimeWithPID", - DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &tid, DBUS_TYPE_UINT32, &priority, DBUS_TYPE_INVALID, - DBUS_TYPE_INVALID)) { + if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(dbus_conn, NULL, + rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MakeThreadRealtimeWithPID", + DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &tid, DBUS_TYPE_UINT32, &priority, DBUS_TYPE_INVALID, + DBUS_TYPE_INVALID)) { return false; } return true; diff --git a/libs/SDL3/src/core/linux/SDL_udev.c b/libs/SDL3/src/core/linux/SDL_udev.c index def0e6a8..3aaa0fb4 100644 --- a/libs/SDL3/src/core/linux/SDL_udev.c +++ b/libs/SDL3/src/core/linux/SDL_udev.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -36,7 +36,22 @@ #include "SDL_evdev_capabilities.h" #include "../unix/SDL_poll.h" -static const char *SDL_UDEV_LIBS[] = { "libudev.so.1", "libudev.so.0" }; +#define SDL_UDEV_FALLBACK_LIBS "libudev.so.1", "libudev.so.0" + +static const char *SDL_UDEV_LIBS[] = { SDL_UDEV_FALLBACK_LIBS }; + +#ifdef SDL_UDEV_DYNAMIC +#define SDL_UDEV_DLNOTE_LIBS SDL_UDEV_DYNAMIC, SDL_UDEV_FALLBACK_LIBS +#else +#define SDL_UDEV_DLNOTE_LIBS SDL_UDEV_FALLBACK_LIBS +#endif + +SDL_ELF_NOTE_DLOPEN( + "events-udev", + "Support for events through libudev", + SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SDL_UDEV_DLNOTE_LIBS +) static SDL_UDEV_PrivateData *_this = NULL; @@ -68,6 +83,7 @@ static bool SDL_UDEV_load_syms(void) SDL_UDEV_SYM(udev_device_get_action); SDL_UDEV_SYM(udev_device_get_devnode); + SDL_UDEV_SYM(udev_device_get_driver); SDL_UDEV_SYM(udev_device_get_syspath); SDL_UDEV_SYM(udev_device_get_subsystem); SDL_UDEV_SYM(udev_device_get_parent_with_subsystem_devtype); @@ -219,7 +235,7 @@ bool SDL_UDEV_Scan(void) return true; } -bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class) +bool SDL_UDEV_GetProductInfo(const char *device_path, struct input_id *inpid, int *class, char **driver) { struct stat statbuf; char type; @@ -231,39 +247,46 @@ bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *pr return false; } - if (stat(device_path, &statbuf) == -1) { + if (stat(device_path, &statbuf) < 0) { return false; } if (S_ISBLK(statbuf.st_mode)) { type = 'b'; - } - else if (S_ISCHR(statbuf.st_mode)) { + } else if (S_ISCHR(statbuf.st_mode)) { type = 'c'; - } - else { + } else { return false; } dev = _this->syms.udev_device_new_from_devnum(_this->udev, type, statbuf.st_rdev); - if (!dev) { return false; } val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID"); if (val) { - *vendor = (Uint16)SDL_strtol(val, NULL, 16); + inpid->vendor = (Uint16)SDL_strtol(val, NULL, 16); } val = _this->syms.udev_device_get_property_value(dev, "ID_MODEL_ID"); if (val) { - *product = (Uint16)SDL_strtol(val, NULL, 16); + inpid->product = (Uint16)SDL_strtol(val, NULL, 16); } val = _this->syms.udev_device_get_property_value(dev, "ID_REVISION"); if (val) { - *version = (Uint16)SDL_strtol(val, NULL, 16); + inpid->version = (Uint16)SDL_strtol(val, NULL, 16); + } + + if (driver) { + val = _this->syms.udev_device_get_driver(dev); + if (!val) { + val = _this->syms.udev_device_get_property_value(dev, "ID_USB_DRIVER"); + } + if (val) { + *driver = SDL_strdup(val); + } } class_temp = device_class(dev); @@ -276,6 +299,45 @@ bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *pr return true; } +char *SDL_UDEV_GetProductSerial(const char *device_path) +{ + struct stat statbuf; + char type; + struct udev_device *dev; + const char *val; + char *result = NULL; + + if (!_this) { + return NULL; + } + + if (stat(device_path, &statbuf) < 0) { + return NULL; + } + + if (S_ISBLK(statbuf.st_mode)) { + type = 'b'; + } else if (S_ISCHR(statbuf.st_mode)) { + type = 'c'; + } else { + return NULL; + } + + dev = _this->syms.udev_device_new_from_devnum(_this->udev, type, statbuf.st_rdev); + if (!dev) { + return NULL; + } + + val = _this->syms.udev_device_get_property_value(dev, "ID_SERIAL_SHORT"); + if (val && *val) { + result = SDL_strdup(val); + } + + _this->syms.udev_device_unref(dev); + + return result; +} + void SDL_UDEV_UnloadLibrary(void) { if (!_this) { diff --git a/libs/SDL3/src/core/linux/SDL_udev.h b/libs/SDL3/src/core/linux/SDL_udev.h index 738f4baf..f2b1ce56 100644 --- a/libs/SDL3/src/core/linux/SDL_udev.h +++ b/libs/SDL3/src/core/linux/SDL_udev.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -31,6 +31,7 @@ #endif #include +#include #include #include @@ -56,6 +57,7 @@ typedef struct SDL_UDEV_Symbols { const char *(*udev_device_get_action)(struct udev_device *); const char *(*udev_device_get_devnode)(struct udev_device *); + const char *(*udev_device_get_driver)(struct udev_device *); const char *(*udev_device_get_syspath)(struct udev_device *); const char *(*udev_device_get_subsystem)(struct udev_device *); struct udev_device *(*udev_device_get_parent_with_subsystem_devtype)(struct udev_device *udev_device, const char *subsystem, const char *devtype); @@ -102,7 +104,8 @@ extern void SDL_UDEV_UnloadLibrary(void); extern bool SDL_UDEV_LoadLibrary(void); extern void SDL_UDEV_Poll(void); extern bool SDL_UDEV_Scan(void); -extern bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class); +extern bool SDL_UDEV_GetProductInfo(const char *device_path, struct input_id *inpid, int *class, char **driver); +extern char *SDL_UDEV_GetProductSerial(const char *device_path); extern bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb); extern void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb); extern const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void); diff --git a/libs/SDL3/src/core/ngage/SDL_ngage.cpp b/libs/SDL3/src/core/ngage/SDL_ngage.cpp index 5f14af1d..518d43fd 100644 --- a/libs/SDL3/src/core/ngage/SDL_ngage.cpp +++ b/libs/SDL3/src/core/ngage/SDL_ngage.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -36,13 +36,13 @@ bool NGAGE_IsClassicModel() return (0x101f8c19 == phone_id); } -void NGAGE_printf(const char *fmt, ...) +void NGAGE_DebugPrintf(const char *fmt, ...) { char buffer[512] = { 0 }; va_list ap; va_start(ap, fmt); - vsprintf(buffer, fmt, ap); + (void)SDL_vsnprintf(buffer, sizeof(buffer), fmt, ap); va_end(ap); TBuf<512> buf; @@ -51,21 +51,6 @@ void NGAGE_printf(const char *fmt, ...) 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; diff --git a/libs/SDL3/src/core/ngage/SDL_ngage.h b/libs/SDL3/src/core/ngage/SDL_ngage.h index 66c4c60e..52f3902f 100644 --- a/libs/SDL3/src/core/ngage/SDL_ngage.h +++ b/libs/SDL3/src/core/ngage/SDL_ngage.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -28,6 +28,7 @@ extern "C" { #endif bool NGAGE_IsClassicModel(); +void NGAGE_DebugPrintf(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(1); #ifdef __cplusplus } diff --git a/libs/SDL3/src/core/openbsd/SDL_wscons.h b/libs/SDL3/src/core/openbsd/SDL_wscons.h index f9b7bf88..a20220b1 100644 --- a/libs/SDL3/src/core/openbsd/SDL_wscons.h +++ b/libs/SDL3/src/core/openbsd/SDL_wscons.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/openbsd/SDL_wscons_kbd.c b/libs/SDL3/src/core/openbsd/SDL_wscons_kbd.c index 5a71044f..3e3a104c 100644 --- a/libs/SDL3/src/core/openbsd/SDL_wscons_kbd.c +++ b/libs/SDL3/src/core/openbsd/SDL_wscons_kbd.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -433,7 +433,7 @@ static SDL_WSCONS_input_data *SDL_WSCONS_Init_Keyboard(const char *dev) } input->keyboardID = SDL_GetNextObjectID(); - SDL_AddKeyboard(input->keyboardID, NULL, false); + SDL_AddKeyboard(input->keyboardID, NULL); input->keymap.map = SDL_calloc(KS_NUMKEYCODES, sizeof(struct wscons_keymap)); if (!input->keymap.map) { diff --git a/libs/SDL3/src/core/openbsd/SDL_wscons_mouse.c b/libs/SDL3/src/core/openbsd/SDL_wscons_mouse.c index 51195f39..1b3dc3e0 100644 --- a/libs/SDL3/src/core/openbsd/SDL_wscons_mouse.c +++ b/libs/SDL3/src/core/openbsd/SDL_wscons_mouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -52,7 +52,7 @@ SDL_WSCONS_mouse_input_data *SDL_WSCONS_Init_Mouse(void) } input->mouseID = SDL_GetNextObjectID(); - SDL_AddMouse(input->mouseID, NULL, false); + SDL_AddMouse(input->mouseID, NULL); #ifdef WSMOUSEIO_SETMODE ioctl(input->fd, WSMOUSEIO_SETMODE, WSMOUSE_COMPAT); diff --git a/libs/SDL3/src/core/unix/SDL_appid.c b/libs/SDL3/src/core/unix/SDL_appid.c index 996e216c..95c5dc1a 100644 --- a/libs/SDL3/src/core/unix/SDL_appid.c +++ b/libs/SDL3/src/core/unix/SDL_appid.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -24,39 +24,6 @@ #include "SDL_appid.h" #include -const char *SDL_GetExeName(void) -{ - static const char *proc_name = NULL; - - // TODO: Use a fallback if BSD has no mounted procfs (OpenBSD has no procfs at all) - if (!proc_name) { -#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_FREEBSD) || defined (SDL_PLATFORM_NETBSD) - static char linkfile[1024]; - int linksize; - -#if defined(SDL_PLATFORM_LINUX) - const char *proc_path = "/proc/self/exe"; -#elif defined(SDL_PLATFORM_FREEBSD) - const char *proc_path = "/proc/curproc/file"; -#elif defined(SDL_PLATFORM_NETBSD) - const char *proc_path = "/proc/curproc/exe"; -#endif - linksize = readlink(proc_path, linkfile, sizeof(linkfile) - 1); - if (linksize > 0) { - linkfile[linksize] = '\0'; - proc_name = SDL_strrchr(linkfile, '/'); - if (proc_name) { - ++proc_name; - } else { - proc_name = linkfile; - } - } -#endif - } - - return proc_name; -} - const char *SDL_GetAppID(void) { const char *id_str = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING); @@ -73,3 +40,4 @@ const char *SDL_GetAppID(void) return id_str; } + diff --git a/libs/SDL3/src/core/unix/SDL_appid.h b/libs/SDL3/src/core/unix/SDL_appid.h index 34825212..5f8eb932 100644 --- a/libs/SDL3/src/core/unix/SDL_appid.h +++ b/libs/SDL3/src/core/unix/SDL_appid.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer -Copyright (C) 1997-2025 Sam Lantinga +Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -24,7 +24,6 @@ freely, subject to the following restrictions: #ifndef SDL_appid_h_ #define SDL_appid_h_ -extern const char *SDL_GetExeName(void); extern const char *SDL_GetAppID(void); #endif // SDL_appid_h_ diff --git a/libs/SDL3/src/core/unix/SDL_fribidi.c b/libs/SDL3/src/core/unix/SDL_fribidi.c new file mode 100644 index 00000000..4599c974 --- /dev/null +++ b/libs/SDL3/src/core/unix/SDL_fribidi.c @@ -0,0 +1,171 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef HAVE_FRIBIDI_H + +#include "SDL_fribidi.h" + +#ifdef SDL_FRIBIDI_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "fribidi", + "Bidirectional text support", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_FRIBIDI_DYNAMIC +) +#endif + +SDL_FriBidi *SDL_FriBidi_Create(void) +{ + SDL_FriBidi *fribidi; + + fribidi = (SDL_FriBidi *)SDL_malloc(sizeof(SDL_FriBidi)); + if (!fribidi) { + return NULL; + } + +#ifdef SDL_FRIBIDI_DYNAMIC + #define SDL_FRIBIDI_LOAD_SYM(x, n, t) x = ((t)SDL_LoadFunction(fribidi->lib, n)); if (!x) { SDL_UnloadObject(fribidi->lib); SDL_free(fribidi); return NULL; } + + fribidi->lib = SDL_LoadObject(SDL_FRIBIDI_DYNAMIC); + if (!fribidi->lib) { + SDL_free(fribidi); + return NULL; + } + + SDL_FRIBIDI_LOAD_SYM(fribidi->unicode_to_charset, "fribidi_unicode_to_charset", SDL_FriBidiUnicodeToCharset); + SDL_FRIBIDI_LOAD_SYM(fribidi->charset_to_unicode, "fribidi_charset_to_unicode", SDL_FriBidiCharsetToUnicode); + SDL_FRIBIDI_LOAD_SYM(fribidi->get_bidi_types, "fribidi_get_bidi_types", SDL_FriBidiGetBidiTypes); + SDL_FRIBIDI_LOAD_SYM(fribidi->get_par_direction, "fribidi_get_par_direction", SDL_FriBidiGetParDirection); + SDL_FRIBIDI_LOAD_SYM(fribidi->get_par_embedding_levels, "fribidi_get_par_embedding_levels", SDL_FriBidiGetParEmbeddingLevels); + SDL_FRIBIDI_LOAD_SYM(fribidi->get_joining_types, "fribidi_get_joining_types", SDL_FriBidiGetJoiningTypes); + SDL_FRIBIDI_LOAD_SYM(fribidi->join_arabic, "fribidi_join_arabic", SDL_FriBidiJoinArabic); + SDL_FRIBIDI_LOAD_SYM(fribidi->shape, "fribidi_shape", SDL_FriBidiShape); + SDL_FRIBIDI_LOAD_SYM(fribidi->reorder_line, "fribidi_reorder_line", SDL_FriBidiReorderLine); +#else + fribidi->unicode_to_charset = fribidi_unicode_to_charset; + fribidi->charset_to_unicode = fribidi_charset_to_unicode; + fribidi->get_bidi_types = fribidi_get_bidi_types; + fribidi->get_par_direction = fribidi_get_par_direction; + fribidi->get_par_embedding_levels = fribidi_get_par_embedding_levels; + fribidi->get_joining_types = fribidi_get_joining_types; + fribidi->join_arabic = fribidi_join_arabic; + fribidi->shape = fribidi_shape; + fribidi->reorder_line = fribidi_reorder_line; +#endif + + return fribidi; +} + +char *SDL_FriBidi_Process(SDL_FriBidi *fribidi, char *utf8, ssize_t utf8_len, bool shaping, FriBidiParType *out_par_type) +{ + FriBidiCharType *types; + FriBidiLevel *levels; + FriBidiArabicProp *props; + FriBidiChar *str; + char *result; + FriBidiStrIndex len; + FriBidiLevel max_level; + FriBidiStrIndex start; + FriBidiStrIndex end; + FriBidiParType direction; + FriBidiParType str_direction; + unsigned int i; + unsigned int c; + + if (!fribidi || !utf8) { + return NULL; + } + + /* Convert to UTF32 */ + if (utf8_len < 0) { + utf8_len = SDL_strlen(utf8); + } + str = SDL_calloc(SDL_utf8strnlen(utf8, utf8_len), sizeof(FriBidiChar)); + len = fribidi->charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, utf8, utf8_len, str); + + /* Setup various BIDI structures */ + direction = FRIBIDI_PAR_LTR; + types = NULL; + levels = NULL; + props = SDL_calloc(len + 1, sizeof(FriBidiArabicProp)); + levels = SDL_calloc(len + 1, sizeof(FriBidiLevel)); + types = SDL_calloc(len + 1, sizeof(FriBidiCharType)); + + /* Shape */ + fribidi->get_bidi_types(str, len, types); + str_direction = fribidi->get_par_direction(types, len); + max_level = fribidi->get_par_embedding_levels(types, len, &direction, levels); + if (shaping) { + fribidi->get_joining_types(str, len, props); + fribidi->join_arabic(types, len, levels, props); + fribidi->shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC, levels, len, props, str); + } + + /* BIDI */ + for (end = 0, start = 0; end < len; end++) { + if (str[end] == '\n' || str[end] == '\r' || str[end] == '\f' || str[end] == '\v' || end == len - 1) { + max_level = fribidi->reorder_line(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC, types, end - start + 1, start, direction, levels, str, NULL); + start = end + 1; + } + } + + /* Silence warning */ + (void)max_level; + + /* Remove fillers */ + for (i = 0, c = 0; i < len; i++) { + if (str[i] != FRIBIDI_CHAR_FILL) { + str[c++] = str[i]; + } + } + len = c; + + /* Convert back to UTF8 */ + result = SDL_malloc(len * 4 + 1); + fribidi->unicode_to_charset(FRIBIDI_CHAR_SET_UTF8, str, len, result); + + /* Cleanup */ + SDL_free(levels); + SDL_free(props); + SDL_free(types); + + /* Return */ + if (out_par_type) { + *out_par_type = str_direction; + } + return result; +} + +void SDL_FriBidi_Destroy(SDL_FriBidi *fribidi) +{ + if (!fribidi) { + return; + } + +#ifdef SDL_FRIBIDI_DYNAMIC + SDL_UnloadObject(fribidi->lib); +#endif + + SDL_free(fribidi); +} + +#endif diff --git a/libs/SDL3/src/core/unix/SDL_fribidi.h b/libs/SDL3/src/core/unix/SDL_fribidi.h new file mode 100644 index 00000000..671ec919 --- /dev/null +++ b/libs/SDL3/src/core/unix/SDL_fribidi.h @@ -0,0 +1,60 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifndef SDL_fribidi_h_ +#define SDL_fribidi_h_ + +#ifdef HAVE_FRIBIDI_H +#include // for ssize_t +#include + +typedef FriBidiStrIndex (*SDL_FriBidiUnicodeToCharset)(FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); +typedef FriBidiStrIndex (*SDL_FriBidiCharsetToUnicode)(FriBidiCharSet, const char *, FriBidiStrIndex, FriBidiChar *); +typedef void (*SDL_FriBidiGetBidiTypes)(const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *); +typedef FriBidiParType (*SDL_FriBidiGetParDirection)(const FriBidiCharType *, const FriBidiStrIndex); +typedef FriBidiLevel (*SDL_FriBidiGetParEmbeddingLevels)(const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, FriBidiLevel *); +typedef void (*SDL_FriBidiGetJoiningTypes)(const FriBidiChar *, const FriBidiStrIndex, FriBidiJoiningType *); +typedef void (*SDL_FriBidiJoinArabic)(const FriBidiCharType *, const FriBidiStrIndex, const FriBidiLevel *, FriBidiArabicProp *); +typedef void (*SDL_FriBidiShape)(FriBidiFlags flags, const FriBidiLevel *, const FriBidiStrIndex, FriBidiArabicProp *, FriBidiChar *str); +typedef FriBidiLevel (*SDL_FriBidiReorderLine)(FriBidiFlags flags, const FriBidiCharType *, const FriBidiStrIndex, const FriBidiStrIndex, const FriBidiParType, FriBidiLevel *, FriBidiChar *, FriBidiStrIndex *); + +typedef struct SDL_FriBidi { + SDL_SharedObject *lib; + SDL_FriBidiUnicodeToCharset unicode_to_charset; + SDL_FriBidiCharsetToUnicode charset_to_unicode; + SDL_FriBidiGetBidiTypes get_bidi_types; + SDL_FriBidiGetParDirection get_par_direction; + SDL_FriBidiGetParEmbeddingLevels get_par_embedding_levels; + SDL_FriBidiGetJoiningTypes get_joining_types; + SDL_FriBidiJoinArabic join_arabic; + SDL_FriBidiShape shape; + SDL_FriBidiReorderLine reorder_line; +} SDL_FriBidi; + +extern SDL_FriBidi *SDL_FriBidi_Create(void); +extern char *SDL_FriBidi_Process(SDL_FriBidi *fribidi, char *utf8, ssize_t utf8_len, bool shaping, FriBidiParType *out_par_type); +extern void SDL_FriBidi_Destroy(SDL_FriBidi *fribidi); + +#endif // HAVE_FRIBIDI_H + +#endif // SDL_fribidi_h_ diff --git a/libs/SDL3/src/core/unix/SDL_gtk.c b/libs/SDL3/src/core/unix/SDL_gtk.c new file mode 100644 index 00000000..92e8a16f --- /dev/null +++ b/libs/SDL3/src/core/unix/SDL_gtk.c @@ -0,0 +1,300 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" +#include "SDL_gtk.h" + +#include +#include +#include + +#define SDL_GTK_SYM2_OPTIONAL(ctx, lib, sub, fn, sym) \ + ctx.sub.fn = (void *)SDL_LoadFunction(lib, #sym) + +#define SDL_GTK_SYM2(ctx, lib, sub, fn, sym) \ + SDL_GTK_SYM2_OPTIONAL(ctx, lib, sub, fn, sym); \ + if (!ctx.sub.fn) { \ + return SDL_SetError("Could not load GTK functions"); \ + } + +#define SDL_GTK_SYM_OPTIONAL(ctx, lib, sub, fn) \ + SDL_GTK_SYM2_OPTIONAL(ctx, lib, sub, fn, sub##_##fn) + +#define SDL_GTK_SYM(ctx, lib, sub, fn) \ + SDL_GTK_SYM2(ctx, lib, sub, fn, sub##_##fn) + +#ifdef SDL_PLATFORM_OPENBSD +#define GDK3_LIB "libgdk-3.so" +#else +#define GDK3_LIB "libgdk-3.so.0" +#endif + +#ifdef SDL_PLATFORM_OPENBSD +#define GTK3_LIB "libgtk-3.so" +#else +#define GTK3_LIB "libgtk-3.so.0" +#endif + +// we never link directly to gtk +static void *libgdk = NULL; +static void *libgtk = NULL; + +static SDL_GtkContext gtk; +static GMainContext *sdl_main_context; + +static gulong signal_connect(gpointer instance, const gchar *detailed_signal, void *c_handler, gpointer data) +{ + return gtk.g.signal_connect_data(instance, detailed_signal, SDL_G_CALLBACK(c_handler), data, NULL, (SDL_GConnectFlags)0); +} + +static void QuitGtk(void) +{ + if (sdl_main_context) { + gtk.g.main_context_unref(sdl_main_context); + sdl_main_context = NULL; + } + + SDL_UnloadObject(libgdk); + SDL_UnloadObject(libgtk); + + libgdk = NULL; + libgtk = NULL; +} + +static bool IsGtkInit(void) +{ + return libgdk != NULL && libgtk != NULL; +} + +#ifndef HAVE_GETRESUID +// Non-POSIX, but Linux and some BSDs have it. +// To reduce the number of code paths, if getresuid() isn't available at +// compile-time, we behave as though it existed but failed at runtime. +static inline int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) { + errno = ENOSYS; + return -1; +} +#endif + +#ifndef HAVE_GETRESGID +// Same as getresuid() but for the primary group +static inline int getresgid(uid_t *ruid, uid_t *euid, uid_t *suid) { + errno = ENOSYS; + return -1; +} +#endif + +bool SDL_CanUseGtk(void) +{ + // "Real", "effective" and "saved" IDs: see e.g. Linux credentials(7) + uid_t ruid = -1, euid = -1, suid = -1; + gid_t rgid = -1, egid = -1, sgid = -1; + + if (!SDL_GetHintBoolean("SDL_ENABLE_GTK", true)) { + SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Not using GTK due to hint"); + return false; + } + + // This is intended to match the check in gtkmain.c, rather than being + // an exhaustive check for having elevated privileges: as a result + // we don't use Linux getauxval() or prctl PR_GET_DUMPABLE, + // BSD issetugid(), or similar OS-specific detection + + if (getresuid(&ruid, &euid, &suid) != 0) { + ruid = suid = getuid(); + euid = geteuid(); + } + + if (getresgid(&rgid, &egid, &sgid) != 0) { + rgid = sgid = getgid(); + egid = getegid(); + } + + // Real ID != effective ID means we are setuid or setgid: + // GTK will refuse to initialize, and instead will call exit(). + if (ruid != euid || rgid != egid) { + SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Not using GTK due to setuid/setgid"); + return false; + } + + // Real ID != saved ID means we are setuid or setgid, we previously + // dropped privileges, but we can regain them; this protects against + // accidents but does not protect against arbitrary code execution. + // Again, GTK will refuse to initialize if this is the case. + if (ruid != suid || rgid != sgid) { + SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Not using GTK due to saved uid/gid"); + return false; + } + + return true; +} + +static bool InitGtk(void) +{ + if (!SDL_CanUseGtk()) { + return false; + } + + if (IsGtkInit()) { + return true; + } + + // GTK only allows a single version to be loaded into a process at a time, + // so if there is one already loaded ensure it is the version we use. + void *progress_get_type = dlsym(RTLD_DEFAULT, "gtk_progress_get_type"); + void *misc_get_type = dlsym(RTLD_DEFAULT, "gtk_misc_get_type"); + if (progress_get_type || misc_get_type) { + void *libgtk3 = dlopen(GTK3_LIB, RTLD_NOLOAD | RTLD_LAZY); + if (!libgtk3) { + QuitGtk(); + return SDL_SetError("Could not load GTK-3, another GTK version already present"); + } + + dlclose(libgtk3); + } + + libgdk = SDL_LoadObject(GDK3_LIB); + libgtk = SDL_LoadObject(GTK3_LIB); + + if (!libgdk || !libgtk) { + QuitGtk(); + return SDL_SetError("Could not load GTK libraries"); + } + + SDL_GTK_SYM(gtk, libgtk, gtk, init_check); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_new); + SDL_GTK_SYM(gtk, libgtk, gtk, separator_menu_item_new); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_item_new_with_label); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_item_set_submenu); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_item_get_label); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_item_set_label); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_shell_append); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_shell_insert); + SDL_GTK_SYM(gtk, libgtk, gtk, check_menu_item_new_with_label); + SDL_GTK_SYM(gtk, libgtk, gtk, check_menu_item_get_active); + SDL_GTK_SYM(gtk, libgtk, gtk, check_menu_item_set_active); + SDL_GTK_SYM(gtk, libgtk, gtk, widget_show); + SDL_GTK_SYM(gtk, libgtk, gtk, widget_destroy); + SDL_GTK_SYM(gtk, libgtk, gtk, widget_get_sensitive); + SDL_GTK_SYM(gtk, libgtk, gtk, widget_set_sensitive); + SDL_GTK_SYM(gtk, libgtk, gtk, settings_get_default); + + SDL_GTK_SYM(gtk, libgdk, g, signal_connect_data); + SDL_GTK_SYM(gtk, libgdk, g, mkdtemp); + SDL_GTK_SYM(gtk, libgdk, g, get_user_cache_dir); + SDL_GTK_SYM(gtk, libgdk, g, object_ref); + SDL_GTK_SYM(gtk, libgdk, g, object_ref_sink); + SDL_GTK_SYM(gtk, libgdk, g, object_unref); + SDL_GTK_SYM(gtk, libgdk, g, object_get); + SDL_GTK_SYM(gtk, libgdk, g, signal_handler_disconnect); + SDL_GTK_SYM(gtk, libgdk, g, main_context_push_thread_default); + SDL_GTK_SYM(gtk, libgdk, g, main_context_pop_thread_default); + SDL_GTK_SYM(gtk, libgdk, g, main_context_new); + SDL_GTK_SYM(gtk, libgdk, g, main_context_unref); + SDL_GTK_SYM(gtk, libgdk, g, main_context_acquire); + SDL_GTK_SYM(gtk, libgdk, g, main_context_iteration); + + gtk.g.signal_connect = signal_connect; + + if (gtk.gtk.init_check(NULL, NULL) == GTK_FALSE) { + QuitGtk(); + return SDL_SetError("Could not init GTK"); + } + + sdl_main_context = gtk.g.main_context_new(); + if (!sdl_main_context) { + QuitGtk(); + return SDL_SetError("Could not create GTK context"); + } + + if (!gtk.g.main_context_acquire(sdl_main_context)) { + QuitGtk(); + return SDL_SetError("Could not acquire GTK context"); + } + + return true; +} + +static SDL_InitState gtk_init; + +bool SDL_Gtk_Init(void) +{ + static bool is_gtk_available = true; + + if (!is_gtk_available) { + return false; // don't keep trying if this fails. + } + + if (SDL_ShouldInit(>k_init)) { + if (InitGtk()) { + SDL_SetInitialized(>k_init, true); + } else { + is_gtk_available = false; + SDL_SetInitialized(>k_init, true); + SDL_Gtk_Quit(); + } + } + + return IsGtkInit(); +} + +void SDL_Gtk_Quit(void) +{ + if (!SDL_ShouldQuit(>k_init)) { + return; + } + + QuitGtk(); + SDL_zero(gtk); + + SDL_SetInitialized(>k_init, false); +} + +SDL_GtkContext *SDL_Gtk_GetContext(void) +{ + return IsGtkInit() ? >k : NULL; +} + +SDL_GtkContext *SDL_Gtk_EnterContext(void) +{ + SDL_Gtk_Init(); + + if (IsGtkInit()) { + gtk.g.main_context_push_thread_default(sdl_main_context); + return >k; + } + + return NULL; +} + +void SDL_Gtk_ExitContext(SDL_GtkContext *ctx) +{ + if (ctx) { + ctx->g.main_context_pop_thread_default(sdl_main_context); + } +} + +void SDL_UpdateGtk(void) +{ + if (IsGtkInit()) { + gtk.g.main_context_iteration(sdl_main_context, GTK_FALSE); + gtk.g.main_context_iteration(NULL, GTK_FALSE); + } +} diff --git a/libs/SDL3/src/core/unix/SDL_gtk.h b/libs/SDL3/src/core/unix/SDL_gtk.h new file mode 100644 index 00000000..663b2851 --- /dev/null +++ b/libs/SDL3/src/core/unix/SDL_gtk.h @@ -0,0 +1,126 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifndef SDL_gtk_h_ +#define SDL_gtk_h_ + +/* Glib 2.0 */ + +typedef unsigned long gulong; +typedef void *gpointer; +typedef char gchar; +typedef int gint; +typedef unsigned int guint; +typedef double gdouble; +typedef gint gboolean; +typedef void (*GCallback)(void); +typedef struct _GClosure GClosure; +typedef void (*GClosureNotify) (gpointer data, GClosure *closure); +typedef gboolean (*GSourceFunc) (gpointer user_data); + +typedef struct _GParamSpec GParamSpec; +typedef struct _GMainContext GMainContext; + +typedef enum SDL_GConnectFlags +{ + SDL_G_CONNECT_DEFAULT = 0, + SDL_G_CONNECT_AFTER = 1 << 0, + SDL_G_CONNECT_SWAPPED = 1 << 1 +} SDL_GConnectFlags; + +#define SDL_G_CALLBACK(f) ((GCallback) (f)) +#define SDL_G_TYPE_CIC(ip, gt, ct) ((ct*) ip) +#define SDL_G_TYPE_CHECK_INSTANCE_CAST(instance, g_type, c_type) (SDL_G_TYPE_CIC ((instance), (g_type), c_type)) + +#define GTK_FALSE 0 +#define GTK_TRUE 1 + + +/* GTK 3.0 */ + +typedef struct _GtkMenu GtkMenu; +typedef struct _GtkMenuItem GtkMenuItem; +typedef struct _GtkMenuShell GtkMenuShell; +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkCheckMenuItem GtkCheckMenuItem; +typedef struct _GtkSettings GtkSettings; + +#define GTK_MENU_ITEM(obj) (SDL_G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU_ITEM, GtkMenuItem)) +#define GTK_WIDGET(widget) (SDL_G_TYPE_CHECK_INSTANCE_CAST ((widget), GTK_TYPE_WIDGET, GtkWidget)) +#define GTK_CHECK_MENU_ITEM(obj) (SDL_G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CHECK_MENU_ITEM, GtkCheckMenuItem)) +#define GTK_MENU(obj) (SDL_G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU, GtkMenu)) + + +typedef struct SDL_GtkContext +{ + /* Glib 2.0 */ + struct + { + gulong (*signal_connect)(gpointer instance, const gchar *detailed_signal, void *c_handler, gpointer data); + gulong (*signal_connect_data)(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data, SDL_GConnectFlags connect_flags); + void (*object_unref)(gpointer object); + gchar *(*mkdtemp)(gchar *template); + gchar *(*get_user_cache_dir)(void); + gpointer (*object_ref_sink)(gpointer object); + gpointer (*object_ref)(gpointer object); + void (*object_get)(gpointer object, const gchar *first_property_name, ...); + void (*signal_handler_disconnect)(gpointer instance, gulong handler_id); + void (*main_context_push_thread_default)(GMainContext *context); + void (*main_context_pop_thread_default)(GMainContext *context); + GMainContext *(*main_context_new)(void); + void (*main_context_unref)(GMainContext *context); + gboolean (*main_context_acquire)(GMainContext *context); + gboolean (*main_context_iteration)(GMainContext *context, gboolean may_block); + } g; + + /* GTK 3.0 */ + struct + { + gboolean (*init_check)(int *argc, char ***argv); + GtkWidget *(*menu_new)(void); + GtkWidget *(*separator_menu_item_new)(void); + GtkWidget *(*menu_item_new_with_label)(const gchar *label); + void (*menu_item_set_submenu)(GtkMenuItem *menu_item, GtkWidget *submenu); + GtkWidget *(*check_menu_item_new_with_label)(const gchar *label); + void (*check_menu_item_set_active)(GtkCheckMenuItem *check_menu_item, gboolean is_active); + void (*widget_set_sensitive)(GtkWidget *widget, gboolean sensitive); + void (*widget_show)(GtkWidget *widget); + void (*menu_shell_append)(GtkMenuShell *menu_shell, GtkWidget *child); + void (*menu_shell_insert)(GtkMenuShell *menu_shell, GtkWidget *child, gint position); + void (*widget_destroy)(GtkWidget *widget); + const gchar *(*menu_item_get_label)(GtkMenuItem *menu_item); + void (*menu_item_set_label)(GtkMenuItem *menu_item, const gchar *label); + gboolean (*check_menu_item_get_active)(GtkCheckMenuItem *check_menu_item); + gboolean (*widget_get_sensitive)(GtkWidget *widget); + GtkSettings *(*settings_get_default)(void); + } gtk; +} SDL_GtkContext; + +extern bool SDL_CanUseGtk(void); +extern bool SDL_Gtk_Init(void); +extern void SDL_Gtk_Quit(void); +extern SDL_GtkContext *SDL_Gtk_EnterContext(void); +extern void SDL_Gtk_ExitContext(SDL_GtkContext *ctx); +extern void SDL_UpdateGtk(void); + +#endif // SDL_gtk_h_ diff --git a/libs/SDL3/src/core/unix/SDL_libthai.c b/libs/SDL3/src/core/unix/SDL_libthai.c new file mode 100644 index 00000000..2edc4f60 --- /dev/null +++ b/libs/SDL3/src/core/unix/SDL_libthai.c @@ -0,0 +1,76 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef HAVE_LIBTHAI_H + +#include "SDL_libthai.h" + +#ifdef SDL_LIBTHAI_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "Thai", + "Thai language support", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_LIBTHAI_DYNAMIC +) +#endif + + +SDL_LibThai *SDL_LibThai_Create(void) +{ + SDL_LibThai *th; + + th = (SDL_LibThai *)SDL_malloc(sizeof(SDL_LibThai)); + if (!th) { + return NULL; + } + +#ifdef SDL_LIBTHAI_DYNAMIC + #define SDL_LIBTHAI_LOAD_SYM(a, x, n, t) x = ((t)SDL_LoadFunction(a->lib, n)); if (!x) { SDL_UnloadObject(a->lib); SDL_free(a); return NULL; } + + th->lib = SDL_LoadObject(SDL_LIBTHAI_DYNAMIC); + if (!th->lib) { + SDL_free(th); + return NULL; + } + + SDL_LIBTHAI_LOAD_SYM(th, th->make_cells, "th_make_cells", SDL_LibThaiMakeCells); +#else + th->make_cells = th_make_cells; +#endif + + return th; +} + +void SDL_LibThai_Destroy(SDL_LibThai *th) +{ + if (!th) { + return; + } + +#ifdef SDL_LIBTHAI_DYNAMIC + SDL_UnloadObject(th->lib); +#endif + + SDL_free(th); +} + +#endif diff --git a/libs/SDL3/src/core/unix/SDL_libthai.h b/libs/SDL3/src/core/unix/SDL_libthai.h new file mode 100644 index 00000000..2cf6da78 --- /dev/null +++ b/libs/SDL3/src/core/unix/SDL_libthai.h @@ -0,0 +1,43 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifndef SDL_libthai_h_ +#define SDL_libthai_h_ + +#ifdef HAVE_LIBTHAI_H +#include + +typedef size_t (*SDL_LibThaiMakeCells)(const thchar_t *s, size_t, struct thcell_t cells[], size_t *, int); + +typedef struct SDL_LibThai { + SDL_SharedObject *lib; + + SDL_LibThaiMakeCells make_cells; +} SDL_LibThai; + +extern SDL_LibThai *SDL_LibThai_Create(void); +extern void SDL_LibThai_Destroy(SDL_LibThai *th); + +#endif // HAVE_LIBTHAI_H + +#endif // SDL_libthai_h_ diff --git a/libs/SDL3/src/core/unix/SDL_poll.c b/libs/SDL3/src/core/unix/SDL_poll.c index 572ed5a6..641fc17c 100644 --- a/libs/SDL3/src/core/unix/SDL_poll.c +++ b/libs/SDL3/src/core/unix/SDL_poll.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -23,15 +23,13 @@ #include "SDL_poll.h" -#ifdef HAVE_POLL #include -#else -#include -#include -#include -#endif #include +#ifdef HAVE_PPOLL +#include +#endif + int SDL_IOReady(int fd, int flags, Sint64 timeoutNS) { int result; @@ -40,9 +38,7 @@ int SDL_IOReady(int fd, int flags, Sint64 timeoutNS) // Note: We don't bother to account for elapsed time if we get EINTR do { -#ifdef HAVE_POLL struct pollfd info; - int timeoutMS; info.fd = fd; info.events = 0; @@ -52,7 +48,21 @@ int SDL_IOReady(int fd, int flags, Sint64 timeoutNS) if (flags & SDL_IOR_WRITE) { info.events |= POLLOUT; } - // FIXME: Add support for ppoll() for nanosecond precision + +#ifdef HAVE_PPOLL + struct timespec *timeout = NULL; + struct timespec ts; + + if (timeoutNS >= 0) { + ts.tv_sec = SDL_NS_TO_SECONDS(timeoutNS); + ts.tv_nsec = timeoutNS - SDL_SECONDS_TO_NS(ts.tv_sec); + timeout = &ts; + } + + result = ppoll(&info, 1, timeout, NULL); +#else + int timeoutMS; + if (timeoutNS > 0) { timeoutMS = (int)SDL_NS_TO_MS(timeoutNS + (SDL_NS_PER_MS - 1)); } else if (timeoutNS == 0) { @@ -61,34 +71,7 @@ int SDL_IOReady(int fd, int flags, Sint64 timeoutNS) timeoutMS = -1; } result = poll(&info, 1, timeoutMS); -#else - fd_set rfdset, *rfdp = NULL; - fd_set wfdset, *wfdp = NULL; - struct timeval tv, *tvp = NULL; - - // If this assert triggers we'll corrupt memory here - SDL_assert(fd >= 0 && fd < FD_SETSIZE); - - if (flags & SDL_IOR_READ) { - FD_ZERO(&rfdset); - FD_SET(fd, &rfdset); - rfdp = &rfdset; - } - if (flags & SDL_IOR_WRITE) { - FD_ZERO(&wfdset); - FD_SET(fd, &wfdset); - wfdp = &wfdset; - } - - if (timeoutNS >= 0) { - tv.tv_sec = (timeoutNS / SDL_NS_PER_SECOND); - tv.tv_usec = SDL_NS_TO_US((timeoutNS % SDL_NS_PER_SECOND) + (SDL_NS_PER_US - 1)); - tvp = &tv; - } - - result = select(fd + 1, rfdp, wfdp, NULL, tvp); -#endif // HAVE_POLL - +#endif } while (result < 0 && errno == EINTR && !(flags & SDL_IOR_NO_RETRY)); return result; diff --git a/libs/SDL3/src/core/unix/SDL_poll.h b/libs/SDL3/src/core/unix/SDL_poll.h index 571619d4..5001c617 100644 --- a/libs/SDL3/src/core/unix/SDL_poll.h +++ b/libs/SDL3/src/core/unix/SDL_poll.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/windows/SDL_directx.h b/libs/SDL3/src/core/windows/SDL_directx.h index 6101e922..77b13dd0 100644 --- a/libs/SDL3/src/core/windows/SDL_directx.h +++ b/libs/SDL3/src/core/windows/SDL_directx.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/windows/SDL_gameinput.cpp b/libs/SDL3/src/core/windows/SDL_gameinput.cpp index e2ea3fb4..baf7ed21 100644 --- a/libs/SDL3/src/core/windows/SDL_gameinput.cpp +++ b/libs/SDL3/src/core/windows/SDL_gameinput.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -38,15 +38,15 @@ bool SDL_InitGameInput(IGameInput **ppGameInput) return false; } - typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput **gameInput); - GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate"); - if (!GameInputCreateFunc) { + typedef HRESULT (WINAPI *pfnGameInputCreate)(IGameInput **gameInput); + pfnGameInputCreate pGameInputCreate = (pfnGameInputCreate)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate"); + if (!pGameInputCreate) { SDL_UnloadObject(g_hGameInputDLL); return false; } IGameInput *pGameInput = NULL; - HRESULT hr = GameInputCreateFunc(&pGameInput); + HRESULT hr = pGameInputCreate(&pGameInput); if (FAILED(hr)) { SDL_UnloadObject(g_hGameInputDLL); return WIN_SetErrorFromHRESULT("GameInputCreate failed", hr); diff --git a/libs/SDL3/src/core/windows/SDL_gameinput.h b/libs/SDL3/src/core/windows/SDL_gameinput.h index 4d2beb56..0d7b6f85 100644 --- a/libs/SDL3/src/core/windows/SDL_gameinput.h +++ b/libs/SDL3/src/core/windows/SDL_gameinput.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -31,7 +31,11 @@ #define GAMEINPUT_API_VERSION 0 #endif -#if GAMEINPUT_API_VERSION == 1 +#if GAMEINPUT_API_VERSION == 3 +using namespace GameInput::v3; +#elif GAMEINPUT_API_VERSION == 2 +using namespace GameInput::v2; +#elif GAMEINPUT_API_VERSION == 1 using namespace GameInput::v1; #endif diff --git a/libs/SDL3/src/core/windows/SDL_hid.c b/libs/SDL3/src/core/windows/SDL_hid.c index 87e87354..1f629d58 100644 --- a/libs/SDL3/src/core/windows/SDL_hid.c +++ b/libs/SDL3/src/core/windows/SDL_hid.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -175,12 +175,39 @@ static CM_Register_NotificationFunc CM_Register_Notification; static CM_Unregister_NotificationFunc CM_Unregister_Notification; static HCMNOTIFICATION s_DeviceNotificationFuncHandle; static Uint64 s_LastDeviceNotification = 1; +static HANDLE s_HotplugEvent = INVALID_HANDLE_VALUE; +static SDL_AtomicInt s_HotplugRunning; +static SDL_Thread *s_HotplugThread; + +#ifdef SDL_VIDEO_DRIVER_WINDOWS +// Defined in SDL_windowsevents.c +extern void WIN_CheckKeyboardAndMouseHotplug(bool hid_loaded); +#endif + +static int SDLCALL DeviceHotplugThread(void *unused) +{ + bool hid_loaded = WIN_LoadHIDDLL(); + + // Always run the initial device detection + do { +#ifdef SDL_VIDEO_DRIVER_WINDOWS + WIN_CheckKeyboardAndMouseHotplug(hid_loaded); +#endif + WaitForSingleObject(s_HotplugEvent, INFINITE); + } while (SDL_GetAtomicInt(&s_HotplugRunning)); + + if (hid_loaded) { + WIN_UnloadHIDDLL(); + } + return 0; +} static DWORD CALLBACK SDL_DeviceNotificationFunc(HCMNOTIFICATION hNotify, PVOID context, CM_NOTIFY_ACTION action, PCM_NOTIFY_EVENT_DATA eventData, DWORD event_data_size) { if (action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL || action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL) { s_LastDeviceNotification = SDL_GetTicksNS(); + SetEvent(s_HotplugEvent); } return ERROR_SUCCESS; } @@ -192,6 +219,11 @@ void WIN_InitDeviceNotification(void) return; } + // Start the device hotplug thread + SDL_SetAtomicInt(&s_HotplugRunning, true); + s_HotplugEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + s_HotplugThread = SDL_CreateThread(DeviceHotplugThread, "DeviceHotplugThread", NULL); + cfgmgr32_lib_handle = LoadLibraryA("cfgmgr32.dll"); if (cfgmgr32_lib_handle) { CM_Register_Notification = (CM_Register_NotificationFunc)GetProcAddress(cfgmgr32_lib_handle, "CM_Register_Notification"); @@ -225,6 +257,12 @@ void WIN_QuitDeviceNotification(void) // Make sure we have balanced calls to init/quit SDL_assert(s_DeviceNotificationsRequested == 0); + // Stop the device hotplug thread + SDL_SetAtomicInt(&s_HotplugRunning, false); + SetEvent(s_HotplugEvent); + SDL_WaitThread(s_HotplugThread, NULL); + s_HotplugThread = NULL; + if (cfgmgr32_lib_handle) { if (s_DeviceNotificationFuncHandle && CM_Unregister_Notification) { CM_Unregister_Notification(s_DeviceNotificationFuncHandle); diff --git a/libs/SDL3/src/core/windows/SDL_hid.h b/libs/SDL3/src/core/windows/SDL_hid.h index 46c22f24..f085f683 100644 --- a/libs/SDL3/src/core/windows/SDL_hid.h +++ b/libs/SDL3/src/core/windows/SDL_hid.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/windows/SDL_immdevice.c b/libs/SDL3/src/core/windows/SDL_immdevice.c index 802a412e..64cea6d2 100644 --- a/libs/SDL3/src/core/windows/SDL_immdevice.c +++ b/libs/SDL3/src/core/windows/SDL_immdevice.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -120,7 +120,7 @@ void SDL_IMMDevice_FreeDeviceHandle(SDL_AudioDevice *device) } } -static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid, GUID *dsoundguid) +static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid, GUID *dsoundguid, SDL_AudioFormat force_format, bool supports_recording_playback_devices) { /* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever). In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for @@ -147,7 +147,7 @@ static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devn if (!device) { // handle is freed by SDL_IMMDevice_FreeDeviceHandle! - SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *)SDL_malloc(sizeof(SDL_IMMDevice_HandleData)); + SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *)SDL_calloc(1, sizeof(*handle)); if (!handle) { return NULL; } @@ -156,15 +156,37 @@ static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devn SDL_free(handle); return NULL; } - SDL_memcpy(&handle->directsound_guid, dsoundguid, sizeof(GUID)); + SDL_copyp(&handle->directsound_guid, dsoundguid); SDL_AudioSpec spec; SDL_zero(spec); spec.channels = (Uint8)fmt->Format.nChannels; spec.freq = fmt->Format.nSamplesPerSec; - spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt); + spec.format = (force_format != SDL_AUDIO_UNKNOWN) ? force_format : SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt); device = SDL_AddAudioDevice(recording, devname, &spec, handle); + + if (!recording && supports_recording_playback_devices) { + // handle is freed by SDL_IMMDevice_FreeDeviceHandle! + SDL_IMMDevice_HandleData *recording_handle = (SDL_IMMDevice_HandleData *)SDL_malloc(sizeof(*recording_handle)); + if (!recording_handle) { + return NULL; + } + + recording_handle->immdevice_id = SDL_wcsdup(devid); + if (!recording_handle->immdevice_id) { + SDL_free(recording_handle); + return NULL; + } + + SDL_copyp(&recording_handle->directsound_guid, dsoundguid); + + if (!SDL_AddAudioDevice(true, devname, &spec, recording_handle)) { + SDL_free(recording_handle->immdevice_id); + SDL_free(recording_handle); + } + } + if (!device) { SDL_free(handle->immdevice_id); SDL_free(handle); @@ -183,6 +205,8 @@ typedef struct SDLMMNotificationClient { const IMMNotificationClientVtbl *lpVtbl; SDL_AtomicInt refcount; + SDL_AudioFormat force_format; + bool supports_recording_playback_devices; } SDLMMNotificationClient; static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_QueryInterface(IMMNotificationClient *client, REFIID iid, void **ppv) @@ -241,6 +265,7 @@ static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceRemoved(IMMNoti static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *iclient, LPCWSTR pwstrDeviceId, DWORD dwNewState) { + SDLMMNotificationClient *client = (SDLMMNotificationClient *)iclient; IMMDevice *device = NULL; if (SUCCEEDED(IMMDeviceEnumerator_GetDevice(enumerator, pwstrDeviceId, &device))) { @@ -255,7 +280,7 @@ static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceStateChanged(IM GUID dsoundguid; GetMMDeviceInfo(device, &utf8dev, &fmt, &dsoundguid); if (utf8dev) { - SDL_IMMDevice_Add(recording, utf8dev, &fmt, pwstrDeviceId, &dsoundguid); + SDL_IMMDevice_Add(recording, utf8dev, &fmt, pwstrDeviceId, &dsoundguid, client->force_format, client->supports_recording_playback_devices); SDL_free(utf8dev); } } else { @@ -286,7 +311,7 @@ static const IMMNotificationClientVtbl notification_client_vtbl = { SDLMMNotificationClient_OnPropertyValueChanged }; -static SDLMMNotificationClient notification_client = { ¬ification_client_vtbl, { 1 } }; +static SDLMMNotificationClient notification_client = { ¬ification_client_vtbl, { 1 }, SDL_AUDIO_UNKNOWN, false }; bool SDL_IMMDevice_Init(const SDL_IMMDevice_callbacks *callbacks) { @@ -363,7 +388,23 @@ bool SDL_IMMDevice_Get(SDL_AudioDevice *device, IMMDevice **immdevice, bool reco return true; } -static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **default_device) +bool SDL_IMMDevice_GetIsCapture(IMMDevice *device) +{ + bool iscapture = false; + IMMEndpoint *endpoint = NULL; + if (SUCCEEDED(IMMDevice_QueryInterface(device, &SDL_IID_IMMEndpoint, (void **)&endpoint))) { + EDataFlow flow; + + if (SUCCEEDED(IMMEndpoint_GetDataFlow(endpoint, &flow))) { + iscapture = (flow == eCapture); + } + } + + IMMEndpoint_Release(endpoint); + return iscapture; +} + +static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **default_device, SDL_AudioFormat force_format, bool supports_recording_playback_devices) { /* Note that WASAPI separates "adapter devices" from "audio endpoint devices" ...one adapter device ("SoundBlaster Pro") might have multiple endpoint devices ("Speakers", "Line-Out"). */ @@ -405,7 +446,7 @@ static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **de SDL_zero(dsoundguid); GetMMDeviceInfo(immdevice, &devname, &fmt, &dsoundguid); if (devname) { - SDL_AudioDevice *sdldevice = SDL_IMMDevice_Add(recording, devname, &fmt, devid, &dsoundguid); + SDL_AudioDevice *sdldevice = SDL_IMMDevice_Add(recording, devname, &fmt, devid, &dsoundguid, force_format, supports_recording_playback_devices); if (default_device && default_devid && SDL_wcscmp(default_devid, devid) == 0) { *default_device = sdldevice; } @@ -422,10 +463,13 @@ static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **de IMMDeviceCollection_Release(collection); } -void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) +void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording, SDL_AudioFormat force_format, bool supports_recording_playback_devices) { - EnumerateEndpointsForFlow(false, default_playback); - EnumerateEndpointsForFlow(true, default_recording); + EnumerateEndpointsForFlow(false, default_playback, force_format, supports_recording_playback_devices); + EnumerateEndpointsForFlow(true, default_recording, force_format, supports_recording_playback_devices); + + notification_client.force_format = force_format; + notification_client.supports_recording_playback_devices = supports_recording_playback_devices; // if this fails, we just won't get hotplug events. Carry on anyhow. IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)¬ification_client); diff --git a/libs/SDL3/src/core/windows/SDL_immdevice.h b/libs/SDL3/src/core/windows/SDL_immdevice.h index 66fdf13b..3fc55485 100644 --- a/libs/SDL3/src/core/windows/SDL_immdevice.h +++ b/libs/SDL3/src/core/windows/SDL_immdevice.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -37,7 +37,8 @@ typedef struct SDL_IMMDevice_callbacks bool SDL_IMMDevice_Init(const SDL_IMMDevice_callbacks *callbacks); void SDL_IMMDevice_Quit(void); bool SDL_IMMDevice_Get(struct SDL_AudioDevice *device, IMMDevice **immdevice, bool recording); -void SDL_IMMDevice_EnumerateEndpoints(struct SDL_AudioDevice **default_playback, struct SDL_AudioDevice **default_recording); +bool SDL_IMMDevice_GetIsCapture(IMMDevice* device); +void SDL_IMMDevice_EnumerateEndpoints(struct SDL_AudioDevice **default_playback, struct SDL_AudioDevice **default_recording, SDL_AudioFormat force_format, bool supports_recording_playback_devices); LPGUID SDL_IMMDevice_GetDirectSoundGUID(struct SDL_AudioDevice *device); LPCWSTR SDL_IMMDevice_GetDevID(struct SDL_AudioDevice *device); void SDL_IMMDevice_FreeDeviceHandle(struct SDL_AudioDevice *device); diff --git a/libs/SDL3/src/core/windows/SDL_windows.c b/libs/SDL3/src/core/windows/SDL_windows.c index d96d8e0b..d011db38 100644 --- a/libs/SDL3/src/core/windows/SDL_windows.c +++ b/libs/SDL3/src/core/windows/SDL_windows.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -24,6 +24,10 @@ #include "SDL_windows.h" +#include "../../video/SDL_surface_c.h" + +#include // CommandLineToArgvW() + #include // for CoInitialize/CoUninitialize (Win32 only) #ifdef HAVE_ROAPI_H #include // For RoInitialize/RoUninitialize (Win32 only) @@ -35,16 +39,6 @@ typedef enum RO_INIT_TYPE } RO_INIT_TYPE; #endif -#ifndef _WIN32_WINNT_VISTA -#define _WIN32_WINNT_VISTA 0x0600 -#endif -#ifndef _WIN32_WINNT_WIN7 -#define _WIN32_WINNT_WIN7 0x0601 -#endif -#ifndef _WIN32_WINNT_WIN8 -#define _WIN32_WINNT_WIN8 0x0602 -#endif - #ifndef LOAD_LIBRARY_SEARCH_SYSTEM32 #define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800 #endif @@ -53,6 +47,116 @@ typedef enum RO_INIT_TYPE #define WC_ERR_INVALID_CHARS 0x00000080 #endif +// Dark mode support +typedef enum { + UXTHEME_APPMODE_DEFAULT, + UXTHEME_APPMODE_ALLOW_DARK, + UXTHEME_APPMODE_FORCE_DARK, + UXTHEME_APPMODE_FORCE_LIGHT, + UXTHEME_APPMODE_MAX +} UxthemePreferredAppMode; + +typedef enum { + WCA_UNDEFINED = 0, + WCA_USEDARKMODECOLORS = 26, + WCA_LAST = 27 +} WINDOWCOMPOSITIONATTRIB; + +typedef struct { + WINDOWCOMPOSITIONATTRIB Attrib; + PVOID pvData; + SIZE_T cbData; +} WINDOWCOMPOSITIONATTRIBDATA; + +typedef struct { + ULONG dwOSVersionInfoSize; + ULONG dwMajorVersion; + ULONG dwMinorVersion; + ULONG dwBuildNumber; + ULONG dwPlatformId; + WCHAR szCSDVersion[128]; +} NT_OSVERSIONINFOW; + +typedef bool (WINAPI *ShouldAppsUseDarkMode_t)(void); +typedef void (WINAPI *AllowDarkModeForWindow_t)(HWND, bool); +typedef void (WINAPI *AllowDarkModeForApp_t)(bool); +typedef void (WINAPI *RefreshImmersiveColorPolicyState_t)(void); +typedef UxthemePreferredAppMode (WINAPI *SetPreferredAppMode_t)(UxthemePreferredAppMode); +typedef BOOL (WINAPI *SetWindowCompositionAttribute_t)(HWND, const WINDOWCOMPOSITIONATTRIBDATA *); +typedef void (NTAPI *RtlGetVersion_t)(NT_OSVERSIONINFOW *); + +// Fake window to help with DirectInput events. +HWND SDL_HelperWindow = NULL; +static const TCHAR *SDL_HelperWindowClassName = TEXT("SDLHelperWindowInputCatcher"); +static const TCHAR *SDL_HelperWindowName = TEXT("SDLHelperWindowInputMsgWindow"); +static ATOM SDL_HelperWindowClass = 0; + +/* + * Creates a HelperWindow used for DirectInput. + */ +bool SDL_HelperWindowCreate(void) +{ + HINSTANCE hInstance = GetModuleHandle(NULL); + WNDCLASS wce; + + // Make sure window isn't created twice. + if (SDL_HelperWindow != NULL) { + return true; + } + + // Create the class. + SDL_zero(wce); + wce.lpfnWndProc = DefWindowProc; + wce.lpszClassName = SDL_HelperWindowClassName; + wce.hInstance = hInstance; + + // Register the class. + SDL_HelperWindowClass = RegisterClass(&wce); + if (SDL_HelperWindowClass == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) { + return WIN_SetError("Unable to create Helper Window Class"); + } + + // Create the window. + SDL_HelperWindow = CreateWindowEx(0, SDL_HelperWindowClassName, + SDL_HelperWindowName, + WS_OVERLAPPED, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, HWND_MESSAGE, NULL, + hInstance, NULL); + if (!SDL_HelperWindow) { + UnregisterClass(SDL_HelperWindowClassName, hInstance); + return WIN_SetError("Unable to create Helper Window"); + } + + return true; +} + +/* + * Destroys the HelperWindow previously created with SDL_HelperWindowCreate. + */ +void SDL_HelperWindowDestroy(void) +{ + HINSTANCE hInstance = GetModuleHandle(NULL); + + // Destroy the window. + if (SDL_HelperWindow != NULL) { + if (DestroyWindow(SDL_HelperWindow) == 0) { + WIN_SetError("Unable to destroy Helper Window"); + return; + } + SDL_HelperWindow = NULL; + } + + // Unregister the class. + if (SDL_HelperWindowClass != 0) { + if ((UnregisterClass(SDL_HelperWindowClassName, hInstance)) == 0) { + WIN_SetError("Unable to destroy Helper Window Class"); + return; + } + SDL_HelperWindowClass = 0; + } +} + // Sets an error message based on an HRESULT bool WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr) { @@ -182,6 +286,36 @@ static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WO return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE; } + +static DWORD WIN_BuildNumber = 0; +static BOOL IsWindowsBuildVersionAtLeast(DWORD dwBuildNumber) +{ + if (WIN_BuildNumber != 0) { + return (WIN_BuildNumber >= dwBuildNumber); + } + + HMODULE ntdll = LoadLibrary(TEXT("ntdll.dll")); + if (!ntdll) { + return false; + } + // There is no function to get Windows build number, so let's get it here via RtlGetVersion + RtlGetVersion_t RtlGetVersionFunc = (RtlGetVersion_t)GetProcAddress(ntdll, "RtlGetVersion"); + NT_OSVERSIONINFOW os_info; + os_info.dwOSVersionInfoSize = sizeof(NT_OSVERSIONINFOW); + os_info.dwBuildNumber = 0; + if (RtlGetVersionFunc) { + RtlGetVersionFunc(&os_info); + } + FreeLibrary(ntdll); + + WIN_BuildNumber = (os_info.dwBuildNumber & ~0xF0000000); + return (WIN_BuildNumber >= dwBuildNumber); +} +#else +static BOOL IsWindowsBuildVersionAtLeast(DWORD dwBuildNumber) +{ + return TRUE; +} #endif // apply some static variables so we only call into the Win32 API once per process for each check. @@ -198,6 +332,24 @@ static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WO return result; #endif +BOOL WIN_IsWine(void) +{ + static bool checked; + static bool is_wine; + + if (!checked) { + HMODULE ntdll = LoadLibrary(TEXT("ntdll.dll")); + if (ntdll) { + if (GetProcAddress(ntdll, "wine_get_version") != NULL) { + is_wine = true; + } + FreeLibrary(ntdll); + } + checked = true; + } + return is_wine; +} + // this is the oldest thing we run on (and we may lose support for this in SDL3 at any time!), // so there's no "OrGreater" as that would always be TRUE. The other functions are here to // ask "can we support a specific feature?" but this function is here to ask "do we need to do @@ -222,6 +374,16 @@ BOOL WIN_IsWindows8OrGreater(void) CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0)); } +BOOL WIN_IsWindows81OrGreater(void) +{ + CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINBLUE), LOBYTE(_WIN32_WINNT_WINBLUE), 0)); +} + +BOOL WIN_IsWindows11OrGreater(void) +{ + return IsWindowsBuildVersionAtLeast(22000); +} + #undef CHECKWINVER @@ -336,6 +498,134 @@ bool WIN_WindowRectValid(const RECT *rect) return (rect->right > 0); } +void WIN_UpdateDarkModeForHWND(HWND hwnd) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (!IsWindowsBuildVersionAtLeast(17763)) { + // Too old to support dark mode + return; + } + HMODULE uxtheme = LoadLibrary(TEXT("uxtheme.dll")); + if (!uxtheme) { + return; + } + RefreshImmersiveColorPolicyState_t RefreshImmersiveColorPolicyStateFunc = (RefreshImmersiveColorPolicyState_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(104)); + ShouldAppsUseDarkMode_t ShouldAppsUseDarkModeFunc = (ShouldAppsUseDarkMode_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(132)); + AllowDarkModeForWindow_t AllowDarkModeForWindowFunc = (AllowDarkModeForWindow_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(133)); + if (!IsWindowsBuildVersionAtLeast(18362)) { + AllowDarkModeForApp_t AllowDarkModeForAppFunc = (AllowDarkModeForApp_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(135)); + if (AllowDarkModeForAppFunc) { + AllowDarkModeForAppFunc(true); + } + } else { + SetPreferredAppMode_t SetPreferredAppModeFunc = (SetPreferredAppMode_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(135)); + if (SetPreferredAppModeFunc) { + SetPreferredAppModeFunc(UXTHEME_APPMODE_ALLOW_DARK); + } + } + if (RefreshImmersiveColorPolicyStateFunc) { + RefreshImmersiveColorPolicyStateFunc(); + } + if (AllowDarkModeForWindowFunc) { + AllowDarkModeForWindowFunc(hwnd, true); + } + BOOL value; + // Check dark mode using ShouldAppsUseDarkMode, but use SDL_GetSystemTheme as a fallback + if (ShouldAppsUseDarkModeFunc) { + value = ShouldAppsUseDarkModeFunc() ? TRUE : FALSE; + } else { + value = (SDL_GetSystemTheme() == SDL_SYSTEM_THEME_DARK) ? TRUE : FALSE; + } + FreeLibrary(uxtheme); + if (!IsWindowsBuildVersionAtLeast(18362)) { + SetProp(hwnd, TEXT("UseImmersiveDarkModeColors"), SDL_reinterpret_cast(HANDLE, SDL_static_cast(INT_PTR, value))); + } else { + HMODULE user32 = GetModuleHandle(TEXT("user32.dll")); + if (user32) { + SetWindowCompositionAttribute_t SetWindowCompositionAttributeFunc = (SetWindowCompositionAttribute_t)GetProcAddress(user32, "SetWindowCompositionAttribute"); + if (SetWindowCompositionAttributeFunc) { + WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &value, sizeof(value) }; + SetWindowCompositionAttributeFunc(hwnd, &data); + } + } + } +#endif +} + +HICON WIN_CreateIconFromSurface(SDL_Surface *surface) +{ +#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + SDL_Surface *s = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888); + if (!s) { + return NULL; + } + + /* The dimensions will be needed after s is freed */ + const int width = s->w; + const int height = s->h; + + BITMAPINFO bmpInfo; + SDL_zero(bmpInfo); + bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmpInfo.bmiHeader.biWidth = width; + bmpInfo.bmiHeader.biHeight = -height; /* Top-down bitmap */ + bmpInfo.bmiHeader.biPlanes = 1; + bmpInfo.bmiHeader.biBitCount = 32; + bmpInfo.bmiHeader.biCompression = BI_RGB; + + HDC hdc = GetDC(NULL); + void *pBits = NULL; + HBITMAP hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, &pBits, NULL, 0); + if (!hBitmap) { + ReleaseDC(NULL, hdc); + SDL_DestroySurface(s); + return NULL; + } + + SDL_memcpy(pBits, s->pixels, width * height * 4); + + SDL_DestroySurface(s); + + HBITMAP hMask = CreateBitmap(width, height, 1, 1, NULL); + if (!hMask) { + DeleteObject(hBitmap); + ReleaseDC(NULL, hdc); + return NULL; + } + + HDC hdcMem = CreateCompatibleDC(hdc); + HGDIOBJ oldBitmap = SelectObject(hdcMem, hMask); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + BYTE* pixel = (BYTE*)pBits + (y * width + x) * 4; + BYTE alpha = pixel[3]; + COLORREF maskColor = (alpha == 0) ? RGB(0, 0, 0) : RGB(255, 255, 255); + SetPixel(hdcMem, x, y, maskColor); + } + } + + ICONINFO iconInfo; + iconInfo.fIcon = TRUE; + iconInfo.xHotspot = 0; + iconInfo.yHotspot = 0; + iconInfo.hbmMask = hMask; + iconInfo.hbmColor = hBitmap; + + HICON hIcon = CreateIconIndirect(&iconInfo); + + SelectObject(hdcMem, oldBitmap); + DeleteDC(hdcMem); + DeleteObject(hBitmap); + DeleteObject(hMask); + ReleaseDC(NULL, hdc); + + return hIcon; +#else + return NULL; +#endif +} + // Some GUIDs we need to know without linking to libraries that aren't available before Vista. /* *INDENT-OFF* */ // clang-format off static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; @@ -372,4 +662,110 @@ int WIN_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, return WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cbMultiByte, lpDefaultChar, lpUsedDefaultChar); } +const char *WIN_CheckDefaultArgcArgv(int *pargc, char ***pargv, void **pallocated) +{ + // If the provided argv is valid, we pass it to the main function as-is, since it's probably what the user wants. + // Otherwise, we take a NULL argv as an instruction for SDL to parse the command line into an argv. + // On Windows, when SDL provides the main entry point, argv is always NULL. + + const char *out_of_mem_str = "Out of memory - aborting"; + const char *proc_err_str = "Error processing command line arguments - aborting"; + + *pallocated = NULL; + + if (*pargv) { + return NULL; // just go with what was provided, no error message. + } + + // We need to be careful about how we allocate/free memory here. We can't use SDL_alloc()/SDL_free() + // because the application might have used SDL_SetMemoryFunctions() to change the allocator. + LPWSTR *argvw = NULL; + char **argv = NULL; + + const LPWSTR command_line = GetCommandLineW(); + + // Because of how the Windows command line is structured, we know for sure that the buffer size required to + // store all argument strings converted to UTF-8 (with null terminators) is guaranteed to be less than or equal + // to the size of the original command line string converted to UTF-8. + const int argdata_size = WideCharToMultiByte(CP_UTF8, 0, command_line, -1, NULL, 0, NULL, NULL); // Includes the null terminator + if (!argdata_size) { + return proc_err_str; + } + + int argc = -1; + argvw = CommandLineToArgvW(command_line, &argc); + if (!argvw || argc < 0) { + return out_of_mem_str; + } + + // Allocate argv followed by the argument string buffer as one contiguous allocation. + argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv) + argdata_size); + if (!argv) { + LocalFree(argvw); + return out_of_mem_str; + } + + char *argdata = ((char *)argv) + (argc + 1) * sizeof(*argv); + int argdata_index = 0; + + for (int i = 0; i < argc; ++i) { + const int bytes_written = WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, argdata + argdata_index, argdata_size - argdata_index, NULL, NULL); + if (!bytes_written) { + HeapFree(GetProcessHeap(), 0, argv); + LocalFree(argvw); + return proc_err_str; + } + argv[i] = argdata + argdata_index; + argdata_index += bytes_written; + } + + argv[argc] = NULL; + + LocalFree(argvw); + + *pargc = argc; + *pallocated = argv; + *pargv = argv; + + return NULL; // no error string. +} + +char *WIN_GetModulePath(HMODULE handle) +{ + DWORD buflen = 128; + WCHAR *path = NULL; + DWORD len = 0; + + while (true) { + void *ptr = SDL_realloc(path, buflen * sizeof(WCHAR)); + if (!ptr) { + SDL_free(path); + return NULL; + } + + path = (WCHAR *)ptr; + + len = GetModuleFileNameW(handle, path, buflen); + // if it truncated, then len >= buflen - 1 + // if there was enough room (or failure), len < buflen - 1 + if (len < (buflen - 1)) { + break; + } + + // buffer too small? Try again. + buflen *= 2; + } + + char *retval = NULL; + if (len == 0) { + WIN_SetError("Couldn't locate module"); + } else { + retval = WIN_StringToUTF8W(path); + } + + SDL_free(path); + + return retval; +} + #endif // defined(SDL_PLATFORM_WINDOWS) diff --git a/libs/SDL3/src/core/windows/SDL_windows.h b/libs/SDL3/src/core/windows/SDL_windows.h index ef54fe37..54f3f2c9 100644 --- a/libs/SDL3/src/core/windows/SDL_windows.h +++ b/libs/SDL3/src/core/windows/SDL_windows.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -21,10 +21,39 @@ // This is an include file for windows.h with the SDL build settings -#ifndef _INCLUDED_WINDOWS_H -#define _INCLUDED_WINDOWS_H +#ifndef SDL_windows_h_ +#define SDL_windows_h_ #ifdef SDL_PLATFORM_WIN32 + +#ifndef _WIN32_WINNT_NT4 +#define _WIN32_WINNT_NT4 0x0400 +#endif +#ifndef _WIN32_WINNT_WIN2K +#define _WIN32_WINNT_WIN2K 0x0500 +#endif +#ifndef _WIN32_WINNT_WINXP +#define _WIN32_WINNT_WINXP 0x0501 +#endif +#ifndef _WIN32_WINNT_WS03 +#define _WIN32_WINNT_WS03 0x0502 +#endif +#ifndef _WIN32_WINNT_VISTA +#define _WIN32_WINNT_VISTA 0x0600 +#endif +#ifndef _WIN32_WINNT_WIN7 +#define _WIN32_WINNT_WIN7 0x0601 +#endif +#ifndef _WIN32_WINNT_WIN8 +#define _WIN32_WINNT_WIN8 0x0602 +#endif +#ifndef _WIN32_WINNT_WINBLUE +#define _WIN32_WINNT_WINBLUE 0x0603 +#endif +#ifndef _WIN32_WINNT_WIN10 +#define _WIN32_WINNT_WIN10 0x0A00 +#endif + #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN 1 #endif @@ -37,11 +66,17 @@ #undef WINVER #undef _WIN32_WINNT #if defined(SDL_VIDEO_RENDER_D3D12) || defined(HAVE_DXGI1_6_H) -#define _WIN32_WINNT 0xA00 // For D3D12, 0xA00 is required +#define _WIN32_WINNT _WIN32_WINNT_WIN10 // For D3D12, 0xA00 is required #elif defined(HAVE_SHELLSCALINGAPI_H) -#define _WIN32_WINNT 0x603 // For DPI support +#define _WIN32_WINNT _WIN32_WINNT_WINBLUE // For DPI support +#elif defined(HAVE_ROAPI_H) +#define _WIN32_WINNT _WIN32_WINNT_WIN8 +#elif defined(HAVE_SENSORSAPI_H) +#define _WIN32_WINNT _WIN32_WINNT_WIN7 +#elif defined(HAVE_MMDEVICEAPI_H) +#define _WIN32_WINNT _WIN32_WINNT_VISTA #else -#define _WIN32_WINNT 0x501 // Need 0x410 for AlphaBlend() and 0x500 for EnumDisplayDevices(), 0x501 for raw input +#define _WIN32_WINNT _WIN32_WINNT_WINXP // Need 0x410 for AlphaBlend() and 0x500 for EnumDisplayDevices(), 0x501 for raw input #endif #define WINVER _WIN32_WINNT @@ -133,6 +168,9 @@ extern void WIN_CoUninitialize(void); extern HRESULT WIN_RoInitialize(void); extern void WIN_RoUninitialize(void); +// Returns true if we're running on Wine +extern BOOL WIN_IsWine(void); + // Returns true if we're running on Windows XP (any service pack). DOES NOT CHECK XP "OR GREATER"! extern BOOL WIN_IsWindowsXP(void); @@ -145,6 +183,12 @@ extern BOOL WIN_IsWindows7OrGreater(void); // Returns true if we're running on Windows 8 and newer extern BOOL WIN_IsWindows8OrGreater(void); +// Returns true if we're running on Windows 8.1 and newer +extern BOOL WIN_IsWindows81OrGreater(void); + +// Returns true if we're running on Windows 11 and newer +extern BOOL WIN_IsWindows11OrGreater(void); + // You need to SDL_free() the result of this call. extern char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid); @@ -157,16 +201,26 @@ extern void WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect); extern void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect); // Returns false if a window client rect is not valid -bool WIN_WindowRectValid(const RECT *rect); +extern bool WIN_WindowRectValid(const RECT *rect); + +extern void WIN_UpdateDarkModeForHWND(HWND hwnd); + +extern HICON WIN_CreateIconFromSurface(SDL_Surface *surface); extern SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat); // WideCharToMultiByte, but with some WinXP management. extern int WIN_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar); +// parse out command lines from OS if argv is NULL, otherwise pass through unchanged. `*pallocated` must be HeapFree'd by caller if not NULL on successful return. Returns NULL on success, error string on problems. +extern const char *WIN_CheckDefaultArgcArgv(int *pargc, char ***pargv, void **pallocated); + +// Does all the win32 tapdancing to make GetModuleFileName work. Returns a SDL_malloc'd UTF-8 string, or NULL on failure. +extern char *WIN_GetModulePath(HMODULE handle); + // Ends C function definitions when using C++ #ifdef __cplusplus } #endif -#endif // _INCLUDED_WINDOWS_H +#endif // SDL_windows_h_ diff --git a/libs/SDL3/src/core/windows/SDL_xinput.c b/libs/SDL3/src/core/windows/SDL_xinput.c index 53dab5e5..93132dd3 100644 --- a/libs/SDL3/src/core/windows/SDL_xinput.c +++ b/libs/SDL3/src/core/windows/SDL_xinput.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/windows/SDL_xinput.h b/libs/SDL3/src/core/windows/SDL_xinput.h index 2306ba43..aa2cf99f 100644 --- a/libs/SDL3/src/core/windows/SDL_xinput.h +++ b/libs/SDL3/src/core/windows/SDL_xinput.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/windows/pch.c b/libs/SDL3/src/core/windows/pch.c index 4b0c6f8f..b2b446f5 100644 --- a/libs/SDL3/src/core/windows/pch.c +++ b/libs/SDL3/src/core/windows/pch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/windows/pch_cpp.cpp b/libs/SDL3/src/core/windows/pch_cpp.cpp index 4b0c6f8f..b2b446f5 100644 --- a/libs/SDL3/src/core/windows/pch_cpp.cpp +++ b/libs/SDL3/src/core/windows/pch_cpp.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/core/windows/version.rc b/libs/SDL3/src/core/windows/version.rc index 0ae85b71..d5b7ec74 100644 --- a/libs/SDL3/src/core/windows/version.rc +++ b/libs/SDL3/src/core/windows/version.rc @@ -9,8 +9,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 3,3,0,0 - PRODUCTVERSION 3,3,0,0 + FILEVERSION 3,4,10,0 + PRODUCTVERSION 3,4,10,0 FILEFLAGSMASK 0x3fL FILEFLAGS 0x0L FILEOS 0x40004L @@ -23,12 +23,12 @@ BEGIN BEGIN VALUE "CompanyName", "\0" VALUE "FileDescription", "SDL\0" - VALUE "FileVersion", "3, 3, 0, 0\0" + VALUE "FileVersion", "3, 4, 10, 0\0" VALUE "InternalName", "SDL\0" - VALUE "LegalCopyright", "Copyright (C) 2025 Sam Lantinga\0" + VALUE "LegalCopyright", "Copyright (C) 2026 Sam Lantinga\0" VALUE "OriginalFilename", "SDL3.dll\0" VALUE "ProductName", "Simple DirectMedia Layer\0" - VALUE "ProductVersion", "3, 3, 0, 0\0" + VALUE "ProductVersion", "3, 4, 10, 0\0" END END BLOCK "VarFileInfo" diff --git a/libs/SDL3/src/cpuinfo/SDL_cpuinfo.c b/libs/SDL3/src/cpuinfo/SDL_cpuinfo.c index fc747c19..966a5ae7 100644 --- a/libs/SDL3/src/cpuinfo/SDL_cpuinfo.c +++ b/libs/SDL3/src/cpuinfo/SDL_cpuinfo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -29,7 +29,11 @@ // CPU feature detection for SDL -#ifdef HAVE_SYSCONF +#if defined(HAVE_GETPAGESIZE) && !defined(SDL_PLATFORM_WINDOWS) +#define USE_GETPAGESIZE +#endif + +#if defined(HAVE_SYSCONF) || defined(USE_GETPAGESIZE) #include #endif #ifdef HAVE_SYSCTLBYNAME @@ -38,13 +42,12 @@ #endif #if defined(SDL_PLATFORM_MACOS) && (defined(__ppc__) || defined(__ppc64__)) #include // For AltiVec check -#elif defined(SDL_PLATFORM_OPENBSD) && defined(__powerpc__) +#elif defined(SDL_PLATFORM_OPENBSD) && defined(__powerpc__) && !defined(HAVE_ELF_AUX_INFO) #include #include // For AltiVec check #include -#elif defined(SDL_PLATFORM_FREEBSD) && defined(__powerpc__) +#elif defined(SDL_PLATFORM_FREEBSD) && defined(__powerpc__) && defined(HAVE_ELF_AUX_INFO) #include -#include #elif defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) #include #include @@ -73,11 +76,7 @@ #include #endif -#if defined(SDL_PLATFORM_ANDROID) && defined(__arm__) && !defined(HAVE_GETAUXVAL) -#include -#endif - -#if defined(HAVE_GETAUXVAL) || defined(HAVE_ELF_AUX_INFO) +#if defined(HAVE_GETAUXVAL) || defined(HAVE_ELF_AUX_INFO) || defined(SDL_PLATFORM_ANDROID) #include #endif @@ -174,7 +173,7 @@ static int CPU_haveCPUID(void) : : "%rax", "%rcx" ); -#elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__) +#elif defined(_MSC_VER) && defined(_M_IX86) __asm { pushfd ; Get original EFLAGS pop eax @@ -247,7 +246,7 @@ done: " popq %%rbx \n" \ : "=a"(a), "=S"(b), "=c"(c), "=d"(d) \ : "a"(func)) -#elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__) +#elif defined(_MSC_VER) && defined(_M_IX86) #define cpuid(func, a, b, c, d) \ __asm { \ __asm mov eax, func \ @@ -311,7 +310,7 @@ static void CPU_calcCPUIDFeatures(void) : "%edx"); #elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) && (_MSC_FULL_VER >= 160040219) // VS2010 SP1 a = (int)_xgetbv(0); -#elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__) +#elif defined(_MSC_VER) && defined(_M_IX86) __asm { xor ecx, ecx @@ -331,7 +330,12 @@ static int CPU_haveAltiVec(void) { volatile int altivec = 0; #ifndef SDL_CPUINFO_DISABLED -#if (defined(SDL_PLATFORM_MACOS) && (defined(__ppc__) || defined(__ppc64__))) || (defined(SDL_PLATFORM_OPENBSD) && defined(__powerpc__)) +#if (defined(SDL_PLATFORM_FREEBSD) || defined(SDL_PLATFORM_OPENBSD)) && defined(__powerpc__) && defined(HAVE_ELF_AUX_INFO) + unsigned long cpufeatures = 0; + elf_aux_info(AT_HWCAP, &cpufeatures, sizeof(cpufeatures)); + altivec = cpufeatures & PPC_FEATURE_HAS_ALTIVEC; + return altivec; +#elif (defined(SDL_PLATFORM_MACOS) && (defined(__ppc__) || defined(__ppc64__))) || (defined(SDL_PLATFORM_OPENBSD) && defined(__powerpc__)) #ifdef SDL_PLATFORM_OPENBSD int selectors[2] = { CTL_MACHDEP, CPU_ALTIVEC }; #else @@ -343,11 +347,6 @@ static int CPU_haveAltiVec(void) if (0 == error) { altivec = (hasVectorUnit != 0); } -#elif defined(SDL_PLATFORM_FREEBSD) && defined(__powerpc__) - unsigned long cpufeatures = 0; - elf_aux_info(AT_HWCAP, &cpufeatures, sizeof(cpufeatures)); - altivec = cpufeatures & PPC_FEATURE_HAS_ALTIVEC; - return altivec; #elif defined(SDL_PLATFORM_LINUX) && defined(__powerpc__) && defined(HAVE_GETAUXVAL) altivec = getauxval(AT_HWCAP) & PPC_FEATURE_HAS_ALTIVEC; #elif defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) @@ -483,30 +482,16 @@ static int CPU_haveNEON(void) return 0; // assume anything else from Apple doesn't have NEON. #elif !defined(__arm__) return 0; // not an ARM CPU at all. -#elif defined(SDL_PLATFORM_OPENBSD) - return 1; // OpenBSD only supports ARMv7 CPUs that have NEON. #elif defined(HAVE_ELF_AUX_INFO) unsigned long hasneon = 0; if (elf_aux_info(AT_HWCAP, (void *)&hasneon, (int)sizeof(hasneon)) != 0) { return 0; } return (hasneon & HWCAP_NEON) == HWCAP_NEON; -#elif (defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)) && defined(HAVE_GETAUXVAL) +#elif (defined(SDL_PLATFORM_LINUX) && defined(HAVE_GETAUXVAL)) || defined(SDL_PLATFORM_ANDROID) return (getauxval(AT_HWCAP) & HWCAP_NEON) == HWCAP_NEON; #elif defined(SDL_PLATFORM_LINUX) return readProcAuxvForNeon(); -#elif defined(SDL_PLATFORM_ANDROID) - // Use NDK cpufeatures to read either /proc/self/auxv or /proc/cpuinfo - { - AndroidCpuFamily cpu_family = android_getCpuFamily(); - if (cpu_family == ANDROID_CPU_FAMILY_ARM) { - uint64_t cpu_features = android_getCpuFeatures(); - if (cpu_features & ANDROID_CPU_ARM_FEATURE_NEON) { - return 1; - } - } - return 0; - } #elif defined(SDL_PLATFORM_RISCOS) // Use the VFPSupport_Features SWI to access the MVFR registers { @@ -519,6 +504,8 @@ static int CPU_haveNEON(void) } return 0; } +#elif defined(SDL_PLATFORM_OPENBSD) + return 1; // OpenBSD only supports ARMv7 CPUs that have NEON. #elif defined(SDL_PLATFORM_EMSCRIPTEN) return 0; #else @@ -1214,6 +1201,65 @@ int SDL_GetSystemRAM(void) return SDL_SystemRAM; } + +static int SDL_SystemPageSize = -1; + +int SDL_GetSystemPageSize(void) +{ + if (SDL_SystemPageSize == -1) { +#ifdef SDL_PLATFORM_SYSTEM_PAGE_SIZE_PRIVATE // consoles will define this in a platform-specific internal header. + SDL_SystemPageSize = SDL_PLATFORM_SYSTEM_PAGE_SIZE_PRIVATE; +#endif +#ifdef SDL_PLATFORM_3DS + SDL_SystemPageSize = 4096; // It's an ARM11 CPU; I assume this is 4K. +#endif +#ifdef SDL_PLATFORM_VITA + SDL_SystemPageSize = 4096; // It's an ARMv7 CPU; I assume this is 4K. +#endif +#ifdef SDL_PLATFORM_PS2 + SDL_SystemPageSize = 4096; // It's a MIPS R5900 CPU; I assume this is 4K. +#endif +#if defined(HAVE_SYSCONF) && (defined(_SC_PAGESIZE) || defined(_SC_PAGE_SIZE)) + if (SDL_SystemPageSize <= 0) { + #if defined(_SC_PAGE_SIZE) + SDL_SystemPageSize = (int)sysconf(_SC_PAGE_SIZE); + #else + SDL_SystemPageSize = (int)sysconf(_SC_PAGESIZE); + #endif + } +#endif +#if defined(HAVE_SYSCTLBYNAME) && defined(HW_PAGESIZE) + if (SDL_SystemPageSize <= 0) { + // NetBSD, OpenBSD, FreeBSD, Darwin...everything agrees to use HW_PAGESIZE. + int mib[2] = { CTL_HW, HW_PAGESIZE }; + int pagesize = 0; + size_t len = sizeof(pagesize); + + if (sysctl(mib, 2, &pagesize, &len, NULL, 0) == 0) { + SDL_SystemPageSize = pagesize; + } + } +#endif +#ifdef USE_GETPAGESIZE + if (SDL_SystemPageSize <= 0) { + SDL_SystemPageSize = getpagesize(); + } +#endif +#if defined(SDL_PLATFORM_WINDOWS) + if (SDL_SystemPageSize <= 0) { + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + SDL_SystemPageSize = (int) sysinfo.dwPageSize; + } +#endif + if (SDL_SystemPageSize < 0) { // in case we got a weird result somewhere, or no better information, force it to 0. + SDL_SystemPageSize = 0; // unknown page size, sorry. + } + } + return SDL_SystemPageSize; +} + + size_t SDL_GetSIMDAlignment(void) { if (SDL_SIMDAlignment == 0xFFFFFFFF) { diff --git a/libs/SDL3/src/cpuinfo/SDL_cpuinfo_c.h b/libs/SDL3/src/cpuinfo/SDL_cpuinfo_c.h index 9da31a99..c3918a58 100644 --- a/libs/SDL3/src/cpuinfo/SDL_cpuinfo_c.h +++ b/libs/SDL3/src/cpuinfo/SDL_cpuinfo_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/dialog/SDL_dialog.c b/libs/SDL3/src/dialog/SDL_dialog.c index a77e4433..817df382 100644 --- a/libs/SDL3/src/dialog/SDL_dialog.c +++ b/libs/SDL3/src/dialog/SDL_dialog.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/dialog/SDL_dialog.h b/libs/SDL3/src/dialog/SDL_dialog.h index beee7ddf..12f61f21 100644 --- a/libs/SDL3/src/dialog/SDL_dialog.h +++ b/libs/SDL3/src/dialog/SDL_dialog.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/dialog/SDL_dialog_utils.c b/libs/SDL3/src/dialog/SDL_dialog_utils.c index 8d2b1869..5af3b476 100644 --- a/libs/SDL3/src/dialog/SDL_dialog_utils.c +++ b/libs/SDL3/src/dialog/SDL_dialog_utils.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -27,7 +27,8 @@ char *convert_filters(const SDL_DialogFileFilter *filters, int nfilters, const char *separator, const char *suffix, const char *filt_prefix, const char *filt_separator, const char *filt_suffix, const char *ext_prefix, - const char *ext_separator, const char *ext_suffix) + const char *ext_separator, const char *ext_suffix, + bool anycase) { char *combined; char *new_combined; @@ -52,7 +53,7 @@ char *convert_filters(const SDL_DialogFileFilter *filters, int nfilters, converted = convert_filter(*f, ntf, filt_prefix, filt_separator, filt_suffix, ext_prefix, ext_separator, - ext_suffix); + ext_suffix, anycase); if (!converted) { SDL_free(combined); @@ -97,7 +98,8 @@ char *convert_filters(const SDL_DialogFileFilter *filters, int nfilters, char *convert_filter(SDL_DialogFileFilter filter, NameTransform ntf, const char *prefix, const char *separator, const char *suffix, const char *ext_prefix, - const char *ext_separator, const char *ext_suffix) + const char *ext_separator, const char *ext_suffix, + bool anycase) { char *converted; char *name_filtered; @@ -105,7 +107,7 @@ char *convert_filter(SDL_DialogFileFilter filter, NameTransform ntf, char *list; list = convert_ext_list(filter.pattern, ext_prefix, ext_separator, - ext_suffix); + ext_suffix, anycase); if (!list) { return NULL; @@ -145,7 +147,7 @@ char *convert_filter(SDL_DialogFileFilter filter, NameTransform ntf, } char *convert_ext_list(const char *list, const char *prefix, - const char *separator, const char *suffix) + const char *separator, const char *suffix, bool anycase) { char *converted; int semicolons; @@ -158,7 +160,7 @@ char *convert_ext_list(const char *list, const char *prefix, } total_length = - SDL_strlen(list) - semicolons // length of list contents + (SDL_strlen(list) - semicolons) * 4 // length of list contents (including "a" -> "[aA]") + semicolons * SDL_strlen(separator) // length of separators + SDL_strlen(prefix) + SDL_strlen(suffix) // length of prefix/suffix + 1; // terminating null byte @@ -179,9 +181,17 @@ char *convert_ext_list(const char *list, const char *prefix, SDL_strlcat(converted, "*", total_length); } else { for (const char *c = list; *c; c++) { - if ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') - || (*c >= '0' && *c <= '9') || *c == '-' || *c == '_' - || *c == '.') { + if (anycase && ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z'))) { + char str[5]; + str[0] = '['; + str[1] = *c; + str[2] = *c ^ 0x20; // ASCII case toggle + str[3] = ']'; + str[4] = '\0'; + SDL_strlcat(converted, str, total_length); + } else if ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') + || (*c >= '0' && *c <= '9') || *c == '-' || *c == '_' + || *c == '.') { char str[2]; str[0] = *c; str[1] = '\0'; diff --git a/libs/SDL3/src/dialog/SDL_dialog_utils.h b/libs/SDL3/src/dialog/SDL_dialog_utils.h index 1343dd75..c1ad9237 100644 --- a/libs/SDL3/src/dialog/SDL_dialog_utils.h +++ b/libs/SDL3/src/dialog/SDL_dialog_utils.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -28,7 +28,7 @@ /* Transform the name given in argument into something viable for the engine. Useful if there are special characters to avoid on certain platforms (such as "|" with Zenity). */ -typedef char *(NameTransform)(const char * name); +typedef char *(*NameTransform)(const char * name); // Converts all the filters into a single string. // [filter]{[filter]...} @@ -37,19 +37,21 @@ char *convert_filters(const SDL_DialogFileFilter *filters, int nfilters, const char *separator, const char *suffix, const char *filt_prefix, const char *filt_separator, const char *filt_suffix, const char *ext_prefix, - const char *ext_separator, const char *ext_suffix); + const char *ext_separator, const char *ext_suffix, + bool anycase); // Converts one filter into a single string. // [filter name][filter extension list] char *convert_filter(SDL_DialogFileFilter filter, NameTransform ntf, const char *prefix, const char *separator, const char *suffix, const char *ext_prefix, - const char *ext_separator, const char *ext_suffix); + const char *ext_separator, const char *ext_suffix, + bool anycase); // Converts the extension list of a filter into a single string. // [extension]{[extension]...} char *convert_ext_list(const char *list, const char *prefix, - const char *separator, const char *suffix); + const char *separator, const char *suffix, bool anycase); /* Must be used if convert_* functions aren't used */ // Returns an error message if there's a problem, NULL otherwise diff --git a/libs/SDL3/src/dialog/android/SDL_androiddialog.c b/libs/SDL3/src/dialog/android/SDL_androiddialog.c index 5eaf9455..705a0d50 100644 --- a/libs/SDL3/src/dialog/android/SDL_androiddialog.c +++ b/libs/SDL3/src/dialog/android/SDL_androiddialog.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m b/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m index d12dab87..a2337cb4 100644 --- a/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m +++ b/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -27,6 +27,12 @@ #import #import +#ifdef SDL_VIDEO_DRIVER_COCOA +extern void Cocoa_SetWindowHasModalDialog(SDL_Window *window, bool has_modal); +#else +#define Cocoa_SetWindowHasModalDialog(window, has_modal) +#endif + static void AddFileExtensionType(NSMutableArray *types, const char *pattern_ptr) { if (!*pattern_ptr) { @@ -155,14 +161,24 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil // Keep behavior consistent with other platforms [dialog setAllowsOtherFileTypes:YES]; - if (default_location) { - [dialog setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:default_location]]]; + if (default_location && *default_location) { + char last = default_location[SDL_strlen(default_location) - 1]; + NSURL* url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:default_location]]; + if (last == '/') { + [dialog setDirectoryURL:url]; + } else { + [dialog setDirectoryURL:[url URLByDeletingLastPathComponent]]; + [dialog setNameFieldStringValue:[url lastPathComponent]]; + } } NSWindow *w = NULL; if (window) { w = (__bridge NSWindow *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL); + if (w) { + Cocoa_SetWindowHasModalDialog(window, true); + } } if (w) { @@ -186,6 +202,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil callback(userdata, files, -1); } + Cocoa_SetWindowHasModalDialog(window, false); ReactivateAfterDialog(); }]; } else { @@ -206,6 +223,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil const char *files[1] = { NULL }; callback(userdata, files, -1); } + ReactivateAfterDialog(); } } diff --git a/libs/SDL3/src/dialog/dummy/SDL_dummydialog.c b/libs/SDL3/src/dialog/dummy/SDL_dummydialog.c index 121a090f..9f36afdb 100644 --- a/libs/SDL3/src/dialog/dummy/SDL_dummydialog.c +++ b/libs/SDL3/src/dialog/dummy/SDL_dummydialog.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/dialog/haiku/SDL_haikudialog.cc b/libs/SDL3/src/dialog/haiku/SDL_haikudialog.cc index 539d6a6a..78881e47 100644 --- a/libs/SDL3/src/dialog/haiku/SDL_haikudialog.cc +++ b/libs/SDL3/src/dialog/haiku/SDL_haikudialog.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -222,9 +222,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil }; if (!SDL_InitBeApp()) { - char *err = SDL_strdup(SDL_GetError()); - SDL_SetError("Couldn't init Be app: %s", err); - SDL_free(err); + SDL_SetError("Couldn't init Be app: %s", SDL_GetError()); callback(userdata, NULL, -1); return; } diff --git a/libs/SDL3/src/dialog/unix/SDL_portaldialog.c b/libs/SDL3/src/dialog/unix/SDL_portaldialog.c index dadfc9f5..c0f957c8 100644 --- a/libs/SDL3/src/dialog/unix/SDL_portaldialog.c +++ b/libs/SDL3/src/dialog/unix/SDL_portaldialog.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -26,6 +26,8 @@ #ifdef SDL_USE_LIBDBUS #include +#include +#include #include #include #include @@ -84,10 +86,25 @@ static void DBus_AppendFilter(SDL_DBusContext *dbus, DBusMessageIter *parent, co dbus->message_iter_append_basic(&filter_entry, DBUS_TYPE_STRING, &filter.name); dbus->message_iter_open_container(&filter_entry, DBUS_TYPE_ARRAY, "(us)", &filter_array); - patterns = SDL_strdup(filter.pattern); + /* Copy the filter string, converting to a case-insensitive version. + * For example, for case-insensitive matching of '*.png', the pattern '*.[pP][nN][gG]' is used. + */ + const size_t len = SDL_strlen(filter.pattern) + 1; + patterns = SDL_malloc(len * 4); // Single characters may be expanded to 4 characters. if (!patterns) { goto cleanup; } + for (size_t i = 0, p = 0; i < len; ++i) { + if ((filter.pattern[i] >= 'a' && filter.pattern[i] <= 'z') || + (filter.pattern[i] >= 'A' && filter.pattern[i] <= 'Z')) { + patterns[p++] = '['; + patterns[p++] = SDL_tolower(filter.pattern[i]); + patterns[p++] = SDL_toupper(filter.pattern[i]); + patterns[p++] = ']'; + } else { + patterns[p++] = filter.pattern[i]; + } + } pattern = SDL_strtok_r(patterns, ";", &state); while (pattern) { @@ -294,7 +311,12 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog 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); + char *location_name = NULL; + char *location_folder = NULL; + struct stat statbuf; bool open_folders = false; + bool save_file_existing = false; + bool save_file_new_named = false; switch (type) { case SDL_FILEDIALOG_OPENFILE: @@ -305,6 +327,28 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog case SDL_FILEDIALOG_SAVEFILE: method = "SaveFile"; method_title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, "Save File"); + if (default_location) { + if (stat(default_location, &statbuf) == 0) { + save_file_existing = S_ISREG(statbuf.st_mode); + } else if (errno == ENOENT) { + char *dirc = SDL_strdup(default_location); + if (dirc) { + location_folder = SDL_strdup(dirname(dirc)); + SDL_free(dirc); + if (location_folder) { + save_file_new_named = (stat(location_folder, &statbuf) == 0) && S_ISDIR(statbuf.st_mode); + } + } + } + + if (save_file_existing || save_file_new_named) { + char *basec = SDL_strdup(default_location); + if (basec) { + location_name = SDL_strdup(basename(basec)); + SDL_free(basec); + } + } + } break; case SDL_FILEDIALOG_OPENFOLDER: @@ -317,10 +361,11 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog /* This is already checked in ../SDL_dialog.c; this silences compiler warnings */ SDL_SetError("Invalid file dialog type: %d", type); callback(userdata, NULL, -1); - return; + goto cleanup; } SDL_DBusContext *dbus = SDL_DBus_GetContext(); + DBusError error; DBusMessage *msg; DBusMessageIter params, options; const char *signal_id = NULL; @@ -332,23 +377,25 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog const char *err_msg = validate_filters(filters, nfilters); + dbus->error_init(&error); + if (err_msg) { SDL_SetError("%s", err_msg); callback(userdata, NULL, -1); - return; + goto cleanup; } if (dbus == NULL) { SDL_SetError("Failed to connect to DBus"); callback(userdata, NULL, -1); - return; + goto cleanup; } msg = dbus->message_new_method_call(PORTAL_DESTINATION, PORTAL_PATH, PORTAL_INTERFACE, method); if (msg == NULL) { SDL_SetError("Failed to send message to portal"); callback(userdata, NULL, -1); - return; + goto cleanup; } dbus->message_iter_init_append(msg, ¶ms); @@ -362,7 +409,7 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog handle_str = SDL_malloc(len * sizeof(char)); if (!handle_str) { callback(userdata, NULL, -1); - return; + goto cleanup; } SDL_snprintf(handle_str, len, "%s%s", WAYLAND_HANDLE_PREFIX, parent_handle); @@ -373,7 +420,7 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog handle_str = SDL_malloc(len * sizeof(char)); if (!handle_str) { callback(userdata, NULL, -1); - return; + goto cleanup; } // The portal wants X11 window ID numbers in hex. @@ -393,7 +440,7 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog handle_str = SDL_malloc(sizeof(char) * (HANDLE_LEN + 1)); if (!handle_str) { callback(userdata, NULL, -1); - return; + goto cleanup; } SDL_snprintf(handle_str, HANDLE_LEN, "%u", ++handle_id); DBus_AppendStringOption(dbus, &options, "handle_token", handle_str); @@ -410,14 +457,34 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog DBus_AppendFilters(dbus, &options, filters, nfilters); } if (default_location) { - DBus_AppendByteArray(dbus, &options, "current_folder", default_location); + if (save_file_existing && location_name) { + /* Open a save dialog at an existing file */ + DBus_AppendByteArray(dbus, &options, "current_file", default_location); + /* Setting "current_name" should not be necessary however the kde-desktop-portal sets the filename without an extension. + * An alternative would be to match the extension to a filter and set "current_filter". + */ + DBus_AppendStringOption(dbus, &options, "current_name", location_name); + } else if (save_file_new_named && location_folder && location_name) { + /* Open a save dialog at a location with a suggested name */ + DBus_AppendByteArray(dbus, &options, "current_folder", location_folder); + DBus_AppendStringOption(dbus, &options, "current_name", location_name); + } else { + DBus_AppendByteArray(dbus, &options, "current_folder", default_location); + } } if (accept) { DBus_AppendStringOption(dbus, &options, "accept_label", accept); } dbus->message_iter_close_container(¶ms, &options); - DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_INFINITE, NULL); + DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_INFINITE, &error); + if (dbus->error_is_set(&error)) { + SDL_SetError("Failed to open dialog via DBus, %s: %s", error.name, error.message); + dbus->error_free(&error); + callback(userdata, NULL, -1); + goto cleanup; + } + if (reply) { DBusMessageIter reply_iter; dbus->message_iter_init(reply, &reply_iter); @@ -443,9 +510,16 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog } SDL_snprintf(filter, filter_len, SIGNAL_FILTER"%s'", signal_id); - dbus->bus_add_match(dbus->session_conn, filter, NULL); + dbus->bus_add_match(dbus->session_conn, filter, &error); SDL_free(filter); + if (dbus->error_is_set(&error)) { + SDL_SetError("Failed to set up DBus listener for dialog, %s: %s", error.name, error.message); + dbus->error_free(&error); + callback(userdata, NULL, -1); + goto cleanup; + } + SignalCallback *data = SDL_malloc(sizeof(SignalCallback)); if (!data) { callback(userdata, NULL, -1); @@ -469,6 +543,10 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog incorrect_type: dbus->message_unref(reply); + +cleanup: + SDL_free(location_name); + SDL_free(location_folder); } bool SDL_Portal_detect(void) diff --git a/libs/SDL3/src/dialog/unix/SDL_portaldialog.h b/libs/SDL3/src/dialog/unix/SDL_portaldialog.h index 4497287c..a31d0bfb 100644 --- a/libs/SDL3/src/dialog/unix/SDL_portaldialog.h +++ b/libs/SDL3/src/dialog/unix/SDL_portaldialog.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/dialog/unix/SDL_unixdialog.c b/libs/SDL3/src/dialog/unix/SDL_unixdialog.c index bec2ac95..d9b57c01 100644 --- a/libs/SDL3/src/dialog/unix/SDL_unixdialog.c +++ b/libs/SDL3/src/dialog/unix/SDL_unixdialog.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/dialog/unix/SDL_zenitydialog.c b/libs/SDL3/src/dialog/unix/SDL_zenitydialog.c index 4632c8e1..91fb3cd9 100644 --- a/libs/SDL3/src/dialog/unix/SDL_zenitydialog.c +++ b/libs/SDL3/src/dialog/unix/SDL_zenitydialog.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -21,6 +21,8 @@ #include "SDL_internal.h" #include "../SDL_dialog_utils.h" +#include "SDL_zenitydialog.h" +#include "SDL_zenitymessagebox.h" #define X11_HANDLE_MAX_WIDTH 28 typedef struct @@ -88,6 +90,7 @@ static bool get_x11_window_handle(SDL_PropertiesID props, char *out) static zenityArgs *create_zenity_args(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) { zenityArgs *args = SDL_calloc(1, sizeof(*args)); + int zenity_major = 0, zenity_minor = 0; if (!args) { return NULL; } @@ -110,6 +113,8 @@ static zenityArgs *create_zenity_args(SDL_FileDialogType type, SDL_DialogFileCal } args->argv = argv; + SDL_get_zenity_version(&zenity_major, &zenity_minor); + /* Properties can be destroyed as soon as the function returns; copy over what we need. */ #define COPY_STRING_PROPERTY(dst, prop) \ { \ @@ -158,7 +163,8 @@ static zenityArgs *create_zenity_args(SDL_FileDialogType type, SDL_DialogFileCal argv[argc++] = args->filename; } - if (get_x11_window_handle(props, args->x11_window_handle)) { + if (get_x11_window_handle(props, args->x11_window_handle) && + (zenity_major > 3 || (zenity_major == 3 && zenity_minor >= 6))) { argv[argc++] = "--modal"; argv[argc++] = "--attach"; argv[argc++] = args->x11_window_handle; @@ -186,7 +192,7 @@ static zenityArgs *create_zenity_args(SDL_FileDialogType type, SDL_DialogFileCal char *filter_str = convert_filter(filters[i], zenity_clean_name, "--file-filter=", " | ", "", - "*.", " *.", ""); + "*.", " *.", "", true); if (!filter_str) { while (i--) { diff --git a/libs/SDL3/src/dialog/unix/SDL_zenitydialog.h b/libs/SDL3/src/dialog/unix/SDL_zenitydialog.h index 4cfe8926..4f86498b 100644 --- a/libs/SDL3/src/dialog/unix/SDL_zenitydialog.h +++ b/libs/SDL3/src/dialog/unix/SDL_zenitydialog.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/dialog/unix/SDL_zenitymessagebox.c b/libs/SDL3/src/dialog/unix/SDL_zenitymessagebox.c new file mode 100644 index 00000000..3683b2a1 --- /dev/null +++ b/libs/SDL3/src/dialog/unix/SDL_zenitymessagebox.c @@ -0,0 +1,189 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#include "SDL_zenitymessagebox.h" + +#define ZENITY_VERSION_LEN 32 // Number of bytes to read from zenity --version (including NUL) + +#define MAX_BUTTONS 8 // Maximum number of buttons supported + +static bool parse_zenity_version(const char *version, int *major, int *minor) +{ + /* We expect the version string is in the form of MAJOR.MINOR.MICRO + * as described in meson.build. We'll ignore everything after that. + */ + const char *version_ptr = version; + char *end_ptr = NULL; + int tmp = (int) SDL_strtol(version_ptr, &end_ptr, 10); + if (tmp == 0 && end_ptr == version_ptr) { + return SDL_SetError("failed to get zenity major version number"); + } + *major = tmp; + + if (*end_ptr == '.') { + version_ptr = end_ptr + 1; // skip the dot + tmp = (int) SDL_strtol(version_ptr, &end_ptr, 10); + if (tmp == 0 && end_ptr == version_ptr) { + return SDL_SetError("failed to get zenity minor version number"); + } + *minor = tmp; + } else { + *minor = 0; + } + return true; +} + +bool SDL_get_zenity_version(int *major, int *minor) +{ + const char *argv[] = { "zenity", "--version", NULL }; + bool result = false; + + SDL_Process *process = SDL_CreateProcess(argv, true); + if (!process) { + return false; + } + + char *output = SDL_ReadProcess(process, NULL, NULL); + if (output) { + result = parse_zenity_version(output, major, minor); + SDL_free(output); + } + SDL_DestroyProcess(process); + + return result; +} + +bool SDL_Zenity_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) +{ + int exit_code = -1; + int zenity_major = 0, zenity_minor = 0, output_len = 0; + int argc = 5, i; + const char *argv[5 + 2 /* icon name */ + 2 /* title */ + 2 /* message */ + 2 * MAX_BUTTONS + 1 /* NULL */] = { + "zenity", "--question", "--switch", "--no-wrap", "--no-markup" + }; + SDL_Process *process; + + if (messageboxdata->numbuttons > MAX_BUTTONS) { + return SDL_SetError("Too many buttons (%d max allowed)", MAX_BUTTONS); + } + + // get zenity version so we know which arg to use + if (!SDL_get_zenity_version(&zenity_major, &zenity_minor)) { + return false; // get_zenity_version() calls SDL_SetError(), so message is already set + } + + /* https://gitlab.gnome.org/GNOME/zenity/-/commit/c686bdb1b45e95acf010efd9ca0c75527fbb4dea + * This commit removed --icon-name without adding a deprecation notice. + * We need to handle it gracefully, otherwise no message box will be shown. + */ + argv[argc++] = zenity_major > 3 || (zenity_major == 3 && zenity_minor >= 90) ? "--icon" : "--icon-name"; + switch (messageboxdata->flags & (SDL_MESSAGEBOX_ERROR | SDL_MESSAGEBOX_WARNING | SDL_MESSAGEBOX_INFORMATION)) { + case SDL_MESSAGEBOX_ERROR: + argv[argc++] = "dialog-error"; + break; + case SDL_MESSAGEBOX_WARNING: + argv[argc++] = "dialog-warning"; + break; + case SDL_MESSAGEBOX_INFORMATION: + default: + argv[argc++] = "dialog-information"; + break; + } + + if (messageboxdata->title && messageboxdata->title[0]) { + argv[argc++] = "--title"; + argv[argc++] = messageboxdata->title; + } else { + argv[argc++] = "--title="; + } + + if (messageboxdata->message && messageboxdata->message[0]) { + argv[argc++] = "--text"; + argv[argc++] = messageboxdata->message; + } else { + argv[argc++] = "--text="; + } + + for (i = 0; i < messageboxdata->numbuttons; ++i) { + if (messageboxdata->buttons[i].text && messageboxdata->buttons[i].text[0]) { + int len = SDL_strlen(messageboxdata->buttons[i].text); + if (len > output_len) { + output_len = len; + } + + argv[argc++] = "--extra-button"; + argv[argc++] = messageboxdata->buttons[i].text; + } else { + argv[argc++] = "--extra-button="; + } + } + if (messageboxdata->numbuttons == 0) { + argv[argc++] = "--extra-button=OK"; + } + argv[argc] = NULL; + + SDL_PropertiesID props = SDL_CreateProperties(); + if (!props) { + return false; + } + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, argv); + // If buttonID is set we need to wait and read the results + if (buttonID) { + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + } else { + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_NULL); + } + process = SDL_CreateProcessWithProperties(props); + SDL_DestroyProperties(props); + if (!process) { + return false; + } + if (buttonID) { + char *output = SDL_ReadProcess(process, NULL, &exit_code); + if (exit_code < 0 || exit_code == 255) { + SDL_free(output); + } else if (output) { + // It likes to add a newline... + char *tmp = SDL_strrchr(output, '\n'); + if (tmp) { + *tmp = '\0'; + } + + // Check which button got pressed + for (i = 0; i < messageboxdata->numbuttons; i += 1) { + if (messageboxdata->buttons[i].text) { + if (SDL_strcmp(output, messageboxdata->buttons[i].text) == 0) { + *buttonID = messageboxdata->buttons[i].buttonID; + break; + } + } + } + SDL_free(output); + } + } else { + SDL_WaitProcess(process, true, &exit_code); + } + SDL_DestroyProcess(process); + + return !(exit_code < 0 || exit_code == 255); +} diff --git a/libs/SDL3/src/video/windows/SDL_surface_utils.h b/libs/SDL3/src/dialog/unix/SDL_zenitymessagebox.h similarity index 73% rename from libs/SDL3/src/video/windows/SDL_surface_utils.h rename to libs/SDL3/src/dialog/unix/SDL_zenitymessagebox.h index a793cdcf..1e08ae99 100644 --- a/libs/SDL3/src/video/windows/SDL_surface_utils.h +++ b/libs/SDL3/src/dialog/unix/SDL_zenitymessagebox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -18,21 +18,11 @@ 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_surface_utils_h_ -#define SDL_surface_utils_h_ +#ifndef SDL_zenitymessagebox_h_ +#define SDL_zenitymessagebox_h_ -#include "../../core/windows/SDL_windows.h" +extern bool SDL_Zenity_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID); +extern bool SDL_get_zenity_version(int *major, int *minor); -#ifdef __cplusplus -extern "C" { -#endif - -extern HICON CreateIconFromSurface(SDL_Surface *surface); - -#ifdef __cplusplus -} -#endif - -#endif +#endif // SDL_zenitymessagebox_h_ diff --git a/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c b/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c index 2f4a12e8..660665c9 100644 --- a/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c +++ b/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -19,15 +19,367 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "SDL_internal.h" +#include "../../core/windows/SDL_windows.h" #include "../SDL_dialog.h" #include "../SDL_dialog_utils.h" -#include +#include #include #include -#include "../../core/windows/SDL_windows.h" +#include #include "../../thread/SDL_systhread.h" +#if WINVER < _WIN32_WINNT_VISTA +typedef struct _COMDLG_FILTERSPEC +{ + LPCWSTR pszName; + LPCWSTR pszSpec; +} COMDLG_FILTERSPEC; + +typedef enum FDE_OVERWRITE_RESPONSE +{ + FDEOR_DEFAULT, + FDEOR_ACCEPT, + FDEOR_REFUSE, +} FDE_OVERWRITE_RESPONSE; + +typedef enum FDE_SHAREVIOLATION_RESPONSE +{ + FDESVR_DEFAULT, + FDESVR_ACCEPT, + FDESVR_REFUSE, +} FDE_SHAREVIOLATION_RESPONSE; + +typedef enum FDAP +{ + FDAP_BOTTOM, + FDAP_TOP, +} FDAP; + +typedef ULONG SFGAOF; +typedef DWORD SHCONTF; + +#endif // WINVER < _WIN32_WINNT_VISTA + +#ifndef __IFileDialog_FWD_DEFINED__ +typedef struct IFileDialog IFileDialog; +#endif +#ifndef __IShellItem_FWD_DEFINED__ +typedef struct IShellItem IShellItem; +#endif +#ifndef __IFileOpenDialog_FWD_DEFINED__ +typedef struct IFileOpenDialog IFileOpenDialog; +#endif +#ifndef __IFileDialogEvents_FWD_DEFINED__ +typedef struct IFileDialogEvents IFileDialogEvents; +#endif +#ifndef __IShellItemArray_FWD_DEFINED__ +typedef struct IShellItemArray IShellItemArray; +#endif +#ifndef __IEnumShellItems_FWD_DEFINED__ +typedef struct IEnumShellItems IEnumShellItems; +#endif +#ifndef __IShellItemFilter_FWD_DEFINED__ +typedef struct IShellItemFilter IShellItemFilter; +#endif +#ifndef __IFileDialog2_FWD_DEFINED__ +typedef struct IFileDialog2 IFileDialog2; +#endif + +#ifndef __IShellItemFilter_INTERFACE_DEFINED__ +typedef struct IShellItemFilterVtbl +{ + HRESULT (__stdcall *QueryInterface)(IShellItemFilter *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IShellItemFilter *This); + ULONG (__stdcall *Release)(IShellItemFilter *This); + HRESULT (__stdcall *IncludeItem)(IShellItemFilter *This, IShellItem *psi); + HRESULT (__stdcall *GetEnumFlagsForItem)(IShellItemFilter *This, IShellItem *psi, SHCONTF *pgrfFlags); +} IShellItemFilterVtbl; + +struct IShellItemFilter +{ + IShellItemFilterVtbl *lpVtbl; +}; + +#endif // #ifndef __IShellItemFilter_INTERFACE_DEFINED__ + +#ifndef __IFileDialogEvents_INTERFACE_DEFINED__ +typedef struct IFileDialogEventsVtbl +{ + HRESULT (__stdcall *QueryInterface)(IFileDialogEvents *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IFileDialogEvents *This); + ULONG (__stdcall *Release)(IFileDialogEvents *This); + HRESULT (__stdcall *OnFileOk)(IFileDialogEvents *This, IFileDialog *pfd); + HRESULT (__stdcall *OnFolderChanging)(IFileDialogEvents *This, IFileDialog *pfd, IShellItem *psiFolder); + HRESULT (__stdcall *OnFolderChange)(IFileDialogEvents *This, IFileDialog *pfd); + HRESULT (__stdcall *OnSelectionChange)(IFileDialogEvents *This, IFileDialog *pfd); + HRESULT (__stdcall *OnShareViolation)(IFileDialogEvents *This, IFileDialog *pfd, IShellItem *psi, FDE_SHAREVIOLATION_RESPONSE *pResponse); + HRESULT (__stdcall *OnTypeChange)(IFileDialogEvents *This, IFileDialog *pfd); + HRESULT (__stdcall *OnOverwrite)(IFileDialogEvents *This, IFileDialog *pfd, IShellItem *psi, FDE_OVERWRITE_RESPONSE *pResponse); +} IFileDialogEventsVtbl; + +struct IFileDialogEvents +{ + IFileDialogEventsVtbl *lpVtbl; +}; + +#endif // #ifndef __IFileDialogEvents_INTERFACE_DEFINED__ + +#ifndef __IShellItem_INTERFACE_DEFINED__ +typedef enum _SIGDN { + SIGDN_NORMALDISPLAY = 0x00000000, + SIGDN_PARENTRELATIVEPARSING = 0x80018001, + SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000, + SIGDN_PARENTRELATIVEEDITING = 0x80031001, + SIGDN_DESKTOPABSOLUTEEDITING = 0x8004C000, + SIGDN_FILESYSPATH = 0x80058000, + SIGDN_URL = 0x80068000, + SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007C001, + SIGDN_PARENTRELATIVE = 0x80080001, + SIGDN_PARENTRELATIVEFORUI = 0x80094001 +} SIGDN; + +enum _SICHINTF { + SICHINT_DISPLAY = 0x00000000, + SICHINT_ALLFIELDS = 0x80000000, + SICHINT_CANONICAL = 0x10000000, + SICHINT_TEST_FILESYSPATH_IF_NOT_EQUAL = 0x20000000 +}; +typedef DWORD SICHINTF; + +extern const IID IID_IShellItem; + +typedef struct IShellItemVtbl +{ + HRESULT (__stdcall *QueryInterface)(IShellItem *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IShellItem *This); + ULONG (__stdcall *Release)(IShellItem *This); + HRESULT (__stdcall *BindToHandler)(IShellItem *This, IBindCtx *pbc, REFGUID bhid, REFIID riid, void **ppv); + HRESULT (__stdcall *GetParent)(IShellItem *This, IShellItem **ppsi); + HRESULT (__stdcall *GetDisplayName)(IShellItem *This, SIGDN sigdnName, LPWSTR *ppszName); + HRESULT (__stdcall *GetAttributes)(IShellItem *This, SFGAOF sfgaoMask, SFGAOF *psfgaoAttribs); + HRESULT (__stdcall *Compare)(IShellItem *This, IShellItem *psi, SICHINTF hint, int *piOrder); +} IShellItemVtbl; + +struct IShellItem +{ + IShellItemVtbl *lpVtbl; +}; + +#endif // #ifndef __IShellItem_INTERFACE_DEFINED__ + +#ifndef __IEnumShellItems_INTERFACE_DEFINED__ +typedef struct IEnumShellItemsVtbl +{ + HRESULT (__stdcall *QueryInterface)(IEnumShellItems *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IEnumShellItems *This); + ULONG (__stdcall *Release)(IEnumShellItems *This); + HRESULT (__stdcall *Next)(IEnumShellItems *This, ULONG celt, IShellItem **rgelt, ULONG *pceltFetched); + HRESULT (__stdcall *Skip)(IEnumShellItems *This, ULONG celt); + HRESULT (__stdcall *Reset)(IEnumShellItems *This); + HRESULT (__stdcall *Clone)(IEnumShellItems *This, IEnumShellItems **ppenum); +} IEnumShellItemsVtbl; + +struct IEnumShellItems +{ + IEnumShellItemsVtbl *lpVtbl; +}; + +#endif // #ifndef __IEnumShellItems_INTERFACE_DEFINED__ + +#ifndef __IShellItemArray_INTERFACE_DEFINED__ +typedef enum SIATTRIBFLAGS +{ + SIATTRIBFLAGS_AND = 0x1, + SIATTRIBFLAGS_OR = 0x2, + SIATTRIBFLAGS_APPCOMPAT = 0x3, + SIATTRIBFLAGS_MASK = 0x3, + SIATTRIBFLAGS_ALLITEMS = 0x4000 +} SIATTRIBFLAGS; + +typedef struct IShellItemArrayVtbl +{ + HRESULT (__stdcall *QueryInterface)(IShellItemArray *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IShellItemArray *This); + ULONG (__stdcall *Release)(IShellItemArray *This); + HRESULT (__stdcall *BindToHandler)(IShellItemArray *This, IBindCtx *pbc, REFGUID bhid, REFIID riid, void **ppvOut); + HRESULT (__stdcall *GetPropertyStore)(IShellItemArray *This, GETPROPERTYSTOREFLAGS flags, REFIID riid, void **ppv); + HRESULT (__stdcall *GetPropertyDescriptionList)(IShellItemArray *This, REFPROPERTYKEY keyType, REFIID riid, void **ppv); + HRESULT (__stdcall *GetAttributes)(IShellItemArray *This, SIATTRIBFLAGS AttribFlags, SFGAOF sfgaoMask, SFGAOF *psfgaoAttribs); + HRESULT (__stdcall *GetCount)(IShellItemArray *This, DWORD *pdwNumItems); + HRESULT (__stdcall *GetItemAt)(IShellItemArray *This, DWORD dwIndex, IShellItem **ppsi); + HRESULT (__stdcall *EnumItems)(IShellItemArray *This, IEnumShellItems **ppenumShellItems); +} IShellItemArrayVtbl; + +struct IShellItemArray +{ + IShellItemArrayVtbl *lpVtbl; +}; + +#endif // #ifndef __IShellItemArray_INTERFACE_DEFINED__ + +// Flags/GUIDs defined for compatibility with pre-Vista headers +#ifndef __IFileDialog_INTERFACE_DEFINED__ +enum _FILEOPENDIALOGOPTIONS +{ + FOS_OVERWRITEPROMPT = 0x2, + FOS_STRICTFILETYPES = 0x4, + FOS_NOCHANGEDIR = 0x8, + FOS_PICKFOLDERS = 0x20, + FOS_FORCEFILESYSTEM = 0x40, + FOS_ALLNONSTORAGEITEMS = 0x80, + FOS_NOVALIDATE = 0x100, + FOS_ALLOWMULTISELECT = 0x200, + FOS_PATHMUSTEXIST = 0x800, + FOS_FILEMUSTEXIST = 0x1000, + FOS_CREATEPROMPT = 0x2000, + FOS_SHAREAWARE = 0x4000, + FOS_NOREADONLYRETURN = 0x8000, + FOS_NOTESTFILECREATE = 0x10000, + FOS_HIDEMRUPLACES = 0x20000, + FOS_HIDEPINNEDPLACES = 0x40000, + FOS_NODEREFERENCELINKS = 0x100000, + FOS_OKBUTTONNEEDSINTERACTION = 0x200000, + FOS_DONTADDTORECENT = 0x2000000, + FOS_FORCESHOWHIDDEN = 0x10000000, + FOS_DEFAULTNOMINIMODE = 0x20000000, + FOS_FORCEPREVIEWPANEON = 0x40000000, + FOS_SUPPORTSTREAMABLEITEMS = 0x80000000 +}; + +typedef DWORD FILEOPENDIALOGOPTIONS; + +extern const IID IID_IFileDialog; + +typedef struct IFileDialogVtbl +{ + HRESULT (__stdcall *QueryInterface)(IFileDialog *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IFileDialog *This); + ULONG (__stdcall *Release)(IFileDialog *This); + HRESULT (__stdcall *Show)(IFileDialog *This, HWND hwndOwner); + HRESULT (__stdcall *SetFileTypes)(IFileDialog *This, UINT cFileTypes, const COMDLG_FILTERSPEC *rgFilterSpec); + HRESULT (__stdcall *SetFileTypeIndex)(IFileDialog *This, UINT iFileType); + HRESULT (__stdcall *GetFileTypeIndex)(IFileDialog *This, UINT *piFileType); + HRESULT (__stdcall *Advise)(IFileDialog *This, IFileDialogEvents *pfde, DWORD *pdwCookie); + HRESULT (__stdcall *Unadvise)(IFileDialog *This, DWORD dwCookie); + HRESULT (__stdcall *SetOptions)(IFileDialog *This, FILEOPENDIALOGOPTIONS fos); + HRESULT (__stdcall *GetOptions)(IFileDialog *This, FILEOPENDIALOGOPTIONS *pfos); + HRESULT (__stdcall *SetDefaultFolder)(IFileDialog *This, IShellItem *psi); + HRESULT (__stdcall *SetFolder)(IFileDialog *This, IShellItem *psi); + HRESULT (__stdcall *GetFolder)(IFileDialog *This, IShellItem **ppsi); + HRESULT (__stdcall *GetCurrentSelection)(IFileDialog *This, IShellItem **ppsi); + HRESULT (__stdcall *SetFileName)(IFileDialog *This, LPCWSTR pszName); + HRESULT (__stdcall *GetFileName)(IFileDialog *This, LPWSTR *pszName); + HRESULT (__stdcall *SetTitle)(IFileDialog *This, LPCWSTR pszTitle); + HRESULT (__stdcall *SetOkButtonLabel)(IFileDialog *This, LPCWSTR pszText); + HRESULT (__stdcall *SetFileNameLabel)(IFileDialog *This, LPCWSTR pszLabel); + HRESULT (__stdcall *GetResult)(IFileDialog *This, IShellItem **ppsi); + HRESULT (__stdcall *AddPlace)(IFileDialog *This, IShellItem *psi, FDAP fdap); + HRESULT (__stdcall *SetDefaultExtension)(IFileDialog *This, LPCWSTR pszDefaultExtension); + HRESULT (__stdcall *Close)(IFileDialog *This, HRESULT hr); + HRESULT (__stdcall *SetClientGuid)(IFileDialog *This, REFGUID guid); + HRESULT (__stdcall *ClearClientData)(IFileDialog *This); + HRESULT (__stdcall *SetFilter)(IFileDialog *This, IShellItemFilter *pFilter); +} IFileDialogVtbl; + +struct IFileDialog +{ + IFileDialogVtbl *lpVtbl; +}; + +#endif // #ifndef __IFileDialog_INTERFACE_DEFINED__ + +#ifndef __IFileOpenDialog_INTERFACE_DEFINED__ +typedef struct IFileOpenDialogVtbl +{ + HRESULT (__stdcall *QueryInterface)(IFileOpenDialog *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IFileOpenDialog *This); + ULONG (__stdcall *Release)(IFileOpenDialog *This); + HRESULT (__stdcall *Show)(IFileOpenDialog *This, HWND hwndOwner); + HRESULT (__stdcall *SetFileTypes)(IFileOpenDialog *This, UINT cFileTypes, const COMDLG_FILTERSPEC *rgFilterSpec); + HRESULT (__stdcall *SetFileTypeIndex)(IFileOpenDialog *This, UINT iFileType); + HRESULT (__stdcall *GetFileTypeIndex)(IFileOpenDialog *This, UINT *piFileType); + HRESULT (__stdcall *Advise)(IFileOpenDialog *This, IFileDialogEvents *pfde, DWORD *pdwCookie); + HRESULT (__stdcall *Unadvise)(IFileOpenDialog *This, DWORD dwCookie); + HRESULT (__stdcall *SetOptions)(IFileOpenDialog *This, FILEOPENDIALOGOPTIONS fos); + HRESULT (__stdcall *GetOptions)(IFileOpenDialog *This, FILEOPENDIALOGOPTIONS *pfos); + HRESULT (__stdcall *SetDefaultFolder)(IFileOpenDialog *This, IShellItem *psi); + HRESULT (__stdcall *SetFolder)(IFileOpenDialog *This, IShellItem *psi); + HRESULT (__stdcall *GetFolder)(IFileOpenDialog *This, IShellItem **ppsi); + HRESULT (__stdcall *GetCurrentSelection)(IFileOpenDialog *This, IShellItem **ppsi); + HRESULT (__stdcall *SetFileName)(IFileOpenDialog *This, LPCWSTR pszName); + HRESULT (__stdcall *GetFileName)(IFileOpenDialog *This, LPWSTR *pszName); + HRESULT (__stdcall *SetTitle)(IFileOpenDialog *This, LPCWSTR pszTitle); + HRESULT (__stdcall *SetOkButtonLabel)(IFileOpenDialog *This, LPCWSTR pszText); + HRESULT (__stdcall *SetFileNameLabel)(IFileOpenDialog *This, LPCWSTR pszLabel); + HRESULT (__stdcall *GetResult)(IFileOpenDialog *This, IShellItem **ppsi); + HRESULT (__stdcall *AddPlace)(IFileOpenDialog *This, IShellItem *psi, FDAP fdap); + HRESULT (__stdcall *SetDefaultExtension)(IFileOpenDialog *This, LPCWSTR pszDefaultExtension); + HRESULT (__stdcall *Close)(IFileOpenDialog *This, HRESULT hr); + HRESULT (__stdcall *SetClientGuid)(IFileOpenDialog *This, REFGUID guid); + HRESULT (__stdcall *ClearClientData)(IFileOpenDialog *This); + HRESULT (__stdcall *SetFilter)(IFileOpenDialog *This, IShellItemFilter *pFilter); + HRESULT (__stdcall *GetResults)(IFileOpenDialog *This, IShellItemArray **ppenum); + HRESULT (__stdcall *GetSelectedItems)(IFileOpenDialog *This, IShellItemArray **ppsai); +} IFileOpenDialogVtbl; + +struct IFileOpenDialog +{ + IFileOpenDialogVtbl *lpVtbl; +}; + +#endif // #ifndef __IFileOpenDialog_INTERFACE_DEFINED__ + +#ifndef __IFileDialog2_INTERFACE_DEFINED__ +typedef struct IFileDialog2Vtbl +{ + HRESULT (__stdcall *QueryInterface)(IFileDialog2 *This, REFIID riid, void **ppvObject); + ULONG (__stdcall *AddRef)(IFileDialog2 *This); + ULONG (__stdcall *Release)(IFileDialog2 *This); + HRESULT (__stdcall *Show)(IFileDialog2 *This, HWND hwndOwner); + HRESULT (__stdcall *SetFileTypes)(IFileDialog2 *This, UINT cFileTypes, const COMDLG_FILTERSPEC *rgFilterSpec); + HRESULT (__stdcall *SetFileTypeIndex)(IFileDialog2 *This, UINT iFileType); + HRESULT (__stdcall *GetFileTypeIndex)(IFileDialog2 *This, UINT *piFileType); + HRESULT (__stdcall *Advise)(IFileDialog2 *This, IFileDialogEvents *pfde, DWORD *pdwCookie); + HRESULT (__stdcall *Unadvise)(IFileDialog2 *This, DWORD dwCookie); + HRESULT (__stdcall *SetOptions)(IFileDialog2 *This, FILEOPENDIALOGOPTIONS fos); + HRESULT (__stdcall *GetOptions)(IFileDialog2 *This, FILEOPENDIALOGOPTIONS *pfos); + HRESULT (__stdcall *SetDefaultFolder)(IFileDialog2 *This, IShellItem *psi); + HRESULT (__stdcall *SetFolder)(IFileDialog2 *This, IShellItem *psi); + HRESULT (__stdcall *GetFolder)(IFileDialog2 *This, IShellItem **ppsi); + HRESULT (__stdcall *GetCurrentSelection)(IFileDialog2 *This, IShellItem **ppsi); + HRESULT (__stdcall *SetFileName)(IFileDialog2 *This, LPCWSTR pszName); + HRESULT (__stdcall *GetFileName)(IFileDialog2 *This, LPWSTR *pszName); + HRESULT (__stdcall *SetTitle)(IFileDialog2 *This, LPCWSTR pszTitle); + HRESULT (__stdcall *SetOkButtonLabel)(IFileDialog2 *This, LPCWSTR pszText); + HRESULT (__stdcall *SetFileNameLabel)(IFileDialog2 *This, LPCWSTR pszLabel); + HRESULT (__stdcall *GetResult)(IFileDialog2 *This, IShellItem **ppsi); + HRESULT (__stdcall *AddPlace)(IFileDialog2 *This, IShellItem *psi, FDAP fdap); + HRESULT (__stdcall *SetDefaultExtension)(IFileDialog2 *This, LPCWSTR pszDefaultExtension); + HRESULT (__stdcall *Close)(IFileDialog2 *This, HRESULT hr); + HRESULT (__stdcall *SetClientGuid)(IFileDialog2 *This, REFGUID guid); + HRESULT (__stdcall *ClearClientData)(IFileDialog2 *This); + HRESULT (__stdcall *SetFilter)(IFileDialog2 *This, IShellItemFilter *pFilter); + HRESULT (__stdcall *SetCancelButtonLabel)(IFileDialog2 *This, LPCWSTR pszLabel); + HRESULT (__stdcall *SetNavigationRoot)(IFileDialog2 *This, IShellItem *psi); +} IFileDialog2Vtbl; + +struct IFileDialog2 +{ + IFileDialog2Vtbl *lpVtbl; +}; + +#endif // #ifndef __IFileDialog2_INTERFACE_DEFINED__ + +/* *INDENT-OFF* */ // clang-format off +static const CLSID SDL_CLSID_FileOpenDialog = { 0xdc1c5a9c, 0xe88a, 0x4dde, { 0xa5, 0xa1, 0x60, 0xf8, 0x2a, 0x20, 0xae, 0xf7 } }; +static const CLSID SDL_CLSID_FileSaveDialog = { 0xc0b4e2f3, 0xba21, 0x4773, { 0x8d, 0xba, 0x33, 0x5e, 0xc9, 0x46, 0xeb, 0x8b } }; + +static const IID SDL_IID_IFileDialog = { 0x42f85136, 0xdb7e, 0x439c, { 0x85, 0xf1, 0xe4, 0x07, 0x5d, 0x13, 0x5f, 0xc8 } }; +static const IID SDL_IID_IFileDialog2 = { 0x61744fc7, 0x85b5, 0x4791, { 0xa9, 0xb0, 0x27, 0x22, 0x76, 0x30, 0x9b, 0x13 } }; +static const IID SDL_IID_IFileOpenDialog = { 0xd57c7288, 0xd4ad, 0x4768, { 0xbe, 0x02, 0x9d, 0x96, 0x95, 0x32, 0xd9, 0x60 } }; +/* *INDENT-ON* */ // clang-format on + // If this number is too small, selecting too many files will give an error #define SELECTLIST_SIZE 65536 @@ -35,9 +387,11 @@ typedef struct { bool is_save; wchar_t *filters_str; + int nfilters; char *default_file; SDL_Window *parent; DWORD flags; + bool allow_many; SDL_DialogFileCallback callback; void *userdata; char *title; @@ -48,6 +402,7 @@ typedef struct typedef struct { SDL_Window *parent; + bool allow_many; SDL_DialogFileCallback callback; char *default_folder; void *userdata; @@ -101,18 +456,331 @@ char *clear_filt_names(const char *filt) return cleared; } +// This function returns NOT success or error, but rather whether the callback +// was invoked or not (and if it was, no fallback should be attempted to prevent +// calling the callback twice). See https://github.com/libsdl-org/SDL/issues/15194 +bool windows_ShowModernFileFolderDialog(SDL_FileDialogType dialog_type, const char *default_file, SDL_Window *parent, bool allow_many, SDL_DialogFileCallback callback, void *userdata, const char *title, const char *accept, const char *cancel, wchar_t *filter_wchar, int nfilters) +{ + bool is_save = dialog_type == SDL_FILEDIALOG_SAVEFILE; + bool is_folder = dialog_type == SDL_FILEDIALOG_OPENFOLDER; + + if (is_save) { + // Just in case; the code relies on that + allow_many = false; + } + + HMODULE shell32_handle = NULL; + + typedef HRESULT(WINAPI *pfnSHCreateItemFromParsingName)(PCWSTR, IBindCtx *, REFIID, void **); + pfnSHCreateItemFromParsingName pSHCreateItemFromParsingName = NULL; + + IFileDialog *pFileDialog = NULL; + IFileOpenDialog *pFileOpenDialog = NULL; + IFileDialog2 *pFileDialog2 = NULL; + IShellItemArray *pItemArray = NULL; + IShellItem *pItem = NULL; + IShellItem *pFolderItem = NULL; + LPWSTR filePath = NULL; + COMDLG_FILTERSPEC *filter_data = NULL; + char **files = NULL; + wchar_t *title_w = NULL; + wchar_t *accept_w = NULL; + wchar_t *cancel_w = NULL; + FILEOPENDIALOGOPTIONS pfos; + + wchar_t *default_file_w = NULL; + wchar_t *default_folder_w = NULL; + + bool callback_called = false; + bool call_callback_on_error = false; + bool co_init = false; + + // We can assume shell32 is already loaded here. + shell32_handle = GetModuleHandle(TEXT("shell32.dll")); + if (!shell32_handle) { + goto quit; + } + + pSHCreateItemFromParsingName = (pfnSHCreateItemFromParsingName)GetProcAddress(shell32_handle, "SHCreateItemFromParsingName"); + if (!pSHCreateItemFromParsingName) { + goto quit; + } + + if (filter_wchar && nfilters > 0) { + wchar_t *filter_ptr = filter_wchar; + filter_data = SDL_calloc(sizeof(COMDLG_FILTERSPEC), nfilters); + if (!filter_data) { + goto quit; + } + + for (int i = 0; i < nfilters; i++) { + filter_data[i].pszName = filter_ptr; + filter_ptr += SDL_wcslen(filter_ptr) + 1; + filter_data[i].pszSpec = filter_ptr; + filter_ptr += SDL_wcslen(filter_ptr) + 1; + } + + // assert(*filter_ptr == L'\0'); + } + + if (title && (title_w = WIN_UTF8ToStringW(title)) == NULL) { + goto quit; + } + + if (accept && (accept_w = WIN_UTF8ToStringW(accept)) == NULL) { + goto quit; + } + + if (cancel && (cancel_w = WIN_UTF8ToStringW(cancel)) == NULL) { + goto quit; + } + + if (default_file) { + default_folder_w = WIN_UTF8ToStringW(default_file); + + if (!default_folder_w) { + goto quit; + } + + for (wchar_t *chrptr = default_folder_w; *chrptr; chrptr++) { + if (*chrptr == L'/' || *chrptr == L'\\') { + default_file_w = chrptr; + } + } + + if (!default_file_w) { + default_file_w = default_folder_w; + default_folder_w = NULL; + } else { + *default_file_w = L'\0'; + default_file_w++; + + if (SDL_wcslen(default_file_w) == 0) { + default_file_w = NULL; + } + } + } + +#define CHECK(op) if (!SUCCEEDED(op)) { goto quit; } + + CHECK(WIN_CoInitialize()); + + co_init = true; + + CHECK(CoCreateInstance(is_save ? &SDL_CLSID_FileSaveDialog : &SDL_CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, &SDL_IID_IFileDialog, (void**)&pFileDialog)); + CHECK(pFileDialog->lpVtbl->QueryInterface(pFileDialog, &SDL_IID_IFileDialog2, (void**)&pFileDialog2)); + + if (allow_many) { + CHECK(pFileDialog->lpVtbl->QueryInterface(pFileDialog, &SDL_IID_IFileOpenDialog, (void**)&pFileOpenDialog)); + } + + CHECK(pFileDialog2->lpVtbl->GetOptions(pFileDialog2, &pfos)); + + pfos |= FOS_NOCHANGEDIR; + if (allow_many) pfos |= FOS_ALLOWMULTISELECT; + if (is_save) pfos |= FOS_OVERWRITEPROMPT; + if (is_folder) pfos |= FOS_PICKFOLDERS; + + CHECK(pFileDialog2->lpVtbl->SetOptions(pFileDialog2, pfos)); + + if (cancel_w) { + CHECK(pFileDialog2->lpVtbl->SetCancelButtonLabel(pFileDialog2, cancel_w)); + } + + if (accept_w) { + CHECK(pFileDialog->lpVtbl->SetOkButtonLabel(pFileDialog, accept_w)); + } + + if (title_w) { + CHECK(pFileDialog->lpVtbl->SetTitle(pFileDialog, title_w)); + } + + if (filter_data) { + CHECK(pFileDialog->lpVtbl->SetFileTypes(pFileDialog, nfilters, filter_data)); + } + + if (default_folder_w) { + CHECK(pSHCreateItemFromParsingName(default_folder_w, NULL, &IID_IShellItem, (void**)&pFolderItem)); + CHECK(pFileDialog->lpVtbl->SetFolder(pFileDialog, pFolderItem)); + } + + if (default_file_w) { + CHECK(pFileDialog->lpVtbl->SetFileName(pFileDialog, default_file_w)); + } + + // Right after this, a dialog is shown. No fallback should be attempted on + // error to prevent showing two dialogs to the user. + call_callback_on_error = true; + + if (parent) { + HWND window = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(parent), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); + + HRESULT hr = pFileDialog->lpVtbl->Show(pFileDialog, window); + + if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { + const char * const results[] = { NULL }; + UINT selected_filter; + + // This is a one-based index, not zero-based. Doc link in similar comment below + CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); + callback(userdata, results, selected_filter - 1); + callback_called = true; + goto quit; + } else if (!SUCCEEDED(hr)) { + goto quit; + } + } else { + HRESULT hr = pFileDialog->lpVtbl->Show(pFileDialog, NULL); + + if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { + const char * const results[] = { NULL }; + UINT selected_filter; + + // This is a one-based index, not zero-based. Doc link in similar comment below + CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); + callback(userdata, results, selected_filter - 1); + callback_called = true; + goto quit; + } else if (!SUCCEEDED(hr)) { + goto quit; + } + } + + if (allow_many) { + DWORD nResults; + UINT selected_filter; + + CHECK(pFileOpenDialog->lpVtbl->GetResults(pFileOpenDialog, &pItemArray)); + CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); + CHECK(pItemArray->lpVtbl->GetCount(pItemArray, &nResults)); + + files = SDL_calloc(nResults + 1, sizeof(char*)); + if (!files) { + goto quit; + } + char** files_ptr = files; + + for (DWORD i = 0; i < nResults; i++) { + CHECK(pItemArray->lpVtbl->GetItemAt(pItemArray, i, &pItem)); + CHECK(pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, &filePath)); + + *(files_ptr++) = WIN_StringToUTF8(filePath); + + CoTaskMemFree(filePath); + filePath = NULL; + pItem->lpVtbl->Release(pItem); + pItem = NULL; + } + + callback(userdata, (const char * const *) files, selected_filter - 1); + callback_called = true; + } else { + // This is a one-based index, not zero-based. + // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-getfiletypeindex#parameters + UINT selected_filter; + + CHECK(pFileDialog->lpVtbl->GetResult(pFileDialog, &pItem)); + CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); + CHECK(pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, &filePath)); + + char *file = WIN_StringToUTF8(filePath); + if (!file) { + goto quit; + } + const char * const results[] = { file, NULL }; + callback(userdata, results, selected_filter - 1); + callback_called = true; + SDL_free(file); + } + +#undef CHECK + +quit: + if (!callback_called && call_callback_on_error) { + WIN_SetError("dialog"); + callback(userdata, NULL, -1); + callback_called = true; + } + + if (co_init) { + WIN_CoUninitialize(); + } + + if (pFileOpenDialog) { + pFileOpenDialog->lpVtbl->Release(pFileOpenDialog); + } + + if (pFileDialog2) { + pFileDialog2->lpVtbl->Release(pFileDialog2); + } + + if (pFileDialog) { + pFileDialog->lpVtbl->Release(pFileDialog); + } + + if (pItem) { + pItem->lpVtbl->Release(pItem); + } + + if (pFolderItem) { + pFolderItem->lpVtbl->Release(pFolderItem); + } + + if (pItemArray) { + pItemArray->lpVtbl->Release(pItemArray); + } + + if (filePath) { + CoTaskMemFree(filePath); + } + + // If both default_file_w and default_folder_w are non-NULL, then + // default_file_w is a pointer into default_folder_w. + if (default_folder_w) { + SDL_free(default_folder_w); + } else { + SDL_free(default_file_w); + } + + SDL_free(title_w); + + SDL_free(accept_w); + + SDL_free(cancel_w); + + SDL_free(filter_data); + + if (files) { + for (char** files_ptr = files; *files_ptr; files_ptr++) { + SDL_free(*files_ptr); + } + SDL_free(files); + } + + return callback_called; +} + // TODO: The new version of file dialogs void windows_ShowFileDialog(void *ptr) { + winArgs *args = (winArgs *) ptr; bool is_save = args->is_save; const char *default_file = args->default_file; SDL_Window *parent = args->parent; DWORD flags = args->flags; + bool allow_many = args->allow_many; SDL_DialogFileCallback callback = args->callback; void *userdata = args->userdata; const char *title = args->title; + const char *accept = args->accept; + const char *cancel = args->cancel; wchar_t *filter_wchar = args->filters_str; + int nfilters = args->nfilters; + + if (windows_ShowModernFileFolderDialog(is_save ? SDL_FILEDIALOG_SAVEFILE : SDL_FILEDIALOG_OPENFILE, default_file, parent, allow_many, callback, userdata, title, accept, cancel, filter_wchar, nfilters)) { + return; + } /* GetOpenFileName and GetSaveFileName have the same signature (yes, LPOPENFILENAMEW even for the save dialog) */ @@ -407,7 +1075,15 @@ void windows_ShowFolderDialog(void *ptr) SDL_DialogFileCallback callback = args->callback; void *userdata = args->userdata; HWND parent = NULL; + int allow_many = args->allow_many; + char *default_folder = args->default_folder; const char *title = args->title; + const char *accept = args->accept; + const char *cancel = args->cancel; + + if (windows_ShowModernFileFolderDialog(SDL_FILEDIALOG_OPENFOLDER, default_folder, window, allow_many, callback, userdata, title, accept, cancel, NULL, 0)) { + return; + } if (window) { parent = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); @@ -467,7 +1143,7 @@ wchar_t *win_get_filters(const SDL_DialogFileFilter *filters, int nfilters) // suffix needs two null bytes in case the filter list is empty char *filterlist = convert_filters(filters, nfilters, clear_filt_names, "", "", "\x01\x01", "", "\x01", - "\x01", "*.", ";*.", ""); + "\x01", "*.", ";*.", "", false); if (!filterlist) { return NULL; @@ -532,9 +1208,11 @@ static void ShowFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_ args->is_save = is_save; args->filters_str = filters_str; + args->nfilters = nfilters; args->default_file = default_location ? SDL_strdup(default_location) : NULL; args->parent = window; args->flags = flags; + args->allow_many = allow_many; args->callback = callback; args->userdata = userdata; args->title = title ? SDL_strdup(title) : NULL; @@ -571,6 +1249,7 @@ void ShowFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Windo } args->parent = window; + args->allow_many = allow_many; args->callback = callback; args->default_folder = default_location ? SDL_strdup(default_location) : NULL; args->userdata = userdata; diff --git a/libs/SDL3/src/dynapi/SDL_dynapi.c b/libs/SDL3/src/dynapi/SDL_dynapi.c index a7b51af6..495a8e52 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi.c +++ b/libs/SDL3/src/dynapi/SDL_dynapi.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -40,6 +40,8 @@ #include #define SDL_MAIN_NOIMPL // don't drag in header-only implementation of SDL_main #include +#include "../core/SDL_core_unsupported.h" +#include "../video/SDL_video_unsupported.h" // These headers have system specific definitions, so aren't included above @@ -86,7 +88,7 @@ static void SDL_InitDynamicAPI(void); } #define SDL_DYNAPI_VARARGS(_static, name, initcall) \ - _static bool SDLCALL SDL_SetError##name(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \ + _static bool SDLCALL SDL_SetError##name(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \ { \ char buf[128], *str = buf; \ int result; \ @@ -98,7 +100,7 @@ static void SDL_InitDynamicAPI(void); if (result >= 0 && (size_t)result >= sizeof(buf)) { \ str = NULL; \ va_start(ap, fmt); \ - result = jump_table.SDL_vasprintf(&str, fmt, ap); \ + result = jump_table.SDL_vasprintf(&str, fmt, ap); \ va_end(ap); \ } \ if (result >= 0) { \ @@ -107,7 +109,7 @@ static void SDL_InitDynamicAPI(void); if (str != buf) { \ jump_table.SDL_free(str); \ } \ - return false; \ + return false; \ } \ _static int SDLCALL SDL_sscanf##name(const char *buf, SDL_SCANF_FORMAT_STRING const char *fmt, ...) \ { \ @@ -149,7 +151,7 @@ static void SDL_InitDynamicAPI(void); va_end(ap); \ return result; \ } \ - _static size_t SDLCALL SDL_IOprintf##name(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \ + _static size_t SDLCALL SDL_IOprintf##name(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \ { \ size_t result; \ va_list ap; \ @@ -199,7 +201,7 @@ static void SDL_InitDynamicAPI(void); jump_table.SDL_LogMessageV(category, priority, fmt, ap); \ va_end(ap); \ } \ - SDL_DYNAPI_VARARGS_LOGFN(_static, name, initcall, Trace, TRACE) \ + SDL_DYNAPI_VARARGS_LOGFN(_static, name, initcall, Trace, TRACE) \ SDL_DYNAPI_VARARGS_LOGFN(_static, name, initcall, Verbose, VERBOSE) \ SDL_DYNAPI_VARARGS_LOGFN(_static, name, initcall, Debug, DEBUG) \ SDL_DYNAPI_VARARGS_LOGFN(_static, name, initcall, Info, INFO) \ @@ -500,9 +502,6 @@ static void dynapi_warn(const char *msg) extern "C" { #endif extern SDL_NORETURN void SDL_ExitProcess(int exitcode); -#ifdef __WATCOMC__ -#pragma aux SDL_ExitProcess aborts; -#endif #ifdef __cplusplus } #endif @@ -516,7 +515,7 @@ static void SDL_InitDynamicAPILocked(void) const DWORD rc = GetEnvironmentVariableA(SDL_DYNAMIC_API_ENVVAR, envbuf, (DWORD) sizeof (envbuf)); char *libname = ((rc != 0) && (rc < sizeof (envbuf))) ? envbuf : NULL; #else - char *libname = getenv(SDL_DYNAMIC_API_ENVVAR); + char *libname = getenv(SDL_DYNAMIC_API_ENVVAR); // This should NOT be SDL_getenv() #endif SDL_DYNAPI_ENTRYFN entry = NULL; // funcs from here by default. diff --git a/libs/SDL3/src/dynapi/SDL_dynapi.h b/libs/SDL3/src/dynapi/SDL_dynapi.h index e975be08..786d86ee 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi.h +++ b/libs/SDL3/src/dynapi/SDL_dynapi.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -57,7 +57,7 @@ #define SDL_DYNAMIC_API 0 #elif defined(SDL_PLATFORM_RISCOS) // probably not useful on RISC OS, since dlopen() can't be used when using static linking. #define SDL_DYNAMIC_API 0 -#elif defined(__clang_analyzer__) || defined(__INTELLISENSE__) || defined(SDL_THREAD_SAFETY_ANALYSIS) +#elif defined(__clang_analyzer__) || defined(__INTELLISENSE__) || defined(SDL_THREAD_SAFETY_ANALYSIS) || defined(__RESHARPER__) #define SDL_DYNAMIC_API 0 // Turn off for static analysis, so reports are more clear. #elif defined(SDL_PLATFORM_VITA) #define SDL_DYNAMIC_API 0 // vitasdk doesn't support dynamic linking diff --git a/libs/SDL3/src/dynapi/SDL_dynapi.sym b/libs/SDL3/src/dynapi/SDL_dynapi.sym index 91bd4300..ec2e97be 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi.sym +++ b/libs/SDL3/src/dynapi/SDL_dynapi.sym @@ -1240,7 +1240,7 @@ SDL3_0.0.0 { SDL_GetDefaultTextureScaleMode; SDL_CreateGPURenderState; SDL_SetGPURenderStateFragmentUniforms; - SDL_SetRenderGPUState; + SDL_SetGPURenderState; SDL_DestroyGPURenderState; SDL_SetWindowProgressState; SDL_SetWindowProgressValue; @@ -1251,9 +1251,26 @@ SDL3_0.0.0 { SDL_GetGPUDeviceProperties; SDL_CreateGPURenderer; SDL_PutAudioStreamPlanarData; - SDL_SetAudioIterationCallbacks; SDL_GetEventDescription; SDL_PutAudioStreamDataNoCopy; + SDL_AddAtomicU32; + SDL_hid_get_properties; + SDL_GetPixelFormatFromGPUTextureFormat; + SDL_GetGPUTextureFormatFromPixelFormat; + SDL_SetTexturePalette; + SDL_GetTexturePalette; + SDL_GetGPURendererDevice; + SDL_LoadPNG_IO; + SDL_LoadPNG; + SDL_SavePNG_IO; + SDL_SavePNG; + SDL_GetSystemPageSize; + SDL_GetPenDeviceType; + SDL_CreateAnimatedCursor; + SDL_RotateSurface; + SDL_LoadSurface_IO; + SDL_LoadSurface; + SDL_SetWindowFillDocument; # extra symbols go here (don't modify this line) local: *; }; diff --git a/libs/SDL3/src/dynapi/SDL_dynapi_overrides.h b/libs/SDL3/src/dynapi/SDL_dynapi_overrides.h index fcc0bad7..bf8d6427 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi_overrides.h +++ b/libs/SDL3/src/dynapi/SDL_dynapi_overrides.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -1265,7 +1265,7 @@ #define SDL_GetDefaultTextureScaleMode SDL_GetDefaultTextureScaleMode_REAL #define SDL_CreateGPURenderState SDL_CreateGPURenderState_REAL #define SDL_SetGPURenderStateFragmentUniforms SDL_SetGPURenderStateFragmentUniforms_REAL -#define SDL_SetRenderGPUState SDL_SetRenderGPUState_REAL +#define SDL_SetGPURenderState SDL_SetGPURenderState_REAL #define SDL_DestroyGPURenderState SDL_DestroyGPURenderState_REAL #define SDL_SetWindowProgressState SDL_SetWindowProgressState_REAL #define SDL_SetWindowProgressValue SDL_SetWindowProgressValue_REAL @@ -1276,6 +1276,24 @@ #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 +#define SDL_AddAtomicU32 SDL_AddAtomicU32_REAL +#define SDL_hid_get_properties SDL_hid_get_properties_REAL +#define SDL_GetPixelFormatFromGPUTextureFormat SDL_GetPixelFormatFromGPUTextureFormat_REAL +#define SDL_GetGPUTextureFormatFromPixelFormat SDL_GetGPUTextureFormatFromPixelFormat_REAL +#define JNI_OnLoad JNI_OnLoad_REAL +#define SDL_SetTexturePalette SDL_SetTexturePalette_REAL +#define SDL_GetTexturePalette SDL_GetTexturePalette_REAL +#define SDL_GetGPURendererDevice SDL_GetGPURendererDevice_REAL +#define SDL_LoadPNG_IO SDL_LoadPNG_IO_REAL +#define SDL_LoadPNG SDL_LoadPNG_REAL +#define SDL_SavePNG_IO SDL_SavePNG_IO_REAL +#define SDL_SavePNG SDL_SavePNG_REAL +#define SDL_GetSystemPageSize SDL_GetSystemPageSize_REAL +#define SDL_GetPenDeviceType SDL_GetPenDeviceType_REAL +#define SDL_CreateAnimatedCursor SDL_CreateAnimatedCursor_REAL +#define SDL_RotateSurface SDL_RotateSurface_REAL +#define SDL_LoadSurface_IO SDL_LoadSurface_IO_REAL +#define SDL_LoadSurface SDL_LoadSurface_REAL +#define SDL_SetWindowFillDocument SDL_SetWindowFillDocument_REAL diff --git a/libs/SDL3/src/dynapi/SDL_dynapi_procs.h b/libs/SDL3/src/dynapi/SDL_dynapi_procs.h index 60e0dfea..78a896a5 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi_procs.h +++ b/libs/SDL3/src/dynapi/SDL_dynapi_procs.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -28,7 +28,7 @@ Also, this file gets included multiple times, don't add #pragma once, etc. */ -// direct jump magic can use these, the rest needs special code. +/* direct jump magic can use these, the rest needs special code. */ #ifndef SDL_DYNAPI_PROC_NO_VARARGS SDL_DYNAPI_PROC(size_t,SDL_IOprintf,(SDL_IOStream *a, SDL_PRINTF_FORMAT_STRING const char *b, ...),(a,b),return) SDL_DYNAPI_PROC(void,SDL_Log,(SDL_PRINTF_FORMAT_STRING const char *a, ...),(a),) @@ -47,7 +47,7 @@ SDL_DYNAPI_PROC(int,SDL_sscanf,(const char *a, SDL_SCANF_FORMAT_STRING const cha SDL_DYNAPI_PROC(int,SDL_swprintf,(SDL_OUT_Z_CAP(b) wchar_t *a, size_t b, SDL_PRINTF_FORMAT_STRING const wchar_t *c, ...),(a,b,c),return) #endif -// New API symbols are added at the end +/* New API symbols are added at the end */ SDL_DYNAPI_PROC(SDL_Surface*,SDL_AcquireCameraFrame,(SDL_Camera *a, Uint64 *b),(a,b),return) SDL_DYNAPI_PROC(SDL_GPUCommandBuffer*,SDL_AcquireGPUCommandBuffer,(SDL_GPUDevice *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_AcquireGPUSwapchainTexture,(SDL_GPUCommandBuffer *a, SDL_Window *b, SDL_GPUTexture **c, Uint32 *d, Uint32 *e),(a,b,c,d,e),return) @@ -288,7 +288,7 @@ SDL_DYNAPI_PROC(const char*,SDL_GetCameraDriver,(int a),(a),return) SDL_DYNAPI_PROC(bool,SDL_GetCameraFormat,(SDL_Camera *a, SDL_CameraSpec *b),(a,b),return) SDL_DYNAPI_PROC(SDL_CameraID,SDL_GetCameraID,(SDL_Camera *a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_GetCameraName,(SDL_CameraID a),(a),return) -SDL_DYNAPI_PROC(int,SDL_GetCameraPermissionState,(SDL_Camera *a),(a),return) +SDL_DYNAPI_PROC(SDL_CameraPermissionState,SDL_GetCameraPermissionState,(SDL_Camera *a),(a),return) SDL_DYNAPI_PROC(SDL_CameraPosition,SDL_GetCameraPosition,(SDL_CameraID a),(a),return) SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetCameraProperties,(SDL_Camera *a),(a),return) SDL_DYNAPI_PROC(SDL_CameraSpec**,SDL_GetCameraSupportedFormats,(SDL_CameraID a, int *b),(a,b),return) @@ -830,7 +830,7 @@ SDL_DYNAPI_PROC(bool,SDL_SetAudioStreamInputChannelMap,(SDL_AudioStream *a, cons SDL_DYNAPI_PROC(bool,SDL_SetAudioStreamOutputChannelMap,(SDL_AudioStream *a, const int *b, int c),(a,b,c),return) SDL_DYNAPI_PROC(bool,SDL_SetAudioStreamPutCallback,(SDL_AudioStream *a, SDL_AudioStreamCallback b, void *c),(a,b,c),return) SDL_DYNAPI_PROC(bool,SDL_SetBooleanProperty,(SDL_PropertiesID a, const char *b, bool c),(a,b,c),return) -SDL_DYNAPI_PROC(bool,SDL_SetClipboardData,(SDL_ClipboardDataCallback a, SDL_ClipboardCleanupCallback b, void *c, const char **d, size_t e),(a,b,c,d,e),return) +SDL_DYNAPI_PROC(bool,SDL_SetClipboardData,(SDL_ClipboardDataCallback a, SDL_ClipboardCleanupCallback b, void *c, const char *const *d, size_t e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(bool,SDL_SetClipboardText,(const char *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_SetCurrentThreadPriority,(SDL_ThreadPriority a),(a),return) SDL_DYNAPI_PROC(bool,SDL_SetCursor,(SDL_Cursor *a),(a),return) @@ -1271,9 +1271,9 @@ SDL_DYNAPI_PROC(bool,SDL_SetRelativeMouseTransform,(SDL_MouseMotionTransformCall SDL_DYNAPI_PROC(bool,SDL_RenderTexture9GridTiled,(SDL_Renderer *a,SDL_Texture *b,const SDL_FRect *c,float d,float e,float f,float g,float h,const SDL_FRect *i,float j),(a,b,c,d,e,f,g,h,i,j),return) SDL_DYNAPI_PROC(bool,SDL_SetDefaultTextureScaleMode,(SDL_Renderer *a,SDL_ScaleMode b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_GetDefaultTextureScaleMode,(SDL_Renderer *a,SDL_ScaleMode *b),(a,b),return) -SDL_DYNAPI_PROC(SDL_GPURenderState*,SDL_CreateGPURenderState,(SDL_Renderer *a,SDL_GPURenderStateDesc *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_GPURenderState*,SDL_CreateGPURenderState,(SDL_Renderer *a,const SDL_GPURenderStateCreateInfo *b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_SetGPURenderStateFragmentUniforms,(SDL_GPURenderState *a,Uint32 b,const void *c,Uint32 d),(a,b,c,d),return) -SDL_DYNAPI_PROC(bool,SDL_SetRenderGPUState,(SDL_Renderer *a,SDL_GPURenderState *b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_SetGPURenderState,(SDL_Renderer *a,SDL_GPURenderState *b),(a,b),return) SDL_DYNAPI_PROC(void,SDL_DestroyGPURenderState,(SDL_GPURenderState *a),(a),) SDL_DYNAPI_PROC(bool,SDL_SetWindowProgressState,(SDL_Window *a,SDL_ProgressState b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_SetWindowProgressValue,(SDL_Window *a,float b),(a,b),return) @@ -1282,8 +1282,26 @@ SDL_DYNAPI_PROC(float,SDL_GetWindowProgressValue,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_SetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode b,SDL_TextureAddressMode c),(a,b,c),return) SDL_DYNAPI_PROC(bool,SDL_GetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode *b,SDL_TextureAddressMode *c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGPUDeviceProperties,(SDL_GPUDevice *a),(a),return) -SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateGPURenderer,(SDL_Window *a,SDL_GPUShaderFormat b,SDL_GPUDevice **c),(a,b,c),return) +SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateGPURenderer,(SDL_GPUDevice *a,SDL_Window *b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamPlanarData,(SDL_AudioStream *a,const void * const*b,int c,int d),(a,b,c,d),return) -SDL_DYNAPI_PROC(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) +SDL_DYNAPI_PROC(Uint32,SDL_AddAtomicU32,(SDL_AtomicU32 *a,int b),(a,b),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_hid_get_properties,(SDL_hid_device *a),(a),return) +SDL_DYNAPI_PROC(SDL_PixelFormat,SDL_GetPixelFormatFromGPUTextureFormat,(SDL_GPUTextureFormat a),(a),return) +SDL_DYNAPI_PROC(SDL_GPUTextureFormat,SDL_GetGPUTextureFormatFromPixelFormat,(SDL_PixelFormat a),(a),return) +SDL_DYNAPI_PROC(Sint32,JNI_OnLoad,(JavaVM *a, void *b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_SetTexturePalette,(SDL_Texture *a,SDL_Palette *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Palette*,SDL_GetTexturePalette,(SDL_Texture *a),(a),return) +SDL_DYNAPI_PROC(SDL_GPUDevice*,SDL_GetGPURendererDevice,(SDL_Renderer *a),(a),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadPNG_IO,(SDL_IOStream *a,bool b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadPNG,(const char *a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_SavePNG_IO,(SDL_Surface *a,SDL_IOStream *b,bool c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_SavePNG,(SDL_Surface *a,const char *b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_GetSystemPageSize,(void),(),return) +SDL_DYNAPI_PROC(SDL_PenDeviceType,SDL_GetPenDeviceType,(SDL_PenID a),(a),return) +SDL_DYNAPI_PROC(SDL_Cursor*,SDL_CreateAnimatedCursor,(SDL_CursorFrameInfo *a,int b,int c,int d),(a,b,c,d),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_RotateSurface,(SDL_Surface *a,float b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadSurface_IO,(SDL_IOStream *a,bool b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadSurface,(const char *a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_SetWindowFillDocument,(SDL_Window *a,bool b),(a,b),return) diff --git a/libs/SDL3/src/dynapi/SDL_dynapi_unsupported.h b/libs/SDL3/src/dynapi/SDL_dynapi_unsupported.h index 143943ab..46a307e5 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi_unsupported.h +++ b/libs/SDL3/src/dynapi/SDL_dynapi_unsupported.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -33,20 +33,4 @@ typedef struct ID3D11Device ID3D11Device; typedef struct IDirect3DDevice9 IDirect3DDevice9; #endif -#ifndef SDL_PLATFORM_GDK -typedef struct XTaskQueueHandle XTaskQueueHandle; -#endif - -#ifndef SDL_PLATFORM_GDK -typedef struct XUserHandle XUserHandle; -#endif - -#ifndef SDL_PLATFORM_ANDROID -typedef void *SDL_RequestAndroidPermissionCallback; -#endif - -#ifndef SDL_PLATFORM_IOS -typedef void *SDL_iOSAnimationCallback; -#endif - #endif diff --git a/libs/SDL3/src/dynapi/gendynapi.py b/libs/SDL3/src/dynapi/gendynapi.py index 09155231..4071b752 100644 --- a/libs/SDL3/src/dynapi/gendynapi.py +++ b/libs/SDL3/src/dynapi/gendynapi.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # Simple DirectMedia Layer -# Copyright (C) 1997-2025 Sam Lantinga +# Copyright (C) 1997-2026 Sam Lantinga # # This software is provided 'as-is', without any express or implied # warranty. In no event will the authors be held liable for any damages @@ -451,7 +451,7 @@ def get_header_list() -> list[Path]: return ret -# Write the new API in files: _procs.h _overrivides.h and .sym +# Write the new API in files: _procs.h _overrides.h and .sym def add_dyn_api(proc: SdlProcedure) -> None: decl_args: list[str] = [] call_args = [] diff --git a/libs/SDL3/src/events/SDL_categories.c b/libs/SDL3/src/events/SDL_categories.c index cb41f6fe..fa120775 100644 --- a/libs/SDL3/src/events/SDL_categories.c +++ b/libs/SDL3/src/events/SDL_categories.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_categories_c.h b/libs/SDL3/src/events/SDL_categories_c.h index 31d926a2..a1259e0e 100644 --- a/libs/SDL3/src/events/SDL_categories_c.h +++ b/libs/SDL3/src/events/SDL_categories_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_clipboardevents.c b/libs/SDL3/src/events/SDL_clipboardevents.c index 529af920..eb0e8927 100644 --- a/libs/SDL3/src/events/SDL_clipboardevents.c +++ b/libs/SDL3/src/events/SDL_clipboardevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_clipboardevents_c.h b/libs/SDL3/src/events/SDL_clipboardevents_c.h index 0b0f6288..03db8d6e 100644 --- a/libs/SDL3/src/events/SDL_clipboardevents_c.h +++ b/libs/SDL3/src/events/SDL_clipboardevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_displayevents.c b/libs/SDL3/src/events/SDL_displayevents.c index e41f75cc..08e57247 100644 --- a/libs/SDL3/src/events/SDL_displayevents.c +++ b/libs/SDL3/src/events/SDL_displayevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -26,6 +26,9 @@ void SDL_SendDisplayEvent(SDL_VideoDisplay *display, SDL_EventType displayevent, int data1, int data2) { + SDL_VideoDevice *_this; + bool post_event = true; + if (!display || display->id == 0) { return; } @@ -40,8 +43,14 @@ void SDL_SendDisplayEvent(SDL_VideoDisplay *display, SDL_EventType displayevent, break; } + // Only post if we are not currently quitting + _this = SDL_GetVideoDevice(); + if (_this == NULL || _this->is_quitting) { + post_event = false; + } + // Post the event, if desired - if (SDL_EventEnabled(displayevent)) { + if (post_event && SDL_EventEnabled(displayevent)) { SDL_Event event; event.type = displayevent; event.common.timestamp = 0; diff --git a/libs/SDL3/src/events/SDL_displayevents_c.h b/libs/SDL3/src/events/SDL_displayevents_c.h index ae0730c7..9efffe0d 100644 --- a/libs/SDL3/src/events/SDL_displayevents_c.h +++ b/libs/SDL3/src/events/SDL_displayevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_dropevents.c b/libs/SDL3/src/events/SDL_dropevents.c index 661f4f3e..9a93f9c4 100644 --- a/libs/SDL3/src/events/SDL_dropevents.c +++ b/libs/SDL3/src/events/SDL_dropevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_dropevents_c.h b/libs/SDL3/src/events/SDL_dropevents_c.h index efce0ac0..dc987db5 100644 --- a/libs/SDL3/src/events/SDL_dropevents_c.h +++ b/libs/SDL3/src/events/SDL_dropevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_events.c b/libs/SDL3/src/events/SDL_events.c index c04d52b2..a8ac576b 100644 --- a/libs/SDL3/src/events/SDL_events.c +++ b/libs/SDL3/src/events/SDL_events.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -29,12 +29,16 @@ #include "../audio/SDL_audio_c.h" #include "../camera/SDL_camera_c.h" #include "../timer/SDL_timer_c.h" +#include "../core/linux/SDL_udev.h" #ifndef SDL_JOYSTICK_DISABLED #include "../joystick/SDL_joystick_c.h" #endif #ifndef SDL_SENSOR_DISABLED #include "../sensor/SDL_sensor_c.h" #endif +#ifdef HAVE_DBUS_DBUS_H +#include "core/linux/SDL_dbus.h" +#endif #include "../video/SDL_sysvideo.h" #ifdef SDL_PLATFORM_ANDROID @@ -42,6 +46,10 @@ #include "../video/android/SDL_androidevents.h" #endif +#ifdef SDL_PLATFORM_UNIX +#include "../tray/SDL_tray_utils.h" +#endif + // An arbitrary limit so we don't have unbounded growth #define SDL_MAX_QUEUED_EVENTS 65535 @@ -51,6 +59,12 @@ // Determines how often to pump events if joysticks or sensors are actively being read #define EVENT_POLL_INTERVAL_NS SDL_MS_TO_NS(1) +// Determines how often to pump events if tray items are active +#define TRAY_POLL_INTERVAL_NS SDL_MS_TO_NS(50) + +// Determines how often to pump events if DBus is active +#define DBUS_POLL_INTERVAL_NS (3 * SDL_NS_PER_SECOND) + // Make sure the type in the SDL_Event aligns properly across the union SDL_COMPILE_TIME_ASSERT(SDL_Event_type, sizeof(Uint32) == sizeof(SDL_EventType)); @@ -458,8 +472,8 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) } else { plusstr[0] = '\0'; } - (void)SDL_snprintf(details, sizeof(details), "%s (timestamp=%u windowid=%u code=%d data1=%p data2=%p)", - plusstr, (uint)event->user.timestamp, (uint)event->user.windowID, + (void)SDL_snprintf(details, sizeof(details), "%s (timestamp=%" SDL_PRIu64 " windowid=%u code=%d data1=%p data2=%p)", + plusstr, event->user.timestamp, (uint)event->user.windowID, (int)event->user.code, event->user.data1, event->user.data2); } @@ -471,7 +485,7 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) SDL_strlcpy(details, " (THIS IS PROBABLY A BUG!)", sizeof(details)); break; SDL_EVENT_CASE(SDL_EVENT_QUIT) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u)", (uint)event->quit.timestamp); + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 ")", event->quit.timestamp); break; SDL_EVENT_CASE(SDL_EVENT_TERMINATING) break; @@ -497,8 +511,8 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) #define SDL_RENDEREVENT_CASE(x) \ case x: \ SDL_strlcpy(name, #x, sizeof(name)); \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u event=%s windowid=%u)", \ - (uint)event->display.timestamp, name, (uint)event->render.windowID); \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " event=%s windowid=%u)", \ + event->display.timestamp, name, (uint)event->render.windowID); \ break SDL_RENDEREVENT_CASE(SDL_EVENT_RENDER_TARGETS_RESET); SDL_RENDEREVENT_CASE(SDL_EVENT_RENDER_DEVICE_RESET); @@ -507,8 +521,8 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) #define SDL_DISPLAYEVENT_CASE(x) \ case x: \ SDL_strlcpy(name, #x, sizeof(name)); \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u display=%u event=%s data1=%d, data2=%d)", \ - (uint)event->display.timestamp, (uint)event->display.displayID, name, (int)event->display.data1, (int)event->display.data2); \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " display=%u event=%s data1=%d, data2=%d)", \ + event->display.timestamp, (uint)event->display.displayID, name, (int)event->display.data1, (int)event->display.data2); \ break SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_ORIENTATION); SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_ADDED); @@ -517,13 +531,14 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED); SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED); SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED); + SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED); #undef SDL_DISPLAYEVENT_CASE #define SDL_WINDOWEVENT_CASE(x) \ case x: \ SDL_strlcpy(name, #x, sizeof(name)); \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u event=%s data1=%d data2=%d)", \ - (uint)event->window.timestamp, (uint)event->window.windowID, name, (int)event->window.data1, (int)event->window.data2); \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u event=%s data1=%d data2=%d)", \ + event->window.timestamp, (uint)event->window.windowID, name, (int)event->window.data1, (int)event->window.data2); \ break SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_SHOWN); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_HIDDEN); @@ -532,7 +547,6 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_RESIZED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_METAL_VIEW_RESIZED); - SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_SAFE_AREA_CHANGED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_MINIMIZED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_MAXIMIZED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_RESTORED); @@ -545,6 +559,7 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_ICCPROF_CHANGED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_DISPLAY_CHANGED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED); + SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_SAFE_AREA_CHANGED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_OCCLUDED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_ENTER_FULLSCREEN); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_LEAVE_FULLSCREEN); @@ -552,7 +567,7 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_HDR_STATE_CHANGED); #undef SDL_WINDOWEVENT_CASE -#define PRINT_KEYDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%u)", (uint)event->kdevice.timestamp, (uint)event->kdevice.which) +#define PRINT_KEYDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%u)", event->kdevice.timestamp, (uint)event->kdevice.which) SDL_EVENT_CASE(SDL_EVENT_KEYBOARD_ADDED) PRINT_KEYDEV_EVENT(event); break; @@ -562,8 +577,8 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) #undef PRINT_KEYDEV_EVENT #define PRINT_KEY_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u state=%s repeat=%s scancode=%u keycode=%u mod=0x%x)", \ - (uint)event->key.timestamp, (uint)event->key.windowID, (uint)event->key.which, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u state=%s repeat=%s scancode=%u keycode=%u mod=0x%x)", \ + event->key.timestamp, (uint)event->key.windowID, (uint)event->key.which, \ event->key.down ? "pressed" : "released", \ event->key.repeat ? "true" : "false", \ (uint)event->key.scancode, \ @@ -578,22 +593,26 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) #undef PRINT_KEY_EVENT SDL_EVENT_CASE(SDL_EVENT_TEXT_EDITING) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u text='%s' start=%d length=%d)", - (uint)event->edit.timestamp, (uint)event->edit.windowID, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u text='%s' start=%d length=%d)", + event->edit.timestamp, (uint)event->edit.windowID, event->edit.text, (int)event->edit.start, (int)event->edit.length); break; SDL_EVENT_CASE(SDL_EVENT_TEXT_EDITING_CANDIDATES) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u num_candidates=%d selected_candidate=%d)", - (uint)event->edit_candidates.timestamp, (uint)event->edit_candidates.windowID, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u num_candidates=%d selected_candidate=%d)", + event->edit_candidates.timestamp, (uint)event->edit_candidates.windowID, (int)event->edit_candidates.num_candidates, (int)event->edit_candidates.selected_candidate); break; SDL_EVENT_CASE(SDL_EVENT_TEXT_INPUT) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u text='%s')", (uint)event->text.timestamp, (uint)event->text.windowID, event->text.text); + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u text='%s')", event->text.timestamp, (uint)event->text.windowID, event->text.text); + break; + SDL_EVENT_CASE(SDL_EVENT_SCREEN_KEYBOARD_SHOWN) + break; + SDL_EVENT_CASE(SDL_EVENT_SCREEN_KEYBOARD_HIDDEN) break; -#define PRINT_MOUSEDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%u)", (uint)event->mdevice.timestamp, (uint)event->mdevice.which) +#define PRINT_MOUSEDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%u)", event->mdevice.timestamp, (uint)event->mdevice.which) SDL_EVENT_CASE(SDL_EVENT_MOUSE_ADDED) PRINT_MOUSEDEV_EVENT(event); break; @@ -603,16 +622,16 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) #undef PRINT_MOUSEDEV_EVENT SDL_EVENT_CASE(SDL_EVENT_MOUSE_MOTION) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u state=%u x=%g y=%g xrel=%g yrel=%g)", - (uint)event->motion.timestamp, (uint)event->motion.windowID, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u state=%u x=%g y=%g xrel=%g yrel=%g)", + event->motion.timestamp, (uint)event->motion.windowID, (uint)event->motion.which, (uint)event->motion.state, event->motion.x, event->motion.y, event->motion.xrel, event->motion.yrel); break; #define PRINT_MBUTTON_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u button=%u state=%s clicks=%u x=%g y=%g)", \ - (uint)event->button.timestamp, (uint)event->button.windowID, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u button=%u state=%s clicks=%u x=%g y=%g)", \ + event->button.timestamp, (uint)event->button.windowID, \ (uint)event->button.which, (uint)event->button.button, \ event->button.down ? "pressed" : "released", \ (uint)event->button.clicks, event->button.x, event->button.y) @@ -625,34 +644,34 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) #undef PRINT_MBUTTON_EVENT SDL_EVENT_CASE(SDL_EVENT_MOUSE_WHEEL) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u x=%g y=%g integer_x=%d integer_y=%d direction=%s)", - (uint)event->wheel.timestamp, (uint)event->wheel.windowID, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u x=%g y=%g integer_x=%d integer_y=%d direction=%s)", + event->wheel.timestamp, (uint)event->wheel.windowID, (uint)event->wheel.which, event->wheel.x, event->wheel.y, (int)event->wheel.integer_x, (int)event->wheel.integer_y, event->wheel.direction == SDL_MOUSEWHEEL_NORMAL ? "normal" : "flipped"); break; SDL_EVENT_CASE(SDL_EVENT_JOYSTICK_AXIS_MOTION) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d axis=%u value=%d)", - (uint)event->jaxis.timestamp, (int)event->jaxis.which, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d axis=%u value=%d)", + event->jaxis.timestamp, (int)event->jaxis.which, (uint)event->jaxis.axis, (int)event->jaxis.value); break; SDL_EVENT_CASE(SDL_EVENT_JOYSTICK_BALL_MOTION) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d ball=%u xrel=%d yrel=%d)", - (uint)event->jball.timestamp, (int)event->jball.which, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d ball=%u xrel=%d yrel=%d)", + event->jball.timestamp, (int)event->jball.which, (uint)event->jball.ball, (int)event->jball.xrel, (int)event->jball.yrel); break; SDL_EVENT_CASE(SDL_EVENT_JOYSTICK_HAT_MOTION) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d hat=%u value=%u)", - (uint)event->jhat.timestamp, (int)event->jhat.which, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d hat=%u value=%u)", + event->jhat.timestamp, (int)event->jhat.which, (uint)event->jhat.hat, (uint)event->jhat.value); break; #define PRINT_JBUTTON_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d button=%u state=%s)", \ - (uint)event->jbutton.timestamp, (int)event->jbutton.which, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d button=%u state=%s)", \ + event->jbutton.timestamp, (int)event->jbutton.which, \ (uint)event->jbutton.button, event->jbutton.down ? "pressed" : "released") SDL_EVENT_CASE(SDL_EVENT_JOYSTICK_BUTTON_DOWN) PRINT_JBUTTON_EVENT(event); @@ -663,12 +682,12 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) #undef PRINT_JBUTTON_EVENT SDL_EVENT_CASE(SDL_EVENT_JOYSTICK_BATTERY_UPDATED) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d state=%u percent=%d)", - (uint)event->jbattery.timestamp, (int)event->jbattery.which, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d state=%u percent=%d)", + event->jbattery.timestamp, (int)event->jbattery.which, event->jbattery.state, event->jbattery.percent); break; -#define PRINT_JOYDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d)", (uint)event->jdevice.timestamp, (int)event->jdevice.which) +#define PRINT_JOYDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d)", event->jdevice.timestamp, (int)event->jdevice.which) SDL_EVENT_CASE(SDL_EVENT_JOYSTICK_ADDED) PRINT_JOYDEV_EVENT(event); break; @@ -681,14 +700,14 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) #undef PRINT_JOYDEV_EVENT SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_AXIS_MOTION) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d axis=%u value=%d)", - (uint)event->gaxis.timestamp, (int)event->gaxis.which, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d axis=%u value=%d)", + event->gaxis.timestamp, (int)event->gaxis.which, (uint)event->gaxis.axis, (int)event->gaxis.value); break; #define PRINT_CBUTTON_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d button=%u state=%s)", \ - (uint)event->gbutton.timestamp, (int)event->gbutton.which, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d button=%u state=%s)", \ + event->gbutton.timestamp, (int)event->gbutton.which, \ (uint)event->gbutton.button, event->gbutton.down ? "pressed" : "released") SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_BUTTON_DOWN) PRINT_CBUTTON_EVENT(event); @@ -698,7 +717,7 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) break; #undef PRINT_CBUTTON_EVENT -#define PRINT_GAMEPADDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d)", (uint)event->gdevice.timestamp, (int)event->gdevice.which) +#define PRINT_GAMEPADDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d)", event->gdevice.timestamp, (int)event->gdevice.which) SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_ADDED) PRINT_GAMEPADDEV_EVENT(event); break; @@ -717,8 +736,8 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) #undef PRINT_GAMEPADDEV_EVENT #define PRINT_CTOUCHPAD_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d touchpad=%d finger=%d x=%f y=%f pressure=%f)", \ - (uint)event->gtouchpad.timestamp, (int)event->gtouchpad.which, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d touchpad=%d finger=%d x=%f y=%f pressure=%f)", \ + event->gtouchpad.timestamp, (int)event->gtouchpad.which, \ (int)event->gtouchpad.touchpad, (int)event->gtouchpad.finger, \ event->gtouchpad.x, event->gtouchpad.y, event->gtouchpad.pressure) SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN) @@ -733,14 +752,14 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) #undef PRINT_CTOUCHPAD_EVENT SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_SENSOR_UPDATE) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d sensor=%d data[0]=%f data[1]=%f data[2]=%f)", - (uint)event->gsensor.timestamp, (int)event->gsensor.which, (int)event->gsensor.sensor, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d sensor=%d data[0]=%f data[1]=%f data[2]=%f)", + event->gsensor.timestamp, (int)event->gsensor.which, (int)event->gsensor.sensor, event->gsensor.data[0], event->gsensor.data[1], event->gsensor.data[2]); break; #define PRINT_FINGER_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u touchid=%" SDL_PRIu64 " fingerid=%" SDL_PRIu64 " x=%f y=%f dx=%f dy=%f pressure=%f)", \ - (uint)event->tfinger.timestamp, event->tfinger.touchID, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " touchid=%" SDL_PRIu64 " fingerid=%" SDL_PRIu64 " x=%f y=%f dx=%f dy=%f pressure=%f)", \ + event->tfinger.timestamp, event->tfinger.touchID, \ event->tfinger.fingerID, event->tfinger.x, event->tfinger.y, \ event->tfinger.dx, event->tfinger.dy, event->tfinger.pressure) SDL_EVENT_CASE(SDL_EVENT_FINGER_DOWN) @@ -757,9 +776,23 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) break; #undef PRINT_FINGER_EVENT +#define PRINT_PINCH_EVENT(event) \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " scale=%f)", \ + event->pinch.timestamp, event->pinch.scale) + SDL_EVENT_CASE(SDL_EVENT_PINCH_BEGIN) + PRINT_PINCH_EVENT(event); + break; + SDL_EVENT_CASE(SDL_EVENT_PINCH_UPDATE) + PRINT_PINCH_EVENT(event); + break; + SDL_EVENT_CASE(SDL_EVENT_PINCH_END) + PRINT_PINCH_EVENT(event); + break; +#undef PRINT_PINCH_EVENT + #define PRINT_PTOUCH_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u pen_state=%u x=%g y=%g eraser=%s state=%s)", \ - (uint)event->ptouch.timestamp, (uint)event->ptouch.windowID, (uint)event->ptouch.which, (uint)event->ptouch.pen_state, event->ptouch.x, event->ptouch.y, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u pen_state=%u x=%g y=%g eraser=%s state=%s)", \ + event->ptouch.timestamp, (uint)event->ptouch.windowID, (uint)event->ptouch.which, (uint)event->ptouch.pen_state, event->ptouch.x, event->ptouch.y, \ event->ptouch.eraser ? "yes" : "no", event->ptouch.down ? "down" : "up"); SDL_EVENT_CASE(SDL_EVENT_PEN_DOWN) PRINT_PTOUCH_EVENT(event); @@ -770,8 +803,8 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) #undef PRINT_PTOUCH_EVENT #define PRINT_PPROXIMITY_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u)", \ - (uint)event->pproximity.timestamp, (uint)event->pproximity.windowID, (uint)event->pproximity.which); + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u)", \ + event->pproximity.timestamp, (uint)event->pproximity.windowID, (uint)event->pproximity.which); SDL_EVENT_CASE(SDL_EVENT_PEN_PROXIMITY_IN) PRINT_PPROXIMITY_EVENT(event); break; @@ -781,19 +814,19 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) #undef PRINT_PPROXIMITY_EVENT SDL_EVENT_CASE(SDL_EVENT_PEN_AXIS) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u pen_state=%u x=%g y=%g axis=%s value=%g)", - (uint)event->paxis.timestamp, (uint)event->paxis.windowID, (uint)event->paxis.which, (uint)event->paxis.pen_state, event->paxis.x, event->paxis.y, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u pen_state=%u x=%g y=%g axis=%s value=%g)", + event->paxis.timestamp, (uint)event->paxis.windowID, (uint)event->paxis.which, (uint)event->paxis.pen_state, event->paxis.x, event->paxis.y, ((((int) event->paxis.axis) >= 0) && (event->paxis.axis < SDL_arraysize(pen_axisnames))) ? pen_axisnames[event->paxis.axis] : "[UNKNOWN]", event->paxis.value); break; SDL_EVENT_CASE(SDL_EVENT_PEN_MOTION) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u pen_state=%u x=%g y=%g)", - (uint)event->pmotion.timestamp, (uint)event->pmotion.windowID, (uint)event->pmotion.which, (uint)event->pmotion.pen_state, event->pmotion.x, event->pmotion.y); + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u pen_state=%u x=%g y=%g)", + event->pmotion.timestamp, (uint)event->pmotion.windowID, (uint)event->pmotion.which, (uint)event->pmotion.pen_state, event->pmotion.x, event->pmotion.y); break; #define PRINT_PBUTTON_EVENT(event) \ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u pen_state=%u x=%g y=%g button=%u state=%s)", \ - (uint)event->pbutton.timestamp, (uint)event->pbutton.windowID, (uint)event->pbutton.which, (uint)event->pbutton.pen_state, event->pbutton.x, event->pbutton.y, \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " windowid=%u which=%u pen_state=%u x=%g y=%g button=%u state=%s)", \ + event->pbutton.timestamp, (uint)event->pbutton.windowID, (uint)event->pbutton.which, (uint)event->pbutton.pen_state, event->pbutton.x, event->pbutton.y, \ (uint)event->pbutton.button, event->pbutton.down ? "down" : "up"); SDL_EVENT_CASE(SDL_EVENT_PEN_BUTTON_DOWN) PRINT_PBUTTON_EVENT(event); @@ -803,7 +836,7 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) break; #undef PRINT_PBUTTON_EVENT -#define PRINT_DROP_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (data='%s' timestamp=%u windowid=%u x=%f y=%f)", event->drop.data, (uint)event->drop.timestamp, (uint)event->drop.windowID, event->drop.x, event->drop.y) +#define PRINT_DROP_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (data='%s' timestamp=%" SDL_PRIu64 " windowid=%u x=%f y=%f)", event->drop.data, event->drop.timestamp, (uint)event->drop.windowID, event->drop.x, event->drop.y) SDL_EVENT_CASE(SDL_EVENT_DROP_FILE) PRINT_DROP_EVENT(event); break; @@ -821,7 +854,7 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) break; #undef PRINT_DROP_EVENT -#define PRINT_AUDIODEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%u recording=%s)", (uint)event->adevice.timestamp, (uint)event->adevice.which, event->adevice.recording ? "true" : "false") +#define PRINT_AUDIODEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%u recording=%s)", event->adevice.timestamp, (uint)event->adevice.which, event->adevice.recording ? "true" : "false") SDL_EVENT_CASE(SDL_EVENT_AUDIO_DEVICE_ADDED) PRINT_AUDIODEV_EVENT(event); break; @@ -833,7 +866,7 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) break; #undef PRINT_AUDIODEV_EVENT -#define PRINT_CAMERADEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%u)", (uint)event->cdevice.timestamp, (uint)event->cdevice.which) +#define PRINT_CAMERADEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%u)", event->cdevice.timestamp, (uint)event->cdevice.which) SDL_EVENT_CASE(SDL_EVENT_CAMERA_DEVICE_ADDED) PRINT_CAMERADEV_EVENT(event); break; @@ -849,8 +882,8 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) #undef PRINT_CAMERADEV_EVENT SDL_EVENT_CASE(SDL_EVENT_SENSOR_UPDATE) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d data[0]=%f data[1]=%f data[2]=%f data[3]=%f data[4]=%f data[5]=%f)", - (uint)event->sensor.timestamp, (int)event->sensor.which, + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d data[0]=%f data[1]=%f data[2]=%f data[3]=%f data[4]=%f data[5]=%f)", + event->sensor.timestamp, (int)event->sensor.which, event->sensor.data[0], event->sensor.data[1], event->sensor.data[2], event->sensor.data[3], event->sensor.data[4], event->sensor.data[5]); break; @@ -889,12 +922,13 @@ static void SDL_LogEvent(const SDL_Event *event) return; } - // sensor/mouse/pen/finger motion are spammy, ignore these if they aren't demanded. + // sensor/mouse/pen/finger/pinch motion are spammy, ignore these if they aren't demanded. if ((SDL_EventLoggingVerbosity < 2) && ((event->type == SDL_EVENT_MOUSE_MOTION) || (event->type == SDL_EVENT_FINGER_MOTION) || (event->type == SDL_EVENT_PEN_AXIS) || (event->type == SDL_EVENT_PEN_MOTION) || + (event->type == SDL_EVENT_PINCH_UPDATE) || (event->type == SDL_EVENT_GAMEPAD_AXIS_MOTION) || (event->type == SDL_EVENT_GAMEPAD_SENSOR_UPDATE) || (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION) || @@ -1131,9 +1165,10 @@ static int SDL_PeepEventsInternal(SDL_Event *events, int numevents, SDL_EventAct return -1; } if (action == SDL_ADDEVENT) { - if (!events) { + CHECK_PARAM(!events) { SDL_UnlockMutex(SDL_EventQ.lock); - return SDL_InvalidParamError("events"); + SDL_InvalidParamError("events"); + return -1; } for (i = 0; i < numevents; ++i) { used += SDL_AddEvent(&events[i]); @@ -1423,6 +1458,10 @@ bool SDL_RunOnMainThread(SDL_MainThreadCallback callback, void *userdata, bool w void SDL_PumpEventMaintenance(void) { +#ifdef SDL_USE_LIBUDEV + SDL_UDEV_Poll(); +#endif + #ifndef SDL_AUDIO_DISABLED SDL_UpdateAudio(); #endif @@ -1445,6 +1484,10 @@ void SDL_PumpEventMaintenance(void) } #endif + SDL_SendPendingPenProximity(); + + SDL_UpdateCursorAnimation(); + SDL_UpdateTrays(); SDL_SendPendingSignalEvents(); // in case we had a signal handler fire, etc. @@ -1453,6 +1496,9 @@ void SDL_PumpEventMaintenance(void) // Run the system dependent event loops static void SDL_PumpEventsInternal(bool push_sentinel) { + // This should only be called on the main thread, check in debug builds + SDL_assert(SDL_IsMainThread()); + // Free any temporary memory from old events SDL_FreeTemporaryMemory(); @@ -1462,6 +1508,11 @@ static void SDL_PumpEventsInternal(bool push_sentinel) // Run any pending main thread callbacks SDL_RunMainThreadCallbacks(); +#ifdef SDL_USE_LIBDBUS + // DBus event processing is independent of the video subsystem + SDL_DBus_PumpEvents(); +#endif + #ifdef SDL_PLATFORM_ANDROID // Android event processing is independent of the video subsystem Android_PumpEvents(0); @@ -1526,6 +1577,17 @@ static Sint64 SDL_events_get_polling_interval(void) } #endif +#ifdef SDL_PLATFORM_UNIX + if (SDL_HasActiveTrays()) { + // Tray events on *nix platforms run separately from window system events, and need periodic polling + poll_intervalNS = SDL_min(poll_intervalNS, TRAY_POLL_INTERVAL_NS); + } +#endif + +#ifdef SDL_USE_LIBDBUS + // Wake periodically to pump DBus events + poll_intervalNS = SDL_min(poll_intervalNS, DBUS_POLL_INTERVAL_NS); +#endif return poll_intervalNS; } @@ -1671,6 +1733,8 @@ bool SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS) #ifdef SDL_PLATFORM_ANDROID for (;;) { + SDL_PumpEventsInternal(true); + if (SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST) > 0) { return true; } diff --git a/libs/SDL3/src/events/SDL_events_c.h b/libs/SDL3/src/events/SDL_events_c.h index e56ac475..377b04ec 100644 --- a/libs/SDL3/src/events/SDL_events_c.h +++ b/libs/SDL3/src/events/SDL_events_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_eventwatch.c b/libs/SDL3/src/events/SDL_eventwatch.c index 08e7248c..5d0ff353 100644 --- a/libs/SDL3/src/events/SDL_eventwatch.c +++ b/libs/SDL3/src/events/SDL_eventwatch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_eventwatch_c.h b/libs/SDL3/src/events/SDL_eventwatch_c.h index c9aea38c..5c382a7c 100644 --- a/libs/SDL3/src/events/SDL_eventwatch_c.h +++ b/libs/SDL3/src/events/SDL_eventwatch_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_keyboard.c b/libs/SDL3/src/events/SDL_keyboard.c index fc06f072..f6969e37 100644 --- a/libs/SDL3/src/events/SDL_keyboard.c +++ b/libs/SDL3/src/events/SDL_keyboard.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -44,12 +44,6 @@ #define KEYCODE_OPTION_LATIN_LETTERS 0x04 #define DEFAULT_KEYCODE_OPTIONS (KEYCODE_OPTION_FRENCH_NUMBERS | KEYCODE_OPTION_LATIN_LETTERS) -typedef struct SDL_KeyboardInstance -{ - SDL_KeyboardID instance_id; - char *name; -} SDL_KeyboardInstance; - typedef struct SDL_Keyboard { // Data common to all keyboards @@ -61,12 +55,13 @@ typedef struct SDL_Keyboard Uint32 keycode_options; bool autorelease_pending; Uint64 hardware_timestamp; - int next_reserved_scancode; } SDL_Keyboard; static SDL_Keyboard SDL_keyboard; static int SDL_keyboard_count; -static SDL_KeyboardInstance *SDL_keyboards; +static SDL_KeyboardID *SDL_keyboards; +static SDL_HashTable *SDL_keyboard_names; +static bool SDL_keyboard_quitting; static void SDLCALL SDL_KeycodeOptionsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { @@ -95,6 +90,9 @@ bool SDL_InitKeyboard(void) { SDL_AddHintCallback(SDL_HINT_KEYCODE_OPTIONS, SDL_KeycodeOptionsChanged, &SDL_keyboard); + + SDL_keyboard_names = SDL_CreateHashTable(0, true, SDL_HashID, SDL_KeyMatchID, SDL_DestroyHashValue, NULL); + return true; } @@ -112,14 +110,14 @@ bool SDL_IsKeyboard(Uint16 vendor, Uint16 product, int num_keys) static int SDL_GetKeyboardIndex(SDL_KeyboardID keyboardID) { for (int i = 0; i < SDL_keyboard_count; ++i) { - if (keyboardID == SDL_keyboards[i].instance_id) { + if (keyboardID == SDL_keyboards[i]) { return i; } } return -1; } -void SDL_AddKeyboard(SDL_KeyboardID keyboardID, const char *name, bool send_event) +void SDL_AddKeyboard(SDL_KeyboardID keyboardID, const char *name) { int keyboard_index = SDL_GetKeyboardIndex(keyboardID); if (keyboard_index >= 0) { @@ -129,26 +127,27 @@ void SDL_AddKeyboard(SDL_KeyboardID keyboardID, const char *name, bool send_even SDL_assert(keyboardID != 0); - SDL_KeyboardInstance *keyboards = (SDL_KeyboardInstance *)SDL_realloc(SDL_keyboards, (SDL_keyboard_count + 1) * sizeof(*keyboards)); + SDL_KeyboardID *keyboards = (SDL_KeyboardID *)SDL_realloc(SDL_keyboards, (SDL_keyboard_count + 1) * sizeof(*keyboards)); if (!keyboards) { return; } - SDL_KeyboardInstance *instance = &keyboards[SDL_keyboard_count]; - instance->instance_id = keyboardID; - instance->name = SDL_strdup(name ? name : ""); + keyboards[SDL_keyboard_count] = keyboardID; SDL_keyboards = keyboards; ++SDL_keyboard_count; - if (send_event) { - SDL_Event event; - SDL_zero(event); - event.type = SDL_EVENT_KEYBOARD_ADDED; - event.kdevice.which = keyboardID; - SDL_PushEvent(&event); + if (!name) { + name = "Keyboard"; } + SDL_InsertIntoHashTable(SDL_keyboard_names, (const void *)(uintptr_t)keyboardID, SDL_strdup(name), true); + + SDL_Event event; + SDL_zero(event); + event.type = SDL_EVENT_KEYBOARD_ADDED; + event.kdevice.which = keyboardID; + SDL_PushEvent(&event); } -void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID, bool send_event) +void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID) { int keyboard_index = SDL_GetKeyboardIndex(keyboardID); if (keyboard_index < 0) { @@ -156,14 +155,12 @@ void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID, bool send_event) return; } - SDL_free(SDL_keyboards[keyboard_index].name); - if (keyboard_index != SDL_keyboard_count - 1) { SDL_memmove(&SDL_keyboards[keyboard_index], &SDL_keyboards[keyboard_index + 1], (SDL_keyboard_count - keyboard_index - 1) * sizeof(SDL_keyboards[keyboard_index])); } --SDL_keyboard_count; - if (send_event) { + if (!SDL_keyboard_quitting) { SDL_Event event; SDL_zero(event); event.type = SDL_EVENT_KEYBOARD_REMOVED; @@ -172,19 +169,6 @@ void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID, bool send_event) } } -void SDL_SetKeyboardName(SDL_KeyboardID keyboardID, const char *name) -{ - SDL_assert(keyboardID != 0); - - const int keyboard_index = SDL_GetKeyboardIndex(keyboardID); - - if (keyboard_index >= 0) { - SDL_KeyboardInstance *instance = &SDL_keyboards[keyboard_index]; - SDL_free(instance->name); - instance->name = SDL_strdup(name ? name : ""); - } -} - bool SDL_HasKeyboard(void) { return (SDL_keyboard_count > 0); @@ -195,14 +179,14 @@ SDL_KeyboardID *SDL_GetKeyboards(int *count) int i; SDL_KeyboardID *keyboards; - keyboards = (SDL_JoystickID *)SDL_malloc((SDL_keyboard_count + 1) * sizeof(*keyboards)); + keyboards = (SDL_KeyboardID *)SDL_malloc((SDL_keyboard_count + 1) * sizeof(*keyboards)); if (keyboards) { if (count) { *count = SDL_keyboard_count; } for (i = 0; i < SDL_keyboard_count; ++i) { - keyboards[i] = SDL_keyboards[i].instance_id; + keyboards[i] = SDL_keyboards[i]; } keyboards[i] = 0; } else { @@ -216,12 +200,25 @@ SDL_KeyboardID *SDL_GetKeyboards(int *count) const char *SDL_GetKeyboardNameForID(SDL_KeyboardID instance_id) { - int keyboard_index = SDL_GetKeyboardIndex(instance_id); - if (keyboard_index < 0) { - SDL_SetError("Keyboard %" SDL_PRIu32 " not found", instance_id); - return NULL; + const char *name = NULL; + + switch (instance_id) { + case SDL_GLOBAL_KEYBOARD_ID: + name = "Keyboard"; + break; + default: + if (!SDL_FindInHashTable(SDL_keyboard_names, (const void *)(uintptr_t)instance_id, (const void **)&name)) { + SDL_SetError("Keyboard %" SDL_PRIu32 " not found", instance_id); + return NULL; + } + if (!name) { + // SDL_strdup() failed during insert + SDL_OutOfMemory(); + return NULL; + } + break; } - return SDL_GetPersistentString(SDL_keyboards[keyboard_index].name); + return name; } void SDL_ResetKeyboard(void) @@ -280,7 +277,7 @@ void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event) !SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_SHIFT))) { keymap->french_numbers = false; break; - } + } } // Detect non-Latin keymap @@ -308,16 +305,12 @@ void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event) static SDL_Scancode GetNextReservedScancode(void) { SDL_Keyboard *keyboard = &SDL_keyboard; - SDL_Scancode scancode; - if (keyboard->next_reserved_scancode && keyboard->next_reserved_scancode < SDL_SCANCODE_RESERVED + 100) { - scancode = (SDL_Scancode)keyboard->next_reserved_scancode; - } else { - scancode = SDL_SCANCODE_RESERVED; + if (!keyboard->keymap) { + keyboard->keymap = SDL_CreateKeymap(true); } - keyboard->next_reserved_scancode = (int)scancode + 1; - return scancode; + return SDL_GetKeymapNextReservedScancode(keyboard->keymap); } static void SetKeymapEntry(SDL_Scancode scancode, SDL_Keymod modstate, SDL_Keycode keycode) @@ -340,7 +333,9 @@ SDL_Window *SDL_GetKeyboardFocus(void) bool SDL_SetKeyboardFocus(SDL_Window *window) { +#if !defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_ANDROID) SDL_VideoDevice *video = SDL_GetVideoDevice(); +#endif SDL_Keyboard *keyboard = &SDL_keyboard; SDL_Mouse *mouse = SDL_GetMouse(); @@ -353,7 +348,23 @@ bool SDL_SetKeyboardFocus(SDL_Window *window) if (keyboard->focus && !window) { // We won't get anymore keyboard messages, so reset keyboard state SDL_ResetKeyboard(); + } + // See if the current window has lost focus + if (keyboard->focus && keyboard->focus != window) { + SDL_SendWindowEvent(keyboard->focus, SDL_EVENT_WINDOW_FOCUS_LOST, 0, 0); + +#if !defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_ANDROID) + // Ensures IME compositions are committed + if (SDL_TextInputActive(keyboard->focus)) { + if (video && video->StopTextInput) { + video->StopTextInput(video, keyboard->focus); + } + } +#endif // !SDL_PLATFORM_IOS && !SDL_PLATFORM_ANDROID + } + + if (keyboard->focus && !window) { // Also leave mouse relative mode if (mouse->relative_mode) { SDL_SetRelativeMouseMode(false); @@ -368,28 +379,18 @@ bool SDL_SetKeyboardFocus(SDL_Window *window) } } - // See if the current window has lost focus - if (keyboard->focus && keyboard->focus != window) { - SDL_SendWindowEvent(keyboard->focus, SDL_EVENT_WINDOW_FOCUS_LOST, 0, 0); - - // Ensures IME compositions are committed - if (SDL_TextInputActive(keyboard->focus)) { - if (video && video->StopTextInput) { - video->StopTextInput(video, keyboard->focus); - } - } - } - keyboard->focus = window; if (keyboard->focus) { SDL_SendWindowEvent(keyboard->focus, SDL_EVENT_WINDOW_FOCUS_GAINED, 0, 0); +#if !defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_ANDROID) if (SDL_TextInputActive(keyboard->focus)) { if (video && video->StartTextInput) { video->StartTextInput(video, keyboard->focus, keyboard->focus->text_input_props); } } +#endif // !SDL_PLATFORM_IOS && !SDL_PLATFORM_ANDROID } SDL_UpdateRelativeMouseMode(); @@ -887,12 +888,17 @@ void SDL_SendEditingTextCandidates(char **candidates, int num_candidates, int se void SDL_QuitKeyboard(void) { + SDL_keyboard_quitting = true; + for (int i = SDL_keyboard_count; i--;) { - SDL_RemoveKeyboard(SDL_keyboards[i].instance_id, false); + SDL_RemoveKeyboard(SDL_keyboards[i]); } SDL_free(SDL_keyboards); SDL_keyboards = NULL; + SDL_DestroyHashTable(SDL_keyboard_names); + SDL_keyboard_names = NULL; + if (SDL_keyboard.keymap && SDL_keyboard.keymap->auto_release) { SDL_DestroyKeymap(SDL_keyboard.keymap); SDL_keyboard.keymap = NULL; @@ -900,13 +906,15 @@ void SDL_QuitKeyboard(void) SDL_RemoveHintCallback(SDL_HINT_KEYCODE_OPTIONS, SDL_KeycodeOptionsChanged, &SDL_keyboard); + + SDL_keyboard_quitting = false; } const bool *SDL_GetKeyboardState(int *numkeys) { SDL_Keyboard *keyboard = &SDL_keyboard; - if (numkeys != (int *)0) { + if (numkeys) { *numkeys = SDL_SCANCODE_COUNT; } return keyboard->keystate; diff --git a/libs/SDL3/src/events/SDL_keyboard_c.h b/libs/SDL3/src/events/SDL_keyboard_c.h index 9a6589dd..a6bcfa0a 100644 --- a/libs/SDL3/src/events/SDL_keyboard_c.h +++ b/libs/SDL3/src/events/SDL_keyboard_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -38,13 +38,10 @@ extern bool SDL_InitKeyboard(void); extern bool SDL_IsKeyboard(Uint16 vendor, Uint16 product, int num_keys); // A keyboard has been added to the system -extern void SDL_AddKeyboard(SDL_KeyboardID keyboardID, const char *name, bool send_event); +extern void SDL_AddKeyboard(SDL_KeyboardID keyboardID, const char *name); // A keyboard has been removed from the system -extern void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID, bool send_event); - -// Set or update the name of a keyboard instance. -extern void SDL_SetKeyboardName(SDL_KeyboardID keyboardID, const char *name); +extern void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID); // Set the mapping of scancode to key codes extern void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event); diff --git a/libs/SDL3/src/events/SDL_keymap.c b/libs/SDL3/src/events/SDL_keymap.c index 318fc686..11f4df54 100644 --- a/libs/SDL3/src/events/SDL_keymap.c +++ b/libs/SDL3/src/events/SDL_keymap.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -183,12 +183,34 @@ SDL_Scancode SDL_GetKeymapScancode(SDL_Keymap *keymap, SDL_Keycode keycode, SDL_ return scancode; } +SDL_Scancode SDL_GetKeymapNextReservedScancode(SDL_Keymap *keymap) +{ + SDL_Scancode scancode; + + if (!keymap) { + return SDL_SCANCODE_UNKNOWN; + } + + if (keymap->next_reserved_scancode && keymap->next_reserved_scancode < SDL_SCANCODE_RESERVED + 100) { + scancode = keymap->next_reserved_scancode; + } else { + scancode = SDL_SCANCODE_RESERVED; + } + keymap->next_reserved_scancode = scancode + 1; + + return scancode; +} + void SDL_DestroyKeymap(SDL_Keymap *keymap) { if (!keymap) { return; } + if (!keymap->auto_release && keymap == SDL_GetCurrentKeymap(true)) { + SDL_SetKeymap(NULL, false); + } + SDL_DestroyHashTable(keymap->scancode_to_keycode); SDL_DestroyHashTable(keymap->keycode_to_scancode); SDL_free(keymap); @@ -268,7 +290,7 @@ static const struct static SDL_Keycode SDL_GetDefaultKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate) { - if (((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) { + CHECK_PARAM(((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) { SDL_InvalidParamError("scancode"); return SDLK_UNKNOWN; } @@ -1031,7 +1053,7 @@ static const char *SDL_extended_key_names[] = { bool SDL_SetScancodeName(SDL_Scancode scancode, const char *name) { - if (((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) { + CHECK_PARAM(((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) { return SDL_InvalidParamError("scancode"); } @@ -1042,7 +1064,8 @@ bool SDL_SetScancodeName(SDL_Scancode scancode, const char *name) const char *SDL_GetScancodeName(SDL_Scancode scancode) { const char *name; - if (((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) { + + CHECK_PARAM(((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) { SDL_InvalidParamError("scancode"); return ""; } @@ -1059,7 +1082,7 @@ SDL_Scancode SDL_GetScancodeFromName(const char *name) { int i; - if (!name || !*name) { + CHECK_PARAM(!name || !*name) { SDL_InvalidParamError("name"); return SDL_SCANCODE_UNKNOWN; } diff --git a/libs/SDL3/src/events/SDL_keymap_c.h b/libs/SDL3/src/events/SDL_keymap_c.h index 311e397e..0ed7e075 100644 --- a/libs/SDL3/src/events/SDL_keymap_c.h +++ b/libs/SDL3/src/events/SDL_keymap_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -25,13 +25,14 @@ typedef struct SDL_Keymap { - SDL_HashTable *scancode_to_keycode; - SDL_HashTable *keycode_to_scancode; - bool auto_release; - bool layout_determined; - bool french_numbers; - bool latin_letters; - bool thai_keyboard; + SDL_HashTable *scancode_to_keycode; + SDL_HashTable *keycode_to_scancode; + SDL_Scancode next_reserved_scancode; + bool auto_release; + bool layout_determined; + bool french_numbers; + bool latin_letters; + bool thai_keyboard; } SDL_Keymap; /* This may return null even when a keymap is bound, depending on the current keyboard mapping options. @@ -42,6 +43,7 @@ SDL_Keymap *SDL_CreateKeymap(bool auto_release); void SDL_SetKeymapEntry(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod modstate, SDL_Keycode keycode); SDL_Keycode SDL_GetKeymapKeycode(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod modstate); SDL_Scancode SDL_GetKeymapScancode(SDL_Keymap *keymap, SDL_Keycode keycode, SDL_Keymod *modstate); +SDL_Scancode SDL_GetKeymapNextReservedScancode(SDL_Keymap *keymap); void SDL_DestroyKeymap(SDL_Keymap *keymap); #endif // SDL_keymap_c_h_ diff --git a/libs/SDL3/src/events/SDL_keysym_to_keycode.c b/libs/SDL3/src/events/SDL_keysym_to_keycode.c index 7cbfcb62..5f6bbd37 100644 --- a/libs/SDL3/src/events/SDL_keysym_to_keycode.c +++ b/libs/SDL3/src/events/SDL_keysym_to_keycode.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_keysym_to_keycode_c.h b/libs/SDL3/src/events/SDL_keysym_to_keycode_c.h index 2321d202..107658fc 100644 --- a/libs/SDL3/src/events/SDL_keysym_to_keycode_c.h +++ b/libs/SDL3/src/events/SDL_keysym_to_keycode_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -25,4 +25,4 @@ // Convert a keysym to an SDL key code extern SDL_Keycode SDL_GetKeyCodeFromKeySym(Uint32 keysym, Uint32 keycode, SDL_Keymod modifiers); -#endif // SDL_keysym_to_scancode_c_h_ +#endif // SDL_keysym_to_keycode_c_h_ diff --git a/libs/SDL3/src/events/SDL_keysym_to_scancode.c b/libs/SDL3/src/events/SDL_keysym_to_scancode.c index 8d43ca35..f54de44c 100644 --- a/libs/SDL3/src/events/SDL_keysym_to_scancode.c +++ b/libs/SDL3/src/events/SDL_keysym_to_scancode.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_keysym_to_scancode_c.h b/libs/SDL3/src/events/SDL_keysym_to_scancode_c.h index 8d0e2143..b8c6db7f 100644 --- a/libs/SDL3/src/events/SDL_keysym_to_scancode_c.h +++ b/libs/SDL3/src/events/SDL_keysym_to_scancode_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_mouse.c b/libs/SDL3/src/events/SDL_mouse.c index 5838d47c..eaf1f2cd 100644 --- a/libs/SDL3/src/events/SDL_mouse.c +++ b/libs/SDL3/src/events/SDL_mouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -34,16 +34,12 @@ #define WARP_EMULATION_THRESHOLD_NS SDL_MS_TO_NS(30) -typedef struct SDL_MouseInstance -{ - SDL_MouseID instance_id; - char *name; -} SDL_MouseInstance; - // The mouse state static SDL_Mouse SDL_mouse; static int SDL_mouse_count; -static SDL_MouseInstance *SDL_mice; +static SDL_MouseID *SDL_mice; +static SDL_HashTable *SDL_mouse_names; +static bool SDL_mouse_initialized; // for mapping mouse events to touch static bool track_mouse_down = false; @@ -192,25 +188,6 @@ static void SDLCALL SDL_PenMouseEventsChanged(void *userdata, const char *name, mouse->pen_mouse_events = SDL_GetStringBoolean(hint, true); } -static void SDLCALL SDL_PenTouchEventsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) -{ - SDL_Mouse *mouse = (SDL_Mouse *)userdata; - - mouse->pen_touch_events = SDL_GetStringBoolean(hint, true); - - if (mouse->pen_touch_events) { - if (!mouse->added_pen_touch_device) { - SDL_AddTouch(SDL_PEN_TOUCHID, SDL_TOUCH_DEVICE_DIRECT, "pen_input"); - mouse->added_pen_touch_device = true; - } - } else { - if (mouse->added_pen_touch_device) { - SDL_DelTouch(SDL_PEN_TOUCHID); - mouse->added_pen_touch_device = false; - } - } -} - static void SDLCALL SDL_MouseAutoCaptureChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { SDL_Mouse *mouse = (SDL_Mouse *)userdata; @@ -254,6 +231,8 @@ bool SDL_PreInitMouse(void) { SDL_Mouse *mouse = SDL_GetMouse(); + SDL_mouse_initialized = true; + SDL_zerop(mouse); SDL_AddHintCallback(SDL_HINT_MOUSE_DOUBLE_CLICK_TIME, @@ -291,9 +270,6 @@ bool SDL_PreInitMouse(void) SDL_AddHintCallback(SDL_HINT_PEN_MOUSE_EVENTS, SDL_PenMouseEventsChanged, mouse); - SDL_AddHintCallback(SDL_HINT_PEN_TOUCH_EVENTS, - SDL_PenTouchEventsChanged, mouse); - SDL_AddHintCallback(SDL_HINT_MOUSE_AUTO_CAPTURE, SDL_MouseAutoCaptureChanged, mouse); @@ -310,6 +286,8 @@ bool SDL_PreInitMouse(void) mouse->cursor_visible = true; + SDL_mouse_names = SDL_CreateHashTable(0, true, SDL_HashID, SDL_KeyMatchID, SDL_DestroyHashValue, NULL); + return true; } @@ -339,14 +317,14 @@ bool SDL_IsMouse(Uint16 vendor, Uint16 product) static int SDL_GetMouseIndex(SDL_MouseID mouseID) { for (int i = 0; i < SDL_mouse_count; ++i) { - if (mouseID == SDL_mice[i].instance_id) { + if (mouseID == SDL_mice[i]) { return i; } } return -1; } -void SDL_AddMouse(SDL_MouseID mouseID, const char *name, bool send_event) +void SDL_AddMouse(SDL_MouseID mouseID, const char *name) { int mouse_index = SDL_GetMouseIndex(mouseID); if (mouse_index >= 0) { @@ -356,26 +334,27 @@ void SDL_AddMouse(SDL_MouseID mouseID, const char *name, bool send_event) SDL_assert(mouseID != 0); - SDL_MouseInstance *mice = (SDL_MouseInstance *)SDL_realloc(SDL_mice, (SDL_mouse_count + 1) * sizeof(*mice)); + SDL_MouseID *mice = (SDL_MouseID *)SDL_realloc(SDL_mice, (SDL_mouse_count + 1) * sizeof(*mice)); if (!mice) { return; } - SDL_MouseInstance *instance = &mice[SDL_mouse_count]; - instance->instance_id = mouseID; - instance->name = SDL_strdup(name ? name : ""); + mice[SDL_mouse_count] = mouseID; SDL_mice = mice; ++SDL_mouse_count; - if (send_event) { - SDL_Event event; - SDL_zero(event); - event.type = SDL_EVENT_MOUSE_ADDED; - event.mdevice.which = mouseID; - SDL_PushEvent(&event); + if (!name) { + name = "Mouse"; } + SDL_InsertIntoHashTable(SDL_mouse_names, (const void *)(uintptr_t)mouseID, SDL_strdup(name), true); + + SDL_Event event; + SDL_zero(event); + event.type = SDL_EVENT_MOUSE_ADDED; + event.mdevice.which = mouseID; + SDL_PushEvent(&event); } -void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event) +void SDL_RemoveMouse(SDL_MouseID mouseID) { int mouse_index = SDL_GetMouseIndex(mouseID); if (mouse_index < 0) { @@ -383,8 +362,6 @@ void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event) return; } - SDL_free(SDL_mice[mouse_index].name); - if (mouse_index != SDL_mouse_count - 1) { SDL_memmove(&SDL_mice[mouse_index], &SDL_mice[mouse_index + 1], (SDL_mouse_count - mouse_index - 1) * sizeof(SDL_mice[mouse_index])); } @@ -404,7 +381,7 @@ void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event) } } - if (send_event) { + if (SDL_mouse_initialized) { SDL_Event event; SDL_zero(event); event.type = SDL_EVENT_MOUSE_REMOVED; @@ -413,19 +390,6 @@ void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event) } } -void SDL_SetMouseName(SDL_MouseID mouseID, const char *name) -{ - SDL_assert(mouseID != 0); - - const int mouse_index = SDL_GetMouseIndex(mouseID); - - if (mouse_index >= 0) { - SDL_MouseInstance *instance = &SDL_mice[mouse_index]; - SDL_free(instance->name); - instance->name = SDL_strdup(name ? name : ""); - } -} - bool SDL_HasMouse(void) { return (SDL_mouse_count > 0); @@ -436,14 +400,14 @@ SDL_MouseID *SDL_GetMice(int *count) int i; SDL_MouseID *mice; - mice = (SDL_JoystickID *)SDL_malloc((SDL_mouse_count + 1) * sizeof(*mice)); + mice = (SDL_MouseID *)SDL_malloc((SDL_mouse_count + 1) * sizeof(*mice)); if (mice) { if (count) { *count = SDL_mouse_count; } for (i = 0; i < SDL_mouse_count; ++i) { - mice[i] = SDL_mice[i].instance_id; + mice[i] = SDL_mice[i]; } mice[i] = 0; } else { @@ -457,12 +421,41 @@ SDL_MouseID *SDL_GetMice(int *count) const char *SDL_GetMouseNameForID(SDL_MouseID instance_id) { - int mouse_index = SDL_GetMouseIndex(instance_id); - if (mouse_index < 0) { - SDL_SetError("Mouse %" SDL_PRIu32 " not found", instance_id); - return NULL; + const char *name = NULL; + + switch (instance_id) { + case SDL_GLOBAL_MOUSE_ID: + name = "Mouse"; + break; + case SDL_TOUCH_MOUSEID: + // We can't tell which touch device it was, just use the first one + { + SDL_TouchID *devices = SDL_GetTouchDevices(NULL); + if (devices) { + name = SDL_GetTouchDeviceName(devices[0]); + SDL_free(devices); + } + } + if (!name) { + name = "Touch"; + } + break; + case SDL_PEN_MOUSEID: + name = "Pen"; + break; + default: + if (!SDL_FindInHashTable(SDL_mouse_names, (const void *)(uintptr_t)instance_id, (const void **)&name)) { + SDL_SetError("Mouse %" SDL_PRIu32 " not found", instance_id); + return NULL; + } + if (!name) { + // SDL_strdup() failed during insert + SDL_OutOfMemory(); + return NULL; + } + break; } - return SDL_GetPersistentString(SDL_mice[mouse_index].name); + return name; } void SDL_SetDefaultCursor(SDL_Cursor *cursor) @@ -857,38 +850,40 @@ static SDL_MouseInputSource *GetMouseInputSource(SDL_Mouse *mouse, SDL_MouseID m SDL_MouseInputSource *source, *match = NULL, *sources; int i; - for (i = 0; i < mouse->num_sources; ++i) { - source = &mouse->sources[i]; - if (source->mouseID == mouseID) { - match = source; - break; - } - } - - if (!down && (!match || !(match->buttonstate & SDL_BUTTON_MASK(button)))) { - /* This might be a button release from a transition between mouse messages and raw input. - * See if there's another mouse source that already has that button down and use that. - */ + if (SDL_mouse_initialized) { for (i = 0; i < mouse->num_sources; ++i) { source = &mouse->sources[i]; - if ((source->buttonstate & SDL_BUTTON_MASK(button))) { + if (source->mouseID == mouseID) { match = source; break; } } - } - if (match) { - return match; - } - sources = (SDL_MouseInputSource *)SDL_realloc(mouse->sources, (mouse->num_sources + 1) * sizeof(*mouse->sources)); - if (sources) { - mouse->sources = sources; - ++mouse->num_sources; - source = &sources[mouse->num_sources - 1]; - SDL_zerop(source); - source->mouseID = mouseID; - return source; + if (!down && (!match || !(match->buttonstate & SDL_BUTTON_MASK(button)))) { + /* This might be a button release from a transition between mouse messages and raw input. + * See if there's another mouse source that already has that button down and use that. + */ + for (i = 0; i < mouse->num_sources; ++i) { + source = &mouse->sources[i]; + if ((source->buttonstate & SDL_BUTTON_MASK(button))) { + match = source; + break; + } + } + } + if (match) { + return match; + } + + sources = (SDL_MouseInputSource *)SDL_realloc(mouse->sources, (mouse->num_sources + 1) * sizeof(*mouse->sources)); + if (sources) { + mouse->sources = sources; + ++mouse->num_sources; + source = &sources[mouse->num_sources - 1]; + SDL_zerop(source); + source->mouseID = mouseID; + return source; + } } return NULL; } @@ -1091,6 +1086,8 @@ void SDL_QuitMouse(void) SDL_Cursor *cursor, *next; SDL_Mouse *mouse = SDL_GetMouse(); + SDL_mouse_initialized = false; + if (mouse->added_mouse_touch_device) { SDL_DelTouch(SDL_MOUSE_TOUCHID); mouse->added_mouse_touch_device = false; @@ -1161,9 +1158,6 @@ void SDL_QuitMouse(void) SDL_RemoveHintCallback(SDL_HINT_PEN_MOUSE_EVENTS, SDL_PenMouseEventsChanged, mouse); - SDL_RemoveHintCallback(SDL_HINT_PEN_TOUCH_EVENTS, - SDL_PenTouchEventsChanged, mouse); - SDL_RemoveHintCallback(SDL_HINT_MOUSE_AUTO_CAPTURE, SDL_MouseAutoCaptureChanged, mouse); @@ -1177,10 +1171,19 @@ void SDL_QuitMouse(void) SDL_MouseIntegerModeChanged, mouse); for (int i = SDL_mouse_count; i--; ) { - SDL_RemoveMouse(SDL_mice[i].instance_id, false); + SDL_RemoveMouse(SDL_mice[i]); } SDL_free(SDL_mice); SDL_mice = NULL; + + SDL_DestroyHashTable(SDL_mouse_names); + SDL_mouse_names = NULL; + + if (mouse->internal) { + SDL_free(mouse->internal); + mouse->internal = NULL; + } + SDL_zerop(mouse); } bool SDL_SetRelativeMouseTransform(SDL_MouseMotionTransformCallback transform, void *userdata) @@ -1367,10 +1370,11 @@ bool SDL_SetRelativeMouseMode(bool enabled) } // Set the relative mode - if (!mouse->SetRelativeMouseMode || !mouse->SetRelativeMouseMode(enabled)) { - if (enabled) { - return SDL_SetError("No relative mode implementation available"); - } + if (!mouse->SetRelativeMouseMode) { + return SDL_Unsupported(); + } + if (!mouse->SetRelativeMouseMode(enabled)) { + return false; } mouse->relative_mode = enabled; @@ -1412,15 +1416,16 @@ bool SDL_GetRelativeMouseMode(void) return mouse->relative_mode; } -void SDL_UpdateRelativeMouseMode(void) +bool SDL_UpdateRelativeMouseMode(void) { SDL_Mouse *mouse = SDL_GetMouse(); SDL_Window *focus = SDL_GetKeyboardFocus(); bool relative_mode = (focus && (focus->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE)); - if (relative_mode != mouse->relative_mode) { - SDL_SetRelativeMouseMode(relative_mode); + if (relative_mode == mouse->relative_mode) { + return true; } + return SDL_SetRelativeMouseMode(relative_mode); } bool SDL_UpdateMouseCapture(bool force_release) @@ -1550,13 +1555,190 @@ SDL_Cursor *SDL_CreateCursor(const Uint8 *data, const Uint8 *mask, int w, int h, return cursor; } +static void SDL_DestroyCursorAnimation(SDL_CursorAnimation *animation) +{ + if (!animation) { + return; + } + + for (int i = 0; i < animation->num_frames; ++i) { + SDL_DestroyCursor(animation->frames[i]); + } + SDL_free(animation->frames); + SDL_free(animation->durations); + SDL_free(animation); +} + +static SDL_CursorAnimation *SDL_CreateCursorAnimation(SDL_CursorFrameInfo *frames, int frame_count, int hot_x, int hot_y) +{ + SDL_CursorAnimation *animation = (SDL_CursorAnimation *)SDL_calloc(1, sizeof(*animation)); + if (!animation) { + return NULL; + } + + animation->frames = (SDL_Cursor **)SDL_calloc(frame_count, sizeof(*animation->frames)); + animation->durations = (Uint32 *)SDL_calloc(frame_count, sizeof(*animation->durations)); + if (!animation->frames || !animation->durations) { + SDL_DestroyCursorAnimation(animation); + return NULL; + } + + for (int i = 0; i < frame_count; ++i) { + animation->frames[i] = SDL_CreateColorCursor(frames[i].surface, hot_x, hot_y); + if (!animation->frames[i]) { + SDL_DestroyCursorAnimation(animation); + return NULL; + } + + animation->durations[i] = frames[i].duration; + } + animation->num_frames = frame_count; + + return animation; +} + +void SDL_UpdateCursorAnimation(void) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + SDL_Cursor *cursor = mouse->cur_cursor; + + if (!cursor || !cursor->animation) { + return; + } + + if (!mouse->focus) { + return; + } + + SDL_CursorAnimation *animation = cursor->animation; + Uint32 duration = animation->durations[animation->current_frame]; + if (!duration) { + // We've reached the stop frame of the animation + return; + } + + Uint64 now = SDL_GetTicks(); + if (now < (animation->last_update + duration)) { + return; + } + + animation->current_frame = (animation->current_frame + 1) % animation->num_frames; + animation->last_update = now; + + SDL_RedrawCursor(); +} + +SDL_Cursor *SDL_CreateAnimatedCursor(SDL_CursorFrameInfo *frames, int frame_count, int hot_x, int hot_y) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + SDL_Cursor *cursor = NULL; + + CHECK_PARAM(!frames) { + SDL_InvalidParamError("frames"); + return NULL; + } + + CHECK_PARAM(!frames[0].surface) { + SDL_SetError("NULL surface in frame 0"); + return NULL; + } + + CHECK_PARAM(frame_count <= 0) { + SDL_InvalidParamError("frame_count"); + return NULL; + } + + if (frame_count == 1) { + return SDL_CreateColorCursor(frames[0].surface, hot_x, hot_y); + } + + // Allow specifying the hot spot via properties on the surface + SDL_PropertiesID props = SDL_GetSurfaceProperties(frames[0].surface); + hot_x = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, hot_x); + hot_y = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, hot_y); + + // Sanity check the hot spot + CHECK_PARAM((hot_x < 0) || (hot_y < 0) || + (hot_x >= frames[0].surface->w) || (hot_y >= frames[0].surface->h)) { + SDL_SetError("Cursor hot spot doesn't lie within cursor"); + return NULL; + } + + bool isstack; + SDL_CursorFrameInfo *temp_frames = SDL_small_alloc(SDL_CursorFrameInfo, frame_count, &isstack); + if (!temp_frames) { + return NULL; + } + SDL_memset(temp_frames, 0, sizeof(SDL_CursorFrameInfo) * frame_count); + + const int w = frames[0].surface->w; + const int h = frames[0].surface->h; + + for (int i = 0; i < frame_count; ++i) { + CHECK_PARAM(!frames[i].surface) { + SDL_SetError("Null surface in frame %i", i); + goto cleanup; + } + + // All cursor images should be the same size. + CHECK_PARAM(frames[i].surface->w != w || frames[i].surface->h != h) { + SDL_SetError("All frames in an animated sequence must have the same dimensions"); + goto cleanup; + } + + if (frames[i].surface->format == SDL_PIXELFORMAT_ARGB8888) { + temp_frames[i].surface = frames[i].surface; + } else { + SDL_Surface *temp = SDL_ConvertSurface(frames[i].surface, SDL_PIXELFORMAT_ARGB8888); + if (!temp) { + goto cleanup; + } + temp_frames[i].surface = temp; + } + temp_frames[i].duration = frames[i].duration; + } + + if (mouse->CreateAnimatedCursor) { + cursor = mouse->CreateAnimatedCursor(temp_frames, frame_count, hot_x, hot_y); + } else { + SDL_CursorAnimation *animation = SDL_CreateCursorAnimation(temp_frames, frame_count, hot_x, hot_y); + if (!animation) { + goto cleanup; + } + + cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor)); + if (!cursor) { + SDL_DestroyCursorAnimation(animation); + goto cleanup; + } + cursor->animation = animation; + } + + if (cursor) { + cursor->next = mouse->cursors; + mouse->cursors = cursor; + } + +cleanup: + // Clean up any temporary converted surfaces. + for (int i = 0; i < frame_count; ++i) { + if (temp_frames[i].surface && frames[i].surface != temp_frames[i].surface) { + SDL_DestroySurface(temp_frames[i].surface); + } + } + + SDL_small_free(temp_frames, isstack); + + return cursor; +} + SDL_Cursor *SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y) { SDL_Mouse *mouse = SDL_GetMouse(); SDL_Surface *temp = NULL; SDL_Cursor *cursor; - if (!surface) { + CHECK_PARAM(!surface) { SDL_InvalidParamError("surface"); return NULL; } @@ -1567,8 +1749,8 @@ SDL_Cursor *SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y) hot_y = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, hot_y); // Sanity check the hot spot - if ((hot_x < 0) || (hot_y < 0) || - (hot_x >= surface->w) || (hot_y >= surface->h)) { + CHECK_PARAM((hot_x < 0) || (hot_y < 0) || + (hot_x >= surface->w) || (hot_y >= surface->h)) { SDL_SetError("Cursor hot spot doesn't lie within cursor"); return NULL; } @@ -1630,6 +1812,11 @@ void SDL_RedrawCursor(void) cursor = NULL; } + if (cursor && cursor->animation) { + SDL_CursorAnimation *animation = cursor->animation; + cursor = animation->frames[animation->current_frame]; + } + if (mouse->ShowCursor) { mouse->ShowCursor(cursor); } @@ -1662,6 +1849,11 @@ bool SDL_SetCursor(SDL_Cursor *cursor) return SDL_SetError("Cursor not associated with the current mouse"); } } + if (cursor->animation) { + SDL_CursorAnimation *animation = cursor->animation; + animation->current_frame = 0; + animation->last_update = SDL_GetTicks(); + } mouse->cur_cursor = cursor; } @@ -1715,6 +1907,10 @@ void SDL_DestroyCursor(SDL_Cursor *cursor) mouse->cursors = curr->next; } + if (curr->animation) { + SDL_DestroyCursorAnimation(curr->animation); + } + if (mouse->FreeCursor && curr->internal) { mouse->FreeCursor(curr); } else { diff --git a/libs/SDL3/src/events/SDL_mouse_c.h b/libs/SDL3/src/events/SDL_mouse_c.h index 0bc913b9..36c458df 100644 --- a/libs/SDL3/src/events/SDL_mouse_c.h +++ b/libs/SDL3/src/events/SDL_mouse_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -31,10 +31,24 @@ typedef struct SDL_CursorData SDL_CursorData; +struct SDL_Cursor; + +typedef struct +{ + Uint64 last_update; + int num_frames; + int current_frame; + struct SDL_Cursor **frames; + Uint32 *durations; +} SDL_CursorAnimation; + struct SDL_Cursor { - struct SDL_Cursor *next; + SDL_CursorAnimation *animation; + SDL_CursorData *internal; + + struct SDL_Cursor *next; }; typedef struct @@ -60,6 +74,9 @@ typedef struct // Create a cursor from a surface SDL_Cursor *(*CreateCursor)(SDL_Surface *surface, int hot_x, int hot_y); + // Create an animated cursor from a sequence of surfaces + SDL_Cursor *(*CreateAnimatedCursor)(SDL_CursorFrameInfo *frames, int frame_count, int hot_x, int hot_y); + // Create a system cursor SDL_Cursor *(*CreateSystemCursor)(SDL_SystemCursor id); @@ -164,13 +181,10 @@ extern void SDL_PostInitMouse(void); extern bool SDL_IsMouse(Uint16 vendor, Uint16 product); // A mouse has been added to the system -extern void SDL_AddMouse(SDL_MouseID mouseID, const char *name, bool send_event); +extern void SDL_AddMouse(SDL_MouseID mouseID, const char *name); // A mouse has been removed from the system -extern void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event); - -// Set or update the name of a mouse instance. -extern void SDL_SetMouseName(SDL_MouseID mouseID, const char *name); +extern void SDL_RemoveMouse(SDL_MouseID mouseID); // Get the mouse state structure extern SDL_Mouse *SDL_GetMouse(void); @@ -184,6 +198,9 @@ extern void SDL_SetDefaultCursor(SDL_Cursor *cursor); // Get the preferred default system cursor extern SDL_SystemCursor SDL_GetDefaultSystemCursor(void); +// Update the current cursor animation if needed +extern void SDL_UpdateCursorAnimation(void); + // Set the mouse focus window extern void SDL_SetMouseFocus(SDL_Window *window); @@ -208,7 +225,7 @@ extern void SDL_PerformWarpMouseInWindow(SDL_Window *window, float x, float y, b // Relative mouse mode extern bool SDL_SetRelativeMouseMode(bool enabled); extern bool SDL_GetRelativeMouseMode(void); -extern void SDL_UpdateRelativeMouseMode(void); +extern bool SDL_UpdateRelativeMouseMode(void); extern void SDL_DisableMouseWarpEmulation(void); // TODO RECONNECT: Set mouse state to "zero" diff --git a/libs/SDL3/src/events/SDL_pen.c b/libs/SDL3/src/events/SDL_pen.c index cd3730ca..d70542b9 100644 --- a/libs/SDL3/src/events/SDL_pen.c +++ b/libs/SDL3/src/events/SDL_pen.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -37,6 +37,8 @@ typedef struct SDL_Pen float x; float y; SDL_PenInputFlags input_state; + bool pending_proximity_out; + SDL_WindowID pending_proximity_window_id; void *driverdata; } SDL_Pen; @@ -45,6 +47,7 @@ typedef struct SDL_Pen static SDL_RWLock *pen_device_rwlock = NULL; static SDL_Pen *pen_devices SDL_GUARDED_BY(pen_device_rwlock) = NULL; static int pen_device_count SDL_GUARDED_BY(pen_device_rwlock) = 0; +static SDL_AtomicInt pending_proximity_out; // You must hold pen_device_rwlock before calling this, and result is only safe while lock is held! // If SDL isn't initialized, grabbing the NULL lock is a no-op and there will be zero devices, so @@ -90,6 +93,33 @@ SDL_PenID SDL_FindPenByCallback(bool (*callback)(void *handle, void *userdata), return result; } +static void UpdateTouchEmulationDevicePresence(void) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + + SDL_LockRWLockForReading(pen_device_rwlock); + bool has_pen = (pen_device_count != 0); + SDL_UnlockRWLock(pen_device_rwlock); + + if (!mouse->pen_touch_events || !has_pen) { + if (mouse->added_pen_touch_device) { + SDL_DelTouch(SDL_PEN_TOUCHID); + mouse->added_pen_touch_device = false; + } + } else if (!mouse->added_pen_touch_device) { + SDL_AddTouch(SDL_PEN_TOUCHID, SDL_TOUCH_DEVICE_DIRECT, "pen_input"); + mouse->added_pen_touch_device = true; + } +} + +static void SDLCALL SDL_PenTouchEventsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + + mouse->pen_touch_events = SDL_GetStringBoolean(hint, true); + + UpdateTouchEmulationDevicePresence(); +} // public API ... @@ -103,22 +133,21 @@ bool SDL_InitPen(void) if (!pen_device_rwlock) { return false; } + + SDL_AddHintCallback(SDL_HINT_PEN_TOUCH_EVENTS, + SDL_PenTouchEventsChanged, NULL); + return true; } void SDL_QuitPen(void) { + SDL_RemoveHintCallback(SDL_HINT_PEN_TOUCH_EVENTS, + SDL_PenTouchEventsChanged, NULL); + + SDL_RemoveAllPenDevices(NULL, NULL); SDL_DestroyRWLock(pen_device_rwlock); pen_device_rwlock = NULL; - if (pen_devices) { - for (int i = pen_device_count; i--; ) { - SDL_free(pen_devices[i].name); - } - SDL_free(pen_devices); - pen_devices = NULL; - } - pen_device_count = 0; - pen_touching = 0; } #if 0 // not a public API at the moment. @@ -199,6 +228,15 @@ SDL_PenInputFlags SDL_GetPenStatus(SDL_PenID instance_id, float *axes, int num_a return result; } +SDL_PenDeviceType SDL_GetPenDeviceType(SDL_PenID instance_id) +{ + SDL_LockRWLockForReading(pen_device_rwlock); + const SDL_Pen *pen = FindPenByInstanceId(instance_id); + const SDL_PenDeviceType result = pen ? pen->info.device_type : SDL_PEN_DEVICE_TYPE_INVALID; + SDL_UnlockRWLock(pen_device_rwlock); + return result; +} + SDL_PenCapabilityFlags SDL_GetPenCapabilityFromAxis(SDL_PenAxis axis) { // the initial capability bits happen to match up, but as @@ -209,7 +247,7 @@ SDL_PenCapabilityFlags SDL_GetPenCapabilityFromAxis(SDL_PenAxis axis) return 0; // oh well. } -SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, const SDL_PenInfo *info, void *handle) +SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, SDL_Window *window, const SDL_PenInfo *info, void *handle, bool in_proximity) { SDL_assert(handle != NULL); // just allocate a Uint8 so you have a unique pointer if not needed! SDL_assert(SDL_FindPenByHandle(handle) == 0); // Backends shouldn't double-add pens! @@ -247,24 +285,23 @@ SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, const SDL_PenInfo SDL_free(namecpy); } - if (result && SDL_EventEnabled(SDL_EVENT_PEN_PROXIMITY_IN)) { - SDL_Event event; - SDL_zero(event); - event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_IN; - event.pproximity.timestamp = timestamp; - event.pproximity.which = result; - SDL_PushEvent(&event); + UpdateTouchEmulationDevicePresence(); + + if (result && in_proximity) { + SDL_SendPenProximity(timestamp, result, window, true, true); } return result; } -void SDL_RemovePenDevice(Uint64 timestamp, SDL_PenID instance_id) +void SDL_RemovePenDevice(Uint64 timestamp, SDL_Window *window, SDL_PenID instance_id) { if (!instance_id) { return; } + SDL_SendPenProximity(timestamp, instance_id, window, false, true); // bye bye + SDL_LockRWLockForWriting(pen_device_rwlock); SDL_Pen *pen = FindPenByInstanceId(instance_id); if (pen) { @@ -291,14 +328,7 @@ void SDL_RemovePenDevice(Uint64 timestamp, SDL_PenID instance_id) } SDL_UnlockRWLock(pen_device_rwlock); - if (pen && SDL_EventEnabled(SDL_EVENT_PEN_PROXIMITY_OUT)) { - SDL_Event event; - SDL_zero(event); - event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_OUT; - event.pproximity.timestamp = timestamp; - event.pproximity.which = instance_id; - SDL_PushEvent(&event); - } + UpdateTouchEmulationDevicePresence(); } // This presumably is happening during video quit, so we don't send PROXIMITY_OUT events here. @@ -308,12 +338,16 @@ void SDL_RemoveAllPenDevices(void (*callback)(SDL_PenID instance_id, void *handl if (pen_device_count > 0) { SDL_assert(pen_devices != NULL); for (int i = 0; i < pen_device_count; i++) { - callback(pen_devices[i].instance_id, pen_devices[i].driverdata, userdata); + if (callback) { + callback(pen_devices[i].instance_id, pen_devices[i].driverdata, userdata); + } SDL_free(pen_devices[i].name); } } SDL_free(pen_devices); pen_devices = NULL; + pen_device_count = 0; + pen_touching = 0; SDL_UnlockRWLock(pen_device_rwlock); } @@ -460,6 +494,15 @@ void SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window } } +static void EnsurePenProximity(Uint64 timestamp, SDL_Pen *pen, SDL_Window *window) +{ + if (pen->pending_proximity_out) { + pen->pending_proximity_out = false; + } else if (!(pen->input_state & SDL_PEN_INPUT_IN_PROXIMITY)) { + SDL_SendPenProximity(timestamp, pen->instance_id, window, true, true); + } +} + void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, float x, float y) { bool send_event = false; @@ -472,6 +515,8 @@ void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *wind SDL_LockRWLockForReading(pen_device_rwlock); SDL_Pen *pen = FindPenByInstanceId(instance_id); if (pen) { + EnsurePenProximity(timestamp, pen, window); + if ((pen->x != x) || (pen->y != y)) { pen->x = x; // we could do an SDL_SetAtomicInt here if we run into trouble... pen->y = y; // we could do an SDL_SetAtomicInt here if we run into trouble... @@ -508,7 +553,9 @@ void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *wind } } else if (pen_touching == 0) { // send mouse motion (without a pressed button) for pens that aren't touching. // this might cause a little chaos if you have multiple pens hovering at the same time, but this seems unlikely in the real world, and also something you did to yourself. :) - SDL_SendMouseMotion(timestamp, window, SDL_PEN_MOUSEID, false, x, y); + if (mouse->pen_mouse_events) { + SDL_SendMouseMotion(timestamp, window, SDL_PEN_MOUSEID, false, x, y); + } } } } @@ -533,6 +580,8 @@ void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *wind SDL_LockRWLockForReading(pen_device_rwlock); SDL_Pen *pen = FindPenByInstanceId(instance_id); if (pen) { + EnsurePenProximity(timestamp, pen, window); + input_state = pen->input_state; const Uint32 flag = (Uint32) (1u << button); const bool current = ((input_state & flag) != 0); @@ -584,3 +633,72 @@ void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *wind } } +void SDL_SendPenProximity(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, bool in, bool immediate) +{ + bool send_event = false; + SDL_PenInputFlags input_state = 0; + + // note that this locks for _reading_ because the lock protects the + // pen_devices array from being reallocated from under us, not the data in it; + // we assume only one thread (in the backend) is modifying an individual pen at + // a time, so it can update input state cleanly here. + SDL_LockRWLockForReading(pen_device_rwlock); + SDL_Pen *pen = FindPenByInstanceId(instance_id); + if (pen) { + if (in || immediate) { + input_state = pen->input_state; + const bool in_proximity = ((input_state & SDL_PEN_INPUT_IN_PROXIMITY) != 0); + if (in_proximity != in) { + if (in) { + input_state |= SDL_PEN_INPUT_IN_PROXIMITY; + } else { + input_state &= ~SDL_PEN_INPUT_IN_PROXIMITY; + } + send_event = true; + pen->input_state = input_state; // we could do an SDL_SetAtomicInt here if we run into trouble... + } + pen->pending_proximity_out = false; + } else { + pen->pending_proximity_out = true; + pen->pending_proximity_window_id = (window ? window->id : 0); + SDL_SetAtomicInt(&pending_proximity_out, true); + } + } + SDL_UnlockRWLock(pen_device_rwlock); + + const Uint32 event_type = in ? SDL_EVENT_PEN_PROXIMITY_IN : SDL_EVENT_PEN_PROXIMITY_OUT; + if (send_event && SDL_EventEnabled(event_type)) { + SDL_Event event; + SDL_zero(event); + event.pproximity.type = event_type; + event.pproximity.timestamp = timestamp; + event.pproximity.windowID = window ? window->id : 0; + event.pproximity.which = instance_id; + SDL_PushEvent(&event); + } +} + +void SDL_SendPendingPenProximity(void) +{ + if (SDL_CompareAndSwapAtomicInt(&pending_proximity_out, true, false)) { + SDL_LockRWLockForReading(pen_device_rwlock); + for (int i = 0; i < pen_device_count; i++) { + SDL_Pen *pen = &pen_devices[i]; + if (pen->pending_proximity_out) { + pen->pending_proximity_out = false; + + SDL_Window *window = NULL; + if (pen->pending_proximity_window_id) { + window = SDL_GetWindowFromID(pen->pending_proximity_window_id); + if (!window) { + // The window is already gone, ignore this event + continue; + } + } + SDL_SendPenProximity(0, pen->instance_id, window, false, true); + } + } + SDL_UnlockRWLock(pen_device_rwlock); + } +} + diff --git a/libs/SDL3/src/events/SDL_pen_c.h b/libs/SDL3/src/events/SDL_pen_c.h index 1eff47f2..0aca9a6d 100644 --- a/libs/SDL3/src/events/SDL_pen_c.h +++ b/libs/SDL3/src/events/SDL_pen_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -36,6 +36,8 @@ typedef Uint32 SDL_PenCapabilityFlags; #define SDL_PEN_CAPABILITY_TANGENTIAL_PRESSURE (1u << 6) /**< Provides barrel pressure on SDL_PEN_AXIS_TANGENTIAL_PRESSURE. */ #define SDL_PEN_CAPABILITY_ERASER (1u << 7) /**< Pen also has an eraser tip. */ +// Rename before making this public as it clashes with SDL_PenDeviceType. +// Prior art in Android calls this "tool type". typedef enum SDL_PenSubtype { SDL_PEN_TYPE_UNKNOWN, /**< Unknown pen device */ @@ -53,15 +55,16 @@ typedef struct SDL_PenInfo Uint32 wacom_id; /**< For Wacom devices: wacom tool type ID, otherwise 0 (useful e.g. with libwacom) */ int num_buttons; /**< Number of pen buttons (not counting the pen tip), or -1 if unknown. */ SDL_PenSubtype subtype; /**< type of pen device */ + SDL_PenDeviceType device_type; } SDL_PenInfo; // Backend calls this when a new pen device is hotplugged, plus once for each pen already connected at startup. // Note that name and info are copied but currently unused; this is placeholder for a potentially more robust API later. // Both are allowed to be NULL. -extern SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, const SDL_PenInfo *info, void *handle); +extern SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, SDL_Window *window, const SDL_PenInfo *info, void *handle, bool in_proximity); // Backend calls this when an existing pen device is disconnected during runtime. They must free their own stuff separately. -extern void SDL_RemovePenDevice(Uint64 timestamp, SDL_PenID instance_id); +extern void SDL_RemovePenDevice(Uint64 timestamp, SDL_Window *window, SDL_PenID instance_id); // Backend can call this to remove all pens, probably during shutdown, with a callback to let them free their own handle. extern void SDL_RemoveAllPenDevices(void (*callback)(SDL_PenID instance_id, void *handle, void *userdata), void *userdata); @@ -78,6 +81,12 @@ extern void SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, SDL_Window // Backend calls this when a pen's button changes, to generate events and update state. extern void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, Uint8 button, bool down); +// Backend calls this when a pen's proximity changes, to generate events and update state. +extern void SDL_SendPenProximity(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, bool in, bool immediate); + +// Pumping events calls this to generate pending pen proximity events +extern void SDL_SendPendingPenProximity(void); + // Backend can optionally use this to find the SDL_PenID for the `handle` that was passed to SDL_AddPenDevice. extern SDL_PenID SDL_FindPenByHandle(void *handle); diff --git a/libs/SDL3/src/events/SDL_quit.c b/libs/SDL3/src/events/SDL_quit.c index 5456203e..0c314264 100644 --- a/libs/SDL3/src/events/SDL_quit.c +++ b/libs/SDL3/src/events/SDL_quit.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -46,8 +46,10 @@ static bool send_foregrounding_pending = false; static void SDL_HandleSIG(int sig) { - // Reset the signal handler +#ifndef HAVE_SIGACTION + // Reset the signal handler if it was installed with signal() (void)signal(sig, SDL_HandleSIG); +#endif // Send a quit event next time the event loop pumps. // We can't send it in signal handler; SDL_malloc() might be interrupted! @@ -104,7 +106,7 @@ static void SDL_EventSignal_Quit(const int sig) if (ohandler != SDL_HandleSIG) { signal(sig, ohandler); } -#endif // HAVE_SIGNAL_H +#endif // HAVE_SIGACTION } // Public functions diff --git a/libs/SDL3/src/events/SDL_scancode_tables.c b/libs/SDL3/src/events/SDL_scancode_tables.c index a780f133..d3cbf7a7 100644 --- a/libs/SDL3/src/events/SDL_scancode_tables.c +++ b/libs/SDL3/src/events/SDL_scancode_tables.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_scancode_tables_c.h b/libs/SDL3/src/events/SDL_scancode_tables_c.h index c8ad5fcd..8c2bf882 100644 --- a/libs/SDL3/src/events/SDL_scancode_tables_c.h +++ b/libs/SDL3/src/events/SDL_scancode_tables_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/SDL_touch.c b/libs/SDL3/src/events/SDL_touch.c index bf3cc023..b8ebb78a 100644 --- a/libs/SDL3/src/events/SDL_touch.c +++ b/libs/SDL3/src/events/SDL_touch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -25,8 +25,21 @@ #include "SDL_events_c.h" #include "../video/SDL_sysvideo.h" -static int SDL_num_touch = 0; -static SDL_Touch **SDL_touchDevices = NULL; +static SDL_Mutex *SDL_touch_lock = NULL; // This needs to support recursive locks +static int SDL_touch_locked = 0; + +struct SDL_Touch +{ + SDL_TouchID id SDL_GUARDED_BY(SDL_touch_lock); + SDL_TouchDeviceType type SDL_GUARDED_BY(SDL_touch_lock); + int num_fingers SDL_GUARDED_BY(SDL_touch_lock); + int max_fingers SDL_GUARDED_BY(SDL_touch_lock); + SDL_Finger **fingers SDL_GUARDED_BY(SDL_touch_lock); + char *name SDL_GUARDED_BY(SDL_touch_lock); +}; + +static int SDL_num_touch SDL_GUARDED_BY(SDL_touch_lock) = 0; +static SDL_Touch **SDL_touchDevices SDL_GUARDED_BY(SDL_touch_lock) = NULL; // for mapping touch events to mice static bool finger_touching = false; @@ -36,31 +49,63 @@ static SDL_TouchID track_touchid; // Public functions bool SDL_InitTouch(void) { + SDL_touch_lock = SDL_CreateMutex(); return true; } +static void SDL_LockTouch(void) SDL_ACQUIRE(SDL_touch_lock) +{ + SDL_LockMutex(SDL_touch_lock); + ++SDL_touch_locked; +} + +static void SDL_UnlockTouch(void) SDL_RELEASE(SDL_touch_lock) +{ + --SDL_touch_locked; + SDL_UnlockMutex(SDL_touch_lock); +} + +static void SDL_AssertTouchLocked(void) SDL_ASSERT_CAPABILITY(SDL_touch_lock) +{ + SDL_assert(SDL_touch_locked > 0); +} + bool SDL_TouchDevicesAvailable(void) { - return SDL_num_touch > 0; + bool available; + + SDL_LockTouch(); + { + available = (SDL_num_touch > 0); + } + SDL_UnlockTouch(); + + return available; } SDL_TouchID *SDL_GetTouchDevices(int *count) { + SDL_TouchID *result; + if (count) { *count = 0; } - const int total = SDL_num_touch; - SDL_TouchID *result = (SDL_TouchID *) SDL_malloc(sizeof (SDL_TouchID) * (total + 1)); - if (result) { - for (int i = 0; i < total; i++) { - result[i] = SDL_touchDevices[i]->id; - } - result[total] = 0; - if (count) { - *count = SDL_num_touch; + SDL_LockTouch(); + { + const int total = SDL_num_touch; + result = (SDL_TouchID *)SDL_malloc(sizeof (SDL_TouchID) * (total + 1)); + if (result) { + for (int i = 0; i < total; i++) { + result[i] = SDL_touchDevices[i]->id; + } + result[total] = 0; + if (count) { + *count = SDL_num_touch; + } } } + SDL_UnlockTouch(); return result; } @@ -70,6 +115,8 @@ static int SDL_GetTouchIndex(SDL_TouchID id) int index; SDL_Touch *touch; + SDL_AssertTouchLocked(); + for (index = 0; index < SDL_num_touch; ++index) { touch = SDL_touchDevices[index]; if (touch->id == id) { @@ -81,6 +128,8 @@ static int SDL_GetTouchIndex(SDL_TouchID id) SDL_Touch *SDL_GetTouch(SDL_TouchID id) { + SDL_AssertTouchLocked(); + int index = SDL_GetTouchIndex(id); if (index < 0 || index >= SDL_num_touch) { if ((id == SDL_MOUSE_TOUCHID) || (id == SDL_PEN_TOUCHID)) { @@ -98,21 +147,40 @@ SDL_Touch *SDL_GetTouch(SDL_TouchID id) const char *SDL_GetTouchDeviceName(SDL_TouchID id) { - SDL_Touch *touch = SDL_GetTouch(id); - if (!touch) { - return NULL; + const char *name = NULL; + + SDL_LockTouch(); + { + SDL_Touch *touch = SDL_GetTouch(id); + if (touch) { + name = SDL_GetPersistentString(touch->name); + } } - return SDL_GetPersistentString(touch->name); + SDL_UnlockTouch(); + + return name; } SDL_TouchDeviceType SDL_GetTouchDeviceType(SDL_TouchID id) { - SDL_Touch *touch = SDL_GetTouch(id); - return touch ? touch->type : SDL_TOUCH_DEVICE_INVALID; + SDL_TouchDeviceType type = SDL_TOUCH_DEVICE_INVALID; + + SDL_LockTouch(); + { + SDL_Touch *touch = SDL_GetTouch(id); + if (touch) { + type = touch->type; + } + } + SDL_UnlockTouch(); + + return type; } static int SDL_GetFingerIndex(const SDL_Touch *touch, SDL_FingerID fingerid) { + SDL_AssertTouchLocked(); + int index; for (index = 0; index < touch->num_fingers; ++index) { if (touch->fingers[index]->id == fingerid) { @@ -124,6 +192,8 @@ static int SDL_GetFingerIndex(const SDL_Touch *touch, SDL_FingerID fingerid) static SDL_Finger *SDL_GetFinger(const SDL_Touch *touch, SDL_FingerID id) { + SDL_AssertTouchLocked(); + int index = SDL_GetFingerIndex(touch, id); if (index < 0 || index >= touch->num_fingers) { return NULL; @@ -140,27 +210,34 @@ SDL_Finger **SDL_GetTouchFingers(SDL_TouchID touchID, int *count) *count = 0; } - SDL_Touch *touch = SDL_GetTouch(touchID); - if (!touch) { - return NULL; - } + SDL_LockTouch(); + { + SDL_Touch *touch = SDL_GetTouch(touchID); + if (!touch) { + SDL_UnlockTouch(); + return NULL; + } - // Create a snapshot of the current finger state - fingers = (SDL_Finger **)SDL_malloc((touch->num_fingers + 1) * sizeof(*fingers) + touch->num_fingers * sizeof(**fingers)); - if (!fingers) { - return NULL; - } - finger_data = (SDL_Finger *)(fingers + (touch->num_fingers + 1)); + // Create a snapshot of the current finger state + fingers = (SDL_Finger **)SDL_malloc((touch->num_fingers + 1) * sizeof(*fingers) + touch->num_fingers * sizeof(**fingers)); + if (!fingers) { + SDL_UnlockTouch(); + return NULL; + } + finger_data = (SDL_Finger *)(fingers + (touch->num_fingers + 1)); - for (int i = 0; i < touch->num_fingers; ++i) { - fingers[i] = &finger_data[i]; - SDL_copyp(fingers[i], touch->fingers[i]); - } - fingers[touch->num_fingers] = NULL; + for (int i = 0; i < touch->num_fingers; ++i) { + fingers[i] = &finger_data[i]; + SDL_copyp(fingers[i], touch->fingers[i]); + } + fingers[touch->num_fingers] = NULL; - if (count) { - *count = touch->num_fingers; + if (count) { + *count = touch->num_fingers; + } } + SDL_UnlockTouch(); + return fingers; } @@ -171,56 +248,55 @@ int SDL_AddTouch(SDL_TouchID touchID, SDL_TouchDeviceType type, const char *name SDL_assert(touchID != 0); - index = SDL_GetTouchIndex(touchID); - if (index >= 0) { - return index; + SDL_LockTouch(); + { + index = SDL_GetTouchIndex(touchID); + if (index >= 0) { + SDL_UnlockTouch(); + return index; + } + + // Add the touch to the list of touch + touchDevices = (SDL_Touch **)SDL_realloc(SDL_touchDevices, + (SDL_num_touch + 1) * sizeof(*touchDevices)); + if (!touchDevices) { + SDL_UnlockTouch(); + return -1; + } + + SDL_touchDevices = touchDevices; + index = SDL_num_touch; + + SDL_touchDevices[index] = (SDL_Touch *)SDL_malloc(sizeof(*SDL_touchDevices[index])); + if (!SDL_touchDevices[index]) { + SDL_UnlockTouch(); + return -1; + } + + // Added touch to list + ++SDL_num_touch; + + // we're setting the touch properties + SDL_touchDevices[index]->id = touchID; + SDL_touchDevices[index]->type = type; + SDL_touchDevices[index]->num_fingers = 0; + SDL_touchDevices[index]->max_fingers = 0; + SDL_touchDevices[index]->fingers = NULL; + SDL_touchDevices[index]->name = SDL_strdup(name ? name : ""); } - - // Add the touch to the list of touch - touchDevices = (SDL_Touch **)SDL_realloc(SDL_touchDevices, - (SDL_num_touch + 1) * sizeof(*touchDevices)); - if (!touchDevices) { - return -1; - } - - SDL_touchDevices = touchDevices; - index = SDL_num_touch; - - SDL_touchDevices[index] = (SDL_Touch *)SDL_malloc(sizeof(*SDL_touchDevices[index])); - if (!SDL_touchDevices[index]) { - return -1; - } - - // Added touch to list - ++SDL_num_touch; - - // we're setting the touch properties - SDL_touchDevices[index]->id = touchID; - SDL_touchDevices[index]->type = type; - SDL_touchDevices[index]->num_fingers = 0; - SDL_touchDevices[index]->max_fingers = 0; - SDL_touchDevices[index]->fingers = NULL; - SDL_touchDevices[index]->name = SDL_strdup(name ? name : ""); + SDL_UnlockTouch(); return index; } -// Set or update the name of a touch. -void SDL_SetTouchName(SDL_TouchID id, const char *name) -{ - SDL_Touch *touch = SDL_GetTouch(id); - if (touch) { - SDL_free(touch->name); - touch->name = SDL_strdup(name ? name : ""); - } -} - static bool SDL_AddFinger(SDL_Touch *touch, SDL_FingerID fingerid, float x, float y, float pressure) { SDL_Finger *finger; SDL_assert(fingerid != 0); + SDL_AssertTouchLocked(); + if (touch->num_fingers == touch->max_fingers) { SDL_Finger **new_fingers; new_fingers = (SDL_Finger **)SDL_realloc(touch->fingers, (touch->max_fingers + 1) * sizeof(*touch->fingers)); @@ -245,6 +321,8 @@ static bool SDL_AddFinger(SDL_Touch *touch, SDL_FingerID fingerid, float x, floa static void SDL_DelFinger(SDL_Touch *touch, SDL_FingerID fingerid) { + SDL_AssertTouchLocked(); + int index = SDL_GetFingerIndex(touch, fingerid); if (index < 0) { return; @@ -266,30 +344,159 @@ void SDL_SendTouch(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_ SDL_Finger *finger; bool down = (type == SDL_EVENT_FINGER_DOWN); - SDL_Touch *touch = SDL_GetTouch(id); - if (!touch) { - return; - } - - SDL_Mouse *mouse = SDL_GetMouse(); - - // SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events - // SDL_HINT_VITA_TOUCH_MOUSE_DEVICE: controlling which touchpad should generate synthetic mouse events, PSVita-only + SDL_LockTouch(); { - // FIXME: maybe we should only restrict to a few SDL_TouchDeviceType - if ((id != SDL_MOUSE_TOUCHID) && (id != SDL_PEN_TOUCHID)) { -#ifdef SDL_PLATFORM_VITA - if (mouse->touch_mouse_events && ((mouse->vita_touch_mouse_device == id) || (mouse->vita_touch_mouse_device == 3))) { -#else - if (mouse->touch_mouse_events) { -#endif - if (window) { + SDL_Touch *touch = SDL_GetTouch(id); + if (!touch) { + SDL_UnlockTouch(); + return; + } + + SDL_Mouse *mouse = SDL_GetMouse(); + + // SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events + // SDL_HINT_VITA_TOUCH_MOUSE_DEVICE: controlling which touchpad should generate synthetic mouse events, PSVita-only + { + // FIXME: maybe we should only restrict to a few SDL_TouchDeviceType + if ((id != SDL_MOUSE_TOUCHID) && (id != SDL_PEN_TOUCHID)) { + #ifdef SDL_PLATFORM_VITA + if (mouse->touch_mouse_events && ((mouse->vita_touch_mouse_device == id) || (mouse->vita_touch_mouse_device == 3))) { + #else + if (mouse->touch_mouse_events) { + #endif + if (window) { + if (down) { + if (finger_touching == false) { + float pos_x = (x * (float)window->w); + float pos_y = (y * (float)window->h); + if (pos_x < 0) { + pos_x = 0; + } + if (pos_x > (float)(window->w - 1)) { + pos_x = (float)(window->w - 1); + } + if (pos_y < 0.0f) { + pos_y = 0.0f; + } + if (pos_y > (float)(window->h - 1)) { + pos_y = (float)(window->h - 1); + } + SDL_SendMouseMotion(timestamp, window, SDL_TOUCH_MOUSEID, false, pos_x, pos_y); + SDL_SendMouseButton(timestamp, window, SDL_TOUCH_MOUSEID, SDL_BUTTON_LEFT, true); + } + } else { + if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) { + SDL_SendMouseButton(timestamp, window, SDL_TOUCH_MOUSEID, SDL_BUTTON_LEFT, false); + } + } + } if (down) { if (finger_touching == false) { + finger_touching = true; + track_touchid = id; + track_fingerid = fingerid; + } + } else { + if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) { + finger_touching = false; + } + } + } + } + } + + // SDL_HINT_MOUSE_TOUCH_EVENTS: if not set, discard synthetic touch events coming from platform layer + if (!mouse->mouse_touch_events && (id == SDL_MOUSE_TOUCHID)) { + SDL_UnlockTouch(); + return; + } else if (!mouse->pen_touch_events && (id == SDL_PEN_TOUCHID)) { + SDL_UnlockTouch(); + return; + } + + finger = SDL_GetFinger(touch, fingerid); + if (down) { + if (finger) { + /* This finger is already down. + Assume the finger-up for the previous touch was lost, and send it. */ + SDL_SendTouch(timestamp, id, fingerid, window, SDL_EVENT_FINGER_CANCELED, x, y, pressure); + } + + if (!SDL_AddFinger(touch, fingerid, x, y, pressure)) { + SDL_UnlockTouch(); + return; + } + + if (SDL_EventEnabled(type)) { + SDL_Event event; + event.type = type; + event.common.timestamp = timestamp; + event.tfinger.touchID = id; + event.tfinger.fingerID = fingerid; + event.tfinger.x = x; + event.tfinger.y = y; + event.tfinger.dx = 0; + event.tfinger.dy = 0; + event.tfinger.pressure = pressure; + event.tfinger.windowID = window ? SDL_GetWindowID(window) : 0; + SDL_PushEvent(&event); + } + } else { + if (!finger) { + // This finger is already up + SDL_UnlockTouch(); + return; + } + + if (SDL_EventEnabled(type)) { + SDL_Event event; + event.type = type; + event.common.timestamp = timestamp; + event.tfinger.touchID = id; + event.tfinger.fingerID = fingerid; + // I don't trust the coordinates passed on fingerUp + event.tfinger.x = finger->x; + event.tfinger.y = finger->y; + event.tfinger.dx = 0; + event.tfinger.dy = 0; + event.tfinger.pressure = pressure; + event.tfinger.windowID = window ? SDL_GetWindowID(window) : 0; + SDL_PushEvent(&event); + } + + SDL_DelFinger(touch, fingerid); + } + } + SDL_UnlockTouch(); +} + +void SDL_SendTouchMotion(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_Window *window, + float x, float y, float pressure) +{ + SDL_Touch *touch; + SDL_Finger *finger; + float xrel, yrel, prel; + + SDL_LockTouch(); + { + touch = SDL_GetTouch(id); + if (!touch) { + SDL_UnlockTouch(); + return; + } + + SDL_Mouse *mouse = SDL_GetMouse(); + + // SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events + { + if ((id != SDL_MOUSE_TOUCHID) && (id != SDL_PEN_TOUCHID)) { + if (mouse->touch_mouse_events) { + if (window) { + if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) { float pos_x = (x * (float)window->w); float pos_y = (y * (float)window->h); - if (pos_x < 0) { - pos_x = 0; + if (pos_x < 0.0f) { + pos_x = 0.0f; } if (pos_x > (float)(window->w - 1)) { pos_x = (float)(window->w - 1); @@ -301,174 +508,62 @@ void SDL_SendTouch(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_ pos_y = (float)(window->h - 1); } SDL_SendMouseMotion(timestamp, window, SDL_TOUCH_MOUSEID, false, pos_x, pos_y); - SDL_SendMouseButton(timestamp, window, SDL_TOUCH_MOUSEID, SDL_BUTTON_LEFT, true); } - } else { - if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) { - SDL_SendMouseButton(timestamp, window, SDL_TOUCH_MOUSEID, SDL_BUTTON_LEFT, false); - } - } - } - if (down) { - if (finger_touching == false) { - finger_touching = true; - track_touchid = id; - track_fingerid = fingerid; - } - } else { - if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) { - finger_touching = false; } } } } - } - // SDL_HINT_MOUSE_TOUCH_EVENTS: if not set, discard synthetic touch events coming from platform layer - if (!mouse->mouse_touch_events && (id == SDL_MOUSE_TOUCHID)) { - return; - } else if (!mouse->pen_touch_events && (id == SDL_PEN_TOUCHID)) { - return; - } - - finger = SDL_GetFinger(touch, fingerid); - if (down) { - if (finger) { - /* This finger is already down. - Assume the finger-up for the previous touch was lost, and send it. */ - SDL_SendTouch(timestamp, id, fingerid, window, SDL_EVENT_FINGER_CANCELED, x, y, pressure); + // SDL_HINT_MOUSE_TOUCH_EVENTS: if not set, discard synthetic touch events coming from platform layer + if (!mouse->mouse_touch_events) { + if (id == SDL_MOUSE_TOUCHID) { + SDL_UnlockTouch(); + return; + } } - if (!SDL_AddFinger(touch, fingerid, x, y, pressure)) { + finger = SDL_GetFinger(touch, fingerid); + if (!finger) { + SDL_SendTouch(timestamp, id, fingerid, window, SDL_EVENT_FINGER_DOWN, x, y, pressure); + SDL_UnlockTouch(); return; } - if (SDL_EventEnabled(type)) { + xrel = x - finger->x; + yrel = y - finger->y; + prel = pressure - finger->pressure; + + // Drop events that don't change state + if (xrel == 0.0f && yrel == 0.0f && prel == 0.0f) { + #if 0 + printf("Touch event didn't change state - dropped!\n"); + #endif + SDL_UnlockTouch(); + return; + } + + // Update internal touch coordinates + finger->x = x; + finger->y = y; + finger->pressure = pressure; + + // Post the event, if desired + if (SDL_EventEnabled(SDL_EVENT_FINGER_MOTION)) { SDL_Event event; - event.type = type; + event.type = SDL_EVENT_FINGER_MOTION; event.common.timestamp = timestamp; event.tfinger.touchID = id; event.tfinger.fingerID = fingerid; event.tfinger.x = x; event.tfinger.y = y; - event.tfinger.dx = 0; - event.tfinger.dy = 0; + event.tfinger.dx = xrel; + event.tfinger.dy = yrel; event.tfinger.pressure = pressure; event.tfinger.windowID = window ? SDL_GetWindowID(window) : 0; SDL_PushEvent(&event); } - } else { - if (!finger) { - // This finger is already up - return; - } - - if (SDL_EventEnabled(type)) { - SDL_Event event; - event.type = type; - event.common.timestamp = timestamp; - event.tfinger.touchID = id; - event.tfinger.fingerID = fingerid; - // I don't trust the coordinates passed on fingerUp - event.tfinger.x = finger->x; - event.tfinger.y = finger->y; - event.tfinger.dx = 0; - event.tfinger.dy = 0; - event.tfinger.pressure = pressure; - event.tfinger.windowID = window ? SDL_GetWindowID(window) : 0; - SDL_PushEvent(&event); - } - - SDL_DelFinger(touch, fingerid); - } -} - -void SDL_SendTouchMotion(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_Window *window, - float x, float y, float pressure) -{ - SDL_Touch *touch; - SDL_Finger *finger; - float xrel, yrel, prel; - - touch = SDL_GetTouch(id); - if (!touch) { - return; - } - - SDL_Mouse *mouse = SDL_GetMouse(); - - // SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events - { - if ((id != SDL_MOUSE_TOUCHID) && (id != SDL_PEN_TOUCHID)) { - if (mouse->touch_mouse_events) { - if (window) { - if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) { - float pos_x = (x * (float)window->w); - float pos_y = (y * (float)window->h); - if (pos_x < 0.0f) { - pos_x = 0.0f; - } - if (pos_x > (float)(window->w - 1)) { - pos_x = (float)(window->w - 1); - } - if (pos_y < 0.0f) { - pos_y = 0.0f; - } - if (pos_y > (float)(window->h - 1)) { - pos_y = (float)(window->h - 1); - } - SDL_SendMouseMotion(timestamp, window, SDL_TOUCH_MOUSEID, false, pos_x, pos_y); - } - } - } - } - } - - // SDL_HINT_MOUSE_TOUCH_EVENTS: if not set, discard synthetic touch events coming from platform layer - if (mouse->mouse_touch_events == 0) { - if (id == SDL_MOUSE_TOUCHID) { - return; - } - } - - finger = SDL_GetFinger(touch, fingerid); - if (!finger) { - SDL_SendTouch(timestamp, id, fingerid, window, SDL_EVENT_FINGER_DOWN, x, y, pressure); - return; - } - - xrel = x - finger->x; - yrel = y - finger->y; - prel = pressure - finger->pressure; - - // Drop events that don't change state - if (xrel == 0.0f && yrel == 0.0f && prel == 0.0f) { -#if 0 - printf("Touch event didn't change state - dropped!\n"); -#endif - return; - } - - // Update internal touch coordinates - finger->x = x; - finger->y = y; - finger->pressure = pressure; - - // Post the event, if desired - if (SDL_EventEnabled(SDL_EVENT_FINGER_MOTION)) { - SDL_Event event; - event.type = SDL_EVENT_FINGER_MOTION; - event.common.timestamp = timestamp; - event.tfinger.touchID = id; - event.tfinger.fingerID = fingerid; - event.tfinger.x = x; - event.tfinger.y = y; - event.tfinger.dx = xrel; - event.tfinger.dy = yrel; - event.tfinger.pressure = pressure; - event.tfinger.windowID = window ? SDL_GetWindowID(window) : 0; - SDL_PushEvent(&event); } + SDL_UnlockTouch(); } void SDL_DelTouch(SDL_TouchID id) @@ -476,37 +571,66 @@ void SDL_DelTouch(SDL_TouchID id) int i, index; SDL_Touch *touch; - if (SDL_num_touch == 0) { - // We've already cleaned up, we won't find this device - return; - } + SDL_LockTouch(); + { + if (SDL_num_touch == 0) { + // We've already cleaned up, we won't find this device + SDL_UnlockTouch(); + return; + } - index = SDL_GetTouchIndex(id); - touch = SDL_GetTouch(id); - if (!touch) { - return; - } + index = SDL_GetTouchIndex(id); + touch = SDL_GetTouch(id); + if (!touch) { + SDL_UnlockTouch(); + return; + } - for (i = 0; i < touch->max_fingers; ++i) { - SDL_free(touch->fingers[i]); - } - SDL_free(touch->fingers); - SDL_free(touch->name); - SDL_free(touch); + for (i = 0; i < touch->max_fingers; ++i) { + SDL_free(touch->fingers[i]); + } + SDL_free(touch->fingers); + SDL_free(touch->name); + SDL_free(touch); - SDL_num_touch--; - SDL_touchDevices[index] = SDL_touchDevices[SDL_num_touch]; + SDL_num_touch--; + SDL_touchDevices[index] = SDL_touchDevices[SDL_num_touch]; + } + SDL_UnlockTouch(); } void SDL_QuitTouch(void) { int i; - for (i = SDL_num_touch; i--;) { - SDL_DelTouch(SDL_touchDevices[i]->id); - } - SDL_assert(SDL_num_touch == 0); + SDL_LockTouch(); + { + for (i = SDL_num_touch; i--;) { + SDL_DelTouch(SDL_touchDevices[i]->id); + } + SDL_assert(SDL_num_touch == 0); - SDL_free(SDL_touchDevices); - SDL_touchDevices = NULL; + SDL_free(SDL_touchDevices); + SDL_touchDevices = NULL; + } + SDL_UnlockTouch(); + + SDL_DestroyMutex(SDL_touch_lock); + SDL_touch_lock = NULL; } + +int SDL_SendPinch(SDL_EventType type, Uint64 timestamp, SDL_Window *window, float scale) +{ + /* Post the event, if desired */ + int posted = 0; + if (SDL_EventEnabled(type)) { + SDL_Event event; + event.type = type; + event.common.timestamp = timestamp; + event.pinch.scale = scale; + event.pinch.windowID = window ? SDL_GetWindowID(window) : 0; + posted = (SDL_PushEvent(&event) > 0); + } + return posted; +} + diff --git a/libs/SDL3/src/events/SDL_touch_c.h b/libs/SDL3/src/events/SDL_touch_c.h index e46ba681..27443059 100644 --- a/libs/SDL3/src/events/SDL_touch_c.h +++ b/libs/SDL3/src/events/SDL_touch_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -23,15 +23,7 @@ #ifndef SDL_touch_c_h_ #define SDL_touch_c_h_ -typedef struct SDL_Touch -{ - SDL_TouchID id; - SDL_TouchDeviceType type; - int num_fingers; - int max_fingers; - SDL_Finger **fingers; - char *name; -} SDL_Touch; +typedef struct SDL_Touch SDL_Touch; // Initialize the touch subsystem extern bool SDL_InitTouch(void); @@ -42,9 +34,6 @@ extern bool SDL_TouchDevicesAvailable(void); // Add a touch, returning the index of the touch, or -1 if there was an error. extern int SDL_AddTouch(SDL_TouchID id, SDL_TouchDeviceType type, const char *name); -// Set or update the name of a touch. -extern void SDL_SetTouchName(SDL_TouchID id, const char *name); - // Get the touch with a given id extern SDL_Touch *SDL_GetTouch(SDL_TouchID id); @@ -60,4 +49,7 @@ extern void SDL_DelTouch(SDL_TouchID id); // Shutdown the touch subsystem extern void SDL_QuitTouch(void); +// Send Gesture events +extern int SDL_SendPinch(SDL_EventType type, Uint64 timestamp, SDL_Window *window, float scale); + #endif // SDL_touch_c_h_ diff --git a/libs/SDL3/src/events/SDL_windowevents.c b/libs/SDL3/src/events/SDL_windowevents.c index fa5488a6..1f8a9615 100644 --- a/libs/SDL3/src/events/SDL_windowevents.c +++ b/libs/SDL3/src/events/SDL_windowevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -56,7 +56,7 @@ void SDL_RemoveWindowEventWatch(SDL_WindowEventWatchPriority priority, SDL_Event SDL_RemoveEventWatchList(&SDL_window_event_watchers[priority], filter, userdata); } -static bool SDLCALL RemoveSupercededWindowEvents(void *userdata, SDL_Event *event) +static bool SDLCALL RemoveSupersededWindowEvents(void *userdata, SDL_Event *event) { SDL_Event *new_event = (SDL_Event *)userdata; @@ -70,6 +70,8 @@ static bool SDLCALL RemoveSupercededWindowEvents(void *userdata, SDL_Event *even bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data1, int data2) { + SDL_VideoDevice *_this; + bool post_event = true; bool posted = false; if (!window) { @@ -77,9 +79,6 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data } SDL_assert(SDL_ObjectValid(window, SDL_OBJECT_TYPE_WINDOW)); - if (window->is_destroying && windowevent != SDL_EVENT_WINDOW_DESTROYED) { - return false; - } switch (windowevent) { case SDL_EVENT_WINDOW_SHOWN: if (!(window->flags & SDL_WINDOW_HIDDEN)) { @@ -99,6 +98,12 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data case SDL_EVENT_WINDOW_MOVED: window->undefined_x = false; window->undefined_y = false; + /* Clear the pending display if this move was not the result of an explicit request, + * and the window is not scheduled to become fullscreen when shown. + */ + if (!window->last_position_pending && !(window->pending_flags & SDL_WINDOW_FULLSCREEN)) { + window->pending_displayID = 0; + } window->last_position_pending = false; if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { window->windowed.x = data1; @@ -185,11 +190,11 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data window->flags &= ~SDL_WINDOW_INPUT_FOCUS; break; case SDL_EVENT_WINDOW_DISPLAY_CHANGED: - if (data1 == 0 || (SDL_DisplayID)data1 == window->last_displayID) { + if (data1 == 0 || (SDL_DisplayID)data1 == window->displayID) { return false; } window->update_fullscreen_on_display_changed = true; - window->last_displayID = (SDL_DisplayID)data1; + window->displayID = (SDL_DisplayID)data1; break; case SDL_EVENT_WINDOW_OCCLUDED: if (window->flags & SDL_WINDOW_OCCLUDED) { @@ -213,6 +218,19 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data break; } + if (window->is_destroying && windowevent != SDL_EVENT_WINDOW_DESTROYED) { + return false; + } + + // Only post if we are not currently quitting + _this = SDL_GetVideoDevice(); + if (_this == NULL || _this->is_quitting) { + post_event = false; + } + + // The window might be destroyed in an event handler, so cache the topmost status first. + const bool window_is_topmost = !window->parent; + // Post the event, if desired SDL_Event event; event.type = windowevent; @@ -224,7 +242,7 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data SDL_DispatchEventWatchList(&SDL_window_event_watchers[SDL_WINDOW_EVENT_WATCH_EARLY], &event); SDL_DispatchEventWatchList(&SDL_window_event_watchers[SDL_WINDOW_EVENT_WATCH_NORMAL], &event); - if (SDL_EventEnabled(windowevent)) { + if (post_event && SDL_EventEnabled(windowevent)) { // Fixes queue overflow with move/resize events that aren't processed if (windowevent == SDL_EVENT_WINDOW_MOVED || windowevent == SDL_EVENT_WINDOW_RESIZED || @@ -232,67 +250,71 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data windowevent == SDL_EVENT_WINDOW_SAFE_AREA_CHANGED || windowevent == SDL_EVENT_WINDOW_EXPOSED || windowevent == SDL_EVENT_WINDOW_OCCLUDED) { - SDL_FilterEvents(RemoveSupercededWindowEvents, &event); + SDL_FilterEvents(RemoveSupersededWindowEvents, &event); } posted = SDL_PushEvent(&event); } - switch (windowevent) { - case SDL_EVENT_WINDOW_SHOWN: - SDL_OnWindowShown(window); - break; - case SDL_EVENT_WINDOW_HIDDEN: - SDL_OnWindowHidden(window); - break; - case SDL_EVENT_WINDOW_MOVED: - SDL_OnWindowMoved(window); - break; - case SDL_EVENT_WINDOW_RESIZED: - SDL_OnWindowResized(window); - break; - case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: - SDL_OnWindowPixelSizeChanged(window); - break; - case SDL_EVENT_WINDOW_MINIMIZED: - SDL_OnWindowMinimized(window); - break; - case SDL_EVENT_WINDOW_MAXIMIZED: - SDL_OnWindowMaximized(window); - break; - case SDL_EVENT_WINDOW_RESTORED: - SDL_OnWindowRestored(window); - break; - case SDL_EVENT_WINDOW_MOUSE_ENTER: - SDL_OnWindowEnter(window); - break; - case SDL_EVENT_WINDOW_MOUSE_LEAVE: - SDL_OnWindowLeave(window); - break; - case SDL_EVENT_WINDOW_FOCUS_GAINED: - SDL_OnWindowFocusGained(window); - break; - case SDL_EVENT_WINDOW_FOCUS_LOST: - SDL_OnWindowFocusLost(window); - break; - case SDL_EVENT_WINDOW_DISPLAY_CHANGED: - SDL_OnWindowDisplayChanged(window); - break; - default: - break; + // Ensure that the window is still valid, as it may have been destroyed in an event handler. + window = SDL_GetWindowFromID(event.window.windowID); + + if (window) { + switch (windowevent) { + case SDL_EVENT_WINDOW_SHOWN: + SDL_OnWindowShown(window); + break; + case SDL_EVENT_WINDOW_HIDDEN: + SDL_OnWindowHidden(window); + break; + case SDL_EVENT_WINDOW_MOVED: + SDL_OnWindowMoved(window); + break; + case SDL_EVENT_WINDOW_RESIZED: + SDL_OnWindowResized(window); + break; + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: + SDL_OnWindowPixelSizeChanged(window); + break; + case SDL_EVENT_WINDOW_MINIMIZED: + SDL_OnWindowMinimized(window); + break; + case SDL_EVENT_WINDOW_MAXIMIZED: + SDL_OnWindowMaximized(window); + break; + case SDL_EVENT_WINDOW_RESTORED: + SDL_OnWindowRestored(window); + break; + case SDL_EVENT_WINDOW_MOUSE_ENTER: + SDL_OnWindowEnter(window); + break; + case SDL_EVENT_WINDOW_MOUSE_LEAVE: + SDL_OnWindowLeave(window); + break; + case SDL_EVENT_WINDOW_FOCUS_GAINED: + SDL_OnWindowFocusGained(window); + break; + case SDL_EVENT_WINDOW_FOCUS_LOST: + SDL_OnWindowFocusLost(window); + break; + case SDL_EVENT_WINDOW_DISPLAY_CHANGED: + SDL_OnWindowDisplayChanged(window); + break; + default: + break; + } } - if (windowevent == SDL_EVENT_WINDOW_CLOSE_REQUESTED && !window->parent && !SDL_HasActiveTrays()) { - int toplevel_count = 0; - SDL_Window *n; - for (n = SDL_GetVideoDevice()->windows; n; n = n->next) { + if (windowevent == SDL_EVENT_WINDOW_CLOSE_REQUESTED && window_is_topmost && !SDL_HasActiveTrays()) { + int count = window ? 0 : 1; + for (SDL_Window *n = _this->windows; n; n = n->next) { if (!n->parent && !(n->flags & SDL_WINDOW_HIDDEN)) { - ++toplevel_count; + ++count; } } - if (toplevel_count <= 1) { + if (count <= 1) { if (SDL_GetHintBoolean(SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE, true)) { - SDL_SendQuit(); // This is the last toplevel window in the list so send the SDL_EVENT_QUIT event + SDL_SendQuit(); // This is the last window in the list, so send the SDL_EVENT_QUIT event } } } diff --git a/libs/SDL3/src/events/SDL_windowevents_c.h b/libs/SDL3/src/events/SDL_windowevents_c.h index 72043054..942e1541 100644 --- a/libs/SDL3/src/events/SDL_windowevents_c.h +++ b/libs/SDL3/src/events/SDL_windowevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -23,6 +23,11 @@ #ifndef SDL_windowevents_c_h_ #define SDL_windowevents_c_h_ +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { SDL_WINDOW_EVENT_WATCH_EARLY, @@ -36,4 +41,9 @@ extern void SDL_RemoveWindowEventWatch(SDL_WindowEventWatchPriority priority, SD extern bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data1, int data2); +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif + #endif // SDL_windowevents_c_h_ diff --git a/libs/SDL3/src/events/blank_cursor.h b/libs/SDL3/src/events/blank_cursor.h index d8d9b4db..14ac8639 100644 --- a/libs/SDL3/src/events/blank_cursor.h +++ b/libs/SDL3/src/events/blank_cursor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/default_cursor.h b/libs/SDL3/src/events/default_cursor.h index 9a76f94d..5cda7b65 100644 --- a/libs/SDL3/src/events/default_cursor.h +++ b/libs/SDL3/src/events/default_cursor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/scancodes_darwin.h b/libs/SDL3/src/events/scancodes_darwin.h index c3a2d4cd..0ca4d556 100644 --- a/libs/SDL3/src/events/scancodes_darwin.h +++ b/libs/SDL3/src/events/scancodes_darwin.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/scancodes_linux.h b/libs/SDL3/src/events/scancodes_linux.h index 2deeb9af..30880243 100644 --- a/libs/SDL3/src/events/scancodes_linux.h +++ b/libs/SDL3/src/events/scancodes_linux.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/scancodes_windows.h b/libs/SDL3/src/events/scancodes_windows.h index 6f35114b..834e2fab 100644 --- a/libs/SDL3/src/events/scancodes_windows.h +++ b/libs/SDL3/src/events/scancodes_windows.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/events/scancodes_xfree86.h b/libs/SDL3/src/events/scancodes_xfree86.h index 81d625e8..cb0baf68 100644 --- a/libs/SDL3/src/events/scancodes_xfree86.h +++ b/libs/SDL3/src/events/scancodes_xfree86.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/filesystem/SDL_filesystem.c b/libs/SDL3/src/filesystem/SDL_filesystem.c index b115019b..81c0b41e 100644 --- a/libs/SDL3/src/filesystem/SDL_filesystem.c +++ b/libs/SDL3/src/filesystem/SDL_filesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -27,7 +27,7 @@ bool SDL_RemovePath(const char *path) { - if (!path) { + CHECK_PARAM(!path) { return SDL_InvalidParamError("path"); } return SDL_SYS_RemovePath(path); @@ -35,9 +35,10 @@ bool SDL_RemovePath(const char *path) bool SDL_RenamePath(const char *oldpath, const char *newpath) { - if (!oldpath) { + CHECK_PARAM(!oldpath) { return SDL_InvalidParamError("oldpath"); - } else if (!newpath) { + } + CHECK_PARAM(!newpath) { return SDL_InvalidParamError("newpath"); } return SDL_SYS_RenamePath(oldpath, newpath); @@ -45,9 +46,10 @@ bool SDL_RenamePath(const char *oldpath, const char *newpath) bool SDL_CopyFile(const char *oldpath, const char *newpath) { - if (!oldpath) { + CHECK_PARAM(!oldpath) { return SDL_InvalidParamError("oldpath"); - } else if (!newpath) { + } + CHECK_PARAM(!newpath) { return SDL_InvalidParamError("newpath"); } return SDL_SYS_CopyFile(oldpath, newpath); @@ -55,7 +57,7 @@ bool SDL_CopyFile(const char *oldpath, const char *newpath) bool SDL_CreateDirectory(const char *path) { - if (!path) { + CHECK_PARAM(!path) { return SDL_InvalidParamError("path"); } @@ -116,9 +118,10 @@ bool SDL_CreateDirectory(const char *path) bool SDL_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback callback, void *userdata) { - if (!path) { + CHECK_PARAM(!path) { return SDL_InvalidParamError("path"); - } else if (!callback) { + } + CHECK_PARAM(!callback) { return SDL_InvalidParamError("callback"); } return SDL_SYS_EnumerateDirectory(path, callback, userdata); @@ -133,10 +136,9 @@ bool SDL_GetPathInfo(const char *path, SDL_PathInfo *info) } SDL_zerop(info); - if (!path) { + CHECK_PARAM(!path) { return SDL_InvalidParamError("path"); } - return SDL_SYS_GetPathInfo(path, info); } @@ -201,7 +203,7 @@ static bool WildcardMatch(const char *pattern, const char *str, bool *matched_to pch = *(++pattern); } - *matched_to_dir = ((pch == '/') || (pch == '\0')); // end of string and the pattern is complete or failed at a '/'? We should descend into this directory. + *matched_to_dir = (pch == '/'); // end of string and the pattern failed at a '/'? We should descend into this directory. return (pch == '\0'); // survived the whole pattern? That's a match! } @@ -364,7 +366,7 @@ char **SDL_InternalGlobDirectory(const char *path, const char *pattern, SDL_Glob } *count = 0; - if (!path) { + CHECK_PARAM(!path) { SDL_InvalidParamError("path"); return NULL; } @@ -378,8 +380,9 @@ char **SDL_InternalGlobDirectory(const char *path, const char *pattern, SDL_Glob return NULL; } char *ptr = &pathcpy[pathlen-1]; - while ((ptr >= pathcpy) && ((*ptr == '/') || (*ptr == '\\'))) { + while ((ptr > pathcpy) && ((*ptr == '/') || (*ptr == '\\'))) { *(ptr--) = '\0'; + --pathlen; } path = pathcpy; } @@ -423,8 +426,15 @@ char **SDL_InternalGlobDirectory(const char *path, const char *pattern, SDL_Glob data.enumerator = enumerator; data.getpathinfo = getpathinfo; data.fsuserdata = userdata; - data.basedirlen = *path ? (SDL_strlen(path) + 1) : 0; // +1 for the '/' we'll be adding. + data.basedirlen = 0; + if (*path) { + if (SDL_strcmp(path, "/") == 0 || SDL_strcmp(path, "\\") == 0) { + data.basedirlen = 1; + } else { + data.basedirlen = pathlen + 1; // +1 for the '/' we'll be adding. + } + } char **result = NULL; if (data.enumerator(path, GlobDirectoryCallback, &data, data.fsuserdata)) { @@ -488,7 +498,8 @@ static char *CachedUserFolders[SDL_FOLDER_COUNT]; const char *SDL_GetUserFolder(SDL_Folder folder) { const int idx = (int) folder; - if ((idx < 0) || (idx >= SDL_arraysize(CachedUserFolders))) { + + CHECK_PARAM((idx < 0) || (idx >= SDL_arraysize(CachedUserFolders))) { SDL_InvalidParamError("folder"); return NULL; } @@ -499,9 +510,27 @@ const char *SDL_GetUserFolder(SDL_Folder folder) return CachedUserFolders[idx]; } +static char *CachedExeName = NULL; +const char *SDL_GetExeName(void) +{ + if (!CachedExeName) { + CachedExeName = SDL_SYS_GetExeName(); + } + return CachedExeName; +} char *SDL_GetPrefPath(const char *org, const char *app) { + CHECK_PARAM(!app) { + SDL_InvalidParamError("app"); + return NULL; + } + + // if org is NULL, just make it "" so backends don't have to check both. + if (!org) { + org = ""; + } + return SDL_SYS_GetPrefPath(org, app); } @@ -516,10 +545,12 @@ void SDL_InitFilesystem(void) void SDL_QuitFilesystem(void) { - if (CachedBasePath) { - SDL_free(CachedBasePath); - CachedBasePath = NULL; - } + SDL_free(CachedBasePath); + CachedBasePath = NULL; + + SDL_free(CachedExeName); + CachedExeName = NULL; + for (int i = 0; i < SDL_arraysize(CachedUserFolders); i++) { if (CachedUserFolders[i]) { SDL_free(CachedUserFolders[i]); diff --git a/libs/SDL3/src/filesystem/SDL_filesystem_c.h b/libs/SDL3/src/filesystem/SDL_filesystem_c.h index 8cfdcabf..685cda28 100644 --- a/libs/SDL3/src/filesystem/SDL_filesystem_c.h +++ b/libs/SDL3/src/filesystem/SDL_filesystem_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/filesystem/SDL_sysfilesystem.h b/libs/SDL3/src/filesystem/SDL_sysfilesystem.h index f9f4c592..4f1cf1d5 100644 --- a/libs/SDL3/src/filesystem/SDL_sysfilesystem.h +++ b/libs/SDL3/src/filesystem/SDL_sysfilesystem.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -24,6 +24,7 @@ // return a string that we can SDL_free(). It will be cached at the higher level. extern char *SDL_SYS_GetBasePath(void); +extern char *SDL_SYS_GetExeName(void); extern char *SDL_SYS_GetPrefPath(const char *org, const char *app); extern char *SDL_SYS_GetUserFolder(SDL_Folder folder); extern char *SDL_SYS_GetCurrentDirectory(void); diff --git a/libs/SDL3/src/filesystem/android/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/android/SDL_sysfilesystem.c index bb424098..89cd0ce1 100644 --- a/libs/SDL3/src/filesystem/android/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/android/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -26,14 +26,16 @@ // System dependent filesystem routines #include "../SDL_sysfilesystem.h" - -#include +#include "../../core/android/SDL_android.h" char *SDL_SYS_GetBasePath(void) { - // The current working directory is / on Android - SDL_Unsupported(); - return NULL; + return SDL_strdup("./"); +} + +char *SDL_SYS_GetExeName(void) +{ + return SDL_GetAndroidPackageName(); } char *SDL_SYS_GetPrefPath(const char *org, const char *app) diff --git a/libs/SDL3/src/filesystem/cocoa/SDL_sysfilesystem.m b/libs/SDL3/src/filesystem/cocoa/SDL_sysfilesystem.m index 6ecef5df..4cd9a563 100644 --- a/libs/SDL3/src/filesystem/cocoa/SDL_sysfilesystem.m +++ b/libs/SDL3/src/filesystem/cocoa/SDL_sysfilesystem.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -47,7 +47,7 @@ char *SDL_SYS_GetBasePath(void) } else if (SDL_strcasecmp(baseType, "parent") == 0) { base = [[[bundle bundlePath] stringByDeletingLastPathComponent] fileSystemRepresentation]; } else { - // this returns the exedir for non-bundled and the resourceDir for bundled apps + // this returns the exedir for non-bundled and the resourceDir for bundled apps base = [[bundle resourcePath] fileSystemRepresentation]; } @@ -63,20 +63,34 @@ char *SDL_SYS_GetBasePath(void) } } +char *SDL_SYS_GetExeName(void) +{ + @autoreleasepool { + NSBundle *bundle = [NSBundle mainBundle]; + const char *name = [[[bundle infoDictionary] objectForKey:@"CFBundleIdentifier"] UTF8String]; + if (!name) { + name = [[[bundle infoDictionary] objectForKey:@"CFBundleDisplayName"] UTF8String]; + if (!name) { + name = [[[bundle infoDictionary] objectForKey:@"CFBundleName"] UTF8String]; + if (!name) { + name = [[[bundle infoDictionary] objectForKey:@"CFBundleExecutable"] UTF8String]; + if (!name) { + name = [[[NSProcessInfo processInfo] processName] UTF8String]; // oh well. + } + } + } + } + + return name ? SDL_strdup(name) : NULL; + } +} + char *SDL_SYS_GetPrefPath(const char *org, const char *app) { @autoreleasepool { char *result = NULL; NSArray *array; - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - if (!org) { - org = ""; - } - #ifndef SDL_PLATFORM_TVOS array = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); #else @@ -106,13 +120,12 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) const size_t len = SDL_strlen(base) + SDL_strlen(org) + SDL_strlen(app) + 4; result = (char *)SDL_malloc(len); if (result != NULL) { - char *ptr; if (*org) { SDL_snprintf(result, len, "%s/%s/%s/", base, org, app); } else { SDL_snprintf(result, len, "%s/%s/", base, app); } - for (ptr = result + 1; *ptr; ptr++) { + for (char *ptr = result + 1; *ptr; ptr++) { if (*ptr == '/') { *ptr = '\0'; mkdir(result, 0700); diff --git a/libs/SDL3/src/filesystem/dummy/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/dummy/SDL_sysfilesystem.c index 5634c702..2df8ec4f 100644 --- a/libs/SDL3/src/filesystem/dummy/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/dummy/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -33,6 +33,12 @@ char *SDL_SYS_GetBasePath(void) return NULL; } +char *SDL_SYS_GetExeName(void) +{ + SDL_Unsupported(); + return NULL; +} + char *SDL_SYS_GetPrefPath(const char *org, const char *app) { SDL_Unsupported(); @@ -47,8 +53,12 @@ char *SDL_SYS_GetUserFolder(SDL_Folder folder) char *SDL_SYS_GetCurrentDirectory(void) { - SDL_Unsupported(); - return NULL; + const char *base = SDL_GetBasePath(); + if (!base) { + return NULL; + } + + return SDL_strdup(base); } #endif // SDL_FILESYSTEM_DUMMY || SDL_FILESYSTEM_DISABLED diff --git a/libs/SDL3/src/filesystem/dummy/SDL_sysfsops.c b/libs/SDL3/src/filesystem/dummy/SDL_sysfsops.c index d8553e91..ce8ca29e 100644 --- a/libs/SDL3/src/filesystem/dummy/SDL_sysfsops.c +++ b/libs/SDL3/src/filesystem/dummy/SDL_sysfsops.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/filesystem/emscripten/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/emscripten/SDL_sysfilesystem.c index 29dc0535..54dcf082 100644 --- a/libs/SDL3/src/filesystem/emscripten/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/emscripten/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -37,31 +37,30 @@ char *SDL_SYS_GetBasePath(void) return SDL_strdup("/"); } +char *SDL_SYS_GetExeName(void) +{ + return NULL; // no EXE name on this system. +} + char *SDL_SYS_GetPrefPath(const char *org, const char *app) { - const char *append = "/libsdl/"; + #ifdef SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING + const char *append = SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING; + #else + const char *append = "/libsdl"; + #endif char *result; char *ptr = NULL; - size_t len = 0; - - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - if (!org) { - org = ""; - } - - len = SDL_strlen(append) + SDL_strlen(org) + SDL_strlen(app) + 3; + const size_t len = SDL_strlen(append) + SDL_strlen(org) + SDL_strlen(app) + 4; result = (char *)SDL_malloc(len); if (!result) { return NULL; } if (*org) { - SDL_snprintf(result, len, "%s%s/%s/", append, org, app); + SDL_snprintf(result, len, "%s/%s/%s/", append, org, app); } else { - SDL_snprintf(result, len, "%s%s/", append, app); + SDL_snprintf(result, len, "%s/%s/", append, app); } for (ptr = result + 1; *ptr; ptr++) { diff --git a/libs/SDL3/src/filesystem/gdk/SDL_sysfilesystem.cpp b/libs/SDL3/src/filesystem/gdk/SDL_sysfilesystem.cpp index 9a97f187..3c66550e 100644 --- a/libs/SDL3/src/filesystem/gdk/SDL_sysfilesystem.cpp +++ b/libs/SDL3/src/filesystem/gdk/SDL_sysfilesystem.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -33,53 +33,34 @@ extern "C" { #include #include -char * -SDL_SYS_GetBasePath(void) +char *SDL_SYS_GetBasePath(void) { - /* NOTE: This function is a UTF8 version of the Win32 SDL_GetBasePath()! - * The GDK actually _recommends_ the 'A' functions over the 'W' functions :o - */ - DWORD buflen = 128; - CHAR *path = NULL; - DWORD len = 0; - int i; - - while (true) { - void *ptr = SDL_realloc(path, buflen * sizeof(CHAR)); - if (!ptr) { - SDL_free(path); - return NULL; - } - - path = (CHAR *)ptr; - - len = GetModuleFileNameA(NULL, path, buflen); - // if it truncated, then len >= buflen - 1 - // if there was enough room (or failure), len < buflen - 1 - if (len < buflen - 1) { - break; - } - - // buffer too small? Try again. - buflen *= 2; + char *path = WIN_GetModulePath(NULL); // look up full path of the current process's EXE file. + if (!path) { + return NULL; // error message was already set. } - if (len == 0) { - SDL_free(path); - WIN_SetError("Couldn't locate our .exe"); - return NULL; + char *ptr = SDL_strrchr(path, '\\'); + SDL_assert(ptr != NULL); // Should have been an absolute path. + + ptr[1] = '\0'; // chop off filename, leave '\\'. + + ptr = (char *) SDL_realloc(path, ((size_t) (ptr - path)) + 2); // try to shrink this allocation down a little. + return ptr ? ptr : path; // return shrunk buffer if shrink worked out, unchanged original buffer if not. +} + +char *SDL_SYS_GetExeName(void) +{ + char *path = WIN_GetModulePath(NULL); // look up full path of the current process's EXE file. + if (!path) { + return NULL; // error message was already set. } - for (i = len - 1; i > 0; i--) { - if (path[i] == '\\') { - break; - } - } - - SDL_assert(i > 0); // Should have been an absolute path. - path[i + 1] = '\0'; // chop off filename. - - return path; + char *ptr = SDL_strrchr(path, '\\'); + const size_t slen = SDL_strlen(ptr); // counts null terminator because we're still sitting on path separator. + SDL_memmove(path, ptr + 1, slen); // move filename string to start of SDL_realloc'd region. + ptr = (char *) SDL_realloc(path, slen); // try to shrink this allocation down a little. + return ptr ? ptr : path; // return shrunk buffer if shrink worked out, unchanged original buffer if not. } char *SDL_SYS_GetPrefPath(const char *org, const char *app) @@ -90,11 +71,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) HRESULT result; const char *csid = SDL_GetHint("SDL_GDK_SERVICE_CONFIGURATION_ID"); - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - // This should be set before calling SDL_GetPrefPath! if (!csid) { SDL_LogWarn(SDL_LOG_CATEGORY_SYSTEM, "Set SDL_GDK_SERVICE_CONFIGURATION_ID before calling SDL_GetPrefPath!"); @@ -142,9 +118,12 @@ char *SDL_SYS_GetUserFolder(SDL_Folder folder) return NULL; } -// TODO char *SDL_SYS_GetCurrentDirectory(void) { - SDL_Unsupported(); - return NULL; + const char *base = SDL_GetBasePath(); + if (!base) { + return NULL; + } + + return SDL_strdup(base); } diff --git a/libs/SDL3/src/filesystem/haiku/SDL_sysfilesystem.cc b/libs/SDL3/src/filesystem/haiku/SDL_sysfilesystem.cc index af8b5ab4..7a051135 100644 --- a/libs/SDL3/src/filesystem/haiku/SDL_sysfilesystem.cc +++ b/libs/SDL3/src/filesystem/haiku/SDL_sysfilesystem.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -44,6 +44,7 @@ char *SDL_SYS_GetBasePath(void) return NULL; } + // !!! FIXME: if find_path promises an absolute path, can we dump this and just do SDL_strrchr(name, '/')? BEntry entry(name, true); BPath path; status_t rc = entry.GetPath(&path); // (path) now has binary's path. @@ -64,6 +65,15 @@ char *SDL_SYS_GetBasePath(void) return result; } +char *SDL_SYS_GetExeName(void) +{ + char name[MAXPATHLEN]; + if (find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, name, sizeof(name)) != B_OK) { + return NULL; + } + char *ptr = SDL_strrchr(name, '/'); + return SDL_strdup(ptr ? ptr + 1 : name); +} char *SDL_SYS_GetPrefPath(const char *org, const char *app) { @@ -72,14 +82,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) const char *append = "/config/settings/"; size_t len = SDL_strlen(home); - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - if (!org) { - org = ""; - } - if (!len || (home[len - 1] == '/')) { ++append; // home empty or ends with separator, skip the one from append } diff --git a/libs/SDL3/src/filesystem/n3ds/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/n3ds/SDL_sysfilesystem.c index 8386a91c..8a129c71 100644 --- a/libs/SDL3/src/filesystem/n3ds/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/n3ds/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -40,14 +40,14 @@ char *SDL_SYS_GetBasePath(void) return base_path; } +char *SDL_SYS_GetExeName(void) +{ + return NULL; // there isn't an "exe name" on this platform. +} + char *SDL_SYS_GetPrefPath(const char *org, const char *app) { char *pref_path = NULL; - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - pref_path = MakePrefPath(app); if (!pref_path) { return NULL; diff --git a/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.c index bc33a2af..8ce3ee15 100644 --- a/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2024 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -21,6 +21,7 @@ #include "SDL_internal.h" extern void NGAGE_GetAppPath(char *path); +extern void NGAGE_GetExeName(char *path); char *SDL_SYS_GetBasePath(void) { @@ -30,13 +31,20 @@ char *SDL_SYS_GetBasePath(void) return base_path; } +char *SDL_SYS_GetExeName(void) +{ + char exe_name[512]; + NGAGE_GetExeName(exe_name); + return SDL_strdup(exe_name); +} + 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) + char *pref_path = NULL; + if (SDL_asprintf(&pref_path, "C:/System/Apps/%s/%s/", org ? org : "SDL_App", app) < 0) { return NULL; - else - return pref_path; + } + return pref_path; } char *SDL_SYS_GetUserFolder(SDL_Folder folder) diff --git a/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.cpp b/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.cpp index 1c2b6aed..415fe55c 100644 --- a/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.cpp +++ b/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2024 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -63,6 +63,23 @@ void NGAGE_GetAppPath(char *path) } } +void NGAGE_GetExeName(char *path) +{ + TBuf<512> aPath; + + TFileName fullExePath = RProcess().FileName(); + + TParsePtrC parser(fullExePath); + aPath.Copy(parser.NameAndExt()); + + 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'; +} + #ifdef __cplusplus } #endif diff --git a/libs/SDL3/src/filesystem/posix/SDL_sysfsops.c b/libs/SDL3/src/filesystem/posix/SDL_sysfsops.c index 015b8d4b..fd428762 100644 --- a/libs/SDL3/src/filesystem/posix/SDL_sysfsops.c +++ b/libs/SDL3/src/filesystem/posix/SDL_sysfsops.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -35,30 +35,70 @@ #include #include +#ifdef SDL_PLATFORM_ANDROID +#include "../../core/android/SDL_android.h" +#endif + + bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata) { + char *apath = NULL; // absolute path (for Android, iOS, etc). Overrides `path`. + +#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_IOS) + if (*path != '/') { + #ifdef SDL_PLATFORM_ANDROID + SDL_asprintf(&apath, "%s/%s", SDL_GetAndroidInternalStoragePath(), path); + #elif defined(SDL_PLATFORM_IOS) + char *base = SDL_GetPrefPath("", ""); + if (!base) { + return false; + } + + SDL_asprintf(&apath, "%s%s", base, path); + SDL_free(base); + #endif + + if (!apath) { + return false; + } + } +#elif 0 // this is just for testing that `apath` works when you aren't on iOS or Android. + if (*path != '/') { + char *c = SDL_SYS_GetCurrentDirectory(); + SDL_asprintf(&apath, "%s%s", c, path); + SDL_free(c); + if (!apath) { + return false; + } + } +#endif + char *pathwithsep = NULL; - int pathwithseplen = SDL_asprintf(&pathwithsep, "%s/", path); + int pathwithseplen = SDL_asprintf(&pathwithsep, "%s%s", apath ? apath : path, (apath ? *apath : *path) ? "/" : ""); + const size_t extralen = apath ? (SDL_strlen(apath) - SDL_strlen(path)) : 0; + SDL_free(apath); if ((pathwithseplen == -1) || (!pathwithsep)) { return false; } // trim down to a single path separator at the end, in case the caller added one or more. pathwithseplen--; - while ((pathwithseplen >= 0) && (pathwithsep[pathwithseplen] == '/')) { + while ((pathwithseplen > 0) && (pathwithsep[pathwithseplen - 1] == '/')) { pathwithsep[pathwithseplen--] = '\0'; } DIR *dir = opendir(pathwithsep); if (!dir) { +#ifdef SDL_PLATFORM_ANDROID // Maybe it's an asset...? + const bool retval = Android_JNI_EnumerateAssetDirectory(pathwithsep + extralen, cb, userdata); + SDL_free(pathwithsep); + return retval; +#else SDL_free(pathwithsep); return SDL_SetError("Can't open directory: %s", strerror(errno)); +#endif } - // make sure there's a path separator at the end now for the actual callback. - pathwithsep[++pathwithseplen] = '/'; - pathwithsep[++pathwithseplen] = '\0'; - SDL_EnumerationResult result = SDL_ENUM_CONTINUE; struct dirent *ent; while ((result == SDL_ENUM_CONTINUE) && ((ent = readdir(dir)) != NULL)) { @@ -66,7 +106,7 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback if ((SDL_strcmp(name, ".") == 0) || (SDL_strcmp(name, "..") == 0)) { continue; } - result = cb(userdata, pathwithsep, name); + result = cb(userdata, pathwithsep + extralen, name); } closedir(dir); @@ -78,7 +118,41 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback bool SDL_SYS_RemovePath(const char *path) { - int rc = remove(path); + int rc; + +#ifdef SDL_PLATFORM_ANDROID + if (*path == '/') { + rc = remove(path); + } else { + char *apath = NULL; + SDL_asprintf(&apath, "%s/%s", SDL_GetAndroidInternalStoragePath(), path); + if (!apath) { + return false; + } + rc = remove(apath); + SDL_free(apath); + } +#elif defined(SDL_PLATFORM_IOS) + if (*path == '/') { + rc = remove(path); + } else { + char *base = SDL_GetPrefPath("", ""); + if (!base) { + return false; + } + + char *apath = NULL; + SDL_asprintf(&apath, "%s%s", base, path); + SDL_free(base); + if (!apath) { + return false; + } + rc = remove(apath); + SDL_free(apath); + } +#else + rc = remove(path); +#endif if (rc < 0) { if (errno == ENOENT) { // It's already gone, this is a success @@ -91,7 +165,65 @@ bool SDL_SYS_RemovePath(const char *path) bool SDL_SYS_RenamePath(const char *oldpath, const char *newpath) { - if (rename(oldpath, newpath) < 0) { + int rc; + +#ifdef SDL_PLATFORM_ANDROID + char *aoldpath = NULL; + char *anewpath = NULL; + if (*oldpath != '/') { + SDL_asprintf(&aoldpath, "%s/%s", SDL_GetAndroidInternalStoragePath(), oldpath); + if (!aoldpath) { + return false; + } + oldpath = aoldpath; + } + if (*newpath != '/') { + SDL_asprintf(&anewpath, "%s/%s", SDL_GetAndroidInternalStoragePath(), newpath); + if (!anewpath) { + SDL_free(aoldpath); + return false; + } + newpath = anewpath; + } + rc = rename(oldpath, newpath); + SDL_free(aoldpath); + SDL_free(anewpath); +#elif defined(SDL_PLATFORM_IOS) + char *base = NULL; + if (*oldpath != '/' || *newpath != '/') { + base = SDL_GetPrefPath("", ""); + if (!base) { + return false; + } + } + + char *aoldpath = NULL; + char *anewpath = NULL; + if (*oldpath != '/') { + SDL_asprintf(&aoldpath, "%s%s", base, oldpath); + if (!aoldpath) { + SDL_free(base); + return false; + } + oldpath = aoldpath; + } + if (*newpath != '/') { + SDL_asprintf(&anewpath, "%s%s", base, newpath); + if (!anewpath) { + SDL_free(base); + SDL_free(aoldpath); + return false; + } + newpath = anewpath; + } + rc = rename(oldpath, newpath); + SDL_free(base); + SDL_free(aoldpath); + SDL_free(anewpath); +#else + rc = rename(oldpath, newpath); +#endif + if (rc < 0) { return SDL_SetError("Can't rename path: %s", strerror(errno)); } return true; @@ -154,7 +286,41 @@ done: bool SDL_SYS_CreateDirectory(const char *path) { - const int rc = mkdir(path, 0770); + int rc; + +#ifdef SDL_PLATFORM_ANDROID + if (*path == '/') { + rc = mkdir(path, 0770); + } else { + char *apath = NULL; + SDL_asprintf(&apath, "%s/%s", SDL_GetAndroidInternalStoragePath(), path); + if (!apath) { + return false; + } + rc = mkdir(apath, 0770); + SDL_free(apath); + } +#elif defined(SDL_PLATFORM_IOS) + if (*path == '/') { + rc = mkdir(path, 0770); + } else { + char *base = SDL_GetPrefPath("", ""); + if (!base) { + return false; + } + + char *apath = NULL; + SDL_asprintf(&apath, "%s%s", base, path); + SDL_free(base); + if (!apath) { + return false; + } + rc = mkdir(apath, 0770); + SDL_free(apath); + } +#else + rc = mkdir(path, 0770); +#endif if (rc < 0) { const int origerrno = errno; if (origerrno == EEXIST) { @@ -171,7 +337,48 @@ bool SDL_SYS_CreateDirectory(const char *path) bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info) { struct stat statbuf; - const int rc = stat(path, &statbuf); + int rc; + +#ifdef SDL_PLATFORM_ANDROID + if (*path == '/') { + rc = stat(path, &statbuf); + } else { + char *apath = NULL; + SDL_asprintf(&apath, "%s/%s", SDL_GetAndroidInternalStoragePath(), path); + if (!apath) { + return false; + } + rc = stat(apath, &statbuf); + SDL_free(apath); + } + if (rc < 0) { + return Android_JNI_GetAssetPathInfo(path, info); + } +#elif defined(SDL_PLATFORM_IOS) + if (*path == '/') { + rc = stat(path, &statbuf); + } else { + char *base = SDL_GetPrefPath("", ""); + if (!base) { + return false; + } + + char *apath = NULL; + SDL_asprintf(&apath, "%s%s", base, path); + SDL_free(base); + if (!apath) { + return false; + } + rc = stat(apath, &statbuf); + SDL_free(apath); + + if (rc < 0) { + rc = stat(path, &statbuf); + } + } +#else + rc = stat(path, &statbuf); +#endif if (rc < 0) { return SDL_SetError("Can't stat: %s", strerror(errno)); } else if (S_ISREG(statbuf.st_mode)) { @@ -203,7 +410,7 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info) return true; } -// Note that this isn't actually part of filesystem, not fsops, but everything that uses posix fsops uses this implementation, even with separate filesystem code. +// Note that this is actually part of filesystem, not fsops, but everything that uses posix fsops uses this implementation, even with separate filesystem code. char *SDL_SYS_GetCurrentDirectory(void) { size_t buflen = 64; @@ -243,4 +450,3 @@ char *SDL_SYS_GetCurrentDirectory(void) } #endif // SDL_FSOPS_POSIX - diff --git a/libs/SDL3/src/filesystem/ps2/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/ps2/SDL_sysfilesystem.c index ca69c2bd..ddc3c683 100644 --- a/libs/SDL3/src/filesystem/ps2/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/ps2/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -46,6 +46,11 @@ char *SDL_SYS_GetBasePath(void) return result; } +char *SDL_SYS_GetExeName(void) +{ + return NULL; // no EXE name on this system. +} + // Do a recursive mkdir of parents folders static void recursive_mkdir(const char *dir) { @@ -80,15 +85,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) char *result = NULL; size_t len; - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - - if (!org) { - org = ""; - } - const char *base = SDL_GetBasePath(); if (!base) { return NULL; @@ -102,7 +98,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) } else { SDL_snprintf(result, len, "%s%s/", base, app); } - recursive_mkdir(result); } diff --git a/libs/SDL3/src/filesystem/psp/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/psp/SDL_sysfilesystem.c index 4b40055b..dd828cf5 100644 --- a/libs/SDL3/src/filesystem/psp/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/psp/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -46,25 +46,20 @@ char *SDL_SYS_GetBasePath(void) return result; } +char *SDL_SYS_GetExeName(void) +{ + return NULL; // no EXE name on this system. +} + char *SDL_SYS_GetPrefPath(const char *org, const char *app) { char *result = NULL; - size_t len; - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - const char *base = SDL_GetBasePath(); if (!base) { return NULL; } - if (!org) { - org = ""; - } - - len = SDL_strlen(base) + SDL_strlen(org) + SDL_strlen(app) + 4; + const size_t len = SDL_strlen(base) + SDL_strlen(org) + SDL_strlen(app) + 4; result = (char *)SDL_malloc(len); if (result) { if (*org) { @@ -72,7 +67,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) } else { SDL_snprintf(result, len, "%s%s/", base, app); } - mkdir(result, 0755); } diff --git a/libs/SDL3/src/filesystem/riscos/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/riscos/SDL_sysfilesystem.c index 95ed80fd..b6168641 100644 --- a/libs/SDL3/src/filesystem/riscos/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/riscos/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -152,26 +152,40 @@ char *SDL_SYS_GetBasePath(void) return result; } +char *SDL_SYS_GetExeName(void) +{ + _kernel_swi_regs regs; + _kernel_oserror *error; + char *canon, *ptr, *retval; + + error = _kernel_swi(OS_GetEnv, ®s, ®s); + if (error) { + return NULL; + } + + canon = canonicalisePath((const char *)regs.r[0], "Run$Path"); + if (!canon) { + return NULL; + } + + // find filename. + ptr = SDL_strrchr(canon, '.'); + retval = SDL_strdup(ptr ? ptr + 1 : canon); + SDL_free(canon); + return retval; +} + char *SDL_SYS_GetPrefPath(const char *org, const char *app) { char *canon, *dir, *result; - size_t len; _kernel_oserror *error; - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - if (!org) { - org = ""; - } - canon = canonicalisePath("", "Run$Path"); if (!canon) { return NULL; } - len = SDL_strlen(canon) + SDL_strlen(org) + SDL_strlen(app) + 4; + const size_t len = SDL_strlen(canon) + SDL_strlen(org) + SDL_strlen(app) + 4; dir = (char *)SDL_malloc(len); if (!dir) { SDL_free(canon); diff --git a/libs/SDL3/src/filesystem/unix/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/unix/SDL_sysfilesystem.c index 858aaa2c..a23d67b3 100644 --- a/libs/SDL3/src/filesystem/unix/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/unix/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -78,7 +78,7 @@ static char *search_path_for_binary(const char *bin) char *envr; size_t alloc_size; char *exe = NULL; - char *start = envr; + char *start; char *ptr; if (!envr_real) { @@ -86,7 +86,7 @@ static char *search_path_for_binary(const char *bin) return NULL; } - envr = SDL_strdup(envr_real); + start = envr = SDL_strdup(envr_real); if (!envr) { return NULL; } @@ -122,7 +122,7 @@ static char *search_path_for_binary(const char *bin) } #endif -char *SDL_SYS_GetBasePath(void) +static char *GetExePath(void) { char *result = NULL; @@ -235,27 +235,42 @@ char *SDL_SYS_GetBasePath(void) /* If we had access to argv[0] here, we could check it for a path, or troll through $PATH looking for it, too. */ - if (result) { // chop off filename. - char *ptr = SDL_strrchr(result, '/'); - if (ptr) { - *(ptr + 1) = '\0'; - } else { // shouldn't happen, but just in case... - SDL_free(result); - result = NULL; - } - } - - if (result) { - // try to shrink buffer... - char *ptr = (char *)SDL_realloc(result, SDL_strlen(result) + 1); - if (ptr) { - result = ptr; // oh well if it failed. - } - } - return result; } +char *SDL_SYS_GetBasePath(void) +{ + char *path = GetExePath(); + if (!path) { + return NULL; + } + + char *ptr = SDL_strrchr(path, '/'); + SDL_assert(ptr != NULL); // Should have been an absolute path. + + ptr[1] = '\0'; // chop off filename, leave '/'. + + ptr = (char *) SDL_realloc(path, ((size_t) (ptr - path)) + 2); // try to shrink this allocation down a little. + return ptr ? ptr : path; // return shrunk buffer if shrink worked out, unchanged original buffer if not. +} + + +char *SDL_SYS_GetExeName(void) +{ + char *path = GetExePath(); + if (!path) { + return NULL; + } + + char *ptr = SDL_strrchr(path, '/'); + SDL_assert(ptr != NULL); // Should have been an absolute path. + const size_t slen = SDL_strlen(ptr); // counts null terminator because we're still sitting on path separator. + SDL_memmove(path, ptr + 1, slen); // move filename string to start of SDL_realloc'd region. + ptr = (char *) SDL_realloc(path, slen); // try to shrink this allocation down a little. + return ptr ? ptr : path; // return shrunk buffer if shrink worked out, unchanged original buffer if not. +} + + char *SDL_SYS_GetPrefPath(const char *org, const char *app) { /* @@ -269,15 +284,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) const char *append; char *result = NULL; char *ptr = NULL; - size_t len = 0; - - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - if (!org) { - org = ""; - } if (!envr) { // You end up with "$HOME/.local/share/Game Name 2" @@ -292,7 +298,7 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) append = "/"; } - len = SDL_strlen(envr); + size_t len = SDL_strlen(envr); if (envr[len - 1] == '/') { append += 1; } diff --git a/libs/SDL3/src/filesystem/vita/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/vita/SDL_sysfilesystem.c index 8b65e8ae..4910000a 100644 --- a/libs/SDL3/src/filesystem/vita/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/vita/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -41,24 +41,17 @@ char *SDL_SYS_GetBasePath(void) return SDL_strdup("app0:/"); } +char *SDL_SYS_GetExeName(void) +{ + return NULL; // no EXE name on this system. +} + char *SDL_SYS_GetPrefPath(const char *org, const char *app) { const char *envr = "ux0:/data/"; char *result = NULL; char *ptr = NULL; - size_t len = 0; - - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - if (!org) { - org = ""; - } - - len = SDL_strlen(envr); - - len += SDL_strlen(org) + SDL_strlen(app) + 3; + size_t len = SDL_strlen(envr) + SDL_strlen(org) + SDL_strlen(app) + 3; result = (char *)SDL_malloc(len); if (!result) { return NULL; diff --git a/libs/SDL3/src/filesystem/windows/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/windows/SDL_sysfilesystem.c index a4c033f0..dd836e25 100644 --- a/libs/SDL3/src/filesystem/windows/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/windows/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -45,51 +45,32 @@ DEFINE_GUID(SDL_FOLDERID_Videos, 0x18989B1D, 0x99B5, 0x455B, 0x84, 0x1C, 0xAB, 0 char *SDL_SYS_GetBasePath(void) { - DWORD buflen = 128; - WCHAR *path = NULL; - char *result = NULL; - DWORD len = 0; - int i; - - while (true) { - void *ptr = SDL_realloc(path, buflen * sizeof(WCHAR)); - if (!ptr) { - SDL_free(path); - return NULL; - } - - path = (WCHAR *)ptr; - - len = GetModuleFileNameW(NULL, path, buflen); - // if it truncated, then len >= buflen - 1 - // if there was enough room (or failure), len < buflen - 1 - if (len < buflen - 1) { - break; - } - - // buffer too small? Try again. - buflen *= 2; + char *path = WIN_GetModulePath(NULL); // look up full path of the current process's EXE file. + if (!path) { + return NULL; // error message was already set. } - if (len == 0) { - SDL_free(path); - WIN_SetError("Couldn't locate our .exe"); - return NULL; + char *ptr = SDL_strrchr(path, '\\'); + SDL_assert(ptr != NULL); // Should have been an absolute path. + + ptr[1] = '\0'; // chop off filename, leave '\\'. + + ptr = (char *) SDL_realloc(path, ((size_t) (ptr - path)) + 2); // try to shrink this allocation down a little. + return ptr ? ptr : path; // return shrunk buffer if shrink worked out, unchanged original buffer if not. +} + +char *SDL_SYS_GetExeName(void) +{ + char *path = WIN_GetModulePath(NULL); // look up full path of the current process's EXE file. + if (!path) { + return NULL; // error message was already set. } - for (i = len - 1; i > 0; i--) { - if (path[i] == '\\') { - break; - } - } - - SDL_assert(i > 0); // Should have been an absolute path. - path[i + 1] = '\0'; // chop off filename. - - result = WIN_StringToUTF8W(path); - SDL_free(path); - - return result; + char *ptr = SDL_strrchr(path, '\\'); + const size_t slen = SDL_strlen(ptr); // counts null terminator because we're still sitting on path separator. + SDL_memmove(path, ptr + 1, slen); // move filename string to start of SDL_realloc'd region. + ptr = (char *) SDL_realloc(path, slen); // try to shrink this allocation down a little. + return ptr ? ptr : path; // return shrunk buffer if shrink worked out, unchanged original buffer if not. } char *SDL_SYS_GetPrefPath(const char *org, const char *app) @@ -110,14 +91,6 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) size_t new_wpath_len = 0; BOOL api_result = FALSE; - if (!app) { - SDL_InvalidParamError("app"); - return NULL; - } - if (!org) { - org = ""; - } - hr = SHGetFolderPathW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, path); if (!SUCCEEDED(hr)) { WIN_SetErrorFromHRESULT("Couldn't locate our prefpath", hr); diff --git a/libs/SDL3/src/filesystem/windows/SDL_sysfsops.c b/libs/SDL3/src/filesystem/windows/SDL_sysfsops.c index 9c48ba95..f0fc0fd9 100644 --- a/libs/SDL3/src/filesystem/windows/SDL_sysfsops.c +++ b/libs/SDL3/src/filesystem/windows/SDL_sysfsops.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -29,6 +29,10 @@ #include "../../core/windows/SDL_windows.h" #include "../SDL_sysfilesystem.h" +#ifndef COPY_FILE_NO_BUFFERING +#define COPY_FILE_NO_BUFFERING 0x00001000 +#endif + bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata) { SDL_EnumerationResult result = SDL_ENUM_CONTINUE; @@ -53,7 +57,7 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback // trim down to a single path separator at the end, in case the caller added one or more. patternlen--; - while ((patternlen >= 0) && ((pattern[patternlen] == '\\') || (pattern[patternlen] == '/'))) { + while ((patternlen > 0) && ((pattern[patternlen] == '\\') || (pattern[patternlen] == '/'))) { pattern[patternlen--] ='\0'; } pattern[++patternlen] = '\\'; diff --git a/libs/SDL3/src/gpu/SDL_gpu.c b/libs/SDL3/src/gpu/SDL_gpu.c index 39cf2eb8..5e6d2aa9 100644 --- a/libs/SDL3/src/gpu/SDL_gpu.c +++ b/libs/SDL3/src/gpu/SDL_gpu.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -23,7 +23,7 @@ // FIXME: This could probably use SDL_ObjectValid #define CHECK_DEVICE_MAGIC(device, retval) \ - if (device == NULL) { \ + CHECK_PARAM(device == NULL) { \ SDL_SetError("Invalid GPU device"); \ return retval; \ } @@ -61,6 +61,10 @@ return; \ } +#if 0 +// The below validation is too aggressive, since there are advanced situations +// where this is legal. This is being temporarily disabled for further review. +// See: https://github.com/libsdl-org/SDL/issues/13871 #define CHECK_SAMPLER_TEXTURES \ RenderPass *rp = (RenderPass *)render_pass; \ for (Uint32 color_target_index = 0; color_target_index < rp->num_color_targets; color_target_index += 1) { \ @@ -92,6 +96,10 @@ SDL_assert_release(!"Texture cannot be simultaneously bound as a depth stencil target and a storage texture!"); \ } \ } +#else +#define CHECK_SAMPLER_TEXTURES +#define CHECK_STORAGE_TEXTURES +#endif #define CHECK_GRAPHICS_PIPELINE_BOUND \ if (!((RenderPass *)render_pass)->graphics_pipeline) { \ @@ -310,11 +318,11 @@ static const SDL_GPUBootstrap *backends[] = { #ifdef SDL_GPU_METAL &MetalDriver, #endif -#ifdef SDL_GPU_VULKAN - &VulkanDriver, -#endif #ifdef SDL_GPU_D3D12 &D3D12Driver, +#endif +#ifdef SDL_GPU_VULKAN + &VulkanDriver, #endif NULL }; @@ -375,6 +383,7 @@ SDL_GPUGraphicsPipeline *SDL_GPU_FetchBlitPipeline( } else { blit_pipeline_create_info.fragment_shader = blit_from_2d_shader; } + blit_pipeline_create_info.rasterizer_state.enable_depth_clip = device->default_enable_depth_clip; blit_pipeline_create_info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1; blit_pipeline_create_info.multisample_state.enable_mask = false; @@ -711,6 +720,7 @@ SDL_GPUDevice *SDL_CreateGPUDeviceWithProperties(SDL_PropertiesID props) selectedBackend = SDL_GPUSelectBackend(props); if (selectedBackend != NULL) { + SDL_DebugLogBackend("gpu", selectedBackend->name); debug_mode = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, true); preferLowPower = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, false); @@ -718,6 +728,15 @@ SDL_GPUDevice *SDL_CreateGPUDeviceWithProperties(SDL_PropertiesID props) if (result != NULL) { result->backend = selectedBackend->name; result->debug_mode = debug_mode; + if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, true)) { + result->default_enable_depth_clip = false; + } else { + result->default_enable_depth_clip = true; + result->validate_feature_depth_clamp_disabled = true; + } + if (!SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN, true)) { + result->validate_feature_anisotropy_disabled = true; + } } } return result; @@ -730,7 +749,6 @@ SDL_GPUDevice *SDL_CreateGPUDeviceWithProperties(SDL_PropertiesID props) void SDL_DestroyGPUDevice(SDL_GPUDevice *device) { CHECK_DEVICE_MAGIC(device, ); - device->DestroyDevice(device); } @@ -745,7 +763,7 @@ int SDL_GetNumGPUDrivers(void) const char * SDL_GetGPUDriver(int index) { - if (index < 0 || index >= SDL_GetNumGPUDrivers()) { + CHECK_PARAM(index < 0 || index >= SDL_GetNumGPUDrivers()) { SDL_InvalidParamError("index"); return NULL; } @@ -759,21 +777,18 @@ const char * SDL_GetGPUDriver(int index) const char * SDL_GetGPUDeviceDriver(SDL_GPUDevice *device) { CHECK_DEVICE_MAGIC(device, NULL); - return device->backend; } SDL_GPUShaderFormat SDL_GetGPUShaderFormats(SDL_GPUDevice *device) { CHECK_DEVICE_MAGIC(device, SDL_GPU_SHADERFORMAT_INVALID); - return device->shader_formats; } SDL_PropertiesID SDL_GetGPUDeviceProperties(SDL_GPUDevice *device) { CHECK_DEVICE_MAGIC(device, 0); - return device->GetDeviceProperties(device); } @@ -950,6 +965,7 @@ SDL_GPUComputePipeline *SDL_CreateGPUComputePipeline( const SDL_GPUComputePipelineCreateInfo *createinfo) { CHECK_DEVICE_MAGIC(device, NULL); + if (createinfo == NULL) { SDL_InvalidParamError("createinfo"); return NULL; @@ -965,13 +981,35 @@ SDL_GPUComputePipeline *SDL_CreateGPUComputePipeline( return NULL; } if (createinfo->num_readwrite_storage_textures > MAX_COMPUTE_WRITE_TEXTURES) { + SDL_COMPILE_TIME_ASSERT(compute_write_textures, MAX_COMPUTE_WRITE_TEXTURES == 8); SDL_assert_release(!"Compute pipeline write-only texture count cannot be higher than 8!"); return NULL; } if (createinfo->num_readwrite_storage_buffers > MAX_COMPUTE_WRITE_BUFFERS) { + SDL_COMPILE_TIME_ASSERT(compute_write_buffers, MAX_COMPUTE_WRITE_BUFFERS == 8); SDL_assert_release(!"Compute pipeline write-only buffer count cannot be higher than 8!"); return NULL; } + if (createinfo->num_samplers > MAX_TEXTURE_SAMPLERS_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(compute_texture_samplers, MAX_TEXTURE_SAMPLERS_PER_STAGE == 16); + SDL_assert_release(!"Compute pipeline sampler count cannot be higher than 16!"); + return NULL; + } + if (createinfo->num_readonly_storage_textures > MAX_STORAGE_TEXTURES_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(compute_storage_textures, MAX_STORAGE_TEXTURES_PER_STAGE == 8); + SDL_assert_release(!"Compute pipeline readonly storage texture count cannot be higher than 8!"); + return NULL; + } + if (createinfo->num_readonly_storage_buffers > MAX_STORAGE_BUFFERS_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(compute_storage_buffers, MAX_STORAGE_BUFFERS_PER_STAGE == 8); + SDL_assert_release(!"Compute pipeline readonly storage buffer count cannot be higher than 8!"); + return NULL; + } + if (createinfo->num_uniform_buffers > MAX_UNIFORM_BUFFERS_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(compute_uniform_buffers, MAX_UNIFORM_BUFFERS_PER_STAGE == 4); + SDL_assert_release(!"Compute pipeline uniform buffer count cannot be higher than 4!"); + return NULL; + } if (createinfo->threadcount_x == 0 || createinfo->threadcount_y == 0 || createinfo->threadcount_z == 0) { @@ -990,7 +1028,8 @@ SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline( const SDL_GPUGraphicsPipelineCreateInfo *graphicsPipelineCreateInfo) { CHECK_DEVICE_MAGIC(device, NULL); - if (graphicsPipelineCreateInfo == NULL) { + + CHECK_PARAM(graphicsPipelineCreateInfo == NULL) { SDL_InvalidParamError("graphicsPipelineCreateInfo"); return NULL; } @@ -1058,6 +1097,7 @@ SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline( return NULL; } if (graphicsPipelineCreateInfo->vertex_input_state.num_vertex_buffers > MAX_VERTEX_BUFFERS) { + SDL_COMPILE_TIME_ASSERT(vertex_buffers, MAX_VERTEX_BUFFERS == 16); SDL_assert_release(!"The number of vertex buffer descriptions in a vertex input state must not exceed 16!"); return NULL; } @@ -1066,6 +1106,7 @@ SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline( return NULL; } if (graphicsPipelineCreateInfo->vertex_input_state.num_vertex_attributes > MAX_VERTEX_ATTRIBUTES) { + SDL_COMPILE_TIME_ASSERT(vertex_attributes, MAX_VERTEX_ATTRIBUTES == 16); SDL_assert_release(!"The number of vertex attributes in a vertex input state must not exceed 16!"); return NULL; } @@ -1105,6 +1146,12 @@ SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline( CHECK_STENCILOP_ENUM_INVALID(stencil_state->pass_op, NULL) CHECK_STENCILOP_ENUM_INVALID(stencil_state->depth_fail_op, NULL) } + + if (device->validate_feature_depth_clamp_disabled && + !graphicsPipelineCreateInfo->rasterizer_state.enable_depth_clip) { + SDL_assert_release(!"Rasterizer state enable_depth_clip must be set to true (FEATURE_DEPTH_CLAMPING disabled)"); + return NULL; + } } return device->CreateGraphicsPipeline( @@ -1117,11 +1164,20 @@ SDL_GPUSampler *SDL_CreateGPUSampler( const SDL_GPUSamplerCreateInfo *createinfo) { CHECK_DEVICE_MAGIC(device, NULL); - if (createinfo == NULL) { + + CHECK_PARAM(createinfo == NULL) { SDL_InvalidParamError("createinfo"); return NULL; } + if (device->debug_mode) { + if (device->validate_feature_anisotropy_disabled && + createinfo->enable_anisotropy) { + SDL_assert_release(!"enable_anisotropy must be set to false (FEATURE_ANISOTROPY disabled)"); + return NULL; + } + } + return device->CreateSampler( device->driverData, createinfo); @@ -1132,7 +1188,8 @@ SDL_GPUShader *SDL_CreateGPUShader( const SDL_GPUShaderCreateInfo *createinfo) { CHECK_DEVICE_MAGIC(device, NULL); - if (createinfo == NULL) { + + CHECK_PARAM(createinfo == NULL) { SDL_InvalidParamError("createinfo"); return NULL; } @@ -1146,6 +1203,26 @@ SDL_GPUShader *SDL_CreateGPUShader( SDL_assert_release(!"Incompatible shader format for GPU backend"); return NULL; } + if (createinfo->num_samplers > MAX_TEXTURE_SAMPLERS_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(shader_texture_samplers, MAX_TEXTURE_SAMPLERS_PER_STAGE == 16); + SDL_assert_release(!"Shader sampler count cannot be higher than 16!"); + return NULL; + } + if (createinfo->num_storage_textures > MAX_STORAGE_TEXTURES_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(shader_storage_textures, MAX_STORAGE_TEXTURES_PER_STAGE == 8); + SDL_assert_release(!"Shader storage texture count cannot be higher than 8!"); + return NULL; + } + if (createinfo->num_storage_buffers > MAX_STORAGE_BUFFERS_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(shader_storage_buffers, MAX_STORAGE_BUFFERS_PER_STAGE == 8); + SDL_assert_release(!"Shader storage buffer count cannot be higher than 8!"); + return NULL; + } + if (createinfo->num_uniform_buffers > MAX_UNIFORM_BUFFERS_PER_STAGE) { + SDL_COMPILE_TIME_ASSERT(shader_uniform_buffers, MAX_UNIFORM_BUFFERS_PER_STAGE == 4); + SDL_assert_release(!"Shader uniform buffer count cannot be higher than 4!"); + return NULL; + } } return device->CreateShader( @@ -1158,7 +1235,8 @@ SDL_GPUTexture *SDL_CreateGPUTexture( const SDL_GPUTextureCreateInfo *createinfo) { CHECK_DEVICE_MAGIC(device, NULL); - if (createinfo == NULL) { + + CHECK_PARAM(createinfo == NULL) { SDL_InvalidParamError("createinfo"); return NULL; } @@ -1180,6 +1258,11 @@ SDL_GPUTexture *SDL_CreateGPUTexture( SDL_assert_release(!"For any texture: num_levels must be >= 1"); failed = true; } + if (createinfo->type == SDL_GPU_TEXTURETYPE_2D && createinfo->layer_count_or_depth != 1) + { + SDL_assert_release(!"2D textures must have a layer count of 1"); + failed = true; + } if ((createinfo->usage & SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ) && (createinfo->usage & SDL_GPU_TEXTUREUSAGE_SAMPLER)) { SDL_assert_release(!"For any texture: usage cannot contain both GRAPHICS_STORAGE_READ and SAMPLER"); failed = true; @@ -1266,10 +1349,6 @@ SDL_GPUTexture *SDL_CreateGPUTexture( } else { if (createinfo->type == SDL_GPU_TEXTURETYPE_2D_ARRAY) { // Array Texture Validation - if (createinfo->usage & SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET) { - SDL_assert_release(!"For array textures: usage must not contain DEPTH_STENCIL_TARGET"); - failed = true; - } if (createinfo->sample_count > SDL_GPU_SAMPLECOUNT_1) { SDL_assert_release(!"For array textures: sample_count must be SDL_GPU_SAMPLECOUNT_1"); failed = true; @@ -1300,11 +1379,18 @@ SDL_GPUBuffer *SDL_CreateGPUBuffer( const SDL_GPUBufferCreateInfo *createinfo) { CHECK_DEVICE_MAGIC(device, NULL); - if (createinfo == NULL) { + + CHECK_PARAM(createinfo == NULL) { SDL_InvalidParamError("createinfo"); return NULL; } + if (device->debug_mode) { + if (createinfo->size < 4) { + SDL_assert_release(!"Cannot create a buffer with size less than 4 bytes!"); + } + } + const char *debugName = SDL_GetStringProperty(createinfo->props, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, NULL); return device->CreateBuffer( @@ -1319,7 +1405,8 @@ SDL_GPUTransferBuffer *SDL_CreateGPUTransferBuffer( const SDL_GPUTransferBufferCreateInfo *createinfo) { CHECK_DEVICE_MAGIC(device, NULL); - if (createinfo == NULL) { + + CHECK_PARAM(createinfo == NULL) { SDL_InvalidParamError("createinfo"); return NULL; } @@ -1341,11 +1428,12 @@ void SDL_SetGPUBufferName( const char *text) { CHECK_DEVICE_MAGIC(device, ); - if (buffer == NULL) { + + CHECK_PARAM(buffer == NULL) { SDL_InvalidParamError("buffer"); return; } - if (text == NULL) { + CHECK_PARAM(text == NULL) { SDL_InvalidParamError("text"); } @@ -1361,11 +1449,12 @@ void SDL_SetGPUTextureName( const char *text) { CHECK_DEVICE_MAGIC(device, ); - if (texture == NULL) { + + CHECK_PARAM(texture == NULL) { SDL_InvalidParamError("texture"); return; } - if (text == NULL) { + CHECK_PARAM(text == NULL) { SDL_InvalidParamError("text"); } @@ -1379,11 +1468,11 @@ void SDL_InsertGPUDebugLabel( SDL_GPUCommandBuffer *command_buffer, const char *text) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } - if (text == NULL) { + CHECK_PARAM(text == NULL) { SDL_InvalidParamError("text"); return; } @@ -1401,11 +1490,11 @@ void SDL_PushGPUDebugGroup( SDL_GPUCommandBuffer *command_buffer, const char *name) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } - if (name == NULL) { + CHECK_PARAM(name == NULL) { SDL_InvalidParamError("name"); return; } @@ -1422,7 +1511,7 @@ void SDL_PushGPUDebugGroup( void SDL_PopGPUDebugGroup( SDL_GPUCommandBuffer *command_buffer) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } @@ -1442,7 +1531,8 @@ void SDL_ReleaseGPUTexture( SDL_GPUTexture *texture) { CHECK_DEVICE_MAGIC(device, ); - if (texture == NULL) { + + CHECK_PARAM(texture == NULL) { return; } @@ -1456,7 +1546,8 @@ void SDL_ReleaseGPUSampler( SDL_GPUSampler *sampler) { CHECK_DEVICE_MAGIC(device, ); - if (sampler == NULL) { + + CHECK_PARAM(sampler == NULL) { return; } @@ -1470,7 +1561,8 @@ void SDL_ReleaseGPUBuffer( SDL_GPUBuffer *buffer) { CHECK_DEVICE_MAGIC(device, ); - if (buffer == NULL) { + + CHECK_PARAM(buffer == NULL) { return; } @@ -1484,7 +1576,8 @@ void SDL_ReleaseGPUTransferBuffer( SDL_GPUTransferBuffer *transfer_buffer) { CHECK_DEVICE_MAGIC(device, ); - if (transfer_buffer == NULL) { + + CHECK_PARAM(transfer_buffer == NULL) { return; } @@ -1498,7 +1591,8 @@ void SDL_ReleaseGPUShader( SDL_GPUShader *shader) { CHECK_DEVICE_MAGIC(device, ); - if (shader == NULL) { + + CHECK_PARAM(shader == NULL) { return; } @@ -1512,7 +1606,8 @@ void SDL_ReleaseGPUComputePipeline( SDL_GPUComputePipeline *compute_pipeline) { CHECK_DEVICE_MAGIC(device, ); - if (compute_pipeline == NULL) { + + CHECK_PARAM(compute_pipeline == NULL) { return; } @@ -1526,7 +1621,8 @@ void SDL_ReleaseGPUGraphicsPipeline( SDL_GPUGraphicsPipeline *graphics_pipeline) { CHECK_DEVICE_MAGIC(device, ); - if (graphics_pipeline == NULL) { + + CHECK_PARAM(graphics_pipeline == NULL) { return; } @@ -1566,6 +1662,7 @@ SDL_GPUCommandBuffer *SDL_AcquireGPUCommandBuffer( commandBufferHeader->copy_pass.in_progress = false; commandBufferHeader->swapchain_texture_acquired = false; commandBufferHeader->submitted = false; + commandBufferHeader->ignore_render_pass_texture_validation = false; SDL_zeroa(commandBufferHeader->render_pass.vertex_sampler_bound); SDL_zeroa(commandBufferHeader->render_pass.vertex_storage_texture_bound); SDL_zeroa(commandBufferHeader->render_pass.vertex_storage_buffer_bound); @@ -1590,14 +1687,18 @@ void SDL_PushGPUVertexUniformData( const void *data, Uint32 length) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } - if (data == NULL) { + CHECK_PARAM(data == NULL) { SDL_InvalidParamError("data"); return; } + CHECK_PARAM(slot_index >= MAX_UNIFORM_BUFFERS_PER_STAGE) { + SDL_SetError("slot_index exceeds MAX_UNIFORM_BUFFERS_PER_STAGE"); + return; + } if (COMMAND_BUFFER_DEVICE->debug_mode) { CHECK_COMMAND_BUFFER @@ -1616,14 +1717,18 @@ void SDL_PushGPUFragmentUniformData( const void *data, Uint32 length) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } - if (data == NULL) { + CHECK_PARAM(data == NULL) { SDL_InvalidParamError("data"); return; } + CHECK_PARAM(slot_index >= MAX_UNIFORM_BUFFERS_PER_STAGE) { + SDL_SetError("slot_index exceeds MAX_UNIFORM_BUFFERS_PER_STAGE"); + return; + } if (COMMAND_BUFFER_DEVICE->debug_mode) { CHECK_COMMAND_BUFFER @@ -1642,14 +1747,18 @@ void SDL_PushGPUComputeUniformData( const void *data, Uint32 length) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } - if (data == NULL) { + CHECK_PARAM(data == NULL) { SDL_InvalidParamError("data"); return; } + CHECK_PARAM(slot_index >= MAX_UNIFORM_BUFFERS_PER_STAGE) { + SDL_SetError("slot_index exceeds MAX_UNIFORM_BUFFERS_PER_STAGE"); + return; + } if (COMMAND_BUFFER_DEVICE->debug_mode) { CHECK_COMMAND_BUFFER @@ -1672,16 +1781,16 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( { CommandBufferCommonHeader *commandBufferHeader; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return NULL; } - if (color_target_infos == NULL && num_color_targets > 0) { + CHECK_PARAM(color_target_infos == NULL && num_color_targets > 0) { SDL_InvalidParamError("color_target_infos"); return NULL; } - if (num_color_targets > MAX_COLOR_TARGET_BINDINGS) { + CHECK_PARAM(num_color_targets > MAX_COLOR_TARGET_BINDINGS) { SDL_SetError("num_color_targets exceeds MAX_COLOR_TARGET_BINDINGS"); return NULL; } @@ -1739,13 +1848,17 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( } if (depth_stencil_target_info != NULL) { - TextureCommonHeader *textureHeader = (TextureCommonHeader *)depth_stencil_target_info->texture; if (!(textureHeader->info.usage & SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)) { SDL_assert_release(!"Depth target must have been created with the DEPTH_STENCIL_TARGET usage flag!"); return NULL; } + if (textureHeader->info.layer_count_or_depth > 255) { + SDL_assert_release(!"Cannot bind a depth texture with more than 255 layers!"); + return NULL; + } + if (depth_stencil_target_info->cycle && (depth_stencil_target_info->load_op == SDL_GPU_LOADOP_LOAD || depth_stencil_target_info->stencil_load_op == SDL_GPU_LOADOP_LOAD)) { SDL_assert_release(!"Cannot cycle depth target when load op or stencil load op is LOAD!"); return NULL; @@ -1777,6 +1890,8 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( commandBufferHeader->render_pass.num_color_targets = num_color_targets; if (depth_stencil_target_info != NULL) { commandBufferHeader->render_pass.depth_stencil_target = depth_stencil_target_info->texture; + } else { + commandBufferHeader->render_pass.depth_stencil_target = NULL; } } @@ -1787,11 +1902,11 @@ void SDL_BindGPUGraphicsPipeline( SDL_GPURenderPass *render_pass, SDL_GPUGraphicsPipeline *graphics_pipeline) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (graphics_pipeline == NULL) { + CHECK_PARAM(graphics_pipeline == NULL) { SDL_InvalidParamError("graphics_pipeline"); return; } @@ -1810,11 +1925,11 @@ void SDL_SetGPUViewport( SDL_GPURenderPass *render_pass, const SDL_GPUViewport *viewport) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (viewport == NULL) { + CHECK_PARAM(viewport == NULL) { SDL_InvalidParamError("viewport"); return; } @@ -1832,11 +1947,11 @@ void SDL_SetGPUScissor( SDL_GPURenderPass *render_pass, const SDL_Rect *scissor) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (scissor == NULL) { + CHECK_PARAM(scissor == NULL) { SDL_InvalidParamError("scissor"); return; } @@ -1854,7 +1969,7 @@ void SDL_SetGPUBlendConstants( SDL_GPURenderPass *render_pass, SDL_FColor blend_constants) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } @@ -1872,7 +1987,7 @@ void SDL_SetGPUStencilReference( SDL_GPURenderPass *render_pass, Uint8 reference) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } @@ -1892,7 +2007,7 @@ void SDL_BindGPUVertexBuffers( const SDL_GPUBufferBinding *bindings, Uint32 num_bindings) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } @@ -1917,7 +2032,7 @@ void SDL_BindGPUIndexBuffer( const SDL_GPUBufferBinding *binding, SDL_GPUIndexElementSize index_element_size) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } @@ -1942,14 +2057,18 @@ void SDL_BindGPUVertexSamplers( const SDL_GPUTextureSamplerBinding *texture_sampler_bindings, Uint32 num_bindings) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (texture_sampler_bindings == NULL && num_bindings > 0) { + CHECK_PARAM(texture_sampler_bindings == NULL && num_bindings > 0) { SDL_InvalidParamError("texture_sampler_bindings"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_TEXTURE_SAMPLERS_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_TEXTURE_SAMPLERS_PER_STAGE"); + return; + } if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS @@ -1977,14 +2096,18 @@ void SDL_BindGPUVertexStorageTextures( SDL_GPUTexture *const *storage_textures, Uint32 num_bindings) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (storage_textures == NULL && num_bindings > 0) { + CHECK_PARAM(storage_textures == NULL && num_bindings > 0) { SDL_InvalidParamError("storage_textures"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_STORAGE_TEXTURES_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_STORAGE_TEXTURES_PER_STAGE"); + return; + } if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS @@ -2008,14 +2131,18 @@ void SDL_BindGPUVertexStorageBuffers( SDL_GPUBuffer *const *storage_buffers, Uint32 num_bindings) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (storage_buffers == NULL && num_bindings > 0) { + CHECK_PARAM(storage_buffers == NULL && num_bindings > 0) { SDL_InvalidParamError("storage_buffers"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_STORAGE_BUFFERS_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_STORAGE_BUFFERS_PER_STAGE"); + return; + } if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS @@ -2038,14 +2165,18 @@ void SDL_BindGPUFragmentSamplers( const SDL_GPUTextureSamplerBinding *texture_sampler_bindings, Uint32 num_bindings) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (texture_sampler_bindings == NULL && num_bindings > 0) { + CHECK_PARAM(texture_sampler_bindings == NULL && num_bindings > 0) { SDL_InvalidParamError("texture_sampler_bindings"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_TEXTURE_SAMPLERS_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_TEXTURE_SAMPLERS_PER_STAGE"); + return; + } if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS @@ -2072,14 +2203,18 @@ void SDL_BindGPUFragmentStorageTextures( SDL_GPUTexture *const *storage_textures, Uint32 num_bindings) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (storage_textures == NULL && num_bindings > 0) { + CHECK_PARAM(storage_textures == NULL && num_bindings > 0) { SDL_InvalidParamError("storage_textures"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_STORAGE_TEXTURES_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_STORAGE_TEXTURES_PER_STAGE"); + return; + } if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS @@ -2103,14 +2238,18 @@ void SDL_BindGPUFragmentStorageBuffers( SDL_GPUBuffer *const *storage_buffers, Uint32 num_bindings) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (storage_buffers == NULL && num_bindings > 0) { + CHECK_PARAM(storage_buffers == NULL && num_bindings > 0) { SDL_InvalidParamError("storage_buffers"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_STORAGE_BUFFERS_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_STORAGE_BUFFERS_PER_STAGE"); + return; + } if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS @@ -2135,7 +2274,7 @@ void SDL_DrawGPUIndexedPrimitives( Sint32 vertex_offset, Uint32 first_instance) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } @@ -2162,7 +2301,7 @@ void SDL_DrawGPUPrimitives( Uint32 first_vertex, Uint32 first_instance) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } @@ -2187,11 +2326,11 @@ void SDL_DrawGPUPrimitivesIndirect( Uint32 offset, Uint32 draw_count) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (buffer == NULL) { + CHECK_PARAM(buffer == NULL) { SDL_InvalidParamError("buffer"); return; } @@ -2215,11 +2354,11 @@ void SDL_DrawGPUIndexedPrimitivesIndirect( Uint32 offset, Uint32 draw_count) { - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } - if (buffer == NULL) { + CHECK_PARAM(buffer == NULL) { SDL_InvalidParamError("buffer"); return; } @@ -2240,14 +2379,14 @@ void SDL_DrawGPUIndexedPrimitivesIndirect( void SDL_EndGPURenderPass( SDL_GPURenderPass *render_pass) { - CommandBufferCommonHeader *commandBufferCommonHeader; - commandBufferCommonHeader = (CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER; - - if (render_pass == NULL) { + CHECK_PARAM(render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; } + CommandBufferCommonHeader *commandBufferCommonHeader; + commandBufferCommonHeader = (CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER; + if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS } @@ -2284,26 +2423,27 @@ SDL_GPUComputePass *SDL_BeginGPUComputePass( { CommandBufferCommonHeader *commandBufferHeader; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return NULL; } - if (storage_texture_bindings == NULL && num_storage_texture_bindings > 0) { + CHECK_PARAM(storage_texture_bindings == NULL && num_storage_texture_bindings > 0) { SDL_InvalidParamError("storage_texture_bindings"); return NULL; } - if (storage_buffer_bindings == NULL && num_storage_buffer_bindings > 0) { + CHECK_PARAM(storage_buffer_bindings == NULL && num_storage_buffer_bindings > 0) { SDL_InvalidParamError("storage_buffer_bindings"); return NULL; } - if (num_storage_texture_bindings > MAX_COMPUTE_WRITE_TEXTURES) { + CHECK_PARAM(num_storage_texture_bindings > MAX_COMPUTE_WRITE_TEXTURES) { SDL_InvalidParamError("num_storage_texture_bindings"); return NULL; } - if (num_storage_buffer_bindings > MAX_COMPUTE_WRITE_BUFFERS) { + CHECK_PARAM(num_storage_buffer_bindings > MAX_COMPUTE_WRITE_BUFFERS) { SDL_InvalidParamError("num_storage_buffer_bindings"); return NULL; } + if (COMMAND_BUFFER_DEVICE->debug_mode) { CHECK_COMMAND_BUFFER_RETURN_NULL CHECK_ANY_PASS_IN_PROGRESS("Cannot begin compute pass during another pass!", NULL) @@ -2357,11 +2497,11 @@ void SDL_BindGPUComputePipeline( SDL_GPUComputePass *compute_pass, SDL_GPUComputePipeline *compute_pipeline) { - if (compute_pass == NULL) { + CHECK_PARAM(compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; } - if (compute_pipeline == NULL) { + CHECK_PARAM(compute_pipeline == NULL) { SDL_InvalidParamError("compute_pipeline"); return; } @@ -2386,14 +2526,18 @@ void SDL_BindGPUComputeSamplers( const SDL_GPUTextureSamplerBinding *texture_sampler_bindings, Uint32 num_bindings) { - if (compute_pass == NULL) { + CHECK_PARAM(compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; } - if (texture_sampler_bindings == NULL && num_bindings > 0) { + CHECK_PARAM(texture_sampler_bindings == NULL && num_bindings > 0) { SDL_InvalidParamError("texture_sampler_bindings"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_TEXTURE_SAMPLERS_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_TEXTURE_SAMPLERS_PER_STAGE"); + return; + } if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS @@ -2416,14 +2560,18 @@ void SDL_BindGPUComputeStorageTextures( SDL_GPUTexture *const *storage_textures, Uint32 num_bindings) { - if (compute_pass == NULL) { + CHECK_PARAM(compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; } - if (storage_textures == NULL && num_bindings > 0) { + CHECK_PARAM(storage_textures == NULL && num_bindings > 0) { SDL_InvalidParamError("storage_textures"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_STORAGE_TEXTURES_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_STORAGE_TEXTURES_PER_STAGE"); + return; + } if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS @@ -2446,14 +2594,18 @@ void SDL_BindGPUComputeStorageBuffers( SDL_GPUBuffer *const *storage_buffers, Uint32 num_bindings) { - if (compute_pass == NULL) { + CHECK_PARAM(compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; } - if (storage_buffers == NULL && num_bindings > 0) { + CHECK_PARAM(storage_buffers == NULL && num_bindings > 0) { SDL_InvalidParamError("storage_buffers"); return; } + CHECK_PARAM(first_slot + num_bindings > MAX_STORAGE_BUFFERS_PER_STAGE) { + SDL_SetError("first_slot + num_bindings exceeds MAX_STORAGE_BUFFERS_PER_STAGE"); + return; + } if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS @@ -2476,7 +2628,7 @@ void SDL_DispatchGPUCompute( Uint32 groupcount_y, Uint32 groupcount_z) { - if (compute_pass == NULL) { + CHECK_PARAM(compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; } @@ -2499,7 +2651,7 @@ void SDL_DispatchGPUComputeIndirect( SDL_GPUBuffer *buffer, Uint32 offset) { - if (compute_pass == NULL) { + CHECK_PARAM(compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; } @@ -2521,7 +2673,7 @@ void SDL_EndGPUComputePass( { CommandBufferCommonHeader *commandBufferCommonHeader; - if (compute_pass == NULL) { + CHECK_PARAM(compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; } @@ -2536,7 +2688,7 @@ void SDL_EndGPUComputePass( if (COMPUTEPASS_DEVICE->debug_mode) { commandBufferCommonHeader = (CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER; commandBufferCommonHeader->compute_pass.in_progress = false; - commandBufferCommonHeader->compute_pass.compute_pipeline = false; + commandBufferCommonHeader->compute_pass.compute_pipeline = NULL; SDL_zeroa(commandBufferCommonHeader->compute_pass.sampler_bound); SDL_zeroa(commandBufferCommonHeader->compute_pass.read_only_storage_texture_bound); SDL_zeroa(commandBufferCommonHeader->compute_pass.read_only_storage_buffer_bound); @@ -2553,7 +2705,8 @@ void *SDL_MapGPUTransferBuffer( bool cycle) { CHECK_DEVICE_MAGIC(device, NULL); - if (transfer_buffer == NULL) { + + CHECK_PARAM(transfer_buffer == NULL) { SDL_InvalidParamError("transfer_buffer"); return NULL; } @@ -2569,7 +2722,8 @@ void SDL_UnmapGPUTransferBuffer( SDL_GPUTransferBuffer *transfer_buffer) { CHECK_DEVICE_MAGIC(device, ); - if (transfer_buffer == NULL) { + + CHECK_PARAM(transfer_buffer == NULL) { SDL_InvalidParamError("transfer_buffer"); return; } @@ -2586,7 +2740,7 @@ SDL_GPUCopyPass *SDL_BeginGPUCopyPass( { CommandBufferCommonHeader *commandBufferHeader; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return NULL; } @@ -2614,11 +2768,11 @@ void SDL_UploadToGPUTexture( const SDL_GPUTextureRegion *destination, bool cycle) { - if (copy_pass == NULL) { + CHECK_PARAM(copy_pass == NULL) { SDL_InvalidParamError("copy_pass"); return; } - if (source == NULL) { + CHECK_PARAM(source == NULL) { SDL_InvalidParamError("source"); return; } @@ -2652,15 +2806,15 @@ void SDL_UploadToGPUBuffer( const SDL_GPUBufferRegion *destination, bool cycle) { - if (copy_pass == NULL) { + CHECK_PARAM(copy_pass == NULL) { SDL_InvalidParamError("copy_pass"); return; } - if (source == NULL) { + CHECK_PARAM(source == NULL) { SDL_InvalidParamError("source"); return; } - if (destination == NULL) { + CHECK_PARAM(destination == NULL) { SDL_InvalidParamError("destination"); return; } @@ -2693,15 +2847,15 @@ void SDL_CopyGPUTextureToTexture( Uint32 d, bool cycle) { - if (copy_pass == NULL) { + CHECK_PARAM(copy_pass == NULL) { SDL_InvalidParamError("copy_pass"); return; } - if (source == NULL) { + CHECK_PARAM(source == NULL) { SDL_InvalidParamError("source"); return; } - if (destination == NULL) { + CHECK_PARAM(destination == NULL) { SDL_InvalidParamError("destination"); return; } @@ -2742,15 +2896,15 @@ void SDL_CopyGPUBufferToBuffer( Uint32 size, bool cycle) { - if (copy_pass == NULL) { + CHECK_PARAM(copy_pass == NULL) { SDL_InvalidParamError("copy_pass"); return; } - if (source == NULL) { + CHECK_PARAM(source == NULL) { SDL_InvalidParamError("source"); return; } - if (destination == NULL) { + CHECK_PARAM(destination == NULL) { SDL_InvalidParamError("destination"); return; } @@ -2780,15 +2934,15 @@ void SDL_DownloadFromGPUTexture( const SDL_GPUTextureRegion *source, const SDL_GPUTextureTransferInfo *destination) { - if (copy_pass == NULL) { + CHECK_PARAM(copy_pass == NULL) { SDL_InvalidParamError("copy_pass"); return; } - if (source == NULL) { + CHECK_PARAM(source == NULL) { SDL_InvalidParamError("source"); return; } - if (destination == NULL) { + CHECK_PARAM(destination == NULL) { SDL_InvalidParamError("destination"); return; } @@ -2816,15 +2970,15 @@ void SDL_DownloadFromGPUBuffer( const SDL_GPUBufferRegion *source, const SDL_GPUTransferBufferLocation *destination) { - if (copy_pass == NULL) { + CHECK_PARAM(copy_pass == NULL) { SDL_InvalidParamError("copy_pass"); return; } - if (source == NULL) { + CHECK_PARAM(source == NULL) { SDL_InvalidParamError("source"); return; } - if (destination == NULL) { + CHECK_PARAM(destination == NULL) { SDL_InvalidParamError("destination"); return; } @@ -2850,7 +3004,7 @@ void SDL_DownloadFromGPUBuffer( void SDL_EndGPUCopyPass( SDL_GPUCopyPass *copy_pass) { - if (copy_pass == NULL) { + CHECK_PARAM(copy_pass == NULL) { SDL_InvalidParamError("copy_pass"); return; } @@ -2871,11 +3025,11 @@ void SDL_GenerateMipmapsForGPUTexture( SDL_GPUCommandBuffer *command_buffer, SDL_GPUTexture *texture) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } - if (texture == NULL) { + CHECK_PARAM(texture == NULL) { SDL_InvalidParamError("texture"); return; } @@ -2913,11 +3067,11 @@ void SDL_BlitGPUTexture( SDL_GPUCommandBuffer *command_buffer, const SDL_GPUBlitInfo *info) { - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return; } - if (info == NULL) { + CHECK_PARAM(info == NULL) { SDL_InvalidParamError("info"); return; } @@ -2978,7 +3132,8 @@ bool SDL_WindowSupportsGPUSwapchainComposition( SDL_GPUSwapchainComposition swapchain_composition) { CHECK_DEVICE_MAGIC(device, false); - if (window == NULL) { + + CHECK_PARAM(window == NULL) { SDL_InvalidParamError("window"); return false; } @@ -2999,7 +3154,8 @@ bool SDL_WindowSupportsGPUPresentMode( SDL_GPUPresentMode present_mode) { CHECK_DEVICE_MAGIC(device, false); - if (window == NULL) { + + CHECK_PARAM(window == NULL) { SDL_InvalidParamError("window"); return false; } @@ -3019,7 +3175,8 @@ bool SDL_ClaimWindowForGPUDevice( SDL_Window *window) { CHECK_DEVICE_MAGIC(device, false); - if (window == NULL) { + + CHECK_PARAM(window == NULL) { return SDL_InvalidParamError("window"); } @@ -3037,7 +3194,8 @@ void SDL_ReleaseWindowFromGPUDevice( SDL_Window *window) { CHECK_DEVICE_MAGIC(device, ); - if (window == NULL) { + + CHECK_PARAM(window == NULL) { SDL_InvalidParamError("window"); return; } @@ -3054,7 +3212,8 @@ bool SDL_SetGPUSwapchainParameters( SDL_GPUPresentMode present_mode) { CHECK_DEVICE_MAGIC(device, false); - if (window == NULL) { + + CHECK_PARAM(window == NULL) { SDL_InvalidParamError("window"); return false; } @@ -3080,6 +3239,7 @@ bool SDL_SetGPUAllowedFramesInFlight( if (device->debug_mode) { if (allowed_frames_in_flight < 1 || allowed_frames_in_flight > 3) { + SDL_COMPILE_TIME_ASSERT(max_frames_in_flight, MAX_FRAMES_IN_FLIGHT == 3); SDL_assert_release(!"allowed_frames_in_flight value must be between 1 and 3!"); } } @@ -3095,7 +3255,8 @@ SDL_GPUTextureFormat SDL_GetGPUSwapchainTextureFormat( SDL_Window *window) { CHECK_DEVICE_MAGIC(device, SDL_GPU_TEXTUREFORMAT_INVALID); - if (window == NULL) { + + CHECK_PARAM(window == NULL) { SDL_InvalidParamError("window"); return SDL_GPU_TEXTUREFORMAT_INVALID; } @@ -3114,13 +3275,13 @@ bool SDL_AcquireGPUSwapchainTexture( { CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { return SDL_InvalidParamError("command_buffer"); } - if (window == NULL) { + CHECK_PARAM(window == NULL) { return SDL_InvalidParamError("window"); } - if (swapchain_texture == NULL) { + CHECK_PARAM(swapchain_texture == NULL) { return SDL_InvalidParamError("swapchain_texture"); } @@ -3149,7 +3310,7 @@ bool SDL_WaitForGPUSwapchain( { CHECK_DEVICE_MAGIC(device, false); - if (window == NULL) { + CHECK_PARAM(window == NULL) { return SDL_InvalidParamError("window"); } @@ -3167,13 +3328,13 @@ bool SDL_WaitAndAcquireGPUSwapchainTexture( { CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { return SDL_InvalidParamError("command_buffer"); } - if (window == NULL) { + CHECK_PARAM(window == NULL) { return SDL_InvalidParamError("window"); } - if (swapchain_texture == NULL) { + CHECK_PARAM(swapchain_texture == NULL) { return SDL_InvalidParamError("swapchain_texture"); } @@ -3201,7 +3362,7 @@ bool SDL_SubmitGPUCommandBuffer( { CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return false; } @@ -3228,7 +3389,7 @@ SDL_GPUFence *SDL_SubmitGPUCommandBufferAndAcquireFence( { CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return NULL; } @@ -3255,7 +3416,7 @@ bool SDL_CancelGPUCommandBuffer( { CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - if (command_buffer == NULL) { + CHECK_PARAM(command_buffer == NULL) { SDL_InvalidParamError("command_buffer"); return false; } @@ -3287,7 +3448,12 @@ bool SDL_WaitForGPUFences( Uint32 num_fences) { CHECK_DEVICE_MAGIC(device, false); - if (fences == NULL && num_fences > 0) { + + if (!num_fences) { + return true; + } + + CHECK_PARAM(fences == NULL) { SDL_InvalidParamError("fences"); return false; } @@ -3304,7 +3470,8 @@ bool SDL_QueryGPUFence( SDL_GPUFence *fence) { CHECK_DEVICE_MAGIC(device, false); - if (fence == NULL) { + + CHECK_PARAM(fence == NULL) { SDL_InvalidParamError("fence"); return false; } @@ -3319,7 +3486,8 @@ void SDL_ReleaseGPUFence( SDL_GPUFence *fence) { CHECK_DEVICE_MAGIC(device, ); - if (fence == NULL) { + + CHECK_PARAM(fence == NULL) { return; } @@ -3340,3 +3508,67 @@ Uint32 SDL_CalculateGPUTextureFormatSize( Uint32 blocksPerColumn = (height + blockHeight - 1) / blockHeight; return depth_or_layer_count * blocksPerRow * blocksPerColumn * SDL_GPUTextureFormatTexelBlockSize(format); } + +SDL_PixelFormat SDL_GetPixelFormatFromGPUTextureFormat(SDL_GPUTextureFormat format) +{ + switch (format) { + case SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM: + return SDL_PIXELFORMAT_BGRA4444; + case SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM: + return SDL_PIXELFORMAT_BGR565; + case SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM: + return SDL_PIXELFORMAT_BGRA5551; + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT: + return SDL_PIXELFORMAT_RGBA32; + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM: + return SDL_PIXELFORMAT_RGBA32; + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM: + return SDL_PIXELFORMAT_RGBA32; + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB: + return SDL_PIXELFORMAT_RGBA32; + case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM: + return SDL_PIXELFORMAT_BGRA32; + case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB: + return SDL_PIXELFORMAT_BGRA32; + case SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM: + return SDL_PIXELFORMAT_ABGR2101010; + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT: + return SDL_PIXELFORMAT_RGBA64; + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM: + return SDL_PIXELFORMAT_RGBA64; + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT: + return SDL_PIXELFORMAT_RGBA64_FLOAT; + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT: + return SDL_PIXELFORMAT_RGBA128_FLOAT; + default: + return SDL_PIXELFORMAT_UNKNOWN; + } +} + +SDL_GPUTextureFormat SDL_GetGPUTextureFormatFromPixelFormat(SDL_PixelFormat format) +{ + switch (format) { + case SDL_PIXELFORMAT_BGRA4444: + return SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM; + case SDL_PIXELFORMAT_BGR565: + return SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM; + case SDL_PIXELFORMAT_BGRA5551: + return SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM; + case SDL_PIXELFORMAT_BGRA32: + case SDL_PIXELFORMAT_BGRX32: + return SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM; + case SDL_PIXELFORMAT_RGBA32: + case SDL_PIXELFORMAT_RGBX32: + return SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; + case SDL_PIXELFORMAT_ABGR2101010: + return SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM; + case SDL_PIXELFORMAT_RGBA64: + return SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM; + case SDL_PIXELFORMAT_RGBA64_FLOAT: + return SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT; + case SDL_PIXELFORMAT_RGBA128_FLOAT: + return SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT; + default: + return SDL_GPU_TEXTUREFORMAT_INVALID; + } +} diff --git a/libs/SDL3/src/gpu/SDL_sysgpu.h b/libs/SDL3/src/gpu/SDL_sysgpu.h index b4b54b26..8e0e3492 100644 --- a/libs/SDL3/src/gpu/SDL_sysgpu.h +++ b/libs/SDL3/src/gpu/SDL_sysgpu.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -35,7 +35,7 @@ #define UNIFORM_BUFFER_SIZE 32768 #define MAX_VERTEX_BUFFERS 16 #define MAX_VERTEX_ATTRIBUTES 16 -#define MAX_COLOR_TARGET_BINDINGS 4 +#define MAX_COLOR_TARGET_BINDINGS 8 #define MAX_PRESENT_COUNT 16 #define MAX_FRAMES_IN_FLIGHT 3 @@ -1096,6 +1096,9 @@ struct SDL_GPUDevice // Store this for SDL_gpu.c's debug layer bool debug_mode; + bool default_enable_depth_clip; + bool validate_feature_depth_clamp_disabled; + bool validate_feature_anisotropy_disabled; }; #define ASSIGN_DRIVER_FUNC(func, name) \ diff --git a/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c b/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c index 09f6f348..b5a6e50f 100644 --- a/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c +++ b/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -23,6 +23,7 @@ #ifdef SDL_GPU_D3D12 +#include "../../events/SDL_windowevents_c.h" #include "../../core/windows/SDL_windows.h" #include "../../video/directx/SDL_d3d12.h" #include "../SDL_sysgpu.h" @@ -90,6 +91,8 @@ #define D3D12_DLL "d3d12_x.dll" #else #define D3D12_DLL "d3d12.dll" +#define USE_PIX_RUNTIME +#define WINPIXEVENTRUNTIME_DLL "WinPixEventRuntime.dll" #endif #define DXGI_DLL "dxgi.dll" #define DXGIDEBUG_DLL "dxgidebug.dll" @@ -108,9 +111,10 @@ #define CREATE_DXGI_FACTORY1_FUNC "CreateDXGIFactory1" #define DXGI_GET_DEBUG_INTERFACE_FUNC "DXGIGetDebugInterface" #define D3D12_GET_DEBUG_INTERFACE_FUNC "D3D12GetDebugInterface" -#define WINDOW_PROPERTY_DATA "SDL_GPUD3D12WindowPropertyData" -#define D3D_FEATURE_LEVEL_CHOICE D3D_FEATURE_LEVEL_11_1 -#define D3D_FEATURE_LEVEL_CHOICE_STR "11_1" +#define D3D12_GET_INTERFACE_FUNC "D3D12GetInterface" +#define WINDOW_PROPERTY_DATA "SDL.internal.gpu.d3d12.data" +#define D3D_FEATURE_LEVEL_CHOICE D3D_FEATURE_LEVEL_11_0 +#define D3D_FEATURE_LEVEL_CHOICE_STR "11_0" #define MAX_ROOT_SIGNATURE_PARAMETERS 64 #define D3D12_FENCE_UNSIGNALED_VALUE 0 #define D3D12_FENCE_SIGNAL_VALUE 1 @@ -128,8 +132,17 @@ #endif // Function Pointer Signatures -typedef HRESULT(WINAPI *PFN_CREATE_DXGI_FACTORY1)(const GUID *riid, void **ppFactory); -typedef HRESULT(WINAPI *PFN_DXGI_GET_DEBUG_INTERFACE)(const GUID *riid, void **ppDebug); +typedef HRESULT (WINAPI *pfnCreateDXGIFactory1)(const GUID *riid, void **ppFactory); +typedef HRESULT (WINAPI *pfnDXGIGetDebugInterface)(const GUID *riid, void **ppDebug); + +#ifdef USE_PIX_RUNTIME +#define PIX_BEGIN_EVENT_ON_COMMAND_LIST_FUNC "PIXBeginEventOnCommandList" +#define PIX_END_EVENT_ON_COMMAND_LIST_FUNC "PIXEndEventOnCommandList" +#define PIX_SET_MARKER_ON_COMMAND_LIST_FUNC "PIXSetMarkerOnCommandList" +typedef void(WINAPI* pfnBeginEventOnCommandList)(ID3D12GraphicsCommandList* commandList, UINT64 color, _In_ PCSTR formatString); +typedef void(WINAPI* pfnEndEventOnCommandList)(ID3D12GraphicsCommandList* commandList); +typedef void(WINAPI* pfnSetMarkerOnCommandList)(ID3D12GraphicsCommandList* commandList, UINT64 color, _In_ PCSTR formatString); +#endif // IIDs (from https://www.magnumdb.com/) static const IID D3D_IID_IDXGIFactory1 = { 0x770aae78, 0xf26f, 0x4dba, { 0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87 } }; @@ -164,6 +177,12 @@ static const IID D3D_IID_ID3D12Debug = { 0x344488b7, 0x6846, 0x474b, { 0xb9, 0x8 static const IID D3D_IID_ID3D12InfoQueue = { 0x0742a90b, 0xc387, 0x483f, { 0xb9, 0x46, 0x30, 0xa7, 0xe4, 0xe6, 0x14, 0x58 } }; static const IID D3D_IID_ID3D12InfoQueue1 = { 0x2852dd88, 0xb484, 0x4c0c, { 0xb6, 0xb1, 0x67, 0x16, 0x85, 0x00, 0xe6, 0x00 } }; +static const GUID D3D_CLSID_ID3D12SDKConfiguration = { 0x7cda6aca, 0xa03e, 0x49c8, { 0x94, 0x58, 0x03, 0x34, 0xd2, 0x0e, 0x07, 0xce } }; +static const GUID D3D_CLSID_ID3D12Debug = { 0xf2352aeb, 0xdd84, 0x49fe, { 0xb9, 0x7b, 0xa9, 0xdc, 0xfd, 0xcc, 0x1b, 0x4f } }; +static const IID D3D_IID_ID3D12SDKConfiguration = { 0xe9eb5314, 0x33aa, 0x42b2, { 0xa7, 0x18, 0xd7, 0x7f, 0x58, 0xb1, 0xf1, 0xc7 } }; +static const IID D3D_IID_ID3D12SDKConfiguration1 = { 0x8aaf9303, 0xad25, 0x48b9, { 0x9a, 0x57, 0xd9, 0xc3, 0x7e, 0x00, 0x9d, 0x9f } }; +static const IID D3D_IID_ID3D12DeviceFactory = { 0x61f307d3, 0xd34e, 0x4e7c, { 0x83, 0x74, 0x3b, 0xa4, 0xde, 0x23, 0xcc, 0xcb } }; + // Enums typedef enum D3D12BufferType @@ -244,7 +263,7 @@ static D3D12_BLEND_OP SDLToD3D12_BlendOp[] = { SDL_COMPILE_TIME_ASSERT(SDLToD3D12_BlendOp, SDL_arraysize(SDLToD3D12_BlendOp) == SDL_GPU_BLENDOP_MAX_ENUM_VALUE); // These are actually color formats. -// For some genius reason, D3D12 splits format capabilites for depth-stencil views. +// For some genius reason, D3D12 splits format capabilities for depth-stencil views. static DXGI_FORMAT SDLToD3D12_TextureFormat[] = { DXGI_FORMAT_UNKNOWN, // INVALID DXGI_FORMAT_A8_UNORM, // A8_UNORM @@ -819,6 +838,8 @@ typedef struct D3D12Sampler typedef struct D3D12WindowData { SDL_Window *window; + D3D12Renderer *renderer; + int refcount; #if (defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) D3D12XBOX_FRAME_PIPELINE_TOKEN frameToken; #else @@ -844,6 +865,14 @@ typedef struct D3D12PresentData Uint32 swapchainImageIndex; } D3D12PresentData; +#ifdef USE_PIX_RUNTIME +typedef struct WinPixEventRuntimeFns { + pfnBeginEventOnCommandList pBeginEventOnCommandList; + pfnEndEventOnCommandList pEndEventOnCommandList; + pfnSetMarkerOnCommandList pSetMarkerOnCommandList; +} WinPixEventRuntimeFns; +#endif + struct D3D12Renderer { // Reference to the parent device @@ -858,19 +887,24 @@ struct D3D12Renderer IDXGIAdapter1 *adapter; SDL_SharedObject *dxgi_dll; SDL_SharedObject *dxgidebug_dll; +#endif +#ifdef USE_PIX_RUNTIME + SDL_SharedObject *winpixeventruntime_dll; + WinPixEventRuntimeFns winpixeventruntimeFns; #endif ID3D12Debug *d3d12Debug; BOOL supportsTearing; SDL_SharedObject *d3d12_dll; ID3D12Device *device; - PFN_D3D12_SERIALIZE_ROOT_SIGNATURE D3D12SerializeRootSignature_func; - const char *semantic; + PFN_D3D12_SERIALIZE_ROOT_SIGNATURE pD3D12SerializeRootSignature; + char *semantic; SDL_iconv_t iconv; ID3D12CommandQueue *commandQueue; bool debug_mode; bool GPUUploadHeapSupported; + bool UnrestrictedBufferTextureCopyPitchSupported; // FIXME: these might not be necessary since we're not using custom heaps bool UMA; bool UMACacheCoherent; @@ -980,6 +1014,10 @@ struct D3D12CommandBuffer // Set at acquire time D3D12DescriptorHeap *gpuDescriptorHeaps[D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER + 1]; + D3D12DescriptorHeap **usedDescriptorHeaps; + Uint32 usedDescriptorHeapCount; + Uint32 usedDescriptorHeapCapacity; + D3D12UniformBuffer **usedUniformBuffers; Uint32 usedUniformBufferCount; Uint32 usedUniformBufferCapacity; @@ -1183,7 +1221,6 @@ struct D3D12UniformBuffer D3D12Buffer *buffer; Uint32 writeOffset; Uint32 drawOffset; - Uint32 currentBlockSize; }; // Forward function declarations @@ -1346,9 +1383,7 @@ static void D3D12_INTERNAL_ReleaseBufferContainer( } // Containers are just client handles, so we can free immediately - if (container->debugName) { - SDL_free(container->debugName); - } + SDL_free(container->debugName); SDL_free(container->buffers); SDL_free(container); @@ -1420,10 +1455,10 @@ static void D3D12_INTERNAL_ReleaseTextureContainer( container->textures[i]); } + SDL_DestroyProperties(container->header.info.props); + // Containers are just client handles, so we can destroy immediately - if (container->debugName) { - SDL_free(container->debugName); - } + SDL_free(container->debugName); SDL_free(container->textures); SDL_free(container); @@ -1564,6 +1599,7 @@ static void D3D12_INTERNAL_DestroyCommandBuffer(D3D12CommandBuffer *commandBuffe SDL_free(commandBuffer->usedSamplers); SDL_free(commandBuffer->usedGraphicsPipelines); SDL_free(commandBuffer->usedComputePipelines); + SDL_free(commandBuffer->usedDescriptorHeaps); SDL_free(commandBuffer->usedUniformBuffers); SDL_free(commandBuffer->textureDownloads); SDL_free(commandBuffer); @@ -1699,7 +1735,13 @@ static void D3D12_INTERNAL_DestroyRenderer(D3D12Renderer *renderer) renderer->dxgidebug_dll = NULL; } #endif - renderer->D3D12SerializeRootSignature_func = NULL; +#ifdef USE_PIX_RUNTIME + if (renderer->winpixeventruntime_dll) { + SDL_UnloadObject(renderer->winpixeventruntime_dll); + renderer->winpixeventruntime_dll = NULL; + } +#endif + renderer->pD3D12SerializeRootSignature = NULL; if (renderer->iconv) { SDL_iconv_close(renderer->iconv); @@ -1711,6 +1753,7 @@ static void D3D12_INTERNAL_DestroyRenderer(D3D12Renderer *renderer) SDL_DestroyMutex(renderer->windowLock); SDL_DestroyMutex(renderer->fenceLock); SDL_DestroyMutex(renderer->disposeLock); + SDL_free(renderer->semantic); SDL_free(renderer); } @@ -1749,6 +1792,16 @@ static inline Uint32 D3D12_INTERNAL_CalcSubresource( return mipLevel + (layer * numLevels); } +static inline Uint32 D3D12_INTERNAL_CalcSubresourceWithPlane( + Uint32 mipLevel, + Uint32 layer, + Uint32 planeSlice, + Uint32 numLevels, + Uint32 arraySize) +{ + return mipLevel + (layer * numLevels) + (planeSlice * numLevels * arraySize); +} + static void D3D12_INTERNAL_ResourceBarrier( D3D12CommandBuffer *commandBuffer, D3D12_RESOURCE_STATES sourceState, @@ -1805,6 +1858,27 @@ static void D3D12_INTERNAL_TextureSubresourceBarrier( textureSubresource->parent->resource, textureSubresource->index, needsUAVBarrier); + + // D3D12 stores planar values on a separate subresource. + // Since depth-stencil is our only supported planar format, + // just force an extra transition if we're using a stencil format. + if (IsStencilFormat(textureSubresource->parent->container->header.info.format)) { + Uint32 planeSubresourceIndex = D3D12_INTERNAL_CalcSubresourceWithPlane( + textureSubresource->level, + textureSubresource->layer, + 1, + textureSubresource->parent->container->header.info.num_levels, + textureSubresource->parent->container->header.info.layer_count_or_depth + ); + + D3D12_INTERNAL_ResourceBarrier( + commandBuffer, + sourceState, + destinationState, + textureSubresource->parent->resource, + planeSubresourceIndex, + needsUAVBarrier); + } } static D3D12_RESOURCE_STATES D3D12_INTERNAL_DefaultTextureResourceState( @@ -2066,9 +2140,7 @@ static void D3D12_SetBufferName( D3D12BufferContainer *container = (D3D12BufferContainer *)buffer; if (renderer->debug_mode && text != NULL) { - if (container->debugName != NULL) { - SDL_free(container->debugName); - } + SDL_free(container->debugName); container->debugName = SDL_strdup(text); @@ -2090,9 +2162,7 @@ static void D3D12_SetTextureName( D3D12TextureContainer *container = (D3D12TextureContainer *)texture; if (renderer->debug_mode && text != NULL) { - if (container->debugName != NULL) { - SDL_free(container->debugName); - } + SDL_free(container->debugName); container->debugName = SDL_strdup(text); @@ -2105,8 +2175,8 @@ static void D3D12_SetTextureName( } } -/* These debug functions are all marked as "for internal usage only" - * on D3D12... works on renderdoc! +/* These debug functions now require the PIX runtime under Windows to avoid validation + * layer errors. Calling them without the PIX runtime in your path is a no-op. */ static void D3D12_InsertDebugLabel( @@ -2114,6 +2184,13 @@ static void D3D12_InsertDebugLabel( const char *text) { D3D12CommandBuffer *d3d12CommandBuffer = (D3D12CommandBuffer *)commandBuffer; +#ifdef USE_PIX_RUNTIME + // Requires PIX runtime under Windows, no-op if DLL unavailable. + WinPixEventRuntimeFns *fns = &d3d12CommandBuffer->renderer->winpixeventruntimeFns; + if (fns->pSetMarkerOnCommandList) { + fns->pSetMarkerOnCommandList(d3d12CommandBuffer->graphicsCommandList, 0 /*default color*/, text); + } +#else WCHAR *wchar_text = WIN_UTF8ToStringW(text); ID3D12GraphicsCommandList_SetMarker( @@ -2123,6 +2200,7 @@ static void D3D12_InsertDebugLabel( (UINT)SDL_wcslen(wchar_text) * sizeof(WCHAR)); SDL_free(wchar_text); +#endif } static void D3D12_PushDebugGroup( @@ -2130,6 +2208,13 @@ static void D3D12_PushDebugGroup( const char *name) { D3D12CommandBuffer *d3d12CommandBuffer = (D3D12CommandBuffer *)commandBuffer; +#ifdef USE_PIX_RUNTIME + // Requires PIX runtime under Windows, no-op if DLL unavailable. + WinPixEventRuntimeFns *fns = &d3d12CommandBuffer->renderer->winpixeventruntimeFns; + if (fns->pBeginEventOnCommandList) { + fns->pBeginEventOnCommandList(d3d12CommandBuffer->graphicsCommandList, 0 /*default color*/, name); + } +#else WCHAR *wchar_text = WIN_UTF8ToStringW(name); ID3D12GraphicsCommandList_BeginEvent( @@ -2139,13 +2224,22 @@ static void D3D12_PushDebugGroup( (UINT)SDL_wcslen(wchar_text) * sizeof(WCHAR)); SDL_free(wchar_text); +#endif } static void D3D12_PopDebugGroup( SDL_GPUCommandBuffer *commandBuffer) { D3D12CommandBuffer *d3d12CommandBuffer = (D3D12CommandBuffer *)commandBuffer; +#ifdef USE_PIX_RUNTIME + // Requires PIX runtime under Windows, no-op if DLL unavailable. + WinPixEventRuntimeFns *fns = &d3d12CommandBuffer->renderer->winpixeventruntimeFns; + if (fns->pEndEventOnCommandList) { + fns->pEndEventOnCommandList(d3d12CommandBuffer->graphicsCommandList); + } +#else ID3D12GraphicsCommandList_EndEvent(d3d12CommandBuffer->graphicsCommandList); +#endif } // State Creation @@ -2267,6 +2361,28 @@ static bool D3D12_INTERNAL_ExpandStagingDescriptorPool( return true; } +static void D3D12_INTERNAL_TrackGPUDescriptorHeap( + D3D12CommandBuffer *commandBuffer, + D3D12DescriptorHeap *descriptorHeap) +{ + Uint32 i; + for (i = 0; i < commandBuffer->usedDescriptorHeapCount; i += 1) { + if (commandBuffer->usedDescriptorHeaps[i] == descriptorHeap) { + return; + } + } + + if (commandBuffer->usedDescriptorHeapCount == commandBuffer->usedDescriptorHeapCapacity) { + commandBuffer->usedDescriptorHeapCapacity += 1; + commandBuffer->usedDescriptorHeaps = (D3D12DescriptorHeap **)SDL_realloc( + commandBuffer->usedDescriptorHeaps, + commandBuffer->usedDescriptorHeapCapacity * sizeof(D3D12DescriptorHeap *)); + } + + commandBuffer->usedDescriptorHeaps[commandBuffer->usedDescriptorHeapCount] = descriptorHeap; + commandBuffer->usedDescriptorHeapCount += 1; +} + static D3D12DescriptorHeap *D3D12_INTERNAL_AcquireGPUDescriptorHeapFromPool( D3D12CommandBuffer *commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE descriptorHeapType) @@ -2288,6 +2404,7 @@ static D3D12DescriptorHeap *D3D12_INTERNAL_AcquireGPUDescriptorHeapFromPool( } SDL_UnlockMutex(pool->lock); + D3D12_INTERNAL_TrackGPUDescriptorHeap(commandBuffer, result); return result; } @@ -2555,7 +2672,7 @@ static D3D12GraphicsRootSignature *D3D12_INTERNAL_CreateGraphicsRootSignature( // Serialize the root signature ID3DBlob *serializedRootSignature; ID3DBlob *errorBlob; - HRESULT res = renderer->D3D12SerializeRootSignature_func(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &serializedRootSignature, &errorBlob); + HRESULT res = renderer->pD3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &serializedRootSignature, &errorBlob); if (FAILED(res)) { if (errorBlob) { @@ -2785,7 +2902,7 @@ static D3D12ComputeRootSignature *D3D12_INTERNAL_CreateComputeRootSignature( ID3DBlob *serializedRootSignature; ID3DBlob *errorBlob; - HRESULT res = renderer->D3D12SerializeRootSignature_func( + HRESULT res = renderer->pD3D12SerializeRootSignature( &rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &serializedRootSignature, @@ -2828,8 +2945,6 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( const SDL_GPUComputePipelineCreateInfo *createinfo) { D3D12Renderer *renderer = (D3D12Renderer *)driverData; - void *bytecode; - size_t bytecodeSize; ID3D12PipelineState *pipelineState; if (!D3D12_INTERNAL_CreateShaderBytecode( @@ -2837,8 +2952,8 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( createinfo->code, createinfo->code_size, createinfo->format, - &bytecode, - &bytecodeSize)) { + NULL, + NULL)) { return NULL; } @@ -2847,13 +2962,12 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( createinfo); if (rootSignature == NULL) { - SDL_free(bytecode); SET_STRING_ERROR_AND_RETURN("Could not create root signature!", NULL); } D3D12_COMPUTE_PIPELINE_STATE_DESC pipelineDesc; - pipelineDesc.CS.pShaderBytecode = bytecode; - pipelineDesc.CS.BytecodeLength = bytecodeSize; + pipelineDesc.CS.pShaderBytecode = createinfo->code; + pipelineDesc.CS.BytecodeLength = createinfo->code_size; pipelineDesc.pRootSignature = rootSignature->handle; pipelineDesc.CachedPSO.CachedBlobSizeInBytes = 0; pipelineDesc.CachedPSO.pCachedBlob = NULL; @@ -2868,7 +2982,6 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( if (FAILED(res)) { D3D12_INTERNAL_SetError(renderer, "Could not create compute pipeline state", res); - SDL_free(bytecode); return NULL; } @@ -2877,7 +2990,6 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( if (!computePipeline) { ID3D12PipelineState_Release(pipelineState); - SDL_free(bytecode); return NULL; } @@ -3519,7 +3631,12 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( dsvDesc.Format = SDLToD3D12_DepthFormat[createinfo->format]; dsvDesc.Flags = (D3D12_DSV_FLAGS)0; - if (isMultisample) { + if (createinfo->type == SDL_GPU_TEXTURETYPE_2D_ARRAY || createinfo->type == SDL_GPU_TEXTURETYPE_CUBE || createinfo->type == SDL_GPU_TEXTURETYPE_CUBE_ARRAY) { + dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DARRAY; + dsvDesc.Texture2DArray.MipSlice = levelIndex; + dsvDesc.Texture2DArray.FirstArraySlice = layerIndex; + dsvDesc.Texture2DArray.ArraySize = 1; + } else if (isMultisample) { dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMS; } else { dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; @@ -3549,6 +3666,7 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( uavDesc.Texture2DArray.MipSlice = levelIndex; uavDesc.Texture2DArray.FirstArraySlice = layerIndex; uavDesc.Texture2DArray.ArraySize = 1; + uavDesc.Texture2DArray.PlaneSlice = 0; } else if (createinfo->type == SDL_GPU_TEXTURETYPE_3D) { uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D; uavDesc.Texture3D.MipSlice = levelIndex; @@ -4288,8 +4406,8 @@ static void D3D12_BeginRenderPass( if (depthStencilTargetInfo != NULL) { D3D12TextureContainer *container = (D3D12TextureContainer *)depthStencilTargetInfo->texture; - Uint32 h = container->header.info.height; - Uint32 w = container->header.info.width; + Uint32 h = container->header.info.height >> depthStencilTargetInfo->mip_level; + Uint32 w = container->header.info.width >> depthStencilTargetInfo->mip_level; // The framebuffer cannot be larger than the smallest target. @@ -4358,8 +4476,8 @@ static void D3D12_BeginRenderPass( D3D12TextureSubresource *subresource = D3D12_INTERNAL_PrepareTextureSubresourceForWrite( d3d12CommandBuffer, container, - 0, - 0, + depthStencilTargetInfo->layer, + depthStencilTargetInfo->mip_level, depthStencilTargetInfo->cycle, D3D12_RESOURCE_STATE_DEPTH_WRITE); @@ -4492,7 +4610,6 @@ static D3D12UniformBuffer *D3D12_INTERNAL_AcquireUniformBufferFromPool( SDL_UnlockMutex(renderer->acquireUniformBufferLock); - uniformBuffer->currentBlockSize = 0; uniformBuffer->drawOffset = 0; uniformBuffer->writeOffset = 0; @@ -4531,6 +4648,7 @@ static void D3D12_INTERNAL_PushUniformData( Uint32 length) { D3D12UniformBuffer *uniformBuffer; + Uint32 blockSize; if (shaderStage == SDL_GPU_SHADERSTAGE_VERTEX) { if (commandBuffer->vertexUniformBuffers[slotIndex] == NULL) { @@ -4555,13 +4673,13 @@ static void D3D12_INTERNAL_PushUniformData( return; } - uniformBuffer->currentBlockSize = + blockSize = D3D12_INTERNAL_Align( length, 256); // If there is no more room, acquire a new uniform buffer - if (uniformBuffer->writeOffset + uniformBuffer->currentBlockSize >= UNIFORM_BUFFER_SIZE) { + if (uniformBuffer->writeOffset + blockSize >= UNIFORM_BUFFER_SIZE) { ID3D12Resource_Unmap( uniformBuffer->buffer->handle, 0, @@ -4591,7 +4709,7 @@ static void D3D12_INTERNAL_PushUniformData( data, length); - uniformBuffer->writeOffset += uniformBuffer->currentBlockSize; + uniformBuffer->writeOffset += blockSize; if (shaderStage == SDL_GPU_SHADERSTAGE_VERTEX) { commandBuffer->needVertexUniformBufferBind[slotIndex] = true; @@ -5841,15 +5959,20 @@ static void D3D12_UploadToTexture( bool cycle) { D3D12CommandBuffer *d3d12CommandBuffer = (D3D12CommandBuffer *)commandBuffer; + D3D12Renderer *renderer = (D3D12Renderer *)d3d12CommandBuffer->renderer; D3D12BufferContainer *transferBufferContainer = (D3D12BufferContainer *)source->transfer_buffer; D3D12Buffer *temporaryBuffer = NULL; D3D12_TEXTURE_COPY_LOCATION sourceLocation; D3D12_TEXTURE_COPY_LOCATION destinationLocation; Uint32 pixelsPerRow = source->pixels_per_row; + Uint32 blockWidth; + Uint32 blockSize; Uint32 rowPitch; + Uint32 blockHeight; Uint32 alignedRowPitch; Uint32 rowsPerSlice = source->rows_per_layer; Uint32 bytesPerSlice; + Uint32 alignedBytesPerSlice; bool needsRealignment; bool needsPlacementCopy; @@ -5864,11 +5987,12 @@ static void D3D12_UploadToTexture( cycle, D3D12_RESOURCE_STATE_COPY_DEST); - /* D3D12 requires texture data row pitch to be 256 byte aligned, which is obviously insane. - * Instead of exposing that restriction to the client, which is a huge rake to step on, - * and a restriction that no other backend requires, we're going to copy data to a temporary buffer, - * copy THAT data to the texture, and then get rid of the temporary buffer ASAP. - * If we're lucky and the row pitch and depth pitch are already aligned, we can skip all of that. + /* Unless the UnrestrictedBufferTextureCopyPitchSupported feature is supported, D3D12 requires + * texture data row pitch to be 256 byte aligned, which is obviously insane. Instead of exposing + * that restriction to the client, which is a huge rake to step on, and a restriction that no + * other backend requires, we're going to copy data to a temporary buffer, copy THAT data to the + * texture, and then get rid of the temporary buffer ASAP. If we're lucky and the row pitch and + * depth pitch are already aligned, we can skip all of that. * * D3D12 also requires offsets to be 512 byte aligned. We'll fix that for the client and warn them as well. * @@ -5879,17 +6003,29 @@ static void D3D12_UploadToTexture( pixelsPerRow = destination->w; } - rowPitch = BytesPerRow(pixelsPerRow, textureContainer->header.info.format); - if (rowsPerSlice == 0) { rowsPerSlice = destination->h; } + blockWidth = Texture_GetBlockWidth(textureContainer->header.info.format); + blockSize = SDL_GPUTextureFormatTexelBlockSize(textureContainer->header.info.format); + rowPitch = (pixelsPerRow + (blockWidth - 1)) / blockWidth * blockSize; + blockHeight = (rowsPerSlice + (blockWidth - 1)) / blockWidth; + bytesPerSlice = rowsPerSlice * rowPitch; - alignedRowPitch = D3D12_INTERNAL_Align(rowPitch, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); - needsRealignment = rowsPerSlice != destination->h || rowPitch != alignedRowPitch; - needsPlacementCopy = source->offset % D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT != 0; + if (renderer->UnrestrictedBufferTextureCopyPitchSupported) { + alignedRowPitch = rowPitch; + needsRealignment = false; + needsPlacementCopy = false; + } else { + alignedRowPitch = (destination->w + (blockWidth - 1)) / blockWidth * blockSize; + alignedRowPitch = D3D12_INTERNAL_Align(alignedRowPitch, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); + needsRealignment = rowsPerSlice != destination->h || rowPitch != alignedRowPitch; + needsPlacementCopy = source->offset % D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT != 0; + } + + alignedBytesPerSlice = alignedRowPitch * destination->h; sourceLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; sourceLocation.PlacedFootprint.Footprint.Format = SDLToD3D12_TextureFormat[textureContainer->header.info.format]; @@ -5903,7 +6039,7 @@ static void D3D12_UploadToTexture( temporaryBuffer = D3D12_INTERNAL_CreateBuffer( d3d12CommandBuffer->renderer, 0, - alignedRowPitch * destination->h * destination->d, + alignedRowPitch * blockHeight * destination->d, D3D12_BUFFER_TYPE_UPLOAD, NULL); @@ -5914,23 +6050,18 @@ static void D3D12_UploadToTexture( sourceLocation.pResource = temporaryBuffer->handle; for (Uint32 sliceIndex = 0; sliceIndex < destination->d; sliceIndex += 1) { - // copy row count minus one to avoid overread - for (Uint32 rowIndex = 0; rowIndex < rowsPerSlice - 1; rowIndex += 1) { + for (Uint32 rowIndex = 0; rowIndex < blockHeight; rowIndex += 1) { SDL_memcpy( - temporaryBuffer->mapPointer + (sliceIndex * rowsPerSlice) + (rowIndex * alignedRowPitch), + temporaryBuffer->mapPointer + (sliceIndex * alignedBytesPerSlice) + (rowIndex * alignedRowPitch), transferBufferContainer->activeBuffer->mapPointer + source->offset + (sliceIndex * bytesPerSlice) + (rowIndex * rowPitch), - alignedRowPitch); + rowPitch); + } - Uint32 offset = source->offset + (sliceIndex * bytesPerSlice) + ((rowsPerSlice - 1) * rowPitch); - SDL_memcpy( - temporaryBuffer->mapPointer + (sliceIndex * rowsPerSlice) + ((rowsPerSlice - 1) * alignedRowPitch), - transferBufferContainer->activeBuffer->mapPointer + offset, - SDL_min(alignedRowPitch, transferBufferContainer->size - offset)); sourceLocation.PlacedFootprint.Footprint.Width = destination->w; - sourceLocation.PlacedFootprint.Footprint.Height = rowsPerSlice; + sourceLocation.PlacedFootprint.Footprint.Height = destination->h; sourceLocation.PlacedFootprint.Footprint.Depth = 1; - sourceLocation.PlacedFootprint.Offset = (sliceIndex * bytesPerSlice); + sourceLocation.PlacedFootprint.Offset = (sliceIndex * alignedBytesPerSlice); ID3D12GraphicsCommandList_CopyTextureRegion( d3d12CommandBuffer->graphicsCommandList, @@ -5954,7 +6085,7 @@ static void D3D12_UploadToTexture( temporaryBuffer = D3D12_INTERNAL_CreateBuffer( d3d12CommandBuffer->renderer, 0, - alignedRowPitch * destination->h * destination->d, + alignedRowPitch * blockHeight * destination->d, D3D12_BUFFER_TYPE_UPLOAD, NULL); @@ -5965,13 +6096,13 @@ static void D3D12_UploadToTexture( SDL_memcpy( temporaryBuffer->mapPointer, transferBufferContainer->activeBuffer->mapPointer + source->offset, - alignedRowPitch * destination->h * destination->d); + alignedRowPitch * blockHeight * destination->d); sourceLocation.pResource = temporaryBuffer->handle; sourceLocation.PlacedFootprint.Offset = 0; sourceLocation.PlacedFootprint.Footprint.Width = destination->w; sourceLocation.PlacedFootprint.Footprint.Height = destination->h; - sourceLocation.PlacedFootprint.Footprint.Depth = 1; + sourceLocation.PlacedFootprint.Footprint.Depth = destination->d; ID3D12GraphicsCommandList_CopyTextureRegion( d3d12CommandBuffer->graphicsCommandList, @@ -6171,6 +6302,7 @@ static void D3D12_DownloadFromTexture( const SDL_GPUTextureTransferInfo *destination) { D3D12CommandBuffer *d3d12CommandBuffer = (D3D12CommandBuffer *)commandBuffer; + D3D12Renderer *renderer = d3d12CommandBuffer->renderer; D3D12_TEXTURE_COPY_LOCATION sourceLocation; D3D12_TEXTURE_COPY_LOCATION destinationLocation; Uint32 pixelsPerRow = destination->pixels_per_row; @@ -6188,11 +6320,12 @@ static void D3D12_DownloadFromTexture( D3D12BufferContainer *destinationContainer = (D3D12BufferContainer *)destination->transfer_buffer; D3D12Buffer *destinationBuffer = destinationContainer->activeBuffer; - /* D3D12 requires texture data row pitch to be 256 byte aligned, which is obviously insane. - * Instead of exposing that restriction to the client, which is a huge rake to step on, - * and a restriction that no other backend requires, we're going to copy data to a temporary buffer, - * copy THAT data to the texture, and then get rid of the temporary buffer ASAP. - * If we're lucky and the row pitch and depth pitch are already aligned, we can skip all of that. + /* Unless the UnrestrictedBufferTextureCopyPitchSupported feature is supported, D3D12 requires + * texture data row pitch to be 256 byte aligned, which is obviously insane. Instead of exposing + * that restriction to the client, which is a huge rake to step on, and a restriction that no + * other backend requires, we're going to copy data to a temporary buffer, copy THAT data to the + * texture, and then get rid of the temporary buffer ASAP. If we're lucky and the row pitch and + * depth pitch are already aligned, we can skip all of that. * * D3D12 also requires offsets to be 512 byte aligned. We'll fix that for the client and warn them as well. * @@ -6212,9 +6345,15 @@ static void D3D12_DownloadFromTexture( rowsPerSlice = source->h; } - alignedRowPitch = D3D12_INTERNAL_Align(rowPitch, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); - needsRealignment = rowsPerSlice != source->h || rowPitch != alignedRowPitch; - needsPlacementCopy = destination->offset % D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT != 0; + if (renderer->UnrestrictedBufferTextureCopyPitchSupported) { + alignedRowPitch = rowPitch; + needsRealignment = false; + needsPlacementCopy = false; + } else { + alignedRowPitch = D3D12_INTERNAL_Align(rowPitch, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); + needsRealignment = rowsPerSlice != source->h || rowPitch != alignedRowPitch; + needsPlacementCopy = destination->offset % D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT != 0; + } sourceLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; sourceLocation.SubresourceIndex = sourceSubresource->index; @@ -7000,6 +7139,8 @@ static bool D3D12_ClaimWindow( return false; } windowData->window = window; + windowData->renderer = renderer; + windowData->refcount = 1; if (D3D12_INTERNAL_CreateSwapchain(renderer, windowData, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_VSYNC)) { SDL_SetPointerProperty(SDL_GetWindowProperties(window), WINDOW_PROPERTY_DATA, windowData); @@ -7015,13 +7156,16 @@ static bool D3D12_ClaimWindow( renderer->claimedWindowCount += 1; SDL_UnlockMutex(renderer->windowLock); - SDL_AddEventWatch(D3D12_INTERNAL_OnWindowResize, window); + SDL_AddWindowEventWatch(SDL_WINDOW_EVENT_WATCH_NORMAL, D3D12_INTERNAL_OnWindowResize, window); return true; } else { SDL_free(windowData); - SET_STRING_ERROR_AND_RETURN("Could not create swapchain, failed to claim window!", false); + return false; } + } else if (windowData->renderer == renderer) { + ++windowData->refcount; + return true; } else { SET_STRING_ERROR_AND_RETURN("Window already claimed", false); } @@ -7035,7 +7179,15 @@ static void D3D12_ReleaseWindow( D3D12WindowData *windowData = D3D12_INTERNAL_FetchWindowData(window); if (windowData == NULL) { - SET_STRING_ERROR_AND_RETURN("Window already unclaimed!", ); + return; + } + if (windowData->renderer != renderer) { + SDL_SetError("Window not claimed by this device"); + return; + } + if (windowData->refcount > 1) { + --windowData->refcount; + return; } D3D12_Wait(driverData); @@ -7063,7 +7215,7 @@ static void D3D12_ReleaseWindow( SDL_free(windowData); SDL_ClearProperty(SDL_GetWindowProperties(window), WINDOW_PROPERTY_DATA); - SDL_RemoveEventWatch(D3D12_INTERNAL_OnWindowResize, window); + SDL_RemoveWindowEventWatch(SDL_WINDOW_EVENT_WATCH_NORMAL, D3D12_INTERNAL_OnWindowResize, window); } static bool D3D12_SetSwapchainParameters( @@ -7276,6 +7428,11 @@ static bool D3D12_INTERNAL_AllocateCommandBuffer( commandBuffer->usedComputePipelines = (D3D12ComputePipeline **)SDL_calloc( commandBuffer->usedComputePipelineCapacity, sizeof(D3D12ComputePipeline *)); + commandBuffer->usedDescriptorHeapCapacity = 4; + commandBuffer->usedDescriptorHeapCount = 0; + commandBuffer->usedDescriptorHeaps = (D3D12DescriptorHeap **)SDL_calloc( + commandBuffer->usedDescriptorHeapCapacity, sizeof(D3D12DescriptorHeap *)); + commandBuffer->usedUniformBufferCapacity = 4; commandBuffer->usedUniformBufferCount = 0; commandBuffer->usedUniformBuffers = (D3D12UniformBuffer **)SDL_calloc( @@ -7694,13 +7851,13 @@ static bool D3D12_INTERNAL_CleanCommandBuffer( NULL); CHECK_D3D12_ERROR_AND_RETURN("Could not reset command list", false); - // Return descriptor heaps to pool - D3D12_INTERNAL_ReturnGPUDescriptorHeapToPool( - renderer, - commandBuffer->gpuDescriptorHeaps[D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV]); - D3D12_INTERNAL_ReturnGPUDescriptorHeapToPool( - renderer, - commandBuffer->gpuDescriptorHeaps[D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER]); + // Return descriptor heaps to pool, pools own their own locks + for (i = 0; i < commandBuffer->usedDescriptorHeapCount; i += 1) { + D3D12_INTERNAL_ReturnGPUDescriptorHeapToPool( + renderer, + commandBuffer->usedDescriptorHeaps[i]); + } + commandBuffer->usedDescriptorHeapCount = 0; commandBuffer->gpuDescriptorHeaps[D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV] = NULL; commandBuffer->gpuDescriptorHeaps[D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER] = NULL; @@ -7935,7 +8092,10 @@ static bool D3D12_Submit( windowData->inFlightFences[windowData->frameCounter] = (SDL_GPUFence *)d3d12CommandBuffer->inFlightFence; (void)SDL_AtomicIncRef(&d3d12CommandBuffer->inFlightFence->referenceCount); - windowData->frameCounter = (windowData->frameCounter + 1) % renderer->allowedFramesInFlight; + + // Normally this is '% allowedFramesInFlight', but the value gets clamped + // at swapchain creation time, so use swapchainTextureCount instead + windowData->frameCounter = (windowData->frameCounter + 1) % windowData->swapchainTextureCount; } // Check for cleanups @@ -8338,14 +8498,16 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) #else SDL_SharedObject *d3d12Dll; SDL_SharedObject *dxgiDll; - PFN_D3D12_CREATE_DEVICE D3D12CreateDeviceFunc; - PFN_CREATE_DXGI_FACTORY1 CreateDXGIFactoryFunc; + PFN_D3D12_CREATE_DEVICE pD3D12CreateDevice; + pfnCreateDXGIFactory1 pCreateDXGIFactory1; HRESULT res; ID3D12Device *device; IDXGIFactory1 *factory; IDXGIFactory4 *factory4; IDXGIFactory6 *factory6; IDXGIAdapter1 *adapter; + bool supports_64UAVs = false; + bool needs_64UAVs = !SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN, false); // Early check to see if the app has _any_ D3D12 formats, if not we don't // have to fuss with loading D3D in the first place. @@ -8366,10 +8528,10 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) return false; } - D3D12CreateDeviceFunc = (PFN_D3D12_CREATE_DEVICE)SDL_LoadFunction( + pD3D12CreateDevice = (PFN_D3D12_CREATE_DEVICE)SDL_LoadFunction( d3d12Dll, D3D12_CREATE_DEVICE_FUNC); - if (D3D12CreateDeviceFunc == NULL) { + if (pD3D12CreateDevice == NULL) { SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: Could not find function " D3D12_CREATE_DEVICE_FUNC " in " D3D12_DLL); SDL_UnloadObject(d3d12Dll); return false; @@ -8383,10 +8545,10 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) return false; } - CreateDXGIFactoryFunc = (PFN_CREATE_DXGI_FACTORY1)SDL_LoadFunction( + pCreateDXGIFactory1 = (pfnCreateDXGIFactory1)SDL_LoadFunction( dxgiDll, CREATE_DXGI_FACTORY1_FUNC); - if (CreateDXGIFactoryFunc == NULL) { + if (pCreateDXGIFactory1 == NULL) { SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: Could not find function " CREATE_DXGI_FACTORY1_FUNC " in " DXGI_DLL); SDL_UnloadObject(dxgiDll); return false; @@ -8395,7 +8557,7 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) // Can we create a device? // Create the DXGI factory - res = CreateDXGIFactoryFunc( + res = pCreateDXGIFactory1( &D3D_IID_IDXGIFactory1, (void **)&factory); if (FAILED(res)) { @@ -8445,33 +8607,71 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) return false; } - res = D3D12CreateDeviceFunc( + SDL_COMPILE_TIME_ASSERT(featurelevel, D3D_FEATURE_LEVEL_CHOICE < D3D_FEATURE_LEVEL_11_1); + + /* If Windows 11 is running and the app has neither DXIL nor TIER2 + * requirements, we can skip doing any device checks entirely + */ + if (!needs_64UAVs && !has_dxil && WIN_IsWindows11OrGreater()) { + IDXGIAdapter1_Release(adapter); + IDXGIFactory1_Release(factory); + + SDL_UnloadObject(d3d12Dll); + SDL_UnloadObject(dxgiDll); + + return true; + } + + res = pD3D12CreateDevice( (IUnknown *)adapter, D3D_FEATURE_LEVEL_CHOICE, D3D_GUID(D3D_IID_ID3D12Device), (void **)&device); - if (SUCCEEDED(res)) { - D3D12_FEATURE_DATA_SHADER_MODEL shaderModel; - shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_0; + // Only check for Tier 2 resource binding if the app needs it + if (needs_64UAVs) { + D3D12_FEATURE_DATA_D3D12_OPTIONS featureOptions; + SDL_zero(featureOptions); - res = ID3D12Device_CheckFeatureSupport( - device, - D3D12_FEATURE_SHADER_MODEL, - &shaderModel, - sizeof(shaderModel)); - if (SUCCEEDED(res) && shaderModel.HighestShaderModel >= D3D_SHADER_MODEL_6_0) { - supports_dxil = true; + res = ID3D12Device_CheckFeatureSupport( + device, + D3D12_FEATURE_D3D12_OPTIONS, + &featureOptions, + sizeof(featureOptions)); + if (SUCCEEDED(res) && featureOptions.ResourceBindingTier >= D3D12_RESOURCE_BINDING_TIER_2) { + supports_64UAVs = true; + } + } + + // Only check for SM6 support if DXIL is provided + if (has_dxil) { + D3D12_FEATURE_DATA_SHADER_MODEL shaderModel; + shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_0; + + res = ID3D12Device_CheckFeatureSupport( + device, + D3D12_FEATURE_SHADER_MODEL, + &shaderModel, + sizeof(shaderModel)); + if (SUCCEEDED(res) && shaderModel.HighestShaderModel >= D3D_SHADER_MODEL_6_0) { + supports_dxil = true; + } } ID3D12Device_Release(device); } + IDXGIAdapter1_Release(adapter); IDXGIFactory1_Release(factory); SDL_UnloadObject(d3d12Dll); SDL_UnloadObject(dxgiDll); + if (!supports_64UAVs && needs_64UAVs) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: Tier 2 Resource Binding is not supported"); + return false; + } + if (!supports_dxil && !has_dxbc) { SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: DXIL is not supported and DXBC is not being provided"); return false; @@ -8489,7 +8689,7 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) #if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) && defined(HAVE_IDXGIINFOQUEUE) static bool D3D12_INTERNAL_TryInitializeDXGIDebug(D3D12Renderer *renderer) { - PFN_DXGI_GET_DEBUG_INTERFACE DXGIGetDebugInterfaceFunc; + pfnDXGIGetDebugInterface pDXGIGetDebugInterface; HRESULT res; renderer->dxgidebug_dll = SDL_LoadObject(DXGIDEBUG_DLL); @@ -8497,19 +8697,19 @@ static bool D3D12_INTERNAL_TryInitializeDXGIDebug(D3D12Renderer *renderer) return false; } - DXGIGetDebugInterfaceFunc = (PFN_DXGI_GET_DEBUG_INTERFACE)SDL_LoadFunction( + pDXGIGetDebugInterface = (pfnDXGIGetDebugInterface)SDL_LoadFunction( renderer->dxgidebug_dll, DXGI_GET_DEBUG_INTERFACE_FUNC); - if (DXGIGetDebugInterfaceFunc == NULL) { + if (pDXGIGetDebugInterface == NULL) { return false; } - res = DXGIGetDebugInterfaceFunc(&D3D_IID_IDXGIDebug, (void **)&renderer->dxgiDebug); + res = pDXGIGetDebugInterface(&D3D_IID_IDXGIDebug, (void **)&renderer->dxgiDebug); if (FAILED(res)) { return false; } - res = DXGIGetDebugInterfaceFunc(&D3D_IID_IDXGIInfoQueue, (void **)&renderer->dxgiInfoQueue); + res = pDXGIGetDebugInterface(&D3D_IID_IDXGIInfoQueue, (void **)&renderer->dxgiInfoQueue); if (FAILED(res)) { return false; } @@ -8518,19 +8718,35 @@ static bool D3D12_INTERNAL_TryInitializeDXGIDebug(D3D12Renderer *renderer) } #endif -static bool D3D12_INTERNAL_TryInitializeD3D12Debug(D3D12Renderer *renderer) -{ - PFN_D3D12_GET_DEBUG_INTERFACE D3D12GetDebugInterfaceFunc; +static bool D3D12_INTERNAL_TryInitializeD3D12Debug(D3D12Renderer *renderer +#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + , ID3D12DeviceFactory * factory +#endif +) { + PFN_D3D12_GET_DEBUG_INTERFACE pD3D12GetDebugInterface; HRESULT res; - D3D12GetDebugInterfaceFunc = (PFN_D3D12_GET_DEBUG_INTERFACE)SDL_LoadFunction( +#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + if (factory) { + res = ID3D12DeviceFactory_GetConfigurationInterface(factory, &D3D_CLSID_ID3D12Debug, &D3D_IID_ID3D12Debug, (void **)&renderer->d3d12Debug); + + if (FAILED(res)) { + return false; + } + + ID3D12Debug_EnableDebugLayer(renderer->d3d12Debug); + return true; + } +#endif + + pD3D12GetDebugInterface = (PFN_D3D12_GET_DEBUG_INTERFACE)SDL_LoadFunction( renderer->d3d12_dll, D3D12_GET_DEBUG_INTERFACE_FUNC); - if (D3D12GetDebugInterfaceFunc == NULL) { + if (pD3D12GetDebugInterface == NULL) { return false; } - res = D3D12GetDebugInterfaceFunc(D3D_GUID(D3D_IID_ID3D12Debug), (void **)&renderer->d3d12Debug); + res = pD3D12GetDebugInterface(D3D_GUID(D3D_IID_ID3D12Debug), (void **)&renderer->d3d12Debug); if (FAILED(res)) { return false; } @@ -8693,13 +8909,14 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD PFN_D3D12_XBOX_CREATE_DEVICE D3D12XboxCreateDeviceFunc; D3D12XBOX_CREATE_DEVICE_PARAMETERS createDeviceParams; #else - PFN_CREATE_DXGI_FACTORY1 CreateDXGIFactoryFunc; + pfnCreateDXGIFactory1 pCreateDXGIFactory1; IDXGIFactory1 *factory1; IDXGIFactory5 *factory5; IDXGIFactory6 *factory6; DXGI_ADAPTER_DESC1 adapterDesc; LARGE_INTEGER umdVersion; - PFN_D3D12_CREATE_DEVICE D3D12CreateDeviceFunc; + PFN_D3D12_GET_INTERFACE pD3D12GetInterface; + PFN_D3D12_CREATE_DEVICE pD3D12CreateDevice; #endif D3D12_FEATURE_DATA_ARCHITECTURE architecture; D3D12_COMMAND_QUEUE_DESC queueDesc; @@ -8729,17 +8946,43 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD hasDxgiDebug = true; #endif +#ifdef USE_PIX_RUNTIME + // Load the PIX runtime DLL so that we can set D3D12 debug events on command lists + renderer->winpixeventruntime_dll = SDL_LoadObject(WINPIXEVENTRUNTIME_DLL); + WinPixEventRuntimeFns *fns = &renderer->winpixeventruntimeFns; + if (renderer->winpixeventruntime_dll) { + // Load the specific functions we need from the PIX runtime + fns->pBeginEventOnCommandList = (pfnBeginEventOnCommandList)SDL_LoadFunction( + renderer->winpixeventruntime_dll, + PIX_BEGIN_EVENT_ON_COMMAND_LIST_FUNC); + fns->pEndEventOnCommandList = (pfnEndEventOnCommandList)SDL_LoadFunction( + renderer->winpixeventruntime_dll, + PIX_END_EVENT_ON_COMMAND_LIST_FUNC); + fns->pSetMarkerOnCommandList = (pfnSetMarkerOnCommandList)SDL_LoadFunction( + renderer->winpixeventruntime_dll, + PIX_SET_MARKER_ON_COMMAND_LIST_FUNC); + } else { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, + "WinPixEventRuntime.dll is not available. " + "It is required for SDL_Push/PopGPUDebugGroup and SDL_InsertGPUDebugLabel to function correctly. " + "See here for instructions on how to obtain it: https://devblogs.microsoft.com/pix/winpixeventruntime/"); + fns->pBeginEventOnCommandList = NULL; + fns->pEndEventOnCommandList = NULL; + fns->pSetMarkerOnCommandList = NULL; + } +#endif + // Load the CreateDXGIFactory1 function - CreateDXGIFactoryFunc = (PFN_CREATE_DXGI_FACTORY1)SDL_LoadFunction( + pCreateDXGIFactory1 = (pfnCreateDXGIFactory1)SDL_LoadFunction( renderer->dxgi_dll, CREATE_DXGI_FACTORY1_FUNC); - if (CreateDXGIFactoryFunc == NULL) { + if (pCreateDXGIFactory1 == NULL) { D3D12_INTERNAL_DestroyRenderer(renderer); SET_STRING_ERROR_AND_RETURN("Could not load function: " CREATE_DXGI_FACTORY1_FUNC, NULL); } // Create the DXGI factory - res = CreateDXGIFactoryFunc( + res = pCreateDXGIFactory1( &D3D_IID_IDXGIFactory1, (void **)&factory1); if (FAILED(res)) { @@ -8864,26 +9107,63 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD SET_STRING_ERROR_AND_RETURN("Could not load function: D3D12XboxCreateDevice", NULL); } #else - D3D12CreateDeviceFunc = (PFN_D3D12_CREATE_DEVICE)SDL_LoadFunction( + pD3D12CreateDevice = (PFN_D3D12_CREATE_DEVICE)SDL_LoadFunction( renderer->d3d12_dll, D3D12_CREATE_DEVICE_FUNC); - if (D3D12CreateDeviceFunc == NULL) { + if (pD3D12CreateDevice == NULL) { D3D12_INTERNAL_DestroyRenderer(renderer); SET_STRING_ERROR_AND_RETURN("Could not load function: " D3D12_CREATE_DEVICE_FUNC, NULL); } #endif - renderer->D3D12SerializeRootSignature_func = (PFN_D3D12_SERIALIZE_ROOT_SIGNATURE)SDL_LoadFunction( + renderer->pD3D12SerializeRootSignature = (PFN_D3D12_SERIALIZE_ROOT_SIGNATURE)SDL_LoadFunction( renderer->d3d12_dll, D3D12_SERIALIZE_ROOT_SIGNATURE_FUNC); - if (renderer->D3D12SerializeRootSignature_func == NULL) { + if (renderer->pD3D12SerializeRootSignature == NULL) { D3D12_INTERNAL_DestroyRenderer(renderer); SET_STRING_ERROR_AND_RETURN("Could not load function: " D3D12_SERIALIZE_ROOT_SIGNATURE_FUNC, NULL); } +#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + // A device factory allows a D3D12 redistributable provided by the client to be loaded. + ID3D12DeviceFactory *factory = NULL; + + if (SDL_HasProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_PATH_STRING) && SDL_HasProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_VERSION_NUMBER)) { + int d3d12SDKVersion = SDL_GetNumberProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_VERSION_NUMBER, 0); + const char *d3d12SDKPath = SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_AGILITY_SDK_PATH_STRING, ".\\D3D12\\"); + ID3D12SDKConfiguration *sdk_config = NULL; + + pD3D12GetInterface = (PFN_D3D12_GET_INTERFACE)SDL_LoadFunction( + renderer->d3d12_dll, + D3D12_GET_INTERFACE_FUNC); + if (pD3D12GetInterface == NULL) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not load D3D12GetInterface, custom D3D12 SDK will not load."); + } else if (SUCCEEDED(pD3D12GetInterface(D3D_GUID(D3D_CLSID_ID3D12SDKConfiguration), D3D_GUID(D3D_IID_ID3D12SDKConfiguration), (void**) &sdk_config))) { + ID3D12SDKConfiguration1 *sdk_config1 = NULL; + if (SUCCEEDED(IUnknown_QueryInterface(sdk_config, &D3D_IID_ID3D12SDKConfiguration1, (void**) &sdk_config1))) { + if (SUCCEEDED(ID3D12SDKConfiguration1_CreateDeviceFactory(sdk_config1, d3d12SDKVersion, d3d12SDKPath, &D3D_IID_ID3D12DeviceFactory, (void**) &factory))) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Loaded vendored D3D12Core.dll"); + } else { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Failed to load vendored D3D12Core.dll"); + } + + ID3D12SDKConfiguration1_Release(sdk_config1); + } + + ID3D12SDKConfiguration_Release(sdk_config); + } else { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Failed to load vendored D3D12 SDK Configuration"); + } + } +#endif + // Initialize the D3D12 debug layer, if applicable if (debugMode) { - bool hasD3d12Debug = D3D12_INTERNAL_TryInitializeD3D12Debug(renderer); + bool hasD3d12Debug = D3D12_INTERNAL_TryInitializeD3D12Debug(renderer +#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + , factory +#endif + ); #if (defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) if (hasD3d12Debug) { SDL_LogInfo( @@ -8937,15 +9217,33 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD s_Device = renderer->device; } #else - res = D3D12CreateDeviceFunc( - (IUnknown *)renderer->adapter, - D3D_FEATURE_LEVEL_CHOICE, - D3D_GUID(D3D_IID_ID3D12Device), - (void **)&renderer->device); - if (FAILED(res)) { - D3D12_INTERNAL_DestroyRenderer(renderer); - CHECK_D3D12_ERROR_AND_RETURN("Could not create D3D12Device", NULL); + if (factory) { + ID3D12DeviceFactory_SetFlags(factory, D3D12_DEVICE_FACTORY_FLAG_ALLOW_RETURNING_EXISTING_DEVICE); + res = ID3D12DeviceFactory_CreateDevice( + factory, + (IUnknown *)renderer->adapter, + D3D_FEATURE_LEVEL_CHOICE, + D3D_GUID(D3D_IID_ID3D12Device), + (void**)&renderer->device); + + ID3D12DeviceFactory_Release(factory); + + if (FAILED(res)) { + D3D12_INTERNAL_DestroyRenderer(renderer); + CHECK_D3D12_ERROR_AND_RETURN("Could not create D3D12Device", NULL); + } + } else { + res = pD3D12CreateDevice( + (IUnknown *)renderer->adapter, + D3D_FEATURE_LEVEL_CHOICE, + D3D_GUID(D3D_IID_ID3D12Device), + (void **)&renderer->device); + + if (FAILED(res)) { + D3D12_INTERNAL_DestroyRenderer(renderer); + CHECK_D3D12_ERROR_AND_RETURN("Could not create D3D12Device", NULL); + } } // Initialize the D3D12 debug info queue, if applicable @@ -8987,6 +9285,22 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD } #endif + // Check for unrestricted texture-buffer copy pitch support + D3D12_FEATURE_DATA_D3D12_OPTIONS13 options13; + res = ID3D12Device_CheckFeatureSupport( + renderer->device, + D3D12_FEATURE_D3D12_OPTIONS13, + &options13, + sizeof(options13)); + + if (SUCCEEDED(res)) { + renderer->UnrestrictedBufferTextureCopyPitchSupported = options13.UnrestrictedBufferTextureCopyPitchSupported; + } + else + { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "CheckFeatureSupport for UnrestrictedBufferTextureCopyPitchSupported failed. You may need to provide a vendored D3D12Core.dll through the Agility SDK on older platforms."); + } + // Create command queue #if (defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) if (s_CommandQueue != NULL) { @@ -9196,7 +9510,7 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD renderer->debug_mode = debugMode; renderer->allowedFramesInFlight = 2; - renderer->semantic = SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING, "TEXCOORD"); + renderer->semantic = SDL_strdup(SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING, "TEXCOORD")); // Blit resources D3D12_INTERNAL_InitBlitResources(renderer); diff --git a/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m b/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m index 26dea454..a057eab7 100644 --- a/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m +++ b/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -31,7 +31,7 @@ // Defines #define METAL_FIRST_VERTEX_BUFFER_SLOT 14 -#define WINDOW_PROPERTY_DATA "SDL_GPUMetalWindowPropertyData" +#define WINDOW_PROPERTY_DATA "SDL.internal.gpu.metal.data" #define SDL_GPU_SHADERSTAGE_COMPUTE 2 #define TRACK_RESOURCE(resource, type, array, count, capacity) \ @@ -423,6 +423,8 @@ static MTLDepthClipMode SDLToMetal_DepthClipMode( // Structs +typedef struct MetalRenderer MetalRenderer; + typedef struct MetalTexture { id handle; @@ -452,6 +454,8 @@ typedef struct MetalFence typedef struct MetalWindowData { SDL_Window *window; + MetalRenderer *renderer; + int refcount; SDL_MetalView view; CAMetalLayer *layer; SDL_GPUPresentMode presentMode; @@ -523,8 +527,6 @@ typedef struct MetalUniformBuffer Uint32 drawOffset; } MetalUniformBuffer; -typedef struct MetalRenderer MetalRenderer; - typedef struct MetalCommandBuffer { CommandBufferCommonHeader common; @@ -914,6 +916,7 @@ static void METAL_INTERNAL_DestroyTextureContainer( container->textures[i]->handle = nil; SDL_free(container->textures[i]); } + SDL_DestroyProperties(container->header.info.props); if (container->debugName != NULL) { SDL_free(container->debugName); } @@ -1115,6 +1118,13 @@ static SDL_GPUGraphicsPipeline *METAL_CreateGraphicsPipeline( SDL_assert_release(!"CreateGraphicsPipeline was passed a vertex shader for the fragment stage"); } } +#ifdef SDL_PLATFORM_VISIONOS + // The default is depth clipping enabled and it can't be changed + if (!createinfo->rasterizer_state.enable_depth_clip) { + SDL_assert_release(!"Rasterizer state enable_depth_clip must be true on this platform"); + } +#endif + pipelineDescriptor = [MTLRenderPipelineDescriptor new]; // Blend @@ -1804,7 +1814,8 @@ static void METAL_UploadToTexture( copyFromBuffer:bufferContainer->activeBuffer->handle sourceOffset:source->offset sourceBytesPerRow:BytesPerRow(destination->w, textureContainer->header.info.format) - sourceBytesPerImage:SDL_CalculateGPUTextureFormatSize(textureContainer->header.info.format, destination->w, destination->h, destination->d) + // sourceBytesPerImage expects the stride between 2D images (slices) of a 3D texture, not the size of the entire region + sourceBytesPerImage:SDL_CalculateGPUTextureFormatSize(textureContainer->header.info.format, destination->w, destination->h, 1) sourceSize:MTLSizeMake(destination->w, destination->h, destination->d) toTexture:metalTexture->handle destinationSlice:destination->layer @@ -2318,6 +2329,8 @@ static void METAL_BeginRenderPass( depthStencilTargetInfo->cycle); passDescriptor.depthAttachment.texture = texture->handle; + passDescriptor.depthAttachment.level = depthStencilTargetInfo->mip_level; + passDescriptor.depthAttachment.slice = depthStencilTargetInfo->layer; passDescriptor.depthAttachment.loadAction = SDLToMetal_LoadOp[depthStencilTargetInfo->load_op]; passDescriptor.depthAttachment.storeAction = SDLToMetal_StoreOp[depthStencilTargetInfo->store_op]; passDescriptor.depthAttachment.clearDepth = depthStencilTargetInfo->clear_depth; @@ -2351,8 +2364,8 @@ static void METAL_BeginRenderPass( if (depthStencilTargetInfo != NULL) { MetalTextureContainer *container = (MetalTextureContainer *)depthStencilTargetInfo->texture; - Uint32 w = container->header.info.width; - Uint32 h = container->header.info.height; + Uint32 w = container->header.info.width >> depthStencilTargetInfo->mip_level; + Uint32 h = container->header.info.height >> depthStencilTargetInfo->mip_level; if (w < vpWidth) { vpWidth = w; @@ -2398,6 +2411,7 @@ static void METAL_BindGraphicsPipeline( { @autoreleasepool { MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer; + MetalGraphicsPipeline *previousPipeline = metalCommandBuffer->graphics_pipeline; MetalGraphicsPipeline *pipeline = (MetalGraphicsPipeline *)graphicsPipeline; SDL_GPURasterizerState *rast = &pipeline->rasterizerState; Uint32 i; @@ -2410,7 +2424,9 @@ static void METAL_BindGraphicsPipeline( [metalCommandBuffer->renderEncoder setTriangleFillMode:SDLToMetal_PolygonMode[pipeline->rasterizerState.fill_mode]]; [metalCommandBuffer->renderEncoder setCullMode:SDLToMetal_CullMode[pipeline->rasterizerState.cull_mode]]; [metalCommandBuffer->renderEncoder setFrontFacingWinding:SDLToMetal_FrontFace[pipeline->rasterizerState.front_face]]; +#ifndef SDL_PLATFORM_VISIONOS [metalCommandBuffer->renderEncoder setDepthClipMode:SDLToMetal_DepthClipMode(pipeline->rasterizerState.enable_depth_clip)]; +#endif [metalCommandBuffer->renderEncoder setDepthBias:((rast->enable_depth_bias) ? rast->depth_bias_constant_factor : 0) slopeScale:((rast->enable_depth_bias) ? rast->depth_bias_slope_factor : 0) @@ -2440,6 +2456,17 @@ static void METAL_BindGraphicsPipeline( metalCommandBuffer); } } + + if (previousPipeline && previousPipeline != pipeline) { + // if the number of uniform buffers has changed, the storage buffers will move as well + // and need a rebind at their new locations + if (previousPipeline->header.num_vertex_uniform_buffers != pipeline->header.num_vertex_uniform_buffers) { + metalCommandBuffer->needVertexStorageBufferBind = true; + } + if (previousPipeline->header.num_fragment_uniform_buffers != pipeline->header.num_fragment_uniform_buffers) { + metalCommandBuffer->needFragmentStorageBufferBind = true; + } + } } } @@ -3518,6 +3545,8 @@ static void METAL_INTERNAL_PerformPendingDestroys( Sint32 i; Uint32 j; + SDL_LockMutex(renderer->disposeLock); + for (i = renderer->bufferContainersToDestroyCount - 1; i >= 0; i -= 1) { referenceCount = 0; for (j = 0; j < renderer->bufferContainersToDestroy[i]->bufferCount; j += 1) { @@ -3547,6 +3576,8 @@ static void METAL_INTERNAL_PerformPendingDestroys( renderer->textureContainersToDestroyCount -= 1; } } + + SDL_UnlockMutex(renderer->disposeLock); } // Fences @@ -3620,7 +3651,7 @@ static bool METAL_SupportsSwapchainComposition( } // This function assumes that it's called from within an autorelease pool -static Uint8 METAL_INTERNAL_CreateSwapchain( +static bool METAL_INTERNAL_CreateSwapchain( MetalRenderer *renderer, MetalWindowData *windowData, SDL_GPUSwapchainComposition swapchainComposition, @@ -3692,7 +3723,7 @@ static Uint8 METAL_INTERNAL_CreateSwapchain( windowData->textureContainer.header.info.width = (Uint32)drawableSize.width; windowData->textureContainer.header.info.height = (Uint32)drawableSize.height; - return 1; + return true; } static bool METAL_SupportsPresentMode( @@ -3722,6 +3753,8 @@ static bool METAL_ClaimWindow( if (windowData == NULL) { windowData = (MetalWindowData *)SDL_calloc(1, sizeof(MetalWindowData)); windowData->window = window; + windowData->renderer = renderer; + windowData->refcount = 1; if (METAL_INTERNAL_CreateSwapchain(renderer, windowData, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_VSYNC)) { SDL_SetPointerProperty(SDL_GetWindowProperties(window), WINDOW_PROPERTY_DATA, windowData); @@ -3742,10 +3775,13 @@ static bool METAL_ClaimWindow( return true; } else { SDL_free(windowData); - SET_STRING_ERROR_AND_RETURN("Could not create swapchain, failed to claim window", false); + return false; } + } else if (windowData->renderer == renderer) { + ++windowData->refcount; + return true; } else { - SET_ERROR_AND_RETURN("%s", "Window already claimed!", false); + SET_STRING_ERROR_AND_RETURN("Window already claimed", false); } } } @@ -3759,7 +3795,15 @@ static void METAL_ReleaseWindow( MetalWindowData *windowData = METAL_INTERNAL_FetchWindowData(window); if (windowData == NULL) { - SET_STRING_ERROR_AND_RETURN("Window is not claimed by this SDL_GpuDevice", ); + return; + } + if (windowData->renderer != renderer) { + SDL_SetError("Window not claimed by this device"); + return; + } + if (windowData->refcount > 1) { + --windowData->refcount; + return; } METAL_Wait(driverData); @@ -3838,7 +3882,7 @@ static bool METAL_INTERNAL_AcquireSwapchainTexture( windowData = METAL_INTERNAL_FetchWindowData(window); if (windowData == NULL) { - SET_STRING_ERROR_AND_RETURN("Window is not claimed by this SDL_GpuDevice", false); + SET_STRING_ERROR_AND_RETURN("Window is not claimed by this SDL_GPUDevice", false); } // Update the window size @@ -4502,11 +4546,21 @@ static SDL_GPUDevice *METAL_CreateDevice(bool debugMode, bool preferLowPower, SD #ifdef SDL_PLATFORM_MACOS hasHardwareSupport = true; + bool allowMacFamily1 = SDL_GetBooleanProperty( + props, + SDL_PROP_GPU_DEVICE_CREATE_METAL_ALLOW_MACFAMILY1_BOOLEAN, + false); if (@available(macOS 10.15, *)) { - hasHardwareSupport = [device supportsFamily:MTLGPUFamilyMac2]; + hasHardwareSupport = allowMacFamily1 ? + [device supportsFamily:MTLGPUFamilyMac1] : + [device supportsFamily:MTLGPUFamilyMac2]; } else if (@available(macOS 10.14, *)) { - hasHardwareSupport = [device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1]; + hasHardwareSupport = allowMacFamily1 ? + [device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v4] : + [device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1]; } +#elif defined(SDL_PLATFORM_VISIONOS) + hasHardwareSupport = true; #else if (@available(iOS 13.0, tvOS 13.0, *)) { hasHardwareSupport = [device supportsFamily:MTLGPUFamilyApple3]; diff --git a/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c b/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c index 068d6820..ae19a138 100644 --- a/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -32,6 +32,7 @@ #include #include "../SDL_sysgpu.h" +#include "../../events/SDL_windowevents_c.h" // Global Vulkan Loader Entry Points @@ -56,6 +57,8 @@ typedef struct VulkanExtensions Uint8 KHR_driver_properties; // Only required for special implementations (i.e. MoltenVK) Uint8 KHR_portability_subset; + // Only required to detect devices using Dozen D3D12 driver + Uint8 MSFT_layered_driver; // Only required for decoding HDR ASTC textures Uint8 EXT_texture_compression_astc_hdr; } VulkanExtensions; @@ -67,7 +70,7 @@ typedef struct VulkanExtensions #define LARGE_ALLOCATION_INCREMENT 67108864 // 64 MiB #define MAX_UBO_SECTION_SIZE 4096 // 4 KiB #define DESCRIPTOR_POOL_SIZE 128 -#define WINDOW_PROPERTY_DATA "SDL_GPUVulkanWindowPropertyData" +#define WINDOW_PROPERTY_DATA "SDL.internal.gpu.vulkan.data" #define IDENTITY_SWIZZLE \ { \ @@ -79,22 +82,6 @@ typedef struct VulkanExtensions // Conversions -static const Uint8 DEVICE_PRIORITY_HIGHPERFORMANCE[] = { - 0, // VK_PHYSICAL_DEVICE_TYPE_OTHER - 3, // VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU - 4, // VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU - 2, // VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU - 1 // VK_PHYSICAL_DEVICE_TYPE_CPU -}; - -static const Uint8 DEVICE_PRIORITY_LOWPOWER[] = { - 0, // VK_PHYSICAL_DEVICE_TYPE_OTHER - 4, // VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU - 3, // VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU - 2, // VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU - 1 // VK_PHYSICAL_DEVICE_TYPE_CPU -}; - static VkPresentModeKHR SDLToVK_PresentMode[] = { VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, @@ -445,6 +432,8 @@ static VkSamplerAddressMode SDLToVK_SamplerAddressMode[] = { // Structures +typedef struct VulkanRenderer VulkanRenderer; +typedef struct VulkanCommandPool VulkanCommandPool; typedef struct VulkanMemoryAllocation VulkanMemoryAllocation; typedef struct VulkanBuffer VulkanBuffer; typedef struct VulkanBufferContainer VulkanBufferContainer; @@ -511,6 +500,7 @@ struct VulkanMemoryAllocation VkDeviceSize usedSpace; Uint8 *mapPointer; SDL_Mutex *memoryLock; + SDL_AtomicInt referenceCount; // Used to avoid defrag races }; typedef struct VulkanMemoryAllocator @@ -613,6 +603,11 @@ struct VulkanTexture VkImageAspectFlags aspectFlags; Uint32 depth; // used for cleanup only + // used to avoid indirection on barriers + Uint32 levelCount; + Uint32 layerCount; + SDL_GPUTextureType type; + // FIXME: It'd be nice if we didn't have to have this on the texture... SDL_GPUTextureUsageFlags usage; // used for defrag transitions only. @@ -679,9 +674,12 @@ typedef struct VulkanFramebuffer typedef struct WindowData { SDL_Window *window; + VulkanRenderer *renderer; + int refcount; SDL_GPUSwapchainComposition swapchainComposition; SDL_GPUPresentMode presentMode; bool needsSwapchainRecreate; + bool needsSurfaceRecreate; Uint32 swapchainCreateWidth; Uint32 swapchainCreateHeight; @@ -940,10 +938,6 @@ typedef struct VulkanFencePool Uint32 availableFenceCapacity; } VulkanFencePool; -typedef struct VulkanCommandPool VulkanCommandPool; - -typedef struct VulkanRenderer VulkanRenderer; - typedef struct VulkanCommandBuffer { CommandBufferCommonHeader common; @@ -1052,10 +1046,18 @@ typedef struct VulkanCommandBuffer Sint32 usedBufferCount; Sint32 usedBufferCapacity; + VulkanBuffer **buffersUsedInPendingTransfers; + Sint32 buffersUsedInPendingTransfersCount; + Sint32 buffersUsedInPendingTransfersCapacity; + VulkanTexture **usedTextures; Sint32 usedTextureCount; Sint32 usedTextureCapacity; + VulkanTexture **texturesUsedInPendingTransfers; + Sint32 texturesUsedInPendingTransfersCount; + Sint32 texturesUsedInPendingTransfersCapacity; + VulkanSampler **usedSamplers; Sint32 usedSamplerCount; Sint32 usedSamplerCapacity; @@ -1079,6 +1081,7 @@ typedef struct VulkanCommandBuffer VulkanFenceHandle *inFlightFence; bool autoReleaseFence; + bool swapchainRequested; bool isDefrag; // Whether this CB was created for defragging } VulkanCommandBuffer; @@ -1092,6 +1095,24 @@ struct VulkanCommandPool Uint32 inactiveCommandBufferCount; }; +// Feature Checks + +typedef struct VulkanFeatures +{ + Uint32 desiredApiVersion; + VkPhysicalDeviceFeatures desiredVulkan10DeviceFeatures; + VkPhysicalDeviceVulkan11Features desiredVulkan11DeviceFeatures; + VkPhysicalDeviceVulkan12Features desiredVulkan12DeviceFeatures; + VkPhysicalDeviceVulkan13Features desiredVulkan13DeviceFeatures; + + bool usesCustomVulkanOptions; + + Uint32 additionalDeviceExtensionCount; + const char **additionalDeviceExtensionNames; + Uint32 additionalInstanceExtensionCount; + const char **additionalInstanceExtensionNames; +} VulkanFeatures; + // Context struct VulkanRenderer @@ -1100,7 +1121,6 @@ struct VulkanRenderer VkPhysicalDevice physicalDevice; VkPhysicalDeviceProperties2KHR physicalDeviceProperties; VkPhysicalDeviceDriverPropertiesKHR physicalDeviceDriverProperties; - VkPhysicalDeviceFeatures desiredDeviceFeatures; VkDevice logicalDevice; Uint8 integratedMemoryNotification; Uint8 outOfDeviceLocalMemoryWarning; @@ -1109,12 +1129,15 @@ struct VulkanRenderer bool debugMode; bool preferLowPower; + bool requireHardwareAcceleration; SDL_PropertiesID props; Uint32 allowedFramesInFlight; VulkanExtensions supports; bool supportsDebugUtils; bool supportsColorspace; + bool supportsPhysicalDeviceProperties2; + bool supportsPortabilityEnumeration; bool supportsFillModeNonSolid; bool supportsMultiDrawIndirect; @@ -1196,6 +1219,10 @@ struct VulkanRenderer SDL_Mutex *descriptorSetLayoutFetchLock; SDL_Mutex *windowLock; + // We don't want transfer commands to block each other, + // but we want all transfers to block during defrag. + SDL_RWLock *defragLock; + Uint8 defragInProgress; VulkanMemoryAllocation **allocationsToDefrag; @@ -1213,6 +1240,7 @@ struct VulkanRenderer static bool VULKAN_INTERNAL_DefragmentMemory(VulkanRenderer *renderer, VulkanCommandBuffer *commandBuffer); static bool VULKAN_INTERNAL_BeginCommandBuffer(VulkanRenderer *renderer, VulkanCommandBuffer *commandBuffer); +static void VULKAN_ReleaseTexture(SDL_GPURenderer *driverData, SDL_GPUTexture *texture); static void VULKAN_ReleaseWindow(SDL_GPURenderer *driverData, SDL_Window *window); static bool VULKAN_Wait(SDL_GPURenderer *driverData); static bool VULKAN_WaitForFences(SDL_GPURenderer *driverData, bool waitAll, SDL_GPUFence *const *fences, Uint32 numFences); @@ -1250,13 +1278,20 @@ static inline const char *VkErrorMessages(VkResult code) #undef ERR_TO_STR } -#define SET_ERROR_AND_RETURN(fmt, msg, ret) \ +#define SET_ERROR(fmt, msg) \ do { \ if (renderer->debugMode) { \ SDL_LogError(SDL_LOG_CATEGORY_GPU, fmt, msg); \ } \ SDL_SetError((fmt), (msg)); \ - return ret; \ + } while (0) + +#define SET_STRING_ERROR(msg) SET_ERROR("%s", msg) + +#define SET_ERROR_AND_RETURN(fmt, msg, ret) \ + do { \ + SET_ERROR(fmt, msg); \ + return ret; \ } while (0) #define SET_STRING_ERROR_AND_RETURN(msg, ret) SET_ERROR_AND_RETURN("%s", msg, ret) @@ -1819,6 +1854,8 @@ static Uint8 VULKAN_INTERNAL_AllocateMemory( allocation->freeRegionCount = 0; allocation->freeRegionCapacity = 1; + SDL_SetAtomicInt(&allocation->referenceCount, 0); + allocation->allocator = allocator; result = renderer->vkAllocateMemory( @@ -2356,7 +2393,7 @@ static Uint8 VULKAN_INTERNAL_BindMemoryForBuffer( // Resource tracking -#define TRACK_RESOURCE(resource, type, array, count, capacity) \ +#define TRACK_RESOURCE(resource, type, array, count, capacity, refcountvar) \ for (Sint32 i = commandBuffer->count - 1; i >= 0; i -= 1) { \ if (commandBuffer->array[i] == resource) { \ return; \ @@ -2371,7 +2408,8 @@ static Uint8 VULKAN_INTERNAL_BindMemoryForBuffer( } \ commandBuffer->array[commandBuffer->count] = resource; \ commandBuffer->count += 1; \ - SDL_AtomicIncRef(&resource->referenceCount); + SDL_AtomicIncRef(&refcountvar) + static void VULKAN_INTERNAL_TrackBuffer( VulkanCommandBuffer *commandBuffer, @@ -2382,7 +2420,23 @@ static void VULKAN_INTERNAL_TrackBuffer( VulkanBuffer *, usedBuffers, usedBufferCount, - usedBufferCapacity) + usedBufferCapacity, + buffer->referenceCount); +} + +// Use this function when a GPU buffer is part of a transfer operation. +// Note that this isn't for transfer buffers, those don't need to refcount their allocations. +static void VULKAN_INTERNAL_TrackBufferTransfer( + VulkanCommandBuffer *commandBuffer, + VulkanBuffer *buffer) +{ + TRACK_RESOURCE( + buffer, + VulkanBuffer *, + buffersUsedInPendingTransfers, + buffersUsedInPendingTransfersCount, + buffersUsedInPendingTransfersCapacity, + buffer->usedRegion->allocation->referenceCount); } static void VULKAN_INTERNAL_TrackTexture( @@ -2394,7 +2448,27 @@ static void VULKAN_INTERNAL_TrackTexture( VulkanTexture *, usedTextures, usedTextureCount, - usedTextureCapacity) + usedTextureCapacity, + texture->referenceCount); +} + +// Use this when a texture is part of a transfer operation. +static void VULKAN_INTERNAL_TrackTextureTransfer( + VulkanCommandBuffer *commandBuffer, + VulkanTexture *texture) +{ + // Textures not managed by our allocator (i.e. the swapchain) don't need to be refcounted. + if (texture->usedRegion == NULL) { + return; + } + + TRACK_RESOURCE( + texture, + VulkanTexture *, + texturesUsedInPendingTransfers, + texturesUsedInPendingTransfersCount, + texturesUsedInPendingTransfersCapacity, + texture->usedRegion->allocation->referenceCount); } static void VULKAN_INTERNAL_TrackSampler( @@ -2406,7 +2480,8 @@ static void VULKAN_INTERNAL_TrackSampler( VulkanSampler *, usedSamplers, usedSamplerCount, - usedSamplerCapacity) + usedSamplerCapacity, + sampler->referenceCount); } static void VULKAN_INTERNAL_TrackGraphicsPipeline( @@ -2418,7 +2493,8 @@ static void VULKAN_INTERNAL_TrackGraphicsPipeline( VulkanGraphicsPipeline *, usedGraphicsPipelines, usedGraphicsPipelineCount, - usedGraphicsPipelineCapacity) + usedGraphicsPipelineCapacity, + graphicsPipeline->referenceCount); } static void VULKAN_INTERNAL_TrackComputePipeline( @@ -2430,7 +2506,8 @@ static void VULKAN_INTERNAL_TrackComputePipeline( VulkanComputePipeline *, usedComputePipelines, usedComputePipelineCount, - usedComputePipelineCapacity) + usedComputePipelineCapacity, + computePipeline->referenceCount); } static void VULKAN_INTERNAL_TrackFramebuffer( @@ -2442,7 +2519,8 @@ static void VULKAN_INTERNAL_TrackFramebuffer( VulkanFramebuffer *, usedFramebuffers, usedFramebufferCount, - usedFramebufferCapacity); + usedFramebufferCapacity, + framebuffer->referenceCount); } static void VULKAN_INTERNAL_TrackUniformBuffer( @@ -2602,12 +2680,16 @@ static void VULKAN_INTERNAL_BufferMemoryBarrier( buffer->transitioned = true; } -static void VULKAN_INTERNAL_TextureSubresourceMemoryBarrier( +static void VULKAN_INTERNAL_TextureMemoryBarrier( VulkanRenderer *renderer, VulkanCommandBuffer *commandBuffer, VulkanTextureUsageMode sourceUsageMode, VulkanTextureUsageMode destinationUsageMode, - VulkanTextureSubresource *textureSubresource) + Uint32 baseLevel, + Uint32 levelCount, + Uint32 baseLayer, + Uint32 layerCount, + VulkanTexture *texture) { VkPipelineStageFlags srcStages = 0; VkPipelineStageFlags dstStages = 0; @@ -2621,12 +2703,12 @@ static void VULKAN_INTERNAL_TextureSubresourceMemoryBarrier( memoryBarrier.newLayout = VK_IMAGE_LAYOUT_UNDEFINED; memoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; memoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - memoryBarrier.image = textureSubresource->parent->image; - memoryBarrier.subresourceRange.aspectMask = textureSubresource->parent->aspectFlags; - memoryBarrier.subresourceRange.baseArrayLayer = textureSubresource->layer; - memoryBarrier.subresourceRange.layerCount = 1; - memoryBarrier.subresourceRange.baseMipLevel = textureSubresource->level; - memoryBarrier.subresourceRange.levelCount = 1; + memoryBarrier.image = texture->image; + memoryBarrier.subresourceRange.aspectMask = texture->aspectFlags; + memoryBarrier.subresourceRange.baseMipLevel = baseLevel; + memoryBarrier.subresourceRange.levelCount = levelCount; + memoryBarrier.subresourceRange.baseArrayLayer = baseLayer; + memoryBarrier.subresourceRange.layerCount = layerCount; if (sourceUsageMode == VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED) { srcStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; @@ -2723,6 +2805,56 @@ static void VULKAN_INTERNAL_TextureSubresourceMemoryBarrier( &memoryBarrier); } +// Transitions the entire texture with a single barrier call. +static void VULKAN_INTERNAL_FullTextureMemoryBarrier( + VulkanRenderer *renderer, + VulkanCommandBuffer *commandBuffer, + VulkanTextureUsageMode sourceUsageMode, + VulkanTextureUsageMode destinationUsageMode, + VulkanTexture *texture) +{ + VULKAN_INTERNAL_TextureMemoryBarrier( + renderer, + commandBuffer, + sourceUsageMode, + destinationUsageMode, + 0, + texture->levelCount, + 0, + texture->layerCount, + texture); +} + +static void VULKAN_INTERNAL_TextureSubresourceMemoryBarrier( + VulkanRenderer *renderer, + VulkanCommandBuffer *commandBuffer, + VulkanTextureUsageMode sourceUsageMode, + VulkanTextureUsageMode destinationUsageMode, + VulkanTextureSubresource *textureSubresource) +{ + Uint32 layerCount = 1; + + // VK_KHR_maintenance9 adds the ability to independently transition arbitrary subsets of slices in a 3D texture + // but otherwise it is not necessarily supported by the driver. + // As a workaround we have to transition the whole texture instead of just the subresource. + // If VK_KHR_maintenance9 becomes widely supported, this can be removed. + // See https://docs.vulkan.org/features/latest/features/proposals/VK_KHR_maintenance9.html#_barriers_with_2d_array_compatible_3d_images + if (textureSubresource->parent->type == SDL_GPU_TEXTURETYPE_3D) { + layerCount = VK_REMAINING_ARRAY_LAYERS; + } + + VULKAN_INTERNAL_TextureMemoryBarrier( + renderer, + commandBuffer, + sourceUsageMode, + destinationUsageMode, + textureSubresource->level, + 1, + textureSubresource->layer, + layerCount, + textureSubresource->parent); +} + static VulkanBufferUsageMode VULKAN_INTERNAL_DefaultBufferUsageMode( VulkanBuffer *buffer) { @@ -2820,13 +2952,12 @@ static void VULKAN_INTERNAL_TextureTransitionFromDefaultUsage( VulkanTextureUsageMode destinationUsageMode, VulkanTexture *texture) { - for (Uint32 i = 0; i < texture->subresourceCount; i += 1) { - VULKAN_INTERNAL_TextureSubresourceTransitionFromDefaultUsage( - renderer, - commandBuffer, - destinationUsageMode, - &texture->subresources[i]); - } + VULKAN_INTERNAL_FullTextureMemoryBarrier( + renderer, + commandBuffer, + VULKAN_INTERNAL_DefaultTextureUsageMode(texture), + destinationUsageMode, + texture); } static void VULKAN_INTERNAL_TextureSubresourceTransitionToDefaultUsage( @@ -2849,14 +2980,12 @@ static void VULKAN_INTERNAL_TextureTransitionToDefaultUsage( VulkanTextureUsageMode sourceUsageMode, VulkanTexture *texture) { - // FIXME: could optimize this barrier - for (Uint32 i = 0; i < texture->subresourceCount; i += 1) { - VULKAN_INTERNAL_TextureSubresourceTransitionToDefaultUsage( - renderer, - commandBuffer, - sourceUsageMode, - &texture->subresources[i]); - } + VULKAN_INTERNAL_FullTextureMemoryBarrier( + renderer, + commandBuffer, + sourceUsageMode, + VULKAN_INTERNAL_DefaultTextureUsageMode(texture), + texture); } // Resource Disposal @@ -3062,7 +3191,9 @@ static void VULKAN_INTERNAL_DestroyCommandPool( SDL_free(commandBuffer->waitSemaphores); SDL_free(commandBuffer->signalSemaphores); SDL_free(commandBuffer->usedBuffers); + SDL_free(commandBuffer->buffersUsedInPendingTransfers); SDL_free(commandBuffer->usedTextures); + SDL_free(commandBuffer->texturesUsedInPendingTransfers); SDL_free(commandBuffer->usedSamplers); SDL_free(commandBuffer->usedGraphicsPipelines); SDL_free(commandBuffer->usedComputePipelines); @@ -3155,7 +3286,7 @@ static void VULKAN_INTERNAL_DestroySampler( SDL_free(vulkanSampler); } -static void VULKAN_INTERNAL_DestroySwapchain( +static void VULKAN_INTERNAL_DestroySwapchainImage( VulkanRenderer *renderer, WindowData *windowData) { @@ -3181,22 +3312,6 @@ static void VULKAN_INTERNAL_DestroySwapchain( SDL_free(windowData->textureContainers); windowData->textureContainers = NULL; - if (windowData->swapchain) { - renderer->vkDestroySwapchainKHR( - renderer->logicalDevice, - windowData->swapchain, - NULL); - windowData->swapchain = VK_NULL_HANDLE; - } - - if (windowData->surface) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); - windowData->surface = VK_NULL_HANDLE; - } - for (i = 0; i < MAX_FRAMES_IN_FLIGHT; i += 1) { if (windowData->imageAvailableSemaphore[i]) { renderer->vkDestroySemaphore( @@ -3221,6 +3336,25 @@ static void VULKAN_INTERNAL_DestroySwapchain( windowData->imageCount = 0; } +static void VULKAN_INTERNAL_DestroySwapchain( + VulkanRenderer *renderer, + WindowData *windowData) +{ + if (windowData == NULL) { + return; + } + + VULKAN_INTERNAL_DestroySwapchainImage(renderer, windowData); + + if (windowData->swapchain) { + renderer->vkDestroySwapchainKHR( + renderer->logicalDevice, + windowData->swapchain, + NULL); + windowData->swapchain = VK_NULL_HANDLE; + } +} + static void VULKAN_INTERNAL_DestroyGraphicsPipelineResourceLayout( VulkanRenderer *renderer, VulkanGraphicsPipelineResourceLayout *resourceLayout) @@ -4150,9 +4284,8 @@ static VulkanBuffer *VULKAN_INTERNAL_CreateBuffer( renderer->logicalDevice, buffer->buffer, NULL); - SDL_free(buffer); - return NULL; + SET_STRING_ERROR_AND_RETURN("Failed to bind memory for buffer!", NULL); } buffer->usedRegion->vulkanBuffer = buffer; // lol @@ -4339,7 +4472,9 @@ static bool VULKAN_INTERNAL_QuerySwapchainSupport( &supportsPresent); // Initialize these in case anything fails + outputDetails->formats = NULL; outputDetails->formatsLength = 0; + outputDetails->presentModes = NULL; outputDetails->presentModesLength = 0; if (!supportsPresent) { @@ -4362,22 +4497,33 @@ static bool VULKAN_INTERNAL_QuerySwapchainSupport( surface, &outputDetails->formatsLength, NULL); - CHECK_VULKAN_ERROR_AND_RETURN(result, vkGetPhysicalDeviceSurfaceFormatsKHR, false); + if (result != VK_SUCCESS) { + // Make sure the driver didn't mess up this value. + outputDetails->formatsLength = 0; + CHECK_VULKAN_ERROR_AND_RETURN(result, vkGetPhysicalDeviceSurfaceFormatsKHR, false); + } result = renderer->vkGetPhysicalDeviceSurfacePresentModesKHR( physicalDevice, surface, &outputDetails->presentModesLength, NULL); - CHECK_VULKAN_ERROR_AND_RETURN(result, vkGetPhysicalDeviceSurfacePresentModesKHR, false); + if (result != VK_SUCCESS) { + // Make sure the driver didn't mess up this value. + outputDetails->presentModesLength = 0; + // Reset this one, too. + outputDetails->formatsLength = 0; + CHECK_VULKAN_ERROR_AND_RETURN(result, vkGetPhysicalDeviceSurfacePresentModesKHR, false); + } // Generate the arrays, if applicable - outputDetails->formats = NULL; if (outputDetails->formatsLength != 0) { outputDetails->formats = (VkSurfaceFormatKHR *)SDL_malloc( sizeof(VkSurfaceFormatKHR) * outputDetails->formatsLength); if (!outputDetails->formats) { // OOM + outputDetails->formatsLength = 0; + outputDetails->presentModesLength = 0; return false; } @@ -4388,17 +4534,22 @@ static bool VULKAN_INTERNAL_QuerySwapchainSupport( outputDetails->formats); if (result != VK_SUCCESS) { SDL_free(outputDetails->formats); + outputDetails->formats = NULL; + outputDetails->formatsLength = 0; + outputDetails->presentModesLength = 0; CHECK_VULKAN_ERROR_AND_RETURN(result, vkGetPhysicalDeviceSurfaceFormatsKHR, false); } } - outputDetails->presentModes = NULL; if (outputDetails->presentModesLength != 0) { outputDetails->presentModes = (VkPresentModeKHR *)SDL_malloc( sizeof(VkPresentModeKHR) * outputDetails->presentModesLength); if (!outputDetails->presentModes) { // OOM SDL_free(outputDetails->formats); + outputDetails->formats = NULL; + outputDetails->formatsLength = 0; + outputDetails->presentModesLength = 0; return false; } @@ -4410,6 +4561,10 @@ static bool VULKAN_INTERNAL_QuerySwapchainSupport( if (result != VK_SUCCESS) { SDL_free(outputDetails->formats); SDL_free(outputDetails->presentModes); + outputDetails->formats = NULL; + outputDetails->presentModes = NULL; + outputDetails->formatsLength = 0; + outputDetails->presentModesLength = 0; CHECK_VULKAN_ERROR_AND_RETURN(result, vkGetPhysicalDeviceSurfacePresentModesKHR, false); } } @@ -4475,36 +4630,11 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( windowData->frameCounter = 0; - SDL_VideoDevice *_this = SDL_GetVideoDevice(); - SDL_assert(_this && _this->Vulkan_CreateSurface); - - // Each swapchain must have its own surface. - if (!_this->Vulkan_CreateSurface( - _this, - windowData->window, - renderer->instance, - NULL, // FIXME: VAllocationCallbacks - &windowData->surface)) { - return false; - } - SDL_assert(windowData->surface); - if (!VULKAN_INTERNAL_QuerySwapchainSupport( renderer, renderer->physicalDevice, windowData->surface, &swapchainSupportDetails)) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); - windowData->surface = VK_NULL_HANDLE; - if (swapchainSupportDetails.formatsLength > 0) { - SDL_free(swapchainSupportDetails.formats); - } - if (swapchainSupportDetails.presentModesLength > 0) { - SDL_free(swapchainSupportDetails.presentModes); - } return false; } @@ -4537,12 +4667,6 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( swapchainSupportDetails.presentModesLength); if (!hasValidSwapchainComposition || !hasValidPresentMode) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); - windowData->surface = VK_NULL_HANDLE; - if (swapchainSupportDetails.formatsLength > 0) { SDL_free(swapchainSupportDetails.formats); } @@ -4563,11 +4687,6 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( // NVIDIA + Win32 can return 0 extent when the window is minimized. Try again! if (swapchainSupportDetails.capabilities.currentExtent.width == 0 || swapchainSupportDetails.capabilities.currentExtent.height == 0) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); - windowData->surface = VK_NULL_HANDLE; if (swapchainSupportDetails.formatsLength > 0) { SDL_free(swapchainSupportDetails.formats); } @@ -4654,14 +4773,18 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( swapchainCreateInfo.compositeAlpha = compositeAlphaFlag; swapchainCreateInfo.presentMode = SDLToVK_PresentMode[windowData->presentMode]; swapchainCreateInfo.clipped = VK_TRUE; - swapchainCreateInfo.oldSwapchain = VK_NULL_HANDLE; - + // The old swapchain could belong to a surface that no longer exists due to app switching. + swapchainCreateInfo.oldSwapchain = windowData->needsSurfaceRecreate ? (VkSwapchainKHR)0 : windowData->swapchain; vulkanResult = renderer->vkCreateSwapchainKHR( renderer->logicalDevice, &swapchainCreateInfo, NULL, &windowData->swapchain); + if (swapchainCreateInfo.oldSwapchain != VK_NULL_HANDLE) { + renderer->vkDestroySwapchainKHR(renderer->logicalDevice, swapchainCreateInfo.oldSwapchain, NULL); + } + if (swapchainSupportDetails.formatsLength > 0) { SDL_free(swapchainSupportDetails.formats); } @@ -4670,11 +4793,7 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( } if (vulkanResult != VK_SUCCESS) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); - windowData->surface = VK_NULL_HANDLE; + windowData->swapchain = VK_NULL_HANDLE; CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSwapchainKHR, false); } @@ -4689,15 +4808,10 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( sizeof(VulkanTextureContainer) * windowData->imageCount); if (!windowData->textureContainers) { // OOM - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); renderer->vkDestroySwapchainKHR( renderer->logicalDevice, windowData->swapchain, NULL); - windowData->surface = VK_NULL_HANDLE; windowData->swapchain = VK_NULL_HANDLE; return false; } @@ -4737,6 +4851,9 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( windowData->textureContainers[i].activeTexture->aspectFlags = VK_IMAGE_ASPECT_COLOR_BIT; windowData->textureContainers[i].activeTexture->depth = 1; windowData->textureContainers[i].activeTexture->usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET; + windowData->textureContainers[i].activeTexture->levelCount = 1; + windowData->textureContainers[i].activeTexture->layerCount = 1; + windowData->textureContainers[i].activeTexture->type = SDL_GPU_TEXTURETYPE_2D; windowData->textureContainers[i].activeTexture->container = &windowData->textureContainers[i]; SDL_SetAtomicInt(&windowData->textureContainers[i].activeTexture->referenceCount, 0); @@ -4755,15 +4872,10 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( windowData->format, windowData->swapchainSwizzle, &windowData->textureContainers[i].activeTexture->subresources[0].renderTargetViews[0])) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); renderer->vkDestroySwapchainKHR( renderer->logicalDevice, windowData->swapchain, NULL); - windowData->surface = VK_NULL_HANDLE; windowData->swapchain = VK_NULL_HANDLE; return false; } @@ -4783,15 +4895,10 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( &windowData->imageAvailableSemaphore[i]); if (vulkanResult != VK_SUCCESS) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); renderer->vkDestroySwapchainKHR( renderer->logicalDevice, windowData->swapchain, NULL); - windowData->surface = VK_NULL_HANDLE; windowData->swapchain = VK_NULL_HANDLE; CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false); } @@ -4809,15 +4916,10 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( &windowData->renderFinishedSemaphore[i]); if (vulkanResult != VK_SUCCESS) { - renderer->vkDestroySurfaceKHR( - renderer->instance, - windowData->surface, - NULL); renderer->vkDestroySwapchainKHR( renderer->logicalDevice, windowData->swapchain, NULL); - windowData->surface = VK_NULL_HANDLE; windowData->swapchain = VK_NULL_HANDLE; CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false); } @@ -4931,9 +5033,7 @@ static void VULKAN_DestroyDevice( j); } - if (renderer->memoryAllocator->subAllocators[i].allocations != NULL) { - SDL_free(renderer->memoryAllocator->subAllocators[i].allocations); - } + SDL_free(renderer->memoryAllocator->subAllocators[i].allocations); SDL_free(renderer->memoryAllocator->subAllocators[i].sortedFreeRegions); } @@ -5624,7 +5724,6 @@ static void VULKAN_PopDebugGroup( static VulkanTexture *VULKAN_INTERNAL_CreateTexture( VulkanRenderer *renderer, - bool transitionToDefaultLayout, const SDL_GPUTextureCreateInfo *createinfo) { VkResult vulkanResult; @@ -5640,6 +5739,9 @@ static VulkanTexture *VULKAN_INTERNAL_CreateTexture( texture->swizzle = SwizzleForSDLFormat(createinfo->format); texture->depth = depth; texture->usage = createinfo->usage; + texture->levelCount = createinfo->num_levels; + texture->layerCount = (createinfo->type == SDL_GPU_TEXTURETYPE_3D) ? 1 : createinfo->layer_count_or_depth; + texture->type = createinfo->type; SDL_SetAtomicInt(&texture->referenceCount, 0); if (IsDepthFormat(createinfo->format)) { @@ -5852,18 +5954,6 @@ static VulkanTexture *VULKAN_INTERNAL_CreateTexture( &nameInfo); } - if (transitionToDefaultLayout) { - // Let's transition to the default barrier state, because for some reason Vulkan doesn't let us do that with initialLayout. - VulkanCommandBuffer *barrierCommandBuffer = (VulkanCommandBuffer *)VULKAN_AcquireCommandBuffer((SDL_GPURenderer *)renderer); - VULKAN_INTERNAL_TextureTransitionToDefaultUsage( - renderer, - barrierCommandBuffer, - VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, - texture); - VULKAN_INTERNAL_TrackTexture(barrierCommandBuffer, texture); - VULKAN_Submit((SDL_GPUCommandBuffer *)barrierCommandBuffer); - } - return texture; } @@ -5930,15 +6020,8 @@ static void VULKAN_INTERNAL_CycleActiveTexture( // No texture is available, generate a new one. texture = VULKAN_INTERNAL_CreateTexture( renderer, - false, &container->header.info); - VULKAN_INTERNAL_TextureTransitionToDefaultUsage( - renderer, - commandBuffer, - VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, - texture); - if (!texture) { return; } @@ -5956,6 +6039,13 @@ static void VULKAN_INTERNAL_CycleActiveTexture( container->textureCount += 1; container->activeTexture = texture; + + // Transition texture after storing it as the memory barrier might need to read the texture's container info + VULKAN_INTERNAL_TextureTransitionToDefaultUsage( + renderer, + commandBuffer, + VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, + texture); } static VulkanBuffer *VULKAN_INTERNAL_PrepareBufferForWrite( @@ -6057,7 +6147,6 @@ static VkRenderPass VULKAN_INTERNAL_CreateRenderPass( colorAttachmentReferences[colorAttachmentReferenceCount].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachmentDescriptionCount += 1; - colorAttachmentReferenceCount += 1; if (colorTargetInfos[i].store_op == SDL_GPU_STOREOP_RESOLVE || colorTargetInfos[i].store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) { VulkanTextureContainer *resolveContainer = (VulkanTextureContainer *)colorTargetInfos[i].resolve_texture; @@ -6072,12 +6161,16 @@ static VkRenderPass VULKAN_INTERNAL_CreateRenderPass( attachmentDescriptions[attachmentDescriptionCount].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachmentDescriptions[attachmentDescriptionCount].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - resolveReferences[resolveReferenceCount].attachment = attachmentDescriptionCount; - resolveReferences[resolveReferenceCount].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + resolveReferences[colorAttachmentReferenceCount].attachment = attachmentDescriptionCount; + resolveReferences[colorAttachmentReferenceCount].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachmentDescriptionCount += 1; resolveReferenceCount += 1; + } else { + resolveReferences[colorAttachmentReferenceCount].attachment = VK_ATTACHMENT_UNUSED; } + + colorAttachmentReferenceCount += 1; } subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; @@ -6829,7 +6922,6 @@ static SDL_GPUTexture *VULKAN_CreateTexture( texture = VULKAN_INTERNAL_CreateTexture( renderer, - true, createinfo); if (texture == NULL) { @@ -6861,6 +6953,25 @@ static SDL_GPUTexture *VULKAN_CreateTexture( texture->container = container; texture->containerIndex = 0; + // Let's transition to the default barrier state, because for some reason Vulkan doesn't let us do that with initialLayout. + // Only do this after "container" is set, so the texture + // is fully initialized before any Submit that could trigger defrag. + { + VulkanCommandBuffer *barrierCommandBuffer = (VulkanCommandBuffer *)VULKAN_AcquireCommandBuffer((SDL_GPURenderer *)renderer); + VULKAN_INTERNAL_TextureTransitionToDefaultUsage( + renderer, + barrierCommandBuffer, + VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, + texture); + + VULKAN_INTERNAL_TrackTexture(barrierCommandBuffer, texture); + + if (!VULKAN_Submit((SDL_GPUCommandBuffer *)barrierCommandBuffer)) { + VULKAN_ReleaseTexture((SDL_GPURenderer *)renderer, (SDL_GPUTexture *)container); + return NULL; + } + } + return (SDL_GPUTexture *)container; } @@ -6954,10 +7065,10 @@ static void VULKAN_ReleaseTexture( VULKAN_INTERNAL_ReleaseTexture(renderer, vulkanTextureContainer->textures[i]); } + SDL_DestroyProperties(vulkanTextureContainer->header.info.props); + // Containers are just client handles, so we can destroy immediately - if (vulkanTextureContainer->debugName != NULL) { - SDL_free(vulkanTextureContainer->debugName); - } + SDL_free(vulkanTextureContainer->debugName); SDL_free(vulkanTextureContainer->textures); SDL_free(vulkanTextureContainer); @@ -7154,6 +7265,8 @@ static VkRenderPass VULKAN_INTERNAL_FetchRenderPass( key.sampleCount = VK_SAMPLE_COUNT_1_BIT; if (numColorTargets > 0) { key.sampleCount = SDLToVK_SampleCount[((VulkanTextureContainer *)colorTargetInfos[0].texture)->header.info.sample_count]; + } else if (numColorTargets == 0 && depthStencilTargetInfo != NULL) { + key.sampleCount = SDLToVK_SampleCount[((VulkanTextureContainer *)depthStencilTargetInfo->texture)->header.info.sample_count]; } key.numColorTargets = numColorTargets; @@ -7262,8 +7375,8 @@ static VulkanFramebuffer *VULKAN_INTERNAL_FetchFramebuffer( } else { VulkanTextureSubresource *subresource = VULKAN_INTERNAL_FetchTextureSubresource( (VulkanTextureContainer *)depthStencilTargetInfo->texture, - 0, - 0); + depthStencilTargetInfo->layer, + depthStencilTargetInfo->mip_level); key.depthStencilAttachmentView = subresource->depthStencilView; } @@ -7318,8 +7431,8 @@ static VulkanFramebuffer *VULKAN_INTERNAL_FetchFramebuffer( if (depthStencilTargetInfo != NULL) { VulkanTextureSubresource *subresource = VULKAN_INTERNAL_FetchTextureSubresource( (VulkanTextureContainer *)depthStencilTargetInfo->texture, - 0, - 0); + depthStencilTargetInfo->layer, + depthStencilTargetInfo->mip_level); imageViewAttachments[attachmentCount] = subresource->depthStencilView; attachmentCount += 1; @@ -7799,8 +7912,8 @@ static void VULKAN_BeginRenderPass( if (depthStencilTargetInfo != NULL) { VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)depthStencilTargetInfo->texture; - w = textureContainer->header.info.width; - h = textureContainer->header.info.height; + w = textureContainer->header.info.width >> depthStencilTargetInfo->mip_level; + h = textureContainer->header.info.height >> depthStencilTargetInfo->mip_level; // The framebuffer cannot be larger than the smallest attachment. @@ -7855,8 +7968,8 @@ static void VULKAN_BeginRenderPass( renderer, vulkanCommandBuffer, textureContainer, - 0, - 0, + depthStencilTargetInfo->layer, + depthStencilTargetInfo->mip_level, depthStencilTargetInfo->cycle, VULKAN_TEXTURE_USAGE_MODE_DEPTH_STENCIL_ATTACHMENT); @@ -7896,15 +8009,17 @@ static void VULKAN_BeginRenderPass( clearValues = SDL_stack_alloc(VkClearValue, clearCount); - for (i = 0; i < totalColorAttachmentCount; i += 1) { - clearValues[i].color.float32[0] = colorTargetInfos[i].clear_color.r; - clearValues[i].color.float32[1] = colorTargetInfos[i].clear_color.g; - clearValues[i].color.float32[2] = colorTargetInfos[i].clear_color.b; - clearValues[i].color.float32[3] = colorTargetInfos[i].clear_color.a; + int clearIndex = 0; + for (i = 0; i < numColorTargets; i += 1) { + clearValues[clearIndex].color.float32[0] = colorTargetInfos[i].clear_color.r; + clearValues[clearIndex].color.float32[1] = colorTargetInfos[i].clear_color.g; + clearValues[clearIndex].color.float32[2] = colorTargetInfos[i].clear_color.b; + clearValues[clearIndex].color.float32[3] = colorTargetInfos[i].clear_color.a; + clearIndex += 1; if (colorTargetInfos[i].store_op == SDL_GPU_STOREOP_RESOLVE || colorTargetInfos[i].store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) { // Skip over the resolve texture, we're not clearing it - i += 1; + clearIndex += 1; } } @@ -8293,7 +8408,6 @@ static void VULKAN_BindComputeStorageTextures( VULKAN_TEXTURE_USAGE_MODE_COMPUTE_STORAGE_READ, textureContainer->activeTexture); - VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); @@ -8592,7 +8706,7 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( dynamicOffsetCount, dynamicOffsets); - commandBuffer->needNewVertexUniformOffsets = false; + commandBuffer->needNewComputeUniformOffsets = false; } static void VULKAN_DispatchCompute( @@ -8750,8 +8864,9 @@ static void VULKAN_UploadToTexture( VulkanTextureSubresource *vulkanTextureSubresource; VkBufferImageCopy imageCopy; - // Note that the transfer buffer does not need a barrier, as it is synced by the client + SDL_LockRWLockForReading(renderer->defragLock); + // Note that the transfer buffer does not need a barrier, as it is synced by the client vulkanTextureSubresource = VULKAN_INTERNAL_PrepareTextureSubresourceForWrite( renderer, vulkanCommandBuffer, @@ -8791,6 +8906,9 @@ static void VULKAN_UploadToTexture( VULKAN_INTERNAL_TrackBuffer(vulkanCommandBuffer, transferBufferContainer->activeBuffer); VULKAN_INTERNAL_TrackTexture(vulkanCommandBuffer, vulkanTextureSubresource->parent); + VULKAN_INTERNAL_TrackTextureTransfer(vulkanCommandBuffer, vulkanTextureSubresource->parent); + + SDL_UnlockRWLock(renderer->defragLock); } static void VULKAN_UploadToBuffer( @@ -8805,8 +8923,9 @@ static void VULKAN_UploadToBuffer( VulkanBufferContainer *bufferContainer = (VulkanBufferContainer *)destination->buffer; VkBufferCopy bufferCopy; - // Note that the transfer buffer does not need a barrier, as it is synced by the client + SDL_LockRWLockForReading(renderer->defragLock); + // Note that the transfer buffer does not need a barrier, as it is synced by the client VulkanBuffer *vulkanBuffer = VULKAN_INTERNAL_PrepareBufferForWrite( renderer, vulkanCommandBuffer, @@ -8814,6 +8933,7 @@ static void VULKAN_UploadToBuffer( cycle, VULKAN_BUFFER_USAGE_MODE_COPY_DESTINATION); + bufferCopy.srcOffset = source->offset; bufferCopy.dstOffset = destination->offset; bufferCopy.size = destination->size; @@ -8833,6 +8953,9 @@ static void VULKAN_UploadToBuffer( VULKAN_INTERNAL_TrackBuffer(vulkanCommandBuffer, transferBufferContainer->activeBuffer); VULKAN_INTERNAL_TrackBuffer(vulkanCommandBuffer, vulkanBuffer); + VULKAN_INTERNAL_TrackBufferTransfer(vulkanCommandBuffer, vulkanBuffer); + + SDL_UnlockRWLock(renderer->defragLock); } // Readback @@ -8848,6 +8971,9 @@ static void VULKAN_DownloadFromTexture( VulkanTextureSubresource *vulkanTextureSubresource; VulkanBufferContainer *transferBufferContainer = (VulkanBufferContainer *)destination->transfer_buffer; VkBufferImageCopy imageCopy; + + SDL_LockRWLockForReading(renderer->defragLock); + vulkanTextureSubresource = VULKAN_INTERNAL_FetchTextureSubresource( textureContainer, source->layer, @@ -8891,6 +9017,9 @@ static void VULKAN_DownloadFromTexture( VULKAN_INTERNAL_TrackBuffer(vulkanCommandBuffer, transferBufferContainer->activeBuffer); VULKAN_INTERNAL_TrackTexture(vulkanCommandBuffer, vulkanTextureSubresource->parent); + VULKAN_INTERNAL_TrackTextureTransfer(vulkanCommandBuffer, vulkanTextureSubresource->parent); + + SDL_UnlockRWLock(renderer->defragLock); } static void VULKAN_DownloadFromBuffer( @@ -8904,8 +9033,9 @@ static void VULKAN_DownloadFromBuffer( VulkanBufferContainer *transferBufferContainer = (VulkanBufferContainer *)destination->transfer_buffer; VkBufferCopy bufferCopy; - // Note that transfer buffer does not need a barrier, as it is synced by the client + SDL_LockRWLockForReading(renderer->defragLock); + // Note that transfer buffer does not need a barrier, as it is synced by the client VULKAN_INTERNAL_BufferTransitionFromDefaultUsage( renderer, vulkanCommandBuffer, @@ -8931,6 +9061,9 @@ static void VULKAN_DownloadFromBuffer( VULKAN_INTERNAL_TrackBuffer(vulkanCommandBuffer, transferBufferContainer->activeBuffer); VULKAN_INTERNAL_TrackBuffer(vulkanCommandBuffer, bufferContainer->activeBuffer); + VULKAN_INTERNAL_TrackBufferTransfer(vulkanCommandBuffer, bufferContainer->activeBuffer); + + SDL_UnlockRWLock(renderer->defragLock); } static void VULKAN_CopyTextureToTexture( @@ -8948,6 +9081,8 @@ static void VULKAN_CopyTextureToTexture( VulkanTextureSubresource *dstSubresource; VkImageCopy imageCopy; + SDL_LockRWLockForReading(renderer->defragLock); + srcSubresource = VULKAN_INTERNAL_FetchTextureSubresource( (VulkanTextureContainer *)source->texture, source->layer, @@ -9009,6 +9144,10 @@ static void VULKAN_CopyTextureToTexture( VULKAN_INTERNAL_TrackTexture(vulkanCommandBuffer, srcSubresource->parent); VULKAN_INTERNAL_TrackTexture(vulkanCommandBuffer, dstSubresource->parent); + VULKAN_INTERNAL_TrackTextureTransfer(vulkanCommandBuffer, srcSubresource->parent); + VULKAN_INTERNAL_TrackTextureTransfer(vulkanCommandBuffer, dstSubresource->parent); + + SDL_UnlockRWLock(renderer->defragLock); } static void VULKAN_CopyBufferToBuffer( @@ -9024,6 +9163,8 @@ static void VULKAN_CopyBufferToBuffer( VulkanBufferContainer *dstContainer = (VulkanBufferContainer *)destination->buffer; VkBufferCopy bufferCopy; + SDL_LockRWLockForReading(renderer->defragLock); + VulkanBuffer *dstBuffer = VULKAN_INTERNAL_PrepareBufferForWrite( renderer, vulkanCommandBuffer, @@ -9062,6 +9203,10 @@ static void VULKAN_CopyBufferToBuffer( VULKAN_INTERNAL_TrackBuffer(vulkanCommandBuffer, srcContainer->activeBuffer); VULKAN_INTERNAL_TrackBuffer(vulkanCommandBuffer, dstBuffer); + VULKAN_INTERNAL_TrackBufferTransfer(vulkanCommandBuffer, srcContainer->activeBuffer); + VULKAN_INTERNAL_TrackBufferTransfer(vulkanCommandBuffer, dstBuffer); + + SDL_UnlockRWLock(renderer->defragLock); } static void VULKAN_GenerateMipmaps( @@ -9075,8 +9220,10 @@ static void VULKAN_GenerateMipmaps( VulkanTextureSubresource *dstTextureSubresource; VkImageBlit blit; + SDL_LockRWLockForReading(renderer->defragLock); + // Blit each slice sequentially. Barriers, barriers everywhere! - for (Uint32 layerOrDepthIndex = 0; layerOrDepthIndex < container->header.info.layer_count_or_depth; layerOrDepthIndex += 1) + for (Uint32 layerOrDepthIndex = 0; layerOrDepthIndex < container->header.info.layer_count_or_depth; layerOrDepthIndex += 1) { for (Uint32 level = 1; level < container->header.info.num_levels; level += 1) { Uint32 layer = container->header.info.type == SDL_GPU_TEXTURETYPE_3D ? 0 : layerOrDepthIndex; Uint32 depth = container->header.info.type == SDL_GPU_TEXTURETYPE_3D ? layerOrDepthIndex : 0; @@ -9155,7 +9302,13 @@ static void VULKAN_GenerateMipmaps( VULKAN_INTERNAL_TrackTexture(vulkanCommandBuffer, srcTextureSubresource->parent); VULKAN_INTERNAL_TrackTexture(vulkanCommandBuffer, dstTextureSubresource->parent); + VULKAN_INTERNAL_TrackTextureTransfer(vulkanCommandBuffer, srcTextureSubresource->parent); + VULKAN_INTERNAL_TrackTextureTransfer(vulkanCommandBuffer, dstTextureSubresource->parent); + } + } + + SDL_UnlockRWLock(renderer->defragLock); } static void VULKAN_EndCopyPass( @@ -9365,11 +9518,21 @@ static bool VULKAN_INTERNAL_AllocateCommandBuffer( commandBuffer->usedBuffers = SDL_malloc( commandBuffer->usedBufferCapacity * sizeof(VulkanBuffer *)); + commandBuffer->buffersUsedInPendingTransfersCapacity = 4; + commandBuffer->buffersUsedInPendingTransfersCount = 0; + commandBuffer->buffersUsedInPendingTransfers = SDL_malloc( + commandBuffer->buffersUsedInPendingTransfersCapacity * sizeof(VulkanBuffer *)); + commandBuffer->usedTextureCapacity = 4; commandBuffer->usedTextureCount = 0; commandBuffer->usedTextures = SDL_malloc( commandBuffer->usedTextureCapacity * sizeof(VulkanTexture *)); + commandBuffer->texturesUsedInPendingTransfersCapacity = 4; + commandBuffer->texturesUsedInPendingTransfersCount = 0; + commandBuffer->texturesUsedInPendingTransfers = SDL_malloc( + commandBuffer->texturesUsedInPendingTransfersCapacity * sizeof(VulkanTexture *)); + commandBuffer->usedSamplerCapacity = 4; commandBuffer->usedSamplerCount = 0; commandBuffer->usedSamplers = SDL_malloc( @@ -9395,6 +9558,8 @@ static bool VULKAN_INTERNAL_AllocateCommandBuffer( commandBuffer->usedUniformBuffers = SDL_malloc( commandBuffer->usedUniformBufferCapacity * sizeof(VulkanUniformBuffer *)); + commandBuffer->swapchainRequested = false; + // Pool it! vulkanCommandPool->inactiveCommandBuffers[vulkanCommandPool->inactiveCommandBufferCount] = commandBuffer; @@ -9505,13 +9670,17 @@ static SDL_GPUCommandBuffer *VULKAN_AcquireCommandBuffer( VulkanCommandBuffer *commandBuffer = VULKAN_INTERNAL_GetInactiveCommandBufferFromPool(renderer, threadID); - commandBuffer->descriptorSetCache = VULKAN_INTERNAL_AcquireDescriptorSetCache(renderer); + if (commandBuffer == NULL) { + SDL_UnlockMutex(renderer->acquireCommandBufferLock); + return NULL; + } + + DescriptorSetCache *descriptorSetCache = + VULKAN_INTERNAL_AcquireDescriptorSetCache(renderer); SDL_UnlockMutex(renderer->acquireCommandBufferLock); - if (commandBuffer == NULL) { - return NULL; - } + commandBuffer->descriptorSetCache = descriptorSetCache; // Reset state @@ -9577,6 +9746,7 @@ static SDL_GPUCommandBuffer *VULKAN_AcquireCommandBuffer( commandBuffer->autoReleaseFence = true; + commandBuffer->swapchainRequested = false; commandBuffer->isDefrag = 0; /* Reset the command buffer here to avoid resets being called @@ -9760,8 +9930,13 @@ static bool VULKAN_ClaimWindow( WindowData *windowData = VULKAN_INTERNAL_FetchWindowData(window); if (windowData == NULL) { - windowData = SDL_calloc(1, sizeof(WindowData)); + windowData = (WindowData *)SDL_calloc(1, sizeof(WindowData)); + if (!windowData) { + return false; + } windowData->window = window; + windowData->renderer = renderer; + windowData->refcount = 1; windowData->presentMode = SDL_GPU_PRESENTMODE_VSYNC; windowData->swapchainComposition = SDL_GPU_SWAPCHAINCOMPOSITION_SDR; @@ -9775,6 +9950,28 @@ static bool VULKAN_ClaimWindow( windowData->swapchainCreateHeight = h; #endif + SDL_VideoDevice *videoDevice = SDL_GetVideoDevice(); + if (!videoDevice) { + SDL_free(windowData); + return SDL_SetError("No video device found"); + } + + if (!videoDevice->Vulkan_CreateSurface) { + SDL_free(windowData); + return SDL_SetError("Video device does not implement Vulkan_CreateSurface"); + } + + // Each window must have its own surface. + if (!videoDevice->Vulkan_CreateSurface( + videoDevice, + windowData->window, + renderer->instance, + NULL, // FIXME: VAllocationCallbacks + &windowData->surface)) { + SDL_free(windowData); + return false; + } + Uint32 createSwapchainResult = VULKAN_INTERNAL_CreateSwapchain(renderer, windowData); if (createSwapchainResult == 1) { SDL_SetPointerProperty(SDL_GetWindowProperties(window), WINDOW_PROPERTY_DATA, windowData); @@ -9791,18 +9988,26 @@ static bool VULKAN_ClaimWindow( renderer->claimedWindowCount += 1; SDL_UnlockMutex(renderer->windowLock); - SDL_AddEventWatch(VULKAN_INTERNAL_OnWindowResize, window); + SDL_AddWindowEventWatch(SDL_WINDOW_EVENT_WATCH_NORMAL, VULKAN_INTERNAL_OnWindowResize, window); return true; } else if (createSwapchainResult == VULKAN_INTERNAL_TRY_AGAIN) { windowData->needsSwapchainRecreate = true; return true; } else { + // Failed to create swapchain, destroy surface and free data + renderer->vkDestroySurfaceKHR( + renderer->instance, + windowData->surface, + NULL); SDL_free(windowData); return false; } + } else if (windowData->renderer == renderer) { + ++windowData->refcount; + return true; } else { - SET_STRING_ERROR_AND_RETURN("Window already claimed!", false); + SET_STRING_ERROR_AND_RETURN("Window already claimed", false); } } @@ -9817,6 +10022,14 @@ static void VULKAN_ReleaseWindow( if (windowData == NULL) { return; } + if (windowData->renderer != renderer) { + SDL_SetError("Window not claimed by this device"); + return; + } + if (windowData->refcount > 1) { + --windowData->refcount; + return; + } VULKAN_Wait(driverData); @@ -9832,6 +10045,11 @@ static void VULKAN_ReleaseWindow( (VulkanRenderer *)driverData, windowData); + renderer->vkDestroySurfaceKHR( + renderer->instance, + windowData->surface, + NULL); + windowData->surface = VK_NULL_HANDLE; SDL_LockMutex(renderer->windowLock); for (i = 0; i < renderer->claimedWindowCount; i += 1) { @@ -9846,7 +10064,7 @@ static void VULKAN_ReleaseWindow( SDL_free(windowData); SDL_ClearProperty(SDL_GetWindowProperties(window), WINDOW_PROPERTY_DATA); - SDL_RemoveEventWatch(VULKAN_INTERNAL_OnWindowResize, window); + SDL_RemoveWindowEventWatch(SDL_WINDOW_EVENT_WATCH_NORMAL, VULKAN_INTERNAL_OnWindowResize, window); } static Uint32 VULKAN_INTERNAL_RecreateSwapchain( @@ -9868,7 +10086,12 @@ static Uint32 VULKAN_INTERNAL_RecreateSwapchain( } } +#ifdef SDL_VIDEO_DRIVER_PRIVATE + // Private platforms also invalidate the window, so don't try to preserve the surface/swapchain VULKAN_INTERNAL_DestroySwapchain(renderer, windowData); +#else + VULKAN_INTERNAL_DestroySwapchainImage(renderer, windowData); +#endif return VULKAN_INTERNAL_CreateSwapchain(renderer, windowData); } @@ -9925,6 +10148,32 @@ static bool VULKAN_INTERNAL_AcquireSwapchainTexture( SET_STRING_ERROR_AND_RETURN("Cannot acquire a swapchain texture from an unclaimed window!", false); } + // The command buffer is flagged for cleanup when the swapchain is requested as a cleanup timing mechanism + vulkanCommandBuffer->swapchainRequested = true; + + if (window->flags & SDL_WINDOW_HIDDEN) { + // Edge case, texture is filled in with NULL but not an error + return true; + } + + if (windowData->needsSurfaceRecreate) { + SDL_VideoDevice *videoDevice = SDL_GetVideoDevice(); + SDL_assert(videoDevice); + SDL_assert(videoDevice->Vulkan_CreateSurface); + renderer->vkDestroySurfaceKHR( + renderer->instance, + windowData->surface, + NULL); + if (!videoDevice->Vulkan_CreateSurface( + videoDevice, + windowData->window, + renderer->instance, + NULL, // FIXME: VAllocationCallbacks + &windowData->surface)) { + SET_STRING_ERROR_AND_RETURN("Failed to recreate Vulkan surface!", false); + } + } + // If window data marked as needing swapchain recreate, try to recreate if (windowData->needsSwapchainRecreate) { Uint32 recreateSwapchainResult = VULKAN_INTERNAL_RecreateSwapchain(renderer, windowData); @@ -9940,13 +10189,10 @@ static bool VULKAN_INTERNAL_AcquireSwapchainTexture( } return true; } - } - if (swapchainTextureWidth) { - *swapchainTextureWidth = windowData->width; - } - if (swapchainTextureHeight) { - *swapchainTextureHeight = windowData->height; + // Unset this flag until after the swapchain has been recreated to let VULKAN_INTERNAL_CreateSwapchain() + // know whether it needs to pass the old swapchain or not. + windowData->needsSurfaceRecreate = false; } if (windowData->inFlightFences[windowData->frameCounter] != NULL) { @@ -9990,6 +10236,13 @@ static bool VULKAN_INTERNAL_AcquireSwapchainTexture( break; // we got the next image! } + // Surface lost — flag for surface + swapchain recreation on next call + if (acquireResult == VK_ERROR_SURFACE_LOST_KHR) { + windowData->needsSurfaceRecreate = true; + windowData->needsSwapchainRecreate = true; + return true; + } + // If acquisition is invalid, let's try to recreate Uint32 recreateSwapchainResult = VULKAN_INTERNAL_RecreateSwapchain(renderer, windowData); if (!recreateSwapchainResult) { @@ -10000,6 +10253,14 @@ static bool VULKAN_INTERNAL_AcquireSwapchainTexture( } } + if (swapchainTextureWidth) { + *swapchainTextureWidth = windowData->width; + } + + if (swapchainTextureHeight) { + *swapchainTextureHeight = windowData->height; + } + swapchainTextureContainer = &windowData->textureContainers[swapchainImageIndex]; // We need a special execution dependency with pWaitDstStageMask or image transition can start before acquire finishes @@ -10347,11 +10608,21 @@ static void VULKAN_INTERNAL_CleanCommandBuffer( } commandBuffer->usedBufferCount = 0; + for (Sint32 i = 0; i < commandBuffer->buffersUsedInPendingTransfersCount; i += 1) { + (void)SDL_AtomicDecRef(&commandBuffer->buffersUsedInPendingTransfers[i]->usedRegion->allocation->referenceCount); + } + commandBuffer->buffersUsedInPendingTransfersCount = 0; + for (Sint32 i = 0; i < commandBuffer->usedTextureCount; i += 1) { (void)SDL_AtomicDecRef(&commandBuffer->usedTextures[i]->referenceCount); } commandBuffer->usedTextureCount = 0; + for (Sint32 i = 0; i < commandBuffer->texturesUsedInPendingTransfersCount; i += 1){ + (void)SDL_AtomicDecRef(&commandBuffer->texturesUsedInPendingTransfers[i]->usedRegion->allocation->referenceCount); + } + commandBuffer->texturesUsedInPendingTransfersCount = 0; + for (Sint32 i = 0; i < commandBuffer->usedSamplerCount; i += 1) { (void)SDL_AtomicDecRef(&commandBuffer->usedSamplers[i]->referenceCount); } @@ -10377,6 +10648,7 @@ static void VULKAN_INTERNAL_CleanCommandBuffer( commandBuffer->presentDataCount = 0; commandBuffer->waitSemaphoreCount = 0; commandBuffer->signalSemaphoreCount = 0; + commandBuffer->swapchainRequested = false; // Reset defrag state @@ -10474,11 +10746,18 @@ static bool VULKAN_Wait( VkResult result; Sint32 i; + SDL_LockMutex(renderer->submitLock); + result = renderer->vkDeviceWaitIdle(renderer->logicalDevice); - CHECK_VULKAN_ERROR_AND_RETURN(result, vkDeviceWaitIdle, false); - - SDL_LockMutex(renderer->submitLock); + if (result != VK_SUCCESS) { + if (renderer->debugMode) { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s %s", "vkDeviceWaitIdle", VkErrorMessages(result)); + } + SDL_SetError("%s %s", "vkDeviceWaitIdle", VkErrorMessages(result)); + SDL_UnlockMutex(renderer->submitLock); + return false; + } for (i = renderer->submittedCommandBufferCount - 1; i >= 0; i -= 1) { commandBuffer = renderer->submittedCommandBuffers[i]; @@ -10533,7 +10812,7 @@ static bool VULKAN_Submit( VulkanTextureSubresource *swapchainTextureSubresource; VulkanMemorySubAllocator *allocator; bool performCleanups = - (renderer->claimedWindowCount > 0 && vulkanCommandBuffer->presentDataCount > 0) || + (renderer->claimedWindowCount > 0 && vulkanCommandBuffer->swapchainRequested) || renderer->claimedWindowCount == 0; SDL_LockMutex(renderer->submitLock); @@ -10625,9 +10904,20 @@ static bool VULKAN_Submit( presentData->windowData->inFlightFences[presentData->windowData->frameCounter] = (SDL_GPUFence *)vulkanCommandBuffer->inFlightFence; (void)SDL_AtomicIncRef(&vulkanCommandBuffer->inFlightFence->referenceCount); - if (presentResult == VK_SUBOPTIMAL_KHR || presentResult == VK_ERROR_OUT_OF_DATE_KHR) { +// On the Android platform, VK_SUBOPTIMAL_KHR is returned whenever the device is rotated. We'll just ignore this for now. +#ifndef SDL_PLATFORM_ANDROID + if (presentResult == VK_SUBOPTIMAL_KHR) { presentData->windowData->needsSwapchainRecreate = true; } +#endif + if (presentResult == VK_ERROR_OUT_OF_DATE_KHR) { + presentData->windowData->needsSwapchainRecreate = true; + } + } else if (presentResult == VK_ERROR_SURFACE_LOST_KHR) { + // Android can destroy the surface at any time when the app goes into the background, + // even after successfully acquiring a swapchain texture and before presenting it. + presentData->windowData->needsSwapchainRecreate = true; + presentData->windowData->needsSurfaceRecreate = true; } else { if (presentResult != VK_SUCCESS) { VULKAN_INTERNAL_ReleaseCommandBuffer(vulkanCommandBuffer); @@ -10718,8 +11008,30 @@ static bool VULKAN_INTERNAL_DefragmentMemory( commandBuffer->isDefrag = 1; SDL_LockMutex(renderer->allocatorLock); + SDL_LockRWLockForWriting(renderer->defragLock); - VulkanMemoryAllocation *allocation = renderer->allocationsToDefrag[renderer->allocationsToDefragCount - 1]; + // Find an allocation that doesn't have any pending transfer operations + Sint32 indexToDefrag = -1; + for (Sint32 i = renderer->allocationsToDefragCount - 1; i >= 0; i -= 1) { + if (SDL_GetAtomicInt(&renderer->allocationsToDefrag[i]->referenceCount) == 0) { + indexToDefrag = i; + break; + } + } + + if (indexToDefrag == -1) { + // Nothing is available to defrag, but it's not an error + SDL_UnlockRWLock(renderer->defragLock); + SDL_UnlockMutex(renderer->allocatorLock); + return true; + } + + VulkanMemoryAllocation *allocation = renderer->allocationsToDefrag[indexToDefrag]; + + // Plug the hole + if ((Uint32) indexToDefrag != renderer->allocationsToDefragCount - 1) { + renderer->allocationsToDefrag[indexToDefrag] = renderer->allocationsToDefrag[renderer->allocationsToDefragCount - 1]; + } renderer->allocationsToDefragCount -= 1; /* For each used region in the allocation @@ -10730,8 +11042,6 @@ static bool VULKAN_INTERNAL_DefragmentMemory( VulkanMemoryUsedRegion *currentRegion = allocation->usedRegions[i]; if (currentRegion->isBuffer && !currentRegion->vulkanBuffer->markedForDestroy) { - currentRegion->vulkanBuffer->usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; - VulkanBuffer *newBuffer = VULKAN_INTERNAL_CreateBuffer( renderer, currentRegion->vulkanBuffer->size, @@ -10741,6 +11051,7 @@ static bool VULKAN_INTERNAL_DefragmentMemory( currentRegion->vulkanBuffer->container != NULL ? currentRegion->vulkanBuffer->container->debugName : NULL); if (newBuffer == NULL) { + SDL_UnlockRWLock(renderer->defragLock); SDL_UnlockMutex(renderer->allocatorLock); SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s", "Failed to allocate defrag buffer!"); return false; @@ -10803,34 +11114,36 @@ static bool VULKAN_INTERNAL_DefragmentMemory( } else if (!currentRegion->isBuffer && !currentRegion->vulkanTexture->markedForDestroy) { VulkanTexture *newTexture = VULKAN_INTERNAL_CreateTexture( renderer, - false, ¤tRegion->vulkanTexture->container->header.info); if (newTexture == NULL) { + SDL_UnlockRWLock(renderer->defragLock); SDL_UnlockMutex(renderer->allocatorLock); SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s", "Failed to allocate defrag buffer!"); return false; } SDL_GPUTextureCreateInfo info = currentRegion->vulkanTexture->container->header.info; + + VULKAN_INTERNAL_TextureTransitionFromDefaultUsage( + renderer, + commandBuffer, + VULKAN_TEXTURE_USAGE_MODE_COPY_SOURCE, + currentRegion->vulkanTexture); + + VULKAN_INTERNAL_FullTextureMemoryBarrier( + renderer, + commandBuffer, + VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, + VULKAN_TEXTURE_USAGE_MODE_COPY_DESTINATION, + newTexture + ); + + // Can only copy one mip level at a time for (Uint32 subresourceIndex = 0; subresourceIndex < currentRegion->vulkanTexture->subresourceCount; subresourceIndex += 1) { - // copy subresource if necessary VulkanTextureSubresource *srcSubresource = ¤tRegion->vulkanTexture->subresources[subresourceIndex]; VulkanTextureSubresource *dstSubresource = &newTexture->subresources[subresourceIndex]; - VULKAN_INTERNAL_TextureSubresourceTransitionFromDefaultUsage( - renderer, - commandBuffer, - VULKAN_TEXTURE_USAGE_MODE_COPY_SOURCE, - srcSubresource); - - VULKAN_INTERNAL_TextureSubresourceMemoryBarrier( - renderer, - commandBuffer, - VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, - VULKAN_TEXTURE_USAGE_MODE_COPY_DESTINATION, - dstSubresource); - VkImageCopy imageCopy; imageCopy.srcOffset.x = 0; imageCopy.srcOffset.y = 0; @@ -10859,16 +11172,16 @@ static bool VULKAN_INTERNAL_DefragmentMemory( 1, &imageCopy); - VULKAN_INTERNAL_TextureSubresourceTransitionToDefaultUsage( - renderer, - commandBuffer, - VULKAN_TEXTURE_USAGE_MODE_COPY_DESTINATION, - dstSubresource); - VULKAN_INTERNAL_TrackTexture(commandBuffer, srcSubresource->parent); VULKAN_INTERNAL_TrackTexture(commandBuffer, dstSubresource->parent); } + VULKAN_INTERNAL_TextureTransitionToDefaultUsage( + renderer, + commandBuffer, + VULKAN_TEXTURE_USAGE_MODE_COPY_DESTINATION, + newTexture); + // re-point original container to new texture newTexture->container = currentRegion->vulkanTexture->container; newTexture->containerIndex = currentRegion->vulkanTexture->containerIndex; @@ -10881,6 +11194,7 @@ static bool VULKAN_INTERNAL_DefragmentMemory( } } + SDL_UnlockRWLock(renderer->defragLock); SDL_UnlockMutex(renderer->allocatorLock); return true; @@ -10950,7 +11264,7 @@ static inline Uint8 CheckDeviceExtensions( supports->ext = 1; \ } CHECK(KHR_swapchain) - else CHECK(KHR_maintenance1) else CHECK(KHR_driver_properties) else CHECK(KHR_portability_subset) else CHECK(EXT_texture_compression_astc_hdr) + else CHECK(KHR_maintenance1) else CHECK(KHR_driver_properties) else CHECK(KHR_portability_subset) else CHECK(MSFT_layered_driver) else CHECK(EXT_texture_compression_astc_hdr) #undef CHECK } @@ -10965,6 +11279,7 @@ static inline Uint32 GetDeviceExtensionCount(VulkanExtensions *supports) supports->KHR_maintenance1 + supports->KHR_driver_properties + supports->KHR_portability_subset + + supports->MSFT_layered_driver + supports->EXT_texture_compression_astc_hdr); } @@ -10981,6 +11296,7 @@ static inline void CreateDeviceExtensionArray( CHECK(KHR_maintenance1) CHECK(KHR_driver_properties) CHECK(KHR_portability_subset) + CHECK(MSFT_layered_driver) CHECK(EXT_texture_compression_astc_hdr) #undef CHECK } @@ -11003,7 +11319,10 @@ static Uint8 VULKAN_INTERNAL_CheckInstanceExtensions( const char **requiredExtensions, Uint32 requiredExtensionsLength, bool *supportsDebugUtils, - bool *supportsColorspace) + bool *supportsColorspace, + bool *supportsPhysicalDeviceProperties2, + bool *supportsPortabilityEnumeration, + int *firstUnsupportedExtensionIndex) { Uint32 extensionCount, i; VkExtensionProperties *availableExtensions; @@ -11026,6 +11345,7 @@ static Uint8 VULKAN_INTERNAL_CheckInstanceExtensions( availableExtensions, extensionCount)) { allExtensionsSupported = 0; + *firstUnsupportedExtensionIndex = i; break; } } @@ -11042,12 +11362,48 @@ static Uint8 VULKAN_INTERNAL_CheckInstanceExtensions( availableExtensions, extensionCount); + // Only needed for KHR_driver_properties! + *supportsPhysicalDeviceProperties2 = SupportsInstanceExtension( + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, + availableExtensions, + extensionCount); + + // Only needed for MoltenVK! + *supportsPortabilityEnumeration = SupportsInstanceExtension( + VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, + availableExtensions, + extensionCount); + SDL_free(availableExtensions); return allExtensionsSupported; } +static Uint8 CheckOptInDeviceExtensions(VulkanFeatures *features, + Uint32 numExtensions, + VkExtensionProperties *availableExtensions, + const char **missingExtensionName) { + Uint8 supportsAll = 1; + for (Uint32 extensionIdx = 0; extensionIdx < features->additionalDeviceExtensionCount; extensionIdx++) { + bool found = false; + for (Uint32 searchIdx = 0; searchIdx < numExtensions; searchIdx++) { + if (SDL_strcmp(features->additionalDeviceExtensionNames[extensionIdx], availableExtensions[searchIdx].extensionName) == 0) { + found = true; + break; + } + } + if (!found) { + supportsAll = 0; + *missingExtensionName = features->additionalDeviceExtensionNames[extensionIdx]; + break; + } + } + + return supportsAll; +} + static Uint8 VULKAN_INTERNAL_CheckDeviceExtensions( VulkanRenderer *renderer, + VulkanFeatures *features, VkPhysicalDevice physicalDevice, VulkanExtensions *physicalDeviceExtensions) { @@ -11073,6 +11429,19 @@ static Uint8 VULKAN_INTERNAL_CheckDeviceExtensions( extensionCount, physicalDeviceExtensions); + if (features->usesCustomVulkanOptions) { + const char *missingExtensionName; + if (!CheckOptInDeviceExtensions(features, extensionCount, availableExtensions, &missingExtensionName)) { + SDL_assert(missingExtensionName); + if (renderer->debugMode) { + SDL_LogError(SDL_LOG_CATEGORY_GPU, + "Required Vulkan device extension '%s' not supported", + missingExtensionName); + } + allExtensionsSupported = 0; + } + } + SDL_free(availableExtensions); return allExtensionsSupported; } @@ -11110,7 +11479,471 @@ static Uint8 VULKAN_INTERNAL_CheckValidationLayers( return layerFound; } -static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) +#define CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, feature, result) \ + if (requested->feature && !supported->feature) { \ + SDL_LogVerbose( \ + SDL_LOG_CATEGORY_GPU, \ + "SDL GPU Vulkan: Application requested unsupported physical device feature '" #feature "'"); \ + result = false; \ + } + +static bool VULKAN_INTERNAL_ValidateOptInVulkan10Features(VkPhysicalDeviceFeatures *requested, VkPhysicalDeviceFeatures *supported) +{ + if (requested && supported) { + bool result = true; + + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, robustBufferAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, fullDrawIndexUint32, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, imageCubeArray, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, independentBlend, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, geometryShader, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, tessellationShader, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sampleRateShading, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, dualSrcBlend, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, logicOp, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiDrawIndirect, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, drawIndirectFirstInstance, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, depthClamp, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, depthBiasClamp, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, fillModeNonSolid, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, depthBounds, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, wideLines, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, largePoints, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, alphaToOne, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiViewport, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, samplerAnisotropy, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, textureCompressionETC2, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, textureCompressionASTC_LDR, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, textureCompressionBC, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, occlusionQueryPrecise, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, pipelineStatisticsQuery, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, vertexPipelineStoresAndAtomics, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, fragmentStoresAndAtomics, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderTessellationAndGeometryPointSize, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderImageGatherExtended, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageExtendedFormats, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageMultisample, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageReadWithoutFormat, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageWriteWithoutFormat, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderUniformBufferArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderSampledImageArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageBufferArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderClipDistance, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderCullDistance, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderFloat64, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInt64, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInt16, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderResourceResidency, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderResourceMinLod, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseBinding, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidencyBuffer, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidencyImage2D, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidencyImage3D, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidency2Samples, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidency4Samples, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidency8Samples, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidency16Samples, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidencyAliased, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, variableMultisampleRate, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, inheritedQueries, result) + + return result; + } else { + return false; + } +} + +static bool VULKAN_INTERNAL_ValidateOptInVulkan11Features(VkPhysicalDeviceVulkan11Features *requested, VkPhysicalDeviceVulkan11Features *supported) +{ + if (requested && supported) { + bool result = true; + + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storageBuffer16BitAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, uniformAndStorageBuffer16BitAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storagePushConstant16, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storageInputOutput16, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiview, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiviewGeometryShader, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiviewTessellationShader, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, variablePointersStorageBuffer, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, variablePointers, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, protectedMemory, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, samplerYcbcrConversion, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderDrawParameters, result) + + return result; + } else { + return false; + } +} + +static bool VULKAN_INTERNAL_ValidateOptInVulkan12Features(VkPhysicalDeviceVulkan12Features *requested, VkPhysicalDeviceVulkan12Features *supported) +{ + if (requested && supported) { + bool result = true; + + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, samplerMirrorClampToEdge, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, drawIndirectCount, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storageBuffer8BitAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, uniformAndStorageBuffer8BitAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storagePushConstant8, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderBufferInt64Atomics, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderSharedInt64Atomics, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderFloat16, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInt8, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInputAttachmentArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderUniformTexelBufferArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageTexelBufferArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderUniformBufferArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderSampledImageArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageBufferArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInputAttachmentArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderUniformTexelBufferArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageTexelBufferArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingUniformBufferUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingSampledImageUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingStorageImageUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingStorageBufferUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingUniformTexelBufferUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingStorageTexelBufferUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingUpdateUnusedWhilePending, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingPartiallyBound, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingVariableDescriptorCount, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, runtimeDescriptorArray, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, samplerFilterMinmax, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, scalarBlockLayout, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, imagelessFramebuffer, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, uniformBufferStandardLayout, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderSubgroupExtendedTypes, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, separateDepthStencilLayouts, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, hostQueryReset, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, timelineSemaphore, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, bufferDeviceAddress, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, bufferDeviceAddressCaptureReplay, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, bufferDeviceAddressMultiDevice, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, vulkanMemoryModel, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, vulkanMemoryModelDeviceScope, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, vulkanMemoryModelAvailabilityVisibilityChains, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderOutputViewportIndex, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderOutputLayer, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, subgroupBroadcastDynamicId, result) + + return result; + } else { + return false; + } +} + +static bool VULKAN_INTERNAL_ValidateOptInVulkan13Features(VkPhysicalDeviceVulkan13Features *requested, VkPhysicalDeviceVulkan13Features *supported) +{ + if (requested && supported) { + bool result = true; + + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, robustImageAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, inlineUniformBlock, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingInlineUniformBlockUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, pipelineCreationCacheControl, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, privateData, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderDemoteToHelperInvocation, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderTerminateInvocation, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, subgroupSizeControl, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, computeFullSubgroups, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, synchronization2, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, textureCompressionASTC_HDR, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderZeroInitializeWorkgroupMemory, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, dynamicRendering, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderIntegerDotProduct, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, maintenance4, result) + + return result; + } else { + return false; + } +} + +#undef CHECK_OPTIONAL_DEVICE_FEATURE + +static bool VULKAN_INTERNAL_ValidateOptInFeatures(VulkanRenderer *renderer, VulkanFeatures *features, VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures *vk10Features) +{ + bool supportsAllFeatures = true; + + int minorVersion = VK_API_VERSION_MINOR(features->desiredApiVersion); + + if (minorVersion < 1) { + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan10Features(&features->desiredVulkan10DeviceFeatures, vk10Features); + } else if (minorVersion < 2) { + // Query device features using the pre-1.2 structures + VkPhysicalDevice16BitStorageFeatures storage = { 0 }; + storage.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES; + + VkPhysicalDeviceMultiviewFeatures multiview = { 0 }; + multiview.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; + + VkPhysicalDeviceProtectedMemoryFeatures protectedMem = { 0 }; + protectedMem.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES; + + VkPhysicalDeviceSamplerYcbcrConversionFeatures ycbcr = { 0 }; + ycbcr.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES; + + VkPhysicalDeviceShaderDrawParametersFeatures drawParams = { 0 }; + drawParams.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES; + + VkPhysicalDeviceVariablePointersFeatures varPointers = { 0 }; + varPointers.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES; + + VkPhysicalDeviceFeatures2 supportedFeatureList = { 0 }; + supportedFeatureList.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + supportedFeatureList.pNext = &storage; + storage.pNext = &multiview; + multiview.pNext = &protectedMem; + protectedMem.pNext = &ycbcr; + ycbcr.pNext = &drawParams; + drawParams.pNext = &varPointers; + + renderer->vkGetPhysicalDeviceFeatures2(physicalDevice, &supportedFeatureList); + + // Pack the results into the post-1.2 structure for easier checking + VkPhysicalDeviceVulkan11Features vk11Features = { 0 }; + vk11Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; + vk11Features.storageBuffer16BitAccess = storage.storageBuffer16BitAccess; + vk11Features.uniformAndStorageBuffer16BitAccess = storage.uniformAndStorageBuffer16BitAccess; + vk11Features.storagePushConstant16 = storage.storagePushConstant16; + vk11Features.storageInputOutput16 = storage.storageInputOutput16; + vk11Features.multiview = multiview.multiview; + vk11Features.multiviewGeometryShader = multiview.multiviewGeometryShader; + vk11Features.multiviewTessellationShader = multiview.multiviewTessellationShader; + vk11Features.protectedMemory = protectedMem.protectedMemory; + vk11Features.samplerYcbcrConversion = ycbcr.samplerYcbcrConversion; + vk11Features.shaderDrawParameters = drawParams.shaderDrawParameters; + vk11Features.variablePointers = varPointers.variablePointers; + vk11Features.variablePointersStorageBuffer = varPointers.variablePointersStorageBuffer; + + // Check support + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan10Features(&features->desiredVulkan10DeviceFeatures, vk10Features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan11Features(&features->desiredVulkan11DeviceFeatures, &vk11Features); + } else { + VkPhysicalDeviceVulkan11Features vk11Features = { 0 }; + vk11Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; + + VkPhysicalDeviceVulkan12Features vk12Features = { 0 }; + vk12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + + VkPhysicalDeviceVulkan13Features vk13Features = { 0 }; + vk13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + + VkPhysicalDeviceFeatures2 supportedFeatureList = { 0 }; + supportedFeatureList.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + supportedFeatureList.pNext = &vk11Features; + vk11Features.pNext = &vk12Features; + vk12Features.pNext = &vk13Features; + + renderer->vkGetPhysicalDeviceFeatures2(physicalDevice, &supportedFeatureList); + + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan10Features(&features->desiredVulkan10DeviceFeatures, vk10Features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan11Features(&features->desiredVulkan11DeviceFeatures, &vk11Features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan12Features(&features->desiredVulkan12DeviceFeatures, &vk12Features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan13Features(&features->desiredVulkan13DeviceFeatures, &vk13Features); + } + + return supportsAllFeatures; +} + +static void VULKAN_INTERNAL_AddDeviceFeatures(VkBool32 *firstFeature, VkBool32 *lastFeature, VkBool32 *firstFeatureToAdd) +{ + while (firstFeature <= lastFeature) { + *firstFeature = (*firstFeature | *firstFeatureToAdd); + firstFeature++; + firstFeatureToAdd++; + } +} + +static bool VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_11(VkPhysicalDeviceFeatures *dst10, + VkPhysicalDeviceVulkan11Features *dst11, + VkBaseOutStructure *src) +{ + bool hasAdded = false; + switch (src->sType) { + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2: + { + VkPhysicalDeviceFeatures2 *newFeatures = (VkPhysicalDeviceFeatures2 *)src; + VULKAN_INTERNAL_AddDeviceFeatures(&dst10->robustBufferAccess, + &dst10->inheritedQueries, + &newFeatures->features.robustBufferAccess); + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES: + { + VkPhysicalDevice16BitStorageFeatures *newFeatures = (VkPhysicalDevice16BitStorageFeatures *)src; + dst11->storageBuffer16BitAccess |= newFeatures->storageBuffer16BitAccess; + dst11->uniformAndStorageBuffer16BitAccess |= newFeatures->uniformAndStorageBuffer16BitAccess; + dst11->storagePushConstant16 |= newFeatures->storagePushConstant16; + dst11->storageInputOutput16 |= newFeatures->storageInputOutput16; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES: + { + VkPhysicalDeviceMultiviewFeatures *newFeatures = (VkPhysicalDeviceMultiviewFeatures *)src; + dst11->multiview |= newFeatures->multiview; + dst11->multiviewGeometryShader |= newFeatures->multiviewGeometryShader; + dst11->multiviewTessellationShader |= newFeatures->multiviewTessellationShader; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES: + { + VkPhysicalDeviceProtectedMemoryFeatures *newFeatures = (VkPhysicalDeviceProtectedMemoryFeatures *)src; + dst11->protectedMemory |= newFeatures->protectedMemory; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES: + { + VkPhysicalDeviceSamplerYcbcrConversionFeatures *newFeatures = (VkPhysicalDeviceSamplerYcbcrConversionFeatures *)src; + dst11->samplerYcbcrConversion |= newFeatures->samplerYcbcrConversion; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES: + { + VkPhysicalDeviceShaderDrawParametersFeatures *newFeatures = (VkPhysicalDeviceShaderDrawParametersFeatures *)src; + dst11->shaderDrawParameters |= newFeatures->shaderDrawParameters; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES: + { + VkPhysicalDeviceVariablePointersFeatures *newFeatures = (VkPhysicalDeviceVariablePointersFeatures *)src; + dst11->variablePointers |= newFeatures->variablePointers; + dst11->variablePointersStorageBuffer |= newFeatures->variablePointersStorageBuffer; + hasAdded = true; + } break; + + default: + break; + } + + return hasAdded; +} + +static bool VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_12_Or_Later(VkPhysicalDeviceFeatures *dst10, + VkPhysicalDeviceVulkan11Features *dst11, + VkPhysicalDeviceVulkan12Features *dst12, + VkPhysicalDeviceVulkan13Features *dst13, + Uint32 apiVersion, + VkBaseOutStructure *src) +{ + int minorVersion = VK_API_VERSION_MINOR(apiVersion); + SDL_assert(apiVersion >= 2); + bool hasAdded = VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_11(dst10, dst11, src); + if (!hasAdded) { + switch (src->sType) { + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES: + { + VkPhysicalDeviceVulkan11Features *newFeatures = (VkPhysicalDeviceVulkan11Features *)src; + VULKAN_INTERNAL_AddDeviceFeatures(&dst11->storageBuffer16BitAccess, + &dst11->shaderDrawParameters, + &newFeatures->storageBuffer16BitAccess); + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES: + { + VkPhysicalDeviceVulkan12Features *newFeatures = (VkPhysicalDeviceVulkan12Features *)src; + VULKAN_INTERNAL_AddDeviceFeatures(&dst12->samplerMirrorClampToEdge, + &dst12->subgroupBroadcastDynamicId, + &newFeatures->samplerMirrorClampToEdge); + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES: + { + if (minorVersion >= 3) { + VkPhysicalDeviceVulkan13Features *newFeatures = (VkPhysicalDeviceVulkan13Features *)src; + VULKAN_INTERNAL_AddDeviceFeatures(&dst13->robustImageAccess, + &dst13->maintenance4, + &newFeatures->robustImageAccess); + hasAdded = true; + } + } break; + + default: + break; + } + } + + return hasAdded; +} + +static void VULKAN_INTERNAL_AddOptInVulkanOptions(SDL_PropertiesID props, VulkanRenderer *renderer, VulkanFeatures *features) +{ + if (SDL_HasProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER)) { + SDL_GPUVulkanOptions *options = (SDL_GPUVulkanOptions *)SDL_GetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER, NULL); + if (options) { + features->usesCustomVulkanOptions = true; + features->desiredApiVersion = options->vulkan_api_version; + + SDL_zero(features->desiredVulkan11DeviceFeatures); + SDL_zero(features->desiredVulkan12DeviceFeatures); + SDL_zero(features->desiredVulkan13DeviceFeatures); + features->desiredVulkan11DeviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; + features->desiredVulkan12DeviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + features->desiredVulkan13DeviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + + // Handle requested device features + VkPhysicalDeviceFeatures *vk10Features = &features->desiredVulkan10DeviceFeatures; + VkPhysicalDeviceVulkan11Features *vk11Features = &features->desiredVulkan11DeviceFeatures; + VkPhysicalDeviceVulkan12Features *vk12Features = &features->desiredVulkan12DeviceFeatures; + VkPhysicalDeviceVulkan13Features *vk13Features = &features->desiredVulkan13DeviceFeatures; + + if (options->vulkan_10_physical_device_features) { + VkPhysicalDeviceFeatures *deviceFeatures = (VkPhysicalDeviceFeatures *)options->vulkan_10_physical_device_features; + VULKAN_INTERNAL_AddDeviceFeatures(&vk10Features->robustBufferAccess, + &vk10Features->inheritedQueries, + &deviceFeatures->robustBufferAccess); + } + + int minorVersion = VK_API_VERSION_MINOR(features->desiredApiVersion); + bool supportsHigherLevelFeatures = minorVersion > 0; + if (supportsHigherLevelFeatures && options->feature_list) { + if (minorVersion < 2) { + // Iterate through the entire list and combine all requested features + VkBaseOutStructure *nextStructure = (VkBaseOutStructure *)options->feature_list; + while (nextStructure) { + VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_11(vk10Features, vk11Features, nextStructure); + nextStructure = nextStructure->pNext; + } + } else { + // Iterate through the entire list and combine all requested features + VkBaseOutStructure *nextStructure = (VkBaseOutStructure *)options->feature_list; + while (nextStructure) { + VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_12_Or_Later(vk10Features, + vk11Features, + vk12Features, + vk13Features, + features->desiredApiVersion, + nextStructure); + nextStructure = nextStructure->pNext; + } + } + } + + features->additionalDeviceExtensionCount = options->device_extension_count; + features->additionalDeviceExtensionNames = options->device_extension_names; + features->additionalInstanceExtensionCount = options->instance_extension_count; + features->additionalInstanceExtensionNames = options->instance_extension_names; + } else if (renderer->debugMode) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, + "VULKAN_INTERNAL_AddOptInVulkanOptions: Additional options property was set, but value was null. This may be a bug."); + } + } +} + +static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer, VulkanFeatures *features) { VkResult vulkanResult; VkApplicationInfo appInfo; @@ -11127,7 +11960,9 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) appInfo.applicationVersion = 0; appInfo.pEngineName = "SDLGPU"; appInfo.engineVersion = SDL_VERSION; - appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = features->usesCustomVulkanOptions + ? features->desiredApiVersion + : VK_MAKE_VERSION(1, 0, 0); createFlags = 0; @@ -11141,40 +11976,53 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) return 0; } + Uint32 extraInstanceExtensionCount = features->additionalInstanceExtensionCount; + const char** extraInstanceExtensionNames = features->additionalInstanceExtensionNames; + /* Extra space for the following extensions: * VK_KHR_get_physical_device_properties2 * VK_EXT_swapchain_colorspace * VK_EXT_debug_utils * VK_KHR_portability_enumeration + * + * Plus additional opt-in extensions. */ instanceExtensionNames = SDL_stack_alloc( const char *, - instanceExtensionCount + 4); - SDL_memcpy((void *)instanceExtensionNames, originalInstanceExtensionNames, instanceExtensionCount * sizeof(const char *)); + instanceExtensionCount + 4 + extraInstanceExtensionCount); + const char** nextInstanceExtensionNamePtr = instanceExtensionNames; + SDL_memcpy((void *)nextInstanceExtensionNamePtr, originalInstanceExtensionNames, instanceExtensionCount * sizeof(const char *)); + nextInstanceExtensionNamePtr += instanceExtensionCount; - // Core since 1.1 - instanceExtensionNames[instanceExtensionCount++] = - VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; - -#ifdef SDL_PLATFORM_APPLE - instanceExtensionNames[instanceExtensionCount++] = - VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; - createFlags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; -#endif + if (extraInstanceExtensionCount > 0) { + SDL_memcpy((void *)nextInstanceExtensionNamePtr, extraInstanceExtensionNames, extraInstanceExtensionCount * sizeof(const char *)); + nextInstanceExtensionNamePtr += extraInstanceExtensionCount; + } + int firstUnsupportedExtensionIndex = 0; if (!VULKAN_INTERNAL_CheckInstanceExtensions( instanceExtensionNames, - instanceExtensionCount, + instanceExtensionCount + extraInstanceExtensionCount, &renderer->supportsDebugUtils, - &renderer->supportsColorspace)) { + &renderer->supportsColorspace, + &renderer->supportsPhysicalDeviceProperties2, + &renderer->supportsPortabilityEnumeration, + &firstUnsupportedExtensionIndex)) { + if (renderer->debugMode) { + SDL_LogError(SDL_LOG_CATEGORY_GPU, + "Required Vulkan instance extension '%s' not supported", + instanceExtensionNames[firstUnsupportedExtensionIndex]); + } + SDL_SetError("Required Vulkan instance extension '%s' not supported", + instanceExtensionNames[firstUnsupportedExtensionIndex]); SDL_stack_free((char *)instanceExtensionNames); - SET_STRING_ERROR_AND_RETURN("Required Vulkan instance extensions not supported", false); + return false; } if (renderer->supportsDebugUtils) { // Append the debug extension - instanceExtensionNames[instanceExtensionCount++] = - VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + *nextInstanceExtensionNamePtr++ = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + instanceExtensionCount++; } else { SDL_LogWarn( SDL_LOG_CATEGORY_GPU, @@ -11184,8 +12032,20 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) if (renderer->supportsColorspace) { // Append colorspace extension - instanceExtensionNames[instanceExtensionCount++] = - VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME; + *nextInstanceExtensionNamePtr++ = VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME; + instanceExtensionCount++; + } + + if (renderer->supportsPhysicalDeviceProperties2) { + // Append KHR_physical_device_properties2 extension + *nextInstanceExtensionNamePtr++ = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; + instanceExtensionCount++; + } + + if (renderer->supportsPortabilityEnumeration) { + *nextInstanceExtensionNamePtr++ = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + instanceExtensionCount++; + createFlags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; } createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; @@ -11193,7 +12053,7 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) createInfo.flags = createFlags; createInfo.pApplicationInfo = &appInfo; createInfo.ppEnabledLayerNames = layerNames; - createInfo.enabledExtensionCount = instanceExtensionCount; + createInfo.enabledExtensionCount = instanceExtensionCount + extraInstanceExtensionCount; createInfo.ppEnabledExtensionNames = instanceExtensionNames; if (renderer->debugMode) { createInfo.enabledLayerCount = SDL_arraysize(layerNames); @@ -11219,60 +12079,193 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) return 1; } -static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( +static bool VULKAN_INTERNAL_GetDeviceRank( VulkanRenderer *renderer, VkPhysicalDevice physicalDevice, VulkanExtensions *physicalDeviceExtensions, - Uint32 *queueFamilyIndex, - Uint8 *deviceRank) + Uint64 *deviceRank) { - Uint32 queueFamilyCount, queueFamilyRank, queueFamilyBest; - VkQueueFamilyProperties *queueProps; - bool supportsPresent; - VkPhysicalDeviceProperties deviceProperties; - VkPhysicalDeviceFeatures deviceFeatures; - Uint32 i; - + static const Uint8 DEVICE_PRIORITY_HIGHPERFORMANCE[] = { + 0, // VK_PHYSICAL_DEVICE_TYPE_OTHER + 3, // VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU + 4, // VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU + 2, // VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU + 1 // VK_PHYSICAL_DEVICE_TYPE_CPU + }; + static const Uint8 DEVICE_PRIORITY_LOWPOWER[] = { + 0, // VK_PHYSICAL_DEVICE_TYPE_OTHER + 4, // VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU + 3, // VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU + 2, // VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU + 1 // VK_PHYSICAL_DEVICE_TYPE_CPU + }; const Uint8 *devicePriority = renderer->preferLowPower ? DEVICE_PRIORITY_LOWPOWER : DEVICE_PRIORITY_HIGHPERFORMANCE; + bool isConformant; - /* Get the device rank before doing any checks, in case one fails. - * Note: If no dedicated device exists, one that supports our features - * would be fine + VkPhysicalDeviceType deviceType; + if (physicalDeviceExtensions->KHR_driver_properties || physicalDeviceExtensions->MSFT_layered_driver) { + VkPhysicalDeviceProperties2KHR physicalDeviceProperties; + VkPhysicalDeviceDriverPropertiesKHR physicalDeviceDriverProperties = { 0 }; + VkPhysicalDeviceLayeredDriverPropertiesMSFT physicalDeviceLayeredDriverProperties = { 0 }; + void** ppNext = &physicalDeviceProperties.pNext; + + physicalDeviceProperties.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + + if (physicalDeviceExtensions->KHR_driver_properties) { + *ppNext = &physicalDeviceDriverProperties; + ppNext = &physicalDeviceDriverProperties.pNext; + + physicalDeviceDriverProperties.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR; + } + + if (physicalDeviceExtensions->MSFT_layered_driver) { + *ppNext = &physicalDeviceLayeredDriverProperties; + ppNext = &physicalDeviceLayeredDriverProperties.pNext; + + physicalDeviceLayeredDriverProperties.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LAYERED_DRIVER_PROPERTIES_MSFT; + } + + *ppNext = NULL; + renderer->vkGetPhysicalDeviceProperties2KHR( + physicalDevice, + &physicalDeviceProperties); + + if (physicalDeviceExtensions->KHR_driver_properties) { + isConformant = (physicalDeviceDriverProperties.conformanceVersion.major >= 1); + } else { + isConformant = true; // We can't check this, so just assume it's conformant + } + + if (physicalDeviceExtensions->MSFT_layered_driver && physicalDeviceLayeredDriverProperties.underlyingAPI != VK_LAYERED_DRIVER_UNDERLYING_API_NONE_MSFT) { + /* Rank Dozen above CPU, but below INTEGRATED. + * This is needed for WSL specifically. + */ + deviceType = VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU; + + /* Dozen hasn't been tested for conformance and it probably won't be, + * but WSL may need this so let's be generous. + * -flibit + */ + isConformant = true; + } else { + deviceType = physicalDeviceProperties.properties.deviceType; + } + } else { + VkPhysicalDeviceProperties physicalDeviceProperties; + renderer->vkGetPhysicalDeviceProperties( + physicalDevice, + &physicalDeviceProperties); + deviceType = physicalDeviceProperties.deviceType; + isConformant = true; // We can't check this, so just assume it's conformant + } + + if (renderer->requireHardwareAcceleration) { + if (deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && + deviceType != VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU && + deviceType != VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU) { + // In addition to CPU, "Other" drivers (including layered drivers) don't count as hardware-accelerated + return 0; + } + } + + /* As far as I know, the only drivers available to users that are also + * non-conformant are incomplete Mesa drivers and Vulkan-on-12. hasvk is one + * example of a non-conformant driver that's built by default. + * -flibit */ - renderer->vkGetPhysicalDeviceProperties( - physicalDevice, - &deviceProperties); - if (*deviceRank < devicePriority[deviceProperties.deviceType]) { + if (!isConformant) { + return 0; + } + + /* Apply a large bias on the devicePriority so that we always respect the order in the priority arrays. + * We also rank by e.g. VRAM which should have less influence than the device type. + */ + Uint64 devicePriorityValue = devicePriority[deviceType] * 1000000; + + if (*deviceRank < devicePriorityValue) { /* This device outranks the best device we've found so far! * This includes a dedicated GPU that has less features than an * integrated GPU, because this is a freak case that is almost * never intentionally desired by the end user */ - *deviceRank = devicePriority[deviceProperties.deviceType]; - } else if (*deviceRank > devicePriority[deviceProperties.deviceType]) { + *deviceRank = devicePriorityValue; + } else if (*deviceRank > devicePriorityValue) { /* Device is outranked by a previous device, don't even try to * run a query and reset the rank to avoid overwrites */ *deviceRank = 0; - return 0; + return false; } + /* If we prefer high performance, sum up all device local memory (rounded to megabytes) + * to deviceRank. In the niche case of someone having multiple dedicated GPUs in the same + * system, this theoretically picks the most powerful one (or at least the one with the + * most memory!) + * + * We do this *after* discarding all non suitable devices, which means if this computer + * has multiple dedicated GPUs that all meet our criteria, *and* the user asked for high + * performance, then we always pick the GPU with more VRAM. + */ + if (!renderer->preferLowPower) { + Uint32 i; + Uint64 videoMemory = 0; + VkPhysicalDeviceMemoryProperties deviceMemory; + renderer->vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemory); + for (i = 0; i < deviceMemory.memoryHeapCount; i++) { + VkMemoryHeap heap = deviceMemory.memoryHeaps[i]; + if (heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) { + videoMemory += heap.size; + } + } + // Round it to megabytes (as per the vulkan spec videoMemory is in bytes) + Uint64 videoMemoryRounded = videoMemory / 1024 / 1024; + *deviceRank += videoMemoryRounded; + } + + return true; +} + +static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( + VulkanRenderer *renderer, + VulkanFeatures *features, + VkPhysicalDevice physicalDevice, + VulkanExtensions *physicalDeviceExtensions, + Uint32 *queueFamilyIndex) +{ + Uint32 queueFamilyCount, queueFamilyRank, queueFamilyBest; + VkQueueFamilyProperties *queueProps; + bool supportsPresent; + VkPhysicalDeviceFeatures deviceFeatures; + Uint32 i; + renderer->vkGetPhysicalDeviceFeatures( physicalDevice, &deviceFeatures); - if ((!deviceFeatures.independentBlend && 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)) { + if ((!deviceFeatures.independentBlend && features->desiredVulkan10DeviceFeatures.independentBlend) || + (!deviceFeatures.imageCubeArray && features->desiredVulkan10DeviceFeatures.imageCubeArray) || + (!deviceFeatures.depthClamp && features->desiredVulkan10DeviceFeatures.depthClamp) || + (!deviceFeatures.shaderClipDistance && features->desiredVulkan10DeviceFeatures.shaderClipDistance) || + (!deviceFeatures.drawIndirectFirstInstance && features->desiredVulkan10DeviceFeatures.drawIndirectFirstInstance) || + (!deviceFeatures.sampleRateShading && features->desiredVulkan10DeviceFeatures.sampleRateShading) || + (!deviceFeatures.samplerAnisotropy && features->desiredVulkan10DeviceFeatures.samplerAnisotropy)) { return 0; } + // Check opt-in device features + if (features->usesCustomVulkanOptions) { + bool supportsAllFeatures = VULKAN_INTERNAL_ValidateOptInFeatures(renderer, features, physicalDevice, &deviceFeatures); + if (!supportsAllFeatures) { + return 0; + } + } + if (!VULKAN_INTERNAL_CheckDeviceExtensions( renderer, + features, physicalDevice, physicalDeviceExtensions)) { return 0; @@ -11356,15 +12349,15 @@ static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( return 1; } -static Uint8 VULKAN_INTERNAL_DeterminePhysicalDevice(VulkanRenderer *renderer) +static Uint8 VULKAN_INTERNAL_DeterminePhysicalDevice(VulkanRenderer *renderer, VulkanFeatures *features) { VkResult vulkanResult; VkPhysicalDevice *physicalDevices; VulkanExtensions *physicalDeviceExtensions; Uint32 i, physicalDeviceCount; Sint32 suitableIndex; - Uint32 queueFamilyIndex, suitableQueueFamilyIndex; - Uint8 deviceRank, highestRank; + Uint32 suitableQueueFamilyIndex; + Uint64 highestRank; vulkanResult = renderer->vkEnumeratePhysicalDevices( renderer->instance, @@ -11410,12 +12403,24 @@ static Uint8 VULKAN_INTERNAL_DeterminePhysicalDevice(VulkanRenderer *renderer) suitableQueueFamilyIndex = 0; highestRank = 0; for (i = 0; i < physicalDeviceCount; i += 1) { + Uint32 queueFamilyIndex; + Uint64 deviceRank; + + if (!VULKAN_INTERNAL_IsDeviceSuitable( + renderer, + features, + physicalDevices[i], + &physicalDeviceExtensions[i], + &queueFamilyIndex)) { + // Device does not meet the minimum requirements, skip it entirely + continue; + } + deviceRank = highestRank; - if (VULKAN_INTERNAL_IsDeviceSuitable( + if (VULKAN_INTERNAL_GetDeviceRank( renderer, physicalDevices[i], &physicalDeviceExtensions[i], - &queueFamilyIndex, &deviceRank)) { /* Use this for rendering. * Note that this may override a previous device that @@ -11424,17 +12429,6 @@ static Uint8 VULKAN_INTERNAL_DeterminePhysicalDevice(VulkanRenderer *renderer) suitableIndex = i; suitableQueueFamilyIndex = queueFamilyIndex; highestRank = deviceRank; - } else if (deviceRank > highestRank) { - /* In this case, we found a... "realer?" GPU, - * but it doesn't actually support our Vulkan. - * We should disqualify all devices below as a - * result, because if we don't we end up - * ignoring real hardware and risk using - * something like LLVMpipe instead! - * -flibit - */ - suitableIndex = -1; - highestRank = deviceRank; } } @@ -11479,7 +12473,8 @@ static Uint8 VULKAN_INTERNAL_DeterminePhysicalDevice(VulkanRenderer *renderer) } static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( - VulkanRenderer *renderer) + VulkanRenderer *renderer, + VulkanFeatures *features) { VkResult vulkanResult; VkDeviceCreateInfo deviceCreateInfo; @@ -11507,12 +12502,12 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( // specifying used device features if (haveDeviceFeatures.fillModeNonSolid) { - renderer->desiredDeviceFeatures.fillModeNonSolid = VK_TRUE; + features->desiredVulkan10DeviceFeatures.fillModeNonSolid = VK_TRUE; renderer->supportsFillModeNonSolid = true; } if (haveDeviceFeatures.multiDrawIndirect) { - renderer->desiredDeviceFeatures.multiDrawIndirect = VK_TRUE; + features->desiredVulkan10DeviceFeatures.multiDrawIndirect = VK_TRUE; renderer->supportsMultiDrawIndirect = true; } @@ -11553,7 +12548,67 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( deviceCreateInfo.enabledExtensionCount); CreateDeviceExtensionArray(&renderer->supports, deviceExtensions); deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions; - deviceCreateInfo.pEnabledFeatures = &renderer->desiredDeviceFeatures; + + VkPhysicalDeviceFeatures2 featureList; + int minor = VK_VERSION_MINOR(features->desiredApiVersion); + + struct { + VkPhysicalDevice16BitStorageFeatures storage; + VkPhysicalDeviceMultiviewFeatures multiview; + VkPhysicalDeviceProtectedMemoryFeatures protectedMem; + VkPhysicalDeviceSamplerYcbcrConversionFeatures ycbcr; + VkPhysicalDeviceShaderDrawParametersFeatures drawParams; + VkPhysicalDeviceVariablePointersFeatures varPointers; + } legacyFeatures; + + if (features->usesCustomVulkanOptions && minor > 0) { + featureList.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + featureList.features = features->desiredVulkan10DeviceFeatures; + if (minor > 1) { + featureList.pNext = &features->desiredVulkan11DeviceFeatures; + features->desiredVulkan11DeviceFeatures.pNext = &features->desiredVulkan12DeviceFeatures; + features->desiredVulkan12DeviceFeatures.pNext = minor > 2 ? &features->desiredVulkan13DeviceFeatures : NULL; + features->desiredVulkan13DeviceFeatures.pNext = NULL; + } else { + // Break VkPhysicalDeviceVulkan11Features into pre 1.2 structures for Vulkan 1.1 Support + SDL_zero(legacyFeatures); + + legacyFeatures.storage.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES; + legacyFeatures.storage.storageBuffer16BitAccess = features->desiredVulkan11DeviceFeatures.storageBuffer16BitAccess; + legacyFeatures.storage.storageInputOutput16 = features->desiredVulkan11DeviceFeatures.storageInputOutput16; + legacyFeatures.storage.storagePushConstant16 = features->desiredVulkan11DeviceFeatures.storagePushConstant16; + legacyFeatures.storage.uniformAndStorageBuffer16BitAccess = features->desiredVulkan11DeviceFeatures.uniformAndStorageBuffer16BitAccess; + + legacyFeatures.multiview.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; + legacyFeatures.multiview.multiview = features->desiredVulkan11DeviceFeatures.multiview; + legacyFeatures.multiview.multiviewGeometryShader = features->desiredVulkan11DeviceFeatures.multiviewGeometryShader; + legacyFeatures.multiview.multiviewTessellationShader = features->desiredVulkan11DeviceFeatures.multiviewTessellationShader; + + legacyFeatures.protectedMem.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES; + legacyFeatures.protectedMem.protectedMemory = features->desiredVulkan11DeviceFeatures.protectedMemory; + + legacyFeatures.ycbcr.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES; + legacyFeatures.ycbcr.samplerYcbcrConversion = features->desiredVulkan11DeviceFeatures.samplerYcbcrConversion; + + legacyFeatures.drawParams.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES; + legacyFeatures.drawParams.shaderDrawParameters = features->desiredVulkan11DeviceFeatures.shaderDrawParameters; + + legacyFeatures.varPointers.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES; + legacyFeatures.varPointers.variablePointers = features->desiredVulkan11DeviceFeatures.variablePointers; + legacyFeatures.varPointers.variablePointersStorageBuffer = features->desiredVulkan11DeviceFeatures.variablePointersStorageBuffer; + + featureList.pNext = &legacyFeatures.storage; + legacyFeatures.storage.pNext = &legacyFeatures.multiview; + legacyFeatures.multiview.pNext = &legacyFeatures.protectedMem; + legacyFeatures.protectedMem.pNext = &legacyFeatures.ycbcr; + legacyFeatures.ycbcr.pNext = &legacyFeatures.drawParams; + legacyFeatures.drawParams.pNext = &legacyFeatures.varPointers; + } + deviceCreateInfo.pEnabledFeatures = NULL; + deviceCreateInfo.pNext = &featureList; + } else { + deviceCreateInfo.pEnabledFeatures = &features->desiredVulkan10DeviceFeatures; + } vulkanResult = renderer->vkCreateDevice( renderer->physicalDevice, @@ -11618,11 +12673,31 @@ static void VULKAN_INTERNAL_LoadEntryPoints(void) } static bool VULKAN_INTERNAL_PrepareVulkan( - VulkanRenderer *renderer) + VulkanRenderer *renderer, + VulkanFeatures *features, + SDL_PropertiesID props) { VULKAN_INTERNAL_LoadEntryPoints(); - if (!VULKAN_INTERNAL_CreateInstance(renderer)) { + SDL_zerop(features); + + // Opt out device features (higher compatibility in exchange for reduced functionality) + features->desiredVulkan10DeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + features->desiredVulkan10DeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + features->desiredVulkan10DeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + features->desiredVulkan10DeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + + // These features have near universal support so they are always enabled + features->desiredVulkan10DeviceFeatures.independentBlend = VK_TRUE; + features->desiredVulkan10DeviceFeatures.sampleRateShading = VK_TRUE; + features->desiredVulkan10DeviceFeatures.imageCubeArray = VK_TRUE; + + // Handle opt-in device features + VULKAN_INTERNAL_AddOptInVulkanOptions(props, renderer, features); + + renderer->requireHardwareAcceleration = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_REQUIRE_HARDWARE_ACCELERATION_BOOLEAN, false); + + if (!VULKAN_INTERNAL_CreateInstance(renderer, features)) { SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Vulkan: Could not create Vulkan instance"); return false; } @@ -11631,7 +12706,7 @@ static bool VULKAN_INTERNAL_PrepareVulkan( renderer->func = (PFN_##func)vkGetInstanceProcAddr(renderer->instance, #func); #include "SDL_gpu_vulkan_vkfuncs.h" - if (!VULKAN_INTERNAL_DeterminePhysicalDevice(renderer)) { + if (!VULKAN_INTERNAL_DeterminePhysicalDevice(renderer, features)) { SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Vulkan: Failed to determine a suitable physical device"); return false; } @@ -11642,6 +12717,7 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) { // Set up dummy VulkanRenderer VulkanRenderer *renderer; + VulkanFeatures features; bool result = false; if (!SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, false)) { @@ -11658,21 +12734,16 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) 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; + // This needs to be set early for log filtering + renderer->debugMode = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, 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; + renderer->preferLowPower = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, false); - result = VULKAN_INTERNAL_PrepareVulkan(renderer); + result = VULKAN_INTERNAL_PrepareVulkan(renderer, &features, props); if (result) { renderer->vkDestroyInstance(renderer->instance, NULL); } + SDL_free(renderer); } SDL_Vulkan_UnloadLibrary(); @@ -11683,6 +12754,7 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, SDL_PropertiesID props) { VulkanRenderer *renderer; + VulkanFeatures features; SDL_GPUDevice *result; Uint32 i; @@ -11700,28 +12772,18 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer = (VulkanRenderer *)SDL_calloc(1, sizeof(*renderer)); if (!renderer) { SDL_Vulkan_UnloadLibrary(); - return false; + return NULL; } 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)) { + if (!VULKAN_INTERNAL_PrepareVulkan(renderer, &features, props)) { + SET_STRING_ERROR("Failed to initialize Vulkan!"); SDL_free(renderer); SDL_Vulkan_UnloadLibrary(); - SET_STRING_ERROR_AND_RETURN("Failed to initialize Vulkan!", NULL); + return NULL; } renderer->props = SDL_CreateProperties(); @@ -11824,15 +12886,15 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S } } - if (!VULKAN_INTERNAL_CreateLogicalDevice( - renderer)) { + if (!VULKAN_INTERNAL_CreateLogicalDevice(renderer, &features)) { + SET_STRING_ERROR("Failed to create logical device!"); SDL_free(renderer); SDL_Vulkan_UnloadLibrary(); - SET_STRING_ERROR_AND_RETURN("Failed to create logical device!", NULL); + return NULL; } // FIXME: just move this into this function - result = (SDL_GPUDevice *)SDL_malloc(sizeof(SDL_GPUDevice)); + result = (SDL_GPUDevice *)SDL_calloc(1, sizeof(SDL_GPUDevice)); ASSIGN_DRIVER(VULKAN) result->driverData = (SDL_GPURenderer *)renderer; diff --git a/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan_vkfuncs.h b/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan_vkfuncs.h index 7316eb91..a56beff1 100644 --- a/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan_vkfuncs.h +++ b/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan_vkfuncs.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -51,6 +51,9 @@ VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceImageFormatProperties) VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceMemoryProperties) VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceProperties) +// Vulkan 1.1 (Needed for opt-in feature checks) +VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceFeatures2) + // VK_KHR_get_physical_device_properties2, needed for KHR_driver_properties VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceProperties2KHR) diff --git a/libs/SDL3/src/haptic/SDL_haptic.c b/libs/SDL3/src/haptic/SDL_haptic.c index 2e8024fa..577502c9 100644 --- a/libs/SDL3/src/haptic/SDL_haptic.c +++ b/libs/SDL3/src/haptic/SDL_haptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -107,10 +107,10 @@ static int SDL_Haptic_Get_Naxes(Uint16 vid, Uint16 pid) static SDL_Haptic *SDL_haptics = NULL; -#define CHECK_HAPTIC_MAGIC(haptic, result) \ - if (!SDL_ObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC)) { \ - SDL_InvalidParamError("haptic"); \ - return result; \ +#define CHECK_HAPTIC_MAGIC(haptic, result) \ + CHECK_PARAM(!SDL_ObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC)) { \ + SDL_InvalidParamError("haptic"); \ + return result; \ } bool SDL_InitHaptics(void) @@ -306,9 +306,9 @@ bool SDL_IsJoystickHaptic(SDL_Joystick *joystick) SDL_LockJoysticks(); { - // Must be a valid joystick + // Must be a valid joystick, but not a gamepad unless running under sdl2-compat if (SDL_IsJoystickValid(joystick) && - !SDL_IsGamepad(SDL_GetJoystickID(joystick))) { + (SDL_GetHintBoolean("SDL2_COMPAT", false) || !SDL_IsGamepad(SDL_GetJoystickID(joystick)))) { #ifdef SDL_JOYSTICK_HIDAPI result = SDL_SYS_JoystickIsHaptic(joystick) || SDL_HIDAPI_JoystickIsHaptic(joystick); #else @@ -512,7 +512,7 @@ bool SDL_HapticEffectSupported(SDL_Haptic *haptic, const SDL_HapticEffect *effec { CHECK_HAPTIC_MAGIC(haptic, false); - if (!effect) { + CHECK_PARAM(!effect) { return false; } @@ -528,7 +528,7 @@ SDL_HapticEffectID SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEf CHECK_HAPTIC_MAGIC(haptic, -1); - if (!effect) { + CHECK_PARAM(!effect) { SDL_InvalidParamError("effect"); return -1; } @@ -577,25 +577,25 @@ bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect, const { CHECK_HAPTIC_MAGIC(haptic, false); + CHECK_PARAM(!ValidEffect(haptic, effect)) { + return false; + } + + CHECK_PARAM(!data) { + return SDL_InvalidParamError("data"); + } + + // Can't change type dynamically. + CHECK_PARAM(data->type != haptic->effects[effect].effect.type) { + return SDL_SetError("Haptic: Updating effect type is illegal."); + } + #ifdef SDL_JOYSTICK_HIDAPI if (SDL_HIDAPI_HapticIsHidapi(haptic)) { return SDL_HIDAPI_HapticUpdateEffect(haptic, effect, data); } #endif - if (!ValidEffect(haptic, effect)) { - return false; - } - - if (!data) { - return SDL_InvalidParamError("data"); - } - - // Can't change type dynamically. - if (data->type != haptic->effects[effect].effect.type) { - return SDL_SetError("Haptic: Updating effect type is illegal."); - } - // Updates the effect if (!SDL_SYS_HapticUpdateEffect(haptic, &haptic->effects[effect], data)) { return false; diff --git a/libs/SDL3/src/haptic/SDL_haptic_c.h b/libs/SDL3/src/haptic/SDL_haptic_c.h index 3f5ce873..0ccaf866 100644 --- a/libs/SDL3/src/haptic/SDL_haptic_c.h +++ b/libs/SDL3/src/haptic/SDL_haptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/haptic/SDL_syshaptic.h b/libs/SDL3/src/haptic/SDL_syshaptic.h index 21b3afd1..74e78f65 100644 --- a/libs/SDL3/src/haptic/SDL_syshaptic.h +++ b/libs/SDL3/src/haptic/SDL_syshaptic.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/haptic/android/SDL_syshaptic.c b/libs/SDL3/src/haptic/android/SDL_syshaptic.c index d155dbcd..760854e9 100644 --- a/libs/SDL3/src/haptic/android/SDL_syshaptic.c +++ b/libs/SDL3/src/haptic/android/SDL_syshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/haptic/android/SDL_syshaptic_c.h b/libs/SDL3/src/haptic/android/SDL_syshaptic_c.h index 4f58f4f7..ac16356c 100644 --- a/libs/SDL3/src/haptic/android/SDL_syshaptic_c.h +++ b/libs/SDL3/src/haptic/android/SDL_syshaptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/haptic/darwin/SDL_syshaptic.c b/libs/SDL3/src/haptic/darwin/SDL_syshaptic.c index b48a3feb..40343206 100644 --- a/libs/SDL3/src/haptic/darwin/SDL_syshaptic.c +++ b/libs/SDL3/src/haptic/darwin/SDL_syshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -785,7 +785,7 @@ static bool SDL_SYS_ToFFEFFECT(SDL_Haptic *haptic, FFEFFECT *dest, const SDL_Hap DWORD *axes = NULL; // Set global stuff. - SDL_memset(dest, 0, sizeof(FFEFFECT)); + SDL_zerop(dest); dest->dwSize = sizeof(FFEFFECT); // Set the structure size. dest->dwSamplePeriod = 0; // Not used by us. dest->dwGain = 10000; // Gain is set globally, not locally. @@ -1166,7 +1166,7 @@ bool SDL_SYS_HapticUpdateEffect(SDL_Haptic *haptic, FFEFFECT temp; // Get the effect. - SDL_memset(&temp, 0, sizeof(FFEFFECT)); + SDL_zero(temp); if (!SDL_SYS_ToFFEFFECT(haptic, &temp, data)) { goto err_update; } diff --git a/libs/SDL3/src/haptic/darwin/SDL_syshaptic_c.h b/libs/SDL3/src/haptic/darwin/SDL_syshaptic_c.h index 03c67ec7..0465cc2a 100644 --- a/libs/SDL3/src/haptic/darwin/SDL_syshaptic_c.h +++ b/libs/SDL3/src/haptic/darwin/SDL_syshaptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/haptic/dummy/SDL_syshaptic.c b/libs/SDL3/src/haptic/dummy/SDL_syshaptic.c index 81f711f0..d44c05cb 100644 --- a/libs/SDL3/src/haptic/dummy/SDL_syshaptic.c +++ b/libs/SDL3/src/haptic/dummy/SDL_syshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.c b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.c index 7f59f6c9..f9985fc1 100644 --- a/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.c +++ b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.c @@ -27,8 +27,6 @@ #include "SDL3/SDL_mutex.h" #include "SDL3/SDL_error.h" -extern struct SDL_JoystickDriver SDL_HIDAPI_JoystickDriver; - typedef struct haptic_list_node { SDL_Haptic *haptic; @@ -146,6 +144,15 @@ bool SDL_HIDAPI_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystic haptic->nplaying = device->driver->NumEffectsPlaying(device); haptic->supported = device->driver->GetFeatures(device); haptic->naxes = device->driver->NumAxes(device); + haptic->neffects = device->driver->NumEffects(device); + haptic->effects = (struct haptic_effect *)SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects); + if (haptic->effects == NULL) { + SDL_free(list_node); + device->driver->Close(device); + SDL_free(device); + return SDL_OutOfMemory(); + } + SDL_memset(haptic->effects, 0, sizeof(struct haptic_effect) * haptic->neffects); // outside of SYS_HAPTIC haptic->instance_id = 255; @@ -217,6 +224,7 @@ void SDL_HIDAPI_HapticClose(SDL_Haptic *haptic) SDL_free(device->ctx); SDL_free(device); + SDL_free(haptic->effects); SDL_free(cur); SDL_UnlockMutex(haptic_list_mutex); return; @@ -240,7 +248,11 @@ void SDL_HIDAPI_HapticQuit(void) 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); + SDL_HapticEffectID new_id = device->driver->CreateEffect(device, base); + if (new_id >= 0){ + haptic->effects[new_id].effect = *base; + } + return new_id; } bool SDL_HIDAPI_HapticUpdateEffect(SDL_Haptic *haptic, SDL_HapticEffectID id, const SDL_HapticEffect *data) diff --git a/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_c.h b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_c.h index 7d678811..3ae4f29b 100644 --- a/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_c.h +++ b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_c.h @@ -67,4 +67,4 @@ struct SDL_HIDAPI_HapticDriver extern SDL_HIDAPI_HapticDriver SDL_HIDAPI_HapticDriverLg4ff; -#endif //SDL_joystick_c_h_ +#endif //SDL_hidapihaptic_c_h_ diff --git a/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index ad9bf0d9..7e6b3dc4 100644 --- a/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -694,9 +694,9 @@ static int lg4ff_init_slots(struct lg4ff_device *device) return -1; } - SDL_memset(&device->states, 0, sizeof(device->states)); - SDL_memset(&device->slots, 0, sizeof(device->slots)); - SDL_memset(¶meters, 0, sizeof(parameters)); + SDL_zero(device->states); + SDL_zero(device->slots); + SDL_zero(parameters); device->slots[0].effect_type = SDL_HAPTIC_CONSTANT; device->slots[1].effect_type = SDL_HAPTIC_SPRING; @@ -737,7 +737,7 @@ static int lg4ff_timer(struct lg4ff_device *device) // XXX how to detect stacked up effects here? - SDL_memset(parameters, 0, sizeof(parameters)); + SDL_zeroa(parameters); gain = (Uint16)((Uint32)device->gain * device->app_gain / 0xffff); @@ -836,7 +836,7 @@ static bool SDL_HIDAPI_HapticDriverLg4ff_JoystickSupported(SDL_Joystick *joystic if (vendor_id != USB_VENDOR_ID_LOGITECH) { return false; } - for (int i = 0;i < sizeof(supported_device_ids) / sizeof(Uint32);i++) { + for (int i = 0;i < SDL_arraysize(supported_device_ids);i++) { if (supported_device_ids[i] == product_id) { return true; } @@ -895,7 +895,7 @@ static void *SDL_HIDAPI_HapticDriverLg4ff_Open(SDL_Joystick *joystick) SDL_OutOfMemory(); return NULL; } - SDL_memset(ctx, 0, sizeof(lg4ff_device)); + SDL_zerop(ctx); ctx->hid_handle = joystick; if (lg4ff_init_slots(ctx) != 0) { @@ -1200,7 +1200,7 @@ static bool SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter(SDL_HIDAPI_HapticDevice * } expand_a = expand_a >> 1; - SDL_memset(cmd, 0x00, 7); + SDL_zeroa(cmd); cmd[0] = 0xfe; cmd[1] = 0x0d; cmd[2] = (Uint8)(expand_a / 0xaaaa); @@ -1215,7 +1215,7 @@ static bool SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter(SDL_HIDAPI_HapticDevice * } // enable - SDL_memset(cmd, 0x00, 7); + SDL_zeroa(cmd); cmd[0] = 0x14; ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); diff --git a/libs/SDL3/src/haptic/linux/SDL_syshaptic.c b/libs/SDL3/src/haptic/linux/SDL_syshaptic.c index 842f577c..e1075625 100644 --- a/libs/SDL3/src/haptic/linux/SDL_syshaptic.c +++ b/libs/SDL3/src/haptic/linux/SDL_syshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -603,7 +603,7 @@ void SDL_SYS_HapticClose(SDL_Haptic *haptic) } // Clear the rest. - SDL_memset(haptic, 0, sizeof(SDL_Haptic)); + SDL_zerop(haptic); } /* @@ -725,7 +725,7 @@ static bool SDL_SYS_ToFFEffect(struct ff_effect *dest, const SDL_HapticEffect *s const SDL_HapticLeftRight *leftright; // Clear up - SDL_memset(dest, 0, sizeof(struct ff_effect)); + SDL_zerop(dest); switch (src->type) { case SDL_HAPTIC_CONSTANT: @@ -1117,13 +1117,12 @@ bool SDL_SYS_HapticResume(SDL_Haptic *haptic) */ bool SDL_SYS_HapticStopAll(SDL_Haptic *haptic) { - int i, ret; + int i; // Linux does not support this natively so we have to loop. for (i = 0; i < haptic->neffects; i++) { if (haptic->effects[i].hweffect != NULL) { - ret = SDL_SYS_HapticStopEffect(haptic, &haptic->effects[i]); - if (ret < 0) { + if (!SDL_SYS_HapticStopEffect(haptic, &haptic->effects[i])) { return SDL_SetError("Haptic: Error while trying to stop all playing effects."); } } diff --git a/libs/SDL3/src/haptic/windows/SDL_dinputhaptic.c b/libs/SDL3/src/haptic/windows/SDL_dinputhaptic.c index 255aac01..fa374e43 100644 --- a/libs/SDL3/src/haptic/windows/SDL_dinputhaptic.c +++ b/libs/SDL3/src/haptic/windows/SDL_dinputhaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -31,11 +31,7 @@ /* * External stuff. */ -#ifdef SDL_VIDEO_DRIVER_WINDOWS extern HWND SDL_HelperWindow; -#else -static const HWND SDL_HelperWindow = NULL; -#endif /* * Internal stuff. @@ -603,7 +599,7 @@ static bool SDL_SYS_ToDIEFFECT(SDL_Haptic *haptic, DIEFFECT *dest, DWORD *axes; // Set global stuff. - SDL_memset(dest, 0, sizeof(DIEFFECT)); + SDL_zerop(dest); dest->dwSize = sizeof(DIEFFECT); // Set the structure size. dest->dwSamplePeriod = 0; // Not used by us. dest->dwGain = 10000; // Gain is set globally, not locally. @@ -950,6 +946,103 @@ err_effectdone: return false; } +BOOL DIGetDirectionUpdateFlag(DIEFFECT *before, DIEFFECT *after) +{ + if (before->cAxes != after->cAxes) { + return true; + } + // rglDirection must be non-null for DIEP_DIRECTION to be a valid flag + if (after->rglDirection == NULL) { + return false; + } + if (before->rglDirection == NULL) { + return true; + } + return SDL_memcmp(before->rglDirection, after->rglDirection, sizeof(LONG) * before->cAxes) != 0; +} + +BOOL DIGetEnvelopeUpdateFlag(DIEFFECT* before, DIEFFECT* after) +{ + if (before->lpEnvelope == NULL && after->lpEnvelope == NULL) { + return false; + } + // A null lpEnvelope is valid for DIEP_ENVELOPE (clears the envelope from the effect) + if (before->lpEnvelope == NULL || after->lpEnvelope == NULL) { + return true; + } + return SDL_memcmp(before->lpEnvelope, after->lpEnvelope, sizeof(DIENVELOPE)) != 0; +} + +BOOL DIGetTypeSpecificParamsUpdateFlag(DIEFFECT *before, DIEFFECT *after) +{ + // Shouldn't happen since this implies an effect's type somehow changed, but need to check to avoid an out-of-bounds memcmp + if (before->cbTypeSpecificParams != after->cbTypeSpecificParams) { + return true; + } + // lpvTypeSpecificParams must be non-null for the DIEP_TYPESPECIFICPARAMS flag. + if (after->lpvTypeSpecificParams == NULL) { + return false; + } + if (before->lpvTypeSpecificParams == NULL) { + return true; + } + return SDL_memcmp(before->lpvTypeSpecificParams, after->lpvTypeSpecificParams, before->cbTypeSpecificParams) != 0; +} + +/* + Calculate the exact flags needed when updating an existing DirectInput haptic effect. +*/ +DWORD DICalculateUpdateFlags(DIEFFECT *before, DIEFFECT *after) +{ + DWORD flags = 0; + + if (DIGetDirectionUpdateFlag(before, after)) { + flags |= DIEP_DIRECTION; + } + + if (before->dwDuration != after->dwDuration) { + flags |= DIEP_DURATION; + } + + if (DIGetEnvelopeUpdateFlag(before, after)) { + flags |= DIEP_ENVELOPE; + } + + if (before->dwStartDelay != after->dwStartDelay) { + flags |= DIEP_STARTDELAY; + } + + if (before->dwTriggerButton != after->dwTriggerButton) { + flags |= DIEP_TRIGGERBUTTON; + } + + if (before->dwTriggerRepeatInterval != after->dwTriggerRepeatInterval) { + flags |= DIEP_TRIGGERREPEATINTERVAL; + } + + if (DIGetTypeSpecificParamsUpdateFlag(before, after)) { + flags |= DIEP_TYPESPECIFICPARAMS; + } + + if (flags == 0) { + /* Awkward: SDL_UpdateHapticEffect was called, but nothing was changed. + * Calling IDirectInputEffect_SetParameters with no flags is nonsense, + * so our options are to either send all the flags, or exit early. + * Sending all the flags seems like the safer option: The programmer may be trying + * to force an update for some reason (e.g. driver bug workaround?). Conversely, + * if the programmer doesn't want IDirectInputEffect_SetParameters to be called, they + * can just avoid calling SDL_UpdateHapticEffect when there's no changes. */ + flags = DIEP_DIRECTION | + DIEP_DURATION | + DIEP_ENVELOPE | + DIEP_STARTDELAY | + DIEP_TRIGGERBUTTON | + DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS; + } + + return flags; +} + bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data) { HRESULT ret; @@ -957,19 +1050,12 @@ bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *eff DIEFFECT temp; // Get the effect. - SDL_memset(&temp, 0, sizeof(DIEFFECT)); + SDL_zero(temp); if (!SDL_SYS_ToDIEFFECT(haptic, &temp, data)) { goto err_update; } - /* Set the flags. Might be worthwhile to diff temp with loaded effect and - * only change those parameters. */ - flags = DIEP_DIRECTION | - DIEP_DURATION | - DIEP_ENVELOPE | - DIEP_STARTDELAY | - DIEP_TRIGGERBUTTON | - DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS; + flags = DICalculateUpdateFlags(&effect->hweffect->effect, &temp); // Create the actual effect. ret = diff --git a/libs/SDL3/src/haptic/windows/SDL_dinputhaptic_c.h b/libs/SDL3/src/haptic/windows/SDL_dinputhaptic_c.h index d6265c92..a06c3e4a 100644 --- a/libs/SDL3/src/haptic/windows/SDL_dinputhaptic_c.h +++ b/libs/SDL3/src/haptic/windows/SDL_dinputhaptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/haptic/windows/SDL_windowshaptic.c b/libs/SDL3/src/haptic/windows/SDL_windowshaptic.c index e21ab918..e58faad4 100644 --- a/libs/SDL3/src/haptic/windows/SDL_windowshaptic.c +++ b/libs/SDL3/src/haptic/windows/SDL_windowshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/haptic/windows/SDL_windowshaptic_c.h b/libs/SDL3/src/haptic/windows/SDL_windowshaptic_c.h index c4b69287..202cb871 100644 --- a/libs/SDL3/src/haptic/windows/SDL_windowshaptic_c.h +++ b/libs/SDL3/src/haptic/windows/SDL_windowshaptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/hidapi/SDL_hidapi.c b/libs/SDL3/src/hidapi/SDL_hidapi.c index 41e24ec8..0dda4d4b 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi.c +++ b/libs/SDL3/src/hidapi/SDL_hidapi.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -32,6 +32,7 @@ #include "SDL_hidapi_c.h" #include "../joystick/usb_ids.h" +#include "../joystick/SDL_joystick_c.h" #include "../SDL_hints_c.h" // Initial type declarations @@ -40,6 +41,15 @@ #ifndef SDL_HIDAPI_DISABLED +#ifdef SDL_LIBUSB_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "hidabi-libusb", + "Support for joysticks through libusb", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_LIBUSB_DYNAMIC +) +#endif + #if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) #include "../core/windows/SDL_windows.h" #endif @@ -378,18 +388,20 @@ static void HIDAPI_UpdateDiscovery(void) } #if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) -#if 0 // just let the usual SDL_PumpEvents loop dispatch these, fixing bug 4286. --ryan. - // We'll only get messages on the same thread that created the window - if (SDL_GetCurrentThreadID() == SDL_HIDAPI_discovery.m_nThreadID) { - MSG msg; - while (PeekMessage(&msg, SDL_HIDAPI_discovery.m_hwndMsg, 0, 0, PM_NOREMOVE)) { - if (GetMessageA(&msg, SDL_HIDAPI_discovery.m_hwndMsg, 0, 0) != 0) { - TranslateMessage(&msg); - DispatchMessage(&msg); + if (SDL_IsVideoThread()) { + // just let the usual SDL_PumpEvents loop dispatch these, fixing bug 2998. --ryan. + } else { + // We'll only get messages on the same thread that created the window + if (SDL_GetCurrentThreadID() == SDL_HIDAPI_discovery.m_nThreadID) { + MSG msg; + while (PeekMessage(&msg, SDL_HIDAPI_discovery.m_hwndMsg, 0, 0, PM_NOREMOVE)) { + if (GetMessageA(&msg, SDL_HIDAPI_discovery.m_hwndMsg, 0, 0) != 0) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } } } } -#endif #endif // defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) #ifdef SDL_PLATFORM_MACOS @@ -531,8 +543,8 @@ static void HIDAPI_ShutdownDiscovery(void) // Platform HIDAPI Implementation #define HIDAPI_USING_SDL_RUNTIME -#define HIDAPI_IGNORE_DEVICE(BUS, VID, PID, USAGE_PAGE, USAGE) \ - SDL_HIDAPI_ShouldIgnoreDevice(BUS, VID, PID, USAGE_PAGE, USAGE) +#define HIDAPI_IGNORE_DEVICE(BUS, VID, PID, USAGE_PAGE, USAGE, LIBUSB) \ + SDL_HIDAPI_ShouldIgnoreDevice(BUS, VID, PID, USAGE_PAGE, USAGE, LIBUSB) struct PLATFORM_hid_device_; typedef struct PLATFORM_hid_device_ PLATFORM_hid_device; @@ -689,131 +701,77 @@ typedef struct DRIVER_hid_device_ DRIVER_hid_device; #ifdef HAVE_LIBUSB // libusb HIDAPI Implementation -// Include this now, for our dynamically-loaded libusb context -#include +#include "../misc/SDL_libusb.h" -static struct -{ - SDL_SharedObject *libhandle; +static SDL_LibUSBContext *libusb_ctx; - /* *INDENT-OFF* */ // clang-format off - int (LIBUSB_CALL *init)(libusb_context **ctx); - void (LIBUSB_CALL *exit)(libusb_context *ctx); - ssize_t (LIBUSB_CALL *get_device_list)(libusb_context *ctx, libusb_device ***list); - void (LIBUSB_CALL *free_device_list)(libusb_device **list, int unref_devices); - int (LIBUSB_CALL *get_device_descriptor)(libusb_device *dev, struct libusb_device_descriptor *desc); - int (LIBUSB_CALL *get_active_config_descriptor)(libusb_device *dev, struct libusb_config_descriptor **config); - int (LIBUSB_CALL *get_config_descriptor)( - libusb_device *dev, - uint8_t config_index, - struct libusb_config_descriptor **config - ); - void (LIBUSB_CALL *free_config_descriptor)(struct libusb_config_descriptor *config); - uint8_t (LIBUSB_CALL *get_bus_number)(libusb_device *dev); - int (LIBUSB_CALL *get_port_numbers)(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len); - uint8_t (LIBUSB_CALL *get_device_address)(libusb_device *dev); - int (LIBUSB_CALL *open)(libusb_device *dev, libusb_device_handle **dev_handle); - void (LIBUSB_CALL *close)(libusb_device_handle *dev_handle); - libusb_device *(LIBUSB_CALL *get_device)(libusb_device_handle *dev_handle); - int (LIBUSB_CALL *claim_interface)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *release_interface)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *kernel_driver_active)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *detach_kernel_driver)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *attach_kernel_driver)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *set_interface_alt_setting)(libusb_device_handle *dev, int interface_number, int alternate_setting); - struct libusb_transfer * (LIBUSB_CALL *alloc_transfer)(int iso_packets); - int (LIBUSB_CALL *submit_transfer)(struct libusb_transfer *transfer); - int (LIBUSB_CALL *cancel_transfer)(struct libusb_transfer *transfer); - void (LIBUSB_CALL *free_transfer)(struct libusb_transfer *transfer); - int (LIBUSB_CALL *control_transfer)( - libusb_device_handle *dev_handle, - uint8_t request_type, - uint8_t bRequest, - uint16_t wValue, - uint16_t wIndex, - unsigned char *data, - uint16_t wLength, - unsigned int timeout - ); - int (LIBUSB_CALL *interrupt_transfer)( - libusb_device_handle *dev_handle, - unsigned char endpoint, - unsigned char *data, - int length, - int *actual_length, - unsigned int timeout - ); - int (LIBUSB_CALL *handle_events)(libusb_context *ctx); - int (LIBUSB_CALL *handle_events_completed)(libusb_context *ctx, int *completed); - const char * (LIBUSB_CALL *error_name)(int errcode); -/* *INDENT-ON* */ // clang-format on - -} libusb_ctx; - -#define libusb_init libusb_ctx.init -#define libusb_exit libusb_ctx.exit -#define libusb_get_device_list libusb_ctx.get_device_list -#define libusb_free_device_list libusb_ctx.free_device_list -#define libusb_get_device_descriptor libusb_ctx.get_device_descriptor -#define libusb_get_active_config_descriptor libusb_ctx.get_active_config_descriptor -#define libusb_get_config_descriptor libusb_ctx.get_config_descriptor -#define libusb_free_config_descriptor libusb_ctx.free_config_descriptor -#define libusb_get_bus_number libusb_ctx.get_bus_number -#define libusb_get_port_numbers libusb_ctx.get_port_numbers -#define libusb_get_device_address libusb_ctx.get_device_address -#define libusb_open libusb_ctx.open -#define libusb_close libusb_ctx.close -#define libusb_get_device libusb_ctx.get_device -#define libusb_claim_interface libusb_ctx.claim_interface -#define libusb_release_interface libusb_ctx.release_interface -#define libusb_kernel_driver_active libusb_ctx.kernel_driver_active -#define libusb_detach_kernel_driver libusb_ctx.detach_kernel_driver -#define libusb_attach_kernel_driver libusb_ctx.attach_kernel_driver -#define libusb_set_interface_alt_setting libusb_ctx.set_interface_alt_setting -#define libusb_alloc_transfer libusb_ctx.alloc_transfer -#define libusb_submit_transfer libusb_ctx.submit_transfer -#define libusb_cancel_transfer libusb_ctx.cancel_transfer -#define libusb_free_transfer libusb_ctx.free_transfer -#define libusb_control_transfer libusb_ctx.control_transfer -#define libusb_interrupt_transfer libusb_ctx.interrupt_transfer -#define libusb_handle_events libusb_ctx.handle_events -#define libusb_handle_events_completed libusb_ctx.handle_events_completed -#define libusb_error_name libusb_ctx.error_name +#define libusb_init libusb_ctx->init +#define libusb_exit libusb_ctx->exit +#define libusb_get_device_list libusb_ctx->get_device_list +#define libusb_free_device_list libusb_ctx->free_device_list +#define libusb_get_device_descriptor libusb_ctx->get_device_descriptor +#define libusb_get_active_config_descriptor libusb_ctx->get_active_config_descriptor +#define libusb_get_config_descriptor libusb_ctx->get_config_descriptor +#define libusb_free_config_descriptor libusb_ctx->free_config_descriptor +#define libusb_get_bus_number libusb_ctx->get_bus_number +#define libusb_get_port_numbers libusb_ctx->get_port_numbers +#define libusb_get_device_address libusb_ctx->get_device_address +#define libusb_open libusb_ctx->open +#define libusb_close libusb_ctx->close +#define libusb_get_device libusb_ctx->get_device +#define libusb_claim_interface libusb_ctx->claim_interface +#define libusb_release_interface libusb_ctx->release_interface +#define libusb_kernel_driver_active libusb_ctx->kernel_driver_active +#define libusb_detach_kernel_driver libusb_ctx->detach_kernel_driver +#define libusb_attach_kernel_driver libusb_ctx->attach_kernel_driver +#define libusb_set_interface_alt_setting libusb_ctx->set_interface_alt_setting +#define libusb_alloc_transfer libusb_ctx->alloc_transfer +#define libusb_submit_transfer libusb_ctx->submit_transfer +#define libusb_cancel_transfer libusb_ctx->cancel_transfer +#define libusb_free_transfer libusb_ctx->free_transfer +#define libusb_control_transfer libusb_ctx->control_transfer +#define libusb_interrupt_transfer libusb_ctx->interrupt_transfer +#define libusb_bulk_transfer libusb_ctx->bulk_transfer +#define libusb_handle_events libusb_ctx->handle_events +#define libusb_handle_events_completed libusb_ctx->handle_events_completed +#define libusb_error_name libusb_ctx->error_name struct LIBUSB_hid_device_; typedef struct LIBUSB_hid_device_ LIBUSB_hid_device; -#define free_hid_device LIBUSB_free_hid_device -#define hid_close LIBUSB_hid_close -#define hid_device LIBUSB_hid_device -#define hid_device_ LIBUSB_hid_device_ -#define hid_enumerate LIBUSB_hid_enumerate -#define hid_error LIBUSB_hid_error -#define hid_exit LIBUSB_hid_exit -#define hid_free_enumeration LIBUSB_hid_free_enumeration -#define hid_get_device_info LIBUSB_hid_get_device_info -#define hid_get_feature_report LIBUSB_hid_get_feature_report -#define hid_get_indexed_string LIBUSB_hid_get_indexed_string -#define hid_get_input_report LIBUSB_hid_get_input_report -#define hid_get_manufacturer_string LIBUSB_hid_get_manufacturer_string -#define hid_get_product_string LIBUSB_hid_get_product_string -#define hid_get_report_descriptor LIBUSB_hid_get_report_descriptor -#define hid_get_serial_number_string LIBUSB_hid_get_serial_number_string -#define hid_init LIBUSB_hid_init -#define hid_open LIBUSB_hid_open -#define hid_open_path LIBUSB_hid_open_path -#define hid_read LIBUSB_hid_read -#define hid_read_timeout LIBUSB_hid_read_timeout -#define hid_send_feature_report LIBUSB_hid_send_feature_report -#define hid_set_nonblocking LIBUSB_hid_set_nonblocking -#define hid_write LIBUSB_hid_write -#define hid_version LIBUSB_hid_version -#define hid_version_str LIBUSB_hid_version_str -#define input_report LIBUSB_input_report -#define make_path LIBUSB_make_path -#define new_hid_device LIBUSB_new_hid_device -#define read_thread LIBUSB_read_thread -#define return_data LIBUSB_return_data +#define free_hid_device LIBUSB_free_hid_device +#define get_usb_code_for_current_locale LIBUSB_get_usb_code_for_current_locale +#define hid_close LIBUSB_hid_close +#define hid_device LIBUSB_hid_device +#define hid_device_ LIBUSB_hid_device_ +#define hid_enumerate LIBUSB_hid_enumerate +#define hid_error LIBUSB_hid_error +#define hid_exit LIBUSB_hid_exit +#define hid_free_enumeration LIBUSB_hid_free_enumeration +#define hid_get_device_info LIBUSB_hid_get_device_info +#define hid_get_feature_report LIBUSB_hid_get_feature_report +#define hid_get_indexed_string LIBUSB_hid_get_indexed_string +#define hid_get_input_report LIBUSB_hid_get_input_report +#define hid_get_manufacturer_string LIBUSB_hid_get_manufacturer_string +#define hid_get_product_string LIBUSB_hid_get_product_string +#define hid_get_report_descriptor LIBUSB_hid_get_report_descriptor +#define hid_get_serial_number_string LIBUSB_hid_get_serial_number_string +#define hid_init LIBUSB_hid_init +#define hid_open LIBUSB_hid_open +#define hid_open_path LIBUSB_hid_open_path +#define hid_read LIBUSB_hid_read +#define hid_read_timeout LIBUSB_hid_read_timeout +#define hid_send_feature_report LIBUSB_hid_send_feature_report +#define hid_set_nonblocking LIBUSB_hid_set_nonblocking +#define hid_write LIBUSB_hid_write +#define hid_libusb_wrap_sys_device LIBUSB_hid_libusb_wrap_sys_device +#define hid_version LIBUSB_hid_version +#define hid_version_str LIBUSB_hid_version_str +#define input_report LIBUSB_input_report +#define make_path LIBUSB_make_path +#define new_hid_device LIBUSB_new_hid_device +#define read_thread LIBUSB_read_thread +#define return_data LIBUSB_return_data #include "SDL_hidapi_libusb.h" @@ -843,6 +801,7 @@ typedef struct LIBUSB_hid_device_ LIBUSB_hid_device; #undef libusb_free_transfer #undef libusb_control_transfer #undef libusb_interrupt_transfer +#undef libusb_bulk_transfer #undef libusb_handle_events #undef libusb_handle_events_completed #undef libusb_error_name @@ -877,6 +836,10 @@ typedef struct LIBUSB_hid_device_ LIBUSB_hid_device; #undef read_thread #undef return_data +#endif // HAVE_LIBUSB + +#endif // !SDL_HIDAPI_DISABLED + /* If the platform has any backend other than libusb, try to avoid using * libusb as the main backend for devices, since it detaches drivers and * therefore makes devices inaccessible to the rest of the OS. @@ -888,26 +851,25 @@ typedef struct LIBUSB_hid_device_ LIBUSB_hid_device; static const struct { Uint16 vendor; Uint16 product; -} SDL_libusb_whitelist[] = { - { 0x057e, 0x0337 } // Nintendo WUP-028, Wii U/Switch GameCube Adapter +} SDL_libusb_required[] = { + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER }, + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER }, + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT }, + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT }, + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_PRO }, }; -static bool IsInWhitelist(Uint16 vendor, Uint16 product) +static bool RequiresLibUSB(Uint16 vendor, Uint16 product) { - int i; - for (i = 0; i < SDL_arraysize(SDL_libusb_whitelist); i += 1) { - if (vendor == SDL_libusb_whitelist[i].vendor && - product == SDL_libusb_whitelist[i].product) { + for (int i = 0; i < SDL_arraysize(SDL_libusb_required); ++i) { + if (vendor == SDL_libusb_required[i].vendor && + product == SDL_libusb_required[i].product) { return true; } } return false; } -#endif // HAVE_LIBUSB - -#endif // !SDL_HIDAPI_DISABLED - #if defined(HAVE_PLATFORM_BACKEND) || defined(HAVE_DRIVER_BACKEND) // We have another way to get HID devices, so use the whitelist to get devices where libusb is preferred #define SDL_HINT_HIDAPI_LIBUSB_WHITELIST_DEFAULT true @@ -917,6 +879,7 @@ static bool IsInWhitelist(Uint16 vendor, Uint16 product) #endif // HAVE_PLATFORM_BACKEND || HAVE_DRIVER_BACKEND static bool use_libusb_whitelist = SDL_HINT_HIDAPI_LIBUSB_WHITELIST_DEFAULT; +static bool use_libusb_gamecube = true; // Shared HIDAPI Implementation @@ -1004,13 +967,14 @@ struct SDL_hid_device void *device; const struct hidapi_backend *backend; SDL_hid_device_info info; + SDL_PropertiesID props; }; #if defined(HAVE_PLATFORM_BACKEND) || defined(HAVE_DRIVER_BACKEND) || defined(HAVE_LIBUSB) static SDL_hid_device *CreateHIDDeviceWrapper(void *device, const struct hidapi_backend *backend) { - SDL_hid_device *wrapper = (SDL_hid_device *)SDL_malloc(sizeof(*wrapper)); + SDL_hid_device *wrapper = (SDL_hid_device *)SDL_calloc(1, sizeof(*wrapper)); SDL_SetObjectValid(wrapper, SDL_OBJECT_TYPE_HIDAPI_DEVICE, true); wrapper->device = device; wrapper->backend = backend; @@ -1082,9 +1046,7 @@ static void SDLCALL OnlyControllersChanged(void *userdata, const char *name, con static void SDLCALL IgnoredDevicesChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { - if (SDL_hidapi_ignored_devices) { - SDL_free(SDL_hidapi_ignored_devices); - } + SDL_free(SDL_hidapi_ignored_devices); if (hint && *hint) { SDL_hidapi_ignored_devices = SDL_strdup(hint); } else { @@ -1092,8 +1054,22 @@ static void SDLCALL IgnoredDevicesChanged(void *userdata, const char *name, cons } } -bool SDL_HIDAPI_ShouldIgnoreDevice(int bus, Uint16 vendor_id, Uint16 product_id, Uint16 usage_page, Uint16 usage) +bool SDL_HIDAPI_ShouldIgnoreDevice(int bus, Uint16 vendor_id, Uint16 product_id, Uint16 usage_page, Uint16 usage, bool libusb) { + if (libusb) { + if (use_libusb_whitelist && !RequiresLibUSB(vendor_id, product_id)) { + return true; + } + if (!use_libusb_gamecube && + vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) { + return true; + } + } else { + if (RequiresLibUSB(vendor_id, product_id)) { + return true; + } + } + // See if there are any devices we should skip in enumeration if (SDL_hidapi_only_controllers && usage_page) { if (vendor_id == USB_VENDOR_VALVE) { @@ -1109,7 +1085,13 @@ 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) { + } else if (vendor_id == USB_VENDOR_FLYDIGI_V1 && product_id == USB_PRODUCT_FLYDIGI_V1_GAMEPAD) { + if (usage_page == USB_USAGEPAGE_VENDOR_FLYDIGI) { + return false; + } + return true; + } else if (vendor_id == USB_VENDOR_FLYDIGI_V2 && + (product_id == USB_PRODUCT_FLYDIGI_V2_APEX || product_id == USB_PRODUCT_FLYDIGI_V2_VADER)) { if (usage_page == USB_USAGEPAGE_VENDOR_FLYDIGI) { return false; } @@ -1163,74 +1145,20 @@ int SDL_hid_init(void) use_libusb_whitelist = SDL_GetHintBoolean(SDL_HINT_HIDAPI_LIBUSB_WHITELIST, SDL_HINT_HIDAPI_LIBUSB_WHITELIST_DEFAULT); + use_libusb_gamecube = SDL_GetHintBoolean(SDL_HINT_HIDAPI_LIBUSB_GAMECUBE, true); #ifdef HAVE_LIBUSB if (!SDL_GetHintBoolean(SDL_HINT_HIDAPI_LIBUSB, true)) { SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "libusb disabled with SDL_HINT_HIDAPI_LIBUSB"); - libusb_ctx.libhandle = NULL; } else { ++attempts; -#ifdef SDL_LIBUSB_DYNAMIC - libusb_ctx.libhandle = SDL_LoadObject(SDL_LIBUSB_DYNAMIC); -#else - libusb_ctx.libhandle = (void *)1; -#endif - if (libusb_ctx.libhandle != NULL) { - bool loaded = true; -#ifdef SDL_LIBUSB_DYNAMIC -#define LOAD_LIBUSB_SYMBOL(type, func) \ - if ((libusb_ctx.func = (type)SDL_LoadFunction(libusb_ctx.libhandle, "libusb_" #func)) == NULL) { \ - loaded = false; \ - } -#else -#define LOAD_LIBUSB_SYMBOL(type, func) \ - libusb_ctx.func = libusb_##func; -#endif - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context **), init) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_context *), exit) - LOAD_LIBUSB_SYMBOL(ssize_t (LIBUSB_CALL *)(libusb_context *, libusb_device ***), get_device_list) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_device **, int), free_device_list) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, struct libusb_device_descriptor *), get_device_descriptor) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, struct libusb_config_descriptor **), get_active_config_descriptor) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, uint8_t, struct libusb_config_descriptor **), get_config_descriptor) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_config_descriptor *), free_config_descriptor) - LOAD_LIBUSB_SYMBOL(uint8_t (LIBUSB_CALL *)(libusb_device *), get_bus_number) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len), get_port_numbers) - LOAD_LIBUSB_SYMBOL(uint8_t (LIBUSB_CALL *)(libusb_device *), get_device_address) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, libusb_device_handle **), open) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_device_handle *), close) - LOAD_LIBUSB_SYMBOL(libusb_device * (LIBUSB_CALL *)(libusb_device_handle *dev_handle), get_device) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), claim_interface) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), release_interface) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), kernel_driver_active) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), detach_kernel_driver) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), attach_kernel_driver) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int, int), set_interface_alt_setting) - LOAD_LIBUSB_SYMBOL(struct libusb_transfer * (LIBUSB_CALL *)(int), alloc_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(struct libusb_transfer *), submit_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(struct libusb_transfer *), cancel_transfer) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_transfer *), free_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, uint8_t, uint8_t, uint16_t, uint16_t, unsigned char *, uint16_t, unsigned int), control_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), interrupt_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *), handle_events) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *, int *), handle_events_completed) - LOAD_LIBUSB_SYMBOL(const char * (LIBUSB_CALL *)(int), error_name) -#undef LOAD_LIBUSB_SYMBOL - - if (!loaded) { -#ifdef SDL_LIBUSB_DYNAMIC - SDL_UnloadObject(libusb_ctx.libhandle); -#endif - libusb_ctx.libhandle = NULL; - // SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, SDL_LIBUSB_DYNAMIC " found but could not load function"); - } else if (LIBUSB_hid_init() < 0) { -#ifdef SDL_LIBUSB_DYNAMIC - SDL_UnloadObject(libusb_ctx.libhandle); -#endif - libusb_ctx.libhandle = NULL; - } else { - ++success; - } + if (!SDL_InitLibUSB(&libusb_ctx)) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Couldn't load libusb"); + } else if (LIBUSB_hid_init() < 0) { + SDL_QuitLibUSB(); + libusb_ctx = NULL; + } else { + ++success; } } #endif // HAVE_LIBUSB @@ -1284,12 +1212,10 @@ int SDL_hid_exit(void) #endif // HAVE_PLATFORM_BACKEND #ifdef HAVE_LIBUSB - if (libusb_ctx.libhandle) { + if (libusb_ctx) { result |= LIBUSB_hid_exit(); -#ifdef SDL_LIBUSB_DYNAMIC - SDL_UnloadObject(libusb_ctx.libhandle); -#endif - libusb_ctx.libhandle = NULL; + SDL_QuitLibUSB(); + libusb_ctx = NULL; } #endif // HAVE_LIBUSB @@ -1384,34 +1310,6 @@ static void RemoveDeviceFromEnumeration(const char *driver_name, struct hid_devi } #endif // HAVE_LIBUSB || HAVE_PLATFORM_BACKEND -#ifdef HAVE_LIBUSB -static void RemoveNonWhitelistedDevicesFromEnumeration(struct hid_device_info **devs, void (*free_device_info)(struct hid_device_info *)) -{ - struct hid_device_info *last = NULL, *curr, *next; - - for (curr = *devs; curr; curr = next) { - next = curr->next; - - if (!IsInWhitelist(curr->vendor_id, curr->product_id)) { -#ifdef DEBUG_HIDAPI - SDL_Log("Device was not in libusb whitelist, skipping: %ls %ls 0x%.4hx/0x%.4hx/%d", - curr->manufacturer_string, curr->product_string, curr->vendor_id, curr->product_id, curr->interface_number); -#endif - if (last) { - last->next = next; - } else { - *devs = next; - } - - curr->next = NULL; - free_device_info(curr); - continue; - } - last = curr; - } -} -#endif // HAVE_LIBUSB - struct SDL_hid_device_info *SDL_hid_enumerate(unsigned short vendor_id, unsigned short product_id) { struct hid_device_info *driver_devs = NULL; @@ -1430,12 +1328,8 @@ struct SDL_hid_device_info *SDL_hid_enumerate(unsigned short vendor_id, unsigned #endif #ifdef HAVE_LIBUSB - if (libusb_ctx.libhandle) { + if (libusb_ctx) { usb_devs = LIBUSB_hid_enumerate(vendor_id, product_id); - - if (use_libusb_whitelist) { - RemoveNonWhitelistedDevicesFromEnumeration(&usb_devs, LIBUSB_hid_free_enumeration); - } } #endif // HAVE_LIBUSB @@ -1531,10 +1425,12 @@ SDL_hid_device *SDL_hid_open(unsigned short vendor_id, unsigned short product_id #endif // HAVE_DRIVER_BACKEND #ifdef HAVE_LIBUSB - if (libusb_ctx.libhandle != NULL) { + if (libusb_ctx) { pDevice = LIBUSB_hid_open(vendor_id, product_id, serial_number); if (pDevice != NULL) { - return CreateHIDDeviceWrapper(pDevice, &LIBUSB_Backend); + SDL_hid_device *dev = CreateHIDDeviceWrapper(pDevice, &LIBUSB_Backend); + SDL_SetPointerProperty(SDL_hid_get_properties(dev), SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER, ((LIBUSB_hid_device *)pDevice)->device_handle); + return dev; } } #endif // HAVE_LIBUSB @@ -1570,10 +1466,12 @@ SDL_hid_device *SDL_hid_open_path(const char *path) #endif // HAVE_DRIVER_BACKEND #ifdef HAVE_LIBUSB - if (libusb_ctx.libhandle != NULL) { + if (libusb_ctx) { pDevice = LIBUSB_hid_open_path(path); if (pDevice != NULL) { - return CreateHIDDeviceWrapper(pDevice, &LIBUSB_Backend); + SDL_hid_device *dev = CreateHIDDeviceWrapper(pDevice, &LIBUSB_Backend); + SDL_SetPointerProperty(SDL_hid_get_properties(dev), SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER, ((LIBUSB_hid_device *)pDevice)->device_handle); + return dev; } } #endif // HAVE_LIBUSB @@ -1583,6 +1481,16 @@ SDL_hid_device *SDL_hid_open_path(const char *path) return NULL; } +SDL_PropertiesID SDL_hid_get_properties(SDL_hid_device *device) +{ + CHECK_DEVICE_MAGIC(device, 0); + + if (!device->props) { + device->props = SDL_CreateProperties(); + } + return device->props; +} + int SDL_hid_write(SDL_hid_device *device, const unsigned char *data, size_t length) { CHECK_DEVICE_MAGIC(device, -1); @@ -1637,6 +1545,7 @@ int SDL_hid_close(SDL_hid_device *device) CHECK_DEVICE_MAGIC(device, -1); device->backend->hid_close(device->device); + SDL_DestroyProperties(device->props); DeleteHIDDeviceWrapper(device); return 0; } @@ -1698,60 +1607,3 @@ void SDL_hid_ble_scan(bool active) hid_ble_scan(active); #endif } - -#ifdef HAVE_ENABLE_GAMECUBE_ADAPTORS -// This is needed to enable input for Nyko and EVORETRO GameCube adaptors -void SDL_EnableGameCubeAdaptors(void) -{ -#ifdef HAVE_LIBUSB - libusb_context *context = NULL; - libusb_device **devs = NULL; - libusb_device_handle *handle = NULL; - struct libusb_device_descriptor desc; - ssize_t i, num_devs; - int kernel_detached = 0; - - if (libusb_ctx.libhandle == NULL) { - return; - } - - if (libusb_ctx.init(&context) == 0) { - num_devs = libusb_ctx.get_device_list(context, &devs); - for (i = 0; i < num_devs; ++i) { - if (libusb_ctx.get_device_descriptor(devs[i], &desc) != 0) { - continue; - } - - if (desc.idVendor != 0x057e || desc.idProduct != 0x0337) { - continue; - } - - if (libusb_ctx.open(devs[i], &handle) != 0) { - continue; - } - - if (libusb_ctx.kernel_driver_active(handle, 0)) { - if (libusb_ctx.detach_kernel_driver(handle, 0) == 0) { - kernel_detached = 1; - } - } - - if (libusb_ctx.claim_interface(handle, 0) == 0) { - libusb_ctx.control_transfer(handle, 0x21, 11, 0x0001, 0, NULL, 0, 1000); - libusb_ctx.release_interface(handle, 0); - } - - if (kernel_detached) { - libusb_ctx.attach_kernel_driver(handle, 0); - } - - libusb_ctx.close(handle); - } - - libusb_ctx.free_device_list(devs, 1); - - libusb_ctx.exit(context); - } -#endif // HAVE_LIBUSB -} -#endif // HAVE_ENABLE_GAMECUBE_ADAPTORS diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_android.h b/libs/SDL3/src/hidapi/SDL_hidapi_android.h index 2f3851f8..6d349cb3 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_android.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_android.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_c.h b/libs/SDL3/src/hidapi/SDL_hidapi_c.h index 6d94f77e..a4be1c74 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_c.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -22,14 +22,5 @@ /* Return true if the HIDAPI should ignore a device during enumeration */ -extern bool SDL_HIDAPI_ShouldIgnoreDevice(int bus_type, Uint16 vendor_id, Uint16 product_id, Uint16 usage_page, Uint16 usage); +extern bool SDL_HIDAPI_ShouldIgnoreDevice(int bus_type, Uint16 vendor_id, Uint16 product_id, Uint16 usage_page, Uint16 usage, bool libusb); -#ifdef SDL_JOYSTICK_HIDAPI -#ifdef HAVE_LIBUSB -#define HAVE_ENABLE_GAMECUBE_ADAPTORS -#endif - -#ifdef HAVE_ENABLE_GAMECUBE_ADAPTORS -extern void SDL_EnableGameCubeAdaptors(void); -#endif -#endif /* SDL_JOYSTICK_HIDAPI */ diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_ios.h b/libs/SDL3/src/hidapi/SDL_hidapi_ios.h index f58f10d5..6aa71c84 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_ios.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_ios.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_libusb.h b/libs/SDL3/src/hidapi/SDL_hidapi_libusb.h index ed8b4a35..a68ac49d 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_libusb.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_libusb.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -82,7 +82,6 @@ #define wcsncpy SDL_wcslcpy -#ifndef SDL_PLATFORM_FREEBSD /* this is awkwardly inlined, so we need to re-implement it here * so we can override the libusb_control_transfer call */ static int SDL_libusb_get_string_descriptor(libusb_device_handle *dev, @@ -93,7 +92,23 @@ static int SDL_libusb_get_string_descriptor(libusb_device_handle *dev, data, (uint16_t)length, 1000); /* Endpoint 0 IN */ } #define libusb_get_string_descriptor SDL_libusb_get_string_descriptor -#endif /* SDL_PLATFORM_FREEBSD */ + +/* this is inlined, except on FreeBSD, so we need to re-implement it here */ +static void SDL_libusb_fill_interrupt_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *buffer, int length, + libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_INTERRUPT; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->user_data = user_data; + transfer->callback = callback; +} +#define libusb_fill_interrupt_transfer SDL_libusb_fill_interrupt_transfer #define HIDAPI_THREAD_MODEL_INCLUDE "hidapi_thread_sdl.h" #ifndef LIBUSB_API_VERSION diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_linux.h b/libs/SDL3/src/hidapi/SDL_hidapi_linux.h index 29723d75..8418e2e9 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_linux.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_linux.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_mac.h b/libs/SDL3/src/hidapi/SDL_hidapi_mac.h index 820b67d6..7ec8e4e1 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_mac.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_mac.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_netbsd.h b/libs/SDL3/src/hidapi/SDL_hidapi_netbsd.h index 1b39e225..8bd5a8e9 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_netbsd.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_netbsd.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_steamxbox.h b/libs/SDL3/src/hidapi/SDL_hidapi_steamxbox.h index b6294a3b..41cecb59 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_steamxbox.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_steamxbox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_windows.h b/libs/SDL3/src/hidapi/SDL_hidapi_windows.h index 91f71c20..0ab9a6a2 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_windows.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_windows.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/hidapi/android/hid.cpp b/libs/SDL3/src/hidapi/android/hid.cpp index 2dbfb8ed..8b6abd97 100644 --- a/libs/SDL3/src/hidapi/android/hid.cpp +++ b/libs/SDL3/src/hidapi/android/hid.cpp @@ -91,6 +91,9 @@ extern "C" { typedef uint32_t uint32; typedef uint64_t uint64; +#define D0G_BLE2_PID 0x1106 +#define TRITON_BLE_PID 0x1303 + struct hid_device_ { @@ -431,15 +434,15 @@ static void ExceptionCheck( JNIEnv *env, const char *pszClassName, const char *p class CHIDDevice { public: - CHIDDevice( int nDeviceID, hid_device_info *pInfo ) + CHIDDevice( int nDeviceID, hid_device_info *pInfo, int nReportID ) { m_nId = nDeviceID; m_pInfo = pInfo; + m_nReportID = nReportID; // The Bluetooth Steam Controller needs special handling const int VALVE_USB_VID = 0x28DE; - const int D0G_BLE2_PID = 0x1106; - if ( pInfo->vendor_id == VALVE_USB_VID && pInfo->product_id == D0G_BLE2_PID ) + if ( pInfo->vendor_id == VALVE_USB_VID && ( pInfo->product_id == D0G_BLE2_PID || pInfo->product_id == TRITON_BLE_PID ) ) { m_bIsBLESteamController = true; } @@ -604,7 +607,7 @@ public: size_t nDataLen = buffer.size() > length ? length : buffer.size(); if ( m_bIsBLESteamController ) { - data[0] = 0x03; + data[0] = m_nReportID; SDL_memcpy( data + 1, buffer.data(), nDataLen ); ++nDataLen; } @@ -716,9 +719,27 @@ public: } } - size_t uBytesToCopy = m_reportResponse.size() > nDataLen ? nDataLen : m_reportResponse.size(); - SDL_memcpy( pData, m_reportResponse.data(), uBytesToCopy ); - m_reportResponse.clear(); + size_t uBytesToCopy = 0; + + if ( bFeature && m_reportResponse.size() > 0 ) + { + // Make sure we preserve the report value if it isn't already in the report. + bool bHasReportAlready = ( *pData == *m_reportResponse.data() ); + + // Make sure we only copy as much as will fit, deducting one byte for the report if we need to leave it intact. + size_t nSafeDataLen = nDataLen - ( bHasReportAlready ? 0 : 1 ); + uBytesToCopy = m_reportResponse.size() < nSafeDataLen ? m_reportResponse.size() : nSafeDataLen; + + SDL_memcpy( pData + ( bHasReportAlready ? 0 : 1 ), m_reportResponse.data(), uBytesToCopy ); + m_reportResponse.clear(); + + if ( !bHasReportAlready ) + { + // Add the report byte back on to return the real length. + uBytesToCopy++; + } + } + LOGV( "=== Got %zu bytes", uBytesToCopy ); return (int)uBytesToCopy; @@ -761,6 +782,7 @@ private: pthread_mutex_t m_refCountLock = PTHREAD_MUTEX_INITIALIZER; int m_nRefCount = 0; int m_nId = 0; + int m_nReportID = 0; hid_device_info *m_pInfo = nullptr; hid_device *m_pDevice = nullptr; bool m_bIsBLESteamController = false; @@ -810,22 +832,22 @@ extern "C" JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback)(JNIEnv *env, jobject thiz); extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface, int nInterfaceClass, int nInterfaceSubclass, int nInterfaceProtocol, bool bBluetooth ); +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, jint nDeviceID, jstring sIdentifier, jint nVendorId, jint nProductId, jstring sSerialNumber, jint nReleaseNumber, jstring sManufacturer, jstring sProduct, jint nInterface, jint nInterfaceClass, jint nInterfaceSubclass, jint nInterfaceProtocol, jboolean bBluetooth, jint nReportID ); extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, int nDeviceID); +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, jint nDeviceID); extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, int nDeviceID, bool bOpened); +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, jint nDeviceID, jboolean bOpened); extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, int nDeviceID); +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, jint nDeviceID); extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value); +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, jint nDeviceID, jbyteArray value); extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReportResponse)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value); +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReportResponse)(JNIEnv *env, jobject thiz, jint nDeviceID, jbyteArray value); extern "C" @@ -890,7 +912,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallbac } extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface, int nInterfaceClass, int nInterfaceSubclass, int nInterfaceProtocol, bool bBluetooth ) +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, jint nDeviceID, jstring sIdentifier, jint nVendorId, jint nProductId, jstring sSerialNumber, jint nReleaseNumber, jstring sManufacturer, jstring sProduct, jint nInterface, jint nInterfaceClass, jint nInterfaceSubclass, jint nInterfaceProtocol, jboolean bBluetooth, jint nReportID ) { LOGV( "HIDDeviceConnected() id=%d VID/PID = %.4x/%.4x, interface %d\n", nDeviceID, nVendorId, nProductId, nInterface ); @@ -916,7 +938,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNI pInfo->bus_type = HID_API_BUS_USB; } - hid_device_ref pDevice( new CHIDDevice( nDeviceID, pInfo ) ); + hid_device_ref pDevice( new CHIDDevice( nDeviceID, pInfo, nReportID ) ); hid_mutex_guard l( &g_DevicesMutex ); hid_device_ref pLast, pCurr; @@ -935,7 +957,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNI } extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, int nDeviceID) +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, jint nDeviceID) { LOGV( "HIDDeviceOpenPending() id=%d\n", nDeviceID ); hid_device_ref pDevice = FindDevice( nDeviceID ); @@ -946,7 +968,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(J } extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, int nDeviceID, bool bOpened) +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, jint nDeviceID, jboolean bOpened) { LOGV( "HIDDeviceOpenResult() id=%d, result=%s\n", nDeviceID, bOpened ? "true" : "false" ); hid_device_ref pDevice = FindDevice( nDeviceID ); @@ -957,7 +979,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JN } extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, int nDeviceID) +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, jint nDeviceID) { LOGV( "HIDDeviceDisconnected() id=%d\n", nDeviceID ); hid_device_ref pDevice; @@ -988,7 +1010,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)( } extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value) +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, jint nDeviceID, jbyteArray value) { jbyte *pBuf = env->GetByteArrayElements(value, NULL); jsize nBufSize = env->GetArrayLength(value); @@ -1004,7 +1026,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(J } extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReportResponse)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value) +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReportResponse)(JNIEnv *env, jobject thiz, jint nDeviceID, jbyteArray value) { jbyte *pBuf = env->GetByteArrayElements(value, NULL); jsize nBufSize = env->GetArrayLength(value); @@ -1092,7 +1114,7 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor const hid_device_info *info = pDevice->GetDeviceInfo(); /* See if there are any devices we should skip in enumeration */ - if (SDL_HIDAPI_ShouldIgnoreDevice(HID_API_BUS_UNKNOWN, info->vendor_id, info->product_id, 0, 0)) { + if (SDL_HIDAPI_ShouldIgnoreDevice(HID_API_BUS_UNKNOWN, info->vendor_id, info->product_id, 0, 0, false)) { continue; } @@ -1396,22 +1418,22 @@ extern "C" JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback)(JNIEnv *env, jobject thiz); extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface, int nInterfaceClass, int nInterfaceSubclass, int nInterfaceProtocol, bool bBluetooth ); +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, jint nDeviceID, jstring sIdentifier, jint nVendorId, jint nProductId, jstring sSerialNumber, jint nReleaseNumber, jstring sManufacturer, jstring sProduct, jint nInterface, jint nInterfaceClass, jint nInterfaceSubclass, jint nInterfaceProtocol, jboolean bBluetooth, jint nReportID ); extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, int nDeviceID); +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, jint nDeviceID); extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, int nDeviceID, bool bOpened); +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, jint nDeviceID, jboolean bOpened); extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, int nDeviceID); +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, jint nDeviceID); extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value); +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, jint nDeviceID, jbyteArray value); extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReportResponse)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value); +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReportResponse)(JNIEnv *env, jobject thiz, jint nDeviceID, jbyteArray value); extern "C" @@ -1427,39 +1449,39 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallbac } extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface, int nInterfaceClass, int nInterfaceSubclass, int nInterfaceProtocol, bool bBluetooth ) +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, jint nDeviceID, jstring sIdentifier, jint nVendorId, jint nProductId, jstring sSerialNumber, jint nReleaseNumber, jstring sManufacturer, jstring sProduct, jint nInterface, jint nInterfaceClass, jint nInterfaceSubclass, jint nInterfaceProtocol, jboolean bBluetooth, jint nReportID ) { LOGV("Stub HIDDeviceConnected() id=%d VID/PID = %.4x/%.4x, interface %d\n", nDeviceID, nVendorId, nProductId, nInterface); } extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, int nDeviceID) +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, jint nDeviceID) { LOGV("Stub HIDDeviceOpenPending() id=%d\n", nDeviceID); } extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, int nDeviceID, bool bOpened) +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, jint nDeviceID, jboolean bOpened) { LOGV("Stub HIDDeviceOpenResult() id=%d, result=%s\n", nDeviceID, bOpened ? "true" : "false"); } extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, int nDeviceID) +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, jint nDeviceID) { LOGV("Stub HIDDeviceDisconnected() id=%d\n", nDeviceID); } extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value) +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, jint nDeviceID, jbyteArray value) { - LOGV("Stub HIDDeviceInput() id=%d len=%u\n", nDeviceID, nBufSize); + LOGV("Stub HIDDeviceInput() id=%d len=%u\n", nDeviceID, env->GetArrayLength(value)); } extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReportResponse)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value) +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReportResponse)(JNIEnv *env, jobject thiz, jint nDeviceID, jbyteArray value) { - LOGV("Stub HIDDeviceReportResponse() id=%d len=%u\n", nDeviceID, nBufSize); + LOGV("Stub HIDDeviceReportResponse() id=%d len=%u\n", nDeviceID, env->GetArrayLength(value)); } #endif /* SDL_HIDAPI_DISABLED */ @@ -1468,7 +1490,7 @@ extern "C" JNINativeMethod HIDDeviceManager_tab[8] = { { "HIDDeviceRegisterCallback", "()V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallback) }, { "HIDDeviceReleaseCallback", "()V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback) }, - { "HIDDeviceConnected", "(ILjava/lang/String;IILjava/lang/String;ILjava/lang/String;Ljava/lang/String;IIIIZ)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected) }, + { "HIDDeviceConnected", "(ILjava/lang/String;IILjava/lang/String;ILjava/lang/String;Ljava/lang/String;IIIIZI)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected) }, { "HIDDeviceOpenPending", "(I)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending) }, { "HIDDeviceOpenResult", "(IZ)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult) }, { "HIDDeviceDisconnected", "(I)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected) }, diff --git a/libs/SDL3/src/hidapi/ios/hid.m b/libs/SDL3/src/hidapi/ios/hid.m index 29e07820..fe7b2e38 100644 --- a/libs/SDL3/src/hidapi/ios/hid.m +++ b/libs/SDL3/src/hidapi/ios/hid.m @@ -63,6 +63,7 @@ #define VALVE_USB_VID 0x28DE #define D0G_BLE2_PID 0x1106 +#define TRITON_BLE_PID 0x1303 typedef uint32_t uint32; typedef uint64_t uint64; @@ -76,7 +77,9 @@ typedef uint64_t uint64; #define VALVE_SERVICE @"100F6C32-1735-4313-B402-38567131E5F3" // (READ/NOTIFICATIONS) -#define VALVE_INPUT_CHAR @"100F6C33-1735-4313-B402-38567131E5F3" +#define VALVE_INPUT_CHAR_0x1106 @"100F6C33-1735-4313-B402-38567131E5F3" +#define VALVE_INPUT_CHAR_0x1303_0x45 @"100F6C7A-1735-4313-B402-38567131E5F3" +#define VALVE_INPUT_CHAR_0x1303_0x47 @"100F6C7C-1735-4313-B402-38567131E5F3" //  (READ/WRITE) #define VALVE_REPORT_CHAR @"100F6C34-1735-4313-B402-38567131E5F3" @@ -101,21 +104,7 @@ typedef struct typedef struct { uint8_t id; - union { - bluetoothSegment segment; - struct { - uint8_t segmentHeader; - uint8_t featureReportMessageID; - uint8_t length; - uint8_t settingIdentifier; - union { - uint16_t usPayload; - uint32_t uPayload; - uint64_t ulPayload; - uint8_t ucPayload[15]; - }; - }; - }; + bluetoothSegment segment; } hidFeatureReport; #pragma pack(pop) @@ -125,34 +114,62 @@ size_t GetBluetoothSegmentSize(bluetoothSegment *segment) return segment->length + 3; } -#define RingBuffer_cbElem 19 -#define RingBuffer_nElem 4096 +#define RingBuffer_nElem 256 typedef struct { int _first, _last; - uint8_t _data[ ( RingBuffer_nElem * RingBuffer_cbElem ) ]; + int _cbElem; + uint8_t *_data; pthread_mutex_t accessLock; } RingBuffer; -static void RingBuffer_init( RingBuffer *this ) +static RingBuffer *RingBuffer_alloc( int cbElem ) { + RingBuffer *this = (RingBuffer *)malloc( sizeof(*this) ); + if (!this) +{ + return NULL; + } + this->_first = -1; this->_last = 0; + this->_cbElem = cbElem; + this->_data = (uint8_t *)malloc(RingBuffer_nElem * cbElem); + if ( !this->_data ) + { + free( this ); + return NULL; + } pthread_mutex_init( &this->accessLock, 0 ); + return this; +} + +static void RingBuffer_free( RingBuffer *this ) +{ + if ( this ) + { + free( this->_data ); + free( this ); + } } static bool RingBuffer_write( RingBuffer *this, const uint8_t *src ) { + if ( !this ) + { + return false; + } + pthread_mutex_lock( &this->accessLock ); - memcpy( &this->_data[ this->_last ], src, RingBuffer_cbElem ); + memcpy( &this->_data[ this->_last ], src, this->_cbElem ); if ( this->_first == -1 ) { this->_first = this->_last; } - this->_last = ( this->_last + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem); + this->_last = ( this->_last + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem); if ( this->_last == this->_first ) { - this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem); + this->_first = ( this->_first + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem); pthread_mutex_unlock( &this->accessLock ); return false; } @@ -162,14 +179,19 @@ static bool RingBuffer_write( RingBuffer *this, const uint8_t *src ) static bool RingBuffer_read( RingBuffer *this, uint8_t *dst ) { + if ( !this ) + { + return false; + } + pthread_mutex_lock( &this->accessLock ); if ( this->_first == -1 ) { pthread_mutex_unlock( &this->accessLock ); return false; } - memcpy( dst, &this->_data[ this->_first ], RingBuffer_cbElem ); - this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem); + memcpy( dst, &this->_data[ this->_first ], this->_cbElem ); + this->_first = ( this->_first + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem); if ( this->_first == this->_last ) { this->_first = -1; @@ -191,12 +213,16 @@ typedef enum @interface HIDBLEDevice : NSObject { - RingBuffer _inputReports; - uint8_t _featureReport[20]; + RingBuffer *_inputReports; + NSData *_featureReport; + NSMutableDictionary *_outputReports; BLEDeviceWaitState _waitStateForReadFeatureReport; BLEDeviceWaitState _waitStateForWriteFeatureReport; } +@property (nonatomic, readwrite) uint16_t pid; +@property (nonatomic, readwrite) uint8_t report_id; +@property (nonatomic, readwrite) int report_length; @property (nonatomic, readwrite) bool connected; @property (nonatomic, readwrite) bool ready; @@ -205,6 +231,7 @@ typedef enum @property (nonatomic, strong) CBCharacteristic *bleCharacteristicReport; - (id)initWithPeripheral:(CBPeripheral *)peripheral; +- (void)onDisconnect; @end @@ -278,8 +305,7 @@ typedef enum HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral]; if ( steamController ) { - steamController.connected = NO; - steamController.ready = NO; + [steamController onDisconnect]; [self.centralManager cancelPeripheralConnection:peripheral]; } } @@ -403,7 +429,7 @@ typedef enum NSLog( @"CoreBluetooth BLE hardware is powered on and ready" ); // at startup, if we have no already attached peripherals, do a 20s scan for new unpaired devices, - // otherwise callers should occaisionally do additional scans. we don't want to continuously be + // otherwise callers should occasionally do additional scans. we don't want to continuously be // scanning because it drains battery, causes other nearby people to have a hard time pairing their // Steam Controllers, and may also trigger firmware weirdness when a device attempts to start // the pairing sequence multiple times concurrently @@ -474,8 +500,7 @@ typedef enum HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral]; if ( steamController ) { - steamController.connected = NO; - steamController.ready = NO; + [steamController onDisconnect]; [self.deviceMap removeObjectForKey:peripheral]; } } @@ -500,12 +525,16 @@ static void process_pending_events(void) { if ( self = [super init] ) { - RingBuffer_init( &_inputReports ); + self.pid = 0; + self.report_id = 0; + self.report_length = 0; + _inputReports = NULL; + _outputReports = [[NSMutableDictionary alloc] init]; + _connected = NO; + _ready = NO; self.bleSteamController = nil; self.bleCharacteristicInput = nil; self.bleCharacteristicReport = nil; - _connected = NO; - _ready = NO; } return self; } @@ -514,7 +543,11 @@ static void process_pending_events(void) { if ( self = [super init] ) { - RingBuffer_init( &_inputReports ); + self.pid = 0; + self.report_id = 0; + self.report_length = 0; + _inputReports = NULL; + _outputReports = [[NSMutableDictionary alloc] init]; _connected = NO; _ready = NO; self.bleSteamController = peripheral; @@ -528,6 +561,18 @@ static void process_pending_events(void) return self; } +- (void)onDisconnect +{ + self.connected = NO; + self.ready = NO; + + if ( _inputReports ) + { + RingBuffer_free( _inputReports ); + _inputReports = NULL; + } +} + - (void)setConnected:(bool)connected { _connected = connected; @@ -543,94 +588,124 @@ static void process_pending_events(void) - (size_t)read_input_report:(uint8_t *)dst { - if ( RingBuffer_read( &_inputReports, dst+1 ) ) + if ( RingBuffer_read( _inputReports, dst+1 ) ) { - *dst = 0x03; - return 20; + *dst = self.report_id; + return _inputReports->_cbElem + 1; } return 0; } - (int)send_report:(const uint8_t *)data length:(size_t)length { - [_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse]; - return (int)length; + if ( self.pid == D0G_BLE2_PID ) + { + [_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse]; + return (int)length; + } + + // We need to look up the correct characteristic for this output report + if ( length > 0 ) + { + CBCharacteristic *aChar = [_outputReports objectForKey:[NSNumber numberWithInt:data[0]]]; + if ( aChar != nil ) + { + [_bleSteamController writeValue:[NSData dataWithBytes:&data[1] length:(length - 1)] forCharacteristic:aChar type:CBCharacteristicWriteWithResponse]; + return (int)length; + } + } + return -1; } -- (int)send_feature_report:(hidFeatureReport *)report +- (int)send_feature_report:(hidFeatureReport *)report length:(size_t)length { #if FEATURE_REPORT_LOGGING uint8_t *reportBytes = (uint8_t *)report; - NSLog( @"HIDBLE:send_feature_report (%02zu/19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", GetBluetoothSegmentSize( report->segment ), + NSLog( @"HIDBLE:send_feature_report (%02zu/19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", length, reportBytes[1], reportBytes[2], reportBytes[3], reportBytes[4], reportBytes[5], reportBytes[6], reportBytes[7], reportBytes[8], reportBytes[9], reportBytes[10], reportBytes[11], reportBytes[12], reportBytes[13], reportBytes[14], reportBytes[15], reportBytes[16], reportBytes[17], reportBytes[18], reportBytes[19] ); #endif - int sendSize = (int)GetBluetoothSegmentSize( &report->segment ); - if ( sendSize > 20 ) - sendSize = 20; - #if 1 // fire-and-forget - we are going to not wait for the response here because all Steam Controller BLE send_feature_report's are ignored, // except errors. - [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse]; + [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:MIN(length, 64)] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse]; // pretend we received a result anybody cares about - return 19; + return (int)length; #else // this is technically the correct send_feature_report logic if you want to make sure it gets through and is // acknowledged or errors out _waitStateForWriteFeatureReport = BLEDeviceWaitState_Waiting; - [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize + [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:MIN(length, 64) ] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse]; - while ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Waiting ) + while ( _connected && _waitStateForWriteFeatureReport == BLEDeviceWaitState_Waiting ) { process_pending_events(); } - if ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Error ) + if ( !_connected || _waitStateForWriteFeatureReport == BLEDeviceWaitState_Error ) { _waitStateForWriteFeatureReport = BLEDeviceWaitState_None; return -1; } _waitStateForWriteFeatureReport = BLEDeviceWaitState_None; - return 19; + return (int)length; #endif } -- (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer +- (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer length:(size_t)length { _waitStateForReadFeatureReport = BLEDeviceWaitState_Waiting; [_bleSteamController readValueForCharacteristic:_bleCharacteristicReport]; - while ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting ) - process_pending_events(); + while ( _connected && _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting ) + { + process_pending_events(); + } - if ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Error ) + if ( !_connected || _waitStateForReadFeatureReport == BLEDeviceWaitState_Error ) { _waitStateForReadFeatureReport = BLEDeviceWaitState_None; return -1; } - memcpy( buffer, _featureReport, sizeof(_featureReport) ); + int amount = 0; + if ( _featureReport.length > 0 ) + { + uint8_t *data = (uint8_t *)_featureReport.bytes; + if ( *data == *buffer ) + { + amount = (int)MIN( length, _featureReport.length ); + memcpy( buffer, _featureReport.bytes, amount ); + } + else + { + // Leave the report in the buffer + amount = (int)MIN( length - 1, _featureReport.length ); + memcpy( &buffer[ 1 ], _featureReport.bytes, amount ); + ++amount; + } + } _waitStateForReadFeatureReport = BLEDeviceWaitState_None; #if FEATURE_REPORT_LOGGING - NSLog( @"HIDBLE:get_feature_report (19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", - buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], - buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], - buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18], - buffer[19] ); + NSLog( @"HIDBLE:get_feature_report (%lu/%zu) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", + _featureReport.length, length, + buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], + buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], + buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18], + buffer[19] ); #endif - return 19; + return amount; } #pragma mark CBPeripheralDelegate Implementation @@ -667,8 +742,25 @@ static void process_pending_events(void) { NSLog( @"Found Characteristic %@", aChar ); - if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR]] ) + if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1106]] ) { + self.pid = D0G_BLE2_PID; + self.report_id = 0x03; + self.report_length = 19; + self.bleCharacteristicInput = aChar; + } + else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1303_0x45]] ) + { + self.pid = TRITON_BLE_PID; + self.report_id = 0x45; + self.report_length = 45; + self.bleCharacteristicInput = aChar; + } + else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1303_0x47]] ) + { + self.pid = TRITON_BLE_PID; + self.report_id = 0x47; + self.report_length = 45; self.bleCharacteristicInput = aChar; } else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] ) @@ -676,6 +768,21 @@ static void process_pending_events(void) self.bleCharacteristicReport = aChar; [self.bleSteamController discoverDescriptorsForCharacteristic: aChar]; } + else + { + NSString *UUIDString = [aChar.UUID UUIDString]; + int report_id = 0; + if ( sscanf( UUIDString.UTF8String, "100F6C%x", &report_id ) == 1 && report_id > 0x35 ) + { + report_id -= 0x35; + //NSLog( @"Found characteristic for output report 0x%.2x", report_id ); + + if (report_id >= 0x80) { + // An output report + [_outputReports setObject:aChar forKey:[NSNumber numberWithInt:report_id]]; + } + } + } } } } @@ -690,17 +797,21 @@ static void process_pending_events(void) if ( self.ready == NO ) { self.ready = YES; + if ( _inputReports == NULL ) + { + _inputReports = RingBuffer_alloc( self.report_length ); + } HIDBLEManager.sharedInstance.nPendingPairs -= 1; } if ( [characteristic.UUID isEqual:_bleCharacteristicInput.UUID] ) { NSData *data = [characteristic value]; - if ( data.length != 19 ) + if ( _inputReports && data.length != _inputReports->_cbElem ) { - NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 19", (unsigned long)data.length ); + NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly %d", (unsigned long)data.length, _inputReports->_cbElem ); } - if ( !RingBuffer_write( &_inputReports, (const uint8_t *)data.bytes ) ) + if ( !RingBuffer_write( _inputReports, (const uint8_t *)data.bytes ) ) { uint64_t ticksNow = mach_approximate_time(); if ( ticksNow - s_ticksLastOverflowReport > (5ull * NSEC_PER_SEC / 10) ) @@ -712,8 +823,6 @@ static void process_pending_events(void) } else if ( [characteristic.UUID isEqual:_bleCharacteristicReport.UUID] ) { - memset( _featureReport, 0, sizeof(_featureReport) ); - if ( error != nil ) { NSLog( @"HIDBLE: get_feature_report error: %@", error ); @@ -721,12 +830,7 @@ static void process_pending_events(void) } else { - NSData *data = [characteristic value]; - if ( data.length != 20 ) - { - NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 20", (unsigned long)data.length ); - } - memcpy( _featureReport, data.bytes, MIN( data.length, sizeof(_featureReport) ) ); + _featureReport = [characteristic value]; _waitStateForReadFeatureReport = BLEDeviceWaitState_Complete; } } @@ -750,7 +854,7 @@ static void process_pending_events(void) - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { - NSLog( @"didUpdateNotifcationStateForCharacteristic %@ (%@)", characteristic, error ); + //NSLog( @"didUpdateNotificationStateForCharacteristic %@ (%@)", characteristic, error ); } @end @@ -850,7 +954,7 @@ static struct hid_device_info *create_device_info_for_hid_device(HIDBLEDevice *d memset( device_info, 0, sizeof(struct hid_device_info) ); device_info->path = strdup( device.bleSteamController.identifier.UUIDString.UTF8String ); device_info->vendor_id = VALVE_USB_VID; - device_info->product_id = D0G_BLE2_PID; + device_info->product_id = device.pid; device_info->product_string = wcsdup( L"Steam Controller" ); device_info->manufacturer_string = wcsdup( L"Valve Corporation" ); device_info->bus_type = HID_API_BUS_BLUETOOTH; @@ -861,14 +965,6 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, { @autoreleasepool { struct hid_device_info *root = NULL; - /* See if there are any devices we should skip in enumeration */ - if (SDL_HIDAPI_ShouldIgnoreDevice(HID_API_BUS_BLUETOOTH, VALVE_USB_VID, D0G_BLE2_PID, 0, 0)) { - return NULL; - } - - if ( ( vendor_id == 0 || vendor_id == VALVE_USB_VID ) && - ( product_id == 0 || product_id == D0G_BLE2_PID ) ) - { HIDBLEManager *bleManager = HIDBLEManager.sharedInstance; [bleManager updateConnectedSteamControllers:false]; NSEnumerator *devices = [bleManager.deviceMap objectEnumerator]; @@ -891,11 +987,22 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, } continue; } + + if ( ( vendor_id != 0 && vendor_id != VALVE_USB_VID ) || + ( product_id != 0 && product_id != device.pid ) ) + { + continue; + } + + /* See if there are any devices we should skip in enumeration */ + if (SDL_HIDAPI_ShouldIgnoreDevice(HID_API_BUS_BLUETOOTH, VALVE_USB_VID, device.pid, 0, 0, false)) { + continue; + } + struct hid_device_info *device_info = create_device_info_for_hid_device(device); device_info->next = root; root = device_info; } - } return root; }} @@ -975,7 +1082,7 @@ int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char if ( !device_handle.connected ) return -1; - return [device_handle send_feature_report:(hidFeatureReport *)(void *)data]; + return [device_handle send_feature_report:(hidFeatureReport *)(void *)data length:length]; } int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) @@ -985,7 +1092,7 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, if ( !device_handle.connected ) return -1; - size_t written = [device_handle get_feature_report:data[0] into:data]; + size_t written = [device_handle get_feature_report:data[0] into:data length:length]; return written == length-1 ? (int)length : (int)written; } @@ -1018,7 +1125,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t NSLog( @"hid_read_timeout with non-zero wait" ); } int result = (int)[device_handle read_input_report:data]; -#if FEATURE_REPORT_LOGGING +#if 0 //FEATURE_REPORT_LOGGING NSLog( @"HIDBLE:hid_read_timeout (%d) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", result, data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], diff --git a/libs/SDL3/src/hidapi/libusb/hid.c b/libs/SDL3/src/hidapi/libusb/hid.c index 94cc50dc..c5e68d7e 100644 --- a/libs/SDL3/src/hidapi/libusb/hid.c +++ b/libs/SDL3/src/hidapi/libusb/hid.c @@ -260,19 +260,19 @@ static int get_usage(uint8_t *report_descriptor, size_t size, } if (key_cmd == 0x4) { - *usage_page = get_bytes(report_descriptor, size, data_len, i); + *usage_page = (unsigned short)get_bytes(report_descriptor, size, data_len, i); usage_page_found = 1; //printf("Usage Page: %x\n", (uint32_t)*usage_page); } if (key_cmd == 0x8) { if (data_len == 4) { /* Usages 5.5 / Usage Page 6.2.2.7 */ - *usage_page = get_bytes(report_descriptor, size, 2, i + 2); + *usage_page = (unsigned short)get_bytes(report_descriptor, size, 2, i + 2); usage_page_found = 1; - *usage = get_bytes(report_descriptor, size, 2, i); + *usage = (unsigned short)get_bytes(report_descriptor, size, 2, i); usage_found = 1; } else { - *usage = get_bytes(report_descriptor, size, data_len, i); + *usage = (unsigned short)get_bytes(report_descriptor, size, data_len, i); usage_found = 1; } //printf("Usage: %x\n", (uint32_t)*usage); @@ -288,7 +288,7 @@ static int get_usage(uint8_t *report_descriptor, size_t size, return -1; /* failure */ } -#if defined(__FreeBSD__) && __FreeBSD__ < 10 +#if defined(__FreeBSD__) && __FreeBSD__ < 10 && !defined(libusb_get_string_descriptor) /* The libusb version included in FreeBSD < 10 doesn't have this function. In mainline libusb, it's inlined in libusb.h. This function will bear a striking resemblance to that one, because there's about one way to code it. @@ -640,7 +640,7 @@ static int hid_get_report_descriptor_libusb(libusb_device_handle *handle, int in /* Get the HID Report Descriptor. See USB HID Specification, section 7.1.1 */ - int res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8), interface_num, tmp, expected_report_descriptor_size, 5000); + int res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8), (uint16_t)interface_num, tmp, expected_report_descriptor_size, 5000); if (res >= 0) { if (res > (int)buf_size) res = (int)buf_size; @@ -671,6 +671,8 @@ static void fill_device_info_usage(struct hid_device_info *cur_dev, libusb_devic cur_dev->usage_page = page; cur_dev->usage = usage; + + free(hid_report_descriptor); } #ifdef INVASIVE_GET_USAGE @@ -851,6 +853,8 @@ static int is_xbox360(unsigned short vendor_id, const struct libusb_interface_de 0x24c6, /* PowerA */ 0x2c22, /* Qanba */ 0x2dc8, /* 8BitDo */ + 0x3537, /* GameSir */ + 0x37d7, /* Flydigi */ 0x9886, /* ASTRO Gaming */ }; @@ -927,6 +931,22 @@ static int should_enumerate_interface(unsigned short vendor_id, const struct lib return 0; } +static int libusb_blacklist(unsigned short vendor_id, unsigned short product_id) +{ + size_t i; + static const struct { unsigned short vid; unsigned short pid; } known_bad[] = { + { 0x1532, 0x0227 } /* Razer Huntsman Gaming keyboard - long delay asking for device details */ + }; + + for (i = 0; i < (sizeof(known_bad)/sizeof(known_bad[0])); i++) { + if ((vendor_id == known_bad[i].vid) && (product_id == known_bad[i].pid || known_bad[i].pid == 0x0000)) { + return 1; + } + } + + return 0; +} + struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) { libusb_device **devs; @@ -957,13 +977,14 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short dev_pid = desc.idProduct; if ((vendor_id != 0x0 && vendor_id != dev_vid) || - (product_id != 0x0 && product_id != dev_pid)) { + (product_id != 0x0 && product_id != dev_pid) || + libusb_blacklist(dev_vid, dev_pid)) { continue; } #ifdef HIDAPI_IGNORE_DEVICE /* See if there are any devices we should skip in enumeration */ - if (HIDAPI_IGNORE_DEVICE(HID_API_BUS_USB, dev_vid, dev_pid, 0, 0)) { + if (HIDAPI_IGNORE_DEVICE(HID_API_BUS_USB, dev_vid, dev_pid, 0, 0, true)) { continue; } #endif @@ -1178,7 +1199,7 @@ static void *read_thread(void *param) dev->transfer = libusb_alloc_transfer(0); libusb_fill_interrupt_transfer(dev->transfer, dev->device_handle, - dev->input_endpoint, + (unsigned char)dev->input_endpoint, buf, (int) length, read_callback, @@ -1248,6 +1269,7 @@ static void init_xbox360(libusb_device_handle *device_handle, unsigned short idV if ((idVendor == 0x05ac && idProduct == 0x055b) /* Gamesir-G3w */ || (idVendor == 0x20d6 && idProduct == 0x4010) /* PowerA Battle Dragon Advanced Wireless Controller */ || + (idVendor == 0x20d6 && idProduct == 0x4025) /* PowerA OPS v1 Wireless Controller */ || idVendor == 0x0f0d /* Hori Xbox controllers */) { unsigned char data[20]; @@ -1597,8 +1619,8 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t res = libusb_control_transfer(dev->device_handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, 0x09/*HID Set_Report*/, - (2/*HID output*/ << 8) | report_number, - dev->interface, + (uint16_t)((2/*HID output*/ << 8) | report_number), + (uint16_t)dev->interface, (unsigned char *)data, (uint16_t)length, 1000/*timeout millis*/); @@ -1614,7 +1636,7 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t /* Use the interrupt out endpoint */ int actual_length; res = libusb_interrupt_transfer(dev->device_handle, - dev->output_endpoint, + (unsigned char)dev->output_endpoint, (unsigned char*)data, (int) length, &actual_length, 1000); @@ -1763,8 +1785,8 @@ int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char res = libusb_control_transfer(dev->device_handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, 0x09/*HID set_report*/, - (3/*HID feature*/ << 8) | report_number, - dev->interface, + (uint16_t)((3/*HID feature*/ << 8) | report_number), + (uint16_t)dev->interface, (unsigned char *)data, (uint16_t)length, 1000/*timeout millis*/); @@ -1794,8 +1816,8 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, res = libusb_control_transfer(dev->device_handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN, 0x01/*HID get_report*/, - (3/*HID feature*/ << 8) | report_number, - dev->interface, + (uint16_t)((3/*HID feature*/ << 8) | report_number), + (uint16_t)dev->interface, (unsigned char *)data, (uint16_t)length, 1000/*timeout millis*/); @@ -1824,8 +1846,8 @@ int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned c res = libusb_control_transfer(dev->device_handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN, 0x01/*HID get_report*/, - (1/*HID Input*/ << 8) | report_number, - dev->interface, + (uint16_t)((1/*HID Input*/ << 8) | report_number), + (uint16_t)dev->interface, (unsigned char *)data, (uint16_t)length, 1000/*timeout millis*/); @@ -1917,7 +1939,7 @@ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index { wchar_t *str; - str = get_usb_string(dev->device_handle, string_index); + str = get_usb_string(dev->device_handle, (uint8_t)string_index); if (str) { wcsncpy(string, str, maxlen); string[maxlen-1] = L'\0'; @@ -2105,7 +2127,7 @@ uint16_t get_usb_code_for_current_locale(void) /* Chop off the encoding part, and make it lower case. */ ptr = search_string; while (*ptr) { - *ptr = tolower(*ptr); + *ptr = (char)tolower(*ptr); if (*ptr == '.') { *ptr = '\0'; break; @@ -2126,7 +2148,7 @@ uint16_t get_usb_code_for_current_locale(void) /* Chop off the variant. Chop it off at the '_'. */ ptr = search_string; while (*ptr) { - *ptr = tolower(*ptr); + *ptr = (char)tolower(*ptr); if (*ptr == '_') { *ptr = '\0'; break; diff --git a/libs/SDL3/src/hidapi/libusb/hidapi_thread_sdl.h b/libs/SDL3/src/hidapi/libusb/hidapi_thread_sdl.h index 1f5c7db8..85039997 100644 --- a/libs/SDL3/src/hidapi/libusb/hidapi_thread_sdl.h +++ b/libs/SDL3/src/hidapi/libusb/hidapi_thread_sdl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/hidapi/linux/hid.c b/libs/SDL3/src/hidapi/linux/hid.c index dbbb3277..c0cb6a7b 100644 --- a/libs/SDL3/src/hidapi/linux/hid.c +++ b/libs/SDL3/src/hidapi/linux/hid.c @@ -134,7 +134,6 @@ static wchar_t *utf8_to_wchar_t(const char *utf8) * Use register_error_str(NULL) to free the error message completely. */ static void register_error_str(wchar_t **error_str, const char *msg) { - free(*error_str); #ifdef HIDAPI_USING_SDL_RUNTIME /* Thread-safe error handling */ if (msg) { @@ -143,11 +142,12 @@ static void register_error_str(wchar_t **error_str, const char *msg) SDL_ClearError(); } #else + free(*error_str); *error_str = utf8_to_wchar_t(msg); #endif } -/* Semilar to register_error_str, but allows passing a format string with va_list args into this function. */ +/* Similar to register_error_str, but allows passing a format string with va_list args into this function. */ static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) { char msg[256]; @@ -906,7 +906,7 @@ static struct hid_device_info * create_device_info_for_device(struct udev_device cur_dev = root; while (cur_dev) { - if (HIDAPI_IGNORE_DEVICE(cur_dev->bus_type, cur_dev->vendor_id, cur_dev->product_id, cur_dev->usage_page, cur_dev->usage)) { + if (HIDAPI_IGNORE_DEVICE(cur_dev->bus_type, cur_dev->vendor_id, cur_dev->product_id, cur_dev->usage_page, cur_dev->usage, false)) { struct hid_device_info *tmp = cur_dev; cur_dev = tmp->next; diff --git a/libs/SDL3/src/hidapi/mac/hid.c b/libs/SDL3/src/hidapi/mac/hid.c index 8acb6da7..79d86bee 100644 --- a/libs/SDL3/src/hidapi/mac/hid.c +++ b/libs/SDL3/src/hidapi/mac/hid.c @@ -573,6 +573,7 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, { unsigned short dev_vid; unsigned short dev_pid; + unsigned short dev_version; int BUF_LEN = 256; wchar_t buf[BUF_LEN]; CFTypeRef transport_prop; @@ -593,15 +594,25 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, dev_vid = get_vendor_id(dev); dev_pid = get_product_id(dev); + dev_version = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey)); #ifdef HIDAPI_IGNORE_DEVICE /* See if there are any devices we should skip in enumeration */ - if (HIDAPI_IGNORE_DEVICE(get_bus_type(dev), dev_vid, dev_pid, usage_page, usage)) { + if (HIDAPI_IGNORE_DEVICE(get_bus_type(dev), dev_vid, dev_pid, usage_page, usage, false)) { free(cur_dev); return NULL; } #endif +#ifdef HIDAPI_USING_SDL_RUNTIME + if (SDL_IsJoystickSteamVirtualGamepad(dev_vid, dev_pid, dev_version)) { + if (IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDVirtualHIDevice)) != kCFBooleanTrue) { + /* This is a real Xbox 360 controller, adjust the version so it's not detected as a Steam virtual gamepad */ + dev_version = 1; + } + } +#endif + cur_dev->usage_page = usage_page; cur_dev->usage = usage; @@ -649,7 +660,7 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, cur_dev->product_id = dev_pid; /* Release Number */ - cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey)); + cur_dev->release_number = dev_version; /* Interface Number. * We can only retrieve the interface number for USB HID devices. @@ -1138,6 +1149,8 @@ return_error: static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char *data, size_t length) { + const char *pass_through_magic = "MAGIC0"; + size_t pass_through_magic_length = strlen(pass_through_magic); const unsigned char *data_to_send = data; CFIndex length_to_send = length; IOReturn res; @@ -1158,6 +1171,11 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char data_to_send = data+1; length_to_send = length-1; } + else if (length > 6 && memcmp(data, pass_through_magic, pass_through_magic_length) == 0) { + report_id = data[pass_through_magic_length]; + data_to_send = data+pass_through_magic_length; + length_to_send = length-pass_through_magic_length; + } /* Avoid crash if the device has been unplugged. */ if (dev->disconnected) { @@ -1424,7 +1442,7 @@ void HID_API_EXPORT hid_close(hid_device *dev) UPD: The crash part was true in/until some version of macOS. Starting with macOS 10.15, there is an opposite effect in some environments: - crash happenes if IOHIDDeviceClose() is not called. + crash happens if IOHIDDeviceClose() is not called. Not leaking a resource in all tested environments. */ if (is_macos_10_10_or_greater || !dev->disconnected) { diff --git a/libs/SDL3/src/hidapi/netbsd/hid.c b/libs/SDL3/src/hidapi/netbsd/hid.c index 82f34d4d..5f585850 100644 --- a/libs/SDL3/src/hidapi/netbsd/hid.c +++ b/libs/SDL3/src/hidapi/netbsd/hid.c @@ -96,7 +96,7 @@ static void register_error_str(wchar_t **error_str, const char *msg) *error_str = utf8_to_wchar_t(msg); } -/* Semilar to register_error_str, but allows passing a format string with va_list args into this function. */ +/* Similar to register_error_str, but allows passing a format string with va_list args into this function. */ static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) { char msg[256]; @@ -711,6 +711,7 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor for (size_t i = 0; i < len; i++) { char devpath[USB_MAX_DEVNAMELEN]; int bus; + struct hid_device_info *prev_end; strlcpy(devpath, "/dev/", sizeof(devpath)); strlcat(devpath, arr[i], sizeof(devpath)); @@ -719,7 +720,17 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor if (bus == -1) continue; + /* + * ehci/ohci/uhci/dwctwo etc. use 'addr 1' for root hubs + * but xhci uses 'addr 0' on NetBSD. + * Check addr 0 (that would be unused on other than xhci) + * and then check addr 1 if there is no device at addr 0. + */ + prev_end = hed.end; enumerate_usb_devices(bus, 0, hid_enumerate_callback, &hed); + if (hed.end == prev_end) + enumerate_usb_devices(bus, 1, + hid_enumerate_callback, &hed); close(bus); } diff --git a/libs/SDL3/src/hidapi/windows/hid.c b/libs/SDL3/src/hidapi/windows/hid.c index 3376ba96..75799782 100644 --- a/libs/SDL3/src/hidapi/windows/hid.c +++ b/libs/SDL3/src/hidapi/windows/hid.c @@ -949,6 +949,7 @@ static int hid_blacklist(unsigned short vendor_id, unsigned short product_id) { 0x0D8C, 0x0014 }, /* Sharkoon Skiller SGH2 headset - causes deadlock asking for device details */ { 0x1532, 0x0109 }, /* Razer Lycosa Gaming keyboard - causes deadlock asking for device details */ { 0x1532, 0x010B }, /* Razer Arctosa Gaming keyboard - causes deadlock asking for device details */ + { 0x1532, 0x0227 }, /* Razer Huntsman Gaming keyboard - long delay asking for device details */ { 0x1B1C, 0x1B3D }, /* Corsair Gaming keyboard - causes deadlock asking for device details */ { 0x1CCF, 0x0000 } /* All Konami Amusement Devices - causes deadlock asking for device details */ }; @@ -990,9 +991,7 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor break; } - if (device_interface_list != NULL) { - free(device_interface_list); - } + free(device_interface_list); // This should NOT be SDL_free() device_interface_list = (wchar_t*)calloc(len, sizeof(wchar_t)); if (device_interface_list == NULL) { @@ -1028,6 +1027,12 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor continue; } +#ifdef HIDAPI_IGNORE_DEVICE + hid_bus_type bus_type = HID_API_BUS_UNKNOWN; + PHIDP_PREPARSED_DATA pp_data = NULL; + HIDP_CAPS caps = { 0 }; +#endif + /* Get the Vendor ID and Product ID for this device. */ attrib.Size = sizeof(HIDD_ATTRIBUTES); if (!HidD_GetAttributes(device_handle, &attrib)) { @@ -1036,14 +1041,12 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor #ifdef HIDAPI_IGNORE_DEVICE /* See if there are any devices we should skip in enumeration */ - hid_bus_type bus_type = get_bus_type(device_interface); - PHIDP_PREPARSED_DATA pp_data = NULL; - HIDP_CAPS caps = { 0 }; + bus_type = get_bus_type(device_interface); if (HidD_GetPreparsedData(device_handle, &pp_data)) { HidP_GetCaps(pp_data, &caps); HidD_FreePreparsedData(pp_data); } - if (HIDAPI_IGNORE_DEVICE(bus_type, attrib.VendorID, attrib.ProductID, caps.UsagePage, caps.Usage)) { + if (HIDAPI_IGNORE_DEVICE(bus_type, attrib.VendorID, attrib.ProductID, caps.UsagePage, caps.Usage, false)) { goto cont_close; } #endif @@ -1398,6 +1401,11 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char } } if (!res) { + if (GetLastError() == ERROR_OPERATION_ABORTED) { + /* The read request was issued on another thread. + This is harmless, so just ignore it. */ + return 0; + } register_winapi_error(dev, L"hid_read_timeout/GetOverlappedResult"); } diff --git a/libs/SDL3/src/io/SDL_asyncio.c b/libs/SDL3/src/io/SDL_asyncio.c index 51adf6b0..31461377 100644 --- a/libs/SDL3/src/io/SDL_asyncio.c +++ b/libs/SDL3/src/io/SDL_asyncio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -23,34 +23,39 @@ #include "SDL_sysasyncio.h" #include "SDL_asyncio_c.h" -static const char *AsyncFileModeValid(const char *mode) +static const char *AsyncFileModeValid(const char *mode, bool *readonly) { - static const struct { const char *valid; const char *with_binary; } mode_map[] = { - { "r", "rb" }, - { "w", "wb" }, - { "r+","r+b" }, - { "w+", "w+b" } + static const struct { const char *valid; const char *with_binary; bool readonly; } mode_map[] = { + { "r", "rb", true }, + { "w", "wb", false }, + { "r+","r+b", false }, + { "w+", "w+b", false } }; for (int i = 0; i < SDL_arraysize(mode_map); i++) { if (SDL_strcmp(mode, mode_map[i].valid) == 0) { + *readonly = mode_map[i].readonly; return mode_map[i].with_binary; } } + + *readonly = false; return NULL; } SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode) { - if (!file) { + CHECK_PARAM(!file) { SDL_InvalidParamError("file"); return NULL; - } else if (!mode) { + } + CHECK_PARAM(!mode) { SDL_InvalidParamError("mode"); return NULL; } - const char *binary_mode = AsyncFileModeValid(mode); + bool readonly = false; + const char *binary_mode = AsyncFileModeValid(mode, &readonly); if (!binary_mode) { SDL_SetError("Unsupported file mode"); return NULL; @@ -61,6 +66,8 @@ SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode) return NULL; } + asyncio->readonly = readonly; + asyncio->lock = SDL_CreateMutex(); if (!asyncio->lock) { SDL_free(asyncio); @@ -78,7 +85,7 @@ SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode) Sint64 SDL_GetAsyncIOSize(SDL_AsyncIO *asyncio) { - if (!asyncio) { + CHECK_PARAM(!asyncio) { SDL_InvalidParamError("asyncio"); return -1; } @@ -87,11 +94,13 @@ Sint64 SDL_GetAsyncIOSize(SDL_AsyncIO *asyncio) static bool RequestAsyncIO(bool reading, SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata) { - if (!asyncio) { + CHECK_PARAM(!asyncio) { return SDL_InvalidParamError("asyncio"); - } else if (!ptr) { + } + CHECK_PARAM(!ptr) { return SDL_InvalidParamError("ptr"); - } else if (!queue) { + } + CHECK_PARAM(!queue) { return SDL_InvalidParamError("queue"); } @@ -143,9 +152,10 @@ bool SDL_WriteAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 siz bool SDL_CloseAsyncIO(SDL_AsyncIO *asyncio, bool flush, SDL_AsyncIOQueue *queue, void *userdata) { - if (!asyncio) { + CHECK_PARAM(!asyncio) { return SDL_InvalidParamError("asyncio"); - } else if (!queue) { + } + CHECK_PARAM(!queue) { return SDL_InvalidParamError("queue"); } @@ -215,7 +225,8 @@ static bool GetAsyncIOTaskOutcome(SDL_AsyncIOTask *task, SDL_AsyncIOOutcome *out outcome->userdata = task->app_userdata; // Take the completed task out of the SDL_AsyncIO that created it. - SDL_LockMutex(asyncio->lock); + SDL_Mutex *lock = asyncio->lock; + SDL_LockMutex(lock); LINKED_LIST_UNLINK(task, asyncio); // see if it's time to queue a pending close request (close requested and no other pending tasks) SDL_AsyncIOTask *closing = asyncio->closing; @@ -228,7 +239,7 @@ static bool GetAsyncIOTaskOutcome(SDL_AsyncIOTask *task, SDL_AsyncIOOutcome *out SDL_AddAtomicInt(&closing->queue->tasks_inflight, -1); } } - SDL_UnlockMutex(task->asyncio->lock); + SDL_UnlockMutex(lock); // was this the result of a closing task? Finally destroy the asyncio. bool retval = true; @@ -298,9 +309,10 @@ void SDL_QuitAsyncIO(void) bool SDL_LoadFileAsync(const char *file, SDL_AsyncIOQueue *queue, void *userdata) { - if (!file) { + CHECK_PARAM(!file) { return SDL_InvalidParamError("file"); - } else if (!queue) { + } + CHECK_PARAM(!queue) { return SDL_InvalidParamError("queue"); } diff --git a/libs/SDL3/src/io/SDL_asyncio_c.h b/libs/SDL3/src/io/SDL_asyncio_c.h index 861ba1a0..0a8ca782 100644 --- a/libs/SDL3/src/io/SDL_asyncio_c.h +++ b/libs/SDL3/src/io/SDL_asyncio_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/io/SDL_iostream.c b/libs/SDL3/src/io/SDL_iostream.c index 989f3b9c..31c74ab5 100644 --- a/libs/SDL3/src/io/SDL_iostream.c +++ b/libs/SDL3/src/io/SDL_iostream.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -94,10 +94,12 @@ static HANDLE SDLCALL windows_file_open(const char *filename, const char *mode) // "r" = reading, file must exist // "w" = writing, truncate existing, file may not exist + // "wx"= writing, file must not exist // "r+"= reading or writing, file must exist // "a" = writing, append file may not exist // "a+"= append + read, file may not exist // "w+" = read, write, truncate. file may not exist + // "w+x"= read, write, file must not exist must_exist = (SDL_strchr(mode, 'r') != NULL) ? OPEN_EXISTING : 0; truncate = (SDL_strchr(mode, 'w') != NULL) ? CREATE_ALWAYS : 0; @@ -105,6 +107,10 @@ static HANDLE SDLCALL windows_file_open(const char *filename, const char *mode) a_mode = (SDL_strchr(mode, 'a') != NULL) ? OPEN_ALWAYS : 0; w_right = (a_mode || SDL_strchr(mode, '+') || truncate) ? GENERIC_WRITE : 0; + if (truncate && (SDL_strchr(mode, 'x') != NULL)) { + truncate = CREATE_NEW; + } + if (!r_right && !w_right) { return INVALID_HANDLE_VALUE; // inconsistent mode } @@ -219,16 +225,19 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size, DWORD error = GetLastError(); switch (error) { case ERROR_BROKEN_PIPE: - case ERROR_HANDLE_EOF: + *status = SDL_IO_STATUS_EOF; break; case ERROR_NO_DATA: *status = SDL_IO_STATUS_NOT_READY; break; default: + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error reading from datastream"); break; } - return 0; + return 0; // !!! FIXME: this should return the bytes read from any readahead we finished out before this (the `iodata->left > 0` code above). In that case, fail on the next read. + } else if (bytes == 0) { + *status = SDL_IO_STATUS_EOF; } read_ahead = SDL_min(total_need, bytes); SDL_memcpy(ptr, iodata->data, read_ahead); @@ -240,16 +249,19 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size, DWORD error = GetLastError(); switch (error) { case ERROR_BROKEN_PIPE: - case ERROR_HANDLE_EOF: + *status = SDL_IO_STATUS_EOF; break; case ERROR_NO_DATA: *status = SDL_IO_STATUS_NOT_READY; break; default: + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error reading from datastream"); break; } - return 0; + return 0; // !!! FIXME: this should return the bytes read from any readahead we finished out before this (the `iodata->left > 0` code above). In that case, fail on the next read. + } else if (bytes == 0) { + *status = SDL_IO_STATUS_EOF; } total_read += bytes; } @@ -263,6 +275,7 @@ static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t if (iodata->left) { if (!SetFilePointer(iodata->h, -(LONG)iodata->left, NULL, FILE_CURRENT)) { + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error seeking in datastream"); return 0; } @@ -274,16 +287,17 @@ static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t LARGE_INTEGER windowsoffset; windowsoffset.QuadPart = 0; if (!SetFilePointerEx(iodata->h, windowsoffset, &windowsoffset, FILE_END)) { + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error seeking in datastream"); return 0; } } if (!WriteFile(iodata->h, ptr, (DWORD)size, &bytes, NULL)) { + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error writing to datastream"); return 0; - } - if (bytes == 0 && size > 0) { + } else if (bytes == 0 && size > 0) { *status = SDL_IO_STATUS_NOT_READY; } return bytes; @@ -365,7 +379,6 @@ typedef struct IOStreamFDData { int fd; bool autoclose; - bool regular_file; } IOStreamFDData; static int SDL_fdatasync(int fd) @@ -415,15 +428,36 @@ static size_t SDLCALL fd_read(void *userdata, void *ptr, size_t size, SDL_IOStat ssize_t bytes; do { bytes = read(iodata->fd, ptr, size); - } while (bytes < 0 && errno == EINTR); + } while ((bytes < 0) && (errno == EINTR)); - if (bytes < 0) { + ssize_t result = bytes; + if ((bytes > 0) && (bytes < size)) { // was it a short read, EOF, or error? + // try to read the difference, so we can rule out a short read. + do { + result = read(iodata->fd, ((Uint8 *) ptr) + bytes, size - bytes); + } while ((result < 0) && (errno == EINTR)); + + if (result > 0) { + bytes += result; + result = bytes; + SDL_assert(bytes <= size); + } + } + + if (result < 0) { if (errno == EAGAIN) { *status = SDL_IO_STATUS_NOT_READY; } else { + *status = SDL_IO_STATUS_ERROR; SDL_SetError("Error reading from datastream: %s", strerror(errno)); } - bytes = 0; + if (bytes < 0) { + bytes = 0; + } + } else if (result == 0) { + *status = SDL_IO_STATUS_EOF; + } else if (result < size) { + *status = SDL_IO_STATUS_NOT_READY; } return (size_t)bytes; } @@ -434,16 +468,36 @@ static size_t SDLCALL fd_write(void *userdata, const void *ptr, size_t size, SDL ssize_t bytes; do { bytes = write(iodata->fd, ptr, size); - } while (bytes < 0 && errno == EINTR); + } while ((bytes < 0) && (errno == EINTR)); - if (bytes < 0) { + ssize_t result = bytes; + if ((bytes > 0) && (bytes < size)) { // was it a short write, or error? + // try to write the difference, so we can rule out a short read. + do { + result = write(iodata->fd, ((Uint8 *) ptr) + bytes, size - bytes); + } while ((result < 0) && (errno == EINTR)); + + if (result > 0) { + bytes += result; + result = bytes; + SDL_assert(bytes <= size); + } + } + + if (result < 0) { if (errno == EAGAIN) { *status = SDL_IO_STATUS_NOT_READY; } else { + *status = SDL_IO_STATUS_ERROR; SDL_SetError("Error writing to datastream: %s", strerror(errno)); } - bytes = 0; + if (bytes < 0) { + bytes = 0; + } + } else if (result < size) { + *status = SDL_IO_STATUS_NOT_READY; } + return (size_t)bytes; } @@ -455,7 +509,8 @@ static bool SDLCALL fd_flush(void *userdata, SDL_IOStatus *status) result = SDL_fdatasync(iodata->fd); } while (result < 0 && errno == EINTR); - if (result < 0) { + // We get EINVAL when flushing a pipe, just make that a no-op + if (result < 0 && errno != EINVAL) { return SDL_SetError("Error flushing datastream: %s", strerror(errno)); } return true; @@ -496,9 +551,6 @@ SDL_IOStream *SDL_IOFromFD(int fd, bool autoclose) iodata->fd = fd; iodata->autoclose = autoclose; - struct stat st; - iodata->regular_file = ((fstat(fd, &st) == 0) && S_ISREG(st.st_mode)); - SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata); if (!iostr) { iface.close(iodata); @@ -521,7 +573,6 @@ typedef struct IOStreamStdioData { FILE *fp; bool autoclose; - bool regular_file; } IOStreamStdioData; #ifdef HAVE_FOPEN64 @@ -606,12 +657,18 @@ static size_t SDLCALL stdio_read(void *userdata, void *ptr, size_t size, SDL_IOS { IOStreamStdioData *iodata = (IOStreamStdioData *) userdata; const size_t bytes = fread(ptr, 1, size, iodata->fp); - if (bytes == 0 && ferror(iodata->fp)) { - if (errno == EAGAIN) { - *status = SDL_IO_STATUS_NOT_READY; - clearerr(iodata->fp); + if (bytes < size) { + if (ferror(iodata->fp)) { + if (errno == EAGAIN) { + *status = SDL_IO_STATUS_NOT_READY; + clearerr(iodata->fp); + } else { + *status = SDL_IO_STATUS_ERROR; + SDL_SetError("Error reading from datastream: %s", strerror(errno)); + } } else { - SDL_SetError("Error reading from datastream: %s", strerror(errno)); + SDL_assert(feof(iodata->fp)); + *status = SDL_IO_STATUS_EOF; } } return bytes; @@ -626,6 +683,7 @@ static size_t SDLCALL stdio_write(void *userdata, const void *ptr, size_t size, *status = SDL_IO_STATUS_NOT_READY; clearerr(iodata->fp); } else { + *status = SDL_IO_STATUS_ERROR; SDL_SetError("Error writing to datastream: %s", strerror(errno)); } } @@ -691,9 +749,6 @@ SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose) iodata->fp = fp; iodata->autoclose = autoclose; - struct stat st; - iodata->regular_file = ((fstat(fileno(fp), &st) == 0) && S_ISREG(st.st_mode)); - SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata); if (!iostr) { iface.close(iodata); @@ -716,6 +771,7 @@ typedef struct IOStreamMemData Uint8 *base; Uint8 *here; Uint8 *stop; + SDL_PropertiesID props; } IOStreamMemData; static Sint64 SDLCALL mem_size(void *userdata) @@ -768,46 +824,61 @@ static size_t mem_io(void *userdata, void *dst, const void *src, size_t size) static size_t SDLCALL mem_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) { IOStreamMemData *iodata = (IOStreamMemData *) userdata; - return mem_io(userdata, ptr, iodata->here, size); + const size_t retval = mem_io(userdata, ptr, iodata->here, size); + if ((retval < size) && (iodata->stop == iodata->here)) { + *status = SDL_IO_STATUS_EOF; + } + return retval; } static size_t SDLCALL mem_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status) { IOStreamMemData *iodata = (IOStreamMemData *) userdata; - return mem_io(userdata, iodata->here, ptr, size); + const size_t retval = mem_io(userdata, iodata->here, ptr, size); + if ((retval < size) && (iodata->stop == iodata->here)) { + SDL_SetError("Memory buffer is full"); + *status = SDL_IO_STATUS_ERROR; + } + return retval; } static bool SDLCALL mem_close(void *userdata) { + IOStreamMemData *iodata = (IOStreamMemData *) userdata; + SDL_free_func free_func = SDL_GetPointerProperty(iodata->props, SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC_POINTER, NULL); + if (free_func) { + free_func(iodata->base); + } SDL_free(userdata); return true; } // Functions to create SDL_IOStream structures from various data sources -#if defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINDOWS) -static bool IsRegularFileOrPipe(FILE *f) -{ -#ifndef SDL_PLATFORM_EMSCRIPTEN - struct stat st; - if (fstat(fileno(f), &st) < 0 || !(S_ISREG(st.st_mode) || S_ISFIFO(st.st_mode))) { - return false; - } -#endif // !SDL_PLATFORM_EMSCRIPTEN +// private platforms might define SKIP_STDIO_DIR_TEST in their build configs, too. +#if defined(SDL_PLATFORM_WINDOWS) || defined(SDL_PLATFORM_EMSCRIPTEN) +#define SKIP_STDIO_DIR_TEST 1 +#endif - return true; +#if defined(HAVE_STDIO_H) && !defined(SKIP_STDIO_DIR_TEST) +static bool IsStdioFileADirectory(FILE *f) +{ + struct stat st; + return ((fstat(fileno(f), &st) == 0) && (S_ISDIR(st.st_mode))); } +#else +#define IsStdioFileADirectory(f) false #endif SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) { SDL_IOStream *iostr = NULL; - if (!file || !*file) { + CHECK_PARAM(!file || !*file) { SDL_InvalidParamError("file"); return NULL; } - if (!mode || !*mode) { + CHECK_PARAM(!mode || !*mode) { SDL_InvalidParamError("mode"); return NULL; } @@ -818,9 +889,9 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) if (*file == '/') { FILE *fp = fopen(file, mode); if (fp) { - if (!IsRegularFileOrPipe(fp)) { + if (IsStdioFileADirectory(fp)) { fclose(fp); - SDL_SetError("%s is not a regular file or pipe", file); + SDL_SetError("%s is a directory", file); return NULL; } return SDL_IOFromFP(fp, true); @@ -849,9 +920,9 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) FILE *fp = fopen(path, mode); SDL_free(path); if (fp) { - if (!IsRegularFileOrPipe(fp)) { + if (IsStdioFileADirectory(fp)) { fclose(fp); - SDL_SetError("%s is not a regular file or pipe", path); + SDL_SetError("%s is a directory", path); return NULL; } return SDL_IOFromFP(fp, true); @@ -885,6 +956,43 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) } } +#elif defined(SDL_PLATFORM_IOS) + + // Try to open the file on the filesystem first + FILE *fp = NULL; + if (*file == '/') { + fp = fopen(file, mode); + } else { + // We can't write to the current directory, so use a writable path + char *base = SDL_GetPrefPath("", ""); + if (!base) { + return NULL; + } + + char *path = NULL; + SDL_asprintf(&path, "%s%s", base, file); + SDL_free(base); + if (!path) { + return NULL; + } + + fp = fopen(path, mode); + SDL_free(path); + + if (!fp) { + fp = fopen(file, mode); + } + } + + if (!fp) { + SDL_SetError("Couldn't open %s: %s", file, strerror(errno)); + } else if (IsStdioFileADirectory(fp)) { + fclose(fp); + SDL_SetError("%s is a directory", file); + } else { + iostr = SDL_IOFromFP(fp, true); + } + #elif defined(SDL_PLATFORM_WINDOWS) HANDLE handle = windows_file_open(file, mode); if (handle != INVALID_HANDLE_VALUE) { @@ -901,10 +1009,9 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) if (!fp) { SDL_SetError("Couldn't open %s: %s", file, strerror(errno)); - } else if (!IsRegularFileOrPipe(fp)) { + } else if (IsStdioFileADirectory(fp)) { fclose(fp); - fp = NULL; - SDL_SetError("%s is not a regular file or pipe", file); + SDL_SetError("%s is a directory", file); } else { iostr = SDL_IOFromFP(fp, true); } @@ -912,19 +1019,16 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) #else SDL_SetError("SDL not compiled with stdio support"); -#endif // !HAVE_STDIO_H +#endif // SDL_PLATFORM_ANDROID return iostr; } SDL_IOStream *SDL_IOFromMem(void *mem, size_t size) { - if (!mem) { + CHECK_PARAM(size && !mem) { SDL_InvalidParamError("mem"); return NULL; - } else if (!size) { - SDL_InvalidParamError("size"); - return NULL; } IOStreamMemData *iodata = (IOStreamMemData *) SDL_calloc(1, sizeof (*iodata)); @@ -950,6 +1054,7 @@ SDL_IOStream *SDL_IOFromMem(void *mem, size_t size) } else { const SDL_PropertiesID props = SDL_GetIOProperties(iostr); if (props) { + iodata->props = props; SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_MEMORY_POINTER, mem); SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER, size); } @@ -959,12 +1064,9 @@ SDL_IOStream *SDL_IOFromMem(void *mem, size_t size) SDL_IOStream *SDL_IOFromConstMem(const void *mem, size_t size) { - if (!mem) { + CHECK_PARAM(size && !mem) { SDL_InvalidParamError("mem"); return NULL; - } else if (!size) { - SDL_InvalidParamError("size"); - return NULL; } IOStreamMemData *iodata = (IOStreamMemData *) SDL_calloc(1, sizeof (*iodata)); @@ -990,6 +1092,7 @@ SDL_IOStream *SDL_IOFromConstMem(const void *mem, size_t size) } else { const SDL_PropertiesID props = SDL_GetIOProperties(iostr); if (props) { + iodata->props = props; SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_MEMORY_POINTER, (void *)mem); SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER, size); } @@ -1019,7 +1122,11 @@ static Sint64 SDLCALL dynamic_mem_seek(void *userdata, Sint64 offset, SDL_IOWhen static size_t SDLCALL dynamic_mem_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) { IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; - return mem_io(&iodata->data, ptr, iodata->data.here, size); + const size_t retval = mem_io(&iodata->data, ptr, iodata->data.here, size); + if ((retval < size) && (iodata->data.stop == iodata->data.here)) { + *status = SDL_IO_STATUS_EOF; + } + return retval; } static bool dynamic_mem_realloc(IOStreamDynamicMemData *iodata, size_t size) @@ -1052,21 +1159,22 @@ static size_t SDLCALL dynamic_mem_write(void *userdata, const void *ptr, size_t if (size > (size_t)(iodata->data.stop - iodata->data.here)) { if (size > (size_t)(iodata->end - iodata->data.here)) { if (!dynamic_mem_realloc(iodata, size)) { + *status = SDL_IO_STATUS_ERROR; return 0; } } iodata->data.stop = iodata->data.here + size; } - return mem_io(&iodata->data, iodata->data.here, ptr, size); + const size_t retval = mem_io(&iodata->data, iodata->data.here, ptr, size); + SDL_assert(retval == size); // we should have allocated enough to cover this! + return retval; } static bool SDLCALL dynamic_mem_close(void *userdata) { const IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; void *mem = SDL_GetPointerProperty(SDL_GetIOProperties(iodata->stream), SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, NULL); - if (mem) { - SDL_free(mem); - } + SDL_free(mem); SDL_free(userdata); return true; } @@ -1097,7 +1205,7 @@ SDL_IOStream *SDL_IOFromDynamicMem(void) SDL_IOStatus SDL_GetIOStatus(SDL_IOStream *context) { - if (!context) { + CHECK_PARAM(!context) { SDL_InvalidParamError("context"); return SDL_IO_STATUS_ERROR; } @@ -1106,11 +1214,11 @@ SDL_IOStatus SDL_GetIOStatus(SDL_IOStream *context) SDL_IOStream *SDL_OpenIO(const SDL_IOStreamInterface *iface, void *userdata) { - if (!iface) { + CHECK_PARAM(!iface) { SDL_InvalidParamError("iface"); return NULL; } - if (iface->version < sizeof(*iface)) { + CHECK_PARAM(iface->version < sizeof(*iface)) { // Update this to handle older versions of this interface SDL_SetError("Invalid interface, should be initialized with SDL_INIT_INTERFACE()"); return NULL; @@ -1146,7 +1254,7 @@ void *SDL_LoadFile_IO(SDL_IOStream *src, size_t *datasize, bool closeio) char *data = NULL, *newdata; bool loading_chunks = false; - if (!src) { + CHECK_PARAM(!src) { SDL_InvalidParamError("src"); goto done; } @@ -1227,12 +1335,12 @@ bool SDL_SaveFile_IO(SDL_IOStream *src, const void *data, size_t datasize, bool size_t size_total = 0; bool success = true; - if (!src) { + CHECK_PARAM(!src) { SDL_InvalidParamError("src"); goto done; } - if (!data && datasize > 0) { + CHECK_PARAM(!data && datasize > 0) { SDL_InvalidParamError("data"); goto done; } @@ -1275,7 +1383,7 @@ bool SDL_SaveFile(const char *file, const void *data, size_t datasize) SDL_PropertiesID SDL_GetIOProperties(SDL_IOStream *context) { - if (!context) { + CHECK_PARAM(!context) { SDL_InvalidParamError("context"); return 0; } @@ -1288,9 +1396,11 @@ SDL_PropertiesID SDL_GetIOProperties(SDL_IOStream *context) Sint64 SDL_GetIOSize(SDL_IOStream *context) { - if (!context) { - return SDL_InvalidParamError("context"); + CHECK_PARAM(!context) { + SDL_InvalidParamError("context"); + return -1; } + if (!context->iface.size) { Sint64 pos, size; @@ -1308,10 +1418,12 @@ Sint64 SDL_GetIOSize(SDL_IOStream *context) Sint64 SDL_SeekIO(SDL_IOStream *context, Sint64 offset, SDL_IOWhence whence) { - if (!context) { + CHECK_PARAM(!context) { SDL_InvalidParamError("context"); return -1; - } else if (!context->iface.seek) { + } + + if (!context->iface.seek) { SDL_Unsupported(); return -1; } @@ -1325,60 +1437,48 @@ Sint64 SDL_TellIO(SDL_IOStream *context) size_t SDL_ReadIO(SDL_IOStream *context, void *ptr, size_t size) { - size_t bytes; - - if (!context) { + CHECK_PARAM(!context) { SDL_InvalidParamError("context"); return 0; - } else if (!context->iface.read) { + } + + if (!context->iface.read) { context->status = SDL_IO_STATUS_WRITEONLY; SDL_Unsupported(); return 0; } + if (size == 0) { + return 0; // context->status doesn't change for this. + } + context->status = SDL_IO_STATUS_READY; SDL_ClearError(); - if (size == 0) { - return 0; - } - - bytes = context->iface.read(context->userdata, ptr, size, &context->status); - if (bytes == 0 && context->status == SDL_IO_STATUS_READY) { - if (*SDL_GetError()) { - context->status = SDL_IO_STATUS_ERROR; - } else { - context->status = SDL_IO_STATUS_EOF; - } - } - return bytes; + return context->iface.read(context->userdata, ptr, size, &context->status); } size_t SDL_WriteIO(SDL_IOStream *context, const void *ptr, size_t size) { - size_t bytes; - - if (!context) { + CHECK_PARAM(!context) { SDL_InvalidParamError("context"); return 0; - } else if (!context->iface.write) { + } + + if (!context->iface.write) { context->status = SDL_IO_STATUS_READONLY; SDL_Unsupported(); return 0; } + if (size == 0) { + return 0; // context->status doesn't change for this. + } + context->status = SDL_IO_STATUS_READY; SDL_ClearError(); - if (size == 0) { - return 0; - } - - bytes = context->iface.write(context->userdata, ptr, size, &context->status); - if ((bytes == 0) && (context->status == SDL_IO_STATUS_READY)) { - context->status = SDL_IO_STATUS_ERROR; - } - return bytes; + return context->iface.write(context->userdata, ptr, size, &context->status); } size_t SDL_IOprintf(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) @@ -1420,7 +1520,7 @@ bool SDL_FlushIO(SDL_IOStream *context) { bool result = true; - if (!context) { + CHECK_PARAM(!context) { return SDL_InvalidParamError("context"); } diff --git a/libs/SDL3/src/io/SDL_iostream_c.h b/libs/SDL3/src/io/SDL_iostream_c.h index c04fe203..fadb98bc 100644 --- a/libs/SDL3/src/io/SDL_iostream_c.h +++ b/libs/SDL3/src/io/SDL_iostream_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/io/SDL_sysasyncio.h b/libs/SDL3/src/io/SDL_sysasyncio.h index fbe41d19..b0ca21b9 100644 --- a/libs/SDL3/src/io/SDL_sysasyncio.h +++ b/libs/SDL3/src/io/SDL_sysasyncio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -124,6 +124,7 @@ struct SDL_AsyncIO SDL_AsyncIOTask tasks; SDL_AsyncIOTask *closing; // The close task, which isn't queued until all pending work for this file is done. bool oneshot; // true if this is a SDL_LoadFileAsync open. + bool readonly; // true if this file is opened read-only. }; // This is implemented for various platforms; param validation is done before calling this. Open file, fill in iface and userdata. diff --git a/libs/SDL3/src/io/generic/SDL_asyncio_generic.c b/libs/SDL3/src/io/generic/SDL_asyncio_generic.c index 4c2a5620..76ece036 100644 --- a/libs/SDL3/src/io/generic/SDL_asyncio_generic.c +++ b/libs/SDL3/src/io/generic/SDL_asyncio_generic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/io/io_uring/SDL_asyncio_liburing.c b/libs/SDL3/src/io/io_uring/SDL_asyncio_liburing.c index 07e8c241..a15218a1 100644 --- a/libs/SDL3/src/io/io_uring/SDL_asyncio_liburing.c +++ b/libs/SDL3/src/io/io_uring/SDL_asyncio_liburing.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -44,9 +44,17 @@ static bool (*AsyncIOFromFile)(const char *file, const char *mode, SDL_AsyncIO * // we never link directly to liburing. // (this says "-ffi" which sounds like a scripting language binding thing, but the non-ffi version // is static-inline code we can't lookup with dlsym. This is by design.) -static const char *liburing_library = "liburing-ffi.so.2"; +#define SDL_DRIVER_LIBURING_DYNAMIC "liburing-ffi.so.2" +static const char *liburing_library = SDL_DRIVER_LIBURING_DYNAMIC; static void *liburing_handle = NULL; +SDL_ELF_NOTE_DLOPEN( + "io-io_uring", + "Support for async IO through liburing", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_DRIVER_LIBURING_DYNAMIC +) + #define SDL_LIBURING_FUNCS \ SDL_LIBURING_FUNC(int, io_uring_queue_init, (unsigned entries, struct io_uring *ring, unsigned flags)) \ SDL_LIBURING_FUNC(struct io_uring_probe *,io_uring_get_probe,(void)) \ @@ -244,7 +252,7 @@ static SDL_AsyncIOTask *ProcessCQE(LibUringAsyncIOQueueData *queuedata, struct i } } - if ((task->type == SDL_ASYNCIO_TASK_CLOSE) && task->flush) { + if (task && (task->type == SDL_ASYNCIO_TASK_CLOSE) && task->flush) { task->flush = false; task = NULL; // don't return this one, it's a linked task, so it'll arrive in a later CQE. } @@ -512,10 +520,12 @@ static void MaybeInitializeLibUring(void) { if (SDL_ShouldInit(&liburing_init)) { if (LoadLibUring()) { + SDL_DebugLogBackend("asyncio", "liburing"); CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_liburing; QuitAsyncIO = SDL_SYS_QuitAsyncIO_liburing; AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_liburing; } else { // can't use liburing? Use the "generic" threadpool implementation instead. + SDL_DebugLogBackend("asyncio", "generic"); CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_Generic; QuitAsyncIO = SDL_SYS_QuitAsyncIO_Generic; AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_Generic; diff --git a/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.c b/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.c index b42f5393..55e60678 100644 --- a/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.c +++ b/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.h b/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.h index b9313907..43f79d32 100644 --- a/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.h +++ b/libs/SDL3/src/io/n3ds/SDL_iostreamromfs.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/io/windows/SDL_asyncio_windows_ioring.c b/libs/SDL3/src/io/windows/SDL_asyncio_windows_ioring.c index 497456cf..81a5d840 100644 --- a/libs/SDL3/src/io/windows/SDL_asyncio_windows_ioring.c +++ b/libs/SDL3/src/io/windows/SDL_asyncio_windows_ioring.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -200,8 +200,14 @@ static SDL_AsyncIOTask *ProcessCQE(WinIoRingAsyncIOQueueData *queuedata, IORING_ task = NULL; // it already finished or was too far along to cancel, so we'll pick up the actual results later. } } else if (FAILED(cqe->ResultCode)) { - task->result = SDL_ASYNCIO_FAILURE; - // !!! FIXME: fill in task->error. + if ((task->type == SDL_ASYNCIO_TASK_CLOSE) && (cqe->ResultCode == E_ACCESSDENIED) && task->asyncio->readonly) { + // we push all close requests through as flushes, as there is currently no async close operation and flushing writes to disk is the time-consuming part. + // However, flushing a read-only handle generates an error, so we catch this specific situation and ignore it. This approach still makes the task go + // through the IoRing so we can handle this all in the same place otherwise. The actual close happens below. + } else { + task->result = SDL_ASYNCIO_FAILURE; + // !!! FIXME: fill in task->error. + } } else { if ((task->type == SDL_ASYNCIO_TASK_WRITE) && (((Uint64) cqe->Information) < task->requested_size)) { task->result = SDL_ASYNCIO_FAILURE; // it's always a failure on short writes. @@ -511,10 +517,12 @@ static void MaybeInitializeWinIoRing(void) { if (SDL_ShouldInit(&ioring_init)) { if (LoadWinIoRing()) { + SDL_DebugLogBackend("asyncio", "ioring"); CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_ioring; QuitAsyncIO = SDL_SYS_QuitAsyncIO_ioring; AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_ioring; } else { // can't use ioring? Use the "generic" threadpool implementation instead. + SDL_DebugLogBackend("asyncio", "generic"); CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_Generic; QuitAsyncIO = SDL_SYS_QuitAsyncIO_Generic; AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_Generic; diff --git a/libs/SDL3/src/joystick/SDL_gamepad.c b/libs/SDL3/src/joystick/SDL_gamepad.c index 8a5ecc0a..dd5b4166 100644 --- a/libs/SDL3/src/joystick/SDL_gamepad.c +++ b/libs/SDL3/src/joystick/SDL_gamepad.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -29,13 +29,17 @@ #include "SDL_gamepad_db.h" #include "controller_type.h" #include "usb_ids.h" +#include "hidapi/SDL_hidapi_flydigi.h" #include "hidapi/SDL_hidapi_nintendo.h" +#include "hidapi/SDL_hidapi_sinput.h" #include "../events/SDL_events_c.h" +#include "../SDL_hints_c.h" - -#ifdef SDL_PLATFORM_ANDROID +#ifdef SDL_PLATFORM_WIN32 +#include "../core/windows/SDL_windows.h" #endif + // Many gamepads turn the center button into an instantaneous button press #define SDL_MINIMUM_GUIDE_BUTTON_DELAY_MS 250 @@ -54,8 +58,25 @@ #define SDL_GAMEPAD_SDKLE_FIELD "sdk<=:" #define SDL_GAMEPAD_SDKLE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_SDKLE_FIELD) +// Helper function to add button mapping +#define SDL_ADD_BUTTON_MAPPING(sdl_name, button_id, maxlen) \ + do { \ + char temp[32]; \ + (void)SDL_snprintf(temp, sizeof(temp), "%s:b%d,", sdl_name, button_id); \ + SDL_strlcat(mapping_string, temp, maxlen); \ + } while (0) + +// Helper function to add axis mapping +#define SDL_ADD_AXIS_MAPPING(sdl_name, axis_id, maxlen) \ + do { \ + char temp[32]; \ + (void)SDL_snprintf(temp, sizeof(temp), "%s:a%d,", sdl_name, axis_id); \ + SDL_strlcat(mapping_string, temp, maxlen); \ + } while (0) + static bool SDL_gamepads_initialized; static SDL_Gamepad *SDL_gamepads SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static SDL_HashTable *SDL_gamepad_names SDL_GUARDED_BY(SDL_joystick_lock) = NULL; // The face button style of a gamepad typedef enum @@ -129,12 +150,12 @@ struct SDL_Gamepad #undef _guarded -#define CHECK_GAMEPAD_MAGIC(gamepad, result) \ - if (!SDL_ObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD) || \ - !SDL_IsJoystickValid(gamepad->joystick)) { \ - SDL_InvalidParamError("gamepad"); \ - SDL_UnlockJoysticks(); \ - return result; \ +#define CHECK_GAMEPAD_MAGIC(gamepad, result) \ + CHECK_PARAM(!SDL_ObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD) || \ + !SDL_IsJoystickValid(gamepad->joystick)) { \ + SDL_InvalidParamError("gamepad"); \ + SDL_UnlockJoysticks(); \ + return result; \ } static SDL_vidpid_list SDL_allowed_gamepads = { @@ -150,6 +171,51 @@ static SDL_vidpid_list SDL_ignored_gamepads = { false }; +/* + List of words in gamepad names that indicate that the gamepad should not be detected. + See also `initial_blacklist_devices` in SDL_joystick.c +*/ + +enum SDL_GamepadBlacklistWordsPosition { + GAMEPAD_BLACKLIST_BEGIN, + GAMEPAD_BLACKLIST_END, + GAMEPAD_BLACKLIST_ANYWHERE, +}; + +struct SDL_GamepadBlacklistWords { + const char* str; + enum SDL_GamepadBlacklistWordsPosition pos; +}; + +static const struct SDL_GamepadBlacklistWords SDL_gamepad_blacklist_words[] = { +#ifdef SDL_PLATFORM_LINUX + {" Motion Sensors", GAMEPAD_BLACKLIST_END}, // Don't treat the PS3 and PS4 motion controls as a separate gamepad + {" IMU", GAMEPAD_BLACKLIST_END}, // Don't treat the Nintendo IMU as a separate gamepad + {" Touchpad", GAMEPAD_BLACKLIST_END}, // "Sony Interactive Entertainment DualSense Wireless Controller Touchpad" + + // Don't treat the Wii extension controls as a separate gamepad + {" Accelerometer", GAMEPAD_BLACKLIST_END}, + {" IR", GAMEPAD_BLACKLIST_END}, + {" Motion Plus", GAMEPAD_BLACKLIST_END}, + {" Nunchuk", GAMEPAD_BLACKLIST_END}, +#endif + + // The Google Pixel fingerprint sensor, as well as other fingerprint sensors, reports itself as a joystick + {"uinput-", GAMEPAD_BLACKLIST_BEGIN}, + + {"Synaptics ", GAMEPAD_BLACKLIST_ANYWHERE}, // "Synaptics TM2768-001", "SynPS/2 Synaptics TouchPad" + {"Trackpad", GAMEPAD_BLACKLIST_ANYWHERE}, + {"Clickpad", GAMEPAD_BLACKLIST_ANYWHERE}, + // "Usb Keyboard Usb Keyboard Consumer Control", "Framework Laptop 16 Keyboard Module - ISO System Control" + {" Keyboard", GAMEPAD_BLACKLIST_ANYWHERE}, + {" Laptop ", GAMEPAD_BLACKLIST_ANYWHERE}, // "Framework Laptop 16 Numpad Module System Control" + {"Mouse ", GAMEPAD_BLACKLIST_BEGIN}, // "Mouse passthrough" + {" Pen", GAMEPAD_BLACKLIST_END}, // "Wacom One by Wacom S Pen" + {" Finger", GAMEPAD_BLACKLIST_END}, // "Wacom HID 495F Finger" + {" LED ", GAMEPAD_BLACKLIST_ANYWHERE}, // "ASRock LED Controller" + {" Thelio ", GAMEPAD_BLACKLIST_ANYWHERE}, // "System76 Thelio Io 2" +}; + static GamepadMapping_t *SDL_PrivateAddMappingForGUID(SDL_GUID jGUID, const char *mappingString, bool *existing, SDL_GamepadMappingPriority priority); static void SDL_PrivateLoadButtonMapping(SDL_Gamepad *gamepad, GamepadMapping_t *pGamepadMapping); static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id, bool create_mapping); @@ -308,14 +374,57 @@ static void RecenterGamepad(SDL_Gamepad *gamepad) } } +static const char *SDL_UpdateGamepadNameForID(SDL_JoystickID instance_id) +{ + const char *current_name = NULL; + + SDL_AssertJoysticksLocked(); + + GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, true); + if (mapping) { + if (SDL_strcmp(mapping->name, "*") == 0) { + current_name = SDL_GetJoystickNameForID(instance_id); + } else { + current_name = mapping->name; + } + } + + if (!SDL_gamepad_names) { + return SDL_GetPersistentString(current_name); + } + + char *name = NULL; + bool found = SDL_FindInHashTable(SDL_gamepad_names, (const void *)(uintptr_t)instance_id, (const void **)&name); + if (!current_name) { + if (!found) { + SDL_SetError("Gamepad %" SDL_PRIu32 " not found", instance_id); + return NULL; + } + if (!name) { + // SDL_strdup() failed during insert + SDL_OutOfMemory(); + return NULL; + } + return name; + } + + if (!name || SDL_strcmp(name, current_name) != 0) { + name = SDL_strdup(current_name); + SDL_InsertIntoHashTable(SDL_gamepad_names, (const void *)(uintptr_t)instance_id, name, true); + } + return name; +} + void SDL_PrivateGamepadAdded(SDL_JoystickID instance_id) { SDL_Event event; - if (!SDL_gamepads_initialized) { + if (!SDL_gamepads_initialized || SDL_IsJoystickBeingAdded()) { return; } + SDL_UpdateGamepadNameForID(instance_id); + event.type = SDL_EVENT_GAMEPAD_ADDED; event.common.timestamp = 0; event.gdevice.which = instance_id; @@ -565,10 +674,14 @@ static void PopMappingChangeTracking(void) GamepadMapping_t *old_mapping = gamepad ? gamepad->mapping : tracker->joystick_mappings[i]; if (new_mapping && !old_mapping) { - SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)true, true); + if (s_gamepadInstanceIDs) { + SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)true, true); + } SDL_PrivateGamepadAdded(joystick); } else if (old_mapping && !new_mapping) { - SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)false, true); + if (s_gamepadInstanceIDs) { + SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)false, true); + } SDL_PrivateGamepadRemoved(joystick); } else if (old_mapping != new_mapping || HasMappingChangeTracking(tracker, new_mapping)) { if (gamepad) { @@ -599,95 +712,390 @@ static GamepadMapping_t *SDL_CreateMappingForAndroidGamepad(SDL_GUID guid) char mapping_string[1024]; int button_mask; int axis_mask; + Uint16 vendor, product; + + SDL_strlcpy(mapping_string, "none,", sizeof(mapping_string)); - button_mask = SDL_Swap16LE(*(Uint16 *)(&guid.data[sizeof(guid.data) - 4])); - axis_mask = SDL_Swap16LE(*(Uint16 *)(&guid.data[sizeof(guid.data) - 2])); - if (!button_mask && !axis_mask) { - // Accelerometer, shouldn't have a gamepad mapping - return NULL; - } - if (!(button_mask & face_button_mask)) { - // We don't know what buttons or axes are supported, don't make up a mapping - return NULL; - } + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + if (vendor == USB_VENDOR_NINTENDO) { + if (product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT || product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT) { + // FIXME: Should we have a separate hint for non-HIDAPI JoyCon handling? + // Android doesn't report JoyCon SL/SR presses for some reason, so no horizontal triggers/vertical paddles + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false)) { + // Vertical mode + if (product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT) { + SDL_strlcat(mapping_string, "Nintendo Switch Joy-Con (L),back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,misc1:b18,", sizeof(mapping_string)); + } else { + SDL_strlcat(mapping_string, "Nintendo Switch Joy-Con (R),a:b0,b:b1,guide:b5,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a0,righty:a1,start:b6,x:b3,y:b2,", sizeof(mapping_string)); + } + } else { + // Mini gamepad mode + if (product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT) { + SDL_strlcat(mapping_string, "Nintendo Switch Joy-Con (L),a:b13,b:b12,guide:b18,leftstick:b7,leftx:a1,lefty:a0~,start:b4,x:b11,y:b14,paddle2:b9,paddle4:b15,", sizeof(mapping_string)); + } else { + SDL_strlcat(mapping_string, "Nintendo Switch Joy-Con (R),a:b1,b:b2,guide:b5,leftstick:b8,leftx:a1~,lefty:a0,start:b6,x:b0,y:b3,paddle1:b10,paddle3:b16,", sizeof(mapping_string)); + } + } + } + } else { + button_mask = SDL_Swap16LE(*(Uint16 *)(&guid.data[sizeof(guid.data) - 4])); + axis_mask = SDL_Swap16LE(*(Uint16 *)(&guid.data[sizeof(guid.data) - 2])); + if (!button_mask && !axis_mask) { + // Accelerometer, shouldn't have a gamepad mapping + return NULL; + } + if (!(button_mask & face_button_mask)) { + // We don't know what buttons or axes are supported, don't make up a mapping + return NULL; + } - SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "*,", sizeof(mapping_string)); - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_SOUTH)) { - SDL_strlcat(mapping_string, "a:b0,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_EAST)) { - SDL_strlcat(mapping_string, "b:b1,", sizeof(mapping_string)); - } else if (button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) { - // Use the back button as "B" for easy UI navigation with TV remotes - SDL_strlcat(mapping_string, "b:b4,", sizeof(mapping_string)); - button_mask &= ~(1 << SDL_GAMEPAD_BUTTON_BACK); - } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_WEST)) { - SDL_strlcat(mapping_string, "x:b2,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_NORTH)) { - SDL_strlcat(mapping_string, "y:b3,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) { - SDL_strlcat(mapping_string, "back:b4,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_GUIDE)) { - // The guide button generally isn't functional (or acts as a home button) on most Android gamepads before Android 11 - if (SDL_GetAndroidSDKVersion() >= 30 /* Android 11 */) { - SDL_strlcat(mapping_string, "guide:b5,", sizeof(mapping_string)); + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_SOUTH)) { + SDL_strlcat(mapping_string, "a:b0,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_EAST)) { + SDL_strlcat(mapping_string, "b:b1,", sizeof(mapping_string)); + } else if (button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) { + // Use the back button as "B" for easy UI navigation with TV remotes + SDL_strlcat(mapping_string, "b:b4,", sizeof(mapping_string)); + button_mask &= ~(1 << SDL_GAMEPAD_BUTTON_BACK); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_WEST)) { + SDL_strlcat(mapping_string, "x:b2,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_NORTH)) { + SDL_strlcat(mapping_string, "y:b3,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) { + SDL_strlcat(mapping_string, "back:b4,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_GUIDE)) { + // The guide button generally isn't functional (or acts as a home button) on most Android gamepads before Android 11 + if (SDL_GetAndroidSDKVersion() >= 30 /* Android 11 */) { + SDL_strlcat(mapping_string, "guide:b5,", sizeof(mapping_string)); + } + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) { + SDL_strlcat(mapping_string, "start:b6,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK)) { + SDL_strlcat(mapping_string, "leftstick:b7,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK)) { + SDL_strlcat(mapping_string, "rightstick:b8,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) { + SDL_strlcat(mapping_string, "leftshoulder:b9,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) { + SDL_strlcat(mapping_string, "rightshoulder:b10,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_UP)) { + SDL_strlcat(mapping_string, "dpup:b11,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN)) { + SDL_strlcat(mapping_string, "dpdown:b12,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT)) { + SDL_strlcat(mapping_string, "dpleft:b13,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) { + SDL_strlcat(mapping_string, "dpright:b14,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTX)) { + SDL_strlcat(mapping_string, "leftx:a0,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTY)) { + SDL_strlcat(mapping_string, "lefty:a1,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHTX)) { + SDL_strlcat(mapping_string, "rightx:a2,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHTY)) { + SDL_strlcat(mapping_string, "righty:a3,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER)) { + SDL_strlcat(mapping_string, "lefttrigger:a4,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) { + SDL_strlcat(mapping_string, "righttrigger:a5,", sizeof(mapping_string)); } } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) { - SDL_strlcat(mapping_string, "start:b6,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK)) { - SDL_strlcat(mapping_string, "leftstick:b7,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK)) { - SDL_strlcat(mapping_string, "rightstick:b8,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) { - SDL_strlcat(mapping_string, "leftshoulder:b9,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) { - SDL_strlcat(mapping_string, "rightshoulder:b10,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_UP)) { - SDL_strlcat(mapping_string, "dpup:b11,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN)) { - SDL_strlcat(mapping_string, "dpdown:b12,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT)) { - SDL_strlcat(mapping_string, "dpleft:b13,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) { - SDL_strlcat(mapping_string, "dpright:b14,", sizeof(mapping_string)); - } - if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTX)) { - SDL_strlcat(mapping_string, "leftx:a0,", sizeof(mapping_string)); - } - if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTY)) { - SDL_strlcat(mapping_string, "lefty:a1,", sizeof(mapping_string)); - } - if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHTX)) { - SDL_strlcat(mapping_string, "rightx:a2,", sizeof(mapping_string)); - } - if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHTY)) { - SDL_strlcat(mapping_string, "righty:a3,", sizeof(mapping_string)); - } - if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER)) { - SDL_strlcat(mapping_string, "lefttrigger:a4,", sizeof(mapping_string)); - } - if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) { - SDL_strlcat(mapping_string, "righttrigger:a5,", sizeof(mapping_string)); - } - return SDL_PrivateAddMappingForGUID(guid, mapping_string, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT); } #endif // SDL_PLATFORM_ANDROID +/* +* Helper function to apply SInput decoded styles to the mapping string +*/ +static inline void SDL_SInputStylesMapExtraction(SDL_SInputStyles_t* styles, char* mapping_string, size_t mapping_string_len) +{ + int current_button = 0; + int current_axis = 0; + int misc_buttons = 0; + int misc_button, misc_end; + bool digital_triggers = false; + bool dualstage_triggers = false; + int bumpers = 0; + bool left_stick = false; + bool right_stick = false; + int paddle_pairs = 0; + char misc_label[32]; + + // Determine how many misc buttons are used + switch (styles->misc_style) { + case SINPUT_MISCSTYLE_1: + misc_buttons = 1; + break; + + case SINPUT_MISCSTYLE_2: + misc_buttons = 2; + break; + + case SINPUT_MISCSTYLE_3: + misc_buttons = 3; + break; + + case SINPUT_MISCSTYLE_4: + misc_buttons = 4; + break; + + default: + break; + } + // The share button is reserved as misc1, additional buttons start at misc2 + misc_button = 2; + misc_end = misc_button + misc_buttons; + + // Analog joysticks (always come first in axis mapping) + switch (styles->analog_style) { + case SINPUT_ANALOGSTYLE_LEFTONLY: + SDL_ADD_AXIS_MAPPING("leftx", current_axis++, mapping_string_len); + SDL_ADD_AXIS_MAPPING("lefty", current_axis++, mapping_string_len); + left_stick = true; + break; + + case SINPUT_ANALOGSTYLE_LEFTRIGHT: + SDL_ADD_AXIS_MAPPING("leftx", current_axis++, mapping_string_len); + SDL_ADD_AXIS_MAPPING("lefty", current_axis++, mapping_string_len); + SDL_ADD_AXIS_MAPPING("rightx", current_axis++, mapping_string_len); + SDL_ADD_AXIS_MAPPING("righty", current_axis++, mapping_string_len); + left_stick = true; + right_stick = true; + break; + + case SINPUT_ANALOGSTYLE_RIGHTONLY: + SDL_ADD_AXIS_MAPPING("rightx", current_axis++, mapping_string_len); + SDL_ADD_AXIS_MAPPING("righty", current_axis++, mapping_string_len); + right_stick = true; + break; + + default: + break; + } + + // Bumpers + switch (styles->bumper_style) { + case SINPUT_BUMPERSTYLE_ONE: + bumpers = 1; + break; + + case SINPUT_BUMPERSTYLE_TWO: + bumpers = 2; + break; + + default: + break; + } + + // Analog triggers + switch (styles->trigger_style) { + // Analog triggers + case SINPUT_TRIGGERSTYLE_ANALOG: + SDL_ADD_AXIS_MAPPING("lefttrigger", current_axis++, mapping_string_len); + SDL_ADD_AXIS_MAPPING("righttrigger", current_axis++, mapping_string_len); + break; + + // Digital triggers + case SINPUT_TRIGGERSTYLE_DIGITAL: + digital_triggers = true; + break; + + // Analog triggers with digital press + case SINPUT_TRIGGERSTYLE_DUALSTAGE: + SDL_ADD_AXIS_MAPPING("lefttrigger", current_axis++, mapping_string_len); + SDL_ADD_AXIS_MAPPING("righttrigger", current_axis++, mapping_string_len); + dualstage_triggers = true; + break; + + default: + break; + } + + switch (styles->paddle_style) { + case SINPUT_PADDLESTYLE_TWO: + paddle_pairs = 1; + break; + + case SINPUT_PADDLESTYLE_FOUR: + paddle_pairs = 2; + break; + + default: + break; + } + + // Digital button mappings + // ABXY buttons (always applied as South, East, West, North) + SDL_ADD_BUTTON_MAPPING("a", current_button++, mapping_string_len); // South (typically A on Xbox, X on PlayStation) + SDL_ADD_BUTTON_MAPPING("b", current_button++, mapping_string_len); // East (typically B on Xbox, Circle on PlayStation) + SDL_ADD_BUTTON_MAPPING("x", current_button++, mapping_string_len); // West (typically X on Xbox, Square on PlayStation) + SDL_ADD_BUTTON_MAPPING("y", current_button++, mapping_string_len); // North (typically Y on Xbox, Triangle on PlayStation) + + // D-Pad (always applied) + SDL_strlcat(mapping_string, "dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,", mapping_string_len); + + // Left and Right stick buttons + if (left_stick) { + SDL_ADD_BUTTON_MAPPING("leftstick", current_button++, mapping_string_len); + } + if (right_stick) { + SDL_ADD_BUTTON_MAPPING("rightstick", current_button++, mapping_string_len); + } + + // Digital shoulder buttons (L/R Shoulder) + if (bumpers > 0) { + SDL_ADD_BUTTON_MAPPING("leftshoulder", current_button++, mapping_string_len); + } + if (bumpers > 1) { + SDL_ADD_BUTTON_MAPPING("rightshoulder", current_button++, mapping_string_len); + } + + // Digital trigger buttons (capability overrides analog) + if (digital_triggers) { + SDL_ADD_BUTTON_MAPPING("lefttrigger", current_button++, mapping_string_len); + SDL_ADD_BUTTON_MAPPING("righttrigger", current_button++, mapping_string_len); + } else if (dualstage_triggers) { + // Dual-stage trigger buttons are appended as MISC buttons + // By convention the trigger buttons are misc3 and misc4 for GameCube style controllers + if (misc_end < 3) { + misc_end = 3; + } + SDL_snprintf(misc_label, sizeof(misc_label), "misc%d", misc_end++); + SDL_ADD_BUTTON_MAPPING(misc_label, current_button++, mapping_string_len); + SDL_snprintf(misc_label, sizeof(misc_label), "misc%d", misc_end++); + SDL_ADD_BUTTON_MAPPING(misc_label, current_button++, mapping_string_len); + } + + // Paddle 1/2 + if (paddle_pairs > 0) { + // Paddle 2 is first for left/right order of SInput + SDL_ADD_BUTTON_MAPPING("paddle2", current_button++, mapping_string_len); + SDL_ADD_BUTTON_MAPPING("paddle1", current_button++, mapping_string_len); + } + + // Start/Plus + SDL_ADD_BUTTON_MAPPING("start", current_button++, mapping_string_len); + + // Back/Minus, Guide/Home, Share/Capture + switch (styles->meta_style) { + case SINPUT_METASTYLE_BACK: + SDL_ADD_BUTTON_MAPPING("back", current_button++, mapping_string_len); + break; + + case SINPUT_METASTYLE_BACKGUIDE: + SDL_ADD_BUTTON_MAPPING("back", current_button++, mapping_string_len); + SDL_ADD_BUTTON_MAPPING("guide", current_button++, mapping_string_len); + break; + + case SINPUT_METASTYLE_BACKGUIDESHARE: + SDL_ADD_BUTTON_MAPPING("back", current_button++, mapping_string_len); + SDL_ADD_BUTTON_MAPPING("guide", current_button++, mapping_string_len); + SDL_ADD_BUTTON_MAPPING("misc1", current_button++, mapping_string_len); + break; + + default: + break; + } + + // Paddle 3/4 + if (paddle_pairs > 1) { + // Paddle 4 is first for left/right order of SInput + SDL_ADD_BUTTON_MAPPING("paddle4", current_button++, mapping_string_len); + SDL_ADD_BUTTON_MAPPING("paddle3", current_button++, mapping_string_len); + } + + // Touchpad buttons + switch (styles->touch_style) { + case SINPUT_TOUCHSTYLE_SINGLE: + SDL_ADD_BUTTON_MAPPING("touchpad", current_button++, mapping_string_len); + break; + + case SINPUT_TOUCHSTYLE_DOUBLE: + SDL_ADD_BUTTON_MAPPING("touchpad", current_button++, mapping_string_len); + // Add the second touchpad button at the end of the misc buttons + SDL_snprintf(misc_label, sizeof(misc_label), "misc%d", misc_end++); + SDL_ADD_BUTTON_MAPPING(misc_label, current_button++, mapping_string_len); + break; + + default: + break; + } + + for (int i = 0; i < misc_buttons; ++i) { + SDL_snprintf(misc_label, sizeof(misc_label), "misc%d", misc_button++); + SDL_ADD_BUTTON_MAPPING(misc_label, current_button++, mapping_string_len); + } +} + +/* +* Helper function to decode SInput features information packed into version +*/ +static void SDL_CreateMappingStringForSInputGamepad(Uint16 vendor, Uint16 product, Uint8 sub_product, Uint16 version, Uint8 face_style, char* mapping_string, size_t mapping_string_len) +{ + SDL_SInputStyles_t decoded = { 0 }; + + switch (face_style) { + default: + SDL_strlcat(mapping_string, "face:abxy,", mapping_string_len); + break; + case 2: + SDL_strlcat(mapping_string, "face:axby,", mapping_string_len); + break; + case 3: + SDL_strlcat(mapping_string, "face:bayx,", mapping_string_len); + break; + case 4: + SDL_strlcat(mapping_string, "face:sony,", mapping_string_len); + break; + } + + // Interpret the mapping string + // dynamically based on the feature responses + decoded.misc_style = (SInput_MiscStyleType)(version % SINPUT_MISCSTYLE_MAX); + version /= SINPUT_MISCSTYLE_MAX; + + decoded.touch_style = (SInput_TouchStyleType)(version % SINPUT_TOUCHSTYLE_MAX); + version /= SINPUT_TOUCHSTYLE_MAX; + + decoded.meta_style = (SInput_MetaStyleType)(version % SINPUT_METASTYLE_MAX); + version /= SINPUT_METASTYLE_MAX; + + decoded.paddle_style = (SInput_PaddleStyleType)(version % SINPUT_PADDLESTYLE_MAX); + version /= SINPUT_PADDLESTYLE_MAX; + + decoded.trigger_style = (SInput_TriggerStyleType)(version % SINPUT_TRIGGERSTYLE_MAX); + version /= SINPUT_TRIGGERSTYLE_MAX; + + decoded.bumper_style = (SInput_BumperStyleType)(version % SINPUT_BUMPERSTYLE_MAX); + version /= SINPUT_BUMPERSTYLE_MAX; + + decoded.analog_style = (SInput_AnalogStyleType)(version % SINPUT_ANALOGSTYLE_MAX); + + SDL_SInputStylesMapExtraction(&decoded, mapping_string, mapping_string_len); +} + /* * Helper function to guess at a mapping for HIDAPI gamepads */ @@ -697,10 +1105,11 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) char mapping_string[1024]; Uint16 vendor; Uint16 product; + Uint16 version; SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string)); - SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, &version, NULL); if (SDL_IsJoystickWheel(vendor, product)) { // We don't want to pick up Logitech FFB wheels here @@ -714,7 +1123,34 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) 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: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)); + SDL_strlcat(mapping_string, "a:b0,b:b2,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b1,y:b3,misc3:b11,misc4:b10,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER) { + SDL_strlcat(mapping_string, "a:b1,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,leftshoulder:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a5,rightx:a2,righty:a3,start:b5,x:b0,y:b2,misc1:b8,misc2:b9,misc3:b10,misc4:b11,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_PRO) { + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,misc1:b11,misc2:b12,paddle1:b13,paddle2:b14,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT) { + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false)) { + // Vertical mode + SDL_strlcat(mapping_string, "back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b11,paddle2:b14,paddle4:b16,", sizeof(mapping_string)); + } else { + // Mini gamepad mode + SDL_strlcat(mapping_string, "a:b1,b:b2,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b0,paddle2:b14,paddle4:b16,", sizeof(mapping_string)); + } + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT) { + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false)) { + // Vertical mode + SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,misc2:b12,paddle1:b13,paddle3:b15,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + } else { + // Mini gamepad mode + SDL_strlcat(mapping_string, "a:b1,b:b3,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b0,y:b2,misc2:b12,paddle1:b13,paddle3:b15,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + } + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR) { + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,misc1:b11,misc2:b12,paddle1:b13,paddle2:b14,paddle3:b15,paddle4:b16,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); } else if (vendor == USB_VENDOR_NINTENDO && (guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCLeft || guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCRight || @@ -742,7 +1178,7 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); break; case k_eSwitchDeviceInfoControllerType_N64: - SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b11,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b11,misc2:b4,rightshoulder:b10,righttrigger:b7,start:b6,x:a5,y:b2,", sizeof(mapping_string)); break; case k_eSwitchDeviceInfoControllerType_SEGA_Genesis: SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b11,", sizeof(mapping_string)); @@ -785,16 +1221,25 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) product == USB_PRODUCT_8BITDO_SN30_PRO || product == USB_PRODUCT_8BITDO_SN30_PRO_BT || product == USB_PRODUCT_8BITDO_PRO_2 || - product == USB_PRODUCT_8BITDO_PRO_2_BT)) { + product == USB_PRODUCT_8BITDO_PRO_2_BT || + product == USB_PRODUCT_8BITDO_PRO_3)) { SDL_strlcat(mapping_string, "a:b1,b:b0,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); if (product == USB_PRODUCT_8BITDO_PRO_2 || product == USB_PRODUCT_8BITDO_PRO_2_BT) { SDL_strlcat(mapping_string, "paddle1:b14,paddle2:b13,", sizeof(mapping_string)); + } else if (product == USB_PRODUCT_8BITDO_PRO_3) { + SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,paddle3:b14,paddle4:b13,", sizeof(mapping_string)); } } else if (vendor == USB_VENDOR_8BITDO && (product == USB_PRODUCT_8BITDO_SF30_PRO || product == USB_PRODUCT_8BITDO_SF30_PRO_BT)) { // This controller has no guide button SDL_strlcat(mapping_string, "a:b1,b:b0,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + } else if (SDL_IsJoystickSInputController(vendor, product)) { + + Uint8 face_style = (guid.data[15] & 0xE0) >> 5; + Uint8 sub_product = guid.data[15] & 0x1F; + + SDL_CreateMappingStringForSInputGamepad(vendor, product, sub_product, version, face_style, mapping_string, sizeof(mapping_string)); } else { // All other gamepads have the standard set of 19 buttons and 6 axes if (SDL_IsJoystickGameCube(vendor, product)) { @@ -805,7 +1250,13 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) if (SDL_IsJoystickSteamController(vendor, product)) { // Steam controllers have 2 back paddle buttons - SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,", sizeof(mapping_string)); + } else if (SDL_IsJoystickSteamDeck(vendor, product)) { + // The Steam Deck's built-in controller has QAM, 4 back buttons, L/R trackpads, and L/R capacitive touch sticks + SDL_strlcat(mapping_string, "misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,touchpad:b17,misc2:b16,misc3:b19,misc4:b18", sizeof(mapping_string)); + } else if (SDL_IsJoystickSteamTriton(vendor, product)) { + // Second generation Steam controllers have 4 back paddle buttons + SDL_strlcat(mapping_string, "misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,touchpad:b17,misc2:b16,misc3:b19,misc4:b18,misc5:b21,misc6:b20", sizeof(mapping_string)); } else if (SDL_IsJoystickNintendoSwitchPro(vendor, product) || SDL_IsJoystickNintendoSwitchProInputOnly(vendor, product)) { // Nintendo Switch Pro controllers have a screenshot button @@ -829,22 +1280,19 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) } } 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)); + SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc1:b11,misc3:b16,misc4:b17", sizeof(mapping_string)); } else if (SDL_IsJoystickFlydigiController(vendor, product)) { SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,paddle3:b13,paddle4:b14,", sizeof(mapping_string)); - switch (guid.data[15]) { - case 20: - case 21: - case 22: - case 23: - case 28: - case 80: - case 81: - case 85: - case 105: + if (guid.data[15] >= SDL_FLYDIGI_VADER2) { // Vader series of controllers have C/Z buttons SDL_strlcat(mapping_string, "misc2:b15,misc3:b16,", sizeof(mapping_string)); - break; + if (guid.data[15] == SDL_FLYDIGI_VADER5_PRO) { + // Vader 5 has additional shoulder macro buttons and a circle button + SDL_strlcat(mapping_string, "misc4:b17,misc5:b18,misc6:b19", sizeof(mapping_string)); + } + } else if (guid.data[15] == SDL_FLYDIGI_APEX5) { + // Apex 5 has additional shoulder macro buttons + SDL_strlcat(mapping_string, "misc2:b15,misc3:b16,", sizeof(mapping_string)); } } else if (vendor == USB_VENDOR_8BITDO && product == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,paddle3:b14,paddle4:b13,", sizeof(mapping_string)); @@ -977,7 +1425,8 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMappingForGUID(SDL_GUID guid, bool { GamepadMapping_t *mapping; - mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, true, adding_mapping); + // Try first with an exact match on version and CRC + mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, true, true); if (mapping) { return mapping; } @@ -987,10 +1436,19 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMappingForGUID(SDL_GUID guid, bool return NULL; } - // Try harder to get the best match, or create a mapping + // Try without CRC match + mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, true, false); + if (mapping) { + return mapping; + } + // Try without version match if (SDL_JoystickGUIDUsesVersion(guid)) { - // Try again, ignoring the version + mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, false, true); + if (mapping) { + return mapping; + } + mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, false, false); if (mapping) { return mapping; @@ -1232,6 +1690,7 @@ static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szG 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); @@ -1470,6 +1929,8 @@ static void SDL_UpdateGamepadFaceStyle(SDL_Gamepad *gamepad) static void SDL_FixupHIDAPIMapping(SDL_Gamepad *gamepad) { + SDL_AssertJoysticksLocked(); + // Check to see if we need fixup bool need_fixup = false; for (int i = 0; i < gamepad->num_bindings; ++i) { @@ -1801,17 +2262,6 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMappingForNameAndGUID(const char * SDL_AssertJoysticksLocked(); mapping = SDL_PrivateGetGamepadMappingForGUID(guid, false); -#ifdef SDL_PLATFORM_LINUX - if (!mapping && name) { - if (SDL_strstr(name, "Xbox 360 Wireless Receiver")) { - // The Linux driver xpad.c maps the wireless dpad to buttons - bool existing; - mapping = SDL_PrivateAddMappingForGUID(guid, - "none,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT); - } - } -#endif // SDL_PLATFORM_LINUX return mapping; } @@ -1938,14 +2388,25 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id return mapping; } +static bool SDL_PrivateIsGamepadPlatformMatch(const char *platform, size_t platform_len) +{ +#ifdef SDL_PLATFORM_MACOS + // We also accept the older SDL2 platform name for macOS + if (SDL_strncasecmp(platform, "Mac OS X", platform_len) == 0) { + return true; + } +#endif + + return SDL_strncasecmp(platform, SDL_GetPlatform(), platform_len) == 0; +} + /* * Add or update an entry into the Mappings Database */ int SDL_AddGamepadMappingsFromIO(SDL_IOStream *src, bool closeio) { - const char *platform = SDL_GetPlatform(); int gamepads = 0; - char *buf, *line, *line_end, *tmp, *comma, line_platform[64]; + char *buf, *line, *line_end, *tmp, *comma, *platform; size_t db_size; size_t platform_len; @@ -1974,13 +2435,11 @@ int SDL_AddGamepadMappingsFromIO(SDL_IOStream *src, bool closeio) tmp += SDL_GAMEPAD_PLATFORM_FIELD_SIZE; comma = SDL_strchr(tmp, ','); if (comma) { - platform_len = comma - tmp + 1; - if (platform_len + 1 < SDL_arraysize(line_platform)) { - SDL_strlcpy(line_platform, tmp, platform_len); - if (SDL_strncasecmp(line_platform, platform, platform_len) == 0 && - SDL_AddGamepadMapping(line) > 0) { - gamepads++; - } + platform = tmp; + platform_len = comma - platform; + if (SDL_PrivateIsGamepadPlatformMatch(platform, platform_len) && + SDL_AddGamepadMapping(line) > 0) { + gamepads++; } } } @@ -1998,7 +2457,11 @@ int SDL_AddGamepadMappingsFromIO(SDL_IOStream *src, bool closeio) int SDL_AddGamepadMappingsFromFile(const char *file) { - return SDL_AddGamepadMappingsFromIO(SDL_IOFromFile(file, "rb"), true); + SDL_IOStream *stream = SDL_IOFromFile(file, "rb"); + if (!stream) { + return -1; + } + return SDL_AddGamepadMappingsFromIO(stream, true); } bool SDL_ReloadGamepadMappings(void) @@ -2111,7 +2574,7 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa SDL_AssertJoysticksLocked(); - if (!mappingString) { + CHECK_PARAM(!mappingString) { SDL_InvalidParamError("mappingString"); return -1; } @@ -2195,7 +2658,11 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa } } else { - value = SDL_GetHintBoolean(hint, default_value); + const char *hint_value = SDL_GetHint(hint); + if (!hint_value) { + hint_value = SDL_getenv_unsafe(hint); + } + value = SDL_GetStringBoolean(hint_value, default_value); if (negate) { value = !value; } @@ -2207,7 +2674,7 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa } } -#ifdef ANDROID +#ifdef SDL_PLATFORM_ANDROID { // Extract and verify the SDK version const char *tmp; @@ -2446,7 +2913,7 @@ bool SDL_SetGamepadMapping(SDL_JoystickID instance_id, const char *mapping) SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id); bool result = false; - if (SDL_memcmp(&guid, &s_zeroGUID, sizeof(guid)) == 0) { + CHECK_PARAM(SDL_memcmp(&guid, &s_zeroGUID, sizeof(guid)) == 0) { return SDL_InvalidParamError("instance_id"); } @@ -2559,6 +3026,10 @@ bool SDL_InitGamepads(void) SDL_gamepads_initialized = true; + SDL_LockJoysticks(); + + SDL_gamepad_names = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, SDL_DestroyHashValue, NULL); + // Watch for joystick events and fire gamepad ones if needed SDL_AddEventWatch(SDL_GamepadEventWatcher, NULL); @@ -2573,6 +3044,8 @@ bool SDL_InitGamepads(void) SDL_free(joysticks); } + SDL_UnlockJoysticks(); + return true; } @@ -2619,19 +3092,10 @@ SDL_JoystickID *SDL_GetGamepads(int *count) const char *SDL_GetGamepadNameForID(SDL_JoystickID instance_id) { - const char *result = NULL; + const char *result; SDL_LockJoysticks(); - { - GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, true); - if (mapping) { - if (SDL_strcmp(mapping->name, "*") == 0) { - result = SDL_GetJoystickNameForID(instance_id); - } else { - result = SDL_GetPersistentString(mapping->name); - } - } - } + result = SDL_UpdateGamepadNameForID(instance_id); SDL_UnlockJoysticks(); return result; @@ -2736,7 +3200,7 @@ char *SDL_GetGamepadMappingForID(SDL_JoystickID instance_id) } /* - * Return 1 if the joystick with this name and GUID is a supported gamepad + * Return true if the joystick with this name and GUID is a supported gamepad */ bool SDL_IsGamepadNameAndGUID(const char *name, SDL_GUID guid) { @@ -2756,7 +3220,7 @@ bool SDL_IsGamepadNameAndGUID(const char *name, SDL_GUID guid) } /* - * Return 1 if the joystick at this device index is a supported gamepad + * Return true if the joystick at this device index is a supported gamepad */ bool SDL_IsGamepad(SDL_JoystickID instance_id) { @@ -2765,7 +3229,8 @@ bool SDL_IsGamepad(SDL_JoystickID instance_id) SDL_LockJoysticks(); { const void *value; - if (SDL_FindInHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, &value)) { + if (s_gamepadInstanceIDs && + SDL_FindInHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, &value)) { result = (bool)(uintptr_t)value; } else { if (SDL_PrivateGetGamepadMapping(instance_id, true) != NULL) { @@ -2773,11 +3238,12 @@ bool SDL_IsGamepad(SDL_JoystickID instance_id) } else { result = false; } - if (!s_gamepadInstanceIDs) { s_gamepadInstanceIDs = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, NULL, NULL); } - SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, (void *)(uintptr_t)result, true); + if (s_gamepadInstanceIDs) { + SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, (void *)(uintptr_t)result, true); + } } } SDL_UnlockJoysticks(); @@ -2786,46 +3252,54 @@ bool SDL_IsGamepad(SDL_JoystickID instance_id) } /* - * Return 1 if the gamepad should be ignored by SDL + * Return true if the gamepad should be ignored by SDL */ bool SDL_ShouldIgnoreGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) { -#ifdef SDL_PLATFORM_LINUX - if (SDL_endswith(name, " Motion Sensors")) { - // Don't treat the PS3 and PS4 motion controls as a separate gamepad - return true; - } - if (SDL_strncmp(name, "Nintendo ", 9) == 0 && SDL_strstr(name, " IMU") != NULL) { - // Don't treat the Nintendo IMU as a separate gamepad - return true; - } - if (SDL_endswith(name, " Accelerometer") || - SDL_endswith(name, " IR") || - SDL_endswith(name, " Motion Plus") || - SDL_endswith(name, " Nunchuk")) { - // Don't treat the Wii extension controls as a separate gamepad - return true; - } -#endif + if (name) { + for (int i = 0; i < SDL_arraysize(SDL_gamepad_blacklist_words); i++) { + const struct SDL_GamepadBlacklistWords *blacklist_word = &SDL_gamepad_blacklist_words[i]; - if (name && SDL_strcmp(name, "uinput-fpc") == 0) { - // The Google Pixel fingerprint sensor reports itself as a joystick - return true; + switch (blacklist_word->pos) { + case GAMEPAD_BLACKLIST_BEGIN: + if (SDL_startswith(name, blacklist_word->str)) { + return true; + } + break; + + case GAMEPAD_BLACKLIST_END: + if (SDL_endswith(name, blacklist_word->str)) { + return true; + } + break; + + case GAMEPAD_BLACKLIST_ANYWHERE: + if (SDL_strstr(name, blacklist_word->str) != NULL) { + if (SDL_startswith(name, "PG-")) { + // Ipega gamepads have modes with keyboard keys in addition to gamepad controls + break; + } + return true; + } + break; + } + } } + const char *hint = SDL_getenv_unsafe("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD"); + bool allow_steam_virtual_gamepad = SDL_GetStringBoolean(hint, false); #ifdef SDL_PLATFORM_WIN32 - if (SDL_GetHintBoolean("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", false) && - SDL_GetHintBoolean("STEAM_COMPAT_PROTON", false)) { - // We are launched by Steam and running under Proton + if (allow_steam_virtual_gamepad && WIN_IsWine()) { + // We are launched by Steam and running under Proton or Wine // We can't tell whether this controller is a Steam Virtual Gamepad, - // so assume that Proton is doing the appropriate filtering of controllers + // so assume that is doing the appropriate filtering of controllers // and anything we see here is fine to use. return false; } #endif // SDL_PLATFORM_WIN32 if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) { - return !SDL_GetHintBoolean("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", false); + return !allow_steam_virtual_gamepad; } if (SDL_allowed_gamepads.num_included_entries > 0) { @@ -3884,6 +4358,11 @@ void SDL_QuitGamepads(void) SDL_CloseGamepad(SDL_gamepads); } + if (SDL_gamepad_names) { + SDL_DestroyHashTable(SDL_gamepad_names); + SDL_gamepad_names = NULL; + } + SDL_UnlockJoysticks(); } diff --git a/libs/SDL3/src/joystick/SDL_gamepad_c.h b/libs/SDL3/src/joystick/SDL_gamepad_c.h index f1b1d10e..ff657438 100644 --- a/libs/SDL3/src/joystick/SDL_gamepad_c.h +++ b/libs/SDL3/src/joystick/SDL_gamepad_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/SDL_gamepad_db.h b/libs/SDL3/src/joystick/SDL_gamepad_db.h index 94b164f0..20674605 100644 --- a/libs/SDL3/src/joystick/SDL_gamepad_db.h +++ b/libs/SDL3/src/joystick/SDL_gamepad_db.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -46,6 +46,7 @@ static const char *s_GamepadMappings[] = { #endif #ifdef SDL_JOYSTICK_DINPUT "03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,", + "03000000c82d00001930000000000000,8BitDo 64 Bluetooth Controller,a:b0,b:b1,back:-a5,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a2,misc1:b17,misc2:+a3,rightshoulder:b7,righttrigger:b9,start:b11,x:+a5,y:-a3,", "03000000c82d00000090000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001038000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000650000000000000,8BitDo M30 Gamepad,a:b0,b:b1,back:b10,guide:b2,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,", @@ -339,6 +340,7 @@ static const char *s_GamepadMappings[] = { "03000000172700004431000000000000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,", "03000000c0160000e105000000000000,Xin-Mo Dual Arcade,crc:2246,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,", /* Ultimate Atari Fight Stick */ "03000000790000004f18000000000000,ZD-T Android,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000073500000400000000000000,ZENAIM ARCADE CONTROLLER,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,", "03000000120c0000101e000000000000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", "03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", @@ -347,6 +349,7 @@ static const char *s_GamepadMappings[] = { "03000000101c0000171c000000000000,uRage Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", #endif #ifdef SDL_PLATFORM_MACOS + "03000000c82d00001930000000000000,8BitDo 64 Bluetooth Controller,a:b0,b:b1,back:-a3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b17,misc2:+a2,misc3:b10,rightshoulder:b7,righttrigger:b9,start:b11,x:+a3,y:-a2,", "03000000c82d00000090000001000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000650000001000000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a5,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", @@ -481,12 +484,15 @@ static const char *s_GamepadMappings[] = { "030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "03000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,", "03000000c0160000e105000000040000,Xin-Mo Dual Arcade,crc:82d5,a:b2,b:b4,back:b18,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,rightshoulder:b8,righttrigger:b10,start:b16,x:b0,y:b6,", /* Ultimate Atari Fight Stick */ + "03000000073500000400000000010000,ZENAIM ARCADE CONTROLLER,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,", "03000000120c0000100e000000010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000120c0000101e000000010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000830500006020000000010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,", #endif #if defined(SDL_JOYSTICK_LINUX) || defined(SDL_PLATFORM_OPENBSD) + "03000000c82d00001930000011010000,8BitDo 64 Bluetooth Controller,a:b0,b:b1,back:-a3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b17,misc2:+a2,misc3:b10,rightshoulder:b7,righttrigger:a4,start:b11,x:+a3,y:-a2,", + "05000000c82d00001930000001000000,8BitDo 64 Bluetooth Controller,a:b0,b:b1,back:-a3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b17,misc2:+a2,misc3:b10,rightshoulder:b7,righttrigger:a4,start:b11,x:+a3,y:-a2,", "03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00005106000000010000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", @@ -498,16 +504,14 @@ static const char *s_GamepadMappings[] = { "03000000c82d00000190000011010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000203800000900000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00002038000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000020000000000000,8BitDo Pro 2 Wired Controller for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "06000000c82d00000020000006010000,8BitDo Pro 2 Wired Controller for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000c82d00000660000011010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000960000011010000,8BitDo Pro 3,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b17,paddle2:b16,paddle3:b2,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00000061000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000102800000900000000010000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00003028000000010000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000260000011010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000160000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000003512000020ab000010010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000202800000900000000010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00001b30000001000000,8BitDo Ultimate 2C Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", /* Bluetooth */ @@ -524,9 +528,6 @@ static const char *s_GamepadMappings[] = { "05000000050b00000045000040000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,", "03000000050b00000579000011010000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b36,paddle1:b52,paddle2:b53,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "05000000050b00000679000000010000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b21,paddle1:b22,paddle2:b23,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", - "030000006f0e00003901000020060000,Afterglow Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000006f0e00003901000000430000,Afterglow Prismatic Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000006f0e00001302000000010000,Afterglow,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "05000000491900000204000021000000,Amazon Fire Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b17,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "03000000491900001904000011010000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,", @@ -537,7 +538,6 @@ static const char *s_GamepadMappings[] = { "03000000503200000210000000000000,Atari Game Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a2,righty:a3,start:b8,x:b2,y:b3,", "05000000503200000210000000000000,Atari Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,", "05000000503200000210000000000000128804098,Atari Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,", - "030000005e0400008e02000047010000,Atari Xbox 360 Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000c62400001b89000011010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "03000000d62000002a79000011010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000120c0000f70e000011010000,Brook Universal Fighting Board,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:,lefty:,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:,righty:,start:b9,x:b0,y:b3,", @@ -549,7 +549,6 @@ static const char *s_GamepadMappings[] = { "050000004c050000f20d000000010000,DualSense Edge Wireless Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000790000001100000010010000,Elecom Gamepad,crc:e86c,a:b2,b:b3,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b0,y:b1,", - "0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "0500000047532067616d657061640000,GS Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", "03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b: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,", @@ -558,16 +557,13 @@ static const char *s_GamepadMappings[] = { "03000000ac0500001a06000011010000,GameSir-T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", "03000000c01100000140000011010000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", - "030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000008f0e00000800000010010000,Gasia Co. Ltd PS(R) Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", - "030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000d11800000094000011010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", "05000000d11800000094000000010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", "03000000280400000140000000010000,Gravis Gamepad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,", "030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", - "03000000c9110000f055000011010000,HJC Game GAMEPAD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", "030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", @@ -581,30 +577,23 @@ static const char *s_GamepadMappings[] = { "030000000d0f0000aa00000011010000,HORI Real Arcade Pro,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", "030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", - "030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,", "030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "030000000d0f00008600000002010000,Hori Fighting Commander,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", - "03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,", - "03000000242e00008816000001010000,Hyperkin X91,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000d80400008200000003000000,IMS PCU#0 Gamepad Interface,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b5,x:b3,y:b2,", "03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,", "05000000491900000204000000000000,Ipega PG-9087S,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,", "03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,", "03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,", - "030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000006d040000d1ca000011010000,Logitech Chillstream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", - "030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", /* Guide button doesn't seem to be sent in DInput mode. */ - "030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,", "03000000c62400002b89000011010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", @@ -616,23 +605,12 @@ static const char *s_GamepadMappings[] = { "03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000380700008433000011010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000380700008483000011010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", - "03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,", - "03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", - "03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,", "030000005e0400000e00000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,", - "030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,", - "030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,", - "030000005e0400008902000020010000,Microsoft Xbox Controller S,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,", "05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", "030000006b140000010c000010010000,NACON GC-400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", "030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,", @@ -641,6 +619,7 @@ 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,", + "0300000085320000030c000011010000,Nacon GC100,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", "03000000790000004318000010010000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", "03000000790000004418000010010000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", "030000007e0500001920000011810000,Nintendo N64 Controller,crc:d670,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b7,righttrigger:b3,start:b11,x:b4,y:b10,", @@ -665,9 +644,7 @@ static const char *s_GamepadMappings[] = { "030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,", "05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,", "05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,", - "030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,", "03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", - "030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000006f0e00000901000011010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,", "03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", @@ -701,8 +678,6 @@ static const char *s_GamepadMappings[] = { "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,", "03000000222c00000225000011010000,Qanba Dragon Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000222c00000025000011010000,Qanba Dragon Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", @@ -711,25 +686,18 @@ static const char *s_GamepadMappings[] = { "03000000222c00000020000011010000,Qanba Drone Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,", "03000000222c00000223000011010000,Qanba Obsidian Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000222c00000023000011010000,Qanba Obsidian Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", - "030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000321500000204000011010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000321500000104000011010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000321500000507000000010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "03000000321500000011000011010000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", - "030000008916000000fe000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "03000000c6240000045d000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", "050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", - "0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,", "03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000006b140000130d000011010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "030000006f0e00001e01000011010000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", - "030000006f0e00004601000001010000,Rock Candy Xbox One Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000006f0e00001f01000000010000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000632500007505000010010000,SHANWAN PS3/PC Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", "03000000341a00000908000010010000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", "03000000457500002211000010010000,SZMY-POWER PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", @@ -743,8 +711,6 @@ static const char *s_GamepadMappings[] = { "03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", "03000000632500002305000010010000,ShanWan USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", "03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,", - "030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", "03000000de2800000112000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,", "03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", @@ -755,12 +721,11 @@ static const char *s_GamepadMappings[] = { "05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", "05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", "05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", - "03000000de2800000512000000016800,Steam Deck Controller,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,misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "03000000de2800000512000000016800,Steam Deck Controller,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,misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,touchpad:b17,misc2:b16,misc3:b19,misc4:b18,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", "03000000de2800000512000011010000,Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:a9,leftx:a0,lefty:a1,misc1:b2,paddle1:b21,paddle2:b20,paddle3:b23,paddle4:b22,rightshoulder:b8,rightstick:b15,righttrigger:a8,rightx:a2,righty:a3,start:b12,x:b5,y:b6,", "03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "0500000011010000311400001b010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b32,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "05000000110100001914000009010000,SteelSeries Stratus XL,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b18,leftshoulder:b6,leftstick:b13,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:+a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", - "03000000ad1b000038f0000090040000,Street Fighter IV FightStick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,", "0300000000f00000f100000000010000,Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,", "030000004f0400000ed0000011010000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", @@ -781,23 +746,18 @@ static const char *s_GamepadMappings[] = { "030000006f0e00000702000011010000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "030000000d0f0000ab01000011010000,Wireless HORIPAD For Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc2:b2,misc3:b16,misc4:b17,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "050000000d0f00009601000091000000,Wireless HORIPAD For Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc2:b2,misc3:b16,misc4:b17,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", - "030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", "0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,", - "030000005e040000a102000014010000,Xbox 360 Wireless Receiver (XBOX),a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", "050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,", "03000000c0160000e105000010010000,Xin-Mo Dual Arcade,crc:82d5,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,", /* Ultimate Atari Fight Stick */ + "03000000073500000400000011010000,ZENAIM ARCADE CONTROLLER,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,", "03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000120c0000101e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000120c0000182e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000790000002201000011010000,ZhiXu GuliKit Controller D,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", "03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,", "03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", @@ -857,8 +817,8 @@ static const char *s_GamepadMappings[] = { "050000005e040000fd020000ff7f3f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", "050000005e040000e00200000ffe3f80,Xbox One Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a2,righty:a3,start:b10,x:b17,y:b2,", "050000005e040000fd020000ffff3f00,Xbox One Wireless Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", - "050000005e040000120b000000783f80,Xbox Series X Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", - "050000005e040000130b0000ffff3f00,Xbox Series X Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005e040000120b000000783f80,Xbox Series X Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005e040000130b0000ffff3f00,Xbox Series X Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", "050000005e04000091020000ff073f80,Xbox Wireless Controller,a:b0,b:b1,back:b4,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", /* The DPAD doesn't seem to work on this controller on Android TV? */ "050000001727000044310000ffff3f80,XiaoMi Game Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a4,righty:a5,start:b6,x:b2,y:b3,", "0500000083050000602000000ffe0000,iBuffalo SNES Controller,a:b1,b:b0,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b15,rightshoulder:b16,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", @@ -888,6 +848,8 @@ static const char *s_GamepadMappings[] = { "050000004c050000cc090000ff870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,", "050000004c050000e60c0000df870000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,touchpad:b10,x:b2,y:b3,", "050000004c050000e60c0000ff870000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,", + "05000000ac050000040000008e586d04,PlayStation VR2 Sense Controller (L),crc:9342,+leftx:+a3,+lefty:+a1,-leftx:+a2,-lefty:+a4,back:b2,leftstick:b4,lefttrigger:+a7,paddle2:b3,x:b0,y:b1,", + "05000000ac050000040000000eb86d04,PlayStation VR2 Sense Controller (R),crc:334b,+rightx:+a3,+righty:+a1,-rightx:+a2,-righty:+a4,a:b0,b:b1,paddle1:b3,rightstick:b4,righttrigger:+a7,start:b2,", "05000000ac0500000300000043006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,", "050000005e040000050b0000df070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b10,paddle2:b12,paddle3:b11,paddle4:b13,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", "050000005e040000050b0000ff070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", @@ -897,7 +859,7 @@ static const char *s_GamepadMappings[] = { "050000005e040000e0020000ff070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", #endif #ifdef SDL_JOYSTICK_EMSCRIPTEN - "default,Standard Gamepad,a:b0,b:b1,back:b8,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b16,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "default,*,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", #endif #ifdef SDL_JOYSTICK_PS2 "0000000050533220436f6e74726f6c00,PS2 Controller,crc:ed87,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", diff --git a/libs/SDL3/src/joystick/SDL_joystick.c b/libs/SDL3/src/joystick/SDL_joystick.c index 0e9fcab3..02cc012f 100644 --- a/libs/SDL3/src/joystick/SDL_joystick.c +++ b/libs/SDL3/src/joystick/SDL_joystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -123,6 +123,7 @@ static bool SDL_joystick_being_added; static SDL_Joystick *SDL_joysticks SDL_GUARDED_BY(SDL_joystick_lock) = NULL; static int SDL_joystick_player_count SDL_GUARDED_BY(SDL_joystick_lock) = 0; static SDL_JoystickID *SDL_joystick_players SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static SDL_HashTable *SDL_joystick_names SDL_GUARDED_BY(SDL_joystick_lock) = NULL; static bool SDL_joystick_allows_background_events = false; static Uint32 initial_old_xboxone_controllers[] = { @@ -439,11 +440,20 @@ static Uint32 initial_blacklist_devices[] = { MAKE_VIDPID(0x04d9, 0x8009), // OBINLB USB-HID Keyboard (Anne Pro II) MAKE_VIDPID(0x04d9, 0xa292), // OBINLB USB-HID Keyboard (Anne Pro II) MAKE_VIDPID(0x04d9, 0xa293), // OBINLB USB-HID Keyboard (Anne Pro II) + MAKE_VIDPID(0x04f2, 0xa13c), // HP Deluxe Webcam KQ246AA + MAKE_VIDPID(0x0e6f, 0x018a), // PDP REALMz Wireless Controller for Switch, USB charging MAKE_VIDPID(0x1532, 0x0266), // Razer Huntsman V2 Analog, non-functional DInput device MAKE_VIDPID(0x1532, 0x0282), // Razer Huntsman Mini Analog, non-functional DInput device - MAKE_VIDPID(0x26ce, 0x01a2), // ASRock LED Controller MAKE_VIDPID(0x20d6, 0x0002), // PowerA Enhanced Wireless Controller for Nintendo Switch (charging port only) + MAKE_VIDPID(0x256c, 0x006d), // Huion Tablet_GS1331, Huion Tablet_GS1331 Touch Strip + MAKE_VIDPID(0x256c, 0x006e), // Huion Tablet Kamvas Pro 22, Huion Tablet Kamvas Pro 22 Touch Strip + MAKE_VIDPID(0x26ce, 0x01a2), // ASRock LED Controller + MAKE_VIDPID(0x3297, 0x1969), // Moonlander MK1 Keyboard + MAKE_VIDPID(0x3434, 0x0121), // Keychron Q3 System Control MAKE_VIDPID(0x3434, 0x0211), // Keychron K1 Pro System Control + MAKE_VIDPID(0x3434, 0x02a0), // Keychron K10 Pro System Control + MAKE_VIDPID(0x3434, 0x0353), // Keychron V5 System Control + MAKE_VIDPID(0x3434, 0xd030), // Keychron Link }; static SDL_vidpid_list blacklist_devices = { SDL_HINT_JOYSTICK_BLACKLIST_DEVICES, 0, 0, NULL, @@ -469,6 +479,8 @@ static Uint32 initial_flightstick_devices[] = { MAKE_VIDPID(0x10f5, 0x7084), // Turtle Beach VelocityOne MAKE_VIDPID(0x231d, 0x0126), // Gunfighter Mk.III 'Space Combat Edition' (right) MAKE_VIDPID(0x231d, 0x0127), // Gunfighter Mk.III 'Space Combat Edition' (left) + MAKE_VIDPID(0x3344, 0x4391), // VIRPIL Controls R-VPC Stick MT-50CM3 + MAKE_VIDPID(0x3344, 0x8390), // VIRPIL Controls L-VPC Stick MT-50CM3 MAKE_VIDPID(0x362c, 0x0001), // Yawman Arrow }; static SDL_vidpid_list flightstick_devices = { @@ -483,6 +495,7 @@ static Uint32 initial_gamecube_devices[] = { MAKE_VIDPID(0x0079, 0x1844), // DragonRise GameCube Controller Adapter MAKE_VIDPID(0x0079, 0x1846), // DragonRise GameCube Controller Adapter MAKE_VIDPID(0x057e, 0x0337), // Nintendo Wii U GameCube Controller Adapter + MAKE_VIDPID(0x057e, 0x2073), // Nintendo Switch 2 NSO GameCube Controller MAKE_VIDPID(0x0926, 0x8888), // Cyber Gadget GameCube Controller MAKE_VIDPID(0x0e6f, 0x0185), // PDP Wired Fight Pad Pro for Nintendo Switch MAKE_VIDPID(0x1a34, 0xf705), // GameCube {HuiJia USB box} @@ -515,6 +528,7 @@ static Uint32 initial_throttle_devices[] = { MAKE_VIDPID(0x044f, 0x0404), // HOTAS Warthog Throttle MAKE_VIDPID(0x0738, 0xa221), // Saitek Pro Flight X-56 Rhino Throttle MAKE_VIDPID(0x10f5, 0x7085), // Turtle Beach VelocityOne Throttle + MAKE_VIDPID(0x3344, 0x0196), // VIRPIL Controls VPC VMAX Prime Throttle }; static SDL_vidpid_list throttle_devices = { SDL_HINT_JOYSTICK_THROTTLE_DEVICES, 0, 0, NULL, @@ -593,6 +607,7 @@ static Uint32 initial_wheel_devices[] = { MAKE_VIDPID(0x346e, 0x0004), // Moza R5 Wheelbase MAKE_VIDPID(0x346e, 0x0005), // Moza R3 Wheelbase MAKE_VIDPID(0x346e, 0x0006), // Moza R12 Wheelbase + MAKE_VIDPID(0x36e6, 0x400f), // PXN VD6 Wheelbase }; static SDL_vidpid_list wheel_devices = { SDL_HINT_JOYSTICK_WHEEL_DEVICES, 0, 0, NULL, @@ -612,18 +627,18 @@ static SDL_vidpid_list zero_centered_devices = { false }; -#define CHECK_JOYSTICK_MAGIC(joystick, result) \ - if (!SDL_ObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK)) { \ - SDL_InvalidParamError("joystick"); \ - SDL_UnlockJoysticks(); \ - return result; \ +#define CHECK_JOYSTICK_MAGIC(joystick, result) \ + CHECK_PARAM(!SDL_ObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK)) { \ + SDL_InvalidParamError("joystick"); \ + SDL_UnlockJoysticks(); \ + return result; \ } -#define CHECK_JOYSTICK_VIRTUAL(joystick, result) \ - if (!joystick->is_virtual) { \ - SDL_SetError("joystick isn't virtual"); \ - SDL_UnlockJoysticks(); \ - return result; \ +#define CHECK_JOYSTICK_VIRTUAL(joystick, result) \ + CHECK_PARAM(!joystick->is_virtual) { \ + SDL_SetError("joystick isn't virtual"); \ + SDL_UnlockJoysticks(); \ + return result; \ } bool SDL_JoysticksInitialized(void) @@ -828,6 +843,8 @@ bool SDL_InitJoysticks(void) SDL_joysticks_initialized = true; + SDL_joystick_names = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, SDL_DestroyHashValue, NULL); + SDL_LoadVIDPIDList(&old_xboxone_controllers); SDL_LoadVIDPIDList(&arcadestick_devices); SDL_LoadVIDPIDList(&blacklist_devices); @@ -975,20 +992,54 @@ const SDL_SteamVirtualGamepadInfo *SDL_GetJoystickVirtualGamepadInfoForID(SDL_Jo /* * Get the implementation dependent name of a joystick */ -const char *SDL_GetJoystickNameForID(SDL_JoystickID instance_id) +static const char *SDL_UpdateJoystickNameForID(SDL_JoystickID instance_id) { SDL_JoystickDriver *driver; int device_index; - const char *name = NULL; + const char *current_name = NULL; const SDL_SteamVirtualGamepadInfo *info; - SDL_LockJoysticks(); + SDL_AssertJoysticksLocked(); + info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id); if (info) { - name = SDL_GetPersistentString(info->name); + current_name = info->name; } else if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { - name = SDL_GetPersistentString(driver->GetDeviceName(device_index)); + current_name = driver->GetDeviceName(device_index); } + + if (!SDL_joystick_names) { + return SDL_GetPersistentString(current_name); + } + + char *name = NULL; + bool found = SDL_FindInHashTable(SDL_joystick_names, (const void *)(uintptr_t)instance_id, (const void **)&name); + if (!current_name) { + if (!found) { + SDL_SetError("Joystick %" SDL_PRIu32 " not found", instance_id); + return NULL; + } + if (!name) { + // SDL_strdup() failed during insert + SDL_OutOfMemory(); + return NULL; + } + return name; + } + + if (!name || SDL_strcmp(name, current_name) != 0) { + name = SDL_strdup(current_name); + SDL_InsertIntoHashTable(SDL_joystick_names, (const void *)(uintptr_t)instance_id, name, true); + } + return name; +} + +const char *SDL_GetJoystickNameForID(SDL_JoystickID instance_id) +{ + const char *name; + + SDL_LockJoysticks(); + name = SDL_UpdateJoystickNameForID(instance_id); SDL_UnlockJoysticks(); return name; @@ -1038,6 +1089,8 @@ static bool SDL_JoystickAxesCenteredAtZero(SDL_Joystick *joystick) { // printf("JOYSTICK '%s' VID/PID 0x%.4x/0x%.4x AXES: %d\n", joystick->name, vendor, product, joystick->naxes); + SDL_AssertJoysticksLocked(); + if (joystick->naxes == 2) { // Assume D-pad or thumbstick style axes are centered at 0 return true; @@ -1654,9 +1707,17 @@ int SDL_GetNumJoystickHats(SDL_Joystick *joystick) */ int SDL_GetNumJoystickBalls(SDL_Joystick *joystick) { - CHECK_JOYSTICK_MAGIC(joystick, -1); + int result; - return joystick->nballs; + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, -1); + + result = joystick->nballs; + } + SDL_UnlockJoysticks(); + + return result; } /* @@ -2229,6 +2290,11 @@ void SDL_QuitJoysticks(void) SDL_QuitGamepadMappings(); + if (SDL_joystick_names) { + SDL_DestroyHashTable(SDL_joystick_names); + SDL_joystick_names = NULL; + } + SDL_joysticks_quitting = false; SDL_joysticks_initialized = false; @@ -2315,6 +2381,7 @@ void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id) SDL_JoystickDriver *driver; int device_index; int player_index = -1; + bool is_gamepad; SDL_AssertJoysticksLocked(); @@ -2337,6 +2404,8 @@ void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id) SDL_SetJoystickIDForPlayerIndex(player_index, instance_id); } + SDL_UpdateJoystickNameForID(instance_id); + { SDL_Event event; @@ -2349,9 +2418,12 @@ void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id) } } + // This might create an automatic gamepad mapping, so wait to send the event + is_gamepad = SDL_IsGamepad(instance_id); + SDL_joystick_being_added = false; - if (SDL_IsGamepad(instance_id)) { + if (is_gamepad) { SDL_PrivateGamepadAdded(instance_id); } } @@ -2363,7 +2435,7 @@ bool SDL_IsJoystickBeingAdded(void) void SDL_PrivateJoystickForceRecentering(SDL_Joystick *joystick) { - Uint8 i, j; + int i, j; Uint64 timestamp = SDL_GetTicksNS(); SDL_AssertJoysticksLocked(); @@ -2627,6 +2699,8 @@ static void SendSteamHandleUpdateEvents(void) SDL_Joystick *joystick; const SDL_SteamVirtualGamepadInfo *info; + SDL_AssertJoysticksLocked(); + // Check to see if any Steam handles changed for (joystick = SDL_joysticks; joystick; joystick = joystick->next) { bool changed = false; @@ -2667,7 +2741,7 @@ void SDL_UpdateJoysticks(void) Uint64 now; SDL_Joystick *joystick; - if (!SDL_WasInit(SDL_INIT_JOYSTICK)) { + if (!SDL_joysticks_initialized) { return; } @@ -2947,10 +3021,14 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons } else if (vendor == 0x0001 && product == 0x0001) { type = SDL_GAMEPAD_TYPE_STANDARD; - } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT) { + } else if (vendor == USB_VENDOR_NINTENDO && + (product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT || + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT)) { type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT; - } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT) { + } else if (vendor == USB_VENDOR_NINTENDO && + (product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT || + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT)) { if (name && SDL_strstr(name, "NES Controller") != NULL) { // We don't have a type for the Nintendo Online NES Controller type = SDL_GAMEPAD_TYPE_STANDARD; @@ -2965,7 +3043,9 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT; } - } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) { + } else if (vendor == USB_VENDOR_NINTENDO && + (product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR || + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR)) { type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR; } else if (forUI && SDL_IsJoystickGameCube(vendor, product)) { @@ -3155,7 +3235,9 @@ bool SDL_IsJoystickNintendoSwitchJoyConGrip(Uint16 vendor_id, Uint16 product_id) bool SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product_id) { - return vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; + return vendor_id == USB_VENDOR_NINTENDO && + (product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR || + product_id == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR); } bool SDL_IsJoystickGameCube(Uint16 vendor_id, Uint16 product_id) @@ -3201,9 +3283,33 @@ bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id) return vendor_id == USB_VENDOR_HORI && (product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER || product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT); } +bool SDL_IsJoystickSInputController(Uint16 vendor_id, Uint16 product_id) +{ + if (vendor_id == USB_VENDOR_RASPBERRYPI) { + if (product_id == USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC || + product_id == USB_PRODUCT_HANDHELDLEGEND_PROGCC || + product_id == USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE || + product_id == USB_PRODUCT_BONZIRICHANNEL_FIREBIRD || + product_id == USB_PRODUCT_VOIDGAMING_PS4FIREBIRD) { + return true; + } + } + return false; +} + bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id) { - return vendor_id == USB_VENDOR_FLYDIGI && product_id == USB_PRODUCT_FLYDIGI_GAMEPAD; + if (vendor_id == USB_VENDOR_FLYDIGI_V1) { + if (product_id == USB_PRODUCT_FLYDIGI_V1_GAMEPAD) { + return true; + } + } + if (vendor_id == USB_VENDOR_FLYDIGI_V2) { + if (product_id == USB_PRODUCT_FLYDIGI_V2_APEX || product_id == USB_PRODUCT_FLYDIGI_V2_VADER) { + return true; + } + } + return false; } bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id) @@ -3212,6 +3318,12 @@ bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id) return eType == k_eControllerType_SteamControllerNeptune; } +bool SDL_IsJoystickSteamTriton(Uint16 vendor_id, Uint16 product_id) +{ + EControllerType eType = GuessControllerType(vendor_id, product_id); + return eType == k_eControllerType_SteamControllerTriton; +} + bool SDL_IsJoystickXInput(SDL_GUID guid) { return (guid.data[14] == 'x') ? true : false; @@ -3222,6 +3334,11 @@ bool SDL_IsJoystickWGI(SDL_GUID guid) return (guid.data[14] == 'w') ? true : false; } +bool SDL_IsJoystickGameInput(SDL_GUID guid) +{ + return (guid.data[14] == 'g') ? true : false; +} + bool SDL_IsJoystickHIDAPI(SDL_GUID guid) { return (guid.data[14] == 'h') ? true : false; @@ -3315,6 +3432,10 @@ static SDL_JoystickType SDL_GetJoystickGUIDType(SDL_GUID guid) return (SDL_JoystickType)guid.data[15]; } + if (SDL_IsJoystickGameInput(guid)) { + return (SDL_JoystickType)guid.data[15]; + } + if (SDL_IsJoystickVIRTUAL(guid)) { return (SDL_JoystickType)guid.data[15]; } @@ -3778,9 +3899,7 @@ static void SDL_LoadVIDPIDListFromHint(const char *hint, int *num_entries, int * (*entries)[(*num_entries)++] = entry; } - if (file) { - SDL_free(file); - } + SDL_free(file); } void SDL_LoadVIDPIDListFromHints(SDL_vidpid_list *list, const char *included_list, const char *excluded_list) @@ -3858,9 +3977,15 @@ void SDL_LoadVIDPIDList(SDL_vidpid_list *list) if (list->included_hint_name) { included_list = SDL_GetHint(list->included_hint_name); + if (!included_list) { + included_list = SDL_getenv_unsafe(list->included_hint_name); + } } if (list->excluded_hint_name) { excluded_list = SDL_GetHint(list->excluded_hint_name); + if (!excluded_list) { + excluded_list = SDL_getenv_unsafe(list->excluded_hint_name); + } } SDL_LoadVIDPIDListFromHints(list, included_list, excluded_list); } diff --git a/libs/SDL3/src/joystick/SDL_joystick_c.h b/libs/SDL3/src/joystick/SDL_joystick_c.h index cbc33608..55665db6 100644 --- a/libs/SDL3/src/joystick/SDL_joystick_c.h +++ b/libs/SDL3/src/joystick/SDL_joystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -135,12 +135,18 @@ extern bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id); // Function to return whether a joystick is a HORI Steam controller extern bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id); +// Function to return whether a joystick is an SInput (Open Format) controller +extern bool SDL_IsJoystickSInputController(Uint16 vendor_id, Uint16 product_id); + // Function to return whether a joystick is a Flydigi controller extern bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id); // Function to return whether a joystick is a Steam Deck extern bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id); +// Function to return whether a joystick is a Steam Triton +extern bool SDL_IsJoystickSteamTriton(Uint16 vendor_id, Uint16 product_id); + // Function to return whether a joystick guid comes from the XInput driver extern bool SDL_IsJoystickXInput(SDL_GUID guid); diff --git a/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.c b/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.c index 6b253de3..7d1ef1a8 100644 --- a/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.c +++ b/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -23,6 +23,9 @@ #include "SDL_joystick_c.h" #include "SDL_steam_virtual_gamepad.h" +#ifdef SDL_PLATFORM_LINUX +#include "../core/unix/SDL_appid.h" +#endif #ifdef SDL_PLATFORM_WIN32 #include "../core/windows/SDL_windows.h" #else @@ -30,8 +33,6 @@ #include #endif -#define SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE "SteamVirtualGamepadInfo" - static char *SDL_steam_virtual_gamepad_info_file SDL_GUARDED_BY(SDL_joystick_lock) = NULL; static Uint64 SDL_steam_virtual_gamepad_info_file_mtime SDL_GUARDED_BY(SDL_joystick_lock) = 0; static Uint64 SDL_steam_virtual_gamepad_info_check_time SDL_GUARDED_BY(SDL_joystick_lock) = 0; @@ -132,8 +133,17 @@ void SDL_InitSteamVirtualGamepadInfo(void) return; } - file = SDL_GetHint(SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE); + file = SDL_getenv_unsafe("SteamVirtualGamepadInfo"); if (file && *file) { +#ifdef SDL_PLATFORM_LINUX + // Older versions of Wine will blacklist the Steam Virtual Gamepad if + // it appears to have the real controller's VID/PID, so ignore this. + const char *exe = SDL_GetExeName(); + if (exe && SDL_strcmp(exe, "wine64-preloader") == 0) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Wine launched by Steam, ignoring SteamVirtualGamepadInfo"); + return; + } +#endif SDL_steam_virtual_gamepad_info_file = SDL_strdup(file); } SDL_UpdateSteamVirtualGamepadInfo(); diff --git a/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.h b/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.h index 65696ea2..35135f5b 100644 --- a/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.h +++ b/libs/SDL3/src/joystick/SDL_steam_virtual_gamepad.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/SDL_sysjoystick.h b/libs/SDL3/src/joystick/SDL_sysjoystick.h index 041ebc3b..47ce42cd 100644 --- a/libs/SDL3/src/joystick/SDL_sysjoystick.h +++ b/libs/SDL3/src/joystick/SDL_sysjoystick.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/android/SDL_sysjoystick.c b/libs/SDL3/src/joystick/android/SDL_sysjoystick.c index 9a3402eb..265c32e3 100644 --- a/libs/SDL3/src/joystick/android/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/android/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -29,8 +29,10 @@ #include "../../events/SDL_keyboard_c.h" #include "../../core/android/SDL_android.h" #include "../hidapi/SDL_hidapijoystick_c.h" +#include "../usb_ids.h" #include "android/keycodes.h" +#include // As of platform android-14, android/keycodes.h is missing these defines #ifndef AKEYCODE_BUTTON_1 @@ -106,7 +108,8 @@ static int keycode_to_SDL(int keycode) button = SDL_GAMEPAD_BUTTON_GUIDE; break; case AKEYCODE_BUTTON_L2: - button = 15; + case AKEYCODE_MEDIA_RECORD: + button = SDL_GAMEPAD_BUTTON_MISC1; break; case AKEYCODE_BUTTON_R2: button = 16; @@ -169,6 +172,31 @@ static int keycode_to_SDL(int keycode) return button; } +static int scancode_to_SDL(int scancode) +{ + int button = 0; + switch (scancode) { + // D-Pad buttons on the left JoyCon + case BTN_DPAD_UP: + button = SDL_GAMEPAD_BUTTON_DPAD_UP; + break; + case BTN_DPAD_DOWN: + button = SDL_GAMEPAD_BUTTON_DPAD_DOWN; + break; + case BTN_DPAD_LEFT: + button = SDL_GAMEPAD_BUTTON_DPAD_LEFT; + break; + case BTN_DPAD_RIGHT: + button = SDL_GAMEPAD_BUTTON_DPAD_RIGHT; + break; + + default: + return -1; + } + SDL_assert(button < ANDROID_MAX_NBUTTONS); + return button; +} + static SDL_Scancode button_to_scancode(int button) { switch (button) { @@ -194,11 +222,14 @@ static SDL_Scancode button_to_scancode(int button) return SDL_SCANCODE_UNKNOWN; } -bool Android_OnPadDown(int device_id, int keycode) +bool Android_OnPadDown(int device_id, int keycode, int scancode) { Uint64 timestamp = SDL_GetTicksNS(); SDL_joylist_item *item; int button = keycode_to_SDL(keycode); + if (button < 0) { + button = scancode_to_SDL(scancode); + } if (button >= 0) { SDL_LockJoysticks(); item = JoystickByDeviceId(device_id); @@ -214,11 +245,14 @@ bool Android_OnPadDown(int device_id, int keycode) return false; } -bool Android_OnPadUp(int device_id, int keycode) +bool Android_OnPadUp(int device_id, int keycode, int scancode) { Uint64 timestamp = SDL_GetTicksNS(); SDL_joylist_item *item; int button = keycode_to_SDL(keycode); + if (button < 0) { + button = scancode_to_SDL(scancode); + } if (button >= 0) { SDL_LockJoysticks(); item = JoystickByDeviceId(device_id); @@ -305,7 +339,37 @@ bool Android_OnHat(int device_id, int hat_id, int x, int y) return false; } -void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, bool can_rumble) +void Android_OnJoySensor(int device_id, int sensor_type, Uint64 sensor_timestamp, float x, float y, float z) +{ + Uint64 timestamp = SDL_GetTicksNS(); + SDL_joylist_item *item; + SDL_SensorType sensor; + float data[3]; + + if (sensor_type == 1) { // Sensor.TYPE_ACCELEROMETER + sensor = SDL_SENSOR_ACCEL; + } else if (sensor_type == 4) { // Sensor.TYPE_GYROSCOPE + sensor = SDL_SENSOR_GYRO; + } else { + // Unsupported sensor + return; + } + + // The axes of sensor events and their signs are the same as SDL's, so no conversion required + data[0] = x; + data[1] = y; + data[2] = z; + + SDL_LockJoysticks(); + item = JoystickByDeviceId(device_id); + if (item && item->joystick) { + SDL_SendJoystickSensor(timestamp, item->joystick, sensor, sensor_timestamp, data, 3); + } + SDL_UnlockJoysticks(); +} + +void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, + bool can_rumble, bool has_rgb_led, bool has_accelerometer, bool has_gyroscope) { SDL_joylist_item *item; SDL_GUID guid; @@ -328,12 +392,17 @@ void Android_AddJoystick(int device_id, const char *name, const char *desc, int goto done; } + if (SDL_ShouldIgnoreJoystick(vendor_id, product_id, 0, name)) { + goto done; + } + #ifdef DEBUG_JOYSTICK SDL_Log("Joystick: %s, descriptor %s, vendor = 0x%.4x, product = 0x%.4x, %d axes, %d hats", name, desc, vendor_id, product_id, naxes, nhats); #endif - if (nhats > 0) { + if (nhats > 0 || (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT)) { // Hat is translated into DPAD buttons + // D-Pad on the left JoyCon is a special case, it's not recognized via keycodes or hats, only scancodes button_mask |= ((1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT) | @@ -376,6 +445,9 @@ void Android_AddJoystick(int device_id, const char *name, const char *desc, int item->naxes = naxes; item->nhats = nhats; item->can_rumble = can_rumble; + item->has_rgb_led = has_rgb_led; + item->has_accelerometer = has_accelerometer; + item->has_gyroscope = has_accelerometer; item->device_instance = SDL_GetNextObjectID(); if (!SDL_joylist_tail) { SDL_joylist = SDL_joylist_tail = item; @@ -577,6 +649,17 @@ static bool ANDROID_JoystickOpen(SDL_Joystick *joystick, int device_index) SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); } + if (item->has_rgb_led) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true); + } + + if (item->has_accelerometer) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 0.0f); + } + if (item->has_gyroscope) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 0.0f); + } + return true; } @@ -603,7 +686,15 @@ static bool ANDROID_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_r static bool ANDROID_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) { - return SDL_Unsupported(); + SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; + if (!item) { + return SDL_SetError("SetLED failed, device disconnected"); + } + if (!item->has_rgb_led) { + return SDL_Unsupported(); + } + Android_JNI_JoystickSetLED(item->device_id, red, green, blue); + return true; } static bool ANDROID_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) @@ -613,7 +704,15 @@ static bool ANDROID_JoystickSendEffect(SDL_Joystick *joystick, const void *data, static bool ANDROID_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) { - return SDL_Unsupported(); + SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; + if (!item) { + return SDL_SetError("SetSensorsEnabled failed, device disconnected"); + } + if (!item->has_accelerometer && !item->has_gyroscope) { + return SDL_Unsupported(); + } + Android_JNI_JoystickSetSensorsEnabled(item->device_id, enabled); + return true; } static void ANDROID_JoystickUpdate(SDL_Joystick *joystick) diff --git a/libs/SDL3/src/joystick/android/SDL_sysjoystick_c.h b/libs/SDL3/src/joystick/android/SDL_sysjoystick_c.h index febb2288..f99abb5e 100644 --- a/libs/SDL3/src/joystick/android/SDL_sysjoystick_c.h +++ b/libs/SDL3/src/joystick/android/SDL_sysjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -28,11 +28,13 @@ #include "../SDL_sysjoystick.h" -extern bool Android_OnPadDown(int device_id, int keycode); -extern bool Android_OnPadUp(int device_id, int keycode); +extern bool Android_OnPadDown(int device_id, int keycode, int scancode); +extern bool Android_OnPadUp(int device_id, int keycode, int scancode); extern bool Android_OnJoy(int device_id, int axisnum, float value); extern bool Android_OnHat(int device_id, int hat_id, int x, int y); -extern void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, bool can_rumble); +extern void Android_OnJoySensor(int device_id, int sensor_type, Uint64 sensor_timestamp, float x, float y, float z); +extern void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, + bool can_rumble, bool has_rgb_led, bool has_accelerometer, bool has_gyroscope); extern void Android_RemoveJoystick(int device_id); // A linked list of available joysticks @@ -46,6 +48,9 @@ typedef struct SDL_joylist_item int nbuttons, naxes, nhats; int dpad_state; bool can_rumble; + bool has_rgb_led; + bool has_accelerometer; + bool has_gyroscope; struct SDL_joylist_item *next; } SDL_joylist_item; diff --git a/libs/SDL3/src/joystick/apple/SDL_mfijoystick.m b/libs/SDL3/src/joystick/apple/SDL_mfijoystick.m index 001ef314..98756f2e 100644 --- a/libs/SDL3/src/joystick/apple/SDL_mfijoystick.m +++ b/libs/SDL3/src/joystick/apple/SDL_mfijoystick.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -27,6 +27,10 @@ #include "../usb_ids.h" #include "../../events/SDL_events_c.h" +#ifdef SDL_VIDEO_DRIVER_UIKIT +#include "../../video/uikit/SDL_uikitvideo.h" +#endif + #include "SDL_mfijoystick_c.h" @@ -158,6 +162,13 @@ static bool IsControllerSwitchJoyConPair(GCController *controller) } return false; } +static bool IsControllerNVIDIASHIELD(GCController *controller) +{ + if ([controller.vendorName hasPrefix:@"NVIDIA Controller"]) { + return true; + } + return false; +} static bool IsControllerStadia(GCController *controller) { if ([controller.vendorName hasPrefix:@"Stadia"]) { @@ -290,6 +301,21 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle Uint8 subtype = 0; const char *name = NULL; + // Technically we only want to do this when the controller is opened, but we ALSO + // want to do this when the controller is opened via HIDAPI or IOKit, and we don't + // have an easy way to know if those devices correspond to a specific GCController. + // + // Since the fact that we're initializing means that the application is likely to + // be opening available controllers, this is probably the right thing to do for now. + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { + for (id key in controller.physicalInputProfile.buttons) { + GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key]; + if ([button isBoundToSystemGesture]) { + button.preferredSystemGestureState = GCSystemGestureStateDisabled; + } + } + } + if (@available(macOS 11.3, iOS 14.5, tvOS 14.5, *)) { if (!GCController.shouldMonitorBackgroundEvents) { GCController.shouldMonitorBackgroundEvents = YES; @@ -302,19 +328,23 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle if (controller.vendorName) { name = controller.vendorName.UTF8String; + } else { + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if (controller.productCategory) { + name = controller.productCategory.UTF8String; + } + } } if (!name) { name = "MFi Gamepad"; } - device->name = SDL_CreateJoystickName(0, 0, NULL, name); - #ifdef DEBUG_CONTROLLER_PROFILE NSLog(@"Product name: %@\n", controller.vendorName); NSLog(@"Product category: %@\n", controller.productCategory); NSLog(@"Elements available:\n"); - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { NSDictionary *elements = controller.physicalInputProfile.elements; for (id key in controller.physicalInputProfile.buttons) { NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital"); @@ -333,6 +363,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle device->is_ps5 = IsControllerPS5(controller); device->is_switch_pro = IsControllerSwitchPro(controller); device->is_switch_joycon_pair = IsControllerSwitchJoyConPair(controller); + device->is_shield = IsControllerNVIDIASHIELD(controller); device->is_stadia = IsControllerStadia(controller); device->is_backbone_one = IsControllerBackboneOne(controller); device->is_switch_joyconL = IsControllerSwitchJoyConL(controller); @@ -344,11 +375,18 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle (device->is_ps5 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS5)) || (device->is_switch_pro && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO)) || (device->is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 0, "")) || + (device->is_shield && HIDAPI_IsDevicePresent(USB_VENDOR_NVIDIA, USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104, 0, "")) || (device->is_stadia && HIDAPI_IsDevicePresent(USB_VENDOR_GOOGLE, USB_PRODUCT_GOOGLE_STADIA_CONTROLLER, 0, "")) || (device->is_switch_joyconL && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT, 0, "")) || (device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, "")) || + (SDL_strstr(name, "GameCube Controller Adapter") && + (HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER, 0, "") || + HIDAPI_IsDevicePresent(USB_VENDOR_DRAGONRISE, USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1, 0, "") || + HIDAPI_IsDevicePresent(USB_VENDOR_DRAGONRISE, USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2, 0, "") || + HIDAPI_IsDevicePresent(USB_VENDOR_DRAGONRISE, USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3, 0, ""))) || (SDL_strcmp(name, "8Bitdo SN30 Pro") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO_BT, 0, ""))) || - (SDL_strcmp(name, "8BitDo Pro 2") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2_BT, 0, "")))) { + (SDL_strcmp(name, "8BitDo Pro 2") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2_BT, 0, ""))) || + (SDL_startswith(name, "8BitDo Ultimate 2 Wireless") && HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS, 0, ""))) { // The HIDAPI driver is taking care of this device return false; } @@ -364,7 +402,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle return false; } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) { device->has_dualshock_touchpad = TRUE; } @@ -388,12 +426,19 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle if (device->has_xbox_paddles) { // Assume Xbox One Elite Series 2 Controller unless/until GCController flows VID/PID product = USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH; + + // controller.vendorName returns a generic name for Xbox controllers ("Controller"), + // and controller.productCategory only returns "Xbox One" for those controllers, + // so we give them proper names based on the ones from SDL_gamepad_db.h + name = "Xbox One Elite 2 Controller"; } else if (device->has_xbox_share_button) { // Assume Xbox Series X Controller unless/until GCController flows VID/PID product = USB_PRODUCT_XBOX_SERIES_X_BLE; + name = "Xbox Series X Controller"; } else { // Assume Xbox One S Bluetooth Controller unless/until GCController flows VID/PID product = USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH; + name = "Xbox One Wireless Controller"; } } else if (device->is_ps4) { // Assume DS4 Slim unless/until GCController flows VID/PID @@ -419,7 +464,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle } else if (device->is_switch_joyconR) { vendor = USB_VENDOR_NINTENDO; product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT; - } else if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + } else if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { vendor = USB_VENDOR_APPLE; product = 4; subtype = 4; @@ -442,7 +487,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle return false; } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { NSDictionary *elements = controller.physicalInputProfile.elements; // Provide both axes and analog buttons as SDL axes @@ -482,7 +527,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle subtype = 4; #ifdef DEBUG_CONTROLLER_PROFILE - NSLog(@"Elements used:\n", controller.vendorName); + NSLog(@"Elements used:\n"); for (id key in device->buttons) { NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital"); } @@ -571,9 +616,11 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle // We don't know how to get input events from this device return false; } + + device->name = SDL_CreateJoystickName(0, 0, NULL, name); Uint16 signature; - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { signature = 0; signature = SDL_crc16(signature, device->name, SDL_strlen(device->name)); for (id key in device->axes) { @@ -727,7 +774,7 @@ static bool IOS_JoystickInit(void) } #ifdef SDL_PLATFORM_MACOS - if (@available(macOS 10.16, *)) { + if (@available(macOS 11.0, *)) { // Continue with initialization on macOS 11+ } else { return true; @@ -784,6 +831,10 @@ static bool IOS_JoystickInit(void) SDL_UnlockJoysticks(); }]; #endif // SDL_JOYSTICK_MFI + +#ifdef SDL_VIDEO_DRIVER_UIKIT + UIKit_SetGameControllerInteraction(true); +#endif } return true; @@ -889,7 +940,7 @@ static bool IOS_JoystickOpen(SDL_Joystick *joystick, int device_index) }; } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = joystick->hwdata->controller; GCMotion *motion = controller.motion; if (motion && motion.hasRotationRate) { @@ -900,7 +951,7 @@ static bool IOS_JoystickOpen(SDL_Joystick *joystick, int device_index) } } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = joystick->hwdata->controller; for (id key in controller.physicalInputProfile.buttons) { GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key]; @@ -910,7 +961,7 @@ static bool IOS_JoystickOpen(SDL_Joystick *joystick, int device_index) } } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = device->controller; if (controller.light) { SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true); @@ -971,7 +1022,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) Uint64 timestamp = SDL_GetTicksNS(); #ifdef DEBUG_CONTROLLER_STATE - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (controller.physicalInputProfile) { for (id key in controller.physicalInputProfile.buttons) { GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key]; @@ -997,7 +1048,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) } #endif // DEBUG_CONTROLLER_STATE - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { NSDictionary *elements = controller.physicalInputProfile.elements; NSDictionary *buttons = controller.physicalInputProfile.buttons; @@ -1015,13 +1066,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) int button = 0; for (id key in device->buttons) { - bool down; - if (button == device->pause_button_index) { - down = (device->pause_button_pressed > 0); - } else { - down = buttons[key].isPressed; - } - SDL_SendJoystickButton(timestamp, joystick, button++, down); + SDL_SendJoystickButton(timestamp, joystick, button++, buttons[key].isPressed); } } else if (controller.extendedGamepad) { bool isstack; @@ -1127,7 +1172,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) } } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (device->has_dualshock_touchpad) { GCControllerDirectionPad *dpad; @@ -1147,7 +1192,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) } } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCMotion *motion = controller.motion; if (motion && motion.sensorsActive) { float data[3]; @@ -1169,7 +1214,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) } } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCDeviceBattery *battery = controller.battery; if (battery) { SDL_PowerState state = SDL_POWERSTATE_UNKNOWN; @@ -1198,8 +1243,8 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) #ifdef SDL_JOYSTICK_MFI @interface SDL3_RumbleMotor : NSObject -@property(nonatomic, strong) CHHapticEngine *engine API_AVAILABLE(macos(10.16), ios(13.0), tvos(14.0)); -@property(nonatomic, strong) id player API_AVAILABLE(macos(10.16), ios(13.0), tvos(14.0)); +@property(nonatomic, strong) CHHapticEngine *engine API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0)); +@property(nonatomic, strong) id player API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0)); @property bool active; @end @@ -1210,7 +1255,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) - (void)cleanup { @autoreleasepool { - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (self.player != nil) { [self.player cancelAndReturnError:nil]; self.player = nil; @@ -1226,7 +1271,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) - (bool)setIntensity:(float)intensity { @autoreleasepool { - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { NSError *error = nil; CHHapticDynamicParameter *param; @@ -1273,7 +1318,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) } } -- (id)initWithController:(GCController *)controller locality:(GCHapticsLocality)locality API_AVAILABLE(macos(10.16), ios(14.0), tvos(14.0)) +- (id)initWithController:(GCController *)controller locality:(GCHapticsLocality)locality API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) { @autoreleasepool { NSError *error; @@ -1375,7 +1420,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) static SDL3_RumbleContext *IOS_JoystickInitRumble(GCController *controller) { @autoreleasepool { - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { SDL3_RumbleMotor *low_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftHandle]; SDL3_RumbleMotor *high_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightHandle]; SDL3_RumbleMotor *left_trigger_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftTrigger]; @@ -1402,7 +1447,7 @@ static bool IOS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumb return SDL_SetError("Controller is no longer connected"); } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (!device->rumble && device->controller && device->controller.haptics) { SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller); if (rumble) { @@ -1428,7 +1473,7 @@ static bool IOS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumbl return SDL_SetError("Controller is no longer connected"); } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (!device->rumble && device->controller && device->controller.haptics) { SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller); if (rumble) { @@ -1454,7 +1499,7 @@ static bool IOS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, U return SDL_SetError("Controller is no longer connected"); } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = device->controller; GCDeviceLight *light = controller.light; if (light) { @@ -1482,7 +1527,7 @@ static bool IOS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) return SDL_SetError("Controller is no longer connected"); } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = device->controller; GCMotion *motion = controller.motion; if (motion) { @@ -1533,7 +1578,7 @@ static void IOS_JoystickClose(SDL_Joystick *joystick) controller.controllerPausedHandler = nil; controller.playerIndex = -1; - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { for (id key in controller.physicalInputProfile.buttons) { GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key]; if ([button isBoundToSystemGesture]) { @@ -1575,6 +1620,10 @@ static void IOS_JoystickQuit(void) while (deviceList != NULL) { IOS_RemoveJoystickDevice(deviceList); } + +#ifdef SDL_VIDEO_DRIVER_UIKIT + UIKit_SetGameControllerInteraction(false); +#endif } numjoysticks = 0; @@ -1587,7 +1636,7 @@ static bool IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping * return false; } - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { int axis = 0; for (id key in device->axes) { if ([(NSString *)key isEqualToString:@"Left Thumbstick X Axis"] || @@ -1724,7 +1773,12 @@ bool IOS_SupportedHIDDevice(IOHIDDeviceRef device) return false; } - if (@available(macOS 10.16, *)) { + if (IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVirtualHIDevice)) == kCFBooleanTrue) { + // Steam virtual gamepads always have kIOHIDVirtualHIDevice property unlike real devices, and are also not supported as GCController + return false; + } + + if (@available(macOS 11.0, *)) { const int MAX_ATTEMPTS = 3; for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { if ([GCController supportsHIDDevice:device]) { @@ -1743,7 +1797,7 @@ bool IOS_SupportedHIDDevice(IOHIDDeviceRef device) /* NOLINTNEXTLINE(readability-non-const-parameter): getCString takes a non-const char* */ static void GetAppleSFSymbolsNameForElement(GCControllerElement *element, char *name) { - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (element) { [element.sfSymbolsName getCString:name maxLength:255 encoding:NSASCIIStringEncoding]; } @@ -1752,7 +1806,7 @@ static void GetAppleSFSymbolsNameForElement(GCControllerElement *element, char * static GCControllerDirectionPad *GetDirectionalPadForController(GCController *controller) { - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { return controller.physicalInputProfile.dpads[GCInputDirectionPad]; } @@ -1775,7 +1829,7 @@ const char *IOS_GetAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_Gamepad #ifdef SDL_JOYSTICK_MFI if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) { - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller; NSDictionary *elements = controller.physicalInputProfile.elements; switch (button) { @@ -1891,7 +1945,7 @@ const char *IOS_GetAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAx #ifdef SDL_JOYSTICK_MFI if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) { - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller; NSDictionary *elements = controller.physicalInputProfile.elements; switch (axis) { diff --git a/libs/SDL3/src/joystick/apple/SDL_mfijoystick_c.h b/libs/SDL3/src/joystick/apple/SDL_mfijoystick_c.h index 783b3f48..6e275c00 100644 --- a/libs/SDL3/src/joystick/apple/SDL_mfijoystick_c.h +++ b/libs/SDL3/src/joystick/apple/SDL_mfijoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -53,6 +53,7 @@ typedef struct joystick_hwdata bool is_switch_joycon_pair; bool is_switch_joyconL; bool is_switch_joyconR; + bool is_shield; bool is_stadia; bool is_backbone_one; int is_siri_remote; diff --git a/libs/SDL3/src/joystick/bsd/SDL_bsdjoystick.c b/libs/SDL3/src/joystick/bsd/SDL_bsdjoystick.c index b3fd3e9c..75716e3b 100644 --- a/libs/SDL3/src/joystick/bsd/SDL_bsdjoystick.c +++ b/libs/SDL3/src/joystick/bsd/SDL_bsdjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -710,10 +710,10 @@ static void BSD_JoystickUpdate(SDL_Joystick *joy) //default: // no-op } - SDL_PrivateJoystickHat(joy, 0, (dpad[0] * HAT_UP) | - (dpad[1] * HAT_DOWN) | - (dpad[2] * HAT_RIGHT) | - (dpad[3] * HAT_LEFT) ); + SDL_SendJoystickHat(timestamp, joy, 0, (dpad[0] * HAT_UP) | + (dpad[1] * HAT_DOWN) | + (dpad[2] * HAT_RIGHT) | + (dpad[3] * HAT_LEFT) ); #endif break; } diff --git a/libs/SDL3/src/joystick/controller_list.h b/libs/SDL3/src/joystick/controller_list.h index 63107389..e71dc782 100644 --- a/libs/SDL3/src/joystick/controller_list.h +++ b/libs/SDL3/src/joystick/controller_list.h @@ -21,7 +21,6 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x0079, 0x181a ), k_eControllerType_PS3Controller, NULL }, // Venom Arcade Stick - { MAKE_CONTROLLER_ID( 0x0079, 0x1844 ), k_eControllerType_PS3Controller, NULL }, // From SDL { MAKE_CONTROLLER_ID( 0x044f, 0xb315 ), k_eControllerType_PS3Controller, NULL }, // Firestorm Dual Analog 3 { MAKE_CONTROLLER_ID( 0x044f, 0xd007 ), k_eControllerType_PS3Controller, NULL }, // Thrustmaster wireless 3-1 { MAKE_CONTROLLER_ID( 0x046d, 0xcad1 ), k_eControllerType_PS3Controller, NULL }, // Logitech Chillstream @@ -61,10 +60,34 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x0f0d, 0x0088 ), k_eControllerType_PS3Controller, NULL }, // HORI Fighting Stick mini 4 { MAKE_CONTROLLER_ID( 0x0f30, 0x1100 ), k_eControllerType_PS3Controller, NULL }, // Qanba Q1 fight stick { MAKE_CONTROLLER_ID( 0x11ff, 0x3331 ), k_eControllerType_PS3Controller, NULL }, // SRXJ-PH2400 + { MAKE_CONTROLLER_ID( 0x12ba, 0x0100 ), k_eControllerType_PS3Controller, NULL }, // PS3 Guitar Hero Guitar + { MAKE_CONTROLLER_ID( 0x12ba, 0x0120 ), k_eControllerType_PS3Controller, NULL }, // PS3 Guitar Hero Drums + { MAKE_CONTROLLER_ID( 0x12ba, 0x0140 ), k_eControllerType_PS3Controller, NULL }, // PS3 DJ Hero Turntable + { MAKE_CONTROLLER_ID( 0x12ba, 0x0200 ), k_eControllerType_PS3Controller, NULL }, // PS3 Rock Band Guitar + { MAKE_CONTROLLER_ID( 0x12ba, 0x0210 ), k_eControllerType_PS3Controller, NULL }, // PS3 Rock Band Drums + { MAKE_CONTROLLER_ID( 0x12ba, 0x0218 ), k_eControllerType_PS3Controller, NULL }, // PS3 Midi Pro Adapter - Drums Mode + { MAKE_CONTROLLER_ID( 0x12ba, 0x074b ), k_eControllerType_PS3Controller, NULL }, // PS3 / Wii U Guitar Hero Live Guitar + { MAKE_CONTROLLER_ID( 0x12ba, 0x2330 ), k_eControllerType_PS3Controller, NULL }, // PS3 Rock Band Keyboard + { MAKE_CONTROLLER_ID( 0x12ba, 0x2338 ), k_eControllerType_PS3Controller, NULL }, // PS3 Midi Pro Adapter - Keyboard Mode + { MAKE_CONTROLLER_ID( 0x12ba, 0x2430 ), k_eControllerType_PS3Controller, NULL }, // PS3 Mustang Guitar + { MAKE_CONTROLLER_ID( 0x12ba, 0x2438 ), k_eControllerType_PS3Controller, NULL }, // PS3 Midi Pro Adapter - Mustang Guitar Mode + { MAKE_CONTROLLER_ID( 0x12ba, 0x2530 ), k_eControllerType_PS3Controller, NULL }, // PS3 Squire Guitar + { MAKE_CONTROLLER_ID( 0x12ba, 0x2538 ), k_eControllerType_PS3Controller, NULL }, // PS3 Midi Pro Adapter - Squire Guitar Mode { MAKE_CONTROLLER_ID( 0x1345, 0x1000 ), k_eControllerType_PS3Controller, NULL }, // PS2 ACME GA-D5 { MAKE_CONTROLLER_ID( 0x1345, 0x6005 ), k_eControllerType_PS3Controller, NULL }, // ps2 maybe break out later { MAKE_CONTROLLER_ID( 0x146b, 0x5500 ), k_eControllerType_PS3Controller, NULL }, // From SDL { MAKE_CONTROLLER_ID( 0x1a34, 0x0836 ), k_eControllerType_PS3Controller, NULL }, // Afterglow PS3 + { MAKE_CONTROLLER_ID( 0x1BAD, 0x0004 ), k_eControllerType_PS3Controller, NULL }, // Wii RB1 Guitar (Uses PS3 protocol) + { MAKE_CONTROLLER_ID( 0x1BAD, 0x0005 ), k_eControllerType_PS3Controller, NULL }, // Wii RB1 Drums (Uses PS3 protocol) + { MAKE_CONTROLLER_ID( 0x1BAD, 0x3010 ), k_eControllerType_PS3Controller, NULL }, // Wii RB2 Guitar (Uses PS3 protocol) + { MAKE_CONTROLLER_ID( 0x1BAD, 0x3110 ), k_eControllerType_PS3Controller, NULL }, // Wii RB2 Drums (Uses PS3 protocol) + { MAKE_CONTROLLER_ID( 0x1BAD, 0x3138 ), k_eControllerType_PS3Controller, NULL }, // Wii RB3 Midi Pro Adapter - Drums Mode (Uses PS3 protocol) + { MAKE_CONTROLLER_ID( 0x1BAD, 0x3330 ), k_eControllerType_PS3Controller, NULL }, // Wii RB3 Keyboard (Uses PS3 protocol) + { MAKE_CONTROLLER_ID( 0x1BAD, 0x3338 ), k_eControllerType_PS3Controller, NULL }, // Wii RB3 Midi Pro Adapter - Keyboard Mode (Uses PS3 protocol) + { MAKE_CONTROLLER_ID( 0x1BAD, 0x3430 ), k_eControllerType_PS3Controller, NULL }, // Wii RB3 Mustang Guitar (Uses PS3 protocol) + { MAKE_CONTROLLER_ID( 0x1BAD, 0x3438 ), k_eControllerType_PS3Controller, NULL }, // Wii RB3 Midi Pro Adapter - Mustang Guitar Mode (Uses PS3 protocol) + { MAKE_CONTROLLER_ID( 0x1BAD, 0x3530 ), k_eControllerType_PS3Controller, NULL }, // Wii RB3 Squire Guitar (Uses PS3 protocol) + { MAKE_CONTROLLER_ID( 0x1BAD, 0x3538 ), k_eControllerType_PS3Controller, NULL }, // Wii RB3 Midi Pro Adapter - Squire Guitar Mode (Uses PS3 protocol) { MAKE_CONTROLLER_ID( 0x20bc, 0x5500 ), k_eControllerType_PS3Controller, NULL }, // ShanWan PS3 { MAKE_CONTROLLER_ID( 0x20d6, 0x576d ), k_eControllerType_PS3Controller, NULL }, // Power A PS3 { MAKE_CONTROLLER_ID( 0x20d6, 0xca6d ), k_eControllerType_PS3Controller, NULL }, // BDA Pro Ex @@ -160,9 +183,12 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x1532, 0x100b ), k_eControllerType_PS5Controller, NULL }, // Razer Wolverine V2 Pro (Wired) { MAKE_CONTROLLER_ID( 0x1532, 0x100c ), k_eControllerType_PS5Controller, NULL }, // Razer Wolverine V2 Pro (Wireless) { MAKE_CONTROLLER_ID( 0x1532, 0x1012 ), k_eControllerType_PS5Controller, NULL }, // Razer Kitsune + { MAKE_CONTROLLER_ID( 0x1532, 0x1024 ), k_eControllerType_PS5Controller, NULL }, // Razer Raiju V5 Pro (PS5 mode wired) + { MAKE_CONTROLLER_ID( 0x1532, 0x1026 ), k_eControllerType_PS5Controller, NULL }, // Razer Raiju V5 Pro (PS5 mode with dongle) { MAKE_CONTROLLER_ID( 0x3285, 0x0d18 ), k_eControllerType_PS5Controller, NULL }, // NACON Revolution 5 Pro (PS5 mode with dongle) { MAKE_CONTROLLER_ID( 0x3285, 0x0d19 ), k_eControllerType_PS5Controller, NULL }, // NACON Revolution 5 Pro (PS5 mode wired) { MAKE_CONTROLLER_ID( 0x358a, 0x0104 ), k_eControllerType_PS5Controller, NULL }, // Backbone One PlayStation Edition for iOS + { MAKE_CONTROLLER_ID( 0x358a, 0x0304 ), k_eControllerType_PS5Controller, NULL }, // Backbone One PlayStation Edition Gen 2 { MAKE_CONTROLLER_ID( 0x0079, 0x0006 ), k_eControllerType_UnknownNonSteamController, NULL }, // DragonRise Generic USB PCB, sometimes configured as a PC Twin Shock Controller - looks like a DS3 but the face buttons are 1-4 instead of symbols @@ -303,6 +329,7 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x24c6, 0xfafb ), k_eControllerType_XBox360Controller, NULL }, // Aplay Controller { MAKE_CONTROLLER_ID( 0x24c6, 0xfafc ), k_eControllerType_XBox360Controller, NULL }, // Afterglow Gamepad 1 { MAKE_CONTROLLER_ID( 0x24c6, 0xfafd ), k_eControllerType_XBox360Controller, NULL }, // Afterglow Gamepad 3 + { MAKE_CONTROLLER_ID( 0x3537, 0x103e ), k_eControllerType_XBox360Controller, "Gamesir Xbox Controller" }, // Gamesir dongle for Supernova (and maybe others) in Xinput mode { MAKE_CONTROLLER_ID( 0x24c6, 0xfafe ), k_eControllerType_XBox360Controller, NULL }, // Rock Candy Gamepad for Xbox 360 { MAKE_CONTROLLER_ID( 0x03f0, 0x0495 ), k_eControllerType_XBoxOneController, NULL }, // HP HyperX Clutch Gladiate @@ -419,7 +446,7 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x20d6, 0x2012 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X Cuphead EnWired Controller - Mugman { MAKE_CONTROLLER_ID( 0x20d6, 0x2015 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Blue Hint { MAKE_CONTROLLER_ID( 0x20d6, 0x2016 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Green Hint - { MAKE_CONTROLLER_ID( 0x20d6, 0x2017 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Cntroller - Arctic Camo + { MAKE_CONTROLLER_ID( 0x20d6, 0x2017 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Arctic Camo { MAKE_CONTROLLER_ID( 0x20d6, 0x2018 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Arc Lightning { MAKE_CONTROLLER_ID( 0x20d6, 0x2019 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Royal Purple { MAKE_CONTROLLER_ID( 0x20d6, 0x201a ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Nebula @@ -436,14 +463,14 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x24c6, 0x592a ), k_eControllerType_XBoxOneController, NULL }, // BDA XB1 Spectra Pro { MAKE_CONTROLLER_ID( 0x24c6, 0x791a ), k_eControllerType_XBoxOneController, NULL }, // PowerA Fusion Fight Pad { MAKE_CONTROLLER_ID( 0x2dc8, 0x2002 ), k_eControllerType_XBoxOneController, NULL }, // 8BitDo Ultimate Wired Controller for Xbox - { MAKE_CONTROLLER_ID( 0x2dc8, 0x3106 ), k_eControllerType_XBoxOneController, NULL }, // 8Bitdo Ultimate Wired Controller. Windows, Android, Switch. + { MAKE_CONTROLLER_ID( 0x2dc8, 0x3106 ), k_eControllerType_XBoxOneController, NULL }, // 8BitDo Ultimate Wired Controller. Windows, Android, Switch. + { MAKE_CONTROLLER_ID( 0x2dc8, 0x310a ), k_eControllerType_XBoxOneController, NULL }, // 8BitDo Ultimate 2C Wireless Controller { MAKE_CONTROLLER_ID( 0x2e24, 0x0652 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin Duke { MAKE_CONTROLLER_ID( 0x2e24, 0x1618 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin Duke { MAKE_CONTROLLER_ID( 0x2e24, 0x1688 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin X91 { MAKE_CONTROLLER_ID( 0x146b, 0x0611 ), k_eControllerType_XBoxOneController, NULL }, // Xbox Controller Mode for NACON Revolution 3 // These have been added via Minidump for unrecognized Xinput controller assert - { MAKE_CONTROLLER_ID( 0x0000, 0x0000 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x045e, 0x02a2 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller - Microsoft VID { MAKE_CONTROLLER_ID( 0x0e6f, 0x1414 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x0e6f, 0x0159 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller @@ -469,7 +496,6 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x046d, 0x0291 ), k_eControllerType_XBox360Controller, NULL }, // logitech xinput { MAKE_CONTROLLER_ID( 0x0079, 0x18d3 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x0f0d, 0x00b1 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0x0001, 0x0001 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x0079, 0x188e ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x0079, 0x187c ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x0079, 0x189c ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller @@ -541,8 +567,11 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x05ac, 0x0002 ), k_eControllerType_AppleController, NULL }, // MFI Standard Gamepad (generic entry for iOS/tvOS) { MAKE_CONTROLLER_ID( 0x057e, 0x2006 ), k_eControllerType_SwitchJoyConLeft, NULL }, // Nintendo Switch Joy-Con (Left) + { MAKE_CONTROLLER_ID( 0x057e, 0x2067 ), k_eControllerType_SwitchJoyConLeft, NULL }, // Nintendo Switch 2 Joy-Con (Left) { MAKE_CONTROLLER_ID( 0x057e, 0x2007 ), k_eControllerType_SwitchJoyConRight, NULL }, // Nintendo Switch Joy-Con (Right) + { MAKE_CONTROLLER_ID( 0x057e, 0x2066 ), k_eControllerType_SwitchJoyConRight, NULL }, // Nintendo Switch 2 Joy-Con (Right) { MAKE_CONTROLLER_ID( 0x057e, 0x2008 ), k_eControllerType_SwitchJoyConPair, NULL }, // Nintendo Switch Joy-Con (Left+Right Combined) + { MAKE_CONTROLLER_ID( 0x057e, 0x2068 ), k_eControllerType_SwitchJoyConPair, NULL }, // Nintendo Switch 2 Joy-Con (Left+Right Combined) // This same controller ID is spoofed by many 3rd-party Switch controllers. // The ones we currently know of are: @@ -551,6 +580,7 @@ static const ControllerDescription_t arrControllers[] = { // * ZhiXu Gamepad Wireless // * Sunwaytek Wireless Motion Controller for Nintendo Switch { MAKE_CONTROLLER_ID( 0x057e, 0x2009 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Switch Pro Controller + { MAKE_CONTROLLER_ID( 0x057e, 0x2069 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Switch 2 Pro Controller //{ MAKE_CONTROLLER_ID( 0x057e, 0x2017 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online SNES Controller //{ MAKE_CONTROLLER_ID( 0x057e, 0x2019 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online N64 Controller //{ MAKE_CONTROLLER_ID( 0x057e, 0x201e ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online SEGA Genesis Controller @@ -575,6 +605,8 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x0e6f, 0x0186 ), k_eControllerType_SwitchProController, NULL }, // PDP Afterglow Wireless Switch Controller - working gyro. USB is for charging only. Many later "Wireless" line devices w/ gyro also use this vid/pid { MAKE_CONTROLLER_ID( 0x0e6f, 0x0187 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Rockcandy Wired Controller { MAKE_CONTROLLER_ID( 0x0e6f, 0x0188 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Afterglow Wired Deluxe+ Audio Controller + { MAKE_CONTROLLER_ID( 0x0e6f, 0x018b ), k_eControllerType_SwitchProController, NULL }, // PDP Afterglow Wave Wireless Controller for Switch + { MAKE_CONTROLLER_ID( 0x0e6f, 0x018c ), k_eControllerType_SwitchProController, "PDP REALMz Wireless Controller" }, // PDP REALMz Wireless Controller for Switch { MAKE_CONTROLLER_ID( 0x0f0d, 0x00aa ), k_eControllerType_SwitchInputOnlyController, NULL }, // HORI Real Arcade Pro V Hayabusa in Switch Mode { MAKE_CONTROLLER_ID( 0x20d6, 0xa711 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Wired Controller Plus/PowerA Wired Controller Nintendo GameCube Style { MAKE_CONTROLLER_ID( 0x20d6, 0xa712 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Fusion Fight Pad @@ -583,9 +615,10 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x20d6, 0xa715 ), k_eControllerType_SwitchInputOnlyController, NULL }, // Power A Fusion Wireless Arcade Stick (USB Mode) Over BT is shows up as 057e 2009 { MAKE_CONTROLLER_ID( 0x20d6, 0xa716 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Fusion Pro Controller - USB requires toggling switch on back of device { MAKE_CONTROLLER_ID( 0x20d6, 0xa718 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Nano Wired Controller - { MAKE_CONTROLLER_ID( 0x33dd, 0x0001 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Black - { MAKE_CONTROLLER_ID( 0x33dd, 0x0002 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch ?? - { MAKE_CONTROLLER_ID( 0x33dd, 0x0003 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Red + { MAKE_CONTROLLER_ID( 0x33dd, 0x0001 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Black + { MAKE_CONTROLLER_ID( 0x33dd, 0x0002 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch ?? + { MAKE_CONTROLLER_ID( 0x33dd, 0x0003 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Red + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00f0 ), k_eControllerType_SwitchInputOnlyController, NULL }, // HORI Taiko Controller For Switch // Valve products { MAKE_CONTROLLER_ID( 0x0000, 0x11fb ), k_eControllerType_MobileTouch, NULL }, // Streaming mobile touch virtual controls @@ -598,4 +631,8 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x28de, 0x1201 ), k_eControllerType_SteamControllerV2, NULL }, // Valve wired Steam Controller (HEADCRAB) { MAKE_CONTROLLER_ID( 0x28de, 0x1202 ), k_eControllerType_SteamControllerV2, NULL }, // Valve Bluetooth Steam Controller (HEADCRAB) { MAKE_CONTROLLER_ID( 0x28de, 0x1205 ), k_eControllerType_SteamControllerNeptune, NULL }, // Valve Steam Deck Builtin Controller + { MAKE_CONTROLLER_ID( 0x28de, 0x1302 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Triton Controller + { MAKE_CONTROLLER_ID( 0x28de, 0x1303 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Triton Controller (BLE) + { MAKE_CONTROLLER_ID( 0x28de, 0x1304 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Proteus Dongle (Proprietary) + { MAKE_CONTROLLER_ID( 0x28de, 0x1305 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Nereid Dongle (Proprietary) }; diff --git a/libs/SDL3/src/joystick/controller_type.h b/libs/SDL3/src/joystick/controller_type.h index 155c8ad1..bb94840a 100644 --- a/libs/SDL3/src/joystick/controller_type.h +++ b/libs/SDL3/src/joystick/controller_type.h @@ -39,6 +39,8 @@ typedef enum k_eControllerType_SteamControllerV2 = 3, k_eControllerType_SteamControllerNeptune = 4, + k_eControllerType_SteamControllerTriton = 10, + // Other Controllers k_eControllerType_UnknownNonSteamController = 30, k_eControllerType_XBox360Controller = 31, diff --git a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c index e87ab824..b88258f1 100644 --- a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c +++ b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -28,6 +28,8 @@ #include "../hidapi/SDL_hidapijoystick_c.h" #include "../../haptic/darwin/SDL_syshaptic_c.h" // For haptic hot plugging #include "../usb_ids.h" +#include "../../SDL_hints_c.h" +#include #define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick") @@ -442,6 +444,16 @@ static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const return slot; } +static bool IsControlledBy360ControllerDriver(IOHIDDeviceRef hidDevice) +{ + bool controlled_by_360controller = false; + io_service_t service = IOHIDDeviceGetService(hidDevice); + if (service != MACH_PORT_NULL) { + controlled_by_360controller = IOObjectConformsTo(service, "Xbox360ControllerClass"); + } + return controlled_by_360controller; +} + static bool GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice) { Sint32 vendor = 0; @@ -499,11 +511,18 @@ static bool GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice) CFNumberGetValue(refCF, kCFNumberSInt32Type, &version); } - if (SDL_IsJoystickXboxOne(vendor, product)) { - // We can't actually use this API for Xbox controllers + if (!IsControlledBy360ControllerDriver(hidDevice) && SDL_IsJoystickXboxOne(vendor, product)) { + // We can't actually use this API for Xbox controllers without the 360Controller driver return false; } + if (SDL_IsJoystickSteamVirtualGamepad(vendor, product, version)) { + if (IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVirtualHIDevice)) != kCFBooleanTrue) { + // This is a real Xbox 360 controller, adjust the version so it's not detected as a Steam virtual gamepad + version = 1; + } + } + // get device name refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey)); if ((!refCF) || (!CFStringGetCString(refCF, manufacturer_string, sizeof(manufacturer_string), kCFStringEncodingUTF8))) { @@ -550,7 +569,7 @@ static bool JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject) #ifdef SDL_JOYSTICK_MFI extern bool IOS_SupportedHIDDevice(IOHIDDeviceRef device); - if (IOS_SupportedHIDDevice(ioHIDDeviceObject)) { + if (!IsControlledBy360ControllerDriver(ioHIDDeviceObject) && IOS_SupportedHIDDevice(ioHIDDeviceObject)) { return true; } #endif diff --git a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h index 3a70d2bd..ec017eab 100644 --- a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h +++ b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/dummy/SDL_sysjoystick.c b/libs/SDL3/src/joystick/dummy/SDL_sysjoystick.c index 8240d452..113df7a4 100644 --- a/libs/SDL3/src/joystick/dummy/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/dummy/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick.c b/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick.c index b481d5da..627a3295 100644 --- a/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -27,6 +27,7 @@ #include "SDL_sysjoystick_c.h" #include "../SDL_joystick_c.h" +#include "../usb_ids.h" static SDL_joylist_item *JoystickByIndex(int index); @@ -34,10 +35,99 @@ static SDL_joylist_item *SDL_joylist = NULL; static SDL_joylist_item *SDL_joylist_tail = NULL; static int numjoysticks = 0; +static int SDL_GetEmscriptenJoystickVendor(int device_index) +{ + return MAIN_THREAD_EM_ASM_INT({ + let gamepad = navigator['getGamepads']()[$0]; + if (!gamepad) { + return 0; + } + + // Chrome, Edge, Opera: Wireless Controller (STANDARD GAMEPAD Vendor: 054c Product: 09cc) + let vendor_str = 'Vendor: '; + if (gamepad['id']['indexOf'](vendor_str) > 0) { + let vendor_str_index = gamepad['id']['indexOf'](vendor_str) + vendor_str['length']; + return parseInt(gamepad['id']['substr'](vendor_str_index, 4), 16); + } + + // Firefox, Safari: 046d-c216-Logitech Dual Action (or 46d-c216-Logicool Dual Action) + let id_split = gamepad['id']['split']('-'); + if (id_split['length'] > 1 && !isNaN(parseInt(id_split[0], 16))) { + return parseInt(id_split[0], 16); + } + + return 0; + }, device_index); +} + +static int SDL_GetEmscriptenJoystickProduct(int device_index) +{ + return MAIN_THREAD_EM_ASM_INT({ + let gamepad = navigator['getGamepads']()[$0]; + if (!gamepad) { + return 0; + } + + // Chrome, Edge, Opera: Wireless Controller (STANDARD GAMEPAD Vendor: 054c Product: 09cc) + let product_str = 'Product: '; + if (gamepad['id']['indexOf'](product_str) > 0) { + let product_str_index = gamepad['id']['indexOf'](product_str) + product_str['length']; + return parseInt(gamepad['id']['substr'](product_str_index, 4), 16); + } + + // Firefox, Safari: 046d-c216-Logitech Dual Action (or 46d-c216-Logicool Dual Action) + let id_split = gamepad['id']['split']('-'); + if (id_split['length'] > 1 && !isNaN(parseInt(id_split[1], 16))) { + return parseInt(id_split[1], 16); + } + + return 0; + }, device_index); +} + +static int SDL_IsEmscriptenJoystickXInput(int device_index) +{ + return MAIN_THREAD_EM_ASM_INT({ + let gamepad = navigator['getGamepads']()[$0]; + if (!gamepad) { + return 0; + } + + // Chrome, Edge, Opera: Xbox 360 Controller (XInput STANDARD GAMEPAD) + // Firefox: xinput + // TODO: Safari + return gamepad['id']['toLowerCase']()['indexOf']('xinput') >= 0; + }, device_index); +} + +static int SDL_GetEmscriptenOSID() +{ + return MAIN_THREAD_EM_ASM_INT({ + const os = ([ + 'Android', + 'Linux', + 'iPhone', + 'Macintosh', + 'Windows', + ]); + const ua = navigator['userAgent']; + for (let i = 0; i < os.length; i++) { + if (ua['indexOf'](os[i]) >= 0) { + return i + 1; + } + } + return 0; + }); +} + static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { SDL_joylist_item *item; int i; + Uint16 bus; + Uint16 vendor, product; + Uint8 os_id; + bool is_xinput; SDL_LockJoysticks(); @@ -53,12 +143,49 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep SDL_zerop(item); item->index = gamepadEvent->index; - item->name = SDL_CreateJoystickName(0, 0, NULL, gamepadEvent->id); + bus = SDL_HARDWARE_BUS_UNKNOWN; + vendor = SDL_GetEmscriptenJoystickVendor(gamepadEvent->index); + product = SDL_GetEmscriptenJoystickProduct(gamepadEvent->index); + is_xinput = SDL_IsEmscriptenJoystickXInput(gamepadEvent->index); + + // Use a generic VID/PID representing an XInput controller + if (!vendor && !product && is_xinput) { + vendor = USB_VENDOR_MICROSOFT; + product = USB_PRODUCT_XBOX360_XUSB_CONTROLLER; + } + + os_id = SDL_GetEmscriptenOSID(); + + if (os_id != 0) { + if (os_id == 1 || os_id == 3) { // Android or iOS (mobile) + bus = SDL_HARDWARE_BUS_BLUETOOTH; + } else { // Desktop + bus = SDL_HARDWARE_BUS_USB; + } + } + + if (SDL_strcmp(gamepadEvent->mapping, "standard") == 0) { + // We should differentiate between devices that are mapped or unmapped by the browser. + os_id += 0x80; + } + + item->name = SDL_CreateJoystickName(vendor, product, NULL, gamepadEvent->id); if (!item->name) { SDL_free(item); goto done; } + if (vendor && product) { + item->guid = SDL_CreateJoystickGUID(bus, vendor, product, 0, NULL, gamepadEvent->id, 0, os_id); + } else { + item->guid = SDL_CreateJoystickGUIDForName(item->name); + item->guid.data[15] = os_id; + } + + if (is_xinput) { + item->guid.data[14] = 'x'; // See SDL_IsJoystickXInput + } + item->mapping = SDL_strdup(gamepadEvent->mapping); if (!item->mapping) { SDL_free(item->name); @@ -66,19 +193,74 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep goto done; } - item->naxes = gamepadEvent->numAxes; - item->nbuttons = gamepadEvent->numButtons; + const int real_button_count = gamepadEvent->numButtons; + const int real_axis_count = gamepadEvent->numAxes; + int first_trigger_button = -1; + int first_hat_button = -1; + int num_buttons = gamepadEvent->numButtons; + int num_axes = gamepadEvent->numAxes; + bool triggers_are_buttons = false; + if ((SDL_strcmp(gamepadEvent->mapping, "standard") == 0) && (num_buttons >= 16)) { // maps to a game console gamepad layout, turn the d-pad into a hat, treat triggers as analog. + num_buttons -= 4; // 4 dpad buttons become a hat. + first_hat_button = 12; + + if (num_axes == 4) { // Chrome gives the triggers analog button values, Firefox exposes them as extra axes. Both have the digital buttons. + num_axes += 2; // the two trigger "buttons" + triggers_are_buttons = true; + } + + // dump the digital trigger buttons in any case. + first_trigger_button = 6; + num_buttons -= 2; + } + + item->first_hat_button = first_hat_button; + item->first_trigger_button = first_trigger_button; + item->triggers_are_buttons = triggers_are_buttons; + item->nhats = (first_hat_button >= 0) ? 1 : 0; + item->naxes = num_axes; + item->nbuttons = num_buttons; item->device_instance = SDL_GetNextObjectID(); item->timestamp = gamepadEvent->timestamp; - for (i = 0; i < item->naxes; i++) { + int buttonidx = 0; + for (i = 0; i < real_button_count; i++, buttonidx++) { + if (buttonidx == first_hat_button) { + buttonidx += 4; // skip these buttons, we're treating them as hat input. + } else if (buttonidx == first_trigger_button) { + buttonidx += 2; // skip these buttons, we're treating them as axes. + } + item->analogButton[i] = gamepadEvent->analogButton[buttonidx]; + item->digitalButton[i] = gamepadEvent->digitalButton[buttonidx]; + } + + for (i = 0; i < real_axis_count; i++) { item->axis[i] = gamepadEvent->axis[i]; } - for (i = 0; i < item->nbuttons; i++) { - item->analogButton[i] = gamepadEvent->analogButton[i]; - item->digitalButton[i] = gamepadEvent->digitalButton[i]; + if (item->triggers_are_buttons) { + item->axis[real_axis_count] = (gamepadEvent->analogButton[first_trigger_button] * 2.0f) - 1.0f; + item->axis[real_axis_count+1] = (gamepadEvent->analogButton[first_trigger_button+1] * 2.0f) - 1.0f; + } + + SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons. + if (first_hat_button != -1) { + Uint8 value = SDL_HAT_CENTERED; + // this currently expects the first button to be up, then down, then left, then right. + if (gamepadEvent->digitalButton[first_hat_button + 0]) { + value |= SDL_HAT_UP; + } + if (gamepadEvent->digitalButton[first_hat_button + 1]) { + value |= SDL_HAT_DOWN; + } + if (gamepadEvent->digitalButton[first_hat_button + 2]) { + value |= SDL_HAT_LEFT; + } + if (gamepadEvent->digitalButton[first_hat_button + 3]) { + value |= SDL_HAT_RIGHT; + } + item->hat = value; } if (!SDL_joylist_tail) { @@ -317,12 +499,29 @@ static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index) joystick->hwdata = (struct joystick_hwdata *)item; item->joystick = joystick; - // HTML5 Gamepad API doesn't say anything about these - joystick->nhats = 0; - + // HTML5 Gamepad API doesn't offer hats, but we can fake it from the d-pad buttons on the "standard" mapping. + joystick->nhats = item->nhats; joystick->nbuttons = item->nbuttons; joystick->naxes = item->naxes; + item->rumble_available = MAIN_THREAD_EM_ASM_INT({ + let gamepad = navigator['getGamepads']()[$0]; + return gamepad && gamepad['vibrationActuator'] && gamepad['vibrationActuator']['effects'] && gamepad['vibrationActuator']['effects']['includes']('dual-rumble'); + }, item->index); + + if (item->rumble_available) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + } + + item->trigger_rumble_available = MAIN_THREAD_EM_ASM_INT({ + let gamepad = navigator['getGamepads']()[$0]; + return gamepad && gamepad['vibrationActuator'] && gamepad['vibrationActuator']['effects'] && gamepad['vibrationActuator']['effects']['includes']('trigger-rumble'); + }, item->index); + + if (item->trigger_rumble_available) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); + } + return true; } @@ -344,28 +543,65 @@ static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick) result = emscripten_get_gamepad_status(item->index, &gamepadState); if (result == EMSCRIPTEN_RESULT_SUCCESS) { if (gamepadState.timestamp == 0 || gamepadState.timestamp != item->timestamp) { - for (i = 0; i < item->nbuttons; i++) { - if (item->digitalButton[i] != gamepadState.digitalButton[i]) { - bool down = (gamepadState.digitalButton[i] != 0); + const int first_hat_button = item->first_hat_button; + const int first_trigger_button = item->first_trigger_button; + const int real_button_count = gamepadState.numButtons; + const int real_axis_count = gamepadState.numAxes; + + int buttonidx = 0; + for (i = 0; i < real_button_count; i++, buttonidx++) { + if (buttonidx == first_hat_button) { + buttonidx += 4; // skip these buttons, we're treating them as hat input. + } else if (buttonidx == first_trigger_button) { + buttonidx += 2; // skip these buttons, we're treating them as axes. + } + if (item->digitalButton[i] != gamepadState.digitalButton[buttonidx]) { + const bool down = (gamepadState.digitalButton[buttonidx] != 0); SDL_SendJoystickButton(timestamp, item->joystick, i, down); } // store values to compare them in the next update - item->analogButton[i] = gamepadState.analogButton[i]; - item->digitalButton[i] = gamepadState.digitalButton[i]; + item->analogButton[i] = gamepadState.analogButton[buttonidx]; + item->digitalButton[i] = gamepadState.digitalButton[buttonidx]; } - for (i = 0; i < item->naxes; i++) { + for (i = 0; i < real_axis_count; i++) { if (item->axis[i] != gamepadState.axis[i]) { - // do we need to do conversion? - SDL_SendJoystickAxis(timestamp, item->joystick, i, - (Sint16)(32767. * gamepadState.axis[i])); + SDL_SendJoystickAxis(timestamp, item->joystick, i, (Sint16)(32767.0f * gamepadState.axis[i])); + item->axis[i] = gamepadState.axis[i]; } - - // store to compare in next update - item->axis[i] = gamepadState.axis[i]; } + if (item->triggers_are_buttons) { + for (i = 0; i < 2; i++) { + if (item->axis[real_axis_count+i] != gamepadState.analogButton[first_trigger_button+i]) { + SDL_SendJoystickAxis(timestamp, item->joystick, real_axis_count+i, (Sint16)(32767.0f * ((gamepadState.analogButton[first_trigger_button+i] * 2.0f) - 1.0f))); + item->axis[real_axis_count+i] = gamepadState.analogButton[first_trigger_button+i]; + } + } + } + + SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons. + if (item->nhats) { + Uint8 value = SDL_HAT_CENTERED; + // this currently expects the first button to be up, then down, then left, then right. + if (gamepadState.digitalButton[first_hat_button + 0]) { + value |= SDL_HAT_UP; + } else if (gamepadState.digitalButton[first_hat_button + 1]) { + value |= SDL_HAT_DOWN; + } + if (gamepadState.digitalButton[first_hat_button + 2]) { + value |= SDL_HAT_LEFT; + } else if (gamepadState.digitalButton[first_hat_button + 3]) { + value |= SDL_HAT_RIGHT; + } + if (item->hat != value) { + item->hat = value; + SDL_SendJoystickHat(timestamp, item->joystick, 0, value); + } + } + + item->timestamp = gamepadState.timestamp; } } @@ -383,19 +619,52 @@ static void EMSCRIPTEN_JoystickClose(SDL_Joystick *joystick) static SDL_GUID EMSCRIPTEN_JoystickGetDeviceGUID(int device_index) { - // the GUID is just the name for now - const char *name = EMSCRIPTEN_JoystickGetDeviceName(device_index); - return SDL_CreateJoystickGUIDForName(name); + return JoystickByDeviceIndex(device_index)->guid; +} + +static bool Emscripten_UpdateRumble(SDL_joylist_item *item) +{ + bool result = MAIN_THREAD_EM_ASM_INT({ + let gamepad = navigator['getGamepads']()[$0]; + if (!gamepad) { + return false; + } + // We check if rumble is available in EMSCRIPTEN_JoystickRumble() and EMSCRIPTEN_JoystickRumbleTriggers(). + // From my testing using "dual-rumble" here covers both main rumble and trigger rumble. + gamepad['vibrationActuator']['playEffect']('dual-rumble', { + 'startDelay': 0, + 'duration': 3000, + 'weakMagnitude': $1 / 0xFFFF, + 'strongMagnitude': $2 / 0xFFFF, + 'leftTrigger': $3 / 0xFFFF, + 'rightTrigger': $4 / 0xFFFF, + }); + + return true; + }, item->index, item->weak_magnitude_rumble, item->strong_magnitude_rumble, item->left_trigger_rumble, item->right_trigger_rumble); + return result; } static bool EMSCRIPTEN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) { - return SDL_Unsupported(); + SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; + if (!item || !item->rumble_available) { + return SDL_Unsupported(); + } + item->strong_magnitude_rumble = low_frequency_rumble; + item->weak_magnitude_rumble = high_frequency_rumble; + return Emscripten_UpdateRumble(item); } static bool EMSCRIPTEN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) { - return SDL_Unsupported(); + SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; + if (!item || !item->trigger_rumble_available) { + return SDL_Unsupported(); + } + item->left_trigger_rumble = left_rumble; + item->right_trigger_rumble = right_rumble; + return Emscripten_UpdateRumble(item); } static bool EMSCRIPTEN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) diff --git a/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick_c.h b/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick_c.h index e03a27c4..c23d5447 100644 --- a/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick_c.h +++ b/libs/SDL3/src/joystick/emscripten/SDL_sysjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -34,12 +34,24 @@ typedef struct SDL_joylist_item char *mapping; SDL_JoystickID device_instance; SDL_Joystick *joystick; + int first_hat_button; + int first_trigger_button; + bool triggers_are_buttons; + int nhats; + SDL_GUID guid; int nbuttons; int naxes; double timestamp; - double axis[64]; + double axis[64]; // !!! FIXME: don't hardcode 64 on all of these. double analogButton[64]; EM_BOOL digitalButton[64]; + Uint8 hat; // there is (currently) only ever one of these, faked from the d-pad buttons. + bool rumble_available; + bool trigger_rumble_available; + Uint16 weak_magnitude_rumble; + Uint16 strong_magnitude_rumble; + Uint16 left_trigger_rumble; + Uint16 right_trigger_rumble; struct SDL_joylist_item *next; } SDL_joylist_item; diff --git a/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.cpp b/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.cpp index 67cf51fa..c6c2b8ed 100644 --- a/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.cpp +++ b/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -39,6 +39,15 @@ enum SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE = 11 }; +enum +{ + SDL_GAMEINPUT_RAWTYPE_NONE, + SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR, + SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_DRUM_KIT, + SDL_GAMEINPUT_RAWTYPE_GUITAR_HERO_LIVE_GUITAR, + SDL_GAMEINPUT_RAWTYPE_LEGACY_ADAPTER, +}; + typedef struct GAMEINPUT_InternalDevice { IGameInputDevice *device; @@ -47,6 +56,8 @@ typedef struct GAMEINPUT_InternalDevice SDL_GUID guid; // generated by SDL SDL_JoystickID device_instance; // generated by SDL const GameInputDeviceInfo *info; + int raw_type; + int steam_virtual_gamepad_slot; bool isAdded; bool isDeleteRequested; } GAMEINPUT_InternalDevice; @@ -70,6 +81,11 @@ static IGameInput *g_pGameInput = NULL; static GameInputCallbackToken g_GameInputCallbackToken = 0; static Uint64 g_GameInputTimestampOffset; +extern "C" +{ + extern bool SDL_XINPUT_Enabled(void); +} + static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info) { if (info->supportedInput & GameInputKindGamepad) { @@ -78,6 +94,94 @@ static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info) return false; } +static Uint8 GAMEINPUT_GetDeviceRawType(const GameInputDeviceInfo *info) +{ +#if GAMEINPUT_API_VERSION >= 3 + GameInputKind supportedInput = info->supportedInput; + if (supportedInput & GameInputKindRawDeviceReport) { + switch (info->vendorId) { + case USB_VENDOR_MADCATZ: + switch (info->productId) { + case USB_PRODUCT_MADCATZ_XB1_STRATOCASTER_GUITAR: + return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR; + case USB_PRODUCT_MADCATZ_XB1_DRUM_KIT: + return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_DRUM_KIT; + case USB_PRODUCT_MADCATZ_XB1_LEGACY_ADAPTER: + return SDL_GAMEINPUT_RAWTYPE_LEGACY_ADAPTER; + default: + break; + } + break; + case USB_VENDOR_PDP: + switch (info->productId) { + case USB_PRODUCT_PDP_XB1_JAGUAR_GUITAR: + case USB_PRODUCT_PDP_XB1_RIFFMASTER_GUITAR: + return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR; + case USB_PRODUCT_PDP_XB1_DRUM_KIT: + return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_DRUM_KIT; + default: + break; + } + break; + case USB_VENDOR_CRKD: + switch (info->productId) { + case USB_PRODUCT_PDP_XB1_JAGUAR_GUITAR: + return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR; + default: + break; + } + break; + case USB_VENDOR_RED_OCTANE: + switch (info->productId) { + case USB_PRODUCT_RED_OCTANE_XB1_GUITAR_HERO_LIVE_GUITAR: + return SDL_GAMEINPUT_RAWTYPE_GUITAR_HERO_LIVE_GUITAR; + default: + break; + } + break; + } + } +#endif // GAMEINPUT_API_VERSION >= 3 + return SDL_GAMEINPUT_RAWTYPE_NONE; +} +static Uint8 GAMEINPUT_GetDeviceSubtype(const GameInputDeviceInfo *info) +{ + GameInputKind supportedInput = info->supportedInput; + Uint8 rawType = GAMEINPUT_GetDeviceRawType(info); + if (rawType == SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR || rawType == SDL_GAMEINPUT_RAWTYPE_GUITAR_HERO_LIVE_GUITAR) { + return SDL_JOYSTICK_TYPE_GUITAR; + } + if (rawType == SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_DRUM_KIT) { + return SDL_JOYSTICK_TYPE_DRUM_KIT; + } + if (supportedInput & GameInputKindRacingWheel) { + return SDL_JOYSTICK_TYPE_WHEEL; + } + if (supportedInput & GameInputKindArcadeStick) { + return SDL_JOYSTICK_TYPE_ARCADE_STICK; + } + if (supportedInput & GameInputKindFlightStick) { + return SDL_JOYSTICK_TYPE_FLIGHT_STICK; + } + if (supportedInput & (GameInputKindGamepad | GameInputKindController)) { + return SDL_JOYSTICK_TYPE_GAMEPAD; + } + // Other device subtypes don't have their own GameInputKind enum entries. + return 0; +} + +#if GAMEINPUT_API_VERSION >= 1 +static int GetSteamVirtualGamepadSlot(const char *device_path) +{ + int slot = -1; + + // The format for the raw input device path is documented here: + // https://partner.steamgames.com/doc/features/steam_controller/steam_input_gamepad_emulation_bestpractices + (void)SDL_sscanf(device_path, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%*X&%*X&%*X#%d#%*u", &slot); + return slot; +} +#endif // GAMEINPUT_API_VERSION >= 1 + static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) { GAMEINPUT_InternalDevice **devicelist = NULL; @@ -87,8 +191,10 @@ static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) Uint16 vendor = 0; Uint16 product = 0; Uint16 version = 0; - const char *manufacturer_string = NULL; const char *product_string = NULL; + Uint8 driver_signature = 'g'; + Uint8 subtype = 0; + int raw_type = SDL_GAMEINPUT_RAWTYPE_NONE; char tmp[4]; int idx = 0; @@ -110,11 +216,48 @@ static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) vendor = info->vendorId; product = info->productId; //version = (info->firmwareVersion.major << 8) | info->firmwareVersion.minor; + subtype = GAMEINPUT_GetDeviceSubtype(info); + raw_type = GAMEINPUT_GetDeviceRawType(info); - if (SDL_JoystickHandledByAnotherDriver(&SDL_GAMEINPUT_JoystickDriver, vendor, product, version, "")) { +#if GAMEINPUT_API_VERSION >= 1 + if (info->displayName) { + product_string = info->displayName; + } +#else + if (info->displayName) { + product_string = info->displayName->data; + } +#endif + + if (SDL_ShouldIgnoreJoystick(vendor, product, version, product_string) || + SDL_JoystickHandledByAnotherDriver(&SDL_GAMEINPUT_JoystickDriver, vendor, product, version, product_string)) { return true; } +#if defined(SDL_JOYSTICK_DINPUT) && defined(SDL_HAPTIC_DINPUT) + // This joystick backend currently doesn't provide a haptic backend, so fallback to DirectInput for haptic-capable devices. + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true) && info->forceFeedbackMotorCount > 0 && pDevice->IsForceFeedbackMotorPoweredOn(0)) { + return true; + } +#endif + + if (!GAMEINPUT_InternalIsGamepad(info) && raw_type == SDL_GAMEINPUT_RAWTYPE_NONE) { +#if defined(SDL_JOYSTICK_DINPUT) || defined(SDL_JOYSTICK_XINPUT) + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true) || SDL_XINPUT_Enabled()) { + // Let other backends handle non-gamepad controllers to possibly avoid bugs and/or regressions. + return true; + } +#endif + if (info->supportedInput & GameInputKindController) { + // Maintain GUID compatibility with DirectInput controller mappings. + driver_signature = 0; + subtype = 0; + } else { + // This joystick backend currently doesn't provide proper reading of other joystick types. + return true; + } + } + for (idx = 0; idx < g_GameInputList.count; ++idx) { elem = g_GameInputList.devices[idx]; if (elem && elem->device == pDevice) { @@ -141,22 +284,18 @@ static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) SDL_strlcat(elem->path, tmp, SDL_arraysize(elem->path)); } -#if GAMEINPUT_API_VERSION >= 1 - if (info->displayName) { - product_string = info->displayName; - } -#else - if (info->displayName) { - product_string = info->displayName->data; - } -#endif - pDevice->AddRef(); elem->device = pDevice; - elem->name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string); - elem->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, manufacturer_string, product_string, 'g', 0); + elem->name = SDL_CreateJoystickName(vendor, product, NULL, product_string); + elem->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, NULL, product_string, driver_signature, subtype); elem->device_instance = SDL_GetNextObjectID(); elem->info = info; + elem->raw_type = raw_type; +#if GAMEINPUT_API_VERSION >= 1 + elem->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(info->pnpPath); +#else + elem->steam_virtual_gamepad_slot = -1; +#endif g_GameInputList.devices = devicelist; g_GameInputList.devices[g_GameInputList.count++] = elem; @@ -244,11 +383,20 @@ static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback( static void GAMEINPUT_JoystickDetect(void); static void GAMEINPUT_JoystickQuit(void); +static bool GAMEINPUT_IsRawGameInputEnabled(void) +{ +#if GAMEINPUT_API_VERSION >= 3 + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT_RAW, true); +#else + return false; +#endif +} + static bool GAMEINPUT_JoystickInit(void) { HRESULT hr; - if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT)) { + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT) && !GAMEINPUT_IsRawGameInputEnabled()) { return true; } @@ -256,8 +404,24 @@ static bool GAMEINPUT_JoystickInit(void) return false; } +#if GAMEINPUT_API_VERSION >= 2 + // Allow background controller input + // SDL manages focus policy at a higher level, so we can set this unconditionally. + g_pGameInput->SetFocusPolicy(GameInputEnableBackgroundInput | GameInputEnableBackgroundGuideButton | GameInputEnableBackgroundShareButton); +#endif + + GameInputKind kind = GameInputKindUnknown; + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT)) { + kind |= GameInputKindController; + } +#if GAMEINPUT_API_VERSION >= 3 + if (GAMEINPUT_IsRawGameInputEnabled()) { + kind |= GameInputKindRawDeviceReport; + } +#endif + hr = g_pGameInput->RegisterDeviceCallback(NULL, - GameInputKindController, + kind, GameInputDeviceConnected, GameInputBlockingEnumeration, NULL, @@ -344,7 +508,7 @@ static const char *GAMEINPUT_JoystickGetDevicePath(int device_index) static int GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) { - return -1; + return GAMEINPUT_InternalFindByIndex(device_index)->steam_virtual_gamepad_slot; } static int GAMEINPUT_JoystickGetDevicePlayerIndex(int device_index) @@ -447,7 +611,7 @@ static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) hwdata->devref = elem; joystick->hwdata = hwdata; - if (GAMEINPUT_InternalIsGamepad(info)) { + if (GAMEINPUT_InternalIsGamepad(info) || GAMEINPUT_GetDeviceRawType(info) != SDL_GAMEINPUT_RAWTYPE_NONE) { joystick->naxes = 6; joystick->nbuttons = 11; joystick->nhats = 1; @@ -462,9 +626,15 @@ static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) } #endif // GAMEINPUT_API_VERSION >= 1 } else { +#if GAMEINPUT_API_VERSION >= 3 + joystick->naxes = info->controllerInfo->controllerAxisCount; + joystick->nbuttons = info->controllerInfo->controllerButtonCount; + joystick->nhats = info->controllerInfo->controllerSwitchCount; +#else joystick->naxes = info->controllerAxisCount; joystick->nbuttons = info->controllerButtonCount; joystick->nhats = info->controllerSwitchCount; +#endif // GAMEINPUT_API_VERSION >= 3 } if (info->supportedRumbleMotors & (GameInputRumbleLowFrequency | GameInputRumbleHighFrequency)) { @@ -474,23 +644,17 @@ static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); } -#if 0 - if (info->supportedInput & GameInputKindTouch) { - SDL_PrivateJoystickAddTouchpad(joystick, info->touchPointCount); +#if GAMEINPUT_API_VERSION >= 3 + if (info->supportedInput & GameInputKindSensors) { + if (info->sensorsInfo->supportedSensors & GameInputSensorsGyrometer) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 60.0f); + } + if (info->sensorsInfo->supportedSensors & GameInputSensorsAccelerometer) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 60.0f); + } } +#endif // GAMEINPUT_API_VERSION >= 3 - if (info->supportedInput & GameInputKindMotion) { - // FIXME: What's the sensor update rate? - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); - } - - if (info->capabilities & GameInputDeviceCapabilityWireless) { - joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; - } else { - joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED; - } -#endif return true; } @@ -532,14 +696,193 @@ static bool GAMEINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool ena return true; } +static void GAMEINPUT_GuitarUpdate(SDL_Joystick *joystick, IGameInputReading *reading, Uint64 timestamp) +{ +#if GAMEINPUT_API_VERSION >= 3 + IGameInputRawDeviceReport *rawState; + if (reading->GetRawReport(&rawState)) { + static WORD s_GuitarButtons[] = { + 0x0010, // SDL_GAMEPAD_BUTTON_SOUTH + 0x0020, // SDL_GAMEPAD_BUTTON_EAST + 0x0040, // SDL_GAMEPAD_BUTTON_WEST + 0x0080, // SDL_GAMEPAD_BUTTON_NORTH + 0x0008, // SDL_GAMEPAD_BUTTON_BACK + 0, // The guide button is not available + 0x0004, // SDL_GAMEPAD_BUTTON_START + 0, // right joystick click unavailable + 0x4000, // SDL_GAMEPAD_BUTTON_RIGHT_STICK + 0x1000, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + 0, // right shoulder unavailable + }; + Uint8 btnidx = 0, hat = 0; + uint8_t rawData[40]; + SDL_memset(rawData, 0, sizeof(rawData)); + size_t len = rawState->GetRawData(sizeof(rawData), rawData); + uint16_t buttons = rawData[0] | rawData[1] << 8; + if (len >= 10) { + for (btnidx = 0; btnidx < SDL_arraysize(s_GuitarButtons); ++btnidx) { + WORD button_mask = s_GuitarButtons[btnidx]; + if (!button_mask) { + continue; + } + bool down = ((buttons & button_mask) != 0); + SDL_SendJoystickButton(timestamp, joystick, btnidx, down); + } + if (buttons & 0x0100) { + hat |= SDL_HAT_UP; + } + if (buttons & 0x0200) { + hat |= SDL_HAT_DOWN; + } + if (buttons & 0x0400) { + hat |= SDL_HAT_LEFT; + } + if (buttons & 0x0800) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, (rawData[3] * 257) - 32768); + // PS3 RB guitars had tilt on right shoulder + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, rawData[2] >= 0xD0); + // PS3 RB guitars send L2 when using solo buttons + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (rawData[6]) ? 32767 : -32768); + // Align pickup selector mappings with PS3 instruments + static const Sint16 effects_mappings[] = {-26880, -13568, -1792, 11008, 24576}; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, effects_mappings[rawData[4] >> 4]); + } + } +#endif // GAMEINPUT_API_VERSION >= 3 +} + +static void GAMEINPUT_GamepadUpdate(SDL_Joystick *joystick, IGameInputReading *reading, Uint64 timestamp) { + GameInputGamepadState state; + static WORD s_XInputButtons[] = { + GameInputGamepadA, // SDL_GAMEPAD_BUTTON_SOUTH + GameInputGamepadB, // SDL_GAMEPAD_BUTTON_EAST + GameInputGamepadX, // SDL_GAMEPAD_BUTTON_WEST + GameInputGamepadY, // SDL_GAMEPAD_BUTTON_NORTH + GameInputGamepadView, // SDL_GAMEPAD_BUTTON_BACK + 0, // The guide button is not available + GameInputGamepadMenu, // SDL_GAMEPAD_BUTTON_START + GameInputGamepadLeftThumbstick, // SDL_GAMEPAD_BUTTON_LEFT_STICK + GameInputGamepadRightThumbstick, // SDL_GAMEPAD_BUTTON_RIGHT_STICK + GameInputGamepadLeftShoulder, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + GameInputGamepadRightShoulder, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + }; + Uint8 btnidx = 0, hat = 0; + + if (reading->GetGamepadState(&state)) { + for (btnidx = 0; btnidx < SDL_arraysize(s_XInputButtons); ++btnidx) { + WORD button_mask = s_XInputButtons[btnidx]; + if (!button_mask) { + continue; + } + bool down = ((state.buttons & button_mask) != 0); + SDL_SendJoystickButton(timestamp, joystick, btnidx, down); + } + + if (state.buttons & GameInputGamepadDPadUp) { + hat |= SDL_HAT_UP; + } + if (state.buttons & GameInputGamepadDPadDown) { + hat |= SDL_HAT_DOWN; + } + if (state.buttons & GameInputGamepadDPadLeft) { + hat |= SDL_HAT_LEFT; + } + if (state.buttons & GameInputGamepadDPadRight) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + +#define CONVERT_AXIS(v) (Sint16)(((v) < 0.0f) ? ((v)*32768.0f) : ((v)*32767.0f)) + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, CONVERT_AXIS(state.leftThumbstickX)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, CONVERT_AXIS(-state.leftThumbstickY)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, CONVERT_AXIS(state.rightThumbstickX)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, CONVERT_AXIS(-state.rightThumbstickY)); +#undef CONVERT_AXIS +#define CONVERT_TRIGGER(v) (Sint16)((v)*65535.0f - 32768.0f) + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, CONVERT_TRIGGER(state.leftTrigger)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, CONVERT_TRIGGER(state.rightTrigger)); +#undef CONVERT_TRIGGER + } +} + +static void GAMEINPUT_ControllerUpdate(SDL_Joystick *joystick, IGameInputReading *reading, Uint64 timestamp) +{ + bool *button_state = SDL_stack_alloc(bool, joystick->nbuttons); + float *axis_state = SDL_stack_alloc(float, joystick->naxes); + GameInputSwitchPosition *switch_state = SDL_stack_alloc(GameInputSwitchPosition, joystick->nhats); + + if (button_state) { + uint32_t i; + uint32_t button_count = reading->GetControllerButtonState(joystick->nbuttons, button_state); + for (i = 0; i < button_count; ++i) { + SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, button_state[i]); + } + SDL_stack_free(button_state); + } + +#define CONVERT_AXIS(v) (Sint16)((v)*65535.0f - 32768.0f) + if (axis_state) { + uint32_t i; + uint32_t axis_count = reading->GetControllerAxisState(joystick->naxes, axis_state); + for (i = 0; i < axis_count; ++i) { + SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, CONVERT_AXIS(axis_state[i])); + } + SDL_stack_free(axis_state); + } +#undef CONVERT_AXIS + + if (switch_state) { + uint32_t i; + uint32_t switch_count = reading->GetControllerSwitchState(joystick->nhats, switch_state); + for (i = 0; i < switch_count; ++i) { + Uint8 hat; + switch (switch_state[i]) { + case GameInputSwitchUp: + hat = SDL_HAT_UP; + break; + case GameInputSwitchUpRight: + hat = SDL_HAT_UP | SDL_HAT_RIGHT; + break; + case GameInputSwitchRight: + hat = SDL_HAT_RIGHT; + break; + case GameInputSwitchDownRight: + hat = SDL_HAT_DOWN | SDL_HAT_RIGHT; + break; + case GameInputSwitchDown: + hat = SDL_HAT_DOWN; + break; + case GameInputSwitchDownLeft: + hat = SDL_HAT_DOWN | SDL_HAT_LEFT; + break; + case GameInputSwitchLeft: + hat = SDL_HAT_LEFT; + break; + case GameInputSwitchUpLeft: + hat = SDL_HAT_UP | SDL_HAT_LEFT; + break; + case GameInputSwitchCenter: + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat); + } + SDL_stack_free(switch_state); + } +} + static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) { GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; + GAMEINPUT_InternalDevice *internal_device = hwdata->devref; IGameInputDevice *device = hwdata->devref->device; const GameInputDeviceInfo *info = hwdata->devref->info; IGameInputReading *reading = NULL; Uint64 timestamp; - GameInputGamepadState state; HRESULT hr; hr = g_pGameInput->GetCurrentReading(info->supportedInput, device, &reading); @@ -549,147 +892,38 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) } timestamp = SDL_US_TO_NS(reading->GetTimestamp() + g_GameInputTimestampOffset); - - if (GAMEINPUT_InternalIsGamepad(info)) { - static WORD s_XInputButtons[] = { - GameInputGamepadA, // SDL_GAMEPAD_BUTTON_SOUTH - GameInputGamepadB, // SDL_GAMEPAD_BUTTON_EAST - GameInputGamepadX, // SDL_GAMEPAD_BUTTON_WEST - GameInputGamepadY, // SDL_GAMEPAD_BUTTON_NORTH - GameInputGamepadView, // SDL_GAMEPAD_BUTTON_BACK - 0, // The guide button is not available - GameInputGamepadMenu, // SDL_GAMEPAD_BUTTON_START - GameInputGamepadLeftThumbstick, // SDL_GAMEPAD_BUTTON_LEFT_STICK - GameInputGamepadRightThumbstick, // SDL_GAMEPAD_BUTTON_RIGHT_STICK - GameInputGamepadLeftShoulder, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER - GameInputGamepadRightShoulder, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER - }; - Uint8 btnidx = 0, hat = 0; - - if (reading->GetGamepadState(&state)) { - for (btnidx = 0; btnidx < SDL_arraysize(s_XInputButtons); ++btnidx) { - WORD button_mask = s_XInputButtons[btnidx]; - if (!button_mask) { - continue; - } - bool down = ((state.buttons & button_mask) != 0); - SDL_SendJoystickButton(timestamp, joystick, btnidx, down); - } - - if (state.buttons & GameInputGamepadDPadUp) { - hat |= SDL_HAT_UP; - } - if (state.buttons & GameInputGamepadDPadDown) { - hat |= SDL_HAT_DOWN; - } - if (state.buttons & GameInputGamepadDPadLeft) { - hat |= SDL_HAT_LEFT; - } - if (state.buttons & GameInputGamepadDPadRight) { - hat |= SDL_HAT_RIGHT; - } - SDL_SendJoystickHat(timestamp, joystick, 0, hat); - -#define CONVERT_AXIS(v) (Sint16)(((v) < 0.0f) ? ((v)*32768.0f) : ((v)*32767.0f)) - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, CONVERT_AXIS(state.leftThumbstickX)); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, CONVERT_AXIS(-state.leftThumbstickY)); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, CONVERT_AXIS(state.rightThumbstickX)); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, CONVERT_AXIS(-state.rightThumbstickY)); -#undef CONVERT_AXIS -#define CONVERT_TRIGGER(v) (Sint16)((v)*65535.0f - 32768.0f) - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, CONVERT_TRIGGER(state.leftTrigger)); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, CONVERT_TRIGGER(state.rightTrigger)); -#undef CONVERT_TRIGGER - } + if (internal_device->raw_type == SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR) { + GAMEINPUT_GuitarUpdate(joystick, reading, timestamp); + } else if (GAMEINPUT_InternalIsGamepad(info)) { + GAMEINPUT_GamepadUpdate(joystick, reading, timestamp); } else { - bool *button_state = SDL_stack_alloc(bool, info->controllerButtonCount); - float *axis_state = SDL_stack_alloc(float, info->controllerAxisCount); - GameInputSwitchPosition *switch_state = SDL_stack_alloc(GameInputSwitchPosition, info->controllerSwitchCount); - - if (button_state) { - uint32_t i; - uint32_t button_count = reading->GetControllerButtonState(info->controllerButtonCount, button_state); - for (i = 0; i < button_count; ++i) { - SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, button_state[i]); - } - SDL_stack_free(button_state); - } - -#define CONVERT_AXIS(v) (Sint16)((v)*65535.0f - 32768.0f) - if (axis_state) { - uint32_t i; - uint32_t axis_count = reading->GetControllerAxisState(info->controllerAxisCount, axis_state); - for (i = 0; i < axis_count; ++i) { - SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, CONVERT_AXIS(axis_state[i])); - } - SDL_stack_free(axis_state); - } -#undef CONVERT_AXIS - - if (switch_state) { - uint32_t i; - uint32_t switch_count = reading->GetControllerSwitchState(info->controllerSwitchCount, switch_state); - for (i = 0; i < switch_count; ++i) { - Uint8 hat; - switch (switch_state[i]) { - case GameInputSwitchUp: - hat = SDL_HAT_UP; - break; - case GameInputSwitchUpRight: - hat = SDL_HAT_UP | SDL_HAT_RIGHT; - break; - case GameInputSwitchRight: - hat = SDL_HAT_RIGHT; - break; - case GameInputSwitchDownRight: - hat = SDL_HAT_DOWN | SDL_HAT_RIGHT; - break; - case GameInputSwitchDown: - hat = SDL_HAT_DOWN; - break; - case GameInputSwitchDownLeft: - hat = SDL_HAT_DOWN | SDL_HAT_LEFT; - break; - case GameInputSwitchLeft: - hat = SDL_HAT_LEFT; - break; - case GameInputSwitchUpLeft: - hat = SDL_HAT_UP | SDL_HAT_LEFT; - break; - case GameInputSwitchCenter: - default: - hat = SDL_HAT_CENTERED; - break; - } - SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat); - } - SDL_stack_free(switch_state); - } - } - -#if 0 - if (info->supportedInput & GameInputKindTouch) { - GameInputTouchState *touch_state = SDL_stack_alloc(GameInputTouchState, info->touchPointCount); - if (touch_state) { - uint32_t i; - uint32_t touch_count = IGameInputReading_GetTouchState(reading, info->touchPointCount, touch_state); - for (i = 0; i < touch_count; ++i) { - GameInputTouchState *touch = &touch_state[i]; - // FIXME: We should use touch->touchId to track fingers instead of using i below - SDL_SendJoystickTouchpad(timestamp, joystick, 0, i, true, touch->positionX * info->touchSensorInfo[i].resolutionX, touch->positionY * info->touchSensorInfo[0].resolutionY, touch->pressure); - } - SDL_stack_free(touch_state); - } + GAMEINPUT_ControllerUpdate(joystick, reading, timestamp); } +#if GAMEINPUT_API_VERSION >= 3 if (hwdata->report_sensors) { - GameInputMotionState motion_state; + GameInputSensorsState sensor_state; - if (IGameInputReading_GetMotionState(reading, &motion_state)) { - // FIXME: How do we interpret the motion data? + if (reading->GetSensorsState(&sensor_state)) { + if ((info->sensorsInfo->supportedSensors & GameInputSensorsGyrometer) != 0) { + float data[3] = { + sensor_state.angularVelocityInRadPerSecX, + sensor_state.angularVelocityInRadPerSecY, + sensor_state.angularVelocityInRadPerSecZ + }; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, timestamp, data, SDL_arraysize(data)); + } + if ((info->sensorsInfo->supportedSensors & GameInputSensorsAccelerometer) != 0) { + float data[3] = { + sensor_state.accelerationInGX * SDL_STANDARD_GRAVITY, + sensor_state.accelerationInGY * SDL_STANDARD_GRAVITY, + sensor_state.accelerationInGZ * SDL_STANDARD_GRAVITY + }; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, data, SDL_arraysize(data)); + } } } -#endif +#endif // GAMEINPUT_API_VERSION >= 3 reading->Release(); @@ -740,7 +974,7 @@ static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMap { GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index); - if (!GAMEINPUT_InternalIsGamepad(elem->info)) { + if (!GAMEINPUT_InternalIsGamepad(elem->info) && elem->raw_type == SDL_GAMEINPUT_RAWTYPE_NONE) { return false; } diff --git a/libs/SDL3/src/joystick/haiku/SDL_haikujoystick.cc b/libs/SDL3/src/joystick/haiku/SDL_haikujoystick.cc index 977e4fc2..c52d9de1 100644 --- a/libs/SDL3/src/joystick/haiku/SDL_haikujoystick.cc +++ b/libs/SDL3/src/joystick/haiku/SDL_haikujoystick.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_8bitdo.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_8bitdo.c index 10f19fc2..6b1dece5 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_8bitdo.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_8bitdo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2024 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -47,9 +47,17 @@ enum #define SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID 0x03 #define SDL_8BITDO_BT_REPORTID_SDL_REPORTID 0x01 +#define SDL_8BITDO_SENSOR_TIMESTAMP_ENABLE 0xAA #define ABITDO_ACCEL_SCALE 4096.f #define ABITDO_GYRO_MAX_DEGREES_PER_SECOND 2000.f + +#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \ + (((Uint32)(B)) << 8) | \ + (((Uint32)(C)) << 16) | \ + (((Uint32)(D)) << 24)) + + typedef struct { bool sensors_supported; @@ -61,6 +69,7 @@ typedef struct bool rgb_supported; bool player_led_supported; bool powerstate_supported; + bool sensor_timestamp_supported; Uint8 serial[6]; Uint16 version; Uint16 version_beta; @@ -69,6 +78,7 @@ typedef struct Uint8 last_state[USB_PACKET_LENGTH]; Uint64 sensor_timestamp; // Nanoseconds. Simulate onboard clock. Different models have different rates vs different connection styles. Uint64 sensor_timestamp_interval; + Uint32 last_tick; } SDL_Driver8BitDo_Context; #pragma pack(push,1) @@ -135,6 +145,7 @@ static bool HIDAPI_Driver8BitDo_IsSupportedDevice(SDL_HIDAPI_Device *device, con case USB_PRODUCT_8BITDO_SN30_PRO_BT: case USB_PRODUCT_8BITDO_PRO_2: case USB_PRODUCT_8BITDO_PRO_2_BT: + case USB_PRODUCT_8BITDO_PRO_3: case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: return true; default: @@ -177,10 +188,17 @@ static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device) 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]; @@ -202,6 +220,8 @@ static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device) HIDAPI_SetDeviceName(device, "8BitDo SN30 Pro"); } else if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT) { HIDAPI_SetDeviceName(device, "8BitDo Pro 2"); + } else if (device->product_id == USB_PRODUCT_8BITDO_PRO_3) { + HIDAPI_SetDeviceName(device, "8BitDo Pro 3"); } return HIDAPI_JoystickConnected(device, NULL); @@ -218,22 +238,47 @@ 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) { - // Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool - case USB_PRODUCT_8BITDO_SN30_PRO_BT: - case USB_PRODUCT_8BITDO_SF30_PRO_BT: - return 90; // Observed to be anywhere between 60-90 hz. Possibly lossy in current state case USB_PRODUCT_8BITDO_SF30_PRO: + case USB_PRODUCT_8BITDO_SF30_PRO_BT: case USB_PRODUCT_8BITDO_SN30_PRO: - return 100; + 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, labelled as "BT" but appears this way when wired. Observed bluetooth packet rate seems to be 80-90hz - return (device->is_bluetooth ? 85 : 100); + case USB_PRODUCT_8BITDO_PRO_2_BT: // Note, labeled as "BT" but appears this way when wired. + case USB_PRODUCT_8BITDO_PRO_3: + if (device->is_bluetooth) { + // Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool + return 85; // Observed Bluetooth packet rate seems to be 80-90hz + } else if (ctx->sensor_timestamp_supported) { + // This firmware appears to update at 200 Hz over USB + return 200; + } else { + // This firmware appears to update at 100 Hz over USB + return 100; + } case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: + if (device->is_bluetooth) { + // Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool + return 120; // Observed Bluetooth packet rate seems to be 120hz + } else { + // This firmware appears to update at 1000 Hz over USB dongle + return 1000; + } default: return 120; - break; } } @@ -252,6 +297,7 @@ static bool HIDAPI_Driver8BitDo_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys // Initialize the joystick capabilities if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT || + device->product_id == USB_PRODUCT_8BITDO_PRO_3 || device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { // This controller has additional buttons joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS; @@ -544,6 +590,24 @@ static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr 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 diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_combined.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_combined.c index 5426edbd..e9bbfd9c 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_combined.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_combined.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -76,9 +76,7 @@ static bool HIDAPI_DriverCombined_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Jo child = device->children[i]; child->driver->CloseJoystick(child, joystick); } - if (serial) { - SDL_free(serial); - } + SDL_free(serial); return false; } @@ -102,9 +100,7 @@ static bool HIDAPI_DriverCombined_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Jo } // Update the joystick with the combined serial numbers - if (joystick->serial) { - SDL_free(joystick->serial); - } + SDL_free(joystick->serial); joystick->serial = serial; return true; diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.c index 1ee50d1e..989609ca 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2024 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -25,6 +25,7 @@ #include "../SDL_sysjoystick.h" #include "SDL_hidapijoystick_c.h" #include "SDL_hidapi_rumble.h" +#include "SDL_hidapi_flydigi.h" #ifdef SDL_JOYSTICK_HIDAPI_FLYDIGI @@ -33,6 +34,10 @@ #define DEBUG_FLYDIGI_PROTOCOL #endif +#ifndef DEG2RAD +#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) +#endif + enum { SDL_GAMEPAD_BUTTON_FLYDIGI_M1 = 11, @@ -42,24 +47,47 @@ enum SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS }; -/* Rate of IMU Sensor Packets over wireless Dongle observed in testcontroller tool at 1000hz */ +/* Rate of IMU Sensor Packets over wireless dongle observed in testcontroller at 1000hz */ #define SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ 1000 #define SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ) -/* Rate of IMU Sensor Packets over wired 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) +/* Rate of IMU Sensor Packets over wired connection observed in testcontroller at 500hz */ +#define SENSOR_INTERVAL_VADER4_PRO_WIRED_RATE_HZ 500 +#define SENSOR_INTERVAL_VADER4_PRO_WIRED_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER4_PRO_WIRED_RATE_HZ) +/* Rate of IMU Sensor Packets over wired connection observed in testcontroller at 500hz */ +#define SENSOR_INTERVAL_VADER5_PRO_RATE_HZ 500 +#define SENSOR_INTERVAL_VADER5_PRO_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER5_PRO_RATE_HZ) -#define FLYDIGI_CMD_REPORT_ID 0x05 -#define FLYDIGI_HAPTIC_COMMAND 0x0F -#define FLYDIGI_GET_CONFIG_COMMAND 0xEB -#define FLYDIGI_GET_INFO_COMMAND 0xEC +/* Rate of IMU Sensor Packets over wireless dongle observed in testcontroller at 295hz */ +#define SENSOR_INTERVAL_APEX5_DONGLE_RATE_HZ 295 +#define SENSOR_INTERVAL_APEX5_DONGLE_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_APEX5_DONGLE_RATE_HZ) +/* Rate of IMU Sensor Packets over wired connection observed in testcontroller at 970hz */ +#define SENSOR_INTERVAL_APEX5_WIRED_RATE_HZ 970 +#define SENSOR_INTERVAL_APEX5_WIRED_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_APEX5_WIRED_RATE_HZ) -#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) +#define FLYDIGI_ACQUIRE_CONTROLLER_HEARTBEAT_TIME 1000 * 30 + +#define FLYDIGI_V1_CMD_REPORT_ID 0x05 +#define FLYDIGI_V1_HAPTIC_COMMAND 0x0F +#define FLYDIGI_V1_GET_INFO_COMMAND 0xEC + +#define FLYDIGI_V2_CMD_REPORT_ID 0x03 +#define FLYDIGI_V2_MAGIC1 0x5A +#define FLYDIGI_V2_MAGIC2 0xA5 +#define FLYDIGI_V2_GET_INFO_COMMAND 0x01 +#define FLYDIGI_V2_GET_STATUS_COMMAND 0x10 +#define FLYDIGI_V2_SET_STATUS_COMMAND 0x11 +#define FLYDIGI_V2_HAPTIC_COMMAND 0x12 +#define FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND 0x1C +#define FLYDIGI_V2_INPUT_REPORT 0xEF typedef struct { + SDL_HIDAPI_Device *device; Uint8 deviceID; + bool available; bool has_cz; + bool has_lmrm; + bool has_circle; bool wireless; bool sensors_supported; bool sensors_enabled; @@ -67,6 +95,9 @@ typedef struct Uint64 sensor_timestamp_ns; // Simulate onboard clock. Advance by known time step. Nanoseconds. Uint64 sensor_timestamp_step_ns; // Based on observed rate of receipt of IMU sensor packets. float accelScale; + float gyroScale; + Uint64 next_heartbeat; + Uint64 last_packet; Uint8 last_state[USB_PACKET_LENGTH]; } SDL_DriverFlydigi_Context; @@ -88,18 +119,209 @@ static bool HIDAPI_DriverFlydigi_IsEnabled(void) 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; + if (SDL_IsJoystickFlydigiController(vendor_id, product_id)) { + if (vendor_id == USB_VENDOR_FLYDIGI_V1) { + if (interface_number == 2) { + // Early controllers have their custom protocol on interface 2 + return true; + } + } else { + // Newer controllers have their custom protocol on interface 1 or 2, but + // only expose one HID interface, so we'll accept any interface we see. + return true; + } + } + return false; } -static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) +static void HIDAPI_DriverFlydigi_UpdateDeviceIdentity(SDL_HIDAPI_Device *device) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; + + Uint8 controller_type = SDL_FLYDIGI_UNKNOWN; + switch (ctx->deviceID) { + case 19: + controller_type = SDL_FLYDIGI_APEX2; + break; + case 24: + case 26: + case 29: + controller_type = SDL_FLYDIGI_APEX3; + break; + case 84: + controller_type = SDL_FLYDIGI_APEX4; + break; + case 20: + case 21: + case 23: + controller_type = SDL_FLYDIGI_VADER2; + break; + case 22: + controller_type = SDL_FLYDIGI_VADER2_PRO; + break; + case 28: + controller_type = SDL_FLYDIGI_VADER3; + break; + case 80: + case 81: + controller_type = SDL_FLYDIGI_VADER3_PRO; + break; + case 85: + case 91: + case 105: + controller_type = SDL_FLYDIGI_VADER4_PRO; + break; + case 128: + case 129: + controller_type = SDL_FLYDIGI_APEX5; + break; + case 130: + controller_type = SDL_FLYDIGI_VADER5_PRO; + break; + case 133: + case 134: + controller_type = SDL_FLYDIGI_APEX5; + break; + default: + // Try to guess from the name of the controller + if (SDL_strcasestr(device->name, "VADER") != NULL) { + if (SDL_strstr(device->name, "VADER2") != NULL) { + controller_type = SDL_FLYDIGI_VADER2; + } else if (SDL_strstr(device->name, "VADER3") != NULL) { + controller_type = SDL_FLYDIGI_VADER3; + } else if (SDL_strstr(device->name, "VADER4") != NULL) { + controller_type = SDL_FLYDIGI_VADER4_PRO; + } else if (SDL_strstr(device->name, "Vader 5") != NULL) { + controller_type = SDL_FLYDIGI_VADER5_PRO; + } + } else if (SDL_strstr(device->name, "APEX") != NULL) { + if (SDL_strstr(device->name, "APEX2") != NULL) { + controller_type = SDL_FLYDIGI_APEX2; + } else if (SDL_strstr(device->name, "APEX3") != NULL) { + controller_type = SDL_FLYDIGI_APEX3; + } else if (SDL_strstr(device->name, "APEX4") != NULL) { + controller_type = SDL_FLYDIGI_APEX4; + } else if (SDL_strstr(device->name, "APEX5") != NULL) { + controller_type = SDL_FLYDIGI_APEX5; + } + } + break; + } + device->guid.data[15] = controller_type; + + // This is the previous sensor default of 125hz. + // Override this in the switch statement below based on observed sensor packet rate. + ctx->sensor_timestamp_step_ns = SDL_NS_PER_SECOND / 125; + + switch (controller_type) { + case SDL_FLYDIGI_APEX2: + HIDAPI_SetDeviceName(device, "Flydigi Apex 2"); + break; + case SDL_FLYDIGI_APEX3: + HIDAPI_SetDeviceName(device, "Flydigi Apex 3"); + break; + case SDL_FLYDIGI_APEX4: + // The Apex 4 controller has sensors, but they're only reported when gyro mouse is enabled + HIDAPI_SetDeviceName(device, "Flydigi Apex 4"); + break; + case SDL_FLYDIGI_APEX5: + HIDAPI_SetDeviceName(device, "Flydigi Apex 5"); + ctx->has_lmrm = true; + ctx->sensors_supported = true; + ctx->accelScale = SDL_STANDARD_GRAVITY / 4096.0f; + ctx->gyroScale = DEG2RAD(2000.0f); + ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_APEX5_DONGLE_NS : SENSOR_INTERVAL_APEX5_WIRED_NS; + break; + case SDL_FLYDIGI_VADER2: + // The Vader 2 controller has sensors, but they're only reported when gyro mouse is enabled + HIDAPI_SetDeviceName(device, "Flydigi Vader 2"); + ctx->has_cz = true; + break; + case SDL_FLYDIGI_VADER2_PRO: + HIDAPI_SetDeviceName(device, "Flydigi Vader 2 Pro"); + ctx->has_cz = true; + break; + case SDL_FLYDIGI_VADER3: + HIDAPI_SetDeviceName(device, "Flydigi Vader 3"); + ctx->has_cz = true; + break; + case SDL_FLYDIGI_VADER3_PRO: + HIDAPI_SetDeviceName(device, "Flydigi Vader 3 Pro"); + ctx->has_cz = true; + ctx->sensors_supported = true; + ctx->accelScale = SDL_STANDARD_GRAVITY / 256.0f; + ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS : SENSOR_INTERVAL_VADER4_PRO_WIRED_NS; + break; + case SDL_FLYDIGI_VADER4_PRO: + HIDAPI_SetDeviceName(device, "Flydigi Vader 4 Pro"); + ctx->has_cz = true; + ctx->sensors_supported = true; + ctx->accelScale = SDL_STANDARD_GRAVITY / 256.0f; + ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS : SENSOR_INTERVAL_VADER4_PRO_WIRED_NS; + break; + case SDL_FLYDIGI_VADER5_PRO: + HIDAPI_SetDeviceName(device, "Flydigi Vader 5 Pro"); + ctx->has_cz = true; + ctx->has_lmrm = true; + ctx->has_circle = true; + ctx->sensors_supported = true; + ctx->accelScale = SDL_STANDARD_GRAVITY / 4096.0f; + ctx->gyroScale = DEG2RAD(2000.0f); + ctx->sensor_timestamp_step_ns = SENSOR_INTERVAL_VADER5_PRO_NS; + break; + default: + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Unknown FlyDigi controller with ID %d, name '%s'", ctx->deviceID, device->name); + break; + } +} + +static void HIDAPI_DriverFlydigi_SetAvailable(SDL_HIDAPI_Device *device, bool available) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; + + if (available == ctx->available) { + return; + } + + if (available) { + if (device->num_joysticks == 0) { + HIDAPI_JoystickConnected(device, NULL); + } + } else { + if (device->num_joysticks > 0) { + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + } + ctx->available = available; +} + +static int HIDAPI_DriverFlydigi_WritePacket(SDL_HIDAPI_Device *device, const Uint8 *data, size_t size) +{ + // We know that at the very least the Vader 5 now uses unnumbered reports for commands instead of FLYDIGI_V2_CMD_REPORT_ID. + // If other Flydigi things prove to do the same, we can tweak this check to be more general. + bool bUsesUnnumberedReports = (device->vendor_id == USB_VENDOR_FLYDIGI_V2 && device->product_id == USB_PRODUCT_FLYDIGI_V2_VADER); + + if (bUsesUnnumberedReports && data[0] == FLYDIGI_V2_CMD_REPORT_ID) { + // Zero out the report byte. + Uint8 output[USB_PACKET_LENGTH]; + SDL_memcpy(output, data, SDL_min(size, USB_PACKET_LENGTH)); + output[0] = 0; + return SDL_hid_write(device->dev, output, SDL_min(size, USB_PACKET_LENGTH)); + } + + return SDL_hid_write(device->dev, data, size); +} + + +static bool HIDAPI_DriverFlydigi_InitControllerV1(SDL_HIDAPI_Device *device) { SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; // Detecting the Vader 2 can take over 1000 read retries, so be generous here for (int attempt = 0; ctx->deviceID == 0 && attempt < 30; ++attempt) { - const Uint8 request[] = { FLYDIGI_CMD_REPORT_ID, FLYDIGI_GET_INFO_COMMAND, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + const Uint8 request[] = { FLYDIGI_V1_CMD_REPORT_ID, FLYDIGI_V1_GET_INFO_COMMAND, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // This write will occasionally return -1, so ignore failure here and try again - (void)SDL_hid_write(device->dev, request, sizeof(request)); + (void)HIDAPI_DriverFlydigi_WritePacket(device, request, sizeof(request)); // Read the reply for (int i = 0; i < 100; ++i) { @@ -119,7 +341,7 @@ static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) #endif if (size == 32 && data[15] == 236) { ctx->deviceID = data[3]; - ctx->firmware_version = data[9] | (data[10] << 8); + ctx->firmware_version = LOAD16(data[9], data[10]); char serial[9]; (void)SDL_snprintf(serial, sizeof(serial), "%.2x%.2x%.2x%.2x", data[5], data[6], data[7], data[8]); @@ -147,80 +369,193 @@ static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) } } - 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; + HIDAPI_DriverFlydigi_UpdateDeviceIdentity(device); + + HIDAPI_DriverFlydigi_SetAvailable(device, true); + + return true; +} + +static bool GetReply(SDL_HIDAPI_Device* device, Uint8 command, Uint8* data, size_t length) +{ + for (int i = 0; i < 100; ++i) { + SDL_Delay(1); + + int size = SDL_hid_read_timeout(device->dev, data, length, 0); + if (size < 0) { + break; + } + if (size == 0) { + continue; + } + +#ifdef DEBUG_FLYDIGI_PROTOCOL + HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size); +#endif + + if (size == 32) { + if (data[1] == FLYDIGI_V2_MAGIC1 && data[2] == FLYDIGI_V2_MAGIC2) { + // Skip the report ID + SDL_memmove(&data[0], &data[1], size - 1); + data[size - 1] = 0; } - } 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; + if (data[0] == FLYDIGI_V2_MAGIC1 && data[1] == FLYDIGI_V2_MAGIC2 && data[2] == command) { + return true; } } } - device->guid.data[15] = ctx->deviceID; + return false; +} - // 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; +static bool SDL_HIDAPI_Flydigi_SendInfoRequest(SDL_HIDAPI_Device *device) +{ + const Uint8 cmd[] = { + FLYDIGI_V2_CMD_REPORT_ID, + FLYDIGI_V2_MAGIC1, + FLYDIGI_V2_MAGIC2, + FLYDIGI_V2_GET_INFO_COMMAND, + 2, + 0 + }; - switch (ctx->deviceID) { - case 19: - HIDAPI_SetDeviceName(device, "Flydigi Apex 2"); + if (HIDAPI_DriverFlydigi_WritePacket(device, cmd, sizeof(cmd)) < 0) { + return SDL_SetError("Couldn't query controller info"); + } + return true; +} + +static void HIDAPI_DriverFlydigi_HandleInfoResponse(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) +{ + SDL_PowerState state; + int percent; + Uint8 status = (data[11] >> 4) & 0x0F; + Uint8 level = (data[11] & 0x0F); + + switch (status) { + case 0: + state = SDL_POWERSTATE_ON_BATTERY; + percent = level * 20; break; - case 24: - case 26: - case 29: - HIDAPI_SetDeviceName(device, "Flydigi Apex 3"); + case 1: + state = SDL_POWERSTATE_CHARGING; + percent = level * 20; 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; + case 2: + state = SDL_POWERSTATE_CHARGED; + percent = 100; break; default: - SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Unknown FlyDigi controller with ID %d, name '%s'", ctx->deviceID, device->name); + state = SDL_POWERSTATE_UNKNOWN; + percent = 0; break; } + SDL_SendJoystickPowerInfo(joystick, state, percent); +} + +static bool SDL_HIDAPI_Flydigi_SendStatusRequest(SDL_HIDAPI_Device *device) +{ + const Uint8 cmd[] = { + FLYDIGI_V2_CMD_REPORT_ID, + FLYDIGI_V2_MAGIC1, + FLYDIGI_V2_MAGIC2, + FLYDIGI_V2_GET_STATUS_COMMAND + }; + + if (HIDAPI_DriverFlydigi_WritePacket(device, cmd, sizeof(cmd)) < 0) { + return SDL_SetError("Couldn't query controller status"); + } + return true; +} + +static void HIDAPI_DriverFlydigi_HandleStatusResponse(SDL_HIDAPI_Device *device, Uint8 *data, int size) +{ + if (data[9] == 1) { + HIDAPI_DriverFlydigi_SetAvailable(device, true); + } else { + // Click "Allow third-party apps to take over mappings" in the FlyDigi Space Station app + HIDAPI_DriverFlydigi_SetAvailable(device, false); + } +} + +static bool SDL_HIDAPI_Flydigi_SendAcquireRequest(SDL_HIDAPI_Device *device, bool acquire) +{ + const Uint8 cmd[32] = { + FLYDIGI_V2_CMD_REPORT_ID, + FLYDIGI_V2_MAGIC1, + FLYDIGI_V2_MAGIC2, + FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND, + 23, + acquire ? 1 : 0, + 'S', 'D', 'L', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + if (HIDAPI_DriverFlydigi_WritePacket(device, cmd, sizeof(cmd)) < 0) { + return SDL_SetError("Couldn't send acquire command"); + } + return true; +} + +static void HIDAPI_DriverFlydigi_HandleAcquireResponse(SDL_HIDAPI_Device *device, Uint8 *data, int size) +{ + if (data[5] != 1 && data[6] == 0) { + // Controller acquiring failed or has been disabled + HIDAPI_DriverFlydigi_SetAvailable(device, false); + } +} + +static bool HIDAPI_DriverFlydigi_InitControllerV2(SDL_HIDAPI_Device *device) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; + + Uint8 data[USB_PACKET_LENGTH]; + if (!SDL_HIDAPI_Flydigi_SendInfoRequest(device)) { + return false; + } + if (!GetReply(device, FLYDIGI_V2_GET_INFO_COMMAND, data, sizeof(data))) { + return SDL_SetError("Couldn't get controller info"); + } + + // Check the firmware version + Uint16 min_firmware_version; + ctx->firmware_version = LOAD16(data[16], data[15]); + switch (device->product_id) { + case USB_PRODUCT_FLYDIGI_V2_APEX: + // Minimum supported firmware version, Apex 5 + min_firmware_version = 0x7031; + break; + case USB_PRODUCT_FLYDIGI_V2_VADER: + // Minimum supported firmware version, Vader 5 Pro + min_firmware_version = 0x7141; + break; + default: + // Unknown product, presumably this version is okay? + min_firmware_version = 0; + break; + } + if (ctx->firmware_version < min_firmware_version) { + return SDL_SetError("Unsupported firmware version"); + } + + switch (data[6]) { + case 1: + // Wired connection + ctx->wireless = false; + break; + case 2: + // Wireless connection + ctx->wireless = true; + break; + default: + break; + } + ctx->deviceID = data[5]; + + HIDAPI_DriverFlydigi_UpdateDeviceIdentity(device); + + // See whether we can acquire the controller + SDL_HIDAPI_Flydigi_SendStatusRequest(device); + + return true; } static bool HIDAPI_DriverFlydigi_InitDevice(SDL_HIDAPI_Device *device) @@ -229,11 +564,15 @@ static bool HIDAPI_DriverFlydigi_InitDevice(SDL_HIDAPI_Device *device) if (!ctx) { return false; } + ctx->device = device; + device->context = ctx; - UpdateDeviceIdentity(device); - - return HIDAPI_JoystickConnected(device, NULL); + if (device->vendor_id == USB_VENDOR_FLYDIGI_V1) { + return HIDAPI_DriverFlydigi_InitControllerV1(device); + } else { + return HIDAPI_DriverFlydigi_InitControllerV2(device); + } } static int HIDAPI_DriverFlydigi_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) @@ -245,10 +584,6 @@ static void HIDAPI_DriverFlydigi_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, { } -#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; @@ -262,6 +597,12 @@ static bool HIDAPI_DriverFlydigi_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy if (ctx->has_cz) { joystick->nbuttons += 2; } + if (ctx->has_lmrm) { + joystick->nbuttons += 2; + } + if (ctx->has_circle) { + joystick->nbuttons += 1; + } joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; joystick->nhats = 1; @@ -270,23 +611,31 @@ static bool HIDAPI_DriverFlydigi_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy } 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; + const float flSensorRate = ctx->wireless ? (float)SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ : (float)SENSOR_INTERVAL_VADER4_PRO_WIRED_RATE_HZ; SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, flSensorRate); SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, flSensorRate); } - return true; } static bool HIDAPI_DriverFlydigi_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) { - 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 (device->vendor_id == USB_VENDOR_FLYDIGI_V1) { + Uint8 rumble_packet[] = { FLYDIGI_V1_CMD_REPORT_ID, FLYDIGI_V1_HAPTIC_COMMAND, 0x00, 0x00 }; + rumble_packet[2] = low_frequency_rumble >> 8; + rumble_packet[3] = high_frequency_rumble >> 8; - if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { - return SDL_SetError("Couldn't send rumble packet"); + if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + } else { + Uint8 rumble_packet[] = { FLYDIGI_V2_CMD_REPORT_ID, FLYDIGI_V2_MAGIC1, FLYDIGI_V2_MAGIC2, FLYDIGI_V2_HAPTIC_COMMAND, 6, 0, 0, 0, 0, 0 }; + rumble_packet[5] = low_frequency_rumble >> 8; + rumble_packet[6] = high_frequency_rumble >> 8; + + if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } } return true; } @@ -320,14 +669,11 @@ static bool HIDAPI_DriverFlydigi_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *de } return SDL_Unsupported(); } -static void HIDAPI_DriverFlydigi_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) + +static void HIDAPI_DriverFlydigi_HandleStatePacketV1(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; @@ -335,28 +681,28 @@ static void HIDAPI_DriverFlydigi_HandleStatePacket(SDL_Joystick *joystick, SDL_D Uint8 hat; switch (data[9] & 0x0F) { - case 0b0001: + case 0x01u: hat = SDL_HAT_UP; break; - case 0b0011: + case 0x02u | 0x01u: hat = SDL_HAT_RIGHTUP; break; - case 0b0010: + case 0x02u: hat = SDL_HAT_RIGHT; break; - case 0b0110: + case 0x02u | 0x04u: hat = SDL_HAT_RIGHTDOWN; break; - case 0b0100: + case 0x04u: hat = SDL_HAT_DOWN; break; - case 0b1100: + case 0x08u | 0x04u: hat = SDL_HAT_LEFTDOWN; break; - case 0b1000: + case 0x08u: hat = SDL_HAT_LEFT; break; - case 0b1001: + case 0x08u | 0x01u: hat = SDL_HAT_LEFTUP; break; default: @@ -394,9 +740,9 @@ static void HIDAPI_DriverFlydigi_HandleStatePacket(SDL_Joystick *joystick, SDL_D 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)); + // 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)); + // SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[8] & 0x10) != 0)); } #define READ_STICK_AXIS(offset) \ @@ -437,46 +783,255 @@ static void HIDAPI_DriverFlydigi_HandleStatePacket(SDL_Joystick *joystick, SDL_D // 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 + const float flAccelScale = ctx->accelScale; + values[0] = -LOAD16(data[11], data[12]) * flAccelScale; // Acceleration along pitch axis + values[1] = LOAD16(data[15], data[16]) * flAccelScale; // Acceleration along yaw axis + values[2] = LOAD16(data[13], data[14]) * flAccelScale; // Acceleration along roll axis SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, values, 3); } SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); } +static void HIDAPI_DriverFlydigi_HandlePacketV1(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) +{ + if (data[0] != 0x04 || data[1] != 0xFE) { + // We don't know how to handle this report, ignore it + return; + } + + if (joystick) { + HIDAPI_DriverFlydigi_HandleStatePacketV1(joystick, ctx, data, size); + } +} + +static void HIDAPI_DriverFlydigi_HandleStatePacketV2(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + Uint8 extra_button_index = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS; + + if (ctx->last_state[11] != data[11]) { + Uint8 hat; + + switch (data[11] & 0x0F) { + case 0x01u: + hat = SDL_HAT_UP; + break; + case 0x02u | 0x01u: + hat = SDL_HAT_RIGHTUP; + break; + case 0x02u: + hat = SDL_HAT_RIGHT; + break; + case 0x02u | 0x04u: + hat = SDL_HAT_RIGHTDOWN; + break; + case 0x04u: + hat = SDL_HAT_DOWN; + break; + case 0x08u | 0x04u: + hat = SDL_HAT_LEFTDOWN; + break; + case 0x08u: + hat = SDL_HAT_LEFT; + break; + case 0x08u | 0x01u: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[11] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[11] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[11] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[11] & 0x80) != 0)); + } + + if (ctx->last_state[12] != data[12]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[12] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[12] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[12] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[12] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[12] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[12] & 0x80) != 0)); + } + + if (ctx->last_state[13] != data[13]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M1, ((data[13] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M2, ((data[13] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M3, ((data[13] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M4, ((data[13] & 0x20) != 0)); + if (ctx->has_cz) { + SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[13] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[13] & 0x02) != 0)); + } + if (ctx->has_lmrm) { + SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[13] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[13] & 0x80) != 0)); + } + } else { + if (ctx->has_cz) { + extra_button_index += 2; + } + if (ctx->has_lmrm) { + extra_button_index += 2; + } + } + + if (ctx->last_state[14] != data[14]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[14] & 0x08) != 0)); + if (ctx->has_circle) { + SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[14] & 0x01) != 0)); + } + } + + axis = LOAD16(data[3], data[4]); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = -LOAD16(data[5], data[6]); + if (axis <= -32768) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = LOAD16(data[7], data[8]); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = -LOAD16(data[9], data[10]); + if (axis <= -32768) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + +#define READ_TRIGGER_AXIS(offset) \ + (Sint16)(((int)data[offset] * 257) - 32768) + { + axis = READ_TRIGGER_AXIS(15); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = READ_TRIGGER_AXIS(16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + } +#undef READ_TRIGGER_AXIS + + if (ctx->sensors_enabled) { + Uint64 sensor_timestamp; + float values[3]; + + // Advance the imu sensor time stamp based on the observed rate of receipt of packets in the testcontroller app. + // This varies between Product ID and connection type. + sensor_timestamp = ctx->sensor_timestamp_ns; + ctx->sensor_timestamp_ns += ctx->sensor_timestamp_step_ns; + + const float flGyroScale = ctx->gyroScale; + values[0] = HIDAPI_RemapVal((float)LOAD16(data[17], data[18]), INT16_MIN, INT16_MAX, -flGyroScale, flGyroScale); + values[1] = HIDAPI_RemapVal((float)LOAD16(data[21], data[22]), INT16_MIN, INT16_MAX, -flGyroScale, flGyroScale); + values[2] = HIDAPI_RemapVal(-(float)LOAD16(data[19], data[20]), INT16_MIN, INT16_MAX, -flGyroScale, flGyroScale); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, values, 3); + + const float flAccelScale = ctx->accelScale; + values[0] = LOAD16(data[23], data[24]) * flAccelScale; // Acceleration along pitch axis + values[1] = LOAD16(data[27], data[28]) * flAccelScale; // Acceleration along yaw axis + values[2] = -LOAD16(data[25], data[26]) * flAccelScale; // Acceleration along roll axis + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, values, 3); + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static void HIDAPI_DriverFlydigi_HandleStatusUpdate(SDL_HIDAPI_Device *device, Uint8 *data, int size) +{ + // The status changed, see if we can acquire the controller now + SDL_HIDAPI_Flydigi_SendStatusRequest(device); +} + +static void HIDAPI_DriverFlydigi_HandlePacketV2(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) +{ + if (size > 0 && data[0] != 0x5A) { + // If first byte is not 0x5A, it must be REPORT_ID, we need to remove it. + ++data; + --size; + } + if (size < 31 || data[0] != FLYDIGI_V2_MAGIC1 || data[1] != FLYDIGI_V2_MAGIC2) { + // We don't know how to handle this report, ignore it + return; + } + + switch (data[2]) { + case FLYDIGI_V2_GET_INFO_COMMAND: + if (joystick) { + HIDAPI_DriverFlydigi_HandleInfoResponse(joystick, ctx, data, size); + } + break; + case FLYDIGI_V2_SET_STATUS_COMMAND: + HIDAPI_DriverFlydigi_HandleStatusUpdate(ctx->device, data, size); + break; + case FLYDIGI_V2_GET_STATUS_COMMAND: + HIDAPI_DriverFlydigi_HandleStatusResponse(ctx->device, data, size); + break; + case FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND: + HIDAPI_DriverFlydigi_HandleAcquireResponse(ctx->device, data, size); + break; + case FLYDIGI_V2_INPUT_REPORT: + if (joystick) { + HIDAPI_DriverFlydigi_HandleStatePacketV2(joystick, ctx, data, size); + } + break; + default: + // We don't recognize this command, ignore it + break; + } +} + static bool HIDAPI_DriverFlydigi_UpdateDevice(SDL_HIDAPI_Device *device) { SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; SDL_Joystick *joystick = NULL; Uint8 data[USB_PACKET_LENGTH]; int size = 0; + Uint64 now = SDL_GetTicks(); if (device->num_joysticks > 0) { joystick = SDL_GetJoystickFromID(device->joysticks[0]); - } else { - return false; + } + + if (device->vendor_id == USB_VENDOR_FLYDIGI_V2 && joystick) { + if (!ctx->next_heartbeat || now >= ctx->next_heartbeat) { + SDL_HIDAPI_Flydigi_SendAcquireRequest(device, true); + SDL_HIDAPI_Flydigi_SendInfoRequest(device); + ctx->next_heartbeat = now + FLYDIGI_ACQUIRE_CONTROLLER_HEARTBEAT_TIME; + } } while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { #ifdef DEBUG_FLYDIGI_PROTOCOL HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size); #endif - if (!joystick) { - continue; - } + ctx->last_packet = now; - HIDAPI_DriverFlydigi_HandleStatePacket(joystick, ctx, data, size); + if (device->vendor_id == USB_VENDOR_FLYDIGI_V1) { + HIDAPI_DriverFlydigi_HandlePacketV1(joystick, ctx, data, size); + } else { + HIDAPI_DriverFlydigi_HandlePacketV2(joystick, ctx, data, size); + } } - if (size < 0) { + if (device->vendor_id == USB_VENDOR_FLYDIGI_V2) { + // If we haven't gotten a packet in a while, check to make sure we can still acquire it + const int INPUT_TIMEOUT_MS = 100; + if (now >= (ctx->last_packet + INPUT_TIMEOUT_MS)) { + ctx->next_heartbeat = now; + } + } + + if (size < 0 && device->num_joysticks > 0) { // Read error, device is disconnected HIDAPI_JoystickDisconnected(device, device->joysticks[0]); } @@ -485,6 +1040,9 @@ static bool HIDAPI_DriverFlydigi_UpdateDevice(SDL_HIDAPI_Device *device) static void HIDAPI_DriverFlydigi_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) { + // Don't unacquire the controller, someone else might be using it too. + // The controller will automatically unacquire itself after a little while + //SDL_HIDAPI_Flydigi_SendAcquireRequest(device, false); } static void HIDAPI_DriverFlydigi_FreeDevice(SDL_HIDAPI_Device *device) diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.h b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.h new file mode 100644 index 00000000..cf60bed0 --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.h @@ -0,0 +1,38 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +// These are values used in the controller type byte of the controller GUID + +typedef enum +{ + SDL_FLYDIGI_UNKNOWN, + SDL_FLYDIGI_APEX2 = (1 << 0), + SDL_FLYDIGI_APEX3, + SDL_FLYDIGI_APEX4, + SDL_FLYDIGI_APEX5, + SDL_FLYDIGI_VADER2 = (1 << 4), + SDL_FLYDIGI_VADER2_PRO, + SDL_FLYDIGI_VADER3, + SDL_FLYDIGI_VADER3_PRO, + SDL_FLYDIGI_VADER4_PRO, + SDL_FLYDIGI_VADER5_PRO, +} SDL_FlyDigiControllerType; + diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c index b95fb89c..3b41e823 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -23,6 +23,7 @@ #ifdef SDL_JOYSTICK_HIDAPI #include "../../SDL_hints_c.h" +#include "../../misc/SDL_libusb.h" #include "../SDL_sysjoystick.h" #include "SDL_hidapijoystick_c.h" #include "SDL_hidapi_rumble.h" @@ -36,6 +37,7 @@ #endif #define MAX_CONTROLLERS 4 +#define PC_RUMBLE_REFRESH 16 typedef struct { @@ -49,6 +51,8 @@ typedef struct // Without this variable, hid_write starts to lag a TON bool rumbleUpdate; bool useRumbleBrake; + bool rumbleActive; + Uint64 pc_rumble_sent; } SDL_DriverGameCube_Context; static void HIDAPI_DriverGameCube_RegisterHints(SDL_HintCallback callback, void *userdata) @@ -102,6 +106,83 @@ static void SDLCALL SDL_JoystickGameCubeRumbleBrakeHintChanged(void *userdata, c } } +static bool HIDAPI_DriverGameCube_EnableAdapter(SDL_HIDAPI_Device *device) +{ +#ifdef HAVE_LIBUSB + // Need to close the device while sending USB commands to it + SDL_hid_close(device->dev); + + // This is needed to enable input for Nyko and EVORETRO GameCube adapters + SDL_LibUSBContext *libusb_ctx; + if (SDL_InitLibUSB(&libusb_ctx)) { + libusb_context *context = NULL; + libusb_device **devs = NULL; + libusb_device_handle *handle = NULL; + struct libusb_device_descriptor desc; + ssize_t i, num_devs; + bool kernel_detached = false; + + if (libusb_ctx->init(&context) == 0) { + num_devs = libusb_ctx->get_device_list(context, &devs); + for (i = 0; i < num_devs; ++i) { + if (libusb_ctx->get_device_descriptor(devs[i], &desc) != 0) { + continue; + } + + if (desc.idVendor != USB_VENDOR_NINTENDO || + desc.idProduct != USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) { + continue; + } + + if (libusb_ctx->open(devs[i], &handle) != 0) { + continue; + } + + if (libusb_ctx->kernel_driver_active(handle, 0)) { + if (libusb_ctx->detach_kernel_driver(handle, 0) == 0) { + kernel_detached = true; + } + } + + if (libusb_ctx->claim_interface(handle, 0) == 0) { + libusb_ctx->control_transfer(handle, 0x21, 11, 0x0001, 0, NULL, 0, 1000); + libusb_ctx->release_interface(handle, 0); + } + + if (kernel_detached) { + libusb_ctx->attach_kernel_driver(handle, 0); + } + + libusb_ctx->close(handle); + } + + libusb_ctx->free_device_list(devs, 1); + + libusb_ctx->exit(context); + } + SDL_QuitLibUSB(); + } + + // Reopen the device now that we're done + device->dev = SDL_hid_open_path(device->path); + if (!device->dev) { + return false; + } +#endif // HAVE_LIBUSB + + Uint8 initMagic = 0x13; + if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "HIDAPI_DriverGameCube_InitDevice(): Couldn't initialize WUP-028"); + return false; + } + + // Wait for the adapter to initialize + SDL_Delay(10); + + return true; +} + static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) { SDL_DriverGameCube_Context *ctx; @@ -109,13 +190,8 @@ static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) Uint8 *curSlot; Uint8 i; int size; - Uint8 initMagic = 0x13; Uint8 rumbleMagic = 0x11; -#ifdef HAVE_ENABLE_GAMECUBE_ADAPTORS - SDL_EnableGameCubeAdaptors(); -#endif - ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx)); if (!ctx) { return false; @@ -129,19 +205,35 @@ static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) } if (ctx->pc_mode) { + // Check to see if this firmware supports rumble + bool rumbleAllowed = false; + if ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) { +#ifdef DEBUG_GAMECUBE_PROTOCOL + HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size); +#endif + if (size == 9) { + // This is firmware version 0x7 or newer + // Rumble is supported if the second USB cable is plugged in + rumbleAllowed = true; + } + } +#ifdef SDL_PLATFORM_WIN32 + // We get a separate device for each slot + ctx->rumbleAllowed[0] = rumbleAllowed; ResetAxisRange(ctx, 0); HIDAPI_JoystickConnected(device, &ctx->joysticks[0]); +#else + for (i = 0; i < MAX_CONTROLLERS; ++i) { + ctx->rumbleAllowed[i] = rumbleAllowed; + ResetAxisRange(ctx, i); + HIDAPI_JoystickConnected(device, &ctx->joysticks[i]); + } +#endif } else { - // This is all that's needed to initialize the device. Really! - if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) { - SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, - "HIDAPI_DriverGameCube_InitDevice(): Couldn't initialize WUP-028"); + if (!HIDAPI_DriverGameCube_EnableAdapter(device)) { return false; } - // Wait for the adapter to initialize - SDL_Delay(10); - // Add all the applicable joysticks while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) { #ifdef DEBUG_GAMECUBE_PROTOCOL @@ -200,14 +292,25 @@ static void HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device { } -static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, const Uint8 *packet, bool invert_c_stick) +static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, Uint8 slot, const Uint8 *packet, bool invert_c_stick) { SDL_Joystick *joystick; - const Uint8 i = 0; // We have a separate context for each connected controller in PC mode, just use the first index + Uint8 i; Uint8 v; Sint16 axis_value; Uint64 timestamp = SDL_GetTicksNS(); +#ifdef SDL_PLATFORM_WIN32 + // We get a separate device for each slot + i = 0; +#else + i = slot; + if (i >= MAX_CONTROLLERS) { + // Invalid packet? + return; + } +#endif + joystick = SDL_GetJoystickFromID(ctx->joysticks[i]); if (!joystick) { // Hasn't been opened yet, skip @@ -341,6 +444,14 @@ static void HIDAPI_DriverGameCube_HandleNintendoPacket(SDL_HIDAPI_Device *device } } +static void HIDAPI_DriverGameCube_SendPCRumble(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx) +{ + Uint8 rumblepkt[3] = { 0x00, 0x00, 0x00 }; + rumblepkt[1] = rumblepkt[2] = ctx->rumbleActive ? 0xFF : 0x00; + SDL_HIDAPI_SendRumble(device, rumblepkt, sizeof(rumblepkt)); + ctx->pc_rumble_sent = SDL_GetTicks(); +} + static bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device) { SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; @@ -357,11 +468,11 @@ static bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device) // This is the older firmware // The first byte is the index of the connected controller // The C stick has an inverted value range compared to the left stick - HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, &packet[1], true); + HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet[0] - 1, &packet[1], true); } else if (size == 9) { // This is the newer firmware (version 0x7) // The C stick has the same value range compared to the left stick - HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, false); + HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, 0, packet, false); } else { // How do we handle this packet? } @@ -370,6 +481,11 @@ static bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device) } } + // PC_Mode rumble needs constant packets in order to keep rumble going + if (ctx->pc_mode && ctx->rumbleActive && SDL_GetTicks() >= (ctx->pc_rumble_sent + PC_RUMBLE_REFRESH)) { + HIDAPI_DriverGameCube_SendPCRumble(device, ctx); + } + // Write rumble packet if (ctx->rumbleUpdate) { SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble)); @@ -410,33 +526,51 @@ static bool HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_ SDL_AssertJoysticksLocked(); if (ctx->pc_mode) { - return SDL_Unsupported(); - } - - for (i = 0; i < MAX_CONTROLLERS; i += 1) { - if (joystick->instance_id == ctx->joysticks[i]) { - if (ctx->wireless[i]) { - return SDL_SetError("Nintendo GameCube WaveBird controllers do not support rumble"); - } - if (!ctx->rumbleAllowed[i]) { - return SDL_SetError("Second USB cable for WUP-028 not connected"); - } - if (ctx->useRumbleBrake) { - if (low_frequency_rumble == 0 && high_frequency_rumble > 0) { - val = 0; // if only low is 0 we want to do a regular stop - } else if (low_frequency_rumble == 0 && high_frequency_rumble == 0) { - val = 2; // if both frequencies are 0 we want to do a hard stop - } else { - val = 1; // normal rumble + for (i = 0; i < MAX_CONTROLLERS; i += 1) { + if (joystick->instance_id == ctx->joysticks[i]) { + if (!ctx->rumbleAllowed[i]) { + return SDL_SetError("Rumble is disabled because the adapter is not at least of firmware 0x7"); } - } else { - val = (low_frequency_rumble > 0 || high_frequency_rumble > 0); + bool shouldrumble = false, coast = false; + if (ctx->useRumbleBrake) { + coast = (low_frequency_rumble == 0 && high_frequency_rumble > 0); + } + shouldrumble = (low_frequency_rumble > 0 || high_frequency_rumble > 0); + if (coast) { + ctx->rumbleActive = false; + } else if (shouldrumble != ctx->rumbleActive) { + ctx->rumbleActive = shouldrumble; + HIDAPI_DriverGameCube_SendPCRumble(device, ctx); + } + return true; } - if (val != ctx->rumble[i + 1]) { - ctx->rumble[i + 1] = val; - ctx->rumbleUpdate = true; + } + } else { + for (i = 0; i < MAX_CONTROLLERS; i += 1) { + if (joystick->instance_id == ctx->joysticks[i]) { + if (ctx->wireless[i]) { + return SDL_SetError("Nintendo GameCube WaveBird controllers do not support rumble"); + } + if (!ctx->rumbleAllowed[i]) { + return SDL_SetError("Second USB cable for WUP-028 not connected"); + } + if (ctx->useRumbleBrake) { + if (low_frequency_rumble == 0 && high_frequency_rumble > 0) { + val = 0; // if only low is 0 we want to do a regular stop + } else if (low_frequency_rumble == 0 && high_frequency_rumble == 0) { + val = 2; // if both frequencies are 0 we want to do a hard stop + } else { + val = 1; // normal rumble + } + } else { + val = (low_frequency_rumble > 0 || high_frequency_rumble > 0); + } + if (val != ctx->rumble[i + 1]) { + ctx->rumble[i + 1] = val; + ctx->rumbleUpdate = true; + } + return true; } - return true; } } @@ -455,10 +589,17 @@ static Uint32 HIDAPI_DriverGameCube_GetJoystickCapabilities(SDL_HIDAPI_Device *d Uint32 result = 0; SDL_AssertJoysticksLocked(); - - if (!ctx->pc_mode) { - Uint8 i; - + Uint8 i; + if (ctx->pc_mode) { + for (i = 0; i < MAX_CONTROLLERS; i += 1) { + if (joystick->instance_id == ctx->joysticks[i]) { + if (ctx->rumbleAllowed[i]) { + result |= SDL_JOYSTICK_CAP_RUMBLE; + break; + } + } + } + } else { for (i = 0; i < MAX_CONTROLLERS; i += 1) { if (joystick->instance_id == ctx->joysticks[i]) { if (!ctx->wireless[i] && ctx->rumbleAllowed[i]) { @@ -496,6 +637,11 @@ static void HIDAPI_DriverGameCube_CloseJoystick(SDL_HIDAPI_Device *device, SDL_J SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble)); ctx->rumbleUpdate = false; } + + if (ctx->pc_mode && ctx->rumbleActive) { + ctx->rumbleActive = false; + HIDAPI_DriverGameCube_SendPCRumble(device, ctx); + } } static void HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device *device) diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gip.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gip.c index 4851adc2..18df94e7 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gip.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gip.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -208,7 +208,7 @@ #define GIP_MOTOR_LEFT_IMPULSE (1u << 3) #define GIP_MOTOR_ALL 0xF -/* Extended Comand constants */ +/* Extended Command constants */ #define GIP_EXTCMD_GET_CAPABILITIES 0x00 #define GIP_EXTCMD_GET_TELEMETRY_DATA 0x01 #define GIP_EXTCMD_GET_SERIAL_NUMBER 0x04 @@ -317,6 +317,14 @@ MAKE_GUID(GUID_Wheel, 0x646979cf, 0x6b71, 0x4e96, 0x8d, 0xf9, 0x59, 0xe3, 0x98, * 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 }; @@ -789,29 +797,19 @@ static bool GIP_AttachmentIsController(GIP_Attachment *attachment) static void GIP_MetadataFree(GIP_Metadata *metadata) { - if (metadata->device.audio_formats) { - SDL_free(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[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); - } + SDL_free(metadata->device.supported_interfaces); + SDL_free(metadata->device.hid_descriptor); - if (metadata->message_metadata) { - SDL_free(metadata->message_metadata); - } - SDL_memset(metadata, 0, sizeof(*metadata)); + SDL_free(metadata->message_metadata); + SDL_zerop(metadata); } static bool GIP_ParseDeviceMetadata(GIP_Metadata *metadata, const Uint8 *bytes, int num_bytes, int *offset) @@ -1241,7 +1239,7 @@ static bool GIP_SendInitSequence(GIP_Attachment *attachment) } if (attachment->attachment_type == GIP_TYPE_CHATPAD && !attachment->keyboard) { attachment->keyboard = (SDL_KeyboardID)(uintptr_t) attachment; - SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad", true); + SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad"); } return true; } @@ -1687,7 +1685,7 @@ static bool GIP_HandleCommandGuideButtonStatus( return false; } if (bytes[1] == VK_LWIN) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (bytes[0] & 0x01) != 0); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (bytes[0] & 0x03) != 0); } return true; @@ -2327,7 +2325,7 @@ static bool GIP_HandleSystemMessage( 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); + SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad"); } attachment->attachment_type = GIP_TYPE_CHATPAD; attachment->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_HID_REPORT); @@ -2688,6 +2686,8 @@ static GIP_Attachment * HIDAPI_DriverGIP_FindAttachment(SDL_HIDAPI_Device *devic GIP_Device *ctx = (GIP_Device *)device->context; int i; + SDL_AssertJoysticksLocked(); + for (i = 0; i < MAX_ATTACHMENTS; i++) { if (ctx->attachments[i] && ctx->attachments[i]->joystick == joystick->instance_id) { return ctx->attachments[i]; @@ -2923,7 +2923,7 @@ static void HIDAPI_DriverGIP_FreeDevice(SDL_HIDAPI_Device *device) attachment->fragment_data = NULL; } if (attachment->keyboard) { - SDL_RemoveKeyboard(attachment->keyboard, true); + SDL_RemoveKeyboard(attachment->keyboard); } GIP_MetadataFree(&attachment->metadata); SDL_free(attachment); diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_lg4ff.c index 90191198..fdf5b11a 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_lg4ff.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -110,8 +110,17 @@ static void HIDAPI_DriverLg4ff_UnregisterHints(SDL_HintCallback callback, void * static bool HIDAPI_DriverLg4ff_IsEnabled(void) { - bool enabled = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, - SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); + #if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) + /* + * hid.dll simply cannot send 7 bytes reports unlike other platforms + * it enforces full length repots of 17 from the device's descriptor, which does not work on the device + * this breaks ffb and led control, so we disable this by default + */ + bool hint_default = false; + #else + bool hint_default = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT); + #endif + bool enabled = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, hint_default); return enabled; } @@ -263,12 +272,12 @@ static bool HIDAPI_DriverLg4ff_IsSupportedDevice( if (vendor_id != USB_VENDOR_ID_LOGITECH) { return false; } - for (i = 0;i < sizeof(supported_device_ids) / sizeof(Uint32);i++) { + for (i = 0;i < SDL_arraysize(supported_device_ids);i++) { if (supported_device_ids[i] == product_id) { break; } } - if (i == sizeof(supported_device_ids) / sizeof(Uint32)) { + if (i == SDL_arraysize(supported_device_ids)) { return false; } Uint16 real_id = HIDAPI_DriverLg4ff_IdentifyWheel(product_id, version); @@ -277,7 +286,7 @@ static bool HIDAPI_DriverLg4ff_IsSupportedDevice( return true; } // a supported native mode is found, send mode change command, then still state that we support the device - if (device != NULL && SDL_HIDAPI_DriverLg4ff_GetEnvInt("SDL_HIDAPI_LG4FF_NO_MODE_SWITCH", 0, 1, 0) == 0) { + if (device && SDL_HIDAPI_DriverLg4ff_GetEnvInt("SDL_HIDAPI_LG4FF_NO_MODE_SWITCH", 0, 1, 0) == 0) { HIDAPI_DriverLg4ff_SwitchMode(device, real_id); } return true; @@ -441,7 +450,7 @@ static bool HIDAPI_DriverLg4ff_SetAutoCenter(SDL_HIDAPI_Device *device, int magn // TODO do not adjust for MOMO wheels, when support is added expand_a = expand_a >> 1; - SDL_memset(cmd, 0x00, sizeof(cmd)); + SDL_zeroa(cmd); cmd[0] = 0xfe; cmd[1] = 0x0d; cmd[2] = (Uint8)(expand_a / 0xaaaa); @@ -454,7 +463,7 @@ static bool HIDAPI_DriverLg4ff_SetAutoCenter(SDL_HIDAPI_Device *device, int magn } // enable - SDL_memset(cmd, 0x00, sizeof(cmd)); + SDL_zeroa(cmd); cmd[0] = 0x14; ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); @@ -481,7 +490,7 @@ static bool HIDAPI_DriverLg4ff_InitDevice(SDL_HIDAPI_Device *device) SDL_OutOfMemory(); return false; } - SDL_memset(ctx, 0, sizeof(SDL_DriverLg4ff_Context)); + SDL_zerop(ctx); device->context = ctx; device->joystick_type = SDL_JOYSTICK_TYPE_WHEEL; diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_luna.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_luna.c index 7c889a66..b560f789 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_luna.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_luna.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_nintendo.h b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_nintendo.h index 0a5836fb..dc7622f8 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_nintendo.h +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_nintendo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps3.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps3.c index 6c82647c..79575be2 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps3.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps3.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -32,8 +32,6 @@ // Define this if you want to log all packets from the controller // #define DEBUG_PS3_PROTOCOL -#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) - typedef enum { k_EPS3ReportIdState = 1, @@ -349,6 +347,11 @@ static float HIDAPI_DriverPS3_ScaleAccel(Sint16 value) return ((float)(value - 511) / 113.0f) * SDL_STANDARD_GRAVITY; } +static float HIDAPI_DriverPS3ThirdParty_ScaleAccel(Sint16 value) +{ + return ((float)(value - 512) / 113.0f) * SDL_STANDARD_GRAVITY; +} + static void HIDAPI_DriverPS3_HandleMiniStatePacket(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size) { Sint16 axis; @@ -637,7 +640,14 @@ static bool HIDAPI_DriverPS3ThirdParty_IsSupportedDevice(SDL_HIDAPI_Device *devi // Supported third party controller return true; } else { - return false; + // Some third party controllers don't have report ids + size = ReadFeatureReport(device->dev, 0x00, data, sizeof(data)); + if (size == 9 && data[2] == 0x26) { + // Supported third party controller + return true; + } else { + return false; + } } } else { // Might be supported by this driver, enumerate and find out @@ -671,6 +681,39 @@ static bool HIDAPI_DriverPS3ThirdParty_InitDevice(SDL_HIDAPI_Device *device) HIDAPI_SetDeviceName(device, "Logitech ChillStream"); } + if (device->vendor_id == USB_VENDOR_HARMONIX) { + switch (device->product_id) { + case USB_PRODUCT_HARMONIX_WII_RB1_GUITAR: + case USB_PRODUCT_HARMONIX_WII_RB2_GUITAR: + device->joystick_type = SDL_JOYSTICK_TYPE_GUITAR; + break; + case USB_PRODUCT_HARMONIX_WII_RB1_DRUMS: + case USB_PRODUCT_HARMONIX_WII_RB2_DRUMS: + case USB_PRODUCT_HARMONIX_WII_RB3_MPA_DRUMS_MODE: + device->joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT; + break; + default: + break; + } + } + + if (device->vendor_id == USB_VENDOR_SCEA) { + switch (device->product_id) { + case USB_PRODUCT_SCEA_PS3_GH_GUITAR: + case USB_PRODUCT_SCEA_PS3_RB_GUITAR: + case USB_PRODUCT_SCEA_PS3WIIU_GHLIVE: + device->joystick_type = SDL_JOYSTICK_TYPE_GUITAR; + break; + case USB_PRODUCT_SCEA_PS3_GH_DRUMS: + case USB_PRODUCT_SCEA_PS3_RB_DRUMS: + case USB_PRODUCT_SCEA_PS3_RB3_MPA_DRUMS_MODE: + device->joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT; + break; + default: + break; + } + } + return HIDAPI_JoystickConnected(device, NULL); } @@ -705,6 +748,8 @@ static bool HIDAPI_DriverPS3ThirdParty_OpenJoystick(SDL_HIDAPI_Device *device, S joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; } + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f); + return true; } @@ -735,7 +780,11 @@ static bool HIDAPI_DriverPS3ThirdParty_SendJoystickEffect(SDL_HIDAPI_Device *dev static bool HIDAPI_DriverPS3ThirdParty_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) { - return SDL_Unsupported(); + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + ctx->report_sensors = enabled; + + return true; } static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket18(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size) @@ -973,6 +1022,15 @@ static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket19(SDL_Joystick *joystic } } + if (ctx->report_sensors) { + float sensor_data[3]; + + sensor_data[0] = -HIDAPI_DriverPS3ThirdParty_ScaleAccel(LOAD16(data[19], data[20])); + sensor_data[1] = -HIDAPI_DriverPS3ThirdParty_ScaleAccel(LOAD16(data[21], data[22])); + sensor_data[2] = -HIDAPI_DriverPS3ThirdParty_ScaleAccel(LOAD16(data[23], data[24])); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, sensor_data, SDL_arraysize(sensor_data)); + } + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); } diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps4.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps4.c index 7404bf22..188a40cb 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps4.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -44,12 +44,6 @@ #define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500 -#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) -#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \ - (((Uint32)(B)) << 8) | \ - (((Uint32)(C)) << 16) | \ - (((Uint32)(D)) << 24)) - enum { SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD = 11 @@ -104,6 +98,7 @@ typedef struct Uint8 rgucTouchpadData1[3]; Uint8 ucTouchpadCounter2; Uint8 rgucTouchpadData2[3]; + Uint8 rgucDeviceSpecific[12]; } PS4StatePacket_t; typedef struct @@ -122,6 +117,14 @@ typedef struct Uint8 ucVolumeSpeaker; } DS4EffectsState_t; +// These enum values match with the validity flags in the PS4 output report +typedef enum +{ + k_EPS4EffectRumble = (1 << 0), + k_EPS4EffectLED = (1 << 1), + k_EPS4EffectLEDBlink = (1 << 2), +} EPS4Effect; + typedef struct { Sint16 bias; @@ -152,6 +155,9 @@ typedef struct bool vibration_supported; bool touchpad_supported; bool effects_supported; + bool guitar_whammy_supported; + bool guitar_tilt_supported; + bool guitar_effects_selector_supported; HIDAPI_PS4_EnhancedReportHint enhanced_report_hint; bool enhanced_reports; bool enhanced_mode; @@ -180,7 +186,7 @@ typedef struct PS4StatePacket_t last_state; } SDL_DriverPS4_Context; -static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, bool application_usage); +static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, EPS4Effect mask, bool application_usage); static void HIDAPI_DriverPS4_RegisterHints(SDL_HintCallback callback, void *userdata) { @@ -353,6 +359,7 @@ static bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device) if (size == 48 && data[2] == 0x27) { Uint8 capabilities = data[4]; Uint8 device_type = data[5]; + Uint8 device_specific_capabilities = data[24]; Uint16 gyro_numerator = LOAD16(data[10], data[11]); Uint16 gyro_denominator = LOAD16(data[12], data[13]); Uint16 accel_numerator = LOAD16(data[14], data[15]); @@ -380,6 +387,15 @@ static bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device) break; case 0x01: joystick_type = SDL_JOYSTICK_TYPE_GUITAR; + if (device_specific_capabilities & 0x01) { + ctx->guitar_effects_selector_supported = true; + } + if (device_specific_capabilities & 0x02) { + ctx->guitar_tilt_supported = true; + } + if (device_specific_capabilities & 0x04) { + ctx->guitar_whammy_supported = true; + } break; case 0x02: joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT; @@ -653,7 +669,7 @@ static float HIDAPI_DriverPS4_ApplyCalibrationData(SDL_DriverPS4_Context *ctx, i return ((float)value - calibration->bias) * calibration->scale; } -static bool HIDAPI_DriverPS4_UpdateEffects(SDL_DriverPS4_Context *ctx, bool application_usage) +static bool HIDAPI_DriverPS4_UpdateEffects(SDL_DriverPS4_Context *ctx, EPS4Effect mask, bool application_usage) { DS4EffectsState_t effects; @@ -674,7 +690,7 @@ static bool HIDAPI_DriverPS4_UpdateEffects(SDL_DriverPS4_Context *ctx, bool appl SetLedsForPlayerIndex(&effects, ctx->player_index); } } - return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage); + return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), mask, application_usage); } static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device) @@ -722,6 +738,10 @@ static void HIDAPI_DriverPS4_SetEnhancedModeAvailable(SDL_DriverPS4_Context *ctx SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval)); } + if (ctx->guitar_tilt_supported) { + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval)); + } + if (ctx->official_controller) { ctx->report_battery = true; } @@ -737,7 +757,7 @@ static void HIDAPI_DriverPS4_SetEnhancedMode(SDL_DriverPS4_Context *ctx) ctx->enhanced_mode = true; // Switch into enhanced report mode - HIDAPI_DriverPS4_UpdateEffects(ctx, false); + HIDAPI_DriverPS4_UpdateEffects(ctx, 0, false); } } @@ -813,7 +833,7 @@ static void SDLCALL SDL_PS4ReportIntervalHintChanged(void *userdata, const char if (new_report_interval != ctx->report_interval) { ctx->report_interval = (Uint8)new_report_interval; - HIDAPI_DriverPS4_UpdateEffects(ctx, false); + HIDAPI_DriverPS4_UpdateEffects(ctx, 0, false); SDL_LockJoysticks(); SDL_PrivateJoystickSensorRate(ctx->joystick, SDL_SENSOR_GYRO, (float)(1000 / ctx->report_interval)); SDL_PrivateJoystickSensorRate(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval)); @@ -833,7 +853,7 @@ static void HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL // This will set the new LED state based on the new player index // SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode - HIDAPI_DriverPS4_UpdateEffects(ctx, false); + HIDAPI_DriverPS4_UpdateEffects(ctx, k_EPS4EffectLED, false); } static bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) @@ -880,7 +900,7 @@ static bool HIDAPI_DriverPS4_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joyst ctx->rumble_left = (low_frequency_rumble >> 8); ctx->rumble_right = (high_frequency_rumble >> 8); - return HIDAPI_DriverPS4_UpdateEffects(ctx, true); + return HIDAPI_DriverPS4_UpdateEffects(ctx, k_EPS4EffectRumble, true); } static bool HIDAPI_DriverPS4_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) @@ -918,10 +938,10 @@ static bool HIDAPI_DriverPS4_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joyst ctx->led_green = green; ctx->led_blue = blue; - return HIDAPI_DriverPS4_UpdateEffects(ctx, true); + return HIDAPI_DriverPS4_UpdateEffects(ctx, k_EPS4EffectLED, true); } -static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, bool application_usage) +static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, EPS4Effect mask, bool application_usage) { Uint8 data[78]; int report_size, offset; @@ -947,13 +967,21 @@ static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *c if (ctx->device->is_bluetooth && ctx->official_controller) { data[0] = k_EPS4ReportIdBluetoothEffects; data[1] = 0xC0 | ctx->report_interval; // Magic value HID + CRC, also sets update interval - data[3] = 0x03; // 0x1 is rumble, 0x2 is lightbar, 0x4 is the blink interval + + // Some third-party PS4 gamepads expect to receive both rumble and LED together, + // so we will only send them separately to official Sony gamepads. Unfortunately, + // this means that we'll fight with other apps that might be controlling one or + // the other separately for third-party PS4 gamepads. We can add a whitelist for + // compatible third-party gamepads later if we want. + data[3] = (Uint8)(ctx->official_controller ? mask : (k_EPS4EffectRumble | k_EPS4EffectLED)); report_size = 78; offset = 6; } else { data[0] = k_EPS4ReportIdUsbEffects; - data[1] = 0x07; // Magic value + + // FIXME: Should we send a consistent default effect mask between BT and USB? + data[1] = (Uint8)(ctx->official_controller ? mask : (k_EPS4EffectRumble | k_EPS4EffectLED | k_EPS4EffectLEDBlink)); report_size = 32; offset = 4; @@ -979,8 +1007,18 @@ static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *c static bool HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size) { SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + EPS4Effect mask; - return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, effect, size, true); + // Unlike the PS5 driver, the PS4 driver does not expect the validity bits as part of + // the provided effect data. The default values we provide also diverge for different + // gamepads and connection mediums. We should probably clean this up eventually. + if (ctx->device->is_bluetooth && ctx->official_controller) { + mask = k_EPS4EffectRumble | k_EPS4EffectLED; + } else { + mask = k_EPS4EffectRumble | k_EPS4EffectLED | k_EPS4EffectLEDBlink; + } + + return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, effect, size, mask, true); } static bool HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) @@ -989,7 +1027,7 @@ static bool HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(ctx); - if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) { + if ((!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) && !ctx->guitar_tilt_supported) { return SDL_Unsupported(); } @@ -1003,8 +1041,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; @@ -1138,7 +1176,7 @@ static void HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_d float data[3]; tick = LOAD16(packet->rgucTimestamp[0], packet->rgucTimestamp[1]); - if (ctx->last_tick < tick) { + if (ctx->last_tick <= tick) { delta = (tick - ctx->last_tick); } else { delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1); @@ -1160,6 +1198,30 @@ static void HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_d SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, data, 3); } + if (ctx->guitar_whammy_supported) { + axis = ((int)packet->rgucDeviceSpecific[1] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + } + + if (ctx->guitar_effects_selector_supported) { + // Align pickup selector mappings with PS3 instruments + static const Sint16 effects_mappings[] = {24576, 11008, -1792, -13568, -26880}; + if (packet->rgucDeviceSpecific[0] < SDL_arraysize(effects_mappings)) { + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, effects_mappings[packet->rgucDeviceSpecific[0]]); + } + } + + if (ctx->guitar_tilt_supported) { + float sensor_data[3]; + sensor_data[0] = ((float)packet->rgucDeviceSpecific[2] / 255) * SDL_STANDARD_GRAVITY; + sensor_data[1] = 0; + sensor_data[2] = 0; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, sensor_data, SDL_arraysize(sensor_data)); + + // Align tilt mappings with PS3 instruments + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, packet->rgucDeviceSpecific[2] > 0xF0); + } + SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state)); } diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps5.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps5.c index abf59a87..b67fc59f 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps5.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_ps5.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -43,12 +43,6 @@ #define ACCEL_RES_PER_G 8192.0f #define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500 -#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) -#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \ - (((Uint32)(B)) << 8) | \ - (((Uint32)(C)) << 16) | \ - (((Uint32)(D)) << 24)) - enum { SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD = 11, @@ -162,6 +156,7 @@ typedef struct Uint8 rgucTouchpadData1[3]; // 32 - X/Y, 12 bits per axis Uint8 ucTouchpadCounter2; // 35 - high bit clear + counter Uint8 rgucTouchpadData2[3]; // 36 - X/Y, 12 bits per axis + Uint8 rgucDeviceSpecific[8]; // 40 // There's more unknown data at the end, and a 32-bit CRC on Bluetooth } PS5StatePacketAlt_t; @@ -230,7 +225,7 @@ typedef struct { SDL_HIDAPI_Device *device; SDL_Joystick *joystick; - bool is_nacon_dongle; + bool is_dongle; bool use_alternate_report; bool sensors_supported; bool lightbar_supported; @@ -238,6 +233,9 @@ typedef struct bool playerled_supported; bool touchpad_supported; bool effects_supported; + bool guitar_whammy_supported; + bool guitar_tilt_supported; + bool guitar_effects_selector_supported; HIDAPI_PS5_EnhancedReportHint enhanced_report_hint; bool enhanced_reports; bool enhanced_mode; @@ -251,6 +249,7 @@ typedef struct Uint64 last_packet; int player_index; bool player_lights; + bool enhanced_rumble; Uint8 rumble_left; Uint8 rumble_right; bool color_set; @@ -299,6 +298,12 @@ static bool HIDAPI_DriverPS5_IsSupportedDevice(SDL_HIDAPI_Device *device, const Uint8 data[USB_PACKET_LENGTH]; int size; + if (vendor_id == USB_VENDOR_BACKBONE && + product_id == USB_PRODUCT_BACKBONE_ONE_PS5_V2) { + // This product doesn't appear to use the DualSense protocol + return false; + } + if (type == SDL_GAMEPAD_TYPE_PS5) { return true; } @@ -432,6 +437,14 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) } } + if (device->vendor_id == USB_VENDOR_SONY) { + if (device->product_id == USB_PRODUCT_SONY_DS5_EDGE || + ctx->firmware_version == 0 || // Assume that it's updated firmware over Bluetooth + ctx->firmware_version >= 0x0224) { + ctx->enhanced_rumble = true; + } + } + // Get the device capabilities if (device->vendor_id == USB_VENDOR_SONY) { ctx->sensors_supported = true; @@ -445,6 +458,7 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) if (size == 48 && data[2] == 0x28) { Uint8 capabilities = data[4]; Uint8 capabilities2 = data[20]; + Uint8 device_specific_capabilities = data[24]; Uint8 device_type = data[5]; #ifdef DEBUG_PS5_PROTOCOL @@ -466,12 +480,25 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) ctx->playerled_supported = true; } + if (capabilities2 & 0x01) { + ctx->report_battery = true; + } + switch (device_type) { case 0x00: joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD; break; case 0x01: joystick_type = SDL_JOYSTICK_TYPE_GUITAR; + if (device_specific_capabilities & 0x01) { + ctx->guitar_effects_selector_supported = true; + } + if (device_specific_capabilities & 0x02) { + ctx->guitar_tilt_supported = true; + } + if (device_specific_capabilities & 0x04) { + ctx->guitar_whammy_supported = true; + } break; case 0x02: joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT; @@ -491,6 +518,7 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) } ctx->use_alternate_report = true; + ctx->report_battery = true; if (device->vendor_id == USB_VENDOR_NACON_ALT && (device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRED || @@ -506,8 +534,10 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) ctx->touchpad_supported = true; ctx->use_alternate_report = true; } else if (device->vendor_id == USB_VENDOR_RAZER && - device->product_id == USB_PRODUCT_RAZER_KITSUNE) { - // The Razer Kitsune doesn't respond to the detection protocol, but has a touchpad + (device->product_id == USB_PRODUCT_RAZER_KITSUNE || + device->product_id == USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRED || + device->product_id == USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRELESS)) { + // The Razer Kitsune and Raiju don't respond to the detection protocol, but have a touchpad joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK; ctx->touchpad_supported = true; ctx->use_alternate_report = true; @@ -515,9 +545,13 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) } ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported || ctx->playerled_supported); - if (device->vendor_id == USB_VENDOR_NACON_ALT && - device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS) { - ctx->is_nacon_dongle = true; + if ((device->vendor_id == USB_VENDOR_NACON_ALT && + device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS) || + (device->vendor_id == USB_VENDOR_RAZER && + (device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS || + device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRELESS || + device->product_id == USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRELESS))) { + ctx->is_dongle = true; } device->joystick_type = joystick_type; @@ -531,7 +565,7 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) } HIDAPI_SetDeviceSerial(device, serial); - if (ctx->is_nacon_dongle) { + if (ctx->is_dongle) { // We don't know if this is connected yet, wait for reports return true; } @@ -684,17 +718,17 @@ static bool HIDAPI_DriverPS5_UpdateEffects(SDL_DriverPS5_Context *ctx, int effec if (ctx->vibration_supported) { if (ctx->rumble_left || ctx->rumble_right) { - if (ctx->firmware_version < 0x0224) { + if (ctx->enhanced_rumble) { + effects.ucEnableBits3 |= 0x04; // Enable improved rumble emulation on 2.24 firmware and newer + + effects.ucRumbleLeft = ctx->rumble_left; + effects.ucRumbleRight = ctx->rumble_right; + } else { effects.ucEnableBits1 |= 0x01; // Enable rumble emulation // Shift to reduce effective rumble strength to match Xbox controllers effects.ucRumbleLeft = ctx->rumble_left >> 1; effects.ucRumbleRight = ctx->rumble_right >> 1; - } else { - effects.ucEnableBits3 |= 0x04; // Enable improved rumble emulation on 2.24 firmware and newer - - effects.ucRumbleLeft = ctx->rumble_left; - effects.ucRumbleRight = ctx->rumble_right; } effects.ucEnableBits1 |= 0x02; // Disable audio haptics } else { @@ -812,14 +846,24 @@ static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx } if (ctx->sensors_supported) { + // Standard DualSense sensor update rate is 250 Hz over USB + float update_rate = 250.0f; + if (ctx->device->is_bluetooth) { // Bluetooth sensor update rate appears to be 1000 Hz - SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 1000.0f); - SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 1000.0f); - } else { - SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 250.0f); - SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 250.0f); + update_rate = 1000.0f; + } else if (SDL_IsJoystickDualSenseEdge(ctx->device->vendor_id, ctx->device->product_id)) { + // DualSense Edge sensor update rate is 1000 Hz over USB + update_rate = 1000.0f; } + + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, update_rate); + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, update_rate); + } + + if (ctx->guitar_tilt_supported) { + float update_rate = 250.0f; + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, update_rate); } ctx->report_battery = true; @@ -950,6 +994,10 @@ static bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystic joystick->nhats = 1; joystick->firmware_version = ctx->firmware_version; + if (ctx->is_dongle) { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; + } + SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, SDL_PS5EnhancedReportsChanged, ctx); SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, @@ -1033,6 +1081,9 @@ static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *c if (!ctx->enhanced_mode) { if (application_usage) { HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx); + + // Wait briefly before sending additional effects + SDL_Delay(10); } if (!ctx->enhanced_mode) { @@ -1045,10 +1096,11 @@ static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *c if (ctx->device->is_bluetooth) { data[0] = k_EPS5ReportIdBluetoothEffects; - data[1] = 0x02; // Magic value + data[1] = 0x00; // Tag and sequence + data[2] = 0x10; // Magic value report_size = 78; - offset = 2; + offset = 3; } else { data[0] = k_EPS5ReportIdUsbEffects; @@ -1105,7 +1157,7 @@ static bool HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx); - if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) { + if ((!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) && !ctx->guitar_tilt_supported) { return SDL_Unsupported(); } @@ -1312,7 +1364,7 @@ static void HIDAPI_DriverPS5_HandleStatePacketCommon(SDL_Joystick *joystick, SDL Uint32 delta; Uint16 tick = LOAD16(packet->rgucSensorTimestamp[0], packet->rgucSensorTimestamp[1]); - if (ctx->last_tick < tick) { + if (ctx->last_tick <= tick) { delta = (tick - ctx->last_tick); } else { delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1); @@ -1355,8 +1407,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,10 +1458,11 @@ 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; + Sint16 axis; if (ctx->report_touchpad) { touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0); @@ -1423,8 +1476,62 @@ static void HIDAPI_DriverPS5_HandleStatePacketAlt(SDL_Joystick *joystick, SDL_hi SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f); } + if (ctx->report_battery) { + SDL_PowerState state; + int percent; + Uint8 status = (packet->ucBatteryLevel >> 4) & 0x0F; + Uint8 level = (packet->ucBatteryLevel & 0x0F); + + // 0x0C means a controller isn't reporting battery levels + if (level != 0x0C) { + switch (status) { + case 0: + state = SDL_POWERSTATE_ON_BATTERY; + percent = SDL_min(level * 10 + 5, 100); + break; + case 1: + state = SDL_POWERSTATE_CHARGING; + percent = SDL_min(level * 10 + 5, 100); + break; + case 2: + state = SDL_POWERSTATE_CHARGED; + percent = 100; + break; + default: + state = SDL_POWERSTATE_UNKNOWN; + percent = 0; + break; + } + SDL_SendJoystickPowerInfo(joystick, state, percent); + } + } + HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, dev, ctx, (PS5StatePacketCommon_t *)packet, timestamp); + if (ctx->guitar_whammy_supported) { + axis = ((int)packet->rgucDeviceSpecific[1] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + } + + if (ctx->guitar_effects_selector_supported) { + // Align pickup selector mappings with PS3 instruments + static const Sint16 effects_mappings[] = {24576, 11008, -1792, -13568, -26880}; + if (packet->rgucDeviceSpecific[0] < SDL_arraysize(effects_mappings)) { + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, effects_mappings[packet->rgucDeviceSpecific[0]]); + } + } + + if (ctx->guitar_tilt_supported) { + float sensor_data[3]; + sensor_data[0] = ((float)packet->rgucDeviceSpecific[2] / 255) * SDL_STANDARD_GRAVITY; + sensor_data[1] = 0; + sensor_data[2] = 0; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, sensor_data, SDL_arraysize(sensor_data)); + + // Align tilt mappings with PS3 instruments + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, packet->rgucDeviceSpecific[2] > 0xF0); + } + SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state)); } @@ -1447,15 +1554,16 @@ static bool HIDAPI_DriverPS5_IsPacketValid(SDL_DriverPS5_Context *ctx, Uint8 *da { switch (data[0]) { case k_EPS5ReportIdState: - if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS5StatePacketAlt_t))) { + if (ctx->is_dongle && size >= (1 + sizeof(PS5StatePacketAlt_t))) { // The report timestamp doesn't change when the controller isn't connected PS5StatePacketAlt_t *packet = (PS5StatePacketAlt_t *)&data[1]; if (SDL_memcmp(packet->rgucPacketSequence, ctx->last_state.state.rgucPacketSequence, sizeof(packet->rgucPacketSequence)) == 0) { return false; } - if (ctx->last_state.alt_state.rgucAccelX[0] == 0 && ctx->last_state.alt_state.rgucAccelX[1] == 0 && - ctx->last_state.alt_state.rgucAccelY[0] == 0 && ctx->last_state.alt_state.rgucAccelY[1] == 0 && - ctx->last_state.alt_state.rgucAccelZ[0] == 0 && ctx->last_state.alt_state.rgucAccelZ[1] == 0) { + if (ctx->last_state.state.rgucPacketSequence[0] == 0 && + ctx->last_state.state.rgucPacketSequence[1] == 0 && + ctx->last_state.state.rgucPacketSequence[2] == 0 && + ctx->last_state.state.rgucPacketSequence[3] == 0) { // We don't have any state to compare yet, go ahead and copy it SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS5StatePacketAlt_t)); return false; @@ -1554,7 +1662,7 @@ static bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device) } } - if (ctx->is_nacon_dongle) { + if (ctx->is_dongle) { if (packet_count == 0) { if (device->num_joysticks > 0) { // Check to see if it looks like the device disconnected diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.c index 5fd93dc0..ef3fede5 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.h b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.h index ede061e0..bcd4a790 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.h +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_rumble.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -28,7 +28,7 @@ #ifdef SDL_THREAD_SAFETY_ANALYSIS extern SDL_Mutex *SDL_HIDAPI_rumble_lock; #endif -bool SDL_HIDAPI_LockRumble(void) SDL_TRY_ACQUIRE(0, SDL_HIDAPI_rumble_lock); +bool SDL_HIDAPI_LockRumble(void) SDL_TRY_ACQUIRE(true, SDL_HIDAPI_rumble_lock); bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size); int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size) SDL_RELEASE(SDL_HIDAPI_rumble_lock); typedef void (*SDL_HIDAPI_RumbleSentCallback)(void *userdata); diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_shield.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_shield.c index 10dcea36..2b3df213 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_shield.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_shield.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -29,7 +29,9 @@ #ifdef SDL_JOYSTICK_HIDAPI_SHIELD // Define this if you want to log all packets from the controller -// #define DEBUG_SHIELD_PROTOCOL +#if 0 +#define DEBUG_SHIELD_PROTOCOL +#endif #define CMD_BATTERY_STATE 0x07 #define CMD_RUMBLE 0x39 @@ -138,6 +140,11 @@ static void HIDAPI_DriverShield_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, static bool HIDAPI_DriverShield_SendCommand(SDL_HIDAPI_Device *device, Uint8 cmd, const void *data, int size) { +#ifdef SDL_PLATFORM_MACOS + // We hang for several seconds when trying to send output reports on macOS + return SDL_Unsupported(); + +#else SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context; ShieldCommandReport_t cmd_pkt; @@ -166,6 +173,8 @@ static bool HIDAPI_DriverShield_SendCommand(SDL_HIDAPI_Device *device, Uint8 cmd } return true; + +#endif // MACOS } static bool HIDAPI_DriverShield_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) @@ -289,6 +298,7 @@ static bool HIDAPI_DriverShield_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *dev static void HIDAPI_DriverShield_HandleStatePacketV103(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size) { Uint64 timestamp = SDL_GetTicksNS(); + Sint16 axis; if (ctx->last_state[3] != data[3]) { Uint8 hat; @@ -346,14 +356,20 @@ static void HIDAPI_DriverShield_HandleStatePacketV103(SDL_Joystick *joystick, SD SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x80) != 0)); } - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_Swap16LE(*(Sint16 *)&data[4]) - 0x8000); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_Swap16LE(*(Sint16 *)&data[6]) - 0x8000); + axis = LOAD16(data[4], data[5]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = LOAD16(data[6], data[7]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_Swap16LE(*(Sint16 *)&data[8]) - 0x8000); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_Swap16LE(*(Sint16 *)&data[10]) - 0x8000); + axis = LOAD16(data[8], data[9]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = LOAD16(data[10], data[11]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[12]) - 0x8000); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[14]) - 0x8000); + axis = LOAD16(data[12], data[13]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = LOAD16(data[14], data[15]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); } @@ -379,6 +395,7 @@ static void HIDAPI_DriverShield_HandleTouchPacketV103(SDL_Joystick *joystick, SD static void HIDAPI_DriverShield_HandleStatePacketV104(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size) { Uint64 timestamp = SDL_GetTicksNS(); + Sint16 axis; if (size < 23) { return; @@ -434,14 +451,20 @@ static void HIDAPI_DriverShield_HandleStatePacketV104(SDL_Joystick *joystick, SD SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[4] & 0x01) != 0)); } - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_Swap16LE(*(Sint16 *)&data[9]) - 0x8000); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_Swap16LE(*(Sint16 *)&data[11]) - 0x8000); + axis = LOAD16(data[9], data[10]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = LOAD16(data[11], data[12]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_Swap16LE(*(Sint16 *)&data[13]) - 0x8000); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_Swap16LE(*(Sint16 *)&data[15]) - 0x8000); + axis = LOAD16(data[13], data[14]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = LOAD16(data[15], data[16]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[19]) - 0x8000); - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[21]) - 0x8000); + axis = LOAD16(data[19], data[20]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = LOAD16(data[21], data[22]) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); if (ctx->last_state[17] != data[17]) { //SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_SHARE, ((data[17] & 0x01) != 0)); @@ -482,13 +505,13 @@ static bool HIDAPI_DriverShield_UpdateDevice(SDL_HIDAPI_Device *device) #ifdef DEBUG_SHIELD_PROTOCOL HIDAPI_DumpPacket("NVIDIA SHIELD packet: size = %d", data, size); #endif + if (!joystick) { + continue; + } // Byte 0 is HID report ID switch (data[0]) { case k_ShieldReportIdControllerState: - if (!joystick) { - break; - } if (size == 16) { HIDAPI_DriverShield_HandleStatePacketV103(joystick, ctx, data, size); } else { @@ -496,9 +519,6 @@ static bool HIDAPI_DriverShield_UpdateDevice(SDL_HIDAPI_Device *device) } break; case k_ShieldReportIdControllerTouch: - if (!joystick) { - break; - } HIDAPI_DriverShield_HandleTouchPacketV103(joystick, ctx, data, size); break; case k_ShieldReportIdCommandResponse: diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_sinput.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_sinput.c new file mode 100644 index 00000000..f84dfaa8 --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_sinput.c @@ -0,0 +1,1131 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Mitchell Cairns + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../SDL_sysjoystick.h" + +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" +#include "SDL_hidapi_sinput.h" + +#ifdef SDL_JOYSTICK_HIDAPI_SINPUT + +/*****************************************************************************************************/ +// This protocol is documented at: +// https://docs.handheldlegend.com/s/sinput +/*****************************************************************************************************/ + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_SINPUT_PROTOCOL +#endif + +#if 0 +#define DEBUG_SINPUT_INIT +#endif + +#define SINPUT_DEVICE_REPORT_SIZE 64 // Size of input reports (And CMD Input reports) +#define SINPUT_DEVICE_REPORT_COMMAND_SIZE 48 // Size of command OUTPUT reports + +#define SINPUT_DEVICE_REPORT_ID_JOYSTICK_INPUT 0x01 +#define SINPUT_DEVICE_REPORT_ID_INPUT_CMDDAT 0x02 +#define SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT 0x03 + +#define SINPUT_DEVICE_COMMAND_HAPTIC 0x01 +#define SINPUT_DEVICE_COMMAND_FEATURES 0x02 +#define SINPUT_DEVICE_COMMAND_PLAYERLED 0x03 +#define SINPUT_DEVICE_COMMAND_JOYSTICKRGB 0x04 + +#define SINPUT_HAPTIC_TYPE_PRECISE 0x01 +#define SINPUT_HAPTIC_TYPE_ERMSIMULATION 0x02 + +#define SINPUT_DEFAULT_GYRO_SENS 2000 +#define SINPUT_DEFAULT_ACCEL_SENS 8 + +#define SINPUT_REPORT_IDX_BUTTONS_0 3 +#define SINPUT_REPORT_IDX_BUTTONS_1 4 +#define SINPUT_REPORT_IDX_BUTTONS_2 5 +#define SINPUT_REPORT_IDX_BUTTONS_3 6 +#define SINPUT_REPORT_IDX_LEFT_X 7 +#define SINPUT_REPORT_IDX_LEFT_Y 9 +#define SINPUT_REPORT_IDX_RIGHT_X 11 +#define SINPUT_REPORT_IDX_RIGHT_Y 13 +#define SINPUT_REPORT_IDX_LEFT_TRIGGER 15 +#define SINPUT_REPORT_IDX_RIGHT_TRIGGER 17 +#define SINPUT_REPORT_IDX_IMU_TIMESTAMP 19 +#define SINPUT_REPORT_IDX_IMU_ACCEL_X 23 +#define SINPUT_REPORT_IDX_IMU_ACCEL_Y 25 +#define SINPUT_REPORT_IDX_IMU_ACCEL_Z 27 +#define SINPUT_REPORT_IDX_IMU_GYRO_X 29 +#define SINPUT_REPORT_IDX_IMU_GYRO_Y 31 +#define SINPUT_REPORT_IDX_IMU_GYRO_Z 33 +#define SINPUT_REPORT_IDX_TOUCH1_X 35 +#define SINPUT_REPORT_IDX_TOUCH1_Y 37 +#define SINPUT_REPORT_IDX_TOUCH1_P 39 +#define SINPUT_REPORT_IDX_TOUCH2_X 41 +#define SINPUT_REPORT_IDX_TOUCH2_Y 43 +#define SINPUT_REPORT_IDX_TOUCH2_P 45 + +#define SINPUT_BUTTON_IDX_EAST 0 +#define SINPUT_BUTTON_IDX_SOUTH 1 +#define SINPUT_BUTTON_IDX_NORTH 2 +#define SINPUT_BUTTON_IDX_WEST 3 +#define SINPUT_BUTTON_IDX_DPAD_UP 4 +#define SINPUT_BUTTON_IDX_DPAD_DOWN 5 +#define SINPUT_BUTTON_IDX_DPAD_LEFT 6 +#define SINPUT_BUTTON_IDX_DPAD_RIGHT 7 +#define SINPUT_BUTTON_IDX_LEFT_STICK 8 +#define SINPUT_BUTTON_IDX_RIGHT_STICK 9 +#define SINPUT_BUTTON_IDX_LEFT_BUMPER 10 +#define SINPUT_BUTTON_IDX_RIGHT_BUMPER 11 +#define SINPUT_BUTTON_IDX_LEFT_TRIGGER 12 +#define SINPUT_BUTTON_IDX_RIGHT_TRIGGER 13 +#define SINPUT_BUTTON_IDX_LEFT_PADDLE1 14 +#define SINPUT_BUTTON_IDX_RIGHT_PADDLE1 15 +#define SINPUT_BUTTON_IDX_START 16 +#define SINPUT_BUTTON_IDX_BACK 17 +#define SINPUT_BUTTON_IDX_GUIDE 18 +#define SINPUT_BUTTON_IDX_CAPTURE 19 +#define SINPUT_BUTTON_IDX_LEFT_PADDLE2 20 +#define SINPUT_BUTTON_IDX_RIGHT_PADDLE2 21 +#define SINPUT_BUTTON_IDX_TOUCHPAD1 22 +#define SINPUT_BUTTON_IDX_TOUCHPAD2 23 +#define SINPUT_BUTTON_IDX_POWER 24 +#define SINPUT_BUTTON_IDX_MISC4 25 +#define SINPUT_BUTTON_IDX_MISC5 26 +#define SINPUT_BUTTON_IDX_MISC6 27 +#define SINPUT_BUTTON_IDX_MISC7 28 +#define SINPUT_BUTTON_IDX_MISC8 29 +#define SINPUT_BUTTON_IDX_MISC9 30 +#define SINPUT_BUTTON_IDX_MISC10 31 + +#define SINPUT_BUTTONMASK_EAST 0x01 +#define SINPUT_BUTTONMASK_SOUTH 0x02 +#define SINPUT_BUTTONMASK_NORTH 0x04 +#define SINPUT_BUTTONMASK_WEST 0x08 +#define SINPUT_BUTTONMASK_DPAD_UP 0x10 +#define SINPUT_BUTTONMASK_DPAD_DOWN 0x20 +#define SINPUT_BUTTONMASK_DPAD_LEFT 0x40 +#define SINPUT_BUTTONMASK_DPAD_RIGHT 0x80 +#define SINPUT_BUTTONMASK_LEFT_STICK 0x01 +#define SINPUT_BUTTONMASK_RIGHT_STICK 0x02 +#define SINPUT_BUTTONMASK_LEFT_BUMPER 0x04 +#define SINPUT_BUTTONMASK_RIGHT_BUMPER 0x08 +#define SINPUT_BUTTONMASK_LEFT_TRIGGER 0x10 +#define SINPUT_BUTTONMASK_RIGHT_TRIGGER 0x20 +#define SINPUT_BUTTONMASK_LEFT_PADDLE1 0x40 +#define SINPUT_BUTTONMASK_RIGHT_PADDLE1 0x80 +#define SINPUT_BUTTONMASK_START 0x01 +#define SINPUT_BUTTONMASK_BACK 0x02 +#define SINPUT_BUTTONMASK_GUIDE 0x04 +#define SINPUT_BUTTONMASK_CAPTURE 0x08 +#define SINPUT_BUTTONMASK_LEFT_PADDLE2 0x10 +#define SINPUT_BUTTONMASK_RIGHT_PADDLE2 0x20 +#define SINPUT_BUTTONMASK_TOUCHPAD1 0x40 +#define SINPUT_BUTTONMASK_TOUCHPAD2 0x80 +#define SINPUT_BUTTONMASK_POWER 0x01 +#define SINPUT_BUTTONMASK_MISC4 0x02 +#define SINPUT_BUTTONMASK_MISC5 0x04 +#define SINPUT_BUTTONMASK_MISC6 0x08 +#define SINPUT_BUTTONMASK_MISC7 0x10 +#define SINPUT_BUTTONMASK_MISC8 0x20 +#define SINPUT_BUTTONMASK_MISC9 0x40 +#define SINPUT_BUTTONMASK_MISC10 0x80 + +#define SINPUT_REPORT_IDX_COMMAND_RESPONSE_ID 1 +#define SINPUT_REPORT_IDX_COMMAND_RESPONSE_BULK 2 + +#define SINPUT_REPORT_IDX_PLUG_STATUS 1 +#define SINPUT_REPORT_IDX_CHARGE_LEVEL 2 + +#define SINPUT_MAX_ALLOWED_TOUCHPADS 2 + +#ifndef EXTRACTSINT16 +#define EXTRACTSINT16(data, idx) ((Sint16)((data)[(idx)] | ((data)[(idx) + 1] << 8))) +#endif + +#ifndef EXTRACTUINT16 +#define EXTRACTUINT16(data, idx) ((Uint16)((data)[(idx)] | ((data)[(idx) + 1] << 8))) +#endif + +#ifndef EXTRACTUINT32 +#define EXTRACTUINT32(data, idx) ((Uint32)((data)[(idx)] | ((data)[(idx) + 1] << 8) | ((data)[(idx) + 2] << 16) | ((data)[(idx) + 3] << 24))) +#endif + +typedef struct +{ + uint8_t type; + + union { + // Frequency Amplitude pairs + struct { + struct { + uint16_t frequency_1; + uint16_t amplitude_1; + uint16_t frequency_2; + uint16_t amplitude_2; + } left; + + struct { + uint16_t frequency_1; + uint16_t amplitude_1; + uint16_t frequency_2; + uint16_t amplitude_2; + } right; + + } type_1; + + // Basic ERM simulation model + struct { + struct { + uint8_t amplitude; + bool brake; + } left; + + struct { + uint8_t amplitude; + bool brake; + } right; + + } type_2; + }; +} SINPUT_HAPTIC_S; + +typedef struct +{ + SDL_HIDAPI_Device *device; + Uint16 protocol_version; + Uint16 usb_device_version; + bool sensors_enabled; + + Uint8 player_idx; + + bool player_leds_supported; + bool joystick_rgb_supported; + bool rumble_supported; + bool accelerometer_supported; + bool gyroscope_supported; + bool left_analog_stick_supported; + bool right_analog_stick_supported; + bool left_analog_trigger_supported; + bool right_analog_trigger_supported; + bool dpad_supported; + bool touchpad_supported; + bool is_handheld; + + Uint8 touchpad_count; // 2 touchpads maximum + Uint8 touchpad_finger_count; // 2 fingers for one touchpad, or 1 per touchpad (2 max) + + Uint16 polling_rate_us; + Uint8 sub_product; // Subtype of the device, 0 in most cases + + Uint16 accelRange; // Example would be 2,4,8,16 +/- (g-force) + Uint16 gyroRange; // Example would be 1000,2000,4000 +/- (degrees per second) + + float accelScale; // Scale factor for accelerometer values + float gyroScale; // Scale factor for gyroscope values + Uint8 last_state[USB_PACKET_LENGTH]; + + Uint8 axes_count; + Uint8 buttons_count; + Uint8 usage_masks[4]; + + Uint32 last_imu_timestamp_us; + + Uint64 imu_timestamp_ns; // Nanoseconds. We accumulate with received deltas +} SDL_DriverSInput_Context; + +// Converts raw int16_t gyro scale setting +static inline float CalculateGyroScale(uint16_t dps_range) +{ + return SDL_PI_F / 180.0f / (32768.0f / (float)dps_range); +} + +// Converts raw int16_t accel scale setting +static inline float CalculateAccelScale(uint16_t g_range) +{ + return SDL_STANDARD_GRAVITY / (32768.0f / (float)g_range); +} + +// This function uses base-n encoding to encode features into the version GUID bytes +// that properly represents the supported device features +// This also sets the driver context button mask correctly based on the features +static void DeviceDynamicEncodingSetup(SDL_HIDAPI_Device *device) +{ + SDL_DriverSInput_Context *ctx = device->context; + + // A new button mask is generated to provide + // SDL with a mapping string that is sane. In case of + // an unconventional gamepad setup, the closest sane + // mapping is provided to the driver. + Uint8 mask[4] = { 0 }; + + // For all gamepads, there is a minimum SInput expectation + // to have dpad, abxy, and start buttons + + // ABXY + D-Pad + mask[0] = 0xFF; + ctx->dpad_supported = true; + + // Start button + mask[2] |= SINPUT_BUTTONMASK_START; + + // Bumpers + bool left_bumper = (ctx->usage_masks[1] & SINPUT_BUTTONMASK_LEFT_BUMPER) != 0; + bool right_bumper = (ctx->usage_masks[1] & SINPUT_BUTTONMASK_RIGHT_BUMPER) != 0; + + int bumperStyle = SINPUT_BUMPERSTYLE_NONE; + if (left_bumper && right_bumper) { + bumperStyle = SINPUT_BUMPERSTYLE_TWO; + mask[1] |= (SINPUT_BUTTONMASK_LEFT_BUMPER | SINPUT_BUTTONMASK_RIGHT_BUMPER); + } else if (left_bumper || right_bumper) { + bumperStyle = SINPUT_BUMPERSTYLE_ONE; + + if (left_bumper) { + mask[1] |= SINPUT_BUTTONMASK_LEFT_BUMPER; + } else if (right_bumper) { + mask[1] |= SINPUT_BUTTONMASK_RIGHT_BUMPER; + } + } + + // Trigger bits live in mask[1] + bool digital_triggers = (ctx->usage_masks[1] & (SINPUT_BUTTONMASK_LEFT_TRIGGER | SINPUT_BUTTONMASK_RIGHT_TRIGGER)) != 0; + + bool analog_triggers = ctx->left_analog_trigger_supported || ctx->right_analog_trigger_supported; + + // Touchpads + bool t1 = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_TOUCHPAD1) != 0; + bool t2 = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_TOUCHPAD2) != 0; + + int analogStyle = SINPUT_ANALOGSTYLE_NONE; + if (ctx->left_analog_stick_supported && ctx->right_analog_stick_supported) { + analogStyle = SINPUT_ANALOGSTYLE_LEFTRIGHT; + mask[1] |= (SINPUT_BUTTONMASK_LEFT_STICK | SINPUT_BUTTONMASK_RIGHT_STICK); + } else if (ctx->left_analog_stick_supported) { + analogStyle = SINPUT_ANALOGSTYLE_LEFTONLY; + mask[1] |= SINPUT_BUTTONMASK_LEFT_STICK; + } else if (ctx->right_analog_stick_supported) { + analogStyle = SINPUT_ANALOGSTYLE_RIGHTONLY; + mask[1] |= SINPUT_BUTTONMASK_RIGHT_STICK; + } + + int triggerStyle = SINPUT_TRIGGERSTYLE_NONE; + + if (analog_triggers && digital_triggers) { + // When we have both analog triggers and digital triggers + // this is interpreted as having dual-stage triggers + triggerStyle = SINPUT_TRIGGERSTYLE_DUALSTAGE; + mask[1] |= (SINPUT_BUTTONMASK_LEFT_TRIGGER | SINPUT_BUTTONMASK_RIGHT_TRIGGER); + } else if (analog_triggers) { + triggerStyle = SINPUT_TRIGGERSTYLE_ANALOG; + } else if (digital_triggers) { + triggerStyle = SINPUT_TRIGGERSTYLE_DIGITAL; + mask[1] |= (SINPUT_BUTTONMASK_LEFT_TRIGGER | SINPUT_BUTTONMASK_RIGHT_TRIGGER); + } + + // Paddle bits may touch mask[1] and mask[2] + bool pg1 = (ctx->usage_masks[1] & (SINPUT_BUTTONMASK_LEFT_PADDLE1 | SINPUT_BUTTONMASK_RIGHT_PADDLE1)) != 0; + bool pg2 = (ctx->usage_masks[2] & (SINPUT_BUTTONMASK_LEFT_PADDLE2 | SINPUT_BUTTONMASK_RIGHT_PADDLE2)) != 0; + + int paddleStyle = SINPUT_PADDLESTYLE_NONE; + if (pg1 && pg2) { + paddleStyle = SINPUT_PADDLESTYLE_FOUR; + mask[1] |= (SINPUT_BUTTONMASK_LEFT_PADDLE1 | SINPUT_BUTTONMASK_RIGHT_PADDLE1); + mask[2] |= (SINPUT_BUTTONMASK_LEFT_PADDLE2 | SINPUT_BUTTONMASK_RIGHT_PADDLE2); + } else if (pg1) { + paddleStyle = SINPUT_PADDLESTYLE_TWO; + mask[1] |= (SINPUT_BUTTONMASK_LEFT_PADDLE1 | SINPUT_BUTTONMASK_RIGHT_PADDLE1); + } + + + // Meta Buttons (Back, Guide, Share) + bool back = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_BACK) != 0; + bool guide = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_GUIDE) != 0; + bool share = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_CAPTURE) != 0; + + int metaStyle = SINPUT_METASTYLE_NONE; + if (share) { + metaStyle = SINPUT_METASTYLE_BACKGUIDESHARE; + mask[2] |= (SINPUT_BUTTONMASK_BACK | SINPUT_BUTTONMASK_GUIDE | SINPUT_BUTTONMASK_CAPTURE); + } else if (guide) { + metaStyle = SINPUT_METASTYLE_BACKGUIDE; + mask[2] |= (SINPUT_BUTTONMASK_BACK | SINPUT_BUTTONMASK_GUIDE); + } else if (back) { + metaStyle = SINPUT_METASTYLE_BACK; + mask[2] |= (SINPUT_BUTTONMASK_BACK); + } + + int touchStyle = SINPUT_TOUCHSTYLE_NONE; + if (t1 && t2) { + touchStyle = SINPUT_TOUCHSTYLE_DOUBLE; + mask[2] |= (SINPUT_BUTTONMASK_TOUCHPAD1 | SINPUT_BUTTONMASK_TOUCHPAD2); + } else if (t1) { + touchStyle = SINPUT_TOUCHSTYLE_SINGLE; + mask[2] |= SINPUT_BUTTONMASK_TOUCHPAD1; + } + + // Misc Buttons + int miscStyle = SINPUT_MISCSTYLE_NONE; + Uint8 extra_misc = ctx->usage_masks[3] & 0x0F; + switch (extra_misc) { + case 0x0F: + miscStyle = SINPUT_MISCSTYLE_4; + mask[3] = 0x0F; + break; + case 0x07: + miscStyle = SINPUT_MISCSTYLE_3; + mask[3] = 0x07; + break; + case 0x03: + miscStyle = SINPUT_MISCSTYLE_2; + mask[3] = 0x03; + break; + case 0x01: + miscStyle = SINPUT_MISCSTYLE_1; + mask[3] = 0x01; + break; + default: + miscStyle = SINPUT_MISCSTYLE_NONE; + mask[3] = 0x00; + break; + } + + int version = analogStyle; + version = (version * (int)SINPUT_BUMPERSTYLE_MAX) + bumperStyle; + version = (version * (int)SINPUT_TRIGGERSTYLE_MAX) + triggerStyle; + version = (version * (int)SINPUT_PADDLESTYLE_MAX) + paddleStyle; + version = (version * (int)SINPUT_METASTYLE_MAX) + metaStyle; + version = (version * (int)SINPUT_TOUCHSTYLE_MAX) + touchStyle; + version = (version * (int)SINPUT_MISCSTYLE_MAX) + miscStyle; + + // Overwrite our button usage masks + // with our sanitized masks + ctx->usage_masks[0] = mask[0]; + ctx->usage_masks[1] = mask[1]; + ctx->usage_masks[2] = mask[2]; + ctx->usage_masks[3] = mask[3]; + + version = SDL_clamp(version, 0, UINT16_MAX); + + // Overwrite 'Version' field of the GUID data + device->guid.data[12] = (Uint8)(version & 0xFF); + device->guid.data[13] = (Uint8)(version >> 8); +} + + +static void ProcessSDLFeaturesResponse(SDL_HIDAPI_Device *device, Uint8 *data) +{ + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + + // Obtain protocol version + ctx->protocol_version = EXTRACTUINT16(data, 0); + + // Bitfields are not portable, so we unpack them into a struct value + ctx->rumble_supported = (data[2] & 0x01) != 0; + ctx->player_leds_supported = (data[2] & 0x02) != 0; + ctx->accelerometer_supported = (data[2] & 0x04) != 0; + ctx->gyroscope_supported = (data[2] & 0x08) != 0; + + ctx->left_analog_stick_supported = (data[2] & 0x10) != 0; + ctx->right_analog_stick_supported = (data[2] & 0x20) != 0; + ctx->left_analog_trigger_supported = (data[2] & 0x40) != 0; + ctx->right_analog_trigger_supported = (data[2] & 0x80) != 0; + + ctx->touchpad_supported = (data[3] & 0x01) != 0; + ctx->joystick_rgb_supported = (data[3] & 0x02) != 0; + + ctx->is_handheld = (data[3] & 0x04) != 0; + + // The gamepad type represents a style of gamepad that most closely + // resembles the gamepad in question (Button style, button layout) + SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN; + type = (SDL_GamepadType)SDL_clamp(data[4], SDL_GAMEPAD_TYPE_UNKNOWN, SDL_GAMEPAD_TYPE_COUNT); + device->type = type; + + // The 3 MSB represent a button layout style SDL_GamepadFaceStyle + // The 5 LSB represent a device sub-type + device->guid.data[15] = data[5]; + + ctx->sub_product = (data[5] & 0x1F); + +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("SInput Face Style: %d", (data[5] & 0xE0) >> 5); + SDL_Log("SInput Sub-product: %d", (data[5] & 0x1F)); +#endif + + ctx->polling_rate_us = EXTRACTUINT16(data, 6); + +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("SInput polling interval (microseconds): %d", ctx->polling_rate_us); +#endif + + ctx->accelRange = EXTRACTUINT16(data, 8); + ctx->gyroRange = EXTRACTUINT16(data, 10); + + ctx->usage_masks[0] = data[12]; + ctx->usage_masks[1] = data[13]; + ctx->usage_masks[2] = data[14]; + ctx->usage_masks[3] = data[15]; + + // Get and validate touchpad parameters + ctx->touchpad_count = data[16]; + ctx->touchpad_finger_count = data[17]; + + // Get device Serial - MAC address + char serial[18]; + (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", + data[18], data[19], data[20], data[21], data[22], data[23]); + +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("Serial num: %s", serial); +#endif + HIDAPI_SetDeviceSerial(device, serial); + +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("Accelerometer Range: %d", ctx->accelRange); +#endif + +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("Gyro Range: %d", ctx->gyroRange); +#endif + + ctx->accelScale = CalculateAccelScale(ctx->accelRange); + ctx->gyroScale = CalculateGyroScale(ctx->gyroRange); + + Uint8 axes = 0; + if (ctx->left_analog_stick_supported) { + axes += 2; + } + + if (ctx->right_analog_stick_supported) { + axes += 2; + } + + if (ctx->left_analog_trigger_supported || ctx->right_analog_trigger_supported) { + // Always add both analog trigger axes if one is present + axes += 2; + } + + ctx->axes_count = axes; + + DeviceDynamicEncodingSetup(device); + + // Derive button count from mask + for (Uint8 byte = 0; byte < 4; ++byte) { + for (Uint8 bit = 0; bit < 8; ++bit) { + if ((ctx->usage_masks[byte] & (1 << bit)) != 0) { + ++ctx->buttons_count; + } + } + } + + // Convert DPAD to hat + const int DPAD_MASK = (1 << SINPUT_BUTTON_IDX_DPAD_UP) | + (1 << SINPUT_BUTTON_IDX_DPAD_DOWN) | + (1 << SINPUT_BUTTON_IDX_DPAD_LEFT) | + (1 << SINPUT_BUTTON_IDX_DPAD_RIGHT); + if ((ctx->usage_masks[0] & DPAD_MASK) == DPAD_MASK) { + ctx->dpad_supported = true; + ctx->usage_masks[0] &= ~DPAD_MASK; + ctx->buttons_count -= 4; + } + +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("Buttons count: %d", ctx->buttons_count); +#endif +} + +static bool RetrieveSDLFeatures(SDL_HIDAPI_Device *device) +{ + int written = 0; + + // Attempt to send the SDL features get command. + for (int attempt = 0; attempt < 8; ++attempt) { + const Uint8 featuresGetCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_FEATURES }; + // This write will occasionally return -1, so ignore failure here and try again + written = SDL_hid_write(device->dev, featuresGetCommand, sizeof(featuresGetCommand)); + + if (written == SINPUT_DEVICE_REPORT_COMMAND_SIZE) { + break; + } + } + + if (written < SINPUT_DEVICE_REPORT_COMMAND_SIZE) { + SDL_SetError("SInput device SDL Features GET command could not write"); + return false; + } + + int read = 0; + + // Read the reply + for (int i = 0; i < 100; ++i) { + SDL_Delay(1); + + Uint8 data[USB_PACKET_LENGTH]; + read = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0); + if (read < 0) { + SDL_SetError("SInput device SDL Features GET command could not read"); + return false; + } + if (read == 0) { + continue; + } + +#ifdef DEBUG_SINPUT_PROTOCOL + HIDAPI_DumpPacket("SInput packet: size = %d", data, read); +#endif + + if ((read == USB_PACKET_LENGTH) && (data[0] == SINPUT_DEVICE_REPORT_ID_INPUT_CMDDAT) && (data[1] == SINPUT_DEVICE_COMMAND_FEATURES)) { + ProcessSDLFeaturesResponse(device, &(data[SINPUT_REPORT_IDX_COMMAND_RESPONSE_BULK])); +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("Received SInput SDL Features command response"); +#endif + return true; + } + } + + return false; +} + +// Type 2 haptics are for more traditional rumble such as +// ERM motors or simulated ERM motors +static inline void HapticsType2Pack(SINPUT_HAPTIC_S *in, Uint8 *out) +{ + // Type of haptics + out[0] = 2; + + out[1] = in->type_2.left.amplitude; + out[2] = in->type_2.left.brake; + + out[3] = in->type_2.right.amplitude; + out[4] = in->type_2.right.brake; +} + +static void HIDAPI_DriverSInput_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, callback, userdata); +} + +static void HIDAPI_DriverSInput_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, callback, userdata); +} + +static bool HIDAPI_DriverSInput_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverSInput_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + return SDL_IsJoystickSInputController(vendor_id, product_id); +} + +static bool HIDAPI_DriverSInput_InitDevice(SDL_HIDAPI_Device *device) +{ +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("SInput device Init"); +#endif + + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + + ctx->device = device; + device->context = ctx; + + if (!RetrieveSDLFeatures(device)) { + return false; + } + + // Store the USB Device Version because we will overwrite this data + ctx->usb_device_version = device->version; + + switch (device->product_id) { + case USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE: + HIDAPI_SetDeviceName(device, "HHL GC Ultimate"); + break; + case USB_PRODUCT_HANDHELDLEGEND_PROGCC: + HIDAPI_SetDeviceName(device, "HHL ProGCC"); + break; + case USB_PRODUCT_VOIDGAMING_PS4FIREBIRD: + HIDAPI_SetDeviceName(device, "Void Gaming PS4 FireBird"); + break; + case USB_PRODUCT_BONZIRICHANNEL_FIREBIRD: + HIDAPI_SetDeviceName(device, "Bonziri FireBird"); + break; + case USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC: + default: + // Use the USB product name + break; + } + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverSInput_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverSInput_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + + if (ctx->player_leds_supported) { + player_index = SDL_clamp(player_index + 1, 0, 255); + Uint8 player_num = (Uint8)player_index; + + ctx->player_idx = player_num; + + // Set player number, finalizing the setup + Uint8 playerLedCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_PLAYERLED, ctx->player_idx }; + int playerNumBytesWritten = SDL_hid_write(device->dev, playerLedCommand, SINPUT_DEVICE_REPORT_COMMAND_SIZE); + + if (playerNumBytesWritten < 0) { + SDL_SetError("SInput device player led command could not write"); + } + } +} + +#ifndef DEG2RAD +#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) +#endif + + +static bool HIDAPI_DriverSInput_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +#if defined(DEBUG_SINPUT_INIT) + SDL_Log("SInput device Open"); +#endif + + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + joystick->nbuttons = ctx->buttons_count; + + SDL_zeroa(ctx->last_state); + + joystick->naxes = ctx->axes_count; + + if (ctx->dpad_supported) { + joystick->nhats = 1; + } + + if (ctx->gyroscope_supported) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 1000000.0f / ctx->polling_rate_us); + } + + if (ctx->accelerometer_supported) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 1000000.0f / ctx->polling_rate_us); + } + + if (ctx->touchpad_supported) { + // If touchpad is supported, minimum 1, max is capped + ctx->touchpad_count = SDL_clamp(ctx->touchpad_count, 1, SINPUT_MAX_ALLOWED_TOUCHPADS); + + if (ctx->touchpad_count > 1) { + // Support two separate touchpads with 1 finger each + // or support one touchpad with 2 fingers max + ctx->touchpad_finger_count = 1; + } + + if (ctx->touchpad_count > 0) { + SDL_PrivateJoystickAddTouchpad(joystick, ctx->touchpad_finger_count); + } + + if (ctx->touchpad_count > 1) { + SDL_PrivateJoystickAddTouchpad(joystick, ctx->touchpad_finger_count); + } + } + + return true; +} + +static bool HIDAPI_DriverSInput_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + + if (ctx->rumble_supported) { + SINPUT_HAPTIC_S hapticData = { 0 }; + Uint8 hapticReport[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_HAPTIC }; + + // Low Frequency = Left + // High Frequency = Right + hapticData.type_2.left.amplitude = (Uint8) (low_frequency_rumble >> 8); + hapticData.type_2.right.amplitude = (Uint8)(high_frequency_rumble >> 8); + + HapticsType2Pack(&hapticData, &(hapticReport[2])); + + SDL_HIDAPI_SendRumble(device, hapticReport, SINPUT_DEVICE_REPORT_COMMAND_SIZE); + + return true; + } + + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSInput_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverSInput_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + + Uint32 caps = 0; + if (ctx->rumble_supported) { + caps |= SDL_JOYSTICK_CAP_RUMBLE; + } + + if (ctx->player_leds_supported) { + caps |= SDL_JOYSTICK_CAP_PLAYER_LED; + } + + if (ctx->joystick_rgb_supported) { + caps |= SDL_JOYSTICK_CAP_RGB_LED; + } + + return caps; +} + +static bool HIDAPI_DriverSInput_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + + if (ctx->joystick_rgb_supported) { + Uint8 joystickRGBCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_JOYSTICKRGB, red, green, blue }; + int joystickRGBBytesWritten = SDL_hid_write(device->dev, joystickRGBCommand, SINPUT_DEVICE_REPORT_COMMAND_SIZE); + + if (joystickRGBBytesWritten < 0) { + SDL_SetError("SInput device joystick rgb command could not write"); + return false; + } + + return true; + } + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSInput_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSInput_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + + if (ctx->accelerometer_supported || ctx->gyroscope_supported) { + ctx->sensors_enabled = enabled; + return true; + } + return SDL_Unsupported(); +} + +static void HIDAPI_DriverSInput_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverSInput_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis = 0; + Sint16 accel = 0; + Sint16 gyro = 0; + Uint64 timestamp = SDL_GetTicksNS(); + float imu_values[3] = { 0 }; + Uint8 output_idx = 0; + + // Process digital buttons according to the supplied + // button mask to create a contiguous button input set + for (Uint8 processes = 0; processes < 4; ++processes) { + + Uint8 button_idx = SINPUT_REPORT_IDX_BUTTONS_0 + processes; + + for (Uint8 buttons = 0; buttons < 8; ++buttons) { + + // If a button is enabled by our usage mask + const Uint8 mask = (0x01 << buttons); + if ((ctx->usage_masks[processes] & mask) != 0) { + + bool down = (data[button_idx] & mask) != 0; + + if ( (output_idx < SDL_GAMEPAD_BUTTON_COUNT) && (ctx->last_state[button_idx] != data[button_idx]) ) { + SDL_SendJoystickButton(timestamp, joystick, output_idx, down); + } + + ++output_idx; + } + } + } + + if (ctx->dpad_supported) { + Uint8 hat = SDL_HAT_CENTERED; + + if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_UP)) { + hat |= SDL_HAT_UP; + } + if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_DOWN)) { + hat |= SDL_HAT_DOWN; + } + if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_LEFT)) { + hat |= SDL_HAT_LEFT; + } + if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_RIGHT)) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + // Analog inputs map to a signed Sint16 range of -32768 to 32767 from the device. + // Use an axis index because not all gamepads will have the same axis inputs. + Uint8 axis_idx = 0; + + // Left Analog Stick + axis = 0; // Reset axis value for joystick + if (ctx->left_analog_stick_supported) { + axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_X); + SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis); + ++axis_idx; + + axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_Y); + SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis); + ++axis_idx; + } + + // Right Analog Stick + axis = 0; // Reset axis value for joystick + if (ctx->right_analog_stick_supported) { + axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_X); + SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis); + ++axis_idx; + + axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_Y); + SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis); + ++axis_idx; + } + + // Left Analog Trigger + axis = SDL_MIN_SINT16; // Reset axis value for trigger + if (ctx->left_analog_trigger_supported) { + axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_TRIGGER); + SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis); + ++axis_idx; + } + + // Right Analog Trigger + axis = SDL_MIN_SINT16; // Reset axis value for trigger + if (ctx->right_analog_trigger_supported) { + axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_TRIGGER); + SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis); + } + + // Battery/Power state handling + if (ctx->last_state[SINPUT_REPORT_IDX_PLUG_STATUS] != data[SINPUT_REPORT_IDX_PLUG_STATUS] || + ctx->last_state[SINPUT_REPORT_IDX_CHARGE_LEVEL] != data[SINPUT_REPORT_IDX_CHARGE_LEVEL]) { + + SDL_PowerState state = SDL_POWERSTATE_UNKNOWN; + Uint8 status = data[SINPUT_REPORT_IDX_PLUG_STATUS]; + int percent = data[SINPUT_REPORT_IDX_CHARGE_LEVEL]; + + percent = SDL_clamp(percent, 0, 100); // Ensure percent is within valid range + + switch (status) { + case 1: + state = SDL_POWERSTATE_NO_BATTERY; + percent = 0; + break; + case 2: + state = SDL_POWERSTATE_CHARGING; + break; + case 3: + state = SDL_POWERSTATE_CHARGED; + percent = 100; + break; + case 4: + state = SDL_POWERSTATE_ON_BATTERY; + break; + default: + break; + } + + if (state != SDL_POWERSTATE_UNKNOWN) { + SDL_SendJoystickPowerInfo(joystick, state, percent); + } + } + + // Extract the IMU timestamp delta (in microseconds) + Uint32 imu_timestamp_us = EXTRACTUINT32(data, SINPUT_REPORT_IDX_IMU_TIMESTAMP); + Uint32 imu_time_delta_us = 0; + + // Check if we should process IMU data and if sensors are enabled + if (ctx->sensors_enabled) { + + if (imu_timestamp_us >= ctx->last_imu_timestamp_us) { + imu_time_delta_us = (imu_timestamp_us - ctx->last_imu_timestamp_us); + } else { + // Handle rollover case + imu_time_delta_us = (UINT32_MAX - ctx->last_imu_timestamp_us) + imu_timestamp_us + 1; + } + + // Convert delta to nanoseconds and update running timestamp + ctx->imu_timestamp_ns += (Uint64)imu_time_delta_us * 1000; + + // Update last timestamp + ctx->last_imu_timestamp_us = imu_timestamp_us; + + // Process Gyroscope + if (ctx->gyroscope_supported) { + + gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_Y); + imu_values[2] = -(float)gyro * ctx->gyroScale; // Y-axis rotation + + gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_Z); + imu_values[1] = (float)gyro * ctx->gyroScale; // Z-axis rotation + + gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_X); + imu_values[0] = -(float)gyro * ctx->gyroScale; // X-axis rotation + + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->imu_timestamp_ns, imu_values, 3); + } + + // Process Accelerometer + if (ctx->accelerometer_supported) { + + accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_Y); + imu_values[2] = -(float)accel * ctx->accelScale; // Y-axis acceleration + + accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_Z); + imu_values[1] = (float)accel * ctx->accelScale; // Z-axis acceleration + + accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_X); + imu_values[0] = -(float)accel * ctx->accelScale; // X-axis acceleration + + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->imu_timestamp_ns, imu_values, 3); + } + } + + // Check if we should process touchpad + if (ctx->touchpad_supported && ctx->touchpad_count > 0) { + Uint8 touchpad = 0; + Uint8 finger = 0; + + Sint16 touch1X = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH1_X); + Sint16 touch1Y = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH1_Y); + Uint16 touch1P = EXTRACTUINT16(data, SINPUT_REPORT_IDX_TOUCH1_P); + + Sint16 touch2X = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH2_X); + Sint16 touch2Y = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH2_Y); + Uint16 touch2P = EXTRACTUINT16(data, SINPUT_REPORT_IDX_TOUCH2_P); + + SDL_SendJoystickTouchpad(timestamp, joystick, touchpad, finger, + touch1P > 0, + touch1X / 65536.0f + 0.5f, + touch1Y / 65536.0f + 0.5f, + touch1P / 32768.0f); + + if (ctx->touchpad_count > 1) { + ++touchpad; + } else if (ctx->touchpad_finger_count > 1) { + ++finger; + } + + if ((touchpad > 0) || (finger > 0)) { + SDL_SendJoystickTouchpad(timestamp, joystick, touchpad, finger, + touch2P > 0, + touch2X / 65536.0f + 0.5f, + touch2Y / 65536.0f + 0.5f, + touch2P / 32768.0f); + } + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverSInput_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_SINPUT_PROTOCOL + HIDAPI_DumpPacket("SInput packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + if (data[0] == SINPUT_DEVICE_REPORT_ID_JOYSTICK_INPUT) { + HIDAPI_DriverSInput_HandleStatePacket(joystick, ctx, data, size); + } + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverSInput_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +static void HIDAPI_DriverSInput_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSInput = { + SDL_HINT_JOYSTICK_HIDAPI_SINPUT, + true, + HIDAPI_DriverSInput_RegisterHints, + HIDAPI_DriverSInput_UnregisterHints, + HIDAPI_DriverSInput_IsEnabled, + HIDAPI_DriverSInput_IsSupportedDevice, + HIDAPI_DriverSInput_InitDevice, + HIDAPI_DriverSInput_GetDevicePlayerIndex, + HIDAPI_DriverSInput_SetDevicePlayerIndex, + HIDAPI_DriverSInput_UpdateDevice, + HIDAPI_DriverSInput_OpenJoystick, + HIDAPI_DriverSInput_RumbleJoystick, + HIDAPI_DriverSInput_RumbleJoystickTriggers, + HIDAPI_DriverSInput_GetJoystickCapabilities, + HIDAPI_DriverSInput_SetJoystickLED, + HIDAPI_DriverSInput_SendJoystickEffect, + HIDAPI_DriverSInput_SetJoystickSensorsEnabled, + HIDAPI_DriverSInput_CloseJoystick, + HIDAPI_DriverSInput_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_SINPUT + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_sinput.h b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_sinput.h new file mode 100644 index 00000000..8bcc70d7 --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_sinput.h @@ -0,0 +1,92 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Mitchell Cairns + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +typedef enum +{ + SINPUT_ANALOGSTYLE_NONE, + SINPUT_ANALOGSTYLE_LEFTONLY, + SINPUT_ANALOGSTYLE_RIGHTONLY, + SINPUT_ANALOGSTYLE_LEFTRIGHT, + SINPUT_ANALOGSTYLE_MAX, +} SInput_AnalogStyleType; + +typedef enum +{ + SINPUT_BUMPERSTYLE_NONE, + SINPUT_BUMPERSTYLE_ONE, + SINPUT_BUMPERSTYLE_TWO, + SINPUT_BUMPERSTYLE_MAX, +} SInput_BumperStyleType; + +typedef enum +{ + SINPUT_TRIGGERSTYLE_NONE, + SINPUT_TRIGGERSTYLE_ANALOG, + SINPUT_TRIGGERSTYLE_DIGITAL, + SINPUT_TRIGGERSTYLE_DUALSTAGE, + SINPUT_TRIGGERSTYLE_MAX, +} SInput_TriggerStyleType; + +typedef enum +{ + SINPUT_PADDLESTYLE_NONE, + SINPUT_PADDLESTYLE_TWO, + SINPUT_PADDLESTYLE_FOUR, + SINPUT_PADDLESTYLE_MAX, +} SInput_PaddleStyleType; + +typedef enum +{ + SINPUT_METASTYLE_NONE, + SINPUT_METASTYLE_BACK, + SINPUT_METASTYLE_BACKGUIDE, + SINPUT_METASTYLE_BACKGUIDESHARE, + SINPUT_METASTYLE_MAX, +} SInput_MetaStyleType; + +typedef enum +{ + SINPUT_TOUCHSTYLE_NONE, + SINPUT_TOUCHSTYLE_SINGLE, + SINPUT_TOUCHSTYLE_DOUBLE, + SINPUT_TOUCHSTYLE_MAX, +} SInput_TouchStyleType; + +typedef enum +{ + SINPUT_MISCSTYLE_NONE, + SINPUT_MISCSTYLE_1, + SINPUT_MISCSTYLE_2, + SINPUT_MISCSTYLE_3, + SINPUT_MISCSTYLE_4, + SINPUT_MISCSTYLE_MAX, +} SInput_MiscStyleType; + +typedef struct +{ + Uint16 analog_style; + Uint16 bumper_style; + Uint16 trigger_style; + Uint16 paddle_style; + Uint16 meta_style; + Uint16 touch_style; + Uint16 misc_style; +} SDL_SInputStyles_t; diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_stadia.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_stadia.c index 487bf41b..5c5e7490 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_stadia.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_stadia.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam.c index b48d3539..cb6add70 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -29,7 +29,9 @@ #ifdef SDL_JOYSTICK_HIDAPI_STEAM // Define this if you want to log all packets from the controller -// #define DEBUG_STEAM_PROTOCOL +#if 0 +#define DEBUG_STEAM_PROTOCOL +#endif #define SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED "SDL_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED" @@ -221,7 +223,7 @@ static void hexdump(const uint8_t *ptr, int len) static void ResetSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler) { - SDL_memset(pAssembler->uBuffer, 0, sizeof(pAssembler->uBuffer)); + SDL_zeroa(pAssembler->uBuffer); pAssembler->nExpectedSegmentNumber = 0; } @@ -320,7 +322,7 @@ static int SetFeatureReport(SDL_HIDAPI_Device *dev, const unsigned char uBuffer[ nActualDataLen -= nBytesInPacket; // Construct packet - SDL_memset(uPacketBuffer, 0, sizeof(uPacketBuffer)); + SDL_zeroa(uPacketBuffer); uPacketBuffer[0] = BLE_REPORT_NUMBER; uPacketBuffer[1] = GetSegmentHeader(nSegmentNumber, nActualDataLen == 0); SDL_memcpy(&uPacketBuffer[2], pBufferPtr, nBytesInPacket); @@ -370,7 +372,7 @@ static int GetFeatureReport(SDL_HIDAPI_Device *dev, unsigned char uBuffer[65]) #endif while (nRetries < BLE_MAX_READ_RETRIES) { - SDL_memset(uSegmentBuffer, 0, sizeof(uSegmentBuffer)); + SDL_zeroa(uSegmentBuffer); uSegmentBuffer[0] = BLE_REPORT_NUMBER; nRet = SDL_hid_get_feature_report(dev->dev, uSegmentBuffer, ucBytesToRead); @@ -457,6 +459,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew DPRINTF("ResetSteamController hid=%p\n", dev); + SDL_zero(buf); buf[0] = 0; buf[1] = ID_GET_ATTRIBUTES_VALUES; res = SetFeatureReport(dev, buf, 2); @@ -510,6 +513,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew } // Clear digital button mappings + SDL_zero(buf); buf[0] = 0; buf[1] = ID_CLEAR_DIGITAL_MAPPINGS; res = SetFeatureReport(dev, buf, 2); @@ -521,7 +525,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew } // Reset the default settings - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_LOAD_DEFAULT_SETTINGS; buf[2] = 0; res = SetFeatureReport(dev, buf, 3); @@ -539,7 +543,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew buf[3 + nSettings * 3 + 2] = ((uint16_t)VALUE) >> 8; \ ++nSettings; - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_SET_SETTINGS_VALUES; ADD_SETTING(SETTING_WIRELESS_PACKET_VERSION, 2); ADD_SETTING(SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE); @@ -567,7 +571,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew bool bMappingsCleared = false; int iRetry; for (iRetry = 0; iRetry < 2; ++iRetry) { - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_GET_DIGITAL_MAPPINGS; buf[2] = 1; // one byte - requesting from index 0 buf[3] = 0; @@ -596,7 +600,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew } // Set our new mappings - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_SET_DIGITAL_MAPPINGS; buf[2] = 6; // 2 settings x 3 bytes buf[3] = IO_DIGITAL_BUTTON_RIGHT_TRIGGER; @@ -634,7 +638,7 @@ static int ReadSteamController(SDL_hid_device *dev, uint8_t *pData, int nDataSiz static void SetPairingState(SDL_HIDAPI_Device *dev, bool bEnablePairing) { unsigned char buf[65]; - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_ENABLE_PAIRING; buf[2] = 2; // 2 payload bytes: bool + timeout buf[3] = bEnablePairing ? 1 : 0; @@ -648,7 +652,7 @@ static void SetPairingState(SDL_HIDAPI_Device *dev, bool bEnablePairing) static void CommitPairing(SDL_HIDAPI_Device *dev) { unsigned char buf[65]; - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_DONGLE_COMMIT_DEVICE; SetFeatureReport(dev, buf, 2); } @@ -663,18 +667,18 @@ static void CloseSteamController(SDL_HIDAPI_Device *dev) int nSettings = 0; // Reset digital button mappings - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_SET_DEFAULT_DIGITAL_MAPPINGS; SetFeatureReport(dev, buf, 2); // Reset the default settings - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_LOAD_DEFAULT_SETTINGS; buf[2] = 0; SetFeatureReport(dev, buf, 3); // Reset mouse mode for lizard mode - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_SET_SETTINGS_VALUES; ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE); buf[2] = (unsigned char)(nSettings * 3); @@ -706,13 +710,6 @@ static void RotatePad(int *pX, int *pY, float flAngleInRad) *pX = (int)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY); *pY = (int)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY); } -static void RotatePadShort(short *pX, short *pY, float flAngleInRad) -{ - int origX = *pX, origY = *pY; - - *pX = (short)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY); - *pY = (short)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY); -} //--------------------------------------------------------------------------- // Format the first part of the state packet @@ -836,9 +833,16 @@ static void FormatStatePacketUntilGyro(SteamControllerStateInternal_t *pState, V //--------------------------------------------------------------------------- static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState) { - const float flRotationAngle = 0.261799f; + int nLeftPadX; + int nLeftPadY; + int nRightPadX; + int nRightPadY; + int nPadOffset; uint32_t ucOptionDataMask; + // 15 degrees in rad + const float flRotationAngle = 0.261799f; + pState->unPacketNum++; ucOptionDataMask = (*pData++ & 0xF0); ucOptionDataMask |= (uint32_t)(*pData++) << 8; @@ -867,7 +871,6 @@ static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, S } if (ucOptionDataMask & k_EBLELeftTrackpadChunk) { int nLength = sizeof(pState->sLeftPadX) + sizeof(pState->sLeftPadY); - int nPadOffset; SDL_memcpy(&pState->sLeftPadX, pData, nLength); if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) { nPadOffset = 1000; @@ -875,14 +878,15 @@ static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, S nPadOffset = 0; } - RotatePadShort(&pState->sLeftPadX, &pState->sLeftPadY, -flRotationAngle); - pState->sLeftPadX = (short)clamp(pState->sLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); - pState->sLeftPadY = (short)clamp(pState->sLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + nLeftPadX = pState->sLeftPadX; + nLeftPadY = pState->sLeftPadY; + RotatePad(&nLeftPadX, &nLeftPadY, -flRotationAngle); + pState->sLeftPadX = (short)clamp(nLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + pState->sLeftPadY = (short)clamp(nLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); pData += nLength; } if (ucOptionDataMask & k_EBLERightTrackpadChunk) { int nLength = sizeof(pState->sRightPadX) + sizeof(pState->sRightPadY); - int nPadOffset = 0; SDL_memcpy(&pState->sRightPadX, pData, nLength); @@ -892,9 +896,11 @@ static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, S nPadOffset = 0; } - RotatePadShort(&pState->sRightPadX, &pState->sRightPadY, flRotationAngle); - pState->sRightPadX = (short)clamp(pState->sRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); - pState->sRightPadY = (short)clamp(pState->sRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + nRightPadX = pState->sRightPadX; + nRightPadY = pState->sRightPadY; + RotatePad(&nRightPadX, &nRightPadY, flRotationAngle); + pState->sRightPadX = (short)clamp(nRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + pState->sRightPadY = (short)clamp(nRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); pData += nLength; } if (ucOptionDataMask & k_EBLEIMUAccelChunk) { @@ -1038,6 +1044,11 @@ static bool HIDAPI_DriverSteam_IsSupportedDevice(SDL_HIDAPI_Device *device, cons return false; } + if (!device) { + // Might be supported by this driver, enumerate and find out + return true; + } + if (device->is_bluetooth) { return true; } @@ -1133,6 +1144,7 @@ static bool HIDAPI_DriverSteam_InitDevice(SDL_HIDAPI_Device *device) unsigned char buf[65]; int res; + SDL_zero(buf); buf[0] = 0; buf[1] = ID_DONGLE_GET_WIRELESS_STATE; res = SetFeatureReport(device, buf, 2); @@ -1194,7 +1206,7 @@ static bool SetHomeLED(SDL_HIDAPI_Device *device, Uint8 value) unsigned char buf[65]; int nSettings = 0; - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_SET_SETTINGS_VALUES; ADD_SETTING(SETTING_LED_USER_BRIGHTNESS, value); buf[2] = (unsigned char)(nSettings * 3); @@ -1305,7 +1317,7 @@ static bool HIDAPI_DriverSteam_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_ unsigned char buf[65]; int nSettings = 0; - SDL_memset(buf, 0, 65); + SDL_zero(buf); buf[1] = ID_SET_SETTINGS_VALUES; if (enabled) { ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO); diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam_hori.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam_hori.c index 0cd192d0..a480ff2b 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam_hori.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam_hori.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -32,8 +32,6 @@ /* Define this if you want to log all packets from the controller */ /*#define DEBUG_HORI_PROTOCOL*/ -#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) - enum { SDL_GAMEPAD_BUTTON_HORI_QAM = 11, @@ -51,6 +49,8 @@ typedef struct Uint8 last_state[USB_PACKET_LENGTH]; Uint64 sensor_ticks; Uint32 last_tick; + Uint64 simulated_sensor_step_ns; + Uint64 simulated_sensor_time_stamp; bool wireless; bool serial_needs_init; } SDL_DriverSteamHori_Context; @@ -114,21 +114,25 @@ static bool HIDAPI_DriverSteamHori_OpenJoystick(SDL_HIDAPI_Device *device, SDL_J /* Initialize the joystick capabilities */ joystick->nbuttons = SDL_GAMEPAD_NUM_HORI_BUTTONS; joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; - joystick->nhats = 1; + joystick->nhats = 1; - ctx->wireless = device->product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT; + ctx->wireless = device->product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT; - if (ctx->wireless && device->serial) { - joystick->serial = SDL_strdup(device->serial); - ctx->serial_needs_init = false; - } else if (!ctx->wireless) { - // Need to actual read from the device to init the serial - HIDAPI_DriverSteamHori_UpdateDevice(device); - } - - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); + if (ctx->wireless && device->serial) { + joystick->serial = SDL_strdup(device->serial); + ctx->serial_needs_init = false; + } else if (!ctx->wireless) { + // Need to actual read from the device to init the serial + HIDAPI_DriverSteamHori_UpdateDevice(device); + } + const float sensorupdaterate = ctx->wireless ? 120.0f : 250.0f; + + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, sensorupdaterate); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, sensorupdaterate); + + const Uint64 sensorupdatestep_ms = ctx->wireless ? 8333 : 4000; // Equivalent to 120hz / 250hz respectively + ctx->simulated_sensor_step_ns = SDL_US_TO_NS(sensorupdatestep_ms); return true; } @@ -275,12 +279,13 @@ static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FL, ((data[7] & 0x80) != 0)); } - if (!ctx->wireless && ctx->serial_needs_init) { + if (!ctx->wireless && ctx->serial_needs_init) { char serial[18]; - (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", - data[38], data[39], data[40], data[41], data[42], data[43]); + (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", + data[38], data[39], data[40], data[41], data[42], data[43]); - joystick->serial = SDL_strdup(serial); + SDL_AssertJoysticksLocked(); + joystick->serial = SDL_strdup(serial); ctx->serial_needs_init = false; } @@ -302,7 +307,7 @@ static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL Uint32 delta; Uint16 tick = LOAD16(data[10], data[11]); - if (ctx->last_tick < tick) { + if (ctx->last_tick <= tick) { delta = (tick - ctx->last_tick); } else { delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1); @@ -312,7 +317,11 @@ static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL ctx->sensor_ticks += delta; /* Sensor timestamp is in 1us units, but there seems to be some issues with the values reported from the device */ - sensor_timestamp = timestamp; // if the values were good we woudl call SDL_US_TO_NS(ctx->sensor_ticks); + // sensor_timestamp = timestamp; // if the values were good we would call SDL_US_TO_NS(ctx->sensor_ticks); + + /* New approach - simulate a fixed rate of 250hz (from observation). This reduces stutter from dropped/racing bluetooth packets.*/ + ctx->simulated_sensor_time_stamp += ctx->simulated_sensor_step_ns; + sensor_timestamp = ctx->simulated_sensor_time_stamp; const float accelScale = SDL_STANDARD_GRAVITY * 8 / 32768.0f; const float gyroScale = DEG2RAD(2048); @@ -320,7 +329,6 @@ static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL imu_data[1] = RemapValClamped(-1.0f * LOAD16(data[12], data[13]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); imu_data[2] = RemapValClamped(-1.0f * LOAD16(data[14], data[15]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); imu_data[0] = RemapValClamped(-1.0f * LOAD16(data[16], data[17]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); - SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, imu_data, 3); @@ -334,8 +342,9 @@ static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL if (ctx->last_state[24] != data[24]) { bool bCharging = (data[24] & 0x10) != 0; int percent = (data[24] & 0xF) * 10; - SDL_PowerState state; - if (bCharging) { + SDL_PowerState state; + + if (bCharging) { state = SDL_POWERSTATE_CHARGING; } else if (ctx->wireless) { state = SDL_POWERSTATE_ON_BATTERY; diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam_triton.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam_triton.c new file mode 100644 index 00000000..01e4efff --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steam_triton.c @@ -0,0 +1,711 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" + +#ifdef SDL_JOYSTICK_HIDAPI_STEAM_TRITON + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_STEAM_PROTOCOL +#endif + +/*****************************************************************************************************/ + +#include "steam/controller_constants.h" +#include "steam/controller_structs.h" + +// Always 1kHz according to USB descriptor, but actually about 4 ms. +#define TRITON_SENSOR_UPDATE_INTERVAL_US 4032 + +// Steam Controller hardware safety timeout is around 50ms, so we resend rumble every 40ms +#define TRITON_RUMBLE_RESEND_INTERVAL_MS 40 + +enum +{ + SDL_GAMEPAD_BUTTON_TRITON_QAM = 11, + SDL_GAMEPAD_BUTTON_TRITON_RIGHT_PADDLE1, + SDL_GAMEPAD_BUTTON_TRITON_LEFT_PADDLE1, + SDL_GAMEPAD_BUTTON_TRITON_RIGHT_PADDLE2, + SDL_GAMEPAD_BUTTON_TRITON_LEFT_PADDLE2, + SDL_GAMEPAD_BUTTON_TRITON_RIGHT_TOUCHPAD, + SDL_GAMEPAD_BUTTON_TRITON_LEFT_TOUCHPAD, + SDL_GAMEPAD_BUTTON_TRITON_RIGHT_JOYSTICK_TOUCH, + SDL_GAMEPAD_BUTTON_TRITON_LEFT_JOYSTICK_TOUCH, + SDL_GAMEPAD_BUTTON_TRITON_RIGHT_GRIP_TOUCH, + SDL_GAMEPAD_BUTTON_TRITON_LEFT_GRIP_TOUCH, + SDL_GAMEPAD_NUM_TRITON_BUTTONS, +}; + +typedef enum +{ + + TRITON_LBUTTON_A = 0x00000001, + TRITON_LBUTTON_B = 0x00000002, + TRITON_LBUTTON_X = 0x00000004, + TRITON_LBUTTON_Y = 0x00000008, + + TRITON_HBUTTON_QAM = 0x00000010, + TRITON_LBUTTON_R3 = 0x00000020, + TRITON_LBUTTON_VIEW = 0x00000040, + TRITON_HBUTTON_R4 = 0x00000080, + + TRITON_LBUTTON_R5 = 0x00000100, + TRITON_LBUTTON_R = 0x00000200, + TRITON_LBUTTON_DPAD_DOWN = 0x00000400, + TRITON_LBUTTON_DPAD_RIGHT = 0x00000800, + + TRITON_LBUTTON_DPAD_LEFT = 0x00001000, + TRITON_LBUTTON_DPAD_UP = 0x00002000, + TRITON_LBUTTON_MENU = 0x00004000, + TRITON_LBUTTON_L3 = 0x00008000, + + TRITON_LBUTTON_STEAM = 0x00010000, + TRITON_HBUTTON_L4 = 0x00020000, + TRITON_LBUTTON_L5 = 0x00040000, + TRITON_LBUTTON_L = 0x00080000, + + TRITON_RIGHT_JOYSTICK_TOUCH = 0x00100000, + TRITON_RIGHT_TOUCHPAD_TOUCH = 0x00200000, + TRITON_RIGHT_TOUCHPAD_CLICK = 0x00400000, + TRITON_RIGHT_TRIGGER_CLICK = 0x00800000, + + TRITON_LEFT_JOYSTICK_TOUCH = 0x01000000, + TRITON_LEFT_TOUCHPAD_TOUCH = 0x02000000, + TRITON_LEFT_TOUCHPAD_CLICK = 0x04000000, + TRITON_LEFT_TRIGGER_CLICK = 0x08000000, + + TRITON_RIGHT_GRIP_TOUCH = 0x10000000, + TRITON_LEFT_GRIP_TOUCH = 0x20000000, +} TritonButtons; + +typedef struct +{ + bool connected; + bool report_sensors; + Uint16 last_sensor_tick16; + Uint32 last_sensor_tick32; + Uint64 sensor_timestamp_ns; + Uint64 last_button_state; + Uint64 last_lizard_update; + Uint16 low_frequency_rumble; + Uint16 high_frequency_rumble; + Uint64 last_rumble_time; + + bool left_touch_down; + float left_touch_x; + float left_touch_y; + bool right_touch_down; + float right_touch_x; + float right_touch_y; +} SDL_DriverSteamTriton_Context; + +static bool IsProteusDongle(Uint16 product_id) +{ + return (product_id == USB_PRODUCT_VALVE_STEAM_PROTEUS_DONGLE || + product_id == USB_PRODUCT_VALVE_STEAM_NEREID_DONGLE); +} + +static bool DisableSteamTritonLizardMode(SDL_hid_device *dev) +{ + int rc; + Uint8 buffer[HID_FEATURE_REPORT_BYTES] = { 1 }; + FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1); + + msg->header.type = ID_SET_SETTINGS_VALUES; + msg->header.length = 1 * sizeof(ControllerSetting); + msg->payload.setSettingsValues.settings[0].settingNum = SETTING_LIZARD_MODE; + msg->payload.setSettingsValues.settings[0].settingValue = LIZARD_MODE_OFF; + + rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) { + return false; + } + + return true; +} + +// Triton newer state MTUs are identical until touchpads. Parse them using this routine. +// Expects report to be a TritonMTUNoQuat_t, so cast as needed +static void HIDAPI_DriverSteamTriton_HandleGenericState(SDL_DriverSteamTriton_Context *ctx, SDL_Joystick *joystick, Uint64 timestamp, TritonMTUNoQuat_t *pTritonReport) +{ + if (pTritonReport->buttons != ctx->last_button_state) { + Uint8 hat = 0; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, + ((pTritonReport->buttons & TRITON_LBUTTON_A) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, + ((pTritonReport->buttons & TRITON_LBUTTON_B) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, + ((pTritonReport->buttons & TRITON_LBUTTON_X) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, + ((pTritonReport->buttons & TRITON_LBUTTON_Y) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + ((pTritonReport->buttons & TRITON_LBUTTON_L) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + ((pTritonReport->buttons & TRITON_LBUTTON_R) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, + ((pTritonReport->buttons & TRITON_LBUTTON_MENU) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, + ((pTritonReport->buttons & TRITON_LBUTTON_VIEW) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, + ((pTritonReport->buttons & TRITON_LBUTTON_STEAM) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_QAM, + ((pTritonReport->buttons & TRITON_HBUTTON_QAM) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, + ((pTritonReport->buttons & TRITON_LBUTTON_L3) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, + ((pTritonReport->buttons & TRITON_LBUTTON_R3) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_RIGHT_PADDLE1, + ((pTritonReport->buttons & TRITON_HBUTTON_R4) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_LEFT_PADDLE1, + ((pTritonReport->buttons & TRITON_HBUTTON_L4) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_RIGHT_PADDLE2, + ((pTritonReport->buttons & TRITON_LBUTTON_R5) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_LEFT_PADDLE2, + ((pTritonReport->buttons & TRITON_LBUTTON_L5) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_RIGHT_TOUCHPAD, + ((pTritonReport->buttons & TRITON_RIGHT_TOUCHPAD_CLICK) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_LEFT_TOUCHPAD, + ((pTritonReport->buttons & TRITON_LEFT_TOUCHPAD_CLICK) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_RIGHT_JOYSTICK_TOUCH, + ((pTritonReport->buttons & TRITON_RIGHT_JOYSTICK_TOUCH) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_LEFT_JOYSTICK_TOUCH, + ((pTritonReport->buttons & TRITON_LEFT_JOYSTICK_TOUCH) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_RIGHT_GRIP_TOUCH, + ((pTritonReport->buttons & TRITON_RIGHT_GRIP_TOUCH) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_LEFT_GRIP_TOUCH, + ((pTritonReport->buttons & TRITON_LEFT_GRIP_TOUCH) != 0)); + + if (pTritonReport->buttons & TRITON_LBUTTON_DPAD_UP) { + hat |= SDL_HAT_UP; + } + if (pTritonReport->buttons & TRITON_LBUTTON_DPAD_DOWN) { + hat |= SDL_HAT_DOWN; + } + if (pTritonReport->buttons & TRITON_LBUTTON_DPAD_LEFT) { + hat |= SDL_HAT_LEFT; + } + if (pTritonReport->buttons & TRITON_LBUTTON_DPAD_RIGHT) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + ctx->last_button_state = pTritonReport->buttons; + } + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, + (int)pTritonReport->sTriggerLeft * 2 - 32768); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, + (int)pTritonReport->sTriggerRight * 2 - 32768); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, + pTritonReport->sLeftStickX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, + -pTritonReport->sLeftStickY); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, + pTritonReport->sRightStickX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, + -pTritonReport->sRightStickY); +} + +static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device, + SDL_Joystick *joystick, + TritonMTUNoQuat_t *pTritonReport) +{ + float values[3]; + SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context; + Uint64 timestamp = SDL_GetTicksNS(); + + HIDAPI_DriverSteamTriton_HandleGenericState(ctx, joystick, timestamp, pTritonReport); + + bool left_touch_down = (pTritonReport->buttons & TRITON_LEFT_TOUCHPAD_TOUCH) ? true : false; + bool right_touch_down = (pTritonReport->buttons & TRITON_RIGHT_TOUCHPAD_TOUCH) ? true : false; + + if (left_touch_down || ctx->left_touch_down) { + if (left_touch_down) { + ctx->left_touch_x = pTritonReport->sLeftPadX / 65536.0f + 0.5f; + ctx->left_touch_y = -(float)pTritonReport->sLeftPadY / 65536.0f + 0.5f; + } + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, + left_touch_down, + ctx->left_touch_x, + ctx->left_touch_y, + pTritonReport->unPressureLeft / 32768.0f); + ctx->left_touch_down = left_touch_down; + } + if (right_touch_down || ctx->right_touch_down) { + if (right_touch_down) { + ctx->right_touch_x = pTritonReport->sRightPadX / 65536.0f + 0.5f; + ctx->right_touch_y = -(float)pTritonReport->sRightPadY / 65536.0f + 0.5f; + } + SDL_SendJoystickTouchpad(timestamp, joystick, 1, 0, + right_touch_down, + ctx->right_touch_x, + ctx->right_touch_y, + pTritonReport->unPressureRight / 32768.0f); + ctx->right_touch_down = right_touch_down; + } + + if (ctx->report_sensors && pTritonReport->imu.timestamp != ctx->last_sensor_tick32) { + Uint32 delta_us = (pTritonReport->imu.timestamp - ctx->last_sensor_tick32); + + ctx->sensor_timestamp_ns += SDL_US_TO_NS(delta_us); + + values[0] = (pTritonReport->imu.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + values[1] = (pTritonReport->imu.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + values[2] = (-pTritonReport->imu.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_ns, values, 3); + + values[0] = (pTritonReport->imu.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + values[1] = (pTritonReport->imu.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + values[2] = (-pTritonReport->imu.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_ns, values, 3); + + ctx->last_sensor_tick32 = pTritonReport->imu.timestamp; + } +} + +static void HIDAPI_DriverSteamTriton_HandleState_Timestamp(SDL_HIDAPI_Device *device, + SDL_Joystick *joystick, + TritonMTUNoQuat32TS_t *pTritonReport) +{ + float values[3]; + SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context; + Uint64 timestamp = SDL_GetTicksNS(); + + HIDAPI_DriverSteamTriton_HandleGenericState(ctx, joystick, timestamp, (TritonMTUNoQuat_t *) pTritonReport); + + bool left_touch_down = (pTritonReport->buttons & TRITON_LEFT_TOUCHPAD_TOUCH) ? true : false; + bool right_touch_down = (pTritonReport->buttons & TRITON_RIGHT_TOUCHPAD_TOUCH) ? true : false; + + if (left_touch_down || ctx->left_touch_down) { + if (left_touch_down) { + ctx->left_touch_x = pTritonReport->sLeftPadX / 65536.0f + 0.5f; + ctx->left_touch_y = -(float)pTritonReport->sLeftPadY / 65536.0f + 0.5f; + } + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, + left_touch_down, + ctx->left_touch_x, + ctx->left_touch_y, + pTritonReport->unPressureLeft / 32768.0f); + ctx->left_touch_down = left_touch_down; + } + if (right_touch_down || ctx->right_touch_down) { + if (right_touch_down) { + ctx->right_touch_x = pTritonReport->sRightPadX / 65536.0f + 0.5f; + ctx->right_touch_y = -(float)pTritonReport->sRightPadY / 65536.0f + 0.5f; + } + SDL_SendJoystickTouchpad(timestamp, joystick, 1, 0, + right_touch_down, + ctx->right_touch_x, + ctx->right_touch_y, + pTritonReport->unPressureRight / 32768.0f); + ctx->right_touch_down = right_touch_down; + } + + if (ctx->report_sensors && pTritonReport->imu.timestamp != ctx->last_sensor_tick16) { + // The timestamp is in units of 32 microseconds + Uint32 delta_us = (Uint32)(pTritonReport->imu.timestamp - ctx->last_sensor_tick16) * 32; + + ctx->sensor_timestamp_ns += SDL_US_TO_NS(delta_us); + + values[0] = (pTritonReport->imu.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + values[1] = (pTritonReport->imu.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + values[2] = (-pTritonReport->imu.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_ns, values, 3); + + values[0] = (pTritonReport->imu.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + values[1] = (pTritonReport->imu.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + values[2] = (-pTritonReport->imu.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_ns, values, 3); + + ctx->last_sensor_tick16 = pTritonReport->imu.timestamp; + } +} + +static void HIDAPI_DriverSteamTriton_HandleBatteryStatus(SDL_HIDAPI_Device *device, + SDL_Joystick *joystick, + TritonBatteryStatus_t *pTritonBatteryStatus) +{ + SDL_PowerState state; + + switch (pTritonBatteryStatus->ucChargeState) { + case k_EChargeStateDischarging: + state = SDL_POWERSTATE_ON_BATTERY; + break; + case k_EChargeStateCharging: + state = SDL_POWERSTATE_CHARGING; + break; + case k_EChargeStateChargingDone: + state = SDL_POWERSTATE_CHARGED; + break; + default: + // Error state? + state = SDL_POWERSTATE_UNKNOWN; + break; + } + SDL_SendJoystickPowerInfo(joystick, state, pTritonBatteryStatus->ucBatteryLevel); +} + +static bool HIDAPI_DriverSteamTriton_SetControllerConnected(SDL_HIDAPI_Device *device, bool connected) +{ + SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context; + + if (ctx->connected != connected) { + ctx->connected = connected; + + if (connected) { + SDL_JoystickID joystickID; + if (!HIDAPI_JoystickConnected(device, &joystickID)) { + return false; + } + } else { + if (device->num_joysticks > 0) { + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + } + } + return true; +} + +static void HIDAPI_DriverSteamTriton_HandleWirelessStatus(SDL_HIDAPI_Device *device, + TritonWirelessStatus_t *pTritonWirelessStatus) +{ + switch (pTritonWirelessStatus->state) { + case k_ETritonWirelessStateConnect: + HIDAPI_DriverSteamTriton_SetControllerConnected(device, true); + break; + case k_ETritonWirelessStateDisconnect: + HIDAPI_DriverSteamTriton_SetControllerConnected(device, false); + break; + default: + break; + } +} + +/*****************************************************************************************************/ + +static void HIDAPI_DriverSteamTriton_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata); +} + +static void HIDAPI_DriverSteamTriton_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata); +} + +static bool HIDAPI_DriverSteamTriton_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverSteamTriton_IsSupportedDevice( + SDL_HIDAPI_Device *device, + const char *name, + SDL_GamepadType type, + Uint16 vendor_id, + Uint16 product_id, + Uint16 version, + int interface_number, + int interface_class, + int interface_subclass, + int interface_protocol) +{ + + if (IsProteusDongle(product_id)) { + if (interface_number >= 2 && interface_number <= 5) { + // The set of controller interfaces for Proteus & Nereid...currently + return true; + } + } else if (SDL_IsJoystickSteamTriton(vendor_id, product_id)) { + return true; + } + return false; +} + +static bool HIDAPI_DriverSteamTriton_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSteamTriton_Context *ctx; + + ctx = (SDL_DriverSteamTriton_Context *)SDL_calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return false; + } + + device->context = ctx; + + HIDAPI_SetDeviceName(device, "Steam Controller"); + + if (IsProteusDongle(device->product_id)) { + return true; + } + + // Wired controller, connected! + return HIDAPI_DriverSteamTriton_SetControllerConnected(device, true); +} + +static int HIDAPI_DriverSteamTriton_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverSteamTriton_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static bool HIDAPI_DriverSteamTriton_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble); + +static bool HIDAPI_DriverSteamTriton_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context; + SDL_Joystick *joystick = NULL; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } + + if (ctx->connected && joystick) { + Uint64 now = SDL_GetTicks(); + if (!ctx->last_lizard_update || (now - ctx->last_lizard_update) >= 3000) { + DisableSteamTritonLizardMode(device->dev); + ctx->last_lizard_update = now; + } + + if (ctx->low_frequency_rumble || ctx->high_frequency_rumble) { + if ((now - ctx->last_rumble_time) >= TRITON_RUMBLE_RESEND_INTERVAL_MS) { + HIDAPI_DriverSteamTriton_RumbleJoystick(device, joystick, ctx->low_frequency_rumble, ctx->high_frequency_rumble); + } + } + } + + for (;;) { + uint8_t data[64]; + int r = SDL_hid_read(device->dev, data, sizeof(data)); + + if (r == 0) { + return true; + } + if (r < 0) { + // Failed to read from controller + HIDAPI_DriverSteamTriton_SetControllerConnected(device, false); + return false; + } + +#ifdef DEBUG_STEAM_PROTOCOL + HIDAPI_DumpPacket("Steam Controller packet: size = %d", data, r); +#endif + + switch (data[0]) { + case ID_TRITON_CONTROLLER_STATE: + case ID_TRITON_CONTROLLER_STATE_BLE: + if (!joystick) { + HIDAPI_DriverSteamTriton_SetControllerConnected(device, true); + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } + } + if (joystick && r >= (1 + sizeof(TritonMTUNoQuat_t))) { + TritonMTUNoQuat_t *pTritonReport = (TritonMTUNoQuat_t *)&data[1]; + HIDAPI_DriverSteamTriton_HandleState(device, joystick, pTritonReport); + } + break; + case ID_TRITON_CONTROLLER_STATE_TIMESTAMP: + if (!joystick) { + HIDAPI_DriverSteamTriton_SetControllerConnected(device, true); + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } + } + if (joystick && r >= (1 + sizeof(TritonMTUNoQuat32TS_t))) { + TritonMTUNoQuat32TS_t *pTritonReport = (TritonMTUNoQuat32TS_t *)&data[1]; + HIDAPI_DriverSteamTriton_HandleState_Timestamp(device, joystick, pTritonReport); + } + break; + case ID_TRITON_BATTERY_STATUS: + if (joystick && r >= (1 + sizeof(TritonBatteryStatus_t))) { + TritonBatteryStatus_t *pTritonBatteryStatus = (TritonBatteryStatus_t *)&data[1]; + HIDAPI_DriverSteamTriton_HandleBatteryStatus(device, joystick, pTritonBatteryStatus); + } + break; + case ID_TRITON_WIRELESS_STATUS_X: + case ID_TRITON_WIRELESS_STATUS: + if (r >= (1 + sizeof(TritonWirelessStatus_t))) { + TritonWirelessStatus_t *pTritonWirelessStatus = (TritonWirelessStatus_t *)&data[1]; + HIDAPI_DriverSteamTriton_HandleWirelessStatus(device, pTritonWirelessStatus); + } + break; + default: + break; + } + } +} + +static bool HIDAPI_DriverSteamTriton_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + float update_rate_in_hz = 1000000.0f / TRITON_SENSOR_UPDATE_INTERVAL_US; + + SDL_AssertJoysticksLocked(); + + // Initialize the joystick capabilities + joystick->nbuttons = SDL_GAMEPAD_NUM_TRITON_BUTTONS; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz); + + SDL_PrivateJoystickAddTouchpad(joystick, 1); + SDL_PrivateJoystickAddTouchpad(joystick, 1); + + return true; +} + +static bool HIDAPI_DriverSteamTriton_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context; + int rc; + + ctx->low_frequency_rumble = low_frequency_rumble; + ctx->high_frequency_rumble = high_frequency_rumble; + ctx->last_rumble_time = SDL_GetTicks(); + + Uint8 buffer[HID_RUMBLE_OUTPUT_REPORT_BYTES] = { 0 }; + OutputReportMsg *msg = (OutputReportMsg *)(buffer); + + msg->report_id = ID_OUT_REPORT_HAPTIC_RUMBLE; + msg->payload.hapticRumble.type = 0; + msg->payload.hapticRumble.intensity = 0; + msg->payload.hapticRumble.left.speed = low_frequency_rumble; + msg->payload.hapticRumble.left.gain = 0; + msg->payload.hapticRumble.right.speed = high_frequency_rumble; + msg->payload.hapticRumble.right.gain = 0; + + rc = SDL_hid_write(device->dev, buffer, sizeof(buffer)); + if (rc < 0) { + SDL_LogError(SDL_LOG_CATEGORY_INPUT, + "Steam Controller HID Write FAILED! rc: %d. SDL_Error: %s", + rc, SDL_GetError()); + + return false; + } + return true; +} + +static bool HIDAPI_DriverSteamTriton_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverSteamTriton_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + return SDL_JOYSTICK_CAP_RUMBLE; +} + +static bool HIDAPI_DriverSteamTriton_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSteamTriton_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + if (size == HID_FEATURE_REPORT_BYTES) { + int rc = SDL_hid_send_feature_report(device->dev, data, size); + if (rc != size) { + return false; + } + return true; + } + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSteamTriton_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context; + int rc; + Uint8 buffer[HID_FEATURE_REPORT_BYTES] = { 1 }; + FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1); + + msg->header.type = ID_SET_SETTINGS_VALUES; + msg->header.length = 1 * sizeof(ControllerSetting); + msg->payload.setSettingsValues.settings[0].settingNum = SETTING_IMU_MODE; + if (enabled) { + msg->payload.setSettingsValues.settings[0].settingValue = (SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO); + } else { + msg->payload.setSettingsValues.settings[0].settingValue = SETTING_GYRO_MODE_OFF; + } + + rc = SDL_hid_send_feature_report(device->dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) { + return false; + } + + ctx->report_sensors = enabled; + + return true; +} + +static void HIDAPI_DriverSteamTriton_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + // Lizard mode id automatically re-enabled by watchdog. Nothing to do here. +} + +static void HIDAPI_DriverSteamTriton_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamTriton = { + SDL_HINT_JOYSTICK_HIDAPI_STEAM, + true, + HIDAPI_DriverSteamTriton_RegisterHints, + HIDAPI_DriverSteamTriton_UnregisterHints, + HIDAPI_DriverSteamTriton_IsEnabled, + HIDAPI_DriverSteamTriton_IsSupportedDevice, + HIDAPI_DriverSteamTriton_InitDevice, + HIDAPI_DriverSteamTriton_GetDevicePlayerIndex, + HIDAPI_DriverSteamTriton_SetDevicePlayerIndex, + HIDAPI_DriverSteamTriton_UpdateDevice, + HIDAPI_DriverSteamTriton_OpenJoystick, + HIDAPI_DriverSteamTriton_RumbleJoystick, + HIDAPI_DriverSteamTriton_RumbleJoystickTriggers, + HIDAPI_DriverSteamTriton_GetJoystickCapabilities, + HIDAPI_DriverSteamTriton_SetJoystickLED, + HIDAPI_DriverSteamTriton_SendJoystickEffect, + HIDAPI_DriverSteamTriton_SetSensorsEnabled, + HIDAPI_DriverSteamTriton_CloseJoystick, + HIDAPI_DriverSteamTriton_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_STEAM_TRITON + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steamdeck.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steamdeck.c index 213b2449..8bfb64df 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steamdeck.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steamdeck.c @@ -39,6 +39,10 @@ enum SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2, + SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_TOUCHPAD, + SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_TOUCHPAD, + SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_JOYSTICK_TOUCH, + SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_JOYSTICK_TOUCH, SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS, }; @@ -63,20 +67,31 @@ typedef enum STEAMDECK_LBUTTON_R5 = 0x00010000, STEAMDECK_LBUTTON_LEFT_PAD = 0x00020000, STEAMDECK_LBUTTON_RIGHT_PAD = 0x00040000, + STEAMDECK_LBUTTON_LEFT_TOUCHPAD_TOUCH = 0x00080000, + STEAMDECK_LBUTTON_RIGHT_TOUCHPAD_TOUCH = 0x00100000, STEAMDECK_LBUTTON_L3 = 0x00400000, STEAMDECK_LBUTTON_R3 = 0x04000000, STEAMDECK_HBUTTON_L4 = 0x00000200, STEAMDECK_HBUTTON_R4 = 0x00000400, + STEAMDECK_HBUTTON_LSTICK_TOUCH = 0x00004000, + STEAMDECK_HBUTTON_RSTICK_TOUCH = 0x00008000, STEAMDECK_HBUTTON_QAM = 0x00040000, } SteamDeckButtons; typedef struct { Uint32 update_rate_us; - Uint32 sensor_timestamp_us; + Uint64 sensor_timestamp_ns; Uint64 last_button_state; Uint8 watchdog_counter; + + bool left_touch_down; + float left_touch_x; + float left_touch_y; + bool right_touch_down; + float right_touch_x; + float right_touch_y; } SDL_DriverSteamDeck_Context; static bool DisableDeckLizardMode(SDL_hid_device *dev) @@ -191,6 +206,16 @@ static void HIDAPI_DriverSteamDeck_HandleState(SDL_HIDAPI_Device *device, SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2, ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L5) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_TOUCHPAD, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_RIGHT_PAD) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_TOUCHPAD, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_LEFT_PAD) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_JOYSTICK_TOUCH, + ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_RSTICK_TOUCH) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_JOYSTICK_TOUCH, + ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_LSTICK_TOUCH) != 0)); + if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_UP) { hat |= SDL_HAT_UP; } @@ -222,29 +247,45 @@ static void HIDAPI_DriverSteamDeck_HandleState(SDL_HIDAPI_Device *device, SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, -pInReport->payload.deckState.sRightStickY); - ctx->sensor_timestamp_us += ctx->update_rate_us; + ctx->sensor_timestamp_ns += SDL_US_TO_NS(ctx->update_rate_us); values[0] = (pInReport->payload.deckState.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); values[1] = (pInReport->payload.deckState.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); values[2] = (-pInReport->payload.deckState.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); - SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_us, values, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_ns, values, 3); values[0] = (pInReport->payload.deckState.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; values[1] = (pInReport->payload.deckState.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; values[2] = (-pInReport->payload.deckState.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; - SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_us, values, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_ns, values, 3); - SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, - pInReport->payload.deckState.sPressurePadLeft > 0, - pInReport->payload.deckState.sLeftPadX / 65536.0f + 0.5f, - pInReport->payload.deckState.sLeftPadY / 65536.0f + 0.5f, - pInReport->payload.deckState.sPressurePadLeft / 32768.0f); + bool left_touch_down = (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_LEFT_TOUCHPAD_TOUCH) ? true : false; + bool right_touch_down = (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_RIGHT_TOUCHPAD_TOUCH) ? true : false; + if (left_touch_down || ctx->left_touch_down) { + if (left_touch_down) { + ctx->left_touch_x = pInReport->payload.deckState.sLeftPadX / 65536.0f + 0.5f; + ctx->left_touch_y = -(float)pInReport->payload.deckState.sLeftPadY / 65536.0f + 0.5f; - 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); + } + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, + left_touch_down, + ctx->left_touch_x, + ctx->left_touch_y, + pInReport->payload.deckState.sPressurePadLeft / 32768.0f); + ctx->left_touch_down = left_touch_down; + } + if (right_touch_down || ctx->right_touch_down) { + if (right_touch_down) { + ctx->right_touch_x = pInReport->payload.deckState.sRightPadX / 65536.0f + 0.5f; + ctx->right_touch_y = -(float)pInReport->payload.deckState.sRightPadY / 65536.0f + 0.5f; + } + SDL_SendJoystickTouchpad(timestamp, joystick, 1, 0, + right_touch_down, + ctx->right_touch_x, + ctx->right_touch_y, + pInReport->payload.deckState.sPressurePadRight / 32768.0f); + ctx->right_touch_down = right_touch_down; + } } /*****************************************************************************************************/ @@ -343,7 +384,7 @@ static bool HIDAPI_DriverSteamDeck_UpdateDevice(SDL_HIDAPI_Device *device) return false; } - SDL_memset(data, 0, sizeof(data)); + SDL_zeroa(data); do { r = SDL_hid_read(device->dev, data, sizeof(data)); diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c index 82420808..8b56dcee 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -34,7 +34,9 @@ #ifdef SDL_JOYSTICK_HIDAPI_SWITCH // Define this if you want to log all packets from the controller -// #define DEBUG_SWITCH_PROTOCOL +#if 0 +#define DEBUG_SWITCH_PROTOCOL +#endif // Define this to get log output for rumble logic // #define DEBUG_RUMBLE @@ -159,20 +161,21 @@ typedef struct Uint8 ucVibrationCode; } SwitchControllerStatePacket_t; +typedef struct +{ + Sint16 sAccelX; + Sint16 sAccelY; + Sint16 sAccelZ; + + Sint16 sGyroX; + Sint16 sGyroY; + Sint16 sGyroZ; +} SwitchControllerIMUState_t; + typedef struct { SwitchControllerStatePacket_t controllerState; - - struct - { - Sint16 sAccelX; - Sint16 sAccelY; - Sint16 sAccelZ; - - Sint16 sGyroX; - Sint16 sGyroY; - Sint16 sGyroZ; - } imuState[3]; + SwitchControllerIMUState_t imuState[3]; } SwitchStatePacket_t; typedef struct @@ -313,6 +316,8 @@ typedef struct Uint64 m_ulIMUUpdateIntervalNS; Uint64 m_ulTimestampNS; bool m_bVerticalMode; + SDL_PowerState m_ePowerState; + int m_nPowerPercent; SwitchInputOnlyControllerStatePacket_t m_lastInputOnlyState; SwitchSimpleStatePacket_t m_lastSimpleState; @@ -388,7 +393,7 @@ static int WriteOutput(SDL_DriverSwitch_Context *ctx, const Uint8 *data, int siz #endif // SWITCH_SYNCHRONOUS_WRITES } -static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs expectedID) +static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs expectedID, const Uint8 *pBuf, Uint8 ucLen) { // Average response time for messages is ~30ms Uint64 endTicks = SDL_GetTicks() + 100; @@ -398,9 +403,17 @@ static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Conte if (nRead > 0) { if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) { SwitchSubcommandInputPacket_t *reply = (SwitchSubcommandInputPacket_t *)&ctx->m_rgucReadBuffer[1]; - if (reply->ucSubcommandID == expectedID && (reply->ucSubcommandAck & 0x80)) { - return reply; + if (reply->ucSubcommandID != expectedID || !(reply->ucSubcommandAck & 0x80)) { + continue; } + if (reply->ucSubcommandID == k_eSwitchSubcommandIDs_SPIFlashRead) { + SDL_assert(ucLen == sizeof(reply->spiReadData.opData)); + if (SDL_memcmp(&reply->spiReadData.opData, pBuf, ucLen) != 0) { + // This was a reply for another SPI read command + continue; + } + } + return reply; } } else { SDL_Delay(1); @@ -437,7 +450,7 @@ static bool ReadProprietaryReply(SDL_DriverSwitch_Context *ctx, ESwitchProprieta static void ConstructSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, const Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandOutputPacket_t *outPacket) { - SDL_memset(outPacket, 0, sizeof(*outPacket)); + SDL_zerop(outPacket); outPacket->commonData.ucPacketType = k_eSwitchOutputReportIDs_RumbleAndSubcommand; outPacket->commonData.ucPacketNumber = ctx->m_nCommandNumber; @@ -487,7 +500,7 @@ static bool WriteSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs continue; } - reply = ReadSubcommandReply(ctx, ucCommandID); + reply = ReadSubcommandReply(ctx, ucCommandID, pBuf, ucLen); } if (ppReply) { @@ -557,15 +570,33 @@ static Uint16 EncodeRumbleLowAmplitude(Uint16 amplitude) return lfa[100][1]; } -static void SetNeutralRumble(SwitchRumbleData_t *pRumble) +static void SetNeutralRumble(SDL_HIDAPI_Device *device, SwitchRumbleData_t *pRumble) { - pRumble->rgucData[0] = 0x00; - pRumble->rgucData[1] = 0x01; - pRumble->rgucData[2] = 0x40; - pRumble->rgucData[3] = 0x40; + bool bStandardNeutralValue; + if (device->vendor_id == USB_VENDOR_NINTENDO && + device->product_id == USB_PRODUCT_NINTENDO_N64_CONTROLLER) { + // The 8BitDo 64 Bluetooth Controller rumbles at startup with the standard neutral value, + // so we'll use a 0 amplitude value instead. + bStandardNeutralValue = false; + } else { + // The KingKong2 PRO Controller doesn't initialize correctly with a 0 amplitude value + // over Bluetooth, so we'll use the standard value in all other cases. + bStandardNeutralValue = true; + } + if (bStandardNeutralValue) { + pRumble->rgucData[0] = 0x00; + pRumble->rgucData[1] = 0x01; + pRumble->rgucData[2] = 0x40; + pRumble->rgucData[3] = 0x40; + } else { + pRumble->rgucData[0] = 0x00; + pRumble->rgucData[1] = 0x00; + pRumble->rgucData[2] = 0x01; + pRumble->rgucData[3] = 0x40; + } } -static void EncodeRumble(SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 ucHighFreqAmp, Uint8 ucLowFreq, Uint16 usLowFreqAmp) +static void EncodeRumble(SDL_HIDAPI_Device *device, SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 ucHighFreqAmp, Uint8 ucLowFreq, Uint16 usLowFreqAmp) { if (ucHighFreqAmp > 0 || usLowFreqAmp > 0) { // High-band frequency and low-band amplitude are actually nine-bits each so they @@ -582,7 +613,7 @@ static void EncodeRumble(SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 u ucHighFreqAmp, ((usLowFreqAmp >> 8) & 0x80), usLowFreqAmp & 0xFF); #endif } else { - SetNeutralRumble(pRumble); + SetNeutralRumble(device, pRumble); } } @@ -747,9 +778,10 @@ static void UpdateSlotLED(SDL_DriverSwitch_Context *ctx) { if (!ctx->m_bInputOnly) { Uint8 led_data = 0; + const Uint8 player_pattern[] = { 0x1, 0x3, 0x7, 0xf, 0x9, 0x5, 0xd, 0x6 }; if (ctx->m_bPlayerLights && ctx->m_nPlayerIndex >= 0) { - led_data = (1 << (ctx->m_nPlayerIndex % 4)); + led_data = player_pattern[ctx->m_nPlayerIndex % 8]; } WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetPlayerLights, &led_data, sizeof(led_data), NULL); } @@ -928,13 +960,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; @@ -947,39 +980,52 @@ 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 * For whatever reason the fields are in a different order for each stick * Left: X-Max, Y-Max, X-Center, Y-Center, X-Min, Y-Min - * Right: X-Center, Y-Center, X-Max, Y-Max, X-Min, Y-Min + * Right: X-Center, Y-Center, X-Min, Y-Min, X-Max, Y-Max */ // Left stick @@ -993,10 +1039,10 @@ static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx) // Right stick ctx->m_StickCalData[1].axis[0].sCenter = ((pRightStickCal[1] << 8) & 0xF00) | pRightStickCal[0]; // X Axis center ctx->m_StickCalData[1].axis[1].sCenter = (pRightStickCal[2] << 4) | (pRightStickCal[1] >> 4); // Y Axis center - ctx->m_StickCalData[1].axis[0].sMax = ((pRightStickCal[4] << 8) & 0xF00) | pRightStickCal[3]; // X Axis max above center - ctx->m_StickCalData[1].axis[1].sMax = (pRightStickCal[5] << 4) | (pRightStickCal[4] >> 4); // Y Axis max above center - ctx->m_StickCalData[1].axis[0].sMin = ((pRightStickCal[7] << 8) & 0xF00) | pRightStickCal[6]; // X Axis min below center - ctx->m_StickCalData[1].axis[1].sMin = (pRightStickCal[8] << 4) | (pRightStickCal[7] >> 4); // Y Axis min below center + ctx->m_StickCalData[1].axis[0].sMin = ((pRightStickCal[4] << 8) & 0xF00) | pRightStickCal[3]; // X Axis min below center + ctx->m_StickCalData[1].axis[1].sMin = (pRightStickCal[5] << 4) | (pRightStickCal[4] >> 4); // Y Axis min below center + ctx->m_StickCalData[1].axis[0].sMax = ((pRightStickCal[7] << 8) & 0xF00) | pRightStickCal[6]; // X Axis max above center + ctx->m_StickCalData[1].axis[1].sMax = (pRightStickCal[8] << 4) | (pRightStickCal[7] >> 4); // Y Axis max above center // Filter out any values that were uninitialized (0xFFF) in the SPI read for (stick = 0; stick < 2; ++stick) { @@ -1372,7 +1418,15 @@ static bool HIDAPI_DriverSwitch_IsSupportedDevice(SDL_HIDAPI_Device *device, con return false; } - return (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO); + if (type != SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO) { + return false; + } + + // The Nintendo Switch 2 Pro uses another driver + if (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH2_PRO) { + return false; + } + return true; } static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) @@ -1404,11 +1458,11 @@ static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO; break; case k_eSwitchDeviceInfoControllerType_HVCLeft: - HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (1)"); + HIDAPI_SetDeviceName(device, "Nintendo Family Computer Controller (1)"); device->type = SDL_GAMEPAD_TYPE_STANDARD; break; case k_eSwitchDeviceInfoControllerType_HVCRight: - HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (2)"); + HIDAPI_SetDeviceName(device, "Nintendo Family Computer Controller (2)"); device->type = SDL_GAMEPAD_TYPE_STANDARD; break; case k_eSwitchDeviceInfoControllerType_NESLeft: @@ -1493,8 +1547,8 @@ static bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device) ctx->m_bInputOnly = SDL_IsJoystickNintendoSwitchProInputOnly(device->vendor_id, device->product_id); if (!ctx->m_bInputOnly) { // Initialize rumble data, important for reading device info on the MOBAPAD M073 - SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); - SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); + SetNeutralRumble(device, &ctx->m_RumblePacket.rumbleData[0]); + SetNeutralRumble(device, &ctx->m_RumblePacket.rumbleData[1]); BReadDeviceInfo(ctx); } @@ -1548,8 +1602,8 @@ static bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys ctx->m_nCurrentInputMode = ctx->m_nInitialInputMode; // Initialize rumble data - SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); - SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); + SetNeutralRumble(device, &ctx->m_RumblePacket.rumbleData[0]); + SetNeutralRumble(device, &ctx->m_RumblePacket.rumbleData[1]); if (!device->is_bluetooth) { if (!BTrySetupUSB(ctx)) { @@ -1569,7 +1623,8 @@ static bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_NESRight && ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SNES && ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_N64 && - ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SEGA_Genesis) { + ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SEGA_Genesis && + !(device->vendor_id == USB_VENDOR_PDP && device->product_id == USB_PRODUCT_PDP_REALMZ_WIRELESS)) { if (LoadIMUCalibration(ctx)) { ctx->m_bSensorsSupported = true; } @@ -1646,11 +1701,11 @@ static bool HIDAPI_DriverSwitch_ActuallyRumbleJoystick(SDL_DriverSwitch_Context const Uint16 k_usLowFreqAmp = EncodeRumbleLowAmplitude(low_frequency_rumble); if (low_frequency_rumble || high_frequency_rumble) { - EncodeRumble(&ctx->m_RumblePacket.rumbleData[0], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp); - EncodeRumble(&ctx->m_RumblePacket.rumbleData[1], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp); + EncodeRumble(ctx->device, &ctx->m_RumblePacket.rumbleData[0], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp); + EncodeRumble(ctx->device, &ctx->m_RumblePacket.rumbleData[1], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp); } else { - SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); - SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); + SetNeutralRumble(ctx->device, &ctx->m_RumblePacket.rumbleData[0]); + SetNeutralRumble(ctx->device, &ctx->m_RumblePacket.rumbleData[1]); } ctx->m_bRumbleActive = (low_frequency_rumble || high_frequency_rumble); @@ -1757,16 +1812,29 @@ static Uint32 HIDAPI_DriverSwitch_GetJoystickCapabilities(SDL_HIDAPI_Device *dev if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_ProController && !ctx->m_bInputOnly) { // Doesn't have an RGB LED, so don't return SDL_JOYSTICK_CAP_RGB_LED here result |= SDL_JOYSTICK_CAP_RUMBLE; + // But has the HOME LED, so treat it like a mono LED + result |= SDL_JOYSTICK_CAP_MONO_LED; } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft || ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { result |= SDL_JOYSTICK_CAP_RUMBLE; + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + result |= SDL_JOYSTICK_CAP_MONO_LED; // Right JoyCon also have the HOME LED + } } return result; } static bool HIDAPI_DriverSwitch_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) { - return SDL_Unsupported(); + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; + + if (!(ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_ProController && !ctx->m_bInputOnly) && + ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_JoyConRight) { + return SDL_Unsupported(); + } + + int value = (int)((SDL_max(red, SDL_max(green, blue)) / 255.0f) * 100.0f); // The colors are received between 0-255 and we need them to be 0-100 + return SetHomeLED(ctx, (Uint8)value); } static bool HIDAPI_DriverSwitch_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) @@ -2558,26 +2626,39 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C * LSB of the battery nibble is used to report charging. * The battery level is reported from 0(empty)-8(full) */ - SDL_PowerState state; int charging = (packet->controllerState.ucBatteryAndConnection & 0x10); int level = (packet->controllerState.ucBatteryAndConnection & 0xE0) >> 4; - int percent = (int)SDL_roundf((level / 8.0f) * 100.0f); - if (charging) { if (level == 8) { - state = SDL_POWERSTATE_CHARGED; + ctx->m_ePowerState = SDL_POWERSTATE_CHARGED; } else { - state = SDL_POWERSTATE_CHARGING; + ctx->m_ePowerState = SDL_POWERSTATE_CHARGING; } } else { - state = SDL_POWERSTATE_ON_BATTERY; + ctx->m_ePowerState = SDL_POWERSTATE_ON_BATTERY; + } + ctx->m_nPowerPercent = (int)SDL_roundf((level / 8.0f) * 100.0f); + + if (!ctx->device->parent) { + SDL_PowerState state = ctx->m_ePowerState; + int percent = ctx->m_nPowerPercent; + SDL_SendJoystickPowerInfo(joystick, state, percent); + } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + SDL_DriverSwitch_Context *other = (SDL_DriverSwitch_Context *)ctx->device->parent->children[0]->context; + SDL_PowerState state = (SDL_PowerState)SDL_min(ctx->m_ePowerState, other->m_ePowerState); + int percent = SDL_min(ctx->m_nPowerPercent, other->m_nPowerPercent); + SDL_SendJoystickPowerInfo(joystick, state, percent); } - SDL_SendJoystickPowerInfo(joystick, state, percent); if (ctx->m_bReportSensors) { - bool bHasSensorData = (packet->imuState[0].sAccelZ != 0 || - packet->imuState[0].sAccelY != 0 || - packet->imuState[0].sAccelX != 0); + // Need to copy the imuState to an aligned variable + SwitchControllerIMUState_t imuState[3]; + SDL_COMPILE_TIME_ASSERT(imuState_size, sizeof(imuState) == sizeof(packet->imuState)); + SDL_memcpy(imuState, packet->imuState, sizeof(packet->imuState)); + + bool bHasSensorData = (imuState[0].sAccelZ != 0 || + imuState[0].sAccelY != 0 || + imuState[0].sAccelX != 0); if (bHasSensorData) { const Uint32 IMU_UPDATE_RATE_SAMPLE_FREQUENCY = 1000; Uint64 sensor_timestamp[3]; @@ -2606,37 +2687,37 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C if (!ctx->device->parent || ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[0], &packet->imuState[2].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[0], &packet->imuState[2].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[0], &imuState[2].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[0], &imuState[2].sAccelX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[1], &packet->imuState[1].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[1], &packet->imuState[1].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[1], &imuState[1].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[1], &imuState[1].sAccelX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[2], &packet->imuState[0].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[2], &packet->imuState[0].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[2], &imuState[0].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[2], &imuState[0].sAccelX); } if (ctx->device->parent && ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[0], &packet->imuState[2].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[0], &packet->imuState[2].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[0], &imuState[2].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[0], &imuState[2].sAccelX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[1], &packet->imuState[1].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[1], &packet->imuState[1].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[1], &imuState[1].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[1], &imuState[1].sAccelX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[2], &packet->imuState[0].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[2], &packet->imuState[0].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[2], &imuState[0].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[2], &imuState[0].sAccelX); } if (ctx->device->parent && ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[0], &packet->imuState[2].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[0], &packet->imuState[2].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[0], &imuState[2].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[0], &imuState[2].sAccelX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[1], &packet->imuState[1].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[1], &packet->imuState[1].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[1], &imuState[1].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[1], &imuState[1].sAccelX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[2], &packet->imuState[0].sGyroX); - SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[2], &packet->imuState[0].sAccelX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[2], &imuState[0].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[2], &imuState[0].sAccelX); } } else if (ctx->m_bHasSensorData) { diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch2.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch2.c new file mode 100644 index 00000000..773405c5 --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch2.c @@ -0,0 +1,1335 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +/* This driver supports the Nintendo Switch Pro controller. + Code and logic contributed by Valve Corporation under the SDL zlib license. +*/ +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../../misc/SDL_libusb.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_SWITCH2 + +#define RUMBLE_INTERVAL 12 +#define RUMBLE_MAX 29000 + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_SWITCH2_PROTOCOL +#endif + +enum +{ + SDL_GAMEPAD_BUTTON_SWITCH2_PRO_SHARE = 11, + SDL_GAMEPAD_BUTTON_SWITCH2_PRO_C, + SDL_GAMEPAD_BUTTON_SWITCH2_PRO_RIGHT_PADDLE, + SDL_GAMEPAD_BUTTON_SWITCH2_PRO_LEFT_PADDLE, + SDL_GAMEPAD_NUM_SWITCH2_PRO_BUTTONS +}; + +enum +{ + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_SHARE = 11, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_C, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_RIGHT_PADDLE1, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_LEFT_PADDLE1, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_RIGHT_PADDLE2, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_LEFT_PADDLE2, + SDL_GAMEPAD_NUM_SWITCH2_JOYCON_BUTTONS +}; + +enum +{ + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_GUIDE = 4, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_START, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_LEFT_SHOULDER, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_RIGHT_SHOULDER, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_SHARE, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_C, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_LEFT_TRIGGER, // Full trigger pull click + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_RIGHT_TRIGGER, // Full trigger pull click + SDL_GAMEPAD_NUM_SWITCH2_GAMECUBE_BUTTONS +}; + +typedef struct +{ + Uint16 neutral; + Uint16 max; + Uint16 min; +} Switch2_AxisCalibration; + +typedef struct +{ + Switch2_AxisCalibration x; + Switch2_AxisCalibration y; +} Switch2_StickCalibration; + +typedef struct +{ + SDL_HIDAPI_Device *device; + SDL_Joystick *joystick; + + SDL_LibUSBContext *libusb; + libusb_device_handle *device_handle; + bool interface_claimed; + Uint8 interface_number; + Uint8 out_endpoint; + Uint8 in_endpoint; + + Uint64 rumble_timestamp; + Uint32 rumble_seq; + Uint16 rumble_hi_amp; + Uint16 rumble_hi_freq; + Uint16 rumble_lo_amp; + Uint16 rumble_lo_freq; + Uint32 rumble_error; + bool rumble_updated; + + Switch2_StickCalibration left_stick; + Switch2_StickCalibration right_stick; + Uint8 left_trigger_zero; + Uint8 right_trigger_zero; + + float gyro_bias_x; + float gyro_bias_y; + float gyro_bias_z; + float accel_bias_x; + float accel_bias_y; + float accel_bias_z; + bool sensors_enabled; + bool sensors_ready; + int sample_count; + Uint64 first_sensor_timestamp; + Uint64 sensor_ts_coeff; + float gyro_coeff; + + bool player_lights; + int player_index; + + bool vertical_mode; + Uint8 last_state[USB_PACKET_LENGTH]; +} SDL_DriverSwitch2_Context; + +static void ParseStickCalibration(Switch2_StickCalibration *stick_data, const Uint8 *data) +{ + stick_data->x.neutral = data[0]; + stick_data->x.neutral |= (data[1] & 0x0F) << 8; + + stick_data->y.neutral = data[1] >> 4; + stick_data->y.neutral |= data[2] << 4; + + stick_data->x.max = data[3]; + stick_data->x.max |= (data[4] & 0x0F) << 8; + + stick_data->y.max = data[4] >> 4; + stick_data->y.max |= data[5] << 4; + + stick_data->x.min = data[6]; + stick_data->x.min |= (data[7] & 0x0F) << 8; + + stick_data->y.min = data[7] >> 4; + stick_data->y.min |= data[8] << 4; +} + +static int SendBulkData(SDL_DriverSwitch2_Context *ctx, const Uint8 *data, unsigned size) +{ + int transferred; + int res = ctx->libusb->bulk_transfer(ctx->device_handle, + ctx->out_endpoint, + (Uint8 *)data, + size, + &transferred, + 1000); + if (res < 0) { + return res; + } + return transferred; +} + +static int RecvBulkData(SDL_DriverSwitch2_Context *ctx, Uint8 *data, unsigned size) +{ + int transferred; + int total_transferred = 0; + int res; + + while (size > 0) { + unsigned current_read = size; + if (current_read > 64) { + current_read = 64; + } + res = ctx->libusb->bulk_transfer(ctx->device_handle, + ctx->in_endpoint, + data, + current_read, + &transferred, + 100); + if (res < 0) { + return res; + } + total_transferred += transferred; + size -= transferred; + data += current_read; + if ((unsigned) transferred < current_read) { + break; + } + } + + return total_transferred; +} + +static void MapJoystickAxis(Uint64 timestamp, SDL_Joystick *joystick, Uint8 axis, const Switch2_AxisCalibration *calib, float value, bool invert) +{ + Sint16 mapped_value; + if (calib && calib->neutral && calib->min && calib->max) { + value -= calib->neutral; + if (value < 0) { + value /= calib->min; + } else { + value /= calib->max; + } + mapped_value = (Sint16) SDL_clamp(value * SDL_MAX_SINT16, SDL_MIN_SINT16, SDL_MAX_SINT16); + } else { + mapped_value = (Sint16) HIDAPI_RemapVal(value, 0, 4096, SDL_MIN_SINT16, SDL_MAX_SINT16); + } + if (invert) { + mapped_value = ~mapped_value; + } + SDL_SendJoystickAxis(timestamp, joystick, axis, mapped_value); +} + +static void MapTriggerAxis(Uint64 timestamp, SDL_Joystick *joystick, Uint8 axis, Uint8 max, float value) +{ + Sint16 mapped_value = (Sint16) HIDAPI_RemapVal( + SDL_clamp((value - max) / (232.f - max), 0, 1), + 0, 1, + SDL_MIN_SINT16, SDL_MAX_SINT16 + ); + SDL_SendJoystickAxis(timestamp, joystick, axis, mapped_value); +} + +static bool UpdateSlotLED(SDL_DriverSwitch2_Context *ctx) +{ + Uint8 set_led_data[] = { + 0x09, 0x91, 0x00, 0x07, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + Uint8 reply[8] = {0}; + const Uint8 player_pattern[] = { 0x1, 0x3, 0x7, 0xf, 0x9, 0x5, 0xd, 0x6 }; + + if (ctx->player_lights && ctx->player_index >= 0) { + set_led_data[8] = player_pattern[ctx->player_index % 8]; + } + int res = SendBulkData(ctx, set_led_data, sizeof(set_led_data)); + if (res < 0) { + return SDL_SetError("Couldn't set LED data: %d\n", res); + } + return (RecvBulkData(ctx, reply, sizeof(reply)) > 0); +} + +static int ReadFlashBlock(SDL_DriverSwitch2_Context *ctx, Uint32 address, Uint8 *out) +{ + Uint8 flash_read_command[] = { + 0x02, 0x91, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + Uint8 buffer[0x50] = {0}; + int res; + + flash_read_command[12] = (Uint8)address; + flash_read_command[13] = (Uint8)(address >> 8); + flash_read_command[14] = (Uint8)(address >> 16); + flash_read_command[15] = (Uint8)(address >> 24); + + res = SendBulkData(ctx, flash_read_command, sizeof(flash_read_command)); + if (res < 0) { + return res; + } + + res = RecvBulkData(ctx, buffer, sizeof(buffer)); + if (res < 0) { + return res; + } + + SDL_memcpy(out, &buffer[0x10], 0x40); + return 0; +} + +static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)userdata; + bool player_lights = SDL_GetStringBoolean(hint, true); + + if (player_lights != ctx->player_lights) { + ctx->player_lights = player_lights; + + UpdateSlotLED(ctx); + HIDAPI_UpdateDeviceProperties(ctx->device); + } +} + +static void HIDAPI_DriverSwitch2_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, callback, userdata); +} + +static void HIDAPI_DriverSwitch2_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, callback, userdata); +} + +static bool HIDAPI_DriverSwitch2_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverSwitch2_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (vendor_id == USB_VENDOR_NINTENDO) { + switch (product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER: + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT: + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT: + case USB_PRODUCT_NINTENDO_SWITCH2_PRO: + return true; + } + } + + return false; +} + +static bool HIDAPI_DriverSwitch2_InitBluetooth(SDL_HIDAPI_Device *device) +{ + // FIXME: Need to add Bluetooth support + return SDL_SetError("Nintendo Switch2 controllers not supported over Bluetooth"); +} + +static bool FindBulkEndpoints(SDL_LibUSBContext *libusb, libusb_device_handle *handle, Uint8 *bInterfaceNumber, Uint8 *out_endpoint, Uint8 *in_endpoint) +{ + struct libusb_config_descriptor *config; + int found = 0; + + if (libusb->get_config_descriptor(libusb->get_device(handle), 0, &config) != 0) { + return false; + } + + for (int i = 0; i < config->bNumInterfaces; i++) { + const struct libusb_interface *iface = &config->interface[i]; + for (int j = 0; j < iface->num_altsetting; j++) { + const struct libusb_interface_descriptor *altsetting = &iface->altsetting[j]; + if (altsetting->bInterfaceNumber == 1) { + for (int k = 0; k < altsetting->bNumEndpoints; k++) { + const struct libusb_endpoint_descriptor *ep = &altsetting->endpoint[k]; + if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK) { + *bInterfaceNumber = altsetting->bInterfaceNumber; + if ((ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT) { + *out_endpoint = ep->bEndpointAddress; + found |= 1; + } + if ((ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) { + *in_endpoint = ep->bEndpointAddress; + found |= 2; + } + if (found == 3) { + libusb->free_config_descriptor(config); + return true; + } + } + } + } + } + } + libusb->free_config_descriptor(config); + return false; +} + +static bool HIDAPI_DriverSwitch2_InitUSB(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + if (!SDL_InitLibUSB(&ctx->libusb)) { + return false; + } + + ctx->device_handle = (libusb_device_handle *)SDL_GetPointerProperty(SDL_hid_get_properties(device->dev), SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER, NULL); + if (!ctx->device_handle) { + return SDL_SetError("Couldn't get libusb device handle"); + } + + if (!FindBulkEndpoints(ctx->libusb, ctx->device_handle, &ctx->interface_number, &ctx->out_endpoint, &ctx->in_endpoint)) { + return SDL_SetError("Couldn't find bulk endpoints"); + } + + ctx->libusb->set_auto_detach_kernel_driver(ctx->device_handle, true); + int res = ctx->libusb->claim_interface(ctx->device_handle, ctx->interface_number); + if (res < 0) { + return SDL_SetError("Couldn't claim interface %d: %d\n", ctx->interface_number, res); + } + ctx->interface_claimed = true; + + const Uint8 *init_sequence[] = { + (Uint8[]) { // Unknown purpose + 0x7, 0x91, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, + }, + (Uint8[]) { // Set feature output bit mask + 0x0c, 0x91, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00 + }, + (Uint8[]) { // Unknown purpose + 0x11, 0x91, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, + }, + (Uint8[]) { // Set rumble data? + 0x0a, 0x91, 0x00, 0x08, 0x00, 0x14, 0x00, 0x00, + 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x35, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }, + (Uint8[]) { // Enable feature output bits + 0x0c, 0x91, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00 + }, + (Uint8[]) { // Unknown purpose + 0x01, 0x91, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, + }, + (Uint8[]) { // Enable rumble + 0x01, 0x91, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, + }, + (Uint8[]) { // Enable grip buttons on charging grip + 0x8, 0x91, 0x0, 0x2, 0x0, 0x4, 0x0, 0x0, 0x01, 0x0, 0x0, 0x0, + }, + (Uint8[]) { // Set report format + 0x03, 0x91, 0x00, 0x0a, 0x00, 0x04, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00 + }, + (Uint8[]) { // Start output + 0x03, 0x91, 0x00, 0x0d, 0x00, 0x08, 0x00, 0x00, + 0x01, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }, + NULL, // Sentinel + }; + + unsigned char calibration_data[0x40] = {0}; + + res = ReadFlashBlock(ctx, 0x13000, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read serial number: %d", res); + } else { + char serial[0x11] = {0}; + SDL_strlcpy(serial, (char*)&calibration_data[2], sizeof(serial)); + HIDAPI_SetDeviceSerial(device, serial); + } + + res = ReadFlashBlock(ctx, 0x13040, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read factory calibration data: %d", res); + } else { + ctx->gyro_bias_x = *(float*)&calibration_data[4]; + ctx->gyro_bias_y = *(float*)&calibration_data[8]; + ctx->gyro_bias_z = *(float*)&calibration_data[12]; + } + + res = ReadFlashBlock(ctx, 0x13080, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read factory calibration data: %d", res); + } else { + ParseStickCalibration(&ctx->left_stick, &calibration_data[0x28]); + } + + res = ReadFlashBlock(ctx, 0x130C0, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read factory calibration data: %d", res); + } else { + ParseStickCalibration(&ctx->right_stick, &calibration_data[0x28]); + } + + res = ReadFlashBlock(ctx, 0x13100, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read factory calibration data: %d", res); + } else { + ctx->accel_bias_x = *(float*)&calibration_data[12]; + ctx->accel_bias_y = *(float*)&calibration_data[16]; + ctx->accel_bias_z = *(float*)&calibration_data[20]; + } + + if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER) { + res = ReadFlashBlock(ctx, 0x13140, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read factory calibration data: %d", res); + } else { + ctx->left_trigger_zero = calibration_data[0]; + ctx->right_trigger_zero = calibration_data[1]; + } + } + + res = ReadFlashBlock(ctx, 0x1FC040, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read user calibration data: %d", res); + } else if (calibration_data[0] == 0xb2 && calibration_data[1] == 0xa1) { + ParseStickCalibration(&ctx->left_stick, &calibration_data[2]); + } + + res = ReadFlashBlock(ctx, 0x1FC080, calibration_data); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read user calibration data: %d", res); + } else if (calibration_data[0] == 0xb2 && calibration_data[1] == 0xa1) { + ParseStickCalibration(&ctx->right_stick, &calibration_data[2]); + } + + for (int i = 0; init_sequence[i]; i++) { + res = SendBulkData(ctx, init_sequence[i], init_sequence[i][5] + 8); + if (res < 0) { + return SDL_SetError("Couldn't send initialization data: %d\n", res); + } + RecvBulkData(ctx, calibration_data, 0x40); + } + + return true; +} + +static bool HIDAPI_DriverSwitch2_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch2_Context *ctx; + + ctx = (SDL_DriverSwitch2_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + device->context = ctx; + + if (device->is_bluetooth) { + if (!HIDAPI_DriverSwitch2_InitBluetooth(device)) { + return false; + } + } else { + if (!HIDAPI_DriverSwitch2_InitUSB(device)) { + return false; + } + } + + ctx->sensor_ts_coeff = 10000; + ctx->gyro_coeff = 34.8f; + + // Sometimes the device handle isn't available during enumeration so we don't get the device name, so set it explicitly + switch (device->product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER: + HIDAPI_SetDeviceName(device, "Nintendo GameCube Controller"); + break; + case USB_PRODUCT_NINTENDO_SWITCH2_PRO: + HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller"); + break; + default: + break; + } + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverSwitch2_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverSwitch2_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + if (!ctx->joystick) { + return; + } + + ctx->player_index = player_index; + + UpdateSlotLED(ctx); +} + +static bool HIDAPI_DriverSwitch2_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + ctx->joystick = joystick; + + // Initialize player index (needed for setting LEDs) + ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); + ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, true); + UpdateSlotLED(ctx); + + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + + // Initialize the joystick capabilities + if (!ctx->device->parent) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); + } + switch (device->product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER: + joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH2_GAMECUBE_BUTTONS; + break; + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT: + if (ctx->device->parent) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO_L, 250.0f); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_L, 250.0f); + } + joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH2_JOYCON_BUTTONS; + break; + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT: + if (ctx->device->parent) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO_R, 250.0f); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_R, 250.0f); + } + joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH2_JOYCON_BUTTONS; + break; + case USB_PRODUCT_NINTENDO_SWITCH2_PRO: + joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH2_PRO_BUTTONS; + break; + default: + // FIXME: How many buttons does this have? + break; + } + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + ctx->rumble_hi_freq = 0x187; + ctx->rumble_lo_freq = 0x112; + + // Set up for vertical mode + ctx->vertical_mode = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false); + + return true; +} + +static bool HIDAPI_DriverSwitch2_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + if (low_frequency_rumble != ctx->rumble_lo_amp || high_frequency_rumble != ctx->rumble_hi_amp) { + ctx->rumble_lo_amp = low_frequency_rumble; + ctx->rumble_hi_amp = high_frequency_rumble; + ctx->rumble_updated = true; + } + + return true; +} + +static bool HIDAPI_DriverSwitch2_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverSwitch2_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + Uint32 result = SDL_JOYSTICK_CAP_RUMBLE; + + if (ctx->player_lights) { + result |= SDL_JOYSTICK_CAP_PLAYER_LED; + } + return result; +} + +static bool HIDAPI_DriverSwitch2_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSwitch2_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSwitch2_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + if (ctx->sensors_ready) { + Uint8 data[] = { + 0x0c, 0x91, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00 + }; + unsigned char reply[12] = {0}; + + if (enabled) { + data[8] |= 4; + } + int res = SendBulkData(ctx, data, sizeof(data)); + if (res < 0) { + return SDL_SetError("Couldn't set sensors enabled: %d\n", res); + } + RecvBulkData(ctx, reply, sizeof(reply)); + } + ctx->sensors_enabled = true; + return true; +} + +static void HandleGameCubeState(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + + if (data[5] != ctx->last_state[5]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[5] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_RIGHT_TRIGGER, ((data[5] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_RIGHT_SHOULDER, ((data[5] & 0x80) != 0)); + } + + if (data[6] != ctx->last_state[6]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_START, ((data[6] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_GUIDE, ((data[6] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_SHARE, ((data[6] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_C, ((data[6] & 0x40) != 0)); + } + + if (data[7] != ctx->last_state[7]) { + Uint8 hat = 0; + + if (data[7] & 0x01) { + hat |= SDL_HAT_DOWN; + } + if (data[7] & 0x02) { + hat |= SDL_HAT_UP; + } + if (data[7] & 0x04) { + hat |= SDL_HAT_RIGHT; + } + if (data[7] & 0x08) { + hat |= SDL_HAT_LEFT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_LEFT_TRIGGER, ((data[7] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_LEFT_SHOULDER, ((data[7] & 0x80) != 0)); + } + + MapTriggerAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFT_TRIGGER, + ctx->left_trigger_zero, + data[61] + ); + MapTriggerAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, + ctx->right_trigger_zero, + data[62] + ); + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.x, + (float) (data[11] | ((data[12] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.y, + (float) ((data[12] >> 4) | (data[13] << 4)), + true + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTX, + &ctx->right_stick.x, + (float) (data[14] | ((data[15] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTY, + &ctx->right_stick.y, + (float)((data[15] >> 4) | (data[16] << 4)), + true + ); +} + +static void HandleCombinedControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + if (data[6] != ctx->last_state[6]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[6] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[6] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_SHARE, ((data[6] & 0x20) != 0)); + } + + if (data[7] != ctx->last_state[7]) { + Uint8 hat = 0; + + if (data[7] & 0x01) { + hat |= SDL_HAT_DOWN; + } + if (data[7] & 0x02) { + hat |= SDL_HAT_UP; + } + if (data[7] & 0x04) { + hat |= SDL_HAT_RIGHT; + } + if (data[7] & 0x08) { + hat |= SDL_HAT_LEFT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[7] & 0x40) != 0)); + } + + if (data[8] != ctx->last_state[8]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_LEFT_PADDLE1, ((data[8] & 0x02) != 0)); + } + + Sint16 axis = (data[7] & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.x, + (float) (data[11] | ((data[12] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.y, + (float) ((data[12] >> 4) | (data[13] << 4)), + true + ); +} + +static void HandleMiniControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + if (data[6] != ctx->last_state[6]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[6] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[6] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_SHARE, ((data[6] & 0x10) != 0)); + } + + if (data[7] != ctx->last_state[7]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[7] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[7] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[7] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[7] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[7] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[7] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_LEFT_PADDLE1, ((data[7] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_LEFT_PADDLE2, ((data[7] & 0x80) != 0)); + } + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.y, + (float) ((data[12] >> 4) | (data[13] << 4)), + true + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.x, + (float) (data[11] | ((data[12] & 0x0F) << 8)), + true + ); +} + +static void HandleCombinedControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + if (data[5] != ctx->last_state[5]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[5] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[5] & 0x40) != 0)); + } + + if (data[6] != ctx->last_state[6]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[6] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[6] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_C, ((data[6] & 0x40) != 0)); + } + + if (data[8] != ctx->last_state[8]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_RIGHT_PADDLE1, ((data[8] & 0x01) != 0)); + } + + Sint16 axis = (data[5] & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTX, + &ctx->left_stick.x, + (float) (data[14] | ((data[15] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTY, + &ctx->left_stick.y, + (float)((data[15] >> 4) | (data[16] << 4)), + true + ); +} + +static void HandleMiniControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + if (data[5] != ctx->last_state[5]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[5] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[5] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[5] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_RIGHT_PADDLE1, ((data[5] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_RIGHT_PADDLE2, ((data[5] & 0x80) != 0)); + } + + if (data[6] != ctx->last_state[6]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[6] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[6] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_C, ((data[6] & 0x40) != 0)); + } + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.y, + (float)((data[15] >> 4) | (data[16] << 4)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.x, + (float) (data[14] | ((data[15] & 0x0F) << 8)), + false + ); +} + +static void HandleSwitchProState(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + + if (data[5] != ctx->last_state[5]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[5] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[5] & 0x40) != 0)); + } + + if (data[6] != ctx->last_state[6]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[6] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[6] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[6] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[6] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_PRO_SHARE, ((data[6] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_PRO_C, ((data[6] & 0x40) != 0)); + } + + if (data[7] != ctx->last_state[7]) { + Uint8 hat = 0; + + if (data[7] & 0x01) { + hat |= SDL_HAT_DOWN; + } + if (data[7] & 0x02) { + hat |= SDL_HAT_UP; + } + if (data[7] & 0x04) { + hat |= SDL_HAT_RIGHT; + } + if (data[7] & 0x08) { + hat |= SDL_HAT_LEFT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[7] & 0x40) != 0)); + } + + if (data[8] != ctx->last_state[8]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_PRO_RIGHT_PADDLE, ((data[8] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_PRO_LEFT_PADDLE, ((data[8] & 0x02) != 0)); + } + + axis = (data[5] & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + axis = (data[7] & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.x, + (float) (data[11] | ((data[12] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.y, + (float) ((data[12] >> 4) | (data[13] << 4)), + true + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTX, + &ctx->right_stick.x, + (float) (data[14] | ((data[15] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTY, + &ctx->right_stick.y, + (float)((data[15] >> 4) | (data[16] << 4)), + true + ); +} + +static void EncodeHDRumble(Uint16 high_freq, Uint16 high_amp, Uint16 low_freq, Uint16 low_amp, Uint8 rumble_data[5]) +{ + rumble_data[0] = (Uint8)(high_freq & 0xFF); + rumble_data[1] = (Uint8)(((high_amp >> 4) & 0xfc) | ((high_freq >> 8) & 0x03)); + rumble_data[2] = (Uint8)((high_amp >> 12) | (low_freq << 4)); + rumble_data[3] = (Uint8)((low_amp & 0xc0) | ((low_freq >> 4) & 0x3f)); + rumble_data[4] = (Uint8)(low_amp >> 8); +} + +static bool UpdateRumble(SDL_DriverSwitch2_Context *ctx) +{ + if (!ctx->rumble_updated && !ctx->rumble_lo_amp && !ctx->rumble_hi_amp) { + return true; + } + + Uint64 timestamp = SDL_GetTicks(); + Uint64 interval = RUMBLE_INTERVAL; + + if (timestamp < ctx->rumble_timestamp) { + return true; + } + + if (!SDL_HIDAPI_LockRumble()) { + return false; + } + + unsigned char rumble_data[64] = {0}; + if (ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER) { + Uint16 rumble_max = SDL_max(ctx->rumble_lo_amp, ctx->rumble_hi_amp); + rumble_data[0x00] = 0x3; + rumble_data[1] = 0x50 | (ctx->rumble_seq & 0xf); + if (rumble_max == 0) { + rumble_data[2] = 2; + ctx->rumble_error = 0; + } else { + if (ctx->rumble_error < rumble_max) { + rumble_data[2] = 1; + ctx->rumble_error += UINT16_MAX - rumble_max; + } else { + rumble_data[2] = 0; + ctx->rumble_error -= rumble_max; + } + } + } else { + // Rumble can get so strong that it might be dangerous to the controller... + // This is a game controller, not a massage device, so let's clamp it somewhat + Uint16 low_amp = (Uint16)((int)ctx->rumble_lo_amp * RUMBLE_MAX / UINT16_MAX); + Uint16 high_amp = (Uint16)((int)ctx->rumble_hi_amp * RUMBLE_MAX / UINT16_MAX); + rumble_data[0x01] = 0x50 | (ctx->rumble_seq & 0xf); + EncodeHDRumble(ctx->rumble_hi_freq, high_amp, ctx->rumble_lo_freq, low_amp, &rumble_data[0x02]); + switch (ctx->device->product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT: + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT: + if (ctx->device->parent) { + // FIXME: This shouldn't be necessary, but the rumble thread appears to back up if we don't do this + interval *= 2; + } + rumble_data[0] = 0x1; + break; + case USB_PRODUCT_NINTENDO_SWITCH2_PRO: + rumble_data[0] = 0x2; + SDL_memcpy(&rumble_data[0x11], &rumble_data[0x01], 6); + break; + } + } + ctx->rumble_seq++; + ctx->rumble_updated = false; + if (!ctx->rumble_lo_amp && !ctx->rumble_hi_amp) { + ctx->rumble_timestamp = 0; + } else { + if (!ctx->rumble_timestamp) { + ctx->rumble_timestamp = timestamp; + } + ctx->rumble_timestamp += interval; + } + + if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, rumble_data, sizeof(rumble_data)) != sizeof(rumble_data)) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; +} + +static void HIDAPI_DriverSwitch2_HandleStatePacket(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + Uint64 timestamp = SDL_GetTicksNS(); + + if (size < 64) { + // We don't know how to handle this report + return; + } + + switch (device->product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER: + HandleGameCubeState(timestamp, joystick, ctx, data, size); + break; + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT: + if (device->parent || ctx->vertical_mode) { + HandleCombinedControllerStateL(timestamp, joystick, ctx, data, size); + } else { + HandleMiniControllerStateL(timestamp, joystick, ctx, data, size); + } + break; + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT: + if (device->parent || ctx->vertical_mode) { + HandleCombinedControllerStateR(timestamp, joystick, ctx, data, size); + } else { + HandleMiniControllerStateR(timestamp, joystick, ctx, data, size); + } + break; + case USB_PRODUCT_NINTENDO_SWITCH2_PRO: + HandleSwitchProState(timestamp, joystick, ctx, data, size); + break; + default: + // FIXME: Need state handling implementation + break; + } + + Uint64 sensor_timestamp = (Uint32) (data[0x2b] | (data[0x2c] << 8U) | (data[0x2d] << 16U) | (data[0x2e] << 24U)); + if (sensor_timestamp && !ctx->sensors_ready) { + ctx->sample_count++; + if (ctx->sample_count >= 5 && !ctx->first_sensor_timestamp) { + ctx->first_sensor_timestamp = sensor_timestamp; + ctx->sample_count = 0; + } else if (ctx->sample_count == 100) { + // Calculate timestamp coefficient + // Timestamp are normally microseconds but sometimes it's something else for no apparent reason + Uint64 coeff = 1000 * (sensor_timestamp - ctx->first_sensor_timestamp) / (ctx->sample_count * 4); + if ((coeff + 100000) / 200000 == 5) { + // Within 10% of 1000 + ctx->sensor_ts_coeff = 10000; + ctx->gyro_coeff = 34.8f; + ctx->sensors_ready = true; + } else if (coeff != 0) { + ctx->sensor_ts_coeff = 10000000000 / coeff; + ctx->gyro_coeff = 40.0f; + ctx->sensors_ready = true; + } else { + // Didn't get a valid reading, try again + ctx->first_sensor_timestamp = 0; + ctx->sample_count = 0; + } + + if (ctx->sensors_ready && !ctx->sensors_enabled) { + Uint8 set_features[] = { + 0x0c, 0x91, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00 + }; + unsigned char reply[12] = {0}; + + SendBulkData(ctx, set_features, sizeof(set_features)); + RecvBulkData(ctx, reply, sizeof(reply)); + } + } + } + if (ctx->sensors_enabled && sensor_timestamp && ctx->sensors_ready) { + sensor_timestamp = sensor_timestamp * ctx->sensor_ts_coeff / 10; + float accel_data[3]; + float gyro_data[3]; + const float g = 9.80665f; + const float accel_scale = g * 8.f / INT16_MAX; + + accel_data[0] = (Sint16)(data[0x31] | (data[0x32] << 8)) * accel_scale; + accel_data[1] = (Sint16)(data[0x35] | (data[0x36] << 8)) * accel_scale; + accel_data[2] = (Sint16)(data[0x33] | (data[0x34] << 8)) * -accel_scale; + + gyro_data[0] = (Sint16)(data[0x37] | (data[0x38] << 8)) * ctx->gyro_coeff / INT16_MAX - ctx->gyro_bias_x; + gyro_data[1] = (Sint16)(data[0x3b] | (data[0x3c] << 8)) * ctx->gyro_coeff / INT16_MAX - ctx->gyro_bias_z; + gyro_data[2] = (Sint16)(data[0x39] | (data[0x3a] << 8)) * -ctx->gyro_coeff / INT16_MAX + ctx->gyro_bias_y; + + switch (ctx->device->product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT: + if (ctx->device->parent) { + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO_L, sensor_timestamp, gyro_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL_L, sensor_timestamp, accel_data, 3); + } else { + float tmp = -accel_data[0]; + accel_data[0] = accel_data[2]; + accel_data[2] = tmp; + + tmp = -gyro_data[0]; + gyro_data[0] = gyro_data[2]; + gyro_data[2] = tmp; + + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, gyro_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, accel_data, 3); + } + break; + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT: + if (ctx->device->parent) { + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, gyro_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, accel_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO_R, sensor_timestamp, gyro_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL_R, sensor_timestamp, accel_data, 3); + } else { + float tmp = accel_data[0]; + accel_data[0] = -accel_data[2]; + accel_data[2] = tmp; + + tmp = gyro_data[0]; + gyro_data[0] = -gyro_data[2]; + gyro_data[2] = tmp; + + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, gyro_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, accel_data, 3); + } + break; + default: + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, gyro_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, accel_data, 3); + break; + } + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverSwitch2_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_SWITCH2_PROTOCOL + if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT) { + HIDAPI_DumpPacket("Nintendo Joy-Con(L) packet: size = %d", data, size); + } else if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT) { + HIDAPI_DumpPacket("Nintendo Joy-Con(R) packet: size = %d", data, size); + } else { + HIDAPI_DumpPacket("Nintendo Switch2 packet: size = %d", data, size); + } +#endif + if (!joystick) { + continue; + } + + HIDAPI_DriverSwitch2_HandleStatePacket(device, joystick, ctx, data, size); + + UpdateRumble(ctx); + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverSwitch2_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + + ctx->joystick = NULL; +} + +static void HIDAPI_DriverSwitch2_FreeDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + if (ctx) { + if (ctx->interface_claimed) { + ctx->libusb->release_interface(ctx->device_handle, ctx->interface_number); + ctx->interface_claimed = false; + } + if (ctx->libusb) { + SDL_QuitLibUSB(); + ctx->libusb = NULL; + } + } +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch2 = { + SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, + true, + HIDAPI_DriverSwitch2_RegisterHints, + HIDAPI_DriverSwitch2_UnregisterHints, + HIDAPI_DriverSwitch2_IsEnabled, + HIDAPI_DriverSwitch2_IsSupportedDevice, + HIDAPI_DriverSwitch2_InitDevice, + HIDAPI_DriverSwitch2_GetDevicePlayerIndex, + HIDAPI_DriverSwitch2_SetDevicePlayerIndex, + HIDAPI_DriverSwitch2_UpdateDevice, + HIDAPI_DriverSwitch2_OpenJoystick, + HIDAPI_DriverSwitch2_RumbleJoystick, + HIDAPI_DriverSwitch2_RumbleJoystickTriggers, + HIDAPI_DriverSwitch2_GetJoystickCapabilities, + HIDAPI_DriverSwitch2_SetJoystickLED, + HIDAPI_DriverSwitch2_SendJoystickEffect, + HIDAPI_DriverSwitch2_SetJoystickSensorsEnabled, + HIDAPI_DriverSwitch2_CloseJoystick, + HIDAPI_DriverSwitch2_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_SWITCH2 + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_wii.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_wii.c index fb3e164a..ac2d7b98 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_wii.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_wii.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -512,6 +512,8 @@ static void UpdatePowerLevelWiiU(SDL_Joystick *joystick, Uint8 extensionBatteryB bool pluggedIn = !(extensionBatteryByte & 0x04); Uint8 batteryLevel = extensionBatteryByte >> 4; + SDL_AssertJoysticksLocked(); + if (pluggedIn) { joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED; } else { diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360.c index 49be08ab..208771e5 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -32,6 +32,10 @@ // Define this if you want to log all packets from the controller // #define DEBUG_XBOX_PROTOCOL +#ifdef SDL_PLATFORM_MACOS +#include +#endif + typedef struct { SDL_HIDAPI_Device *device; @@ -39,6 +43,9 @@ typedef struct int player_index; bool player_lights; Uint8 last_state[USB_PACKET_LENGTH]; +#ifdef SDL_PLATFORM_MACOS + bool controlled_by_360controller; +#endif } SDL_DriverXbox360_Context; static void HIDAPI_DriverXbox360_RegisterHints(SDL_HintCallback callback, void *userdata) @@ -59,6 +66,22 @@ static bool HIDAPI_DriverXbox360_IsEnabled(void) SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT))); } +#ifdef SDL_PLATFORM_MACOS +static bool IsControlledBy360ControllerDriverMacOS(SDL_HIDAPI_Device *device) +{ + bool controlled_by_360controller = false; + if (device && device->path && SDL_strncmp("DevSrvsID:", device->path, 10) == 0) { + uint64_t entry_id = SDL_strtoull(device->path + 10, NULL, 10); + io_service_t service = IOServiceGetMatchingService(0, IORegistryEntryIDMatching(entry_id)); + if (service != MACH_PORT_NULL) { + controlled_by_360controller = IOObjectConformsTo(service, "Xbox360ControllerClass"); + IOObjectRelease(service); + } + } + return controlled_by_360controller; +} +#endif + static bool HIDAPI_DriverXbox360_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) { const int XB360W_IFACE_PROTOCOL = 129; // Wireless @@ -80,13 +103,22 @@ static bool HIDAPI_DriverXbox360_IsSupportedDevice(SDL_HIDAPI_Device *device, co // This is the chatpad or other input interface, not the Xbox 360 interface return false; } +#ifdef SDL_PLATFORM_MACOS + if (IsControlledBy360ControllerDriverMacOS(device)) { + // Wired Xbox controllers are handled by this driver, when they are + // controlled by the 360Controller driver available from: + // https://github.com/360Controller/360Controller/releases + return true; + } +#endif #if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI) if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) { // GCController support doesn't work with the Steam Virtual Gamepad return true; } else { - // On macOS you can't write output reports to wired XBox controllers, - // so we'll just use the GCController support instead. + // On macOS when it isn't controlled by the 360Controller driver and + // it doesn't look like a Steam virtual gamepad we should rely on + // GCController support instead. return false; } #else @@ -138,6 +170,9 @@ static bool HIDAPI_DriverXbox360_InitDevice(SDL_HIDAPI_Device *device) return false; } ctx->device = device; +#ifdef SDL_PLATFORM_MACOS + ctx->controlled_by_360controller = IsControlledBy360ControllerDriverMacOS(device); +#endif device->context = ctx; @@ -198,6 +233,19 @@ static bool HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy static bool HIDAPI_DriverXbox360_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) { +#ifdef SDL_PLATFORM_MACOS + if (((SDL_DriverXbox360_Context *)device->context)->controlled_by_360controller) { + // On macOS the 360Controller driver uses this short report, + // and we need to prefix it with a magic token so hidapi passes it through untouched + Uint8 rumble_packet[] = { 'M', 'A', 'G', 'I', 'C', '0', 0x00, 0x04, 0x00, 0x00 }; + rumble_packet[6 + 2] = (low_frequency_rumble >> 8); + rumble_packet[6 + 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; + } +#endif Uint8 rumble_packet[] = { 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; rumble_packet[3] = (low_frequency_rumble >> 8); diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360w.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360w.c index bf63707a..57206bb8 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360w.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xbox360w.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xboxone.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xboxone.c index 342eabdf..a3fa66e7 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xboxone.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_xboxone.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -26,6 +26,7 @@ #include "../SDL_sysjoystick.h" #include "SDL_hidapijoystick_c.h" #include "SDL_hidapi_rumble.h" +#include "SDL_report_descriptor.h" #ifdef SDL_JOYSTICK_HIDAPI_XBOXONE @@ -33,7 +34,9 @@ // #define DEBUG_JOYSTICK // Define this if you want to log all packets from the controller -// #define DEBUG_XBOX_PROTOCOL +#if 0 +#define DEBUG_XBOX_PROTOCOL +#endif #if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) #define XBOX_ONE_DRIVER_ACTIVE 1 @@ -132,6 +135,8 @@ typedef struct bool has_unmapped_state; bool has_trigger_rumble; bool has_share_button; + bool has_separate_back_button; + bool has_separate_guide_button; Uint8 last_paddle_state; Uint8 low_frequency_rumble; Uint8 high_frequency_rumble; @@ -140,6 +145,8 @@ typedef struct SDL_XboxOneRumbleState rumble_state; Uint64 rumble_time; bool rumble_pending; + SDL_ReportDescriptor *descriptor; + Uint32 last_buttons; Uint8 last_state[USB_PACKET_LENGTH]; Uint8 *chunk_buffer; Uint32 chunk_length; @@ -266,8 +273,14 @@ static bool SendSerialRequest(SDL_DriverXboxOne_Context *ctx) static bool ControllerSendsAnnouncement(Uint16 vendor_id, Uint16 product_id) { - if (vendor_id == USB_VENDOR_PDP && product_id == 0x0246) { - // The PDP Rock Candy (PID 0x0246) doesn't send the announce packet on Linux for some reason + // The PDP Rock Candy (PID 0x0246) and PowerA Fusion Pro 4 (PID 0x400b) + // don't send the announce packet on Linux for some reason. + // + // Just to be safe and cover future products, we'll always send the startup + // protocol sequence for PDP and PowerA controllers + if (vendor_id == USB_VENDOR_PDP || + vendor_id == USB_VENDOR_POWERA || + vendor_id == USB_VENDOR_POWERA_ALT) { return false; } return true; @@ -350,6 +363,10 @@ static bool HIDAPI_DriverXboxOne_IsEnabled(void) static bool HIDAPI_DriverXboxOne_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) { + static const int LIBUSB_CLASS_VENDOR_SPEC = 0xFF; + static const int XBONE_IFACE_SUBCLASS = 71; + static const int XBONE_IFACE_PROTOCOL = 208; + #if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI) if (!SDL_IsJoystickBluetoothXboxOne(vendor_id, product_id)) { // On macOS we get a shortened version of the real report and @@ -358,6 +375,19 @@ static bool HIDAPI_DriverXboxOne_IsSupportedDevice(SDL_HIDAPI_Device *device, co return false; } #endif +#ifdef SDL_PLATFORM_WIN32 + if (device && SDL_strncmp(device->path, "\\\\?\\HID#", 8) == 0) { + // Windows provides a fake HID endpoint for XGIP controllers, don't use this + return false; + } +#endif + if (interface_class && + (interface_class != LIBUSB_CLASS_VENDOR_SPEC || + interface_subclass != XBONE_IFACE_SUBCLASS || + interface_protocol != XBONE_IFACE_PROTOCOL)) { + // This isn't the Xbox gamepad interface + return false; + } return (type == SDL_GAMEPAD_TYPE_XBOXONE); } @@ -373,6 +403,56 @@ static bool HIDAPI_DriverXboxOne_InitDevice(SDL_HIDAPI_Device *device) device->context = ctx; + Uint8 descriptor[1024]; + int descriptor_len = SDL_hid_get_report_descriptor(device->dev, descriptor, sizeof(descriptor)); + if (descriptor_len > 0) { + HIDAPI_DumpPacket("Xbox One report descriptor: size = %d", descriptor, descriptor_len); + + ctx->descriptor = SDL_ParseReportDescriptor(descriptor, descriptor_len); + if (ctx->descriptor) { + // Collapse the buttons into a single field read + int field_count = ctx->descriptor->field_count; + DescriptorInputField *fields = ctx->descriptor->fields; + int button_count = 0; + for (int i = 0; i < field_count; ++i) { + DescriptorInputField *field = &fields[i]; + if (field->usage == MAKE_USAGE(USB_USAGEPAGE_BUTTON, 1)) { + Uint32 expected_usage = field->usage; + int expected_offset = field->bit_offset; + for (int j = i; j < field_count; ++j) { + DescriptorInputField *other = &fields[j]; + if (other->usage != expected_usage || + other->bit_offset != expected_offset) { + break; + } + + ++button_count; + ++expected_usage; + ++expected_offset; + } + field->bit_size = button_count; + + int next_field = i + button_count; + int fields_left = (field_count - next_field); + SDL_memmove(&fields[i+1], &fields[next_field], (fields_left * sizeof(fields[0]))); + ctx->descriptor->field_count -= (button_count - 1); + break; + } + } + if (!SDL_DescriptorHasUsage(ctx->descriptor, USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_X) || + !SDL_DescriptorHasUsage(ctx->descriptor, USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_Y) || + (button_count != 12 && button_count != 15)) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Xbox report descriptor missing expected usages, ignoring"); + SDL_DestroyDescriptor(ctx->descriptor); + ctx->descriptor = NULL; + } + } else { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't parse Xbox report descriptor: %s", SDL_GetError()); + } + } else { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Xbox report descriptor not available"); + } + ctx->vendor_id = device->vendor_id; ctx->product_id = device->product_id; ctx->start_time = SDL_GetTicks(); @@ -581,6 +661,310 @@ static bool HIDAPI_DriverXboxOne_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *de return SDL_Unsupported(); } +static void HIDAPI_DriverXboxOne_HandleBatteryState(SDL_Joystick *joystick, Uint32 flags) +{ + bool on_usb = (((flags & 0x0C) >> 2) == 0); + SDL_PowerState state; + int percent = 0; + + // Mapped percentage value from: + // https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_getbatterystate + switch (flags & 0x03) { + case 0: + percent = 10; + break; + case 1: + percent = 40; + break; + case 2: + percent = 70; + break; + case 3: + percent = 100; + break; + } + if (on_usb) { + state = SDL_POWERSTATE_CHARGING; + } else { + state = SDL_POWERSTATE_ON_BATTERY; + } + SDL_SendJoystickPowerInfo(joystick, state, percent); +} + +static void HandleDescriptorAxis(Uint64 timestamp, SDL_Joystick *joystick, SDL_GamepadAxis axis, Uint32 value) +{ + Sint16 axis_value = (Sint16)((int)value - 0x8000); + SDL_SendJoystickAxis(timestamp, joystick, axis, axis_value); +} + +static void HandleDescriptorTrigger(Uint64 timestamp, SDL_Joystick *joystick, SDL_GamepadAxis axis, Uint32 value) +{ + Sint16 axis_value = (Sint16)(((int)value * 64) - 32768); + if (axis_value == 32704) { + axis_value = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, axis, axis_value); +} + +static bool HIDAPI_DriverXboxOne_HandleDescriptorReport(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) +{ + const SDL_ReportDescriptor *descriptor = ctx->descriptor; + Uint64 timestamp = SDL_GetTicksNS(); + + // Skip the report ID + const Uint8 report_id = *data; + ++data; + --size; + + for (int i = 0; i < descriptor->field_count; ++i) { + DescriptorInputField *field = &descriptor->fields[i]; + if (field->report_id != report_id) { + continue; + } + + Uint32 value; + if (!SDL_ReadReportData(data, size, field->bit_offset, field->bit_size, &value)) { + continue; + } + + switch (field->usage) { + case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_X): + { + HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, value); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_Y): + { + HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, value); + break; + } + // Some controllers use Z+RZ for the right thumbstick and BRAKE and ACCEL for the left and right triggers + // and others use RX+RY for the right thumbstick and Z and RZ for the left and right triggers + case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_Z): + { + if (field->bit_size == 16) { + HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, value); + } else if (field->bit_size == 10) { + HandleDescriptorTrigger(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, value); + } + break; + } + case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_RX): + { + HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, value); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_RY): + { + HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, value); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_RZ): + { + if (field->bit_size == 16) { + HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, value); + } else if (field->bit_size == 10) { + HandleDescriptorTrigger(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, value); + } + break; + } + case MAKE_USAGE(USB_USAGEPAGE_SIMULATION, USB_USAGE_SIMULATION_BRAKE): + { + HandleDescriptorTrigger(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, value); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_SIMULATION, USB_USAGE_SIMULATION_ACCELERATOR): + { + HandleDescriptorTrigger(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, value); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_HAT): + { + Uint8 hat; + + switch (value) { + case 1: + hat = SDL_HAT_UP; + break; + case 2: + hat = SDL_HAT_RIGHTUP; + break; + case 3: + hat = SDL_HAT_RIGHT; + break; + case 4: + hat = SDL_HAT_RIGHTDOWN; + break; + case 5: + hat = SDL_HAT_DOWN; + break; + case 6: + hat = SDL_HAT_LEFTDOWN; + break; + case 7: + hat = SDL_HAT_LEFT; + break; + case 8: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 1): + { + static const SDL_GamepadButton button_map_12[] = { + // 0x0001 + SDL_GAMEPAD_BUTTON_SOUTH, + // 0x0002 + SDL_GAMEPAD_BUTTON_EAST, + // 0x0004 + SDL_GAMEPAD_BUTTON_WEST, + // 0x0008 + SDL_GAMEPAD_BUTTON_NORTH, + // 0x0010 + SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + // 0x0020 + SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + // 0x0040 + SDL_GAMEPAD_BUTTON_BACK, + // 0x0080 + SDL_GAMEPAD_BUTTON_START, + // 0x0100 + SDL_GAMEPAD_BUTTON_LEFT_STICK, + // 0x0200 + SDL_GAMEPAD_BUTTON_RIGHT_STICK, + // 0x0400 + SDL_GAMEPAD_BUTTON_GUIDE, + // 0x0800 + SDL_GAMEPAD_BUTTON_INVALID, + }; + static const SDL_GamepadButton button_map_15[] = { + // 0x0001 + SDL_GAMEPAD_BUTTON_SOUTH, + // 0x0002 + SDL_GAMEPAD_BUTTON_EAST, + // 0x0004 + SDL_GAMEPAD_BUTTON_INVALID, + // 0x0008 + SDL_GAMEPAD_BUTTON_WEST, + // 0x0010 + SDL_GAMEPAD_BUTTON_NORTH, + // 0x0020 + SDL_GAMEPAD_BUTTON_INVALID, + // 0x0040 + SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + // 0x0080 + SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + // 0x0100 + SDL_GAMEPAD_BUTTON_INVALID, + // 0x0200 + SDL_GAMEPAD_BUTTON_INVALID, + // 0x0400 + SDL_GAMEPAD_BUTTON_BACK, + // 0x0800 + SDL_GAMEPAD_BUTTON_START, + // 0x1000 + SDL_GAMEPAD_BUTTON_GUIDE, + // 0x2000 + SDL_GAMEPAD_BUTTON_LEFT_STICK, + // 0x4000 + SDL_GAMEPAD_BUTTON_RIGHT_STICK, + }; + + if (value == ctx->last_buttons) { + break; + } + ctx->last_buttons = value; + + const SDL_GamepadButton *button_map; + if (field->bit_size == 12) { + button_map = button_map_12; + } else if (field->bit_size == 15) { + button_map = button_map_15; + } else { + // Should never happen + break; + } + for (int button_index = 0; button_index < field->bit_size; ++button_index, value >>= 1) { + SDL_GamepadButton button = button_map[button_index]; + if (button == SDL_GAMEPAD_BUTTON_INVALID) { + continue; + } + if (button == SDL_GAMEPAD_BUTTON_BACK && ctx->has_separate_back_button) { + continue; + } + if (button == SDL_GAMEPAD_BUTTON_GUIDE && ctx->has_separate_guide_button) { + continue; + } + + bool pressed = ((value & 1) != 0); + SDL_SendJoystickButton(timestamp, joystick, button, pressed); + } + break; + } + case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_AC_BACK): + { + bool pressed = (value != 0); + if (pressed) { + ctx->has_separate_back_button = true; + } + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, pressed); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_AC_HOME): + { + bool pressed = (value != 0); + if (pressed) { + ctx->has_separate_guide_button = true; + } + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, pressed); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_RECORD): + { + if (ctx->has_share_button) { + bool pressed = (value != 0); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, pressed); + } + break; + } + case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_ORDER_MOVIE): + { + // This value is the currently selected profile + ctx->has_unmapped_state = (value == 0); + break; + } + case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_ASSIGN_SELECTION): + { + if (ctx->has_paddles) { + if (!ctx->has_unmapped_state) { + value = 0; + } + + Uint8 button = (Uint8)(SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON + ctx->has_share_button); // Next available button + SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x1) != 0)); + SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x2) != 0)); + SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x4) != 0)); + SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x8) != 0)); + } + break; + } + case MAKE_USAGE(USB_USAGEPAGE_DEVICE_CONTROLS, USB_USAGE_DEVICE_CONTROLS_BATTERY_STRENGTH): + { + HIDAPI_DriverXboxOne_HandleBatteryState(joystick, value); + break; + } + default: + break; + } + } + return true; +} + /* * The Xbox One Elite controller with 5.13+ firmware sends the unmapped state in a separate packet. * We can use this to send the paddle state when they aren't mapped @@ -810,7 +1194,7 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D } } - axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[2])) * 64) - 32768; + axis = ((int)LOAD16(data[2], data[3]) * 64) - 32768; if (axis == 32704) { axis = 32767; } @@ -819,7 +1203,7 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D } SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); - axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[4])) * 64) - 32768; + axis = ((int)LOAD16(data[4], data[5]) * 64) - 32768; if (axis == -32768 && size == 26 && (data[18] & 0x40)) { axis = 32767; } @@ -828,13 +1212,13 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D } SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); - axis = SDL_Swap16LE(*(Sint16 *)(&data[6])); + axis = LOAD16(data[6], data[7]); SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); - axis = SDL_Swap16LE(*(Sint16 *)(&data[8])); + axis = LOAD16(data[8], data[9]); SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); - axis = SDL_Swap16LE(*(Sint16 *)(&data[10])); + axis = LOAD16(data[10], data[11]); SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); - axis = SDL_Swap16LE(*(Sint16 *)(&data[12])); + axis = LOAD16(data[12], data[13]); SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); @@ -1030,25 +1414,25 @@ static void HIDAPI_DriverXboxOneBluetooth_HandleStatePacket(SDL_Joystick *joysti SDL_SendJoystickHat(timestamp, joystick, 0, hat); } - axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[9])) * 64) - 32768; + axis = ((int)LOAD16(data[9], data[10]) * 64) - 32768; if (axis == 32704) { axis = 32767; } SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); - axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[11])) * 64) - 32768; + axis = ((int)LOAD16(data[11], data[12]) * 64) - 32768; if (axis == 32704) { axis = 32767; } SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); - axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[1])) - 0x8000; + axis = (int)(Uint16)LOAD16(data[1], data[2]) - 0x8000; SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); - axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[3])) - 0x8000; + axis = (int)(Uint16)LOAD16(data[3], data[4]) - 0x8000; SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); - axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[5])) - 0x8000; + axis = (int)(Uint16)LOAD16(data[5], data[6]) - 0x8000; SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); - axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[7])) - 0x8000; + axis = (int)(Uint16)LOAD16(data[7], data[8]) - 0x8000; SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); @@ -1064,33 +1448,7 @@ static void HIDAPI_DriverXboxOneBluetooth_HandleGuidePacket(SDL_Joystick *joysti static void HIDAPI_DriverXboxOneBluetooth_HandleBatteryPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) { - Uint8 flags = data[1]; - bool on_usb = (((flags & 0x0C) >> 2) == 0); - SDL_PowerState state; - int percent = 0; - - // Mapped percentage value from: - // https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_getbatterystate - switch (flags & 0x03) { - case 0: - percent = 10; - break; - case 1: - percent = 40; - break; - case 2: - percent = 70; - break; - case 3: - percent = 100; - break; - } - if (on_usb) { - state = SDL_POWERSTATE_CHARGING; - } else { - state = SDL_POWERSTATE_ON_BATTERY; - } - SDL_SendJoystickPowerInfo(joystick, state, percent); + HIDAPI_DriverXboxOne_HandleBatteryState(joystick, data[1]); } static void HIDAPI_DriverXboxOne_HandleSerialIDPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) @@ -1586,7 +1944,12 @@ static bool HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device) #ifdef DEBUG_XBOX_PROTOCOL HIDAPI_DumpPacket("Xbox One packet: size = %d", data, size); #endif - if (device->is_bluetooth) { + if (ctx->descriptor) { + if (!joystick) { + break; + } + HIDAPI_DriverXboxOne_HandleDescriptorReport(joystick, ctx, data, size); + } else if (device->is_bluetooth) { switch (data[0]) { case 0x01: if (!joystick) { @@ -1645,6 +2008,8 @@ static void HIDAPI_DriverXboxOne_FreeDevice(SDL_HIDAPI_Device *device) { SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; + SDL_DestroyDescriptor(ctx->descriptor); + HIDAPI_GIP_DestroyChunkBuffer(ctx); } diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_zuiki.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_zuiki.c new file mode 100644 index 00000000..d5958be0 --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_zuiki.c @@ -0,0 +1,500 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + Copyright (C) 2025 Zuiki Inc. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_ZUIKI + +#define GYRO_SCALE (1024.0f / 32768.0f * SDL_PI_F / 180.0f) // Calculate scaling factor based on gyroscope data range and radians +#define ACCEL_SCALE (8.0f / 32768.0f * SDL_STANDARD_GRAVITY) // Calculate acceleration scaling factor based on gyroscope data range and standard gravity +#define FILTER_SIZE 11 // Must be an odd number +#define MAX_RETRY_COUNT 10 // zuiki device initialization retry count + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_ZUIKI_PROTOCOL +#endif + +typedef struct { + float buffer[FILTER_SIZE]; + uint8_t index; + uint8_t count; +} MedianFilter_t; + +typedef struct +{ + Uint8 last_state[USB_PACKET_LENGTH]; + bool sensors_supported; // Sensor enabled status flag + Uint64 sensor_timestamp_ns; // Sensor timestamp (nanoseconds, cumulative update) + float sensor_rate; + MedianFilter_t filter_gyro_x; + MedianFilter_t filter_gyro_y; + MedianFilter_t filter_gyro_z; +} SDL_DriverZUIKI_Context; + +static float median_filter_update(MedianFilter_t* mf, float input) { + mf->buffer[mf->index] = input; + mf->index = (mf->index + 1) % FILTER_SIZE; + if (mf->count < FILTER_SIZE) mf->count++; + float temp[FILTER_SIZE]; + SDL_memcpy(temp, mf->buffer, sizeof(temp)); + for (uint8_t i = 0; i < mf->count - 1; i++) { + for (uint8_t j = i + 1; j < mf->count; j++) { + if (temp[i] > temp[j]) { + float t = temp[i]; + temp[i] = temp[j]; + temp[j] = t; + } + } + } + return temp[mf->count / 2]; +} + + +static void HIDAPI_DriverZUIKI_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_ZUIKI, callback, userdata); +} + +static void HIDAPI_DriverZUIKI_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_ZUIKI, callback, userdata); +} + +static bool HIDAPI_DriverZUIKI_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_ZUIKI, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverZUIKI_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (vendor_id == USB_VENDOR_ZUIKI) { + switch (product_id) { + case USB_PRODUCT_ZUIKI_MASCON_PRO: + case USB_PRODUCT_ZUIKI_EVOTOP_UWB_DINPUT: + case USB_PRODUCT_ZUIKI_EVOTOP_PC_DINPUT: + case USB_PRODUCT_ZUIKI_EVOTOP_PC_BT: + return true; + default: + break; + } + } + return false; +} + +static bool HIDAPI_DriverZUIKI_InitDevice(SDL_HIDAPI_Device *device) +{ + Uint8 data[USB_PACKET_LENGTH * 2]; + SDL_DriverZUIKI_Context *ctx = (SDL_DriverZUIKI_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + device->context = ctx; + ctx->sensors_supported = false; + + // Read report data once for device initialization + int size = -1; + Uint8 retry_count = 0; + while (retry_count < MAX_RETRY_COUNT) { + size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 10); + if (size > 0) { + break; + } + retry_count++; + } + if (size <= 0) { + return false; + } + + switch (device->product_id) { + case USB_PRODUCT_ZUIKI_MASCON_PRO: + HIDAPI_SetDeviceName(device, "ZUIKI MASCON PRO"); + break; + case USB_PRODUCT_ZUIKI_EVOTOP_PC_DINPUT: + ctx->sensors_supported = true; + ctx->sensor_rate = 200.0f; + break; + case USB_PRODUCT_ZUIKI_EVOTOP_UWB_DINPUT: + ctx->sensors_supported = true; + ctx->sensor_rate = 100.0f; + break; + case USB_PRODUCT_ZUIKI_EVOTOP_PC_BT: + if (size > 0 && data[16] != 0) { + ctx->sensors_supported = true; + ctx->sensor_rate = 50.0f; + } + HIDAPI_SetDeviceName(device, "ZUIKI EVOTOP"); + break; + default: + break; + } + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverZUIKI_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverZUIKI_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +#ifndef DEG2RAD +#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) +#endif + +static bool HIDAPI_DriverZUIKI_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverZUIKI_Context *ctx = (SDL_DriverZUIKI_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + SDL_zeroa(ctx->last_state); + + joystick->nbuttons = 11; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + if (ctx->sensors_supported) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, ctx->sensor_rate); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, ctx->sensor_rate); + } + + return true; +} + +static bool HIDAPI_DriverZUIKI_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + Uint8 rumble_packet[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + rumble_packet[4] = low_frequency_rumble >> 8; + rumble_packet[5] = high_frequency_rumble >> 8; + if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; +} + +static bool HIDAPI_DriverZUIKI_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverZUIKI_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + Uint32 caps = 0; + caps |= SDL_JOYSTICK_CAP_RUMBLE; + return caps; +} + +static bool HIDAPI_DriverZUIKI_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverZUIKI_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + if (SDL_HIDAPI_SendRumble(device, data, size) != size) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; +} + +static bool HIDAPI_DriverZUIKI_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverZUIKI_Context *ctx = (SDL_DriverZUIKI_Context *)device->context; + if (ctx->sensors_supported) { + return true; + } + return SDL_Unsupported(); +} + +static void HIDAPI_DriverZUIKI_HandleOldStatePacket(SDL_Joystick *joystick, SDL_DriverZUIKI_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[2] != data[2]) { + Uint8 hat; + + switch (data[2]) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + if (ctx->last_state[0] != data[0]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x20) != 0)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (data[0] & 0x40) ? SDL_MAX_SINT16 : SDL_MIN_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (data[0] & 0x80) ? SDL_MAX_SINT16 : SDL_MIN_SINT16); + } + + if (ctx->last_state[1] != data[1]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, ((data[1] & 0x20) != 0)); + /* todo for switch C key */ + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + axis = READ_STICK_AXIS(3); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(4); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(5); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(6); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + + if (ctx->sensors_supported) { + Uint64 sensor_timestamp = timestamp; + float gyro_values[3]; + gyro_values[0] = median_filter_update(&ctx->filter_gyro_x, LOAD16(data[8], data[9]) * GYRO_SCALE); + gyro_values[1] = median_filter_update(&ctx->filter_gyro_y, LOAD16(data[12], data[13]) * GYRO_SCALE); + gyro_values[2] = median_filter_update(&ctx->filter_gyro_z, -LOAD16(data[10], data[11]) * GYRO_SCALE); + float accel_values[3]; + accel_values[0] = LOAD16(data[14], data[15]) * ACCEL_SCALE; + accel_values[2] = -LOAD16(data[16], data[17]) * ACCEL_SCALE; + accel_values[1] = LOAD16(data[18], data[19]) * ACCEL_SCALE; +#ifdef DEBUG_ZUIKI_PROTOCOL + SDL_Log("Gyro raw: %d, %d, %d -> scaled: %.2f, %.2f, %.2f rad/s", + LOAD16(data[8], data[9]), LOAD16(data[10], data[11]), LOAD16(data[12], data[13]), + gyro_values[0], gyro_values[1], gyro_values[2]); + SDL_Log("Accel raw: %d, %d, %d -> scaled: %.2f, %.2f, %.2f m/s²", + LOAD16(data[14], data[15]), LOAD16(data[16], data[17]), LOAD16(data[18], data[19]), + accel_values[0], accel_values[1], accel_values[2]); +#endif + + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, gyro_values, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, accel_values, 3); + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static void HIDAPI_DriverZUIKI_Handle_EVOTOP_PCBT_StatePacket(SDL_Joystick *joystick, SDL_DriverZUIKI_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + axis = (Sint16)HIDAPI_RemapVal((float)(data[2] << 8 | data[1]), 0x0000, 0xffff, SDL_MIN_SINT16, SDL_MAX_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = (Sint16)HIDAPI_RemapVal((float)(data[4] << 8 | data[3]), 0x0000, 0xffff, SDL_MIN_SINT16, SDL_MAX_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = (Sint16)HIDAPI_RemapVal((float)(data[6] << 8 | data[5]), 0x0000, 0xffff, SDL_MIN_SINT16, SDL_MAX_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = (Sint16)HIDAPI_RemapVal((float)(data[8] << 8 | data[7]), 0x0000, 0xffff, SDL_MIN_SINT16, SDL_MAX_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + + axis = (Sint16)HIDAPI_RemapVal((float)(data[10] << 8 | data[9]), 0x0000, 0x03ff, SDL_MIN_SINT16, SDL_MAX_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = (Sint16)HIDAPI_RemapVal((float)(data[12] << 8 | data[11]), 0x0000, 0x03ff, SDL_MIN_SINT16, SDL_MAX_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + if (ctx->last_state[13] != data[13]) { + Uint8 hat; + switch (data[13]) { + case 1: + hat = SDL_HAT_UP; + break; + case 2: + hat = SDL_HAT_RIGHTUP; + break; + case 3: + hat = SDL_HAT_RIGHT; + break; + case 4: + hat = SDL_HAT_RIGHTDOWN; + break; + case 5: + hat = SDL_HAT_DOWN; + break; + case 6: + hat = SDL_HAT_LEFTDOWN; + break; + case 7: + hat = SDL_HAT_LEFT; + break; + case 8: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + if (ctx->last_state[14] != data[14]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x80) != 0)); + } + + if (ctx->last_state[15] != data[15]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[15] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[15] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[15] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x40) != 0)); + } + + if (ctx->sensors_supported) { + Uint64 sensor_timestamp = timestamp; + float gyro_values[3]; + gyro_values[0] = median_filter_update(&ctx->filter_gyro_x, LOAD16(data[17], data[18]) * GYRO_SCALE); + gyro_values[1] = median_filter_update(&ctx->filter_gyro_y, LOAD16(data[21], data[22]) * GYRO_SCALE); + gyro_values[2] = median_filter_update(&ctx->filter_gyro_z, -LOAD16(data[19], data[20]) * GYRO_SCALE); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, gyro_values, 3); + float accel_values[3]; + accel_values[0] = LOAD16(data[23], data[24]) * ACCEL_SCALE; + accel_values[2] = -LOAD16(data[25], data[26]) * ACCEL_SCALE; + accel_values[1] = LOAD16(data[27], data[28]) * ACCEL_SCALE; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, accel_values, 3); +#ifdef DEBUG_ZUIKI_PROTOCOL + SDL_Log("Gyro raw: %d, %d, %d -> scaled: %.2f, %.2f, %.2f rad/s", + LOAD16(data[17], data[18]), LOAD16(data[19], data[20]), LOAD16(data[21], data[22]), + gyro_values[0], gyro_values[1], gyro_values[2]); + SDL_Log("Accel raw: %d, %d, %d -> scaled: %.2f, %.2f, %.2f m/s²", + LOAD16(data[23], data[24]), LOAD16(data[25], data[26]), LOAD16(data[27], data[28]), + accel_values[0], accel_values[1], accel_values[2]); +#endif + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverZUIKI_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverZUIKI_Context *ctx = (SDL_DriverZUIKI_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_ZUIKI_PROTOCOL + HIDAPI_DumpPacket("ZUIKI packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + if (device->product_id == USB_PRODUCT_ZUIKI_EVOTOP_PC_BT) { + HIDAPI_DriverZUIKI_Handle_EVOTOP_PCBT_StatePacket(joystick, ctx, data, size); + } else if (device->product_id == USB_PRODUCT_ZUIKI_EVOTOP_PC_DINPUT + || device->product_id == USB_PRODUCT_ZUIKI_MASCON_PRO + || device->product_id == USB_PRODUCT_ZUIKI_EVOTOP_UWB_DINPUT) { + HIDAPI_DriverZUIKI_HandleOldStatePacket(joystick, ctx, data, size); + } + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverZUIKI_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +static void HIDAPI_DriverZUIKI_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverZUIKI = { + SDL_HINT_JOYSTICK_HIDAPI_ZUIKI, + true, + HIDAPI_DriverZUIKI_RegisterHints, + HIDAPI_DriverZUIKI_UnregisterHints, + HIDAPI_DriverZUIKI_IsEnabled, + HIDAPI_DriverZUIKI_IsSupportedDevice, + HIDAPI_DriverZUIKI_InitDevice, + HIDAPI_DriverZUIKI_GetDevicePlayerIndex, + HIDAPI_DriverZUIKI_SetDevicePlayerIndex, + HIDAPI_DriverZUIKI_UpdateDevice, + HIDAPI_DriverZUIKI_OpenJoystick, + HIDAPI_DriverZUIKI_RumbleJoystick, + HIDAPI_DriverZUIKI_RumbleJoystickTriggers, + HIDAPI_DriverZUIKI_GetJoystickCapabilities, + HIDAPI_DriverZUIKI_SetJoystickLED, + HIDAPI_DriverZUIKI_SendJoystickEffect, + HIDAPI_DriverZUIKI_SetJoystickSensorsEnabled, + HIDAPI_DriverZUIKI_CloseJoystick, + HIDAPI_DriverZUIKI_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_ZUIKI + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c index 5d26deaf..0fdaa9ad 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -70,11 +70,17 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = { #ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK &SDL_HIDAPI_DriverSteamDeck, #endif +#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK + &SDL_HIDAPI_DriverSteamTriton, +#endif #ifdef SDL_JOYSTICK_HIDAPI_SWITCH &SDL_HIDAPI_DriverNintendoClassic, &SDL_HIDAPI_DriverJoyCons, &SDL_HIDAPI_DriverSwitch, #endif +#ifdef SDL_JOYSTICK_HIDAPI_SWITCH2 + &SDL_HIDAPI_DriverSwitch2, +#endif #ifdef SDL_JOYSTICK_HIDAPI_WII &SDL_HIDAPI_DriverWii, #endif @@ -97,6 +103,12 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = { #ifdef SDL_JOYSTICK_HIDAPI_FLYDIGI &SDL_HIDAPI_DriverFlydigi, #endif +#ifdef SDL_JOYSTICK_HIDAPI_SINPUT + &SDL_HIDAPI_DriverSInput, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_ZUIKI + &SDL_HIDAPI_DriverZUIKI, +#endif }; static int SDL_HIDAPI_numdrivers = 0; static SDL_AtomicInt SDL_HIDAPI_updating_devices; @@ -145,7 +157,7 @@ void HIDAPI_DumpPacket(const char *prefix, const Uint8 *data, int size) current_len += SDL_snprintf(&buffer[current_len], length - current_len, " 0x%.2x", data[i]); } SDL_strlcat(buffer, "\n", length); - SDL_Log("%s", buffer); + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "%s", buffer); SDL_free(buffer); } @@ -159,6 +171,8 @@ bool HIDAPI_SupportsPlaystationDetection(Uint16 vendor, Uint16 product) } switch (vendor) { + case USB_VENDOR_CRKD: + return true; case USB_VENDOR_DRAGONRISE: return true; case USB_VENDOR_HORI: @@ -271,6 +285,8 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U 0x24c6, // PowerA 0x2c22, // Qanba 0x2dc8, // 8BitDo + 0x3537, // GameSir + 0x37d7, // Flydigi 0x9886, // ASTRO Gaming }; @@ -354,7 +370,7 @@ static SDL_HIDAPI_DeviceDriver *HIDAPI_GetDeviceDriver(SDL_HIDAPI_Device *device return NULL; } - if (device->vendor_id != USB_VENDOR_VALVE && device->vendor_id != USB_VENDOR_FLYDIGI) { + if (device->vendor_id != USB_VENDOR_VALVE && device->vendor_id != USB_VENDOR_FLYDIGI_V1 && device->vendor_id != USB_VENDOR_FLYDIGI_V2) { if (device->usage_page && device->usage_page != USAGE_PAGE_GENERIC_DESKTOP) { return NULL; } @@ -446,7 +462,9 @@ static void HIDAPI_SetupDeviceDriver(SDL_HIDAPI_Device *device, bool *removed) S if (device->driver) { bool enabled; - if (device->vendor_id == USB_VENDOR_NINTENDO && device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) { + if (device->vendor_id == USB_VENDOR_NINTENDO && + (device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR || + device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR)) { enabled = SDL_HIDAPI_combine_joycons; } else { enabled = device->driver->enabled; @@ -880,14 +898,19 @@ static SDL_HIDAPI_Device *HIDAPI_AddDevice(const struct SDL_hid_device_info *inf for (curr = SDL_HIDAPI_devices, last = NULL; curr; last = curr, curr = curr->next) { } + char *path = SDL_strdup(info->path); + if (!path) { + SDL_OutOfMemory(); + return NULL; + } + device = (SDL_HIDAPI_Device *)SDL_calloc(1, sizeof(*device)); if (!device) { + SDL_free(path); return NULL; } SDL_SetObjectValid(device, SDL_OBJECT_TYPE_HIDAPI_JOYSTICK, true); - if (info->path) { - device->path = SDL_strdup(info->path); - } + device->path = path; device->seen = true; device->vendor_id = info->vendor_id; device->product_id = info->product_id; @@ -1062,7 +1085,11 @@ static bool HIDAPI_CreateCombinedJoyCons(void) SDL_zero(info); info.path = "nintendo_joycons_combined"; info.vendor_id = USB_VENDOR_NINTENDO; - info.product_id = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; + if (joycons[0]->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT) { + info.product_id = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; + } else { + info.product_id = USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR; + } info.interface_number = -1; info.usage_page = USB_USAGEPAGE_GENERIC_DESKTOP; info.usage = USB_USAGE_GENERIC_GAMEPAD; @@ -1115,6 +1142,11 @@ static void HIDAPI_UpdateDeviceList(void) devs = SDL_hid_enumerate(0, 0); if (devs) { for (info = devs; info; info = info->next) { + if (!info->path) { + // We can't open this, ignore it + continue; + } + device = HIDAPI_GetJoystickByInfo(info->path, info->vendor_id, info->product_id); if (device) { device->seen = true; @@ -1290,6 +1322,16 @@ bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, */ SDL_LockJoysticks(); for (device = SDL_HIDAPI_devices; device; device = device->next) { + // The HIDAPI functionality will be available when the FlyDigi Space Station app has + // enabled third party controller mapping, so the driver needs to be active to watch + // for that change. Since this is dynamic and we don't have a way to re-trigger device + // changes when that happens, we'll pretend the driver isn't available so the XInput + // interface will always show up (but won't have any input when the controller is in + // enhanced mode) + if (device->vendor_id == USB_VENDOR_FLYDIGI_V2) { + continue; + } + if (device->driver && HIDAPI_IsEquivalentToDevice(vendor_id, product_id, device)) { result = true; diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h index f6b8ebfa..61294418 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -35,15 +35,21 @@ #define SDL_JOYSTICK_HIDAPI_STEAM #define SDL_JOYSTICK_HIDAPI_STEAMDECK #define SDL_JOYSTICK_HIDAPI_SWITCH +#ifdef HAVE_LIBUSB +#define SDL_JOYSTICK_HIDAPI_SWITCH2 +#endif #define SDL_JOYSTICK_HIDAPI_WII #define SDL_JOYSTICK_HIDAPI_XBOX360 #define SDL_JOYSTICK_HIDAPI_XBOXONE #define SDL_JOYSTICK_HIDAPI_SHIELD #define SDL_JOYSTICK_HIDAPI_STEAM_HORI +#define SDL_JOYSTICK_HIDAPI_STEAM_TRITON #define SDL_JOYSTICK_HIDAPI_LG4FF #define SDL_JOYSTICK_HIDAPI_8BITDO #define SDL_JOYSTICK_HIDAPI_FLYDIGI #define SDL_JOYSTICK_HIDAPI_GIP +#define SDL_JOYSTICK_HIDAPI_SINPUT +#define SDL_JOYSTICK_HIDAPI_ZUIKI // Joystick capability definitions #define SDL_JOYSTICK_CAP_MONO_LED 0x00000001 @@ -157,14 +163,24 @@ extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch2; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamTriton; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLg4ff; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_Driver8BitDo; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverFlydigi; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSInput; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverZUIKI; + +#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) +#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \ + (((Uint32)(B)) << 8) | \ + (((Uint32)(C)) << 16) | \ + (((Uint32)(D)) << 24)) // Return true if a HID device is present and supported as a joystick of the given type extern bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type); diff --git a/libs/SDL3/src/joystick/hidapi/SDL_report_descriptor.c b/libs/SDL3/src/joystick/hidapi/SDL_report_descriptor.c new file mode 100644 index 00000000..f5155832 --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_report_descriptor.c @@ -0,0 +1,616 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include "SDL_report_descriptor.h" + +// This is a very simple (and non-compliant!) report descriptor parser +// used to quickly parse Xbox Bluetooth reports + +typedef enum +{ + DescriptorItemTypeMain = 0, + DescriptorItemTypeGlobal = 1, + DescriptorItemTypeLocal = 2, + DescriptorItemTypeReserved = 3, +} ItemType; + +typedef enum +{ + MainTagInput = 0x8, + MainTagOutput = 0x9, + MainTagFeature = 0xb, + MainTagCollection = 0xa, + MainTagEndCollection = 0xc, +} MainTag; + +typedef enum +{ + MainFlagConstant = 0x0001, + MainFlagVariable = 0x0002, + MainFlagRelative = 0x0004, + MainFlagWrap = 0x0008, + MainFlagNonLinear = 0x0010, + MainFlagNoPreferred = 0x0020, + MainFlagNullState = 0x0040, + MainFlagVolatile = 0x0080, + MainFlagBufferedBytes = 0x0100, +} MainFlag; + +typedef enum +{ + GlobalTagUsagePage = 0x0, + GlobalTagLogicalMinimum = 0x1, + GlobalTagLogicalMaximum = 0x2, + GlobalTagPhysicalMinimum = 0x3, + GlobalTagPhysicalMaximum = 0x4, + GlobalTagUnitExponent = 0x5, + GlobalTagUnit = 0x6, + GlobalTagReportSize = 0x7, + GlobalTagReportID = 0x8, + GlobalTagReportCount = 0x9, + GlobalTagPush = 0xa, + GlobalTagPop = 0xb, +} GlobalTag; + +typedef enum +{ + LocalTagUsage = 0x0, + LocalTagUsageMinimum = 0x1, + LocalTagUsageMaximum = 0x2, + LocalTagDesignatorIndex = 0x3, + LocalTagDesignatorMinimum = 0x4, + LocalTagDesignatorMaximum = 0x5, + LocalTagStringIndex = 0x7, + LocalTagStringMinimum = 0x8, + LocalTagStringMaximum = 0x9, + LocalTagDelimiter = 0xa, +} LocalTag; + +typedef struct +{ + Uint32 usage_page; + Uint32 report_size; + Uint32 report_count; + Uint32 report_id; +} DescriptorGlobalState; + +typedef struct +{ + Uint32 usage_minimum; + Uint32 usage_maximum; + int usage_maxcount; + int usage_count; + Uint32 *usages; +} DescriptorLocalState; + +typedef struct +{ + int collection_depth; + DescriptorGlobalState global; + DescriptorLocalState local; + int field_maxcount; + int field_count; + int field_offset; + DescriptorInputField *fields; +} DescriptorContext; + +static void DebugDescriptor(DescriptorContext *ctx, const char *fmt, ...) +{ +#ifdef DEBUG_DESCRIPTOR + va_list ap; + va_start(ap, fmt); + char *message = NULL; + SDL_vasprintf(&message, fmt, ap); + va_end(ap); + if (ctx->collection_depth > 0) { + size_t len = 4 * ctx->collection_depth + SDL_strlen(message) + 1; + char *output = (char *)SDL_malloc(len); + if (output) { + SDL_memset(output, ' ', 4 * ctx->collection_depth); + output[4 * ctx->collection_depth] = '\0'; + SDL_strlcat(output, message, len); + SDL_free(message); + message = output; + } + } + SDL_Log("%s", message); + SDL_free(message); +#endif // DEBUG_DESCRIPTOR +} + +static void DebugMainTag(DescriptorContext *ctx, const char *tag, Uint32 flags) +{ +#ifdef DEBUG_DESCRIPTOR + char message[1024] = { 0 }; + + SDL_strlcat(message, tag, sizeof(message)); + SDL_strlcat(message, "(", sizeof(message)); + if (flags & MainFlagConstant) { + SDL_strlcat(message, " Constant", sizeof(message)); + } else { + SDL_strlcat(message, " Data", sizeof(message)); + } + if (flags & MainFlagVariable) { + SDL_strlcat(message, " Variable", sizeof(message)); + } else { + SDL_strlcat(message, " Array", sizeof(message)); + } + if (flags & MainFlagRelative) { + SDL_strlcat(message, " Relative", sizeof(message)); + } else { + SDL_strlcat(message, " Absolute", sizeof(message)); + } + if (flags & MainFlagWrap) { + SDL_strlcat(message, " Wrap", sizeof(message)); + } else { + SDL_strlcat(message, " No Wrap", sizeof(message)); + } + if (flags & MainFlagNonLinear) { + SDL_strlcat(message, " Non Linear", sizeof(message)); + } else { + SDL_strlcat(message, " Linear", sizeof(message)); + } + if (flags & MainFlagNoPreferred) { + SDL_strlcat(message, " No Preferred", sizeof(message)); + } else { + SDL_strlcat(message, " Preferred State", sizeof(message)); + } + if (flags & MainFlagNullState) { + SDL_strlcat(message, " Null State", sizeof(message)); + } else { + SDL_strlcat(message, " No Null Position", sizeof(message)); + } + if (flags & MainFlagVolatile) { + SDL_strlcat(message, " Volatile", sizeof(message)); + } else { + SDL_strlcat(message, " Non Volatile", sizeof(message)); + } + if (flags & MainFlagBufferedBytes) { + SDL_strlcat(message, " Buffered Bytes", sizeof(message)); + } else { + SDL_strlcat(message, " Bit Field", sizeof(message)); + } + SDL_strlcat(message, " )", sizeof(message)); + + DebugDescriptor(ctx, "%s", message); + +#endif // DEBUG_DESCRIPTOR +} + +static Uint32 ReadValue(const Uint8 *data, int size) +{ + Uint32 value = 0; + + int shift = 0; + while (size--) { + value |= ((Uint32)(*data++)) << shift; + shift += 8; + } + return value; +} + +static void ResetLocalState(DescriptorContext *ctx) +{ + ctx->local.usage_minimum = 0; + ctx->local.usage_maximum = 0; + ctx->local.usage_count = 0; +} + +static bool AddUsage(DescriptorContext *ctx, Uint32 usage) +{ + if (ctx->local.usage_count == ctx->local.usage_maxcount) { + int usage_maxcount = ctx->local.usage_maxcount + 4; + Uint32 *usages = (Uint32 *)SDL_realloc(ctx->local.usages, usage_maxcount * sizeof(*usages)); + if (!usages) { + return false; + } + ctx->local.usages = usages; + ctx->local.usage_maxcount = usage_maxcount; + } + + if (usage <= 0xFFFF) { + usage |= (ctx->global.usage_page << 16); + } + ctx->local.usages[ctx->local.usage_count++] = usage; + return true; +} + +static bool AddInputField(DescriptorContext *ctx, Uint32 usage, int bit_size) +{ + if (ctx->field_count == ctx->field_maxcount) { + int field_maxcount = ctx->field_maxcount + 4; + DescriptorInputField *fields = (DescriptorInputField *)SDL_realloc(ctx->fields, field_maxcount * sizeof(*fields)); + if (!fields) { + return false; + } + ctx->fields = fields; + ctx->field_maxcount = field_maxcount; + } + + DescriptorInputField *field = &ctx->fields[ctx->field_count++]; + field->report_id = (Uint8)ctx->global.report_id; + field->usage = usage; + field->bit_offset = ctx->field_offset; + field->bit_size = bit_size; + + DebugDescriptor(ctx, "Adding report %d field 0x%.8x size %d bits at bit offset %d", field->report_id, field->usage, field->bit_size, field->bit_offset); + return true; +} + +static bool AddInputFields(DescriptorContext *ctx) +{ + Uint32 usage = 0; + + if (ctx->global.report_count == 0 || ctx->global.report_size == 0) { + return true; + } + + if (ctx->local.usage_count == 0 && + ctx->local.usage_minimum > 0 && + ctx->local.usage_maximum >= ctx->local.usage_minimum) { + for (usage = ctx->local.usage_minimum; usage <= ctx->local.usage_maximum; ++usage) { + if (!AddUsage(ctx, usage)) { + return false; + } + } + } + + int usage_index = 0; + for (Uint32 i = 0; i < ctx->global.report_count; ++i) { + if (usage_index < ctx->local.usage_count) { + usage = ctx->local.usages[usage_index]; + if (usage_index < (ctx->local.usage_count - 1)) { + ++usage_index; + } + } + + int size = (int)ctx->global.report_size; + if (usage > 0) { + if (!AddInputField(ctx, usage, size)) { + return false; + } + } + ctx->field_offset += size; + } + return true; +} + +static bool ParseMainItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data) +{ + Uint32 flags; + + switch (tag) { + case MainTagInput: + flags = ReadValue(data, size); + DebugMainTag(ctx, "MainTagInput", flags); + AddInputFields(ctx); + break; + case MainTagOutput: + flags = ReadValue(data, size); + DebugMainTag(ctx, "MainTagOutput", flags); + break; + case MainTagFeature: + flags = ReadValue(data, size); + DebugMainTag(ctx, "MainTagFeature", flags); + break; + case MainTagCollection: + DebugDescriptor(ctx, "MainTagCollection"); + switch (*data) { + case 0x00: + DebugDescriptor(ctx, "Physical"); + break; + case 0x01: + DebugDescriptor(ctx, "Application"); + break; + case 0x02: + DebugDescriptor(ctx, "Logical"); + break; + case 0x03: + DebugDescriptor(ctx, "Report"); + break; + case 0x04: + DebugDescriptor(ctx, "Named Array"); + break; + case 0x05: + DebugDescriptor(ctx, "Usage Switch"); + break; + case 0x06: + DebugDescriptor(ctx, "Usage Modifier"); + break; + default: + break; + } + ++ctx->collection_depth; + break; + case MainTagEndCollection: + if (ctx->collection_depth > 0) { + --ctx->collection_depth; + } + DebugDescriptor(ctx, "MainTagEndCollection"); + break; + default: + DebugDescriptor(ctx, "Unknown main tag: %d", tag); + break; + } + + ResetLocalState(ctx); + + return true; +} + +static bool ParseGlobalItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data) +{ + Uint32 value; + + switch (tag) { + case GlobalTagUsagePage: + ctx->global.usage_page = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagUsagePage: 0x%.4x", ctx->global.usage_page); + break; + case GlobalTagLogicalMinimum: + value = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagLogicalMinimum: %u", value); + break; + case GlobalTagLogicalMaximum: + value = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagLogicalMaximum: %u", value); + break; + case GlobalTagPhysicalMinimum: + value = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagPhysicalMinimum: %u", value); + break; + case GlobalTagPhysicalMaximum: + value = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagPhysicalMaximum: %u", value); + break; + case GlobalTagUnitExponent: + DebugDescriptor(ctx, "GlobalTagUnitExponent"); + break; + case GlobalTagUnit: + DebugDescriptor(ctx, "GlobalTagUnit"); + break; + case GlobalTagReportSize: + ctx->global.report_size = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagReportSize: %u", ctx->global.report_size); + break; + case GlobalTagReportID: + ctx->global.report_id = ReadValue(data, size); + ctx->field_offset = 0; + DebugDescriptor(ctx, "GlobalTagReportID: %u", ctx->global.report_id); + break; + case GlobalTagReportCount: + ctx->global.report_count = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagReportCount: %u", ctx->global.report_count); + break; + case GlobalTagPush: + DebugDescriptor(ctx, "GlobalTagPush"); + break; + case GlobalTagPop: + DebugDescriptor(ctx, "GlobalTagPop"); + break; + default: + DebugDescriptor(ctx, "Unknown global tag"); + break; + } + return true; +} + +static bool ParseLocalItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data) +{ + Uint32 value; + + switch (tag) { + case LocalTagUsage: + value = ReadValue(data, size); + AddUsage(ctx, value); + DebugDescriptor(ctx, "LocalTagUsage: 0x%.4x", value); + break; + case LocalTagUsageMinimum: + ctx->local.usage_minimum = ReadValue(data, size); + DebugDescriptor(ctx, "LocalTagUsageMinimum: 0x%.4x", ctx->local.usage_minimum); + break; + case LocalTagUsageMaximum: + ctx->local.usage_maximum = ReadValue(data, size); + DebugDescriptor(ctx, "LocalTagUsageMaximum: 0x%.4x", ctx->local.usage_maximum); + break; + case LocalTagDesignatorIndex: + DebugDescriptor(ctx, "LocalTagDesignatorIndex"); + break; + case LocalTagDesignatorMinimum: + DebugDescriptor(ctx, "LocalTagDesignatorMinimum"); + break; + case LocalTagDesignatorMaximum: + DebugDescriptor(ctx, "LocalTagDesignatorMaximum"); + break; + case LocalTagStringIndex: + DebugDescriptor(ctx, "LocalTagStringIndex"); + break; + case LocalTagStringMinimum: + DebugDescriptor(ctx, "LocalTagStringMinimum"); + break; + case LocalTagStringMaximum: + DebugDescriptor(ctx, "LocalTagStringMaximum"); + break; + case LocalTagDelimiter: + DebugDescriptor(ctx, "LocalTagDelimiter"); + break; + default: + DebugDescriptor(ctx, "Unknown local tag"); + break; + } + return true; +} + +static bool ParseDescriptor(DescriptorContext *ctx, const Uint8 *descriptor, int descriptor_size) +{ + SDL_zerop(ctx); + + for (const Uint8 *here = descriptor; here < descriptor + descriptor_size; ) { + static const int sizes[4] = { 0, 1, 2, 4 }; + Uint8 data = *here++; + int size = sizes[(data & 0x3)]; + int type = ((data >> 2) & 0x3); + int tag = (data >> 4); + + if ((here + size) > (descriptor + descriptor_size)) { + return SDL_SetError("Invalid descriptor"); + } + +#ifdef DEBUG_DESCRIPTOR + SDL_Log("Data: 0x%.2x, size: %d, type: %d, tag: %d", data, size, type, tag); +#endif + switch (type) { + case DescriptorItemTypeMain: + if (!ParseMainItem(ctx, tag, size, here)) { + return false; + } + break; + case DescriptorItemTypeGlobal: + if (!ParseGlobalItem(ctx, tag, size, here)) { + return false; + } + break; + case DescriptorItemTypeLocal: + if (!ParseLocalItem(ctx, tag, size, here)) { + return false; + } + break; + case DescriptorItemTypeReserved: + // Long items are currently unsupported + return SDL_Unsupported(); + } + + here += size; + } + return true; +} + +static void CleanupContext(DescriptorContext *ctx) +{ + SDL_free(ctx->local.usages); + SDL_free(ctx->fields); +} + +SDL_ReportDescriptor *SDL_ParseReportDescriptor(const Uint8 *descriptor, int descriptor_size) +{ + SDL_ReportDescriptor *result = NULL; + + DescriptorContext ctx; + if (ParseDescriptor(&ctx, descriptor, descriptor_size)) { + result = (SDL_ReportDescriptor *)SDL_malloc(sizeof(*result)); + if (result) { + result->field_count = ctx.field_count; + result->fields = ctx.fields; + ctx.fields = NULL; + } + } + CleanupContext(&ctx); + + return result; +} + +bool SDL_DescriptorHasUsage(SDL_ReportDescriptor *descriptor, Uint16 usage_page, Uint16 usage) +{ + if (!descriptor) { + return false; + } + + Uint32 full_usage = (((Uint32)usage_page << 16) | usage); + for (int i = 0; i < descriptor->field_count; ++i) { + if (descriptor->fields[i].usage == full_usage) { + return true; + } + } + return false; +} + +void SDL_DestroyDescriptor(SDL_ReportDescriptor *descriptor) +{ + if (descriptor) { + SDL_free(descriptor->fields); + SDL_free(descriptor); + } +} + +bool SDL_ReadReportData(const Uint8 *data, int size, int bit_offset, int bit_size, Uint32 *value) +{ + int offset = (bit_offset / 8); + if (offset >= size) { + *value = 0; + return SDL_SetError("Out of bounds reading report data"); + } + + *value = ReadValue(data + offset, (bit_size + 7) / 8); + + int shift = (bit_offset % 8); + if (shift > 0) { + *value >>= shift; + } + + switch (bit_size) { + case 1: + *value &= 0x1; + break; + case 4: + *value &= 0xf; + break; + case 10: + *value &= 0x3ff; + break; + case 15: + *value &= 0x7fff; + break; + default: + SDL_assert((bit_size % 8) == 0); + break; + } + return true; +} + +#ifdef TEST_MAIN + +#include + +int main(int argc, char *argv[]) +{ + const char *file = argv[1]; + if (argc < 2) { + SDL_Log("Usage: %s file", argv[0]); + return 1; + } + + size_t descriptor_size = 0; + Uint8 *descriptor = SDL_LoadFile(argv[1], &descriptor_size); + if (!descriptor) { + SDL_Log("Couldn't load %s: %s", argv[1], SDL_GetError()); + return 2; + } + + DescriptorContext ctx; + if (!ParseDescriptor(&ctx, descriptor, descriptor_size)) { + SDL_Log("Couldn't parse %s: %s", argv[1], SDL_GetError()); + return 3; + } + return 0; +} + +#endif // TEST_MAIN diff --git a/libs/SDL3/src/joystick/hidapi/SDL_report_descriptor.h b/libs/SDL3/src/joystick/hidapi/SDL_report_descriptor.h new file mode 100644 index 00000000..3e21f8ba --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_report_descriptor.h @@ -0,0 +1,40 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +typedef struct +{ + Uint8 report_id; + Uint32 usage; + int bit_offset; + int bit_size; +} DescriptorInputField; + +typedef struct +{ + int field_count; + DescriptorInputField *fields; +} SDL_ReportDescriptor; + +extern SDL_ReportDescriptor *SDL_ParseReportDescriptor(const Uint8 *descriptor, int descriptor_size); +extern bool SDL_DescriptorHasUsage(SDL_ReportDescriptor *descriptor, Uint16 usage_page, Uint16 usage); +extern void SDL_DestroyDescriptor(SDL_ReportDescriptor *descriptor); +extern bool SDL_ReadReportData(const Uint8 *data, int size, int bit_offset, int bit_size, Uint32 *value); diff --git a/libs/SDL3/src/joystick/hidapi/steam/controller_constants.h b/libs/SDL3/src/joystick/hidapi/steam/controller_constants.h index 78af0164..e838a892 100644 --- a/libs/SDL3/src/joystick/hidapi/steam/controller_constants.h +++ b/libs/SDL3/src/joystick/hidapi/steam/controller_constants.h @@ -353,7 +353,7 @@ enum GamepadButtons // Mode adjust enum ModeAdjustModes { - MODE_ADJUST_SENSITITY=1, + MODE_ADJUST_SENSITIVITY=1, MODE_ADJUST_LEFT_PAD_SECONDARY_MODE, MODE_ADJUST_RIGHT_PAD_SECONDARY_MODE, MODE_ADJUST_COUNT @@ -364,8 +364,8 @@ typedef enum { ATTRIB_UNIQUE_ID, ATTRIB_PRODUCT_ID, - ATTRIB_PRODUCT_REVISON, // deprecated - ATTRIB_CAPABILITIES = ATTRIB_PRODUCT_REVISON, // intentional aliasing + ATTRIB_PRODUCT_REVISION, // deprecated + ATTRIB_CAPABILITIES = ATTRIB_PRODUCT_REVISION, // intentional aliasing ATTRIB_FIRMWARE_VERSION, // deprecated ATTRIB_FIRMWARE_BUILD_TIME, ATTRIB_RADIO_FIRMWARE_BUILD_TIME, @@ -411,6 +411,12 @@ typedef enum { TRACKPAD_NUM_MODES } TrackpadDPadMode; +typedef enum +{ + LIZARD_MODE_OFF, + LIZARD_MODE_ON, +} LizardModeState_t; + // Read-write controller settings (only add to this enum and never change the order) typedef enum { @@ -423,7 +429,7 @@ typedef enum SETTING_USB_DEBUG_MODE, SETTING_LEFT_TRACKPAD_MODE, SETTING_RIGHT_TRACKPAD_MODE, - SETTING_MOUSE_POINTER_ENABLED, + SETTING_LIZARD_MODE, // 10 SETTING_DPAD_DEADZONE, @@ -579,4 +585,4 @@ typedef enum } #endif -#endif // _CONTROLLER_CONSTANTS_H +#endif // _CONTROLLER_CONSTANTS_ diff --git a/libs/SDL3/src/joystick/hidapi/steam/controller_structs.h b/libs/SDL3/src/joystick/hidapi/steam/controller_structs.h index ea2a3522..69c10ed6 100644 --- a/libs/SDL3/src/joystick/hidapi/steam/controller_structs.h +++ b/libs/SDL3/src/joystick/hidapi/steam/controller_structs.h @@ -112,7 +112,7 @@ typedef struct { int16_t dur_ms; // Duration of tone / rumble (if applicable) (neg = infinite) uint16_t noise_intensity; - uint16_t lfo_freq; // Drives both tone and rumble geneators + uint16_t lfo_freq; // Drives both tone and rumble generators uint8_t lfo_depth; // percentage, typically 100 uint8_t rand_tone_gain; // Randomize each LFO cycle's gain uint8_t script_id; // Used w/ dBgain for scripted haptics @@ -154,6 +154,99 @@ typedef struct } FeatureReportMsg; +// Triton and derivatives utilize output reports for haptic commands. This is a +// snapshot from Nov 2024 -- things may change. + +// Triton Output Report Lengths #defs include +1 for the OR ID + +// Output Report Haptic Messages for Triton +typedef struct +{ + uint8_t type; + uint16_t intensity; + struct + { + uint16_t speed; + int8_t gain; + } left, right; +} MsgHapticRumble; +#define HID_RUMBLE_OUTPUT_REPORT_BYTES 10 + + +typedef struct +{ + uint8_t side; + uint16_t on_us; + uint16_t off_us; + uint16_t repeat_count; +} MsgHapticPulse; +#define HID_HAPTIC_PULSE_OUTPUT_REPORT_BYTES 8 + +typedef struct +{ + uint8_t side; + uint8_t command; + int8_t gain_db; +} MsgHapticCommand; +#define HID_HAPTIC_COMMAND_REPORT_BYTES 4 + +typedef struct +{ + uint8_t side; + int8_t gain_db; + uint16_t frequency; + uint16_t duration_ms; + uint16_t lfo_freq; + uint8_t lfo_depth; +} MsgHapticLfoTone; +#define HID_HAPTIC_LFO_TONE_REPORT_BYTES 10 + +typedef struct +{ + uint8_t side; + int8_t gain_db; + uint16_t duration_ms; + struct + { + uint16_t frequency; + } start, end; +} MsgHapticLogSweep; +#define HID_HAPTIC_LOG_SWEEP_REPORT_BYTES 9 + +typedef struct m +{ + uint8_t side; + uint8_t script_id; + int8_t gain_db; +} MsgHapticScript; +#define HID_HAPTIC_SCRIPT_REPORT_BYTES 4 + +typedef enum +{ + ID_OUT_REPORT_HAPTIC_RUMBLE = 0x80, + ID_OUT_REPORT_HAPTIC_PULSE = 0x81, + ID_OUT_REPORT_HAPTIC_COMMAND = 0x82, + ID_OUT_REPORT_HAPTIC_LFO_TONE = 0x83, + ID_OUT_REPORT_HAPTIC_LOG_SWEEP = 0x84, + ID_OUT_REPORT_HAPTIC_SCRIPT = 0x85, +} ValveTritonOutReportMessageIDs; + +typedef struct +{ + uint8_t report_id; + union + { + MsgHapticRumble hapticRumble; + MsgHapticPulse hapticPulse; + MsgHapticCommand hapticCommand; + MsgHapticLfoTone hapticLfoTone; + MsgHapticLogSweep hapticLogSweep; + MsgHapticScript hapticScript; + } payload; + +} OutputReportMsg; + + // Roll this version forward anytime that you are breaking compatibility of existing // message types within ValveInReport_t or the header itself. Hopefully this should // be super rare and instead you should just add new message payloads to the union, @@ -261,7 +354,7 @@ typedef struct short sRightPadX; short sRightPadY; - //This mimcs how the dongle reconstitutes HID packets, there will be 0-4 shorts depending on gyro mode + //This mimics how the dongle reconstitutes HID packets, there will be 0-4 shorts depending on gyro mode unsigned char ucGyroDataType; //TODO could maybe find some unused bits in the button field for this info (is only 2bits) short sGyro[4]; @@ -413,51 +506,201 @@ typedef struct unsigned short sPressurePadRight; } SteamDeckStatePacket_t; + typedef struct { - ValveInReportHeader_t header; - - union - { - ValveControllerStatePacket_t controllerState; - ValveControllerBLEStatePacket_t controllerBLEState; - ValveControllerDebugPacket_t debugState; - ValveControllerTrackpadImage_t padImage; - ValveControllerRawTrackpadImage_t rawPadImage; - SteamControllerWirelessEvent_t wirelessEvent; - SteamControllerStatusEvent_t statusEvent; - SteamDeckStatePacket_t deckState; - } payload; - + ValveInReportHeader_t header; + + union + { + ValveControllerStatePacket_t controllerState; + ValveControllerBLEStatePacket_t controllerBLEState; + ValveControllerDebugPacket_t debugState; + ValveControllerTrackpadImage_t padImage; + ValveControllerRawTrackpadImage_t rawPadImage; + SteamControllerWirelessEvent_t wirelessEvent; + SteamControllerStatusEvent_t statusEvent; + SteamDeckStatePacket_t deckState; + } payload; + } ValveInReport_t; - -// Enumeration for BLE packet protocol enum EBLEPacketReportNums { - // Skipping past 2-3 because they are escape characters in Uart protocol - k_EBLEReportState = 4, - k_EBLEReportStatus = 5, + k_EBLEReportState = 4, + k_EBLEReportStatus = 5, }; - - // Enumeration of data chunks in BLE state packets enum EBLEOptionDataChunksBitmask { - // First byte upper nibble - k_EBLEButtonChunk1 = 0x10, - k_EBLEButtonChunk2 = 0x20, - k_EBLEButtonChunk3 = 0x40, - k_EBLELeftJoystickChunk = 0x80, + // First byte upper nibble + k_EBLEButtonChunk1 = 0x10, + k_EBLEButtonChunk2 = 0x20, + k_EBLEButtonChunk3 = 0x40, + k_EBLELeftJoystickChunk = 0x80, - // Second full byte - k_EBLELeftTrackpadChunk = 0x100, - k_EBLERightTrackpadChunk = 0x200, - k_EBLEIMUAccelChunk = 0x400, - k_EBLEIMUGyroChunk = 0x800, - k_EBLEIMUQuatChunk = 0x1000, + // Second full byte + k_EBLELeftTrackpadChunk = 0x100, + k_EBLERightTrackpadChunk = 0x200, + k_EBLEIMUAccelChunk = 0x400, + k_EBLEIMUGyroChunk = 0x800, + k_EBLEIMUQuatChunk = 0x1000, }; +// Triton and derivatives do not use the ValveInReport_t structure + +enum ETritonReportIDTypes +{ + ID_TRITON_CONTROLLER_STATE = 0x42, + ID_TRITON_BATTERY_STATUS = 0x43, + ID_TRITON_CONTROLLER_STATE_BLE = 0x45, + ID_TRITON_WIRELESS_STATUS_X = 0x46, + ID_TRITON_CONTROLLER_STATE_TIMESTAMP = 0x47, + + ID_TRITON_WIRELESS_STATUS = 0x79, +}; + +enum ETritonWirelessState +{ + k_ETritonWirelessStateDisconnect = 1, + k_ETritonWirelessStateConnect = 2, +}; + +typedef struct +{ + uint32_t timestamp; + short sAccelX; + short sAccelY; + short sAccelZ; + + short sGyroX; + short sGyroY; + short sGyroZ; + + short sGyroQuatW; + short sGyroQuatX; + short sGyroQuatY; + short sGyroQuatZ; +} TritonMTUIMU_t; + +typedef struct { + uint32_t timestamp; + short sAccelX; + short sAccelY; + short sAccelZ; + + short sGyroX; + short sGyroY; + short sGyroZ; +} TritonMTUIMUNoQuat_t; + +typedef struct +{ + uint16_t timestamp; + short sAccelX; + short sAccelY; + short sAccelZ; + + short sGyroX; + short sGyroY; + short sGyroZ; +} TritonMTUIMUNoQuat32usTS_t; + +typedef struct +{ + uint8_t seq_num; + uint32_t buttons; + short sTriggerLeft; + short sTriggerRight; + + short sLeftStickX; + short sLeftStickY; + short sRightStickX; + short sRightStickY; + + short sLeftPadX; + short sLeftPadY; + unsigned short unPressureLeft; + + short sRightPadX; + short sRightPadY; + unsigned short unPressureRight; + TritonMTUIMU_t imu; +} TritonMTUFull_t; + +typedef struct { + uint8_t seq_num; + uint32_t buttons; + short sTriggerLeft; + short sTriggerRight; + + short sLeftStickX; + short sLeftStickY; + short sRightStickX; + short sRightStickY; + + short sLeftPadX; + short sLeftPadY; + unsigned short unPressureLeft; + + short sRightPadX; + short sRightPadY; + unsigned short unPressureRight; + TritonMTUIMUNoQuat_t imu; +} TritonMTUNoQuat_t; + +// New Ibex packet that adds a timestamp to the trackpad sampling +// and reduces the size of the IMU timestamp. Timestamps are now 16 bits +typedef struct +{ + uint8_t seq_num; + uint32_t buttons; + short sTriggerLeft; + short sTriggerRight; + + short sLeftStickX; + short sLeftStickY; + short sRightStickX; + short sRightStickY; + + unsigned short unTrackpadTimestamp; + short sLeftPadX; + short sLeftPadY; + unsigned short unPressureLeft; + + short sRightPadX; + short sRightPadY; + unsigned short unPressureRight; + + TritonMTUIMUNoQuat32usTS_t imu; +} TritonMTUNoQuat32TS_t; + +enum EChargeState +{ + k_EChargeStateReset, + k_EChargeStateDischarging, + k_EChargeStateCharging, + k_EChargeStateSrcValidate, + k_EChargeStateChargingDone, +}; + +typedef struct +{ + unsigned char ucChargeState; // EChargeState + unsigned char ucBatteryLevel; + unsigned short sBatteryVoltage; + unsigned short sSystemVoltage; + unsigned short sInputVoltage; + unsigned short sCurrent; + unsigned short sInputCurrent; + unsigned short sTemperature; +} TritonBatteryStatus_t; + +typedef struct +{ + unsigned char state; +} TritonWirelessStatus_t; + #pragma pack() -#endif // _CONTROLLER_STRUCTS +#endif // _CONTROLLER_STRUCTS_ diff --git a/libs/SDL3/src/joystick/linux/SDL_sysjoystick.c b/libs/SDL3/src/joystick/linux/SDL_sysjoystick.c index ea73821c..ede0d5ef 100644 --- a/libs/SDL3/src/joystick/linux/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/linux/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -75,6 +75,18 @@ #ifndef BTN_DPAD_RIGHT #define BTN_DPAD_RIGHT 0x223 #endif +#ifndef BTN_GRIPL +#define BTN_GRIPL 0x224 +#endif +#ifndef BTN_GRIPR +#define BTN_GRIPR 0x225 +#endif +#ifndef BTN_GRIPL2 +#define BTN_GRIPL2 0x226 +#endif +#ifndef BTN_GRIPR2 +#define BTN_GRIPR2 0x227 +#endif #ifndef BTN_TRIGGER_HAPPY #define BTN_TRIGGER_HAPPY 0x2c0 @@ -150,7 +162,10 @@ typedef struct SDL_joylist_item { SDL_JoystickID device_instance; char *path; // "/dev/input/event2" or whatever + Uint16 vendor; + Uint16 product; char *name; // "SideWinder 3D Pro" or whatever + char *driver; // "xpad" or whatever SDL_GUID guid; dev_t devnum; int steam_virtual_gamepad_slot; @@ -274,19 +289,20 @@ static bool GuessIsSensor(int fd) return false; } -static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_GUID *guid) +static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_GUID *guid, char **driver_return) { struct input_id inpid; - char *name; + char *name = NULL; + char *driver = NULL; char product_string[128]; int class = 0; SDL_zero(inpid); #ifdef SDL_USE_LIBUDEV // Opening input devices can generate synchronous device I/O, so avoid it if we can - if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) && + if (SDL_UDEV_GetProductInfo(path, &inpid, &class, &driver) && !(class & SDL_UDEV_DEVICE_JOYSTICK)) { - return false; + goto error; } #endif @@ -294,34 +310,33 @@ static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *ve *fd = open(path, O_RDONLY | O_CLOEXEC, 0); } if (!fd || *fd < 0) { - return false; + goto error; } if (ioctl(*fd, JSIOCGNAME(sizeof(product_string)), product_string) <= 0) { // When udev enumeration or classification, we only got joysticks here, so no need to test if (enumeration_method != ENUMERATION_LIBUDEV && !class && !GuessIsJoystick(*fd)) { - return false; + goto error; } // Could have vendor and product already from udev, but should agree with evdev if (ioctl(*fd, EVIOCGID, &inpid) < 0) { - return false; + goto error; } if (ioctl(*fd, EVIOCGNAME(sizeof(product_string)), product_string) < 0) { - return false; + goto error; } } name = SDL_CreateJoystickName(inpid.vendor, inpid.product, NULL, product_string); if (!name) { - return false; + goto error; } if (!IsVirtualJoystick(inpid.vendor, inpid.product, inpid.version, name) && SDL_JoystickHandledByAnotherDriver(&SDL_LINUX_JoystickDriver, inpid.vendor, inpid.product, inpid.version, name)) { - SDL_free(name); - return false; + goto error; } FixupDeviceInfoForMapping(*fd, &inpid); @@ -331,14 +346,19 @@ static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *ve #endif if (SDL_ShouldIgnoreJoystick(inpid.vendor, inpid.product, inpid.version, name)) { - SDL_free(name); - return false; + goto error; } *name_return = name; + *driver_return = driver; *vendor_return = inpid.vendor; *product_return = inpid.product; *guid = SDL_CreateJoystickGUID(inpid.bustype, inpid.vendor, inpid.product, inpid.version, NULL, product_string, 0, 0); return true; + +error: + SDL_free(driver); + SDL_free(name); + return false; } static bool IsSensor(const char *path, int *fd) @@ -349,7 +369,7 @@ static bool IsSensor(const char *path, int *fd) SDL_zero(inpid); #ifdef SDL_USE_LIBUDEV // Opening input devices can generate synchronous device I/O, so avoid it if we can - if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) && + if (SDL_UDEV_GetProductInfo(path, &inpid, &class, NULL) && !(class & SDL_UDEV_DEVICE_ACCELEROMETER)) { return false; } @@ -422,6 +442,7 @@ static void FreeJoylistItem(SDL_joylist_item *item) SDL_free(item->mapping); SDL_free(item->path); SDL_free(item->name); + SDL_free(item->driver); SDL_free(item); } @@ -436,6 +457,7 @@ static void MaybeAddDevice(const char *path) struct stat sb; int fd = -1; char *name = NULL; + char *driver = NULL; Uint16 vendor, product; SDL_GUID guid; SDL_joylist_item *item; @@ -473,7 +495,7 @@ static void MaybeAddDevice(const char *path) SDL_Log("Checking %s", path); #endif - if (IsJoystick(path, &fd, &name, &vendor, &product, &guid)) { + if (IsJoystick(path, &fd, &name, &vendor, &product, &guid, &driver)) { #ifdef DEBUG_INPUT_EVENTS SDL_Log("found joystick: %s", path); #endif @@ -486,8 +508,11 @@ static void MaybeAddDevice(const char *path) item->devnum = sb.st_rdev; item->steam_virtual_gamepad_slot = -1; item->path = SDL_strdup(path); + item->vendor = vendor; + item->product = product; item->name = name; item->guid = guid; + item->driver = driver; if (vendor == USB_VENDOR_VALVE && product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { @@ -861,7 +886,7 @@ static void LINUX_ScanSteamVirtualGamepads(void) // Opening input devices can generate synchronous device I/O, so avoid it if we can class = 0; SDL_zero(inpid); - if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) && + if (SDL_UDEV_GetProductInfo(path, &inpid, &class, NULL) && (inpid.vendor != USB_VENDOR_VALVE || inpid.product != USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD)) { free(entries[i]); // This should NOT be SDL_free() continue; @@ -948,7 +973,7 @@ static void LINUX_JoystickDetect(void) { #ifdef SDL_USE_LIBUDEV if (enumeration_method == ENUMERATION_LIBUDEV) { - SDL_UDEV_Poll(); + // Polling will happen in the main event loop } else #endif #ifdef HAVE_INOTIFY @@ -1136,6 +1161,8 @@ static SDL_JoystickID LINUX_JoystickGetDeviceInstanceID(int device_index) static bool allocate_balldata(SDL_Joystick *joystick) { + SDL_AssertJoysticksLocked(); + joystick->hwdata->balls = (struct hwdata_ball *)SDL_calloc(joystick->nballs, sizeof(struct hwdata_ball)); if (joystick->hwdata->balls == NULL) { @@ -1512,7 +1539,7 @@ static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item) return NULL; } - SDL_memset(uniq_item, 0, sizeof(uniq_item)); + SDL_zeroa(uniq_item); fd_item = open(item->path, O_RDONLY | O_CLOEXEC, 0); if (fd_item < 0) { return NULL; @@ -1534,7 +1561,7 @@ static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item) continue; } - SDL_memset(uniq_sensor, 0, sizeof(uniq_sensor)); + SDL_zeroa(uniq_sensor); fd_sensor = open(item_sensor->path, O_RDONLY | O_CLOEXEC, 0); if (fd_sensor < 0) { continue; @@ -1587,6 +1614,10 @@ static bool LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index) item_sensor->hwdata = joystick->hwdata; } +#ifdef SDL_USE_LIBUDEV + joystick->serial = SDL_UDEV_GetProductSerial(item->path); +#endif + // mark joystick as fresh and ready joystick->hwdata->fresh = true; @@ -1747,6 +1778,8 @@ static void HandleHat(Uint64 timestamp, SDL_Joystick *stick, int hatidx, int axi static void HandleBall(SDL_Joystick *stick, Uint8 ball, int axis, int value) { + SDL_AssertJoysticksLocked(); + stick->hwdata->balls[ball].axis[axis] += value; } @@ -1844,6 +1877,20 @@ static void PollAllValues(Uint64 timestamp, SDL_Joystick *joystick) // Joyballs are relative input, so there's no poll state. Events only! } +static void CorrectSensorData(struct joystick_hwdata *hwdata, float *values, float *data) +{ + if (hwdata->item->vendor == USB_VENDOR_NINTENDO) { + // The Nintendo driver uses a different axis order than SDL + data[0] = -values[1]; + data[1] = values[2]; + data[2] = -values[0]; + } else { + data[0] = values[0]; + data[1] = values[1]; + data[2] = values[2]; + } +} + static void PollAllSensors(Uint64 timestamp, SDL_Joystick *joystick) { struct input_absinfo absinfo; @@ -1854,27 +1901,31 @@ static void PollAllSensors(Uint64 timestamp, SDL_Joystick *joystick) SDL_assert(joystick->hwdata->fd_sensor >= 0); if (joystick->hwdata->has_gyro) { - float data[3] = {0.0f, 0.0f, 0.0f}; + float values[3] = {0.0f, 0.0f, 0.0f}; for (i = 0; i < 3; i++) { if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_RX + i), &absinfo) >= 0) { - data[i] = absinfo.value * (SDL_PI_F / 180.f) / joystick->hwdata->gyro_scale[i]; + values[i] = absinfo.value * (SDL_PI_F / 180.f) / joystick->hwdata->gyro_scale[i]; #ifdef DEBUG_INPUT_EVENTS SDL_Log("Joystick : Re-read Gyro (axis %d) val= %f", i, data[i]); #endif } } + float data[3]; + CorrectSensorData(joystick->hwdata, values, data); SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, SDL_US_TO_NS(joystick->hwdata->sensor_tick), data, 3); } if (joystick->hwdata->has_accelerometer) { - float data[3] = {0.0f, 0.0f, 0.0f}; + float values[3] = {0.0f, 0.0f, 0.0f}; for (i = 0; i < 3; i++) { if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_X + i), &absinfo) >= 0) { - data[i] = absinfo.value * SDL_STANDARD_GRAVITY / joystick->hwdata->accelerometer_scale[i]; + values[i] = absinfo.value * SDL_STANDARD_GRAVITY / joystick->hwdata->accelerometer_scale[i]; #ifdef DEBUG_INPUT_EVENTS SDL_Log("Joystick : Re-read Accelerometer (axis %d) val= %f", i, data[i]); #endif } } + float data[3]; + CorrectSensorData(joystick->hwdata, values, data); SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, SDL_US_TO_NS(joystick->hwdata->sensor_tick), data, 3); } } @@ -2035,7 +2086,7 @@ static void HandleInputEvents(SDL_Joystick *joystick) if (code == MSC_TIMESTAMP) { Sint32 tick = event->value; Sint32 delta; - if (joystick->hwdata->last_tick < tick) { + if (joystick->hwdata->last_tick <= tick) { delta = (tick - joystick->hwdata->last_tick); } else { delta = (SDL_MAX_SINT32 - joystick->hwdata->last_tick + tick + 1); @@ -2058,12 +2109,15 @@ static void HandleInputEvents(SDL_Joystick *joystick) PollAllSensors(SDL_GetTicksNS(), joystick); // try to sync up to current state now } else { Uint64 timestamp = SDL_EVDEV_GetEventTimestamp(event); + float data[3]; + CorrectSensorData(joystick->hwdata, joystick->hwdata->gyro_data, data); SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, SDL_US_TO_NS(joystick->hwdata->sensor_tick), - joystick->hwdata->gyro_data, 3); + data, 3); + CorrectSensorData(joystick->hwdata, joystick->hwdata->accel_data, data); SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, SDL_US_TO_NS(joystick->hwdata->sensor_tick), - joystick->hwdata->accel_data, 3); + data, 3); } break; default: @@ -2244,6 +2298,12 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping MAPPED_DPAD_LEFT = 0x4, MAPPED_DPAD_RIGHT = 0x8, MAPPED_DPAD_ALL = 0xF, + + MAPPED_LEFT_PADDLE1 = 0x1, + MAPPED_RIGHT_PADDLE1 = 0x2, + MAPPED_LEFT_PADDLE2 = 0x4, + MAPPED_RIGHT_PADDLE2 = 0x8, + MAPPED_PADDLE_ALL = 0xF, }; unsigned int mapped; bool result = false; @@ -2609,6 +2669,27 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping SDL_Log("Mapped DPUP+DOWN to axis %d (ABS_HAT0Y)", out->dpup.target); SDL_Log("Mapped DPLEFT+RIGHT to axis %d (ABS_HAT0X)", out->dpleft.target); #endif + } else if (item->driver && SDL_strcmp(item->driver, "xpad") == 0) { + // xpad will sometimes map the D-Pad as BTN_TRIGGER_HAPPY1 - BTN_TRIGGER_HAPPY4 + if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY1] && + joystick->hwdata->has_key[BTN_TRIGGER_HAPPY2] && + joystick->hwdata->has_key[BTN_TRIGGER_HAPPY3] && + joystick->hwdata->has_key[BTN_TRIGGER_HAPPY4]) { + out->dpleft.kind = EMappingKind_Button; + out->dpright.kind = EMappingKind_Button; + out->dpup.kind = EMappingKind_Button; + out->dpdown.kind = EMappingKind_Button; + out->dpleft.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY1]; + out->dpright.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY2]; + out->dpup.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY3]; + out->dpdown.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY4]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped DPLEFT to button %d (BTN_TRIGGER_HAPPY1)", out->dpleft.target); + SDL_Log("Mapped DPRIGHT to button %d (BTN_TRIGGER_HAPPY2)", out->dpright.target); + SDL_Log("Mapped DPUP to button %d (BTN_TRIGGER_HAPPY3)", out->dpup.target); + SDL_Log("Mapped DPDOWN to button %d (BTN_TRIGGER_HAPPY4)", out->dpdown.target); +#endif + } } } @@ -2653,8 +2734,44 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping #endif } - if (SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) { + mapped = 0; + + if (joystick->hwdata->has_key[BTN_GRIPR]) { + out->right_paddle1.kind = EMappingKind_Button; + out->right_paddle1.target = joystick->hwdata->key_map[BTN_GRIPR]; + mapped |= MAPPED_RIGHT_PADDLE1; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHT_PADDLE1 to button %d (BTN_GRIPR)", out->right_paddle1.target); +#endif + } + if (joystick->hwdata->has_key[BTN_GRIPL]) { + out->left_paddle1.kind = EMappingKind_Button; + out->left_paddle1.target = joystick->hwdata->key_map[BTN_GRIPL]; + mapped |= MAPPED_LEFT_PADDLE1; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFT_PADDLE1 to button %d (BTN_GRIPL)", out->left_paddle1.target); +#endif + } + if (joystick->hwdata->has_key[BTN_GRIPR2]) { + out->right_paddle2.kind = EMappingKind_Button; + out->right_paddle2.target = joystick->hwdata->key_map[BTN_GRIPR2]; + mapped |= MAPPED_RIGHT_PADDLE2; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHT_PADDLE2 to button %d (BTN_GRIPR)", out->right_paddle2.target); +#endif + } + if (joystick->hwdata->has_key[BTN_GRIPL2]) { + out->left_paddle2.kind = EMappingKind_Button; + out->left_paddle2.target = joystick->hwdata->key_map[BTN_GRIPL2]; + mapped |= MAPPED_LEFT_PADDLE2; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_GRIPL2)", out->left_paddle2.target); +#endif + } + + if (mapped != MAPPED_PADDLE_ALL && SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) { // The Xbox Elite controllers have the paddles as BTN_TRIGGER_HAPPY5 - BTN_TRIGGER_HAPPY8 + // in older drivers if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY5] && joystick->hwdata->has_key[BTN_TRIGGER_HAPPY6] && joystick->hwdata->has_key[BTN_TRIGGER_HAPPY7] && @@ -2667,6 +2784,7 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping out->right_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY6]; out->left_paddle2.kind = EMappingKind_Button; out->left_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY8]; + mapped = MAPPED_PADDLE_ALL; #ifdef DEBUG_GAMEPAD_MAPPING SDL_Log("Mapped RIGHT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY5)", out->right_paddle1.target); SDL_Log("Mapped LEFT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY7)", out->left_paddle1.target); @@ -2674,15 +2792,15 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY8)", out->left_paddle2.target); #endif } + } - // The Xbox Series X controllers have the Share button as KEY_RECORD - if (joystick->hwdata->has_key[KEY_RECORD]) { - out->misc1.kind = EMappingKind_Button; - out->misc1.target = joystick->hwdata->key_map[KEY_RECORD]; + // Xbox Series controllers have the Share button as KEY_RECORD + if (joystick->hwdata->has_key[KEY_RECORD]) { + out->misc1.kind = EMappingKind_Button; + out->misc1.target = joystick->hwdata->key_map[KEY_RECORD]; #ifdef DEBUG_GAMEPAD_MAPPING - SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target); + SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target); #endif - } } // Cache the mapping for later diff --git a/libs/SDL3/src/joystick/linux/SDL_sysjoystick_c.h b/libs/SDL3/src/joystick/linux/SDL_sysjoystick_c.h index ae5384f1..d149948c 100644 --- a/libs/SDL3/src/joystick/linux/SDL_sysjoystick_c.h +++ b/libs/SDL3/src/joystick/linux/SDL_sysjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/n3ds/SDL_sysjoystick.c b/libs/SDL3/src/joystick/n3ds/SDL_sysjoystick.c index 8396ac5f..fda19dd2 100644 --- a/libs/SDL3/src/joystick/n3ds/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/n3ds/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/ps2/SDL_sysjoystick.c b/libs/SDL3/src/joystick/ps2/SDL_sysjoystick.c index b938b1f3..36a4dc09 100644 --- a/libs/SDL3/src/joystick/ps2/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/ps2/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -191,6 +191,66 @@ static SDL_JoystickID PS2_JoystickGetDeviceInstanceID(int device_index) return device_index + 1; } +static void PS2_WaitPadReady(int port, int slot) +{ + int state = padGetState(port, slot); + while ((state != PAD_STATE_STABLE) && (state != PAD_STATE_FINDCTP1)) { + SDL_Delay(1); + state = padGetState(port, slot); + } +} + +static void PS2_InitializePad(int port, int slot) +{ + int modes; + int i; + char actAlign[6]; + + PS2_WaitPadReady(port, slot); + + // How many different modes can this device operate in? + modes = padInfoMode(port, slot, PAD_MODETABLE, -1); + + // Verify that the controller has a DUAL SHOCK mode + for (i = 0; i < modes; i++) { + if (padInfoMode(port, slot, PAD_MODETABLE, i) == PAD_TYPE_DUALSHOCK) { + break; + } + } + if (i >= modes) { + // This is no Dual Shock controller + return; + } + + // If ExId != 0x0 => This controller has actuator engines + // This check should always pass if the Dual Shock test above passed + if (!padInfoMode(port, slot, PAD_MODECUREXID, 0)) { + // This is no Dual Shock controller?? + return; + } + + // When using MMODE_LOCK, user cant change mode with Select button + padSetMainMode(port, slot, PAD_MMODE_DUALSHOCK, PAD_MMODE_LOCK); + + PS2_WaitPadReady(port, slot); + padEnterPressMode(port, slot); + + PS2_WaitPadReady(port, slot); + if (padInfoAct(port, slot, -1, 0)) { + actAlign[0] = 0; // Enable small engine + actAlign[1] = 1; // Enable big engine + actAlign[2] = 0xff; + actAlign[3] = 0xff; + actAlign[4] = 0xff; + actAlign[5] = 0xff; + + PS2_WaitPadReady(port, slot); + padSetActAlign(port, slot, actAlign); + } + + PS2_WaitPadReady(port, slot); +} + /* Function to open a joystick for use. The joystick to open is specified by the device index. This should fill the nbuttons and naxes fields of the joystick structure. @@ -198,8 +258,7 @@ static SDL_JoystickID PS2_JoystickGetDeviceInstanceID(int device_index) */ static bool PS2_JoystickOpen(SDL_Joystick *joystick, int device_index) { - int index = joystick->instance_id; - struct JoyInfo *info = &joyInfo[index]; + struct JoyInfo *info = &joyInfo[device_index]; if (!info->opened) { if (padPortOpen(info->port, info->slot, (void *)info->padBuf) > 0) { @@ -208,6 +267,8 @@ static bool PS2_JoystickOpen(SDL_Joystick *joystick, int device_index) return false; } } + PS2_InitializePad(info->port, info->slot); + joystick->nbuttons = PS2_BUTTONS; joystick->naxes = PS2_TOTAL_AXIS; joystick->nhats = 0; @@ -222,7 +283,7 @@ static bool PS2_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumb { char actAlign[6]; int res; - int index = joystick->instance_id; + int index = (int)(joystick->instance_id - 1); struct JoyInfo *info = &joyInfo[index]; if (!rumble_status(index)) { @@ -277,7 +338,7 @@ static void PS2_JoystickUpdate(SDL_Joystick *joystick) uint16_t mask, previous, current; struct padButtonStatus buttons; uint8_t all_axis[PS2_TOTAL_AXIS]; - int index = joystick->instance_id; + int index = (int)(joystick->instance_id - 1); struct JoyInfo *info = &joyInfo[index]; int state = padGetState(info->port, info->slot); Uint64 timestamp = SDL_GetTicksNS(); @@ -322,7 +383,7 @@ static void PS2_JoystickUpdate(SDL_Joystick *joystick) // Function to close a joystick after use static void PS2_JoystickClose(SDL_Joystick *joystick) { - int index = joystick->instance_id; + int index = (int)(joystick->instance_id - 1); struct JoyInfo *info = &joyInfo[index]; padPortClose(info->port, info->slot); info->opened = 0; diff --git a/libs/SDL3/src/joystick/psp/SDL_sysjoystick.c b/libs/SDL3/src/joystick/psp/SDL_sysjoystick.c index 8a0154c0..feeebe60 100644 --- a/libs/SDL3/src/joystick/psp/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/psp/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/usb_ids.h b/libs/SDL3/src/joystick/usb_ids.h index 33a8a6cb..5e48181c 100644 --- a/libs/SDL3/src/joystick/usb_ids.h +++ b/libs/SDL3/src/joystick/usb_ids.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -30,10 +30,13 @@ #define USB_VENDOR_ASTRO 0x9886 #define USB_VENDOR_ASUS 0x0b05 #define USB_VENDOR_BACKBONE 0x358a +#define USB_VENDOR_CRKD 0x3651 #define USB_VENDOR_GAMESIR 0x3537 #define USB_VENDOR_DRAGONRISE 0x0079 -#define USB_VENDOR_FLYDIGI 0x04b4 +#define USB_VENDOR_FLYDIGI_V1 0x04b4 +#define USB_VENDOR_FLYDIGI_V2 0x37d7 #define USB_VENDOR_GOOGLE 0x18d1 +#define USB_VENDOR_HARMONIX 0x1bad #define USB_VENDOR_HORI 0x0f0d #define USB_VENDOR_HP 0x03f0 #define USB_VENDOR_HYPERKIN 0x2e24 @@ -50,7 +53,9 @@ #define USB_VENDOR_POWERA_ALT 0x20d6 #define USB_VENDOR_QANBA 0x2c22 #define USB_VENDOR_RAZER 0x1532 +#define USB_VENDOR_RED_OCTANE 0x1430 #define USB_VENDOR_SAITEK 0x06a3 +#define USB_VENDOR_SCEA 0x12ba #define USB_VENDOR_SHANWAN 0x2563 #define USB_VENDOR_SHANWAN_ALT 0x20bc #define USB_VENDOR_SONY 0x054c @@ -59,18 +64,22 @@ #define USB_VENDOR_SWITCH 0x2563 #define USB_VENDOR_VALVE 0x28de #define USB_VENDOR_ZEROPLUS 0x0c12 +#define USB_VENDOR_RASPBERRYPI 0x2e8a // Commercial hardware from various companies are registered under this VID +#define USB_VENDOR_ZUIKI 0x33dd -#define USB_PRODUCT_8BITDO_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_8BITDO_PRO_2 0x6003 // mode switch to D +#define USB_PRODUCT_8BITDO_PRO_2_BT 0x6006 // mode switch to D +#define USB_PRODUCT_8BITDO_PRO_3 0x6009 // mode switch to D +#define USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS 0x6012 // mode switch to BT #define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419 #define USB_PRODUCT_ASTRO_C40_XBOX360 0x0024 #define USB_PRODUCT_BACKBONE_ONE_IOS 0x0103 #define USB_PRODUCT_BACKBONE_ONE_IOS_PS5 0x0104 +#define USB_PRODUCT_BACKBONE_ONE_PS5_V2 0x0304 #define USB_PRODUCT_BDA_XB1_CLASSIC 0x581a #define USB_PRODUCT_BDA_XB1_FIGHTPAD 0x791a #define USB_PRODUCT_BDA_XB1_SPECTRA_PRO 0x592a @@ -78,7 +87,14 @@ #define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 0x1843 #define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 0x1844 #define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3 0x1846 -#define USB_PRODUCT_FLYDIGI_GAMEPAD 0x2412 +#define USB_PRODUCT_FLYDIGI_V1_GAMEPAD 0x2412 +#define USB_PRODUCT_FLYDIGI_V2_APEX 0x2501 +#define USB_PRODUCT_FLYDIGI_V2_VADER 0x2401 +#define USB_PRODUCT_HARMONIX_WII_RB1_GUITAR 0x0004 +#define USB_PRODUCT_HARMONIX_WII_RB1_DRUMS 0x0005 +#define USB_PRODUCT_HARMONIX_WII_RB2_GUITAR 0x3010 +#define USB_PRODUCT_HARMONIX_WII_RB2_DRUMS 0x3110 +#define USB_PRODUCT_HARMONIX_WII_RB3_MPA_DRUMS_MODE 0x3138 #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 @@ -87,6 +103,9 @@ #define USB_PRODUCT_LOGITECH_F310 0xc216 #define USB_PRODUCT_LOGITECH_CHILLSTREAM 0xcad1 #define USB_PRODUCT_MADCATZ_SAITEK_SIDE_PANEL_CONTROL_DECK 0x2218 +#define USB_PRODUCT_MADCATZ_XB1_STRATOCASTER_GUITAR 0x4161 +#define USB_PRODUCT_MADCATZ_XB1_DRUM_KIT 0x4262 +#define USB_PRODUCT_MADCATZ_XB1_LEGACY_ADAPTER 0x4164 #define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRELESS 0x0d16 #define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRED 0x0d17 #define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS 0x0d18 @@ -101,17 +120,28 @@ #define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR 0x2008 // Used by joycond #define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT 0x2007 #define USB_PRODUCT_NINTENDO_SWITCH_PRO 0x2009 +#define USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER 0x2073 +#define USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT 0x2067 +#define USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR 0x2068 +#define USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT 0x2066 +#define USB_PRODUCT_NINTENDO_SWITCH2_PRO 0x2069 #define USB_PRODUCT_NINTENDO_WII_REMOTE 0x0306 #define USB_PRODUCT_NINTENDO_WII_REMOTE2 0x0330 #define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103 0x7210 #define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104 0x7214 #define USB_PRODUCT_PDP_ROCK_CANDY 0x0246 +#define USB_PRODUCT_PDP_REALMZ_WIRELESS 0x018c +#define USB_PRODUCT_PDP_XB1_DRUM_KIT 0x0171 +#define USB_PRODUCT_PDP_XB1_JAGUAR_GUITAR 0x0170 +#define USB_PRODUCT_PDP_XB1_RIFFMASTER_GUITAR 0x0248 #define USB_PRODUCT_POWERA_MINI 0x541a #define USB_PRODUCT_RAZER_ATROX 0x0a00 #define USB_PRODUCT_RAZER_KITSUNE 0x1012 #define USB_PRODUCT_RAZER_PANTHERA 0x0401 #define USB_PRODUCT_RAZER_PANTHERA_EVO 0x1008 #define USB_PRODUCT_RAZER_RAIJU 0x1000 +#define USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRED 0x1024 +#define USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRELESS 0x1026 #define USB_PRODUCT_RAZER_TOURNAMENT_EDITION_USB 0x1007 #define USB_PRODUCT_RAZER_TOURNAMENT_EDITION_BLUETOOTH 0x100a #define USB_PRODUCT_RAZER_ULTIMATE_EDITION_USB 0x1004 @@ -123,7 +153,14 @@ #define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_XBOX_WIRED 0x1010 #define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_XBOX_WIRELESS 0x1011 #define USB_PRODUCT_RAZER_WOLVERINE_V3_PRO 0x0a3f +#define USB_PRODUCT_RED_OCTANE_XB1_GUITAR_HERO_LIVE_GUITAR 0x0170 #define USB_PRODUCT_SAITEK_CYBORG_V3 0xf622 +#define USB_PRODUCT_SCEA_PS3_GH_GUITAR 0x0100 +#define USB_PRODUCT_SCEA_PS3_GH_DRUMS 0x0120 +#define USB_PRODUCT_SCEA_PS3_RB_GUITAR 0x0200 +#define USB_PRODUCT_SCEA_PS3_RB_DRUMS 0x0210 +#define USB_PRODUCT_SCEA_PS3_RB3_MPA_DRUMS_MODE 0x0218 +#define USB_PRODUCT_SCEA_PS3WIIU_GHLIVE 0x074b #define USB_PRODUCT_SHANWAN_DS3 0x0523 #define USB_PRODUCT_SONY_DS3 0x0268 #define USB_PRODUCT_SONY_DS4 0x05c4 @@ -137,6 +174,8 @@ #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_VALVE_STEAM_PROTEUS_DONGLE 0x1304 +#define USB_PRODUCT_VALVE_STEAM_NEREID_DONGLE 0x1305 #define USB_PRODUCT_VICTRIX_FS_PRO 0x0203 #define USB_PRODUCT_VICTRIX_FS_PRO_V2 0x0207 #define USB_PRODUCT_XBOX360_XUSB_CONTROLLER 0x02a1 // XUSB driver software PID @@ -159,10 +198,22 @@ #define USB_PRODUCT_XBOX_SERIES_X_BLE 0x0b13 #define USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER 0x02ff // XBOXGIP driver software PID #define USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD 0x11ff +#define USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC 0x10c6 +#define USB_PRODUCT_HANDHELDLEGEND_PROGCC 0x10df +#define USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE 0x10dd +#define USB_PRODUCT_BONZIRICHANNEL_FIREBIRD 0x10e0 +#define USB_PRODUCT_ZUIKI_MASCON_PRO 0x0006 +#define USB_PRODUCT_ZUIKI_EVOTOP_UWB_DINPUT 0X001c +#define USB_PRODUCT_ZUIKI_EVOTOP_PC_DINPUT 0X001d +#define USB_PRODUCT_ZUIKI_EVOTOP_PC_BT 0X0017 +#define USB_PRODUCT_VOIDGAMING_PS4FIREBIRD 0x10e5 // USB usage pages #define USB_USAGEPAGE_GENERIC_DESKTOP 0x0001 +#define USB_USAGEPAGE_SIMULATION 0x0002 +#define USB_USAGEPAGE_DEVICE_CONTROLS 0x0006 #define USB_USAGEPAGE_BUTTON 0x0009 +#define USB_USAGEPAGE_CONSUMER 0x000C #define USB_USAGEPAGE_VENDOR_FLYDIGI 0xFFA0 // USB usages for USAGE_PAGE_GENERIC_DESKTOP @@ -184,6 +235,22 @@ #define USB_USAGE_GENERIC_WHEEL 0x0038 #define USB_USAGE_GENERIC_HAT 0x0039 +// USB usages for USB_USAGEPAGE_SIMULATION +#define USB_USAGE_SIMULATION_ACCELERATOR 0x00C4 +#define USB_USAGE_SIMULATION_BRAKE 0x00C5 + +// USB usages for USB_USAGEPAGE_DEVICE_CONTROLS +#define USB_USAGE_DEVICE_CONTROLS_BATTERY_STRENGTH 0x0020 + +// USB usages for USB_USAGEPAGE_CONSUMER +#define USB_USAGE_CONSUMER_ASSIGN_SELECTION 0x0081 +#define USB_USAGE_CONSUMER_ORDER_MOVIE 0x0085 +#define USB_USAGE_CONSUMER_RECORD 0x00B2 +#define USB_USAGE_CONSUMER_AC_HOME 0x0223 +#define USB_USAGE_CONSUMER_AC_BACK 0x0224 + +#define MAKE_USAGE(PAGE, USAGE) (((Uint32)PAGE) << 16 | USAGE) + /* Bluetooth SIG assigned Company Identifiers https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers/ */ #define BLUETOOTH_VENDOR_AMAZON 0x0171 diff --git a/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick.c b/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick.c index 69256627..2655dbd8 100644 --- a/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick.c +++ b/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -138,11 +138,11 @@ SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *des SDL_AssertJoysticksLocked(); - if (!desc) { + CHECK_PARAM(!desc) { SDL_InvalidParamError("desc"); return 0; } - if (desc->version < sizeof(*desc)) { + CHECK_PARAM(desc->version < sizeof(*desc)) { // Update this to handle older versions of this interface SDL_SetError("Invalid desc, should be initialized with SDL_INIT_INTERFACE()"); return 0; @@ -471,7 +471,7 @@ bool SDL_SendJoystickVirtualSensorDataInner(SDL_Joystick *joystick, SDL_SensorTy return false; } hwdata->sensor_events = sensor_events; - hwdata->max_sensor_events = hwdata->max_sensor_events; + hwdata->max_sensor_events = new_max_sensor_events; } VirtualSensorEvent *event = &hwdata->sensor_events[hwdata->num_sensor_events++]; diff --git a/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick_c.h b/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick_c.h index 14fe59eb..f5ea107b 100644 --- a/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick_c.h +++ b/libs/SDL3/src/joystick/virtual/SDL_virtualjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/vita/SDL_sysjoystick.c b/libs/SDL3/src/joystick/vita/SDL_sysjoystick.c index 6a694113..59f553fb 100644 --- a/libs/SDL3/src/joystick/vita/SDL_sysjoystick.c +++ b/libs/SDL3/src/joystick/vita/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c b/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c index a9638823..62ffbb46 100644 --- a/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c +++ b/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -40,15 +40,12 @@ #define CONVERT_MAGNITUDE(x) (((x)*10000) / 0x7FFF) // external variables referenced. -#ifdef SDL_VIDEO_DRIVER_WINDOWS extern HWND SDL_HelperWindow; -#else -static const HWND SDL_HelperWindow = NULL; -#endif // local variables static bool coinitialized = false; static LPDIRECTINPUT8 dinput = NULL; +static bool has_broken_EZFRD64DLL = false; // Taken from Wine - Thanks! static DIOBJECTDATAFORMAT dfDIJoystick2[] = { @@ -441,6 +438,29 @@ bool SDL_DINPUT_JoystickInit(void) dinput = NULL; return SetDIerror("IDirectInput::Initialize", result); } + +#ifdef _WIN64 + if (SDL_GetHintBoolean("SDL_JOYSTICK_CHECK_EZFRD64", true)) { + // The 64-bit version of EZFRD64.DLL crashes after being loaded, + // which happens implicitly when querying the device capabilities, + // so make sure we don't do that if there's a possibility of crashing + static const char *directories[] = { + "C:/Windows/USB_Vibration", + "C:/Windows/USB Vibration" + }; + for (int i = 0; i < SDL_arraysize(directories) && !has_broken_EZFRD64DLL; ++i) { + int count = 0; + char **files = SDL_GlobDirectory(directories[i], "*/EZFRD64.DLL", SDL_GLOB_CASEINSENSITIVE, &count); + if (count > 0) { + has_broken_EZFRD64DLL = true; + } + SDL_free(files); + } + if (has_broken_EZFRD64DLL) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Broken EZFRD64.DLL detected, disabling DirectInput force feedback"); + } + } +#endif return true; } @@ -782,12 +802,14 @@ bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joysti return SetDIerror("IDirectInputDevice8::SetDataFormat", result); } - // Get device capabilities - result = - IDirectInputDevice8_GetCapabilities(joystick->hwdata->InputDevice, - &joystick->hwdata->Capabilities); - if (FAILED(result)) { - return SetDIerror("IDirectInputDevice8::GetCapabilities", result); + if (!has_broken_EZFRD64DLL) { + // Get device capabilities to see if we are force feedback capable + result = + IDirectInputDevice8_GetCapabilities(joystick->hwdata->InputDevice, + &joystick->hwdata->Capabilities); + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::GetCapabilities", result); + } } // Force capable? diff --git a/libs/SDL3/src/joystick/windows/SDL_dinputjoystick_c.h b/libs/SDL3/src/joystick/windows/SDL_dinputjoystick_c.h index 0643ce10..6f89f72b 100644 --- a/libs/SDL3/src/joystick/windows/SDL_dinputjoystick_c.h +++ b/libs/SDL3/src/joystick/windows/SDL_dinputjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c b/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c index 8590d9a8..6846288a 100644 --- a/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c +++ b/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 2025 Sam Lantinga + Copyright (C) 2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -56,7 +56,7 @@ #ifdef SDL_JOYSTICK_RAWINPUT_WGI #include "../../core/windows/SDL_windows.h" -typedef struct WindowsGamingInputGamepadState WindowsGamingInputGamepadState; +struct WindowsGamingInputGamepadState; #define GamepadButtons_GUIDE 0x40000000 #define COBJMACROS #include "windows.gaming.input.h" @@ -157,7 +157,7 @@ struct joystick_hwdata Uint8 wgi_correlation_id; Uint8 wgi_correlation_count; Uint8 wgi_uncorrelate_count; - WindowsGamingInputGamepadState *wgi_slot; + struct WindowsGamingInputGamepadState *wgi_slot; struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; #endif @@ -923,12 +923,8 @@ static void RAWINPUT_AddDevice(HANDLE hDevice) device->name = SDL_CreateJoystickName(device->vendor_id, device->product_id, manufacturer_string, product_string); device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, device->vendor_id, device->product_id, device->version, manufacturer_string, product_string, 'r', 0); - if (manufacturer_string) { - SDL_free(manufacturer_string); - } - if (product_string) { - SDL_free(product_string); - } + SDL_free(manufacturer_string); + SDL_free(product_string); } device->path = SDL_strdup(dev_name); @@ -963,12 +959,8 @@ err: CloseHandle(hFile); } if (device) { - if (device->name) { - SDL_free(device->name); - } - if (device->path) { - SDL_free(device->path); - } + SDL_free(device->name); + SDL_free(device->path); SDL_free(device); } #undef CHECK @@ -1984,20 +1976,24 @@ static void RAWINPUT_UpdateOtherAPIs(SDL_Joystick *joystick) state = SDL_POWERSTATE_ON_BATTERY; break; } - switch (battery_info->BatteryLevel) { - case BATTERY_LEVEL_EMPTY: - percent = 10; - break; - case BATTERY_LEVEL_LOW: - percent = 40; - break; - case BATTERY_LEVEL_MEDIUM: - percent = 70; - break; - default: - case BATTERY_LEVEL_FULL: - percent = 100; - break; + if (state == SDL_POWERSTATE_ON_BATTERY || state == SDL_POWERSTATE_CHARGING) { + switch (battery_info->BatteryLevel) { + case BATTERY_LEVEL_EMPTY: + percent = 10; + break; + case BATTERY_LEVEL_LOW: + percent = 40; + break; + case BATTERY_LEVEL_MEDIUM: + percent = 70; + break; + default: + case BATTERY_LEVEL_FULL: + percent = 100; + break; + } + } else { + percent = -1; } SDL_SendJoystickPowerInfo(joystick, state, percent); } diff --git a/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick_c.h b/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick_c.h index b67544bd..dc6e5ff8 100644 --- a/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick_c.h +++ b/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -25,8 +25,8 @@ extern bool RAWINPUT_IsEnabled(void); // Registers for input events -extern int RAWINPUT_RegisterNotifications(HWND hWnd); -extern int RAWINPUT_UnregisterNotifications(void); +extern bool RAWINPUT_RegisterNotifications(HWND hWnd); +extern bool RAWINPUT_UnregisterNotifications(void); // Returns 0 if message was handled extern LRESULT CALLBACK RAWINPUT_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); diff --git a/libs/SDL3/src/joystick/windows/SDL_windows_gaming_input.c b/libs/SDL3/src/joystick/windows/SDL_windows_gaming_input.c index dbc5658e..f9076740 100644 --- a/libs/SDL3/src/joystick/windows/SDL_windows_gaming_input.c +++ b/libs/SDL3/src/joystick/windows/SDL_windows_gaming_input.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -61,7 +61,7 @@ typedef struct WindowsGamingInputControllerState int steam_virtual_gamepad_slot; } WindowsGamingInputControllerState; -typedef HRESULT(WINAPI *CoIncrementMTAUsage_t)(CO_MTA_USAGE_COOKIE *pCookie); +typedef HRESULT(WINAPI *CoIncrementMTAUsage_t)(HANDLE* pCookie); // CO_MTA_USAGE_COOKIE* typedef HRESULT(WINAPI *RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void **factory); typedef HRESULT(WINAPI *WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER *hstringHeader, HSTRING *string); typedef HRESULT(WINAPI *WindowsDeleteString_t)(HSTRING string); @@ -69,6 +69,7 @@ typedef PCWSTR(WINAPI *WindowsGetStringRawBuffer_t)(HSTRING string, UINT32 *leng static struct { + bool ro_initialized; CoIncrementMTAUsage_t CoIncrementMTAUsage; RoGetActivationFactory_t RoGetActivationFactory; WindowsCreateStringReference_t WindowsCreateStringReference; @@ -585,13 +586,14 @@ static bool WGI_JoystickInit(void) { HRESULT hr; - if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, true)) { + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, false)) { return true; } if (FAILED(WIN_RoInitialize())) { return SDL_SetError("RoInitialize() failed"); } + wgi.ro_initialized = true; #define RESOLVE(x) wgi.x = (x##_t)WIN_LoadComBaseFunction(#x); if (!wgi.x) return WIN_SetError("GetProcAddress failed for " #x); RESOLVE(CoIncrementMTAUsage); @@ -607,7 +609,7 @@ static bool WGI_JoystickInit(void) * As a workaround, we will keep a reference to the MTA to prevent COM from unloading DLLs later. * See https://github.com/libsdl-org/SDL/issues/5552 for more details. */ - static CO_MTA_USAGE_COOKIE cookie = NULL; + static HANDLE cookie = NULL; // CO_MTA_USAGE_COOKIE if (!cookie) { hr = wgi.CoIncrementMTAUsage(&cookie); if (FAILED(hr)) { @@ -971,9 +973,7 @@ static void WGI_JoystickQuit(void) while (wgi.controller_count > 0) { IEventHandler_CRawGameControllerVtbl_InvokeRemoved(&controller_removed.iface, NULL, wgi.controllers[wgi.controller_count - 1].controller); } - if (wgi.controllers) { - SDL_free(wgi.controllers); - } + SDL_free(wgi.controllers); if (wgi.arcade_stick_statics) { __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics_Release(wgi.arcade_stick_statics); @@ -1002,7 +1002,9 @@ static void WGI_JoystickQuit(void) __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_Release(wgi.controller_statics); } - WIN_RoUninitialize(); + if (wgi.ro_initialized) { + WIN_RoUninitialize(); + } SDL_zero(wgi); } diff --git a/libs/SDL3/src/joystick/windows/SDL_windowsjoystick.c b/libs/SDL3/src/joystick/windows/SDL_windowsjoystick.c index e7fbfcb3..a7ed293a 100644 --- a/libs/SDL3/src/joystick/windows/SDL_windowsjoystick.c +++ b/libs/SDL3/src/joystick/windows/SDL_windowsjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -416,11 +416,15 @@ void WINDOWS_JoystickDetect(void) while (pCurList) { JoyStick_DeviceData *pListNext = NULL; - if (!pCurList->bXInputDevice) { #ifdef SDL_HAPTIC_DINPUT +#ifdef SDL_JOYSTICK_XINPUT + if (!pCurList->bXInputDevice) { SDL_DINPUT_HapticMaybeRemoveDevice(&pCurList->dxdevice); -#endif } +#else + SDL_DINPUT_HapticMaybeRemoveDevice(&pCurList->dxdevice); +#endif +#endif SDL_PrivateJoystickRemoved(pCurList->nInstanceID); @@ -432,11 +436,15 @@ void WINDOWS_JoystickDetect(void) for (pCurList = SYS_Joystick; pCurList; pCurList = pCurList->pNext) { if (pCurList->send_add_event) { - if (!pCurList->bXInputDevice) { #ifdef SDL_HAPTIC_DINPUT +#ifdef SDL_JOYSTICK_XINPUT + if (!pCurList->bXInputDevice) { SDL_DINPUT_HapticMaybeAddDevice(&pCurList->dxdevice); -#endif } +#else + SDL_DINPUT_HapticMaybeAddDevice(&pCurList->dxdevice); +#endif +#endif SDL_PrivateJoystickAdded(pCurList->nInstanceID); @@ -489,16 +497,18 @@ static int WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) device = device->pNext; } +#ifdef SDL_JOYSTICK_XINPUT if (device->bXInputDevice) { // The slot for XInput devices can change as controllers are seated return SDL_XINPUT_GetSteamVirtualGamepadSlot(device->XInputUserId); - } else { - return device->steam_virtual_gamepad_slot; } +#endif + return device->steam_virtual_gamepad_slot; } static int WINDOWS_JoystickGetDevicePlayerIndex(int device_index) { +#ifdef SDL_JOYSTICK_XINPUT JoyStick_DeviceData *device = SYS_Joystick; int index; @@ -507,6 +517,9 @@ static int WINDOWS_JoystickGetDevicePlayerIndex(int device_index) } return device->bXInputDevice ? (int)device->XInputUserId : -1; +#else + return -1; +#endif } static void WINDOWS_JoystickSetDevicePlayerIndex(int device_index, int player_index) @@ -560,20 +573,22 @@ static bool WINDOWS_JoystickOpen(SDL_Joystick *joystick, int device_index) } joystick->hwdata->guid = device->guid; +#ifdef SDL_JOYSTICK_XINPUT if (device->bXInputDevice) { return SDL_XINPUT_JoystickOpen(joystick, device); - } else { - return SDL_DINPUT_JoystickOpen(joystick, device); } +#endif + return SDL_DINPUT_JoystickOpen(joystick, device); } static bool WINDOWS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) { +#ifdef SDL_JOYSTICK_XINPUT if (joystick->hwdata->bXInputDevice) { return SDL_XINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble); - } else { - return SDL_DINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble); } +#endif + return SDL_DINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble); } static bool WINDOWS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) @@ -602,21 +617,27 @@ static void WINDOWS_JoystickUpdate(SDL_Joystick *joystick) return; } +#ifdef SDL_JOYSTICK_XINPUT if (joystick->hwdata->bXInputDevice) { SDL_XINPUT_JoystickUpdate(joystick); - } else { - SDL_DINPUT_JoystickUpdate(joystick); + return; } +#endif + SDL_DINPUT_JoystickUpdate(joystick); } // Function to close a joystick after use static void WINDOWS_JoystickClose(SDL_Joystick *joystick) { +#ifdef SDL_JOYSTICK_XINPUT if (joystick->hwdata->bXInputDevice) { SDL_XINPUT_JoystickClose(joystick); } else { SDL_DINPUT_JoystickClose(joystick); } +#else + SDL_DINPUT_JoystickClose(joystick); +#endif SDL_free(joystick->hwdata); } diff --git a/libs/SDL3/src/joystick/windows/SDL_windowsjoystick_c.h b/libs/SDL3/src/joystick/windows/SDL_windowsjoystick_c.h index 16b91848..d0ce9fda 100644 --- a/libs/SDL3/src/joystick/windows/SDL_windowsjoystick_c.h +++ b/libs/SDL3/src/joystick/windows/SDL_windowsjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -37,9 +37,11 @@ typedef struct JoyStick_DeviceData char *joystickname; Uint8 send_add_event; SDL_JoystickID nInstanceID; +#ifdef SDL_JOYSTICK_XINPUT bool bXInputDevice; BYTE SubType; Uint8 XInputUserId; +#endif DIDEVICEINSTANCE dxdevice; char path[MAX_PATH]; int steam_virtual_gamepad_slot; @@ -85,10 +87,12 @@ struct joystick_hwdata LPDIRECTINPUTEFFECT ffeffect_ref; #endif +#ifdef SDL_JOYSTICK_XINPUT bool bXInputDevice; // true if this device supports using the xinput API rather than DirectInput bool bXInputHaptic; // Supports force feedback via XInput. Uint8 userid; // XInput userid index for this joystick DWORD dwPacketNumber; +#endif }; #ifdef SDL_JOYSTICK_DINPUT diff --git a/libs/SDL3/src/joystick/windows/SDL_xinputjoystick.c b/libs/SDL3/src/joystick/windows/SDL_xinputjoystick.c index 9f6ce103..d7a132e1 100644 --- a/libs/SDL3/src/joystick/windows/SDL_xinputjoystick.c +++ b/libs/SDL3/src/joystick/windows/SDL_xinputjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -300,20 +300,24 @@ static void UpdateXInputJoystickBatteryInformation(SDL_Joystick *joystick, XINPU state = SDL_POWERSTATE_ON_BATTERY; break; } - switch (pBatteryInformation->BatteryLevel) { - case BATTERY_LEVEL_EMPTY: - percent = 10; - break; - case BATTERY_LEVEL_LOW: - percent = 40; - break; - case BATTERY_LEVEL_MEDIUM: - percent = 70; - break; - default: - case BATTERY_LEVEL_FULL: - percent = 100; - break; + if (state == SDL_POWERSTATE_ON_BATTERY || state == SDL_POWERSTATE_CHARGING) { + switch (pBatteryInformation->BatteryLevel) { + case BATTERY_LEVEL_EMPTY: + percent = 10; + break; + case BATTERY_LEVEL_LOW: + percent = 40; + break; + case BATTERY_LEVEL_MEDIUM: + percent = 70; + break; + default: + case BATTERY_LEVEL_FULL: + percent = 100; + break; + } + } else { + percent = -1; } SDL_SendJoystickPowerInfo(joystick, state, percent); } @@ -391,6 +395,7 @@ void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick) return; } + // FIXME: This does end up making a device ioctl() to query data, we shouldn't do this every update. SDL_zero(XBatteryInformation); if (XINPUTGETBATTERYINFORMATION) { result = XINPUTGETBATTERYINFORMATION(joystick->hwdata->userid, BATTERY_DEVTYPE_GAMEPAD, &XBatteryInformation); diff --git a/libs/SDL3/src/joystick/windows/SDL_xinputjoystick_c.h b/libs/SDL3/src/joystick/windows/SDL_xinputjoystick_c.h index 305b0909..9ac12245 100644 --- a/libs/SDL3/src/joystick/windows/SDL_xinputjoystick_c.h +++ b/libs/SDL3/src/joystick/windows/SDL_xinputjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/libm/e_exp.c b/libs/SDL3/src/libm/e_exp.c index f39bb5c5..413e3609 100644 --- a/libs/SDL3/src/libm/e_exp.c +++ b/libs/SDL3/src/libm/e_exp.c @@ -27,7 +27,7 @@ * the interval [0,0.34658]: * Write * R(r**2) = r*(exp(r)+1)/(exp(r)-1) = 2 + r*r/6 - r**4/360 + ... - * We use a special Reme algorithm on [0,0.34658] to generate + * We use a special Remez algorithm on [0,0.34658] to generate * a polynomial of degree 5 to approximate R. The maximum error * of this polynomial approximation is bounded by 2**-59. In * other words, @@ -76,10 +76,6 @@ #include "math_libm.h" #include "math_private.h" -#ifdef __WATCOMC__ /* Watcom defines huge=__huge */ -#undef huge -#endif - static const double one = 1.0, halF[2] = {0.5,-0.5,}, diff --git a/libs/SDL3/src/libm/e_log.c b/libs/SDL3/src/libm/e_log.c index f935fa70..e6eaf3d5 100644 --- a/libs/SDL3/src/libm/e_log.c +++ b/libs/SDL3/src/libm/e_log.c @@ -16,7 +16,7 @@ #endif /* __ieee754_log(x) - * Return the logrithm of x + * Return the logarithm of x * * Method : * 1. Argument Reduction: find k and f such that @@ -27,7 +27,7 @@ * Let s = f/(2+f) ; based on log(1+f) = log(1+s) - log(1-s) * = 2s + 2/3 s**3 + 2/5 s**5 + ....., * = 2s + s*R - * We use a special Reme algorithm on [0,0.1716] to generate + * We use a special Remez algorithm on [0,0.1716] to generate * a polynomial of degree 14 to approximate R The maximum error * of this polynomial approximation is bounded by 2**-58.45. In * other words, diff --git a/libs/SDL3/src/libm/e_pow.c b/libs/SDL3/src/libm/e_pow.c index d1a141ec..dc393e1f 100644 --- a/libs/SDL3/src/libm/e_pow.c +++ b/libs/SDL3/src/libm/e_pow.c @@ -64,10 +64,6 @@ #pragma warning ( disable : 4756 ) #endif -#ifdef __WATCOMC__ /* Watcom defines huge=__huge */ -#undef huge -#endif - static const double bp[] = {1.0, 1.5,}, dp_h[] = { 0.0, 5.84962487220764160156e-01,}, /* 0x3FE2B803, 0x40000000 */ diff --git a/libs/SDL3/src/libm/e_sqrt.c b/libs/SDL3/src/libm/e_sqrt.c index 8ac58c62..f0add579 100644 --- a/libs/SDL3/src/libm/e_sqrt.c +++ b/libs/SDL3/src/libm/e_sqrt.c @@ -331,7 +331,7 @@ B. sqrt(x) by Reciproot Iteration Let x0 and x1 be the leading and the trailing 32-bit words of a floating point number x (in IEEE double format) respectively - (see section A). By performing shifs and subtracts on x0 and y0, + (see section A). By performing shifts and subtracts on x0 and y0, we obtain a 7.8-bit approximation of 1/sqrt(x) as follows. k := 0x5fe80000 - (x0>>1); diff --git a/libs/SDL3/src/libm/k_rem_pio2.c b/libs/SDL3/src/libm/k_rem_pio2.c index 3dd5b2bf..29dd9f37 100644 --- a/libs/SDL3/src/libm/k_rem_pio2.c +++ b/libs/SDL3/src/libm/k_rem_pio2.c @@ -48,7 +48,7 @@ * 64-bit precision 2 * 113-bit precision 3 * The actual value is the sum of them. Thus for 113-bit - * precison, one may have to do something like: + * precision, one may have to do something like: * * long double t,w,r_head, r_tail; * t = (long double)y[2] + (long double)y[1]; diff --git a/libs/SDL3/src/libm/math_libm.h b/libs/SDL3/src/libm/math_libm.h index b7b16140..43f2e5b6 100644 --- a/libs/SDL3/src/libm/math_libm.h +++ b/libs/SDL3/src/libm/math_libm.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/libm/s_atan.c b/libs/SDL3/src/libm/s_atan.c index ce429d22..c8a0d015 100644 --- a/libs/SDL3/src/libm/s_atan.c +++ b/libs/SDL3/src/libm/s_atan.c @@ -61,10 +61,6 @@ static const double aT[] = { 1.62858201153657823623e-02, /* 0x3F90AD3A, 0xE322DA11 */ }; -#ifdef __WATCOMC__ /* Watcom defines huge=__huge */ -#undef huge -#endif - static const double one = 1.0, huge = 1.0e300; diff --git a/libs/SDL3/src/libm/s_floor.c b/libs/SDL3/src/libm/s_floor.c index 4809af15..a3f4f2fa 100644 --- a/libs/SDL3/src/libm/s_floor.c +++ b/libs/SDL3/src/libm/s_floor.c @@ -25,10 +25,6 @@ #include "math_libm.h" #include "math_private.h" -#ifdef __WATCOMC__ /* Watcom defines huge=__huge */ -#undef huge -#endif - static const double huge = 1.0e300; double floor(double x) diff --git a/libs/SDL3/src/libm/s_scalbn.c b/libs/SDL3/src/libm/s_scalbn.c index b3a06044..799e7fc5 100644 --- a/libs/SDL3/src/libm/s_scalbn.c +++ b/libs/SDL3/src/libm/s_scalbn.c @@ -21,10 +21,6 @@ #include "math_private.h" #include -#ifdef __WATCOMC__ /* Watcom defines huge=__huge */ -#undef huge -#endif - static const double two54 = 1.80143985094819840000e+16, /* 0x43500000, 0x00000000 */ twom54 = 5.55111512312578270212e-17, /* 0x3C900000, 0x00000000 */ diff --git a/libs/SDL3/src/loadso/dlopen/SDL_sysloadso.c b/libs/SDL3/src/loadso/dlopen/SDL_sysloadso.c index c84d1a1b..7d223ce7 100644 --- a/libs/SDL3/src/loadso/dlopen/SDL_sysloadso.c +++ b/libs/SDL3/src/loadso/dlopen/SDL_sysloadso.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/loadso/dummy/SDL_sysloadso.c b/libs/SDL3/src/loadso/dummy/SDL_sysloadso.c index 733386ba..ff7db420 100644 --- a/libs/SDL3/src/loadso/dummy/SDL_sysloadso.c +++ b/libs/SDL3/src/loadso/dummy/SDL_sysloadso.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/loadso/windows/SDL_sysloadso.c b/libs/SDL3/src/loadso/windows/SDL_sysloadso.c index 89e41456..d2c26664 100644 --- a/libs/SDL3/src/loadso/windows/SDL_sysloadso.c +++ b/libs/SDL3/src/loadso/windows/SDL_sysloadso.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -29,7 +29,7 @@ SDL_SharedObject *SDL_LoadObject(const char *sofile) { - if (!sofile) { + CHECK_PARAM(!sofile) { SDL_InvalidParamError("sofile"); return NULL; } diff --git a/libs/SDL3/src/locale/SDL_locale.c b/libs/SDL3/src/locale/SDL_locale.c index 09e011b7..0cdd1013 100644 --- a/libs/SDL3/src/locale/SDL_locale.c +++ b/libs/SDL3/src/locale/SDL_locale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/locale/SDL_syslocale.h b/libs/SDL3/src/locale/SDL_syslocale.h index 40b805d5..69ac9fd4 100644 --- a/libs/SDL3/src/locale/SDL_syslocale.h +++ b/libs/SDL3/src/locale/SDL_syslocale.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/locale/android/SDL_syslocale.c b/libs/SDL3/src/locale/android/SDL_syslocale.c index da13ee44..f121643c 100644 --- a/libs/SDL3/src/locale/android/SDL_syslocale.c +++ b/libs/SDL3/src/locale/android/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/locale/dummy/SDL_syslocale.c b/libs/SDL3/src/locale/dummy/SDL_syslocale.c index f6cf9d7f..1bd77027 100644 --- a/libs/SDL3/src/locale/dummy/SDL_syslocale.c +++ b/libs/SDL3/src/locale/dummy/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/locale/emscripten/SDL_syslocale.c b/libs/SDL3/src/locale/emscripten/SDL_syslocale.c index 6983628c..0dd86a36 100644 --- a/libs/SDL3/src/locale/emscripten/SDL_syslocale.c +++ b/libs/SDL3/src/locale/emscripten/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -27,7 +27,7 @@ bool SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) { /* *INDENT-OFF* */ // clang-format off - EM_ASM({ + MAIN_THREAD_EM_ASM({ var buf = $0; var buflen = $1; var list = undefined; diff --git a/libs/SDL3/src/locale/haiku/SDL_syslocale.cc b/libs/SDL3/src/locale/haiku/SDL_syslocale.cc index 045b751a..6014dc96 100644 --- a/libs/SDL3/src/locale/haiku/SDL_syslocale.cc +++ b/libs/SDL3/src/locale/haiku/SDL_syslocale.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/locale/macos/SDL_syslocale.m b/libs/SDL3/src/locale/macos/SDL_syslocale.m index 6ea1761d..aee33d8d 100644 --- a/libs/SDL3/src/locale/macos/SDL_syslocale.m +++ b/libs/SDL3/src/locale/macos/SDL_syslocale.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/locale/n3ds/SDL_syslocale.c b/libs/SDL3/src/locale/n3ds/SDL_syslocale.c index 5d0bbd32..52e3742a 100644 --- a/libs/SDL3/src/locale/n3ds/SDL_syslocale.c +++ b/libs/SDL3/src/locale/n3ds/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -35,10 +35,12 @@ bool SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) static const char AVAILABLE_LOCALES[][6] = { "ja_JP", "en_US", "fr_FR", "de_DE", "it_IT", "es_ES", "zh_CN", "ko_KR", "nl_NL", "pt_PT", "ru_RU", "zh_TW" }; - u8 current_locale = GetLocaleIndex(); - if (current_locale != BAD_LOCALE) { - SDL_strlcpy(buf, AVAILABLE_LOCALES[current_locale], buflen); + const u8 current_locale = GetLocaleIndex(); + if ((current_locale == BAD_LOCALE) || (current_locale >= SDL_arraysize(AVAILABLE_LOCALES))) { + return SDL_SetError("Could not obtain system locale"); } + + SDL_strlcpy(buf, AVAILABLE_LOCALES[current_locale], buflen); return true; } diff --git a/libs/SDL3/src/locale/ngage/SDL_syslocale.cpp b/libs/SDL3/src/locale/ngage/SDL_syslocale.cpp index 4ab0eb73..d94da018 100644 --- a/libs/SDL3/src/locale/ngage/SDL_syslocale.cpp +++ b/libs/SDL3/src/locale/ngage/SDL_syslocale.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/locale/psp/SDL_syslocale.c b/libs/SDL3/src/locale/psp/SDL_syslocale.c index 4b5e1b93..b6ec4f70 100644 --- a/libs/SDL3/src/locale/psp/SDL_syslocale.c +++ b/libs/SDL3/src/locale/psp/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/locale/unix/SDL_syslocale.c b/libs/SDL3/src/locale/unix/SDL_syslocale.c index 98c68c39..7c698d3d 100644 --- a/libs/SDL3/src/locale/unix/SDL_syslocale.c +++ b/libs/SDL3/src/locale/unix/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/locale/vita/SDL_syslocale.c b/libs/SDL3/src/locale/vita/SDL_syslocale.c index 76a6f6a2..edb1d38f 100644 --- a/libs/SDL3/src/locale/vita/SDL_syslocale.c +++ b/libs/SDL3/src/locale/vita/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/locale/windows/SDL_syslocale.c b/libs/SDL3/src/locale/windows/SDL_syslocale.c index 7f69c569..01412719 100644 --- a/libs/SDL3/src/locale/windows/SDL_syslocale.c +++ b/libs/SDL3/src/locale/windows/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -23,7 +23,7 @@ #include "../../core/windows/SDL_windows.h" #include "../SDL_syslocale.h" -typedef BOOL(WINAPI *pfnGetUserPreferredUILanguages)(DWORD, PULONG, WCHAR *, PULONG); +typedef BOOL (WINAPI *pfnGetUserPreferredUILanguages)(DWORD, PULONG, WCHAR *, PULONG); #ifndef MUI_LANGUAGE_NAME #define MUI_LANGUAGE_NAME 0x8 #endif diff --git a/libs/SDL3/src/main/SDL_main_callbacks.c b/libs/SDL3/src/main/SDL_main_callbacks.c index 913c9a71..757baef0 100644 --- a/libs/SDL3/src/main/SDL_main_callbacks.c +++ b/libs/SDL3/src/main/SDL_main_callbacks.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -147,3 +147,19 @@ void SDL_QuitMainCallbacks(SDL_AppResult result) SDL_Quit(); } +static void SDL_CheckDefaultArgcArgv(int *argc, char ***argv) +{ + if (!*argv) { + static char dummyargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; + static char *argvdummy[2] = { dummyargv0, NULL }; + *argc = 1; + *argv = argvdummy; + } +} + +int SDL_CallMainFunction(int argc, char *argv[], SDL_main_func mainFunction) +{ + SDL_CheckDefaultArgcArgv(&argc, &argv); + SDL_SetMainReady(); + return mainFunction(argc, argv); +} diff --git a/libs/SDL3/src/main/SDL_main_callbacks.h b/libs/SDL3/src/main/SDL_main_callbacks.h index 1fd3725c..3a8c30ac 100644 --- a/libs/SDL3/src/main/SDL_main_callbacks.h +++ b/libs/SDL3/src/main/SDL_main_callbacks.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -27,6 +27,7 @@ SDL_AppResult SDL_InitMainCallbacks(int argc, char *argv[], SDL_AppInit_func app SDL_AppResult SDL_IterateMainCallbacks(bool pump_events); void SDL_QuitMainCallbacks(SDL_AppResult result); +// Check args and call the main function +extern int SDL_CallMainFunction(int argc, char *argv[], SDL_main_func mainFunction); + #endif // SDL_main_callbacks_h_ - - diff --git a/libs/SDL3/src/main/SDL_runapp.c b/libs/SDL3/src/main/SDL_runapp.c index eccdd3fc..c0ccd8fa 100644 --- a/libs/SDL3/src/main/SDL_runapp.c +++ b/libs/SDL3/src/main/SDL_runapp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -19,25 +19,22 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "SDL_internal.h" +#include "SDL_main_callbacks.h" -/* Most platforms that use/need SDL_main have their own SDL_RunApp() implementation. - * If not, you can special case it here by appending || defined(__YOUR_PLATFORM__) */ -#if ( !defined(SDL_MAIN_NEEDED) && !defined(SDL_MAIN_AVAILABLE) ) || defined(SDL_PLATFORM_ANDROID) +// Add your platform here if you define a custom SDL_RunApp() implementation +#if !defined(SDL_PLATFORM_WIN32) && \ + !defined(SDL_PLATFORM_GDK) && \ + !defined(SDL_PLATFORM_IOS) && \ + !defined(SDL_PLATFORM_TVOS) && \ + !defined(SDL_PLATFORM_EMSCRIPTEN) && \ + !defined(SDL_PLATFORM_PSP) && \ + !defined(SDL_PLATFORM_PS2) && \ + !defined(SDL_PLATFORM_3DS) int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { (void)reserved; - - if(!argv) - { - // make sure argv isn't NULL, in case some user code doesn't like that - static char dummyargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; - static char *argvdummy[2] = { dummyargv0, NULL }; - argc = 1; - argv = argvdummy; - } - - return mainFunction(argc, argv); + return SDL_CallMainFunction(argc, argv, mainFunction); } #endif diff --git a/libs/SDL3/src/main/emscripten/SDL_sysmain_callbacks.c b/libs/SDL3/src/main/emscripten/SDL_sysmain_callbacks.c index babffb3b..fca34886 100644 --- a/libs/SDL3/src/main/emscripten/SDL_sysmain_callbacks.c +++ b/libs/SDL3/src/main/emscripten/SDL_sysmain_callbacks.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -24,9 +24,61 @@ #include +// For Emscripten, we let you use SDL_HINT_MAIN_CALLBACK_RATE, because it might be useful to drop it super-low for +// things like loopwave that don't really do much but wait on the audio device, but be warned that browser timers +// are super-unreliable in modern times, so you likely won't hit your desired callback rate with good precision. +// Almost all apps should leave this alone, so we can use requestAnimationFrame, which is intended to run reliably +// at the refresh rate of the user's display. +static Uint32 callback_rate_increment = 0; +static bool iterate_after_waitevent = false; +static bool callback_rate_changed = false; +static void SDLCALL MainCallbackRateHintChanged(void *userdata, const char *name, const char *oldValue, const char *newValue) +{ + callback_rate_changed = true; + iterate_after_waitevent = newValue && (SDL_strcmp(newValue, "waitevent") == 0); + if (iterate_after_waitevent) { + callback_rate_increment = 0; + } else { + const double callback_rate = newValue ? SDL_atof(newValue) : 0.0; + if (callback_rate > 0.0) { + callback_rate_increment = (Uint32) SDL_NS_TO_MS((double) SDL_NS_PER_SECOND / callback_rate); + } else { + callback_rate_increment = 0; + } + } +} + +// just tell us when any new event is pushed on the queue, so we can check a flag for "waitevent" mode. +static bool saw_new_event = false; +static bool SDLCALL EmscriptenMainCallbackEventWatcher(void *userdata, SDL_Event *event) +{ + saw_new_event = true; + return true; +} + static void EmscriptenInternalMainloop(void) { - const SDL_AppResult rc = SDL_IterateMainCallbacks(true); + // callback rate changed? Update emscripten's mainloop iteration speed. + if (callback_rate_changed) { + callback_rate_changed = false; + if (callback_rate_increment == 0) { + emscripten_set_main_loop_timing(EM_TIMING_RAF, 1); + } else { + emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, callback_rate_increment); + } + } + + if (iterate_after_waitevent) { + SDL_PumpEvents(); + if (!saw_new_event) { + // do nothing yet. Note that we're still going to iterate here because we can't block, + // but we can stop the app's iteration from progressing until there's an event. + return; + } + saw_new_event = false; + } + + const SDL_AppResult rc = SDL_IterateMainCallbacks(!iterate_after_waitevent); if (rc != SDL_APP_CONTINUE) { SDL_QuitMainCallbacks(rc); emscripten_cancel_main_loop(); // kill" the mainloop, so it stops calling back into it. @@ -36,9 +88,18 @@ 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) { - const SDL_AppResult rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit); + SDL_AppResult rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit); if (rc == SDL_APP_CONTINUE) { - emscripten_set_main_loop(EmscriptenInternalMainloop, 0, 0); // run at refresh rate, don't throw an exception since we do an orderly return. + if (!SDL_AddEventWatch(EmscriptenMainCallbackEventWatcher, NULL)) { + rc = SDL_APP_FAILURE; + } else { + SDL_AddHintCallback(SDL_HINT_MAIN_CALLBACK_RATE, MainCallbackRateHintChanged, NULL); + callback_rate_changed = false; + emscripten_set_main_loop(EmscriptenInternalMainloop, 0, 0); // don't throw an exception since we do an orderly return. + if (callback_rate_increment > 0.0) { + emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, callback_rate_increment); + } + } } else { SDL_QuitMainCallbacks(rc); } diff --git a/libs/SDL3/src/main/emscripten/SDL_sysmain_runapp.c b/libs/SDL3/src/main/emscripten/SDL_sysmain_runapp.c index 8e2252ee..5671c96e 100644 --- a/libs/SDL3/src/main/emscripten/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/emscripten/SDL_sysmain_runapp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -22,10 +22,17 @@ #ifdef SDL_PLATFORM_EMSCRIPTEN +#include "../SDL_main_callbacks.h" + #include EM_JS_DEPS(sdlrunapp, "$dynCall,$stringToNewUTF8"); +EMSCRIPTEN_KEEPALIVE int CallSDLEmscriptenMainFunction(int argc, char *argv[], SDL_main_func mainFunction) +{ + return SDL_CallMainFunction(argc, argv, mainFunction); +} + int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { (void)reserved; @@ -44,13 +51,36 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserv //console.log("Setting SDL env var '" + key + "' to '" + value + "' ..."); dynCall('iiii', $0, [ckey, cvalue, 1]); } - _free(ckey); // these must use free(), not SDL_free()! - _free(cvalue); + _Emscripten_force_free(ckey); // these must use free(), not SDL_free()! + _Emscripten_force_free(cvalue); } } }, SDL_setenv_unsafe); - return mainFunction(argc, argv); + #ifdef SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING + MAIN_THREAD_EM_ASM({ + const persistent_path = UTF8ToString($0); + const argc = $1; + const argv = $2; + const mainFunction = $3; + //console.log("SDL is automounting persistent storage to '" + persistent_path + "' ...please wait."); + FS.mkdirTree(persistent_path); + FS.mount(IDBFS, { autoPersist: true }, persistent_path); + FS.syncfs(true, function(err) { + if (err) { + console.error(`WARNING: Failed to populate persistent store at '${persistent_path}' (${err.name}: ${err.message}). Save games likely lost?`); + } + _CallSDLEmscriptenMainFunction(argc, argv, mainFunction); // error or not, start the actual SDL_main(). + }); + }, SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING, argc, argv, mainFunction); + + // we need to stop running code until FS.syncfs() finishes, but we need the runtime to not clean up. + // The actual SDL_main/SDL_AppInit() will be called when the sync is done and things will pick back up where they were. + emscripten_exit_with_live_runtime(); + return 0; + #else + return CallSDLEmscriptenMainFunction(argc, argv, mainFunction); + #endif } #endif diff --git a/libs/SDL3/src/main/gdk/SDL_sysmain_runapp.cpp b/libs/SDL3/src/main/gdk/SDL_sysmain_runapp.cpp index 5a202259..b485891a 100644 --- a/libs/SDL3/src/main/gdk/SDL_sysmain_runapp.cpp +++ b/libs/SDL3/src/main/gdk/SDL_sysmain_runapp.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -24,66 +24,28 @@ extern "C" { #include "../../core/gdk/SDL_gdk.h" #include "../../core/windows/SDL_windows.h" #include "../../events/SDL_events_c.h" +#include "../SDL_main_callbacks.h" } #include #include #include // CommandLineToArgvW() #include -// Pop up an out of memory message, returns to Windows -static BOOL OutOfMemory(void) -{ - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Out of memory - aborting", NULL); - return FALSE; -} - -/* Gets the arguments with GetCommandLine, converts them to argc and argv - and calls SDL_main */ extern "C" -int SDL_RunApp(int, char **, SDL_main_func mainFunction, void *reserved) +int SDL_RunApp(int argc, char **argv, SDL_main_func mainFunction, void *reserved) { - LPWSTR *argvw; - char **argv; - int i, argc, result; - HRESULT hr; + (void)reserved; + + void *heap_allocated = NULL; + const char *args_error = WIN_CheckDefaultArgcArgv(&argc, &argv, &heap_allocated); + if (args_error) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", args_error, NULL); + return -1; + } + + int result = -1; XTaskQueueHandle taskQueue; - - argvw = CommandLineToArgvW(GetCommandLineW(), &argc); - if (argvw == NULL) { - return OutOfMemory(); - } - - /* Note that we need to be careful about how we allocate/free memory here. - * If the application calls SDL_SetMemoryFunctions(), we can't rely on - * SDL_free() to use the same allocator after SDL_main() returns. - */ - - // Parse it into argv and argc - argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv)); - if (argv == NULL) { - return OutOfMemory(); - } - for (i = 0; i < argc; ++i) { - const int utf8size = WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, NULL, 0, NULL, NULL); - if (!utf8size) { // uhoh? - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL); - return -1; - } - - argv[i] = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, utf8size); // this size includes the null-terminator character. - if (!argv[i]) { - return OutOfMemory(); - } - - if (WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, argv[i], utf8size, NULL, NULL) == 0) { // failed? uhoh! - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL); - return -1; - } - } - argv[i] = NULL; - LocalFree(argvw); - - hr = XGameRuntimeInitialize(); + HRESULT hr = XGameRuntimeInitialize(); if (SUCCEEDED(hr) && SDL_GetGDKTaskQueue(&taskQueue)) { Uint32 titleid = 0; @@ -104,14 +66,12 @@ int SDL_RunApp(int, char **, SDL_main_func mainFunction, void *reserved) SDL_SetError("[GDK] Unable to get titleid. Will not call XblInitialize. Check MicrosoftGame.config!"); } - SDL_SetMainReady(); - if (!GDK_RegisterChangeNotifications()) { return -1; } // Run the application main() code - result = mainFunction(argc, argv); + result = SDL_CallMainFunction(argc, argv, mainFunction); GDK_UnregisterChangeNotifications(); @@ -134,14 +94,11 @@ int SDL_RunApp(int, char **, SDL_main_func mainFunction, void *reserved) #else SDL_assert_always(0 && "[GDK] Could not initialize - aborting"); #endif - result = -1; } - // Free argv, to avoid memory leak - for (i = 0; i < argc; ++i) { - HeapFree(GetProcessHeap(), 0, argv[i]); + if (heap_allocated) { + HeapFree(GetProcessHeap(), 0, heap_allocated); } - HeapFree(GetProcessHeap(), 0, argv); return result; } diff --git a/libs/SDL3/src/main/generic/SDL_sysmain_callbacks.c b/libs/SDL3/src/main/generic/SDL_sysmain_callbacks.c index afe5dbde..77aa2f54 100644 --- a/libs/SDL3/src/main/generic/SDL_sysmain_callbacks.c +++ b/libs/SDL3/src/main/generic/SDL_sysmain_callbacks.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -25,7 +25,7 @@ #ifndef SDL_PLATFORM_IOS -static int callback_rate_increment = 0; +static Uint64 callback_rate_increment = 0; static bool iterate_after_waitevent = false; static void SDLCALL MainCallbackRateHintChanged(void *userdata, const char *name, const char *oldValue, const char *newValue) @@ -34,9 +34,9 @@ static void SDLCALL MainCallbackRateHintChanged(void *userdata, const char *name if (iterate_after_waitevent) { callback_rate_increment = 0; } else { - const int callback_rate = newValue ? SDL_atoi(newValue) : 0; - if (callback_rate > 0) { - callback_rate_increment = ((Uint64) 1000000000) / ((Uint64) callback_rate); + const double callback_rate = newValue ? SDL_atof(newValue) : 0.0; + if (callback_rate > 0.0) { + callback_rate_increment = (Uint64) ((double) SDL_NS_PER_SECOND / callback_rate); } else { callback_rate_increment = 0; } @@ -54,7 +54,7 @@ static SDL_AppResult GenericIterateMainCallbacks(void) int SDL_EnterAppMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) { SDL_AppResult rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit); - if (rc == 0) { + if (rc == SDL_APP_CONTINUE) { SDL_AddHintCallback(SDL_HINT_MAIN_CALLBACK_RATE, MainCallbackRateHintChanged, NULL); Uint64 next_iteration = callback_rate_increment ? (SDL_GetTicksNS() + callback_rate_increment) : 0; diff --git a/libs/SDL3/src/main/ios/SDL_sysmain_callbacks.m b/libs/SDL3/src/main/ios/SDL_sysmain_callbacks.m index 8ebaec67..544f0d6d 100644 --- a/libs/SDL3/src/main/ios/SDL_sysmain_callbacks.m +++ b/libs/SDL3/src/main/ios/SDL_sysmain_callbacks.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -43,6 +43,17 @@ static SDLIosMainCallbacksDisplayLink *globalDisplayLink; { if ((self = [super init])) { self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(appIteration:)]; + // Enable high refresh rates on iOS + // To enable this on phones, you should add the following line to Info.plist: + // CADisableMinimumFrameDurationOnPhone + // If main callbacks are used then this CADisplayLink will affect framerate, not one in SDL_uikitviewcontroller. + if (@available(iOS 15.0, tvOS 15.0, *)) { + const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(SDL_GetPrimaryDisplay()); + if (mode && mode->refresh_rate > 60.0f) { + int frame_rate = (int)mode->refresh_rate; + self.displayLink.preferredFrameRateRange = CAFrameRateRangeMake((frame_rate * 2) / 3, frame_rate, frame_rate); + } + } [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; } return self; diff --git a/libs/SDL3/src/main/n3ds/SDL_sysmain_runapp.c b/libs/SDL3/src/main/n3ds/SDL_sysmain_runapp.c index 06e4bf41..a6fed9bb 100644 --- a/libs/SDL3/src/main/n3ds/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/n3ds/SDL_sysmain_runapp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -23,17 +23,19 @@ #ifdef SDL_PLATFORM_3DS +#include "../SDL_main_callbacks.h" + #include <3ds.h> int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { int result; + // init osSetSpeedupEnable(true); romfsInit(); - SDL_SetMainReady(); - result = mainFunction(argc, argv); + result = SDL_CallMainFunction(argc, argv, mainFunction); // quit romfsExit(); diff --git a/libs/SDL3/src/main/ngage/SDL_sysmain_callbacks.c b/libs/SDL3/src/main/ngage/SDL_sysmain_callbacks.c index 7a6e7ecc..be17256a 100644 --- a/libs/SDL3/src/main/ngage/SDL_sysmain_callbacks.c +++ b/libs/SDL3/src/main/ngage/SDL_sysmain_callbacks.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/main/ngage/SDL_sysmain_main.cpp b/libs/SDL3/src/main/ngage/SDL_sysmain_main.cpp index c03634fb..fc3ce058 100644 --- a/libs/SDL3/src/main/ngage/SDL_sysmain_main.cpp +++ b/libs/SDL3/src/main/ngage/SDL_sysmain_main.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/main/ngage/SDL_sysmain_main.hpp b/libs/SDL3/src/main/ngage/SDL_sysmain_main.hpp index da99b6f4..018c3e15 100644 --- a/libs/SDL3/src/main/ngage/SDL_sysmain_main.hpp +++ b/libs/SDL3/src/main/ngage/SDL_sysmain_main.hpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/main/ps2/SDL_sysmain_runapp.c b/libs/SDL3/src/main/ps2/SDL_sysmain_runapp.c index 658bce30..889cccab 100644 --- a/libs/SDL3/src/main/ps2/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/ps2/SDL_sysmain_runapp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -25,6 +25,8 @@ // SDL_RunApp() code for PS2 based on SDL_ps2_main.c, fjtrujy@gmail.com +#include "../SDL_main_callbacks.h" + #include #include #include @@ -66,19 +68,17 @@ static void deinit_drivers(void) int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { - int res; + int result; (void)reserved; prepare_IOP(); init_drivers(); - SDL_SetMainReady(); - - res = mainFunction(argc, argv); + result = SDL_CallMainFunction(argc, argv, mainFunction); deinit_drivers(); - return res; + return result; } #endif // SDL_PLATFORM_PS2 diff --git a/libs/SDL3/src/main/psp/SDL_sysmain_runapp.c b/libs/SDL3/src/main/psp/SDL_sysmain_runapp.c index 7f09914b..08279f4a 100644 --- a/libs/SDL3/src/main/psp/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/psp/SDL_sysmain_runapp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -28,6 +28,7 @@ #include #include #include "../../events/SDL_events_c.h" +#include "../SDL_main_callbacks.h" /* If application's main() is redefined as SDL_main, and libSDL_main is linked, then this file will create the standard exit callback, @@ -72,11 +73,10 @@ int sdl_psp_setup_callbacks(void) int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { (void)reserved; + sdl_psp_setup_callbacks(); - SDL_SetMainReady(); - - return mainFunction(argc, argv); + return SDL_CallMainFunction(argc, argv, mainFunction); } #endif // SDL_PLATFORM_PSP diff --git a/libs/SDL3/src/main/windows/SDL_sysmain_runapp.c b/libs/SDL3/src/main/windows/SDL_sysmain_runapp.c index d2055397..ff50f3dc 100644 --- a/libs/SDL3/src/main/windows/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/windows/SDL_sysmain_runapp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -23,76 +23,26 @@ #ifdef SDL_PLATFORM_WIN32 #include "../../core/windows/SDL_windows.h" +#include "../SDL_main_callbacks.h" /* Win32-specific SDL_RunApp(), which does most of the SDL_main work, based on SDL_windows_main.c, placed in the public domain by Sam Lantinga 4/13/98 */ -#include // CommandLineToArgvW() - -// Pop up an out of memory message, returns to Windows -static int OutOfMemory(void) +int MINGW32_FORCEALIGN SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void *reserved) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Out of memory - aborting", NULL); - return -1; -} + (void)reserved; -int MINGW32_FORCEALIGN SDL_RunApp(int _argc, char *_argv[], SDL_main_func mainFunction, void * reserved) -{ - /* Gets the arguments with GetCommandLine, converts them to argc and argv - and calls SDL_main */ - - LPWSTR *argvw; - char **argv; - int i, argc, result; - - (void)_argc; (void)_argv; (void)reserved; - - argvw = CommandLineToArgvW(GetCommandLineW(), &argc); - if (!argvw) { - return OutOfMemory(); - } - - /* Note that we need to be careful about how we allocate/free memory here. - * If the application calls SDL_SetMemoryFunctions(), we can't rely on - * SDL_free() to use the same allocator after SDL_main() returns. - */ - - // Parse it into argv and argc - argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv)); - if (!argv) { - return OutOfMemory(); - } - for (i = 0; i < argc; ++i) { - const int utf8size = WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, NULL, 0, NULL, NULL); - if (!utf8size) { // uhoh? - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL); - return -1; - } - - argv[i] = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, utf8size); // this size includes the null-terminator character. - if (!argv[i]) { - return OutOfMemory(); - } - - if (WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, argv[i], utf8size, NULL, NULL) == 0) { // failed? uhoh! - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL); - return -1; + int result = -1; + void *heap_allocated = NULL; + const char *args_error = WIN_CheckDefaultArgcArgv(&argc, &argv, &heap_allocated); + if (args_error) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", args_error, NULL); + } else { + result = SDL_CallMainFunction(argc, argv, mainFunction); + if (heap_allocated) { + HeapFree(GetProcessHeap(), 0, heap_allocated); } } - argv[i] = NULL; - LocalFree(argvw); - - SDL_SetMainReady(); - - // Run the application main() code - result = mainFunction(argc, argv); - - // Free argv, to avoid memory leak - for (i = 0; i < argc; ++i) { - HeapFree(GetProcessHeap(), 0, argv[i]); - } - HeapFree(GetProcessHeap(), 0, argv); - return result; } diff --git a/libs/SDL3/src/misc/SDL_libusb.c b/libs/SDL3/src/misc/SDL_libusb.c new file mode 100644 index 00000000..82fd95ba --- /dev/null +++ b/libs/SDL3/src/misc/SDL_libusb.c @@ -0,0 +1,119 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include "SDL_libusb.h" + +#ifdef HAVE_LIBUSB + +#ifdef SDL_LIBUSB_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "libusb", + "Support for joysticks through libusb", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_LIBUSB_DYNAMIC +) +#endif + +static SDL_AtomicInt SDL_libusb_refcount; +static bool SDL_libusb_loaded; +static SDL_SharedObject *SDL_libusb_handle; +static SDL_LibUSBContext SDL_libusb_context; + +bool SDL_InitLibUSB(SDL_LibUSBContext **ctx) +{ + if (SDL_AtomicIncRef(&SDL_libusb_refcount) == 0) { +#ifdef SDL_LIBUSB_DYNAMIC + SDL_libusb_handle = SDL_LoadObject(SDL_LIBUSB_DYNAMIC); + if (SDL_libusb_handle) +#endif + { + SDL_libusb_loaded = true; +#ifdef SDL_LIBUSB_DYNAMIC +#define LOAD_LIBUSB_SYMBOL(type, func) \ + if ((SDL_libusb_context.func = (type)SDL_LoadFunction(SDL_libusb_handle, "libusb_" #func)) == NULL) { \ + SDL_libusb_loaded = false; \ + } +#else +#define LOAD_LIBUSB_SYMBOL(type, func) \ + SDL_libusb_context.func = libusb_##func; +#endif + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context **), init) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_context *), exit) + LOAD_LIBUSB_SYMBOL(ssize_t (LIBUSB_CALL *)(libusb_context *, libusb_device ***), get_device_list) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_device **, int), free_device_list) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, struct libusb_device_descriptor *), get_device_descriptor) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, struct libusb_config_descriptor **), get_active_config_descriptor) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, uint8_t, struct libusb_config_descriptor **), get_config_descriptor) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_config_descriptor *), free_config_descriptor) + LOAD_LIBUSB_SYMBOL(uint8_t (LIBUSB_CALL *)(libusb_device *), get_bus_number) +#ifdef SDL_PLATFORM_FREEBSD + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *dev, uint8_t *port_numbers, uint8_t port_numbers_len), get_port_numbers) +#else + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len), get_port_numbers) +#endif + LOAD_LIBUSB_SYMBOL(uint8_t (LIBUSB_CALL *)(libusb_device *), get_device_address) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, libusb_device_handle **), open) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_device_handle *), close) + LOAD_LIBUSB_SYMBOL(libusb_device * (LIBUSB_CALL *)(libusb_device_handle *dev_handle), get_device) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), claim_interface) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), release_interface) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), kernel_driver_active) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), detach_kernel_driver) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), attach_kernel_driver) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), set_auto_detach_kernel_driver) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int, int), set_interface_alt_setting) + LOAD_LIBUSB_SYMBOL(struct libusb_transfer * (LIBUSB_CALL *)(int), alloc_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(struct libusb_transfer *), submit_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(struct libusb_transfer *), cancel_transfer) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_transfer *), free_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, uint8_t, uint8_t, uint16_t, uint16_t, unsigned char *, uint16_t, unsigned int), control_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), interrupt_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), bulk_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *), handle_events) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *, int *), handle_events_completed) + LOAD_LIBUSB_SYMBOL(const char * (LIBUSB_CALL *)(int), error_name) +#undef LOAD_LIBUSB_SYMBOL + } + } + + if (SDL_libusb_loaded) { + *ctx = &SDL_libusb_context; + return true; + } else { + SDL_QuitLibUSB(); + *ctx = NULL; + return false; + } +} + +void SDL_QuitLibUSB(void) +{ + if (SDL_AtomicDecRef(&SDL_libusb_refcount)) { + if (SDL_libusb_handle) { + SDL_UnloadObject(SDL_libusb_handle); + SDL_libusb_handle = NULL; + } + SDL_libusb_loaded = false; + } +} + +#endif // HAVE_LIBUSB diff --git a/libs/SDL3/src/misc/SDL_libusb.h b/libs/SDL3/src/misc/SDL_libusb.h new file mode 100644 index 00000000..9cce444a --- /dev/null +++ b/libs/SDL3/src/misc/SDL_libusb.h @@ -0,0 +1,102 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifdef HAVE_LIBUSB +// libusb HIDAPI Implementation + +// Include this now, for our dynamically-loaded libusb context +#include + +typedef struct SDL_LibUSBContext +{ +/* *INDENT-OFF* */ // clang-format off + int (LIBUSB_CALL *init)(libusb_context **ctx); + void (LIBUSB_CALL *exit)(libusb_context *ctx); + ssize_t (LIBUSB_CALL *get_device_list)(libusb_context *ctx, libusb_device ***list); + void (LIBUSB_CALL *free_device_list)(libusb_device **list, int unref_devices); + int (LIBUSB_CALL *get_device_descriptor)(libusb_device *dev, struct libusb_device_descriptor *desc); + int (LIBUSB_CALL *get_active_config_descriptor)(libusb_device *dev, struct libusb_config_descriptor **config); + int (LIBUSB_CALL *get_config_descriptor)( + libusb_device *dev, + uint8_t config_index, + struct libusb_config_descriptor **config + ); + void (LIBUSB_CALL *free_config_descriptor)(struct libusb_config_descriptor *config); + uint8_t (LIBUSB_CALL *get_bus_number)(libusb_device *dev); +#ifdef SDL_PLATFORM_FREEBSD + int (LIBUSB_CALL *get_port_numbers)(libusb_device *dev, uint8_t *port_numbers, uint8_t port_numbers_len); +#else + int (LIBUSB_CALL *get_port_numbers)(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len); +#endif + uint8_t (LIBUSB_CALL *get_device_address)(libusb_device *dev); + int (LIBUSB_CALL *open)(libusb_device *dev, libusb_device_handle **dev_handle); + void (LIBUSB_CALL *close)(libusb_device_handle *dev_handle); + libusb_device *(LIBUSB_CALL *get_device)(libusb_device_handle *dev_handle); + int (LIBUSB_CALL *claim_interface)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *release_interface)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *kernel_driver_active)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *detach_kernel_driver)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *attach_kernel_driver)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *set_auto_detach_kernel_driver)(libusb_device_handle *dev_handle, int enable); + int (LIBUSB_CALL *set_interface_alt_setting)(libusb_device_handle *dev, int interface_number, int alternate_setting); + struct libusb_transfer * (LIBUSB_CALL *alloc_transfer)(int iso_packets); + int (LIBUSB_CALL *submit_transfer)(struct libusb_transfer *transfer); + int (LIBUSB_CALL *cancel_transfer)(struct libusb_transfer *transfer); + void (LIBUSB_CALL *free_transfer)(struct libusb_transfer *transfer); + int (LIBUSB_CALL *control_transfer)( + libusb_device_handle *dev_handle, + uint8_t request_type, + uint8_t bRequest, + uint16_t wValue, + uint16_t wIndex, + unsigned char *data, + uint16_t wLength, + unsigned int timeout + ); + int (LIBUSB_CALL *interrupt_transfer)( + libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *actual_length, + unsigned int timeout + ); + int (LIBUSB_CALL *bulk_transfer)( + libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *transferred, + unsigned int timeout + ); + int (LIBUSB_CALL *handle_events)(libusb_context *ctx); + int (LIBUSB_CALL *handle_events_completed)(libusb_context *ctx, int *completed); + const char * (LIBUSB_CALL *error_name)(int errcode); +/* *INDENT-ON* */ // clang-format on + +} SDL_LibUSBContext; + +extern bool SDL_InitLibUSB(SDL_LibUSBContext **ctx); +extern void SDL_QuitLibUSB(void); + +#endif // HAVE_LIBUSB diff --git a/libs/SDL3/src/misc/SDL_sysurl.h b/libs/SDL3/src/misc/SDL_sysurl.h index b5ee7007..103221c2 100644 --- a/libs/SDL3/src/misc/SDL_sysurl.h +++ b/libs/SDL3/src/misc/SDL_sysurl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/misc/SDL_url.c b/libs/SDL3/src/misc/SDL_url.c index b3163ab1..7ec28830 100644 --- a/libs/SDL3/src/misc/SDL_url.c +++ b/libs/SDL3/src/misc/SDL_url.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -24,7 +24,7 @@ bool SDL_OpenURL(const char *url) { - if (!url) { + CHECK_PARAM(!url) { return SDL_InvalidParamError("url"); } return SDL_SYS_OpenURL(url); diff --git a/libs/SDL3/src/misc/android/SDL_sysurl.c b/libs/SDL3/src/misc/android/SDL_sysurl.c index d79f1e01..8ed17a4d 100644 --- a/libs/SDL3/src/misc/android/SDL_sysurl.c +++ b/libs/SDL3/src/misc/android/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/misc/dummy/SDL_sysurl.c b/libs/SDL3/src/misc/dummy/SDL_sysurl.c index 64dcfa91..1828e3ef 100644 --- a/libs/SDL3/src/misc/dummy/SDL_sysurl.c +++ b/libs/SDL3/src/misc/dummy/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/misc/emscripten/SDL_sysurl.c b/libs/SDL3/src/misc/emscripten/SDL_sysurl.c index 881de012..7dd4262c 100644 --- a/libs/SDL3/src/misc/emscripten/SDL_sysurl.c +++ b/libs/SDL3/src/misc/emscripten/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/misc/haiku/SDL_sysurl.cc b/libs/SDL3/src/misc/haiku/SDL_sysurl.cc index 1ce59559..2c673abc 100644 --- a/libs/SDL3/src/misc/haiku/SDL_sysurl.cc +++ b/libs/SDL3/src/misc/haiku/SDL_sysurl.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -25,7 +25,11 @@ bool SDL_SYS_OpenURL(const char *url) { +#if B_BEOS_VERSION <= B_HAIKU_VERSION_1_BETA_5 BUrl burl(url); +#else + BUrl burl(url, true); +#endif const status_t rc = burl.OpenWithPreferredApplication(false); if (rc != B_NO_ERROR) { return SDL_SetError("URL open failed (err=%d)", (int)rc); diff --git a/libs/SDL3/src/misc/ios/SDL_sysurl.m b/libs/SDL3/src/misc/ios/SDL_sysurl.m index 1221edad..a778be97 100644 --- a/libs/SDL3/src/misc/ios/SDL_sysurl.m +++ b/libs/SDL3/src/misc/ios/SDL_sysurl.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/misc/macos/SDL_sysurl.m b/libs/SDL3/src/misc/macos/SDL_sysurl.m index d41696a2..06dd6f0f 100644 --- a/libs/SDL3/src/misc/macos/SDL_sysurl.m +++ b/libs/SDL3/src/misc/macos/SDL_sysurl.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/misc/riscos/SDL_sysurl.c b/libs/SDL3/src/misc/riscos/SDL_sysurl.c index f5a92b27..7e80f3b5 100644 --- a/libs/SDL3/src/misc/riscos/SDL_sysurl.c +++ b/libs/SDL3/src/misc/riscos/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/misc/unix/SDL_sysurl.c b/libs/SDL3/src/misc/unix/SDL_sysurl.c index e72894be..32e2d476 100644 --- a/libs/SDL3/src/misc/unix/SDL_sysurl.c +++ b/libs/SDL3/src/misc/unix/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -33,8 +33,42 @@ extern char **environ; #endif +#ifdef HAVE_DBUS_DBUS_H +#include "../../core/linux/SDL_dbus.h" +#endif + +#ifdef SDL_VIDEO_DRIVER_WAYLAND +#include "../../video/wayland/SDL_waylandutil.h" +#endif + +// Wayland requires an activation token for the browser to take focus. +static void GetActivationToken(char **token, char **window_id) +{ +#ifdef SDL_VIDEO_DRIVER_WAYLAND + SDL_VideoDevice *vid = SDL_GetVideoDevice(); + + if (vid && SDL_strcmp(vid->name, "wayland") == 0) { + Wayland_GetActivationTokenForExport(vid, token, window_id); + } +#endif +} + bool SDL_SYS_OpenURL(const char *url) { + char *activation_token = NULL; + char *window_id = NULL; + + GetActivationToken(&activation_token, &window_id); + + // Prefer the D-Bus portal, if available. +#ifdef HAVE_DBUS_DBUS_H + if (SDL_DBus_OpenURI(url, window_id, activation_token)) { + SDL_free(activation_token); + SDL_free(window_id); + return true; + } +#endif + const char *args[] = { "xdg-open", url, NULL }; SDL_Environment *env = NULL; SDL_Process *process = NULL; @@ -47,6 +81,9 @@ bool SDL_SYS_OpenURL(const char *url) // Clear LD_PRELOAD so Chrome opens correctly when this application is launched by Steam SDL_UnsetEnvironmentVariable(env, "LD_PRELOAD"); + if (activation_token) { + SDL_SetEnvironmentVariable(env, "XDG_ACTIVATION_TOKEN", activation_token, false); + } SDL_PropertiesID props = SDL_CreateProperties(); if (!props) { @@ -64,6 +101,8 @@ bool SDL_SYS_OpenURL(const char *url) result = true; done: + SDL_free(activation_token); + SDL_free(window_id); SDL_DestroyEnvironment(env); SDL_DestroyProcess(process); diff --git a/libs/SDL3/src/misc/vita/SDL_sysurl.c b/libs/SDL3/src/misc/vita/SDL_sysurl.c index fb901c3f..3f4fb222 100644 --- a/libs/SDL3/src/misc/vita/SDL_sysurl.c +++ b/libs/SDL3/src/misc/vita/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/misc/windows/SDL_sysurl.c b/libs/SDL3/src/misc/windows/SDL_sysurl.c index 8e7bf0bf..252768b5 100644 --- a/libs/SDL3/src/misc/windows/SDL_sysurl.c +++ b/libs/SDL3/src/misc/windows/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/power/SDL_power.c b/libs/SDL3/src/power/SDL_power.c index 5dde3b14..83088909 100644 --- a/libs/SDL3/src/power/SDL_power.c +++ b/libs/SDL3/src/power/SDL_power.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -84,7 +84,7 @@ static SDL_GetPowerInfo_Impl implementations[] = { SDL_PowerState SDL_GetPowerInfo(int *seconds, int *percent) { #ifndef SDL_POWER_DISABLED - const int total = sizeof(implementations) / sizeof(implementations[0]); + const int total = SDL_arraysize(implementations); SDL_PowerState result = SDL_POWERSTATE_UNKNOWN; int i; #endif diff --git a/libs/SDL3/src/power/SDL_syspower.h b/libs/SDL3/src/power/SDL_syspower.h index 5f8fa533..6ef3e26b 100644 --- a/libs/SDL3/src/power/SDL_syspower.h +++ b/libs/SDL3/src/power/SDL_syspower.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/power/android/SDL_syspower.c b/libs/SDL3/src/power/android/SDL_syspower.c index 7f8d3f93..0e602f85 100644 --- a/libs/SDL3/src/power/android/SDL_syspower.c +++ b/libs/SDL3/src/power/android/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/power/emscripten/SDL_syspower.c b/libs/SDL3/src/power/emscripten/SDL_syspower.c index 14928de4..c3975bef 100644 --- a/libs/SDL3/src/power/emscripten/SDL_syspower.c +++ b/libs/SDL3/src/power/emscripten/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/power/haiku/SDL_syspower.c b/libs/SDL3/src/power/haiku/SDL_syspower.c index c334fa6e..5b240249 100644 --- a/libs/SDL3/src/power/haiku/SDL_syspower.c +++ b/libs/SDL3/src/power/haiku/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -56,7 +56,7 @@ bool SDL_GetPowerInfo_Haiku(SDL_PowerState *state, int *seconds, int *percent) return false; // maybe some other method will work? } - SDL_memset(regs, '\0', sizeof(regs)); + SDL_zeroa(regs); regs[0] = APM_FUNC_OFFSET + APM_FUNC_GET_POWER_STATUS; regs[1] = APM_DEVICE_ALL; rc = ioctl(fd, APM_BIOS_CALL, regs); diff --git a/libs/SDL3/src/power/linux/SDL_syspower.c b/libs/SDL3/src/power/linux/SDL_syspower.c index 428be4aa..88e7b481 100644 --- a/libs/SDL3/src/power/linux/SDL_syspower.c +++ b/libs/SDL3/src/power/linux/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -536,17 +536,17 @@ static void check_upower_device(DBusConnection *conn, const char *path, SDL_Powe Sint64 si64 = 0; double d = 0.0; - if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Type", DBUS_TYPE_UINT32, &ui32)) { + if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Type", DBUS_TYPE_UINT32, &ui32)) { return; // Don't know _what_ we're looking at. Give up on it. } else if (ui32 != 2) { // 2==Battery return; // we don't care about UPS and such. - } else if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "PowerSupply", DBUS_TYPE_BOOLEAN, &ui32)) { + } else if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "PowerSupply", DBUS_TYPE_BOOLEAN, &ui32)) { return; } else if (!ui32) { return; // we don't care about random devices with batteries, like wireless controllers, etc } - if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "IsPresent", DBUS_TYPE_BOOLEAN, &ui32)) { + if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "IsPresent", DBUS_TYPE_BOOLEAN, &ui32)) { return; } if (!ui32) { @@ -555,9 +555,11 @@ static void check_upower_device(DBusConnection *conn, const char *path, SDL_Powe /* Get updated information on the battery status * This can occasionally fail, and we'll just return slightly stale data in that case */ - SDL_DBus_CallMethodOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Refresh", DBUS_TYPE_INVALID, DBUS_TYPE_INVALID); + SDL_DBus_CallMethodOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Refresh", + DBUS_TYPE_INVALID, + DBUS_TYPE_INVALID); - if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "State", DBUS_TYPE_UINT32, &ui32)) { + if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "State", DBUS_TYPE_UINT32, &ui32)) { st = SDL_POWERSTATE_UNKNOWN; // uh oh } else if (ui32 == 1) { // 1 == charging st = SDL_POWERSTATE_CHARGING; @@ -578,14 +580,14 @@ static void check_upower_device(DBusConnection *conn, const char *path, SDL_Powe } } - if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Percentage", DBUS_TYPE_DOUBLE, &d)) { + if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Percentage", DBUS_TYPE_DOUBLE, &d)) { pct = -1; // some old/cheap batteries don't set this property. } else { pct = (int)d; pct = (pct > 100) ? 100 : pct; // clamp between 0%, 100% } - if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "TimeToEmpty", DBUS_TYPE_INT64, &si64)) { + if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "TimeToEmpty", DBUS_TYPE_INT64, &si64)) { secs = -1; } else { secs = (int)si64; @@ -620,25 +622,41 @@ bool SDL_GetPowerInfo_Linux_org_freedesktop_upower(SDL_PowerState *state, int *s #ifdef SDL_USE_LIBDBUS SDL_DBusContext *dbus = SDL_DBus_GetContext(); + DBusMessage *reply = NULL; char **paths = NULL; + char *path = NULL; int i, numpaths = 0; - if (!dbus || !SDL_DBus_CallMethodOnConnection(dbus->system_conn, UPOWER_DBUS_NODE, UPOWER_DBUS_PATH, UPOWER_DBUS_INTERFACE, "EnumerateDevices", - DBUS_TYPE_INVALID, - DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &numpaths, DBUS_TYPE_INVALID)) { + if (!dbus) { return false; // try a different approach than UPower. } - result = true; // Clearly we can use this interface. - *state = SDL_POWERSTATE_NO_BATTERY; // assume we're just plugged in. - *seconds = -1; - *percent = -1; + if (SDL_DBus_CallMethodOnConnection(dbus->system_conn, &reply, UPOWER_DBUS_NODE, UPOWER_DBUS_PATH, UPOWER_DBUS_INTERFACE, "GetDisplayDevice", + DBUS_TYPE_INVALID, + DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { + result = true; // Clearly we can use this interface. + *state = SDL_POWERSTATE_NO_BATTERY; // assume we're just plugged in. + *seconds = -1; + *percent = -1; - for (i = 0; i < numpaths; i++) { - check_upower_device(dbus->system_conn, paths[i], state, seconds, percent); + check_upower_device(dbus->system_conn, path, state, seconds, percent); + SDL_DBus_FreeReply(&reply); + + } else if (SDL_DBus_CallMethodOnConnection(dbus->system_conn, NULL, UPOWER_DBUS_NODE, UPOWER_DBUS_PATH, UPOWER_DBUS_INTERFACE, "EnumerateDevices", + DBUS_TYPE_INVALID, + DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &numpaths, DBUS_TYPE_INVALID)) { + result = true; // Clearly we can use this interface. + *state = SDL_POWERSTATE_NO_BATTERY; // assume we're just plugged in. + *seconds = -1; + *percent = -1; + + for (i = 0; i < numpaths; i++) { + check_upower_device(dbus->system_conn, paths[i], state, seconds, percent); + } + + dbus->free_string_array(paths); } - dbus->free_string_array(paths); #endif // SDL_USE_LIBDBUS return result; diff --git a/libs/SDL3/src/power/macos/SDL_syspower.c b/libs/SDL3/src/power/macos/SDL_syspower.c index 4764da9c..77b2d59d 100644 --- a/libs/SDL3/src/power/macos/SDL_syspower.c +++ b/libs/SDL3/src/power/macos/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/power/n3ds/SDL_syspower.c b/libs/SDL3/src/power/n3ds/SDL_syspower.c index 822398a4..65d9b027 100644 --- a/libs/SDL3/src/power/n3ds/SDL_syspower.c +++ b/libs/SDL3/src/power/n3ds/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/power/psp/SDL_syspower.c b/libs/SDL3/src/power/psp/SDL_syspower.c index 231411fd..a252a935 100644 --- a/libs/SDL3/src/power/psp/SDL_syspower.c +++ b/libs/SDL3/src/power/psp/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/power/uikit/SDL_syspower.h b/libs/SDL3/src/power/uikit/SDL_syspower.h index 41fc43b1..94a6f76a 100644 --- a/libs/SDL3/src/power/uikit/SDL_syspower.h +++ b/libs/SDL3/src/power/uikit/SDL_syspower.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/power/uikit/SDL_syspower.m b/libs/SDL3/src/power/uikit/SDL_syspower.m index 561f2de2..443dbfcf 100644 --- a/libs/SDL3/src/power/uikit/SDL_syspower.m +++ b/libs/SDL3/src/power/uikit/SDL_syspower.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/power/vita/SDL_syspower.c b/libs/SDL3/src/power/vita/SDL_syspower.c index 5dc8f5e1..722603fb 100644 --- a/libs/SDL3/src/power/vita/SDL_syspower.c +++ b/libs/SDL3/src/power/vita/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/power/windows/SDL_syspower.c b/libs/SDL3/src/power/windows/SDL_syspower.c index ab334f3b..af0fb99a 100644 --- a/libs/SDL3/src/power/windows/SDL_syspower.c +++ b/libs/SDL3/src/power/windows/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/process/SDL_process.c b/libs/SDL3/src/process/SDL_process.c index 5db6db49..a1adabd3 100644 --- a/libs/SDL3/src/process/SDL_process.c +++ b/libs/SDL3/src/process/SDL_process.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -25,7 +25,7 @@ SDL_Process *SDL_CreateProcess(const char * const *args, bool pipe_stdio) { - if (!args || !args[0] || !args[0][0]) { + CHECK_PARAM(!args || !args[0] || !args[0][0]) { SDL_InvalidParamError("args"); return NULL; } @@ -47,12 +47,12 @@ 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])) { + CHECK_PARAM((!args || !args[0] || !args[0][0]) && (!cmdline || !cmdline[0])) { SDL_SetError("Either SDL_PROP_PROCESS_CREATE_ARGS_POINTER or SDL_PROP_PROCESS_CREATE_CMDLINE_STRING must be valid"); return NULL; } #else - if (!args || !args[0] || !args[0][0]) { + CHECK_PARAM(!args || !args[0] || !args[0][0]) { SDL_InvalidParamError("SDL_PROP_PROCESS_CREATE_ARGS_POINTER"); return NULL; } @@ -81,7 +81,7 @@ SDL_Process *SDL_CreateProcessWithProperties(SDL_PropertiesID props) SDL_PropertiesID SDL_GetProcessProperties(SDL_Process *process) { - if (!process) { + CHECK_PARAM(!process) { return SDL_InvalidParamError("process"); } return process->props; @@ -98,7 +98,7 @@ void *SDL_ReadProcess(SDL_Process *process, size_t *datasize, int *exitcode) *exitcode = -1; } - if (!process) { + CHECK_PARAM(!process) { SDL_InvalidParamError("process"); return NULL; } @@ -118,7 +118,7 @@ void *SDL_ReadProcess(SDL_Process *process, size_t *datasize, int *exitcode) SDL_IOStream *SDL_GetProcessInput(SDL_Process *process) { - if (!process) { + CHECK_PARAM(!process) { SDL_InvalidParamError("process"); return NULL; } @@ -134,7 +134,7 @@ SDL_IOStream *SDL_GetProcessInput(SDL_Process *process) SDL_IOStream *SDL_GetProcessOutput(SDL_Process *process) { - if (!process) { + CHECK_PARAM(!process) { SDL_InvalidParamError("process"); return NULL; } @@ -150,7 +150,7 @@ SDL_IOStream *SDL_GetProcessOutput(SDL_Process *process) bool SDL_KillProcess(SDL_Process *process, bool force) { - if (!process) { + CHECK_PARAM(!process) { return SDL_InvalidParamError("process"); } @@ -163,7 +163,7 @@ bool SDL_KillProcess(SDL_Process *process, bool force) bool SDL_WaitProcess(SDL_Process *process, bool block, int *exitcode) { - if (!process) { + CHECK_PARAM(!process) { return SDL_InvalidParamError("process"); } diff --git a/libs/SDL3/src/process/SDL_sysprocess.h b/libs/SDL3/src/process/SDL_sysprocess.h index 839ef9db..0bf71500 100644 --- a/libs/SDL3/src/process/SDL_sysprocess.h +++ b/libs/SDL3/src/process/SDL_sysprocess.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -20,6 +20,9 @@ */ #include "SDL_internal.h" +#ifndef SDL_sysprocess_h_ +#define SDL_sysprocess_h_ + typedef struct SDL_ProcessData SDL_ProcessData; struct SDL_Process @@ -35,3 +38,5 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID bool SDL_SYS_KillProcess(SDL_Process *process, bool force); bool SDL_SYS_WaitProcess(SDL_Process *process, bool block, int *exitcode); void SDL_SYS_DestroyProcess(SDL_Process *process); + +#endif // SDL_sysprocess_h_ diff --git a/libs/SDL3/src/process/dummy/SDL_dummyprocess.c b/libs/SDL3/src/process/dummy/SDL_dummyprocess.c index ba69e4a0..98064cc4 100644 --- a/libs/SDL3/src/process/dummy/SDL_dummyprocess.c +++ b/libs/SDL3/src/process/dummy/SDL_dummyprocess.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/process/posix/SDL_posixprocess.c b/libs/SDL3/src/process/posix/SDL_posixprocess.c index 2e05b013..2d061f96 100644 --- a/libs/SDL3/src/process/posix/SDL_posixprocess.c +++ b/libs/SDL3/src/process/posix/SDL_posixprocess.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -203,7 +203,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID #ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR #ifdef SDL_PLATFORM_APPLE if (__builtin_available(macOS 10.15, *)) { - if (posix_spawn_file_actions_addchdir(&fa, working_directory) != 0) { + if (posix_spawn_file_actions_addchdir_np(&fa, working_directory) != 0) { SDL_SetError("posix_spawn_file_actions_addchdir failed: %s", strerror(errno)); goto posix_spawn_fail_all; } diff --git a/libs/SDL3/src/process/windows/SDL_windowsprocess.c b/libs/SDL3/src/process/windows/SDL_windowsprocess.c index 65d8a394..dfefa54f 100644 --- a/libs/SDL3/src/process/windows/SDL_windowsprocess.c +++ b/libs/SDL3/src/process/windows/SDL_windowsprocess.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -263,6 +263,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID HANDLE stdin_pipe[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; HANDLE stdout_pipe[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; HANDLE stderr_pipe[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; + HANDLE handle; DWORD pipe_mode = PIPE_NOWAIT; bool result = false; @@ -357,7 +358,10 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID break; case SDL_PROCESS_STDIO_INHERITED: default: - if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_INPUT_HANDLE), + handle = GetStdHandle(STD_INPUT_HANDLE); + if (!handle) { + startup_info.hStdInput = NULL; + } else if (!DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &startup_info.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS)) { startup_info.hStdInput = INVALID_HANDLE_VALUE; @@ -394,7 +398,10 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID break; case SDL_PROCESS_STDIO_INHERITED: default: - if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_OUTPUT_HANDLE), + handle = GetStdHandle(STD_OUTPUT_HANDLE); + if (!handle) { + startup_info.hStdOutput = NULL; + } else if (!DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &startup_info.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS)) { startup_info.hStdOutput = INVALID_HANDLE_VALUE; @@ -405,7 +412,10 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID } if (redirect_stderr) { - if (!DuplicateHandle(GetCurrentProcess(), startup_info.hStdOutput, + handle = startup_info.hStdOutput; + if (!handle) { + startup_info.hStdError = NULL; + } else if (!DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &startup_info.hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS)) { startup_info.hStdError = INVALID_HANDLE_VALUE; @@ -440,7 +450,10 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID break; case SDL_PROCESS_STDIO_INHERITED: default: - if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_ERROR_HANDLE), + handle = GetStdHandle(STD_ERROR_HANDLE); + if (!handle) { + startup_info.hStdError = NULL; + } else if (!DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &startup_info.hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS)) { startup_info.hStdError = INVALID_HANDLE_VALUE; @@ -520,8 +533,31 @@ done: return result; } +static BOOL CALLBACK terminate_app(HWND hwnd, LPARAM lparam) +{ + DWORD current_proc_id = 0, *term_info = (DWORD *) lparam; + GetWindowThreadProcessId(hwnd, ¤t_proc_id); + if (current_proc_id == term_info[0] && PostMessage(hwnd, WM_CLOSE, 0, 0)) { + term_info[1]++; + } + return TRUE; +} + bool SDL_SYS_KillProcess(SDL_Process *process, bool force) { + if (!force) { + // term_info[0] is the process ID, term_info[1] is number of successful tries + DWORD term_info[2]; + term_info[0] = process->internal->process_information.dwProcessId; + term_info[1] = 0; + EnumWindows(terminate_app, (LPARAM) &term_info); + if (term_info[1] || PostThreadMessage(process->internal->process_information.dwThreadId, WM_CLOSE, 0, 0)) { + return true; + } + if (GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, term_info[0])) { + return true; + } + } if (!TerminateProcess(process->internal->process_information.hProcess, 1)) { return WIN_SetError("TerminateProcess failed"); } diff --git a/libs/SDL3/src/render/SDL_d3dmath.c b/libs/SDL3/src/render/SDL_d3dmath.c deleted file mode 100644 index 6a1ab59a..00000000 --- a/libs/SDL3/src/render/SDL_d3dmath.c +++ /dev/null @@ -1,135 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. -*/ -#include "SDL_internal.h" - -#if defined(SDL_VIDEO_RENDER_D3D) || \ - defined(SDL_VIDEO_RENDER_D3D11) || \ - defined(SDL_VIDEO_RENDER_D3D12) || \ - defined(SDL_VIDEO_RENDER_VULKAN) - -#include "SDL_d3dmath.h" - -// Direct3D matrix math functions - -Float4X4 MatrixIdentity(void) -{ - Float4X4 m; - SDL_zero(m); - m.v._11 = 1.0f; - m.v._22 = 1.0f; - m.v._33 = 1.0f; - m.v._44 = 1.0f; - return m; -} - -Float4X4 MatrixMultiply(Float4X4 M1, Float4X4 M2) -{ - Float4X4 m; - m.v._11 = M1.v._11 * M2.v._11 + M1.v._12 * M2.v._21 + M1.v._13 * M2.v._31 + M1.v._14 * M2.v._41; - m.v._12 = M1.v._11 * M2.v._12 + M1.v._12 * M2.v._22 + M1.v._13 * M2.v._32 + M1.v._14 * M2.v._42; - m.v._13 = M1.v._11 * M2.v._13 + M1.v._12 * M2.v._23 + M1.v._13 * M2.v._33 + M1.v._14 * M2.v._43; - m.v._14 = M1.v._11 * M2.v._14 + M1.v._12 * M2.v._24 + M1.v._13 * M2.v._34 + M1.v._14 * M2.v._44; - m.v._21 = M1.v._21 * M2.v._11 + M1.v._22 * M2.v._21 + M1.v._23 * M2.v._31 + M1.v._24 * M2.v._41; - m.v._22 = M1.v._21 * M2.v._12 + M1.v._22 * M2.v._22 + M1.v._23 * M2.v._32 + M1.v._24 * M2.v._42; - m.v._23 = M1.v._21 * M2.v._13 + M1.v._22 * M2.v._23 + M1.v._23 * M2.v._33 + M1.v._24 * M2.v._43; - m.v._24 = M1.v._21 * M2.v._14 + M1.v._22 * M2.v._24 + M1.v._23 * M2.v._34 + M1.v._24 * M2.v._44; - m.v._31 = M1.v._31 * M2.v._11 + M1.v._32 * M2.v._21 + M1.v._33 * M2.v._31 + M1.v._34 * M2.v._41; - m.v._32 = M1.v._31 * M2.v._12 + M1.v._32 * M2.v._22 + M1.v._33 * M2.v._32 + M1.v._34 * M2.v._42; - m.v._33 = M1.v._31 * M2.v._13 + M1.v._32 * M2.v._23 + M1.v._33 * M2.v._33 + M1.v._34 * M2.v._43; - m.v._34 = M1.v._31 * M2.v._14 + M1.v._32 * M2.v._24 + M1.v._33 * M2.v._34 + M1.v._34 * M2.v._44; - m.v._41 = M1.v._41 * M2.v._11 + M1.v._42 * M2.v._21 + M1.v._43 * M2.v._31 + M1.v._44 * M2.v._41; - m.v._42 = M1.v._41 * M2.v._12 + M1.v._42 * M2.v._22 + M1.v._43 * M2.v._32 + M1.v._44 * M2.v._42; - m.v._43 = M1.v._41 * M2.v._13 + M1.v._42 * M2.v._23 + M1.v._43 * M2.v._33 + M1.v._44 * M2.v._43; - m.v._44 = M1.v._41 * M2.v._14 + M1.v._42 * M2.v._24 + M1.v._43 * M2.v._34 + M1.v._44 * M2.v._44; - return m; -} - -Float4X4 MatrixScaling(float x, float y, float z) -{ - Float4X4 m; - SDL_zero(m); - m.v._11 = x; - m.v._22 = y; - m.v._33 = z; - m.v._44 = 1.0f; - return m; -} - -Float4X4 MatrixTranslation(float x, float y, float z) -{ - Float4X4 m; - SDL_zero(m); - m.v._11 = 1.0f; - m.v._22 = 1.0f; - m.v._33 = 1.0f; - m.v._44 = 1.0f; - m.v._41 = x; - m.v._42 = y; - m.v._43 = z; - return m; -} - -Float4X4 MatrixRotationX(float r) -{ - float sinR = SDL_sinf(r); - float cosR = SDL_cosf(r); - Float4X4 m; - SDL_zero(m); - m.v._11 = 1.0f; - m.v._22 = cosR; - m.v._23 = sinR; - m.v._32 = -sinR; - m.v._33 = cosR; - m.v._44 = 1.0f; - return m; -} - -Float4X4 MatrixRotationY(float r) -{ - float sinR = SDL_sinf(r); - float cosR = SDL_cosf(r); - Float4X4 m; - SDL_zero(m); - m.v._11 = cosR; - m.v._13 = -sinR; - m.v._22 = 1.0f; - m.v._31 = sinR; - m.v._33 = cosR; - m.v._44 = 1.0f; - return m; -} - -Float4X4 MatrixRotationZ(float r) -{ - float sinR = SDL_sinf(r); - float cosR = SDL_cosf(r); - Float4X4 m; - SDL_zero(m); - m.v._11 = cosR; - m.v._12 = sinR; - m.v._21 = -sinR; - m.v._22 = cosR; - m.v._33 = 1.0f; - m.v._44 = 1.0f; - return m; -} - -#endif // SDL_VIDEO_RENDER_D3D || SDL_VIDEO_RENDER_D3D11 || SDL_VIDEO_RENDER_D3D12 || SDL_VIDEO_RENDER_VULKAN diff --git a/libs/SDL3/src/render/SDL_d3dmath.h b/libs/SDL3/src/render/SDL_d3dmath.h index 84cff9e8..7b998696 100644 --- a/libs/SDL3/src/render/SDL_d3dmath.h +++ b/libs/SDL3/src/render/SDL_d3dmath.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -69,13 +69,108 @@ typedef struct }; } Float4X4; -extern Float4X4 MatrixIdentity(void); -extern Float4X4 MatrixMultiply(Float4X4 M1, Float4X4 M2); -extern Float4X4 MatrixScaling(float x, float y, float z); -extern Float4X4 MatrixTranslation(float x, float y, float z); -extern Float4X4 MatrixRotationX(float r); -extern Float4X4 MatrixRotationY(float r); -extern Float4X4 MatrixRotationZ(float r); +static inline Float4X4 MatrixIdentity(void) +{ + Float4X4 m; + SDL_zero(m); + m.v._11 = 1.0f; + m.v._22 = 1.0f; + m.v._33 = 1.0f; + m.v._44 = 1.0f; + return m; +} + +static inline Float4X4 MatrixMultiply(Float4X4 M1, Float4X4 M2) +{ + Float4X4 m; + m.v._11 = M1.v._11 * M2.v._11 + M1.v._12 * M2.v._21 + M1.v._13 * M2.v._31 + M1.v._14 * M2.v._41; + m.v._12 = M1.v._11 * M2.v._12 + M1.v._12 * M2.v._22 + M1.v._13 * M2.v._32 + M1.v._14 * M2.v._42; + m.v._13 = M1.v._11 * M2.v._13 + M1.v._12 * M2.v._23 + M1.v._13 * M2.v._33 + M1.v._14 * M2.v._43; + m.v._14 = M1.v._11 * M2.v._14 + M1.v._12 * M2.v._24 + M1.v._13 * M2.v._34 + M1.v._14 * M2.v._44; + m.v._21 = M1.v._21 * M2.v._11 + M1.v._22 * M2.v._21 + M1.v._23 * M2.v._31 + M1.v._24 * M2.v._41; + m.v._22 = M1.v._21 * M2.v._12 + M1.v._22 * M2.v._22 + M1.v._23 * M2.v._32 + M1.v._24 * M2.v._42; + m.v._23 = M1.v._21 * M2.v._13 + M1.v._22 * M2.v._23 + M1.v._23 * M2.v._33 + M1.v._24 * M2.v._43; + m.v._24 = M1.v._21 * M2.v._14 + M1.v._22 * M2.v._24 + M1.v._23 * M2.v._34 + M1.v._24 * M2.v._44; + m.v._31 = M1.v._31 * M2.v._11 + M1.v._32 * M2.v._21 + M1.v._33 * M2.v._31 + M1.v._34 * M2.v._41; + m.v._32 = M1.v._31 * M2.v._12 + M1.v._32 * M2.v._22 + M1.v._33 * M2.v._32 + M1.v._34 * M2.v._42; + m.v._33 = M1.v._31 * M2.v._13 + M1.v._32 * M2.v._23 + M1.v._33 * M2.v._33 + M1.v._34 * M2.v._43; + m.v._34 = M1.v._31 * M2.v._14 + M1.v._32 * M2.v._24 + M1.v._33 * M2.v._34 + M1.v._34 * M2.v._44; + m.v._41 = M1.v._41 * M2.v._11 + M1.v._42 * M2.v._21 + M1.v._43 * M2.v._31 + M1.v._44 * M2.v._41; + m.v._42 = M1.v._41 * M2.v._12 + M1.v._42 * M2.v._22 + M1.v._43 * M2.v._32 + M1.v._44 * M2.v._42; + m.v._43 = M1.v._41 * M2.v._13 + M1.v._42 * M2.v._23 + M1.v._43 * M2.v._33 + M1.v._44 * M2.v._43; + m.v._44 = M1.v._41 * M2.v._14 + M1.v._42 * M2.v._24 + M1.v._43 * M2.v._34 + M1.v._44 * M2.v._44; + return m; +} + +static inline Float4X4 MatrixScaling(float x, float y, float z) +{ + Float4X4 m; + SDL_zero(m); + m.v._11 = x; + m.v._22 = y; + m.v._33 = z; + m.v._44 = 1.0f; + return m; +} + +static inline Float4X4 MatrixTranslation(float x, float y, float z) +{ + Float4X4 m; + SDL_zero(m); + m.v._11 = 1.0f; + m.v._22 = 1.0f; + m.v._33 = 1.0f; + m.v._44 = 1.0f; + m.v._41 = x; + m.v._42 = y; + m.v._43 = z; + return m; +} + +static inline Float4X4 MatrixRotationX(float r) +{ + float sinR = SDL_sinf(r); + float cosR = SDL_cosf(r); + Float4X4 m; + SDL_zero(m); + m.v._11 = 1.0f; + m.v._22 = cosR; + m.v._23 = sinR; + m.v._32 = -sinR; + m.v._33 = cosR; + m.v._44 = 1.0f; + return m; +} + +static inline Float4X4 MatrixRotationY(float r) +{ + float sinR = SDL_sinf(r); + float cosR = SDL_cosf(r); + Float4X4 m; + SDL_zero(m); + m.v._11 = cosR; + m.v._13 = -sinR; + m.v._22 = 1.0f; + m.v._31 = sinR; + m.v._33 = cosR; + m.v._44 = 1.0f; + return m; +} + +static inline Float4X4 MatrixRotationZ(float r) +{ + float sinR = SDL_sinf(r); + float cosR = SDL_cosf(r); + Float4X4 m; + SDL_zero(m); + m.v._11 = cosR; + m.v._12 = sinR; + m.v._21 = -sinR; + m.v._22 = cosR; + m.v._33 = 1.0f; + m.v._44 = 1.0f; + return m; +} // Ends C function definitions when using C++ #ifdef __cplusplus diff --git a/libs/SDL3/src/render/SDL_render.c b/libs/SDL3/src/render/SDL_render.c index 0a088a8b..b77d33e6 100644 --- a/libs/SDL3/src/render/SDL_render.c +++ b/libs/SDL3/src/render/SDL_render.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -50,22 +50,22 @@ this should probably be removed at some point in the future. --ryan. */ #define SDL_PROP_TEXTURE_PARENT_POINTER "SDL.internal.texture.parent" #define CHECK_RENDERER_MAGIC_BUT_NOT_DESTROYED_FLAG(renderer, result) \ - if (!SDL_ObjectValid(renderer, SDL_OBJECT_TYPE_RENDERER)) { \ + CHECK_PARAM(!SDL_ObjectValid(renderer, SDL_OBJECT_TYPE_RENDERER)) { \ SDL_InvalidParamError("renderer"); \ return result; \ } -#define CHECK_RENDERER_MAGIC(renderer, result) \ - CHECK_RENDERER_MAGIC_BUT_NOT_DESTROYED_FLAG(renderer, result); \ - if ((renderer)->destroyed) { \ +#define CHECK_RENDERER_MAGIC(renderer, result) \ + CHECK_RENDERER_MAGIC_BUT_NOT_DESTROYED_FLAG(renderer, result); \ + CHECK_PARAM(renderer->destroyed) { \ SDL_SetError("Renderer's window has been destroyed, can't use further"); \ - return result; \ + return result; \ } -#define CHECK_TEXTURE_MAGIC(texture, result) \ - if (!SDL_ObjectValid(texture, SDL_OBJECT_TYPE_TEXTURE)) { \ - SDL_InvalidParamError("texture"); \ - return result; \ +#define CHECK_TEXTURE_MAGIC(texture, result) \ + CHECK_PARAM(!SDL_ObjectValid(texture, SDL_OBJECT_TYPE_TEXTURE)) { \ + SDL_InvalidParamError("texture"); \ + return result; \ } // Predefined blend modes @@ -320,6 +320,12 @@ static bool FlushRenderCommands(SDL_Renderer *renderer) DebugLogRenderCommands(renderer->render_commands); +#if DONT_DRAW_WHILE_HIDDEN + // Don't send commands to the GPU while we're hidden + if (renderer->hidden) { + result = true; + } else +#endif result = renderer->RunCommandQueue(renderer, renderer->render_commands, renderer->vertex_data, renderer->vertex_data_used); // Move the whole render command queue to the unused pool so we can reuse them next time. @@ -347,6 +353,15 @@ static bool FlushRenderCommandsIfTextureNeeded(SDL_Texture *texture) return true; } +static bool FlushRenderCommandsIfPaletteNeeded(SDL_Renderer *renderer, SDL_TexturePalette *palette) +{ + if (palette->last_command_generation == renderer->render_command_generation) { + // the current command queue depends on this palette, flush the queue now before it changes + return FlushRenderCommands(renderer); + } + return true; +} + static bool FlushRenderCommandsIfGPURenderStateNeeded(SDL_GPURenderState *state) { SDL_Renderer *renderer = state->renderer; @@ -697,6 +712,60 @@ static bool QueueCmdFillRects(SDL_Renderer *renderer, const SDL_FRect *rects, co return result; } +static bool UpdateTexturePalette(SDL_Texture *texture) +{ + SDL_Renderer *renderer = texture->renderer; + SDL_Palette *public = texture->public_palette; + + if (!SDL_ISPIXELFORMAT_INDEXED(texture->format)) { + return true; + } + + if (!public) { + return SDL_SetError("Texture doesn't have a palette"); + } + + if (texture->native) { + // Keep the native texture in sync with palette updates + if (texture->palette_version == public->version) { + return true; + } + + if (!FlushRenderCommandsIfTextureNeeded(texture->native)) { + return false; + } + + SDL_Surface *surface; + bool result = SDL_LockTextureToSurface(texture->native, NULL, &surface); + if (result) { + result = SDL_BlitSurface(texture->palette_surface, NULL, surface, NULL); + SDL_UnlockTexture(texture->native); + } + if (!result) { + return false; + } + texture->palette_version = public->version; + return true; + } + + SDL_TexturePalette *palette = texture->palette; + if (palette->version != public->version) { + // Keep the native palette in sync with palette updates + if (!FlushRenderCommandsIfPaletteNeeded(renderer, palette)) { + return false; + } + + if (!renderer->UpdatePalette(renderer, palette, public->ncolors, public->colors)) { + return false; + } + + palette->version = public->version; + } + + palette->last_command_generation = renderer->render_command_generation; + return true; +} + static bool QueueCmdCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FRect *dstrect) { SDL_RenderCommand *cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_COPY, texture); @@ -832,7 +901,7 @@ int SDL_GetNumRenderDrivers(void) const char *SDL_GetRenderDriver(int index) { #ifndef SDL_RENDER_DISABLED - if (index < 0 || index >= SDL_GetNumRenderDrivers()) { + CHECK_PARAM(index < 0 || index >= SDL_GetNumRenderDrivers()) { SDL_InvalidParamError("index"); return NULL; } @@ -885,17 +954,16 @@ static bool SDL_RendererEventWatch(void *userdata, SDL_Event *event) bool SDL_CreateWindowAndRenderer(const char *title, int width, int height, SDL_WindowFlags window_flags, SDL_Window **window, SDL_Renderer **renderer) { - bool hidden = (window_flags & SDL_WINDOW_HIDDEN) != 0; - - if (!window) { + CHECK_PARAM(!window) { return SDL_InvalidParamError("window"); } - if (!renderer) { + CHECK_PARAM(!renderer) { return SDL_InvalidParamError("renderer"); } // Hide the window so if the renderer recreates it, we don't get a visual flash on screen + bool hidden = (window_flags & SDL_WINDOW_HIDDEN) != 0; window_flags |= SDL_WINDOW_HIDDEN; *window = SDL_CreateWindow(title, width, height, window_flags); if (!*window) { @@ -953,7 +1021,7 @@ static SDL_RenderLineMethod SDL_GetRenderLineMethod(void) static void SDL_CalculateSimulatedVSyncInterval(SDL_Renderer *renderer, SDL_Window *window) { - SDL_DisplayID displayID = SDL_GetDisplayForWindow(window); + SDL_DisplayID displayID = window ? SDL_GetDisplayForWindow(window) : 0; const SDL_DisplayMode *mode; int refresh_num, refresh_den; @@ -985,6 +1053,27 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) const char *hint; SDL_PropertiesID new_props; + // The GPU renderer is the only one that can be created without a window or surface + CHECK_PARAM(!window && !surface && (!driver_name || SDL_strcmp(driver_name, SDL_GPU_RENDERER) != 0)) { + SDL_InvalidParamError("window"); + return NULL; + } + + CHECK_PARAM(window && surface) { + SDL_SetError("A renderer can't target both a window and surface"); + return NULL; + } + + CHECK_PARAM(window && SDL_WindowHasSurface(window)) { + SDL_SetError("Surface already associated with window"); + return NULL; + } + + CHECK_PARAM(window && SDL_GetRenderer(window)) { + SDL_SetError("Renderer already associated with window"); + return NULL; + } + #ifdef SDL_PLATFORM_ANDROID if (!Android_WaitActiveAndLockActivity()) { return NULL; @@ -998,21 +1087,6 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) SDL_SetObjectValid(renderer, SDL_OBJECT_TYPE_RENDERER, true); - if ((!window && !surface) || (window && surface)) { - SDL_InvalidParamError("window"); - goto error; - } - - if (window && SDL_WindowHasSurface(window)) { - SDL_SetError("Surface already associated with window"); - goto error; - } - - if (window && SDL_GetRenderer(window)) { - SDL_SetError("Renderer already associated with window"); - goto error; - } - hint = SDL_GetHint(SDL_HINT_RENDER_VSYNC); if (hint && *hint) { SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, SDL_GetHintBoolean(SDL_HINT_RENDER_VSYNC, true)); @@ -1028,6 +1102,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) goto error; } } else { + char *driver_error = NULL; bool rc = false; if (!driver_name) { driver_name = SDL_GetHint(SDL_HINT_RENDER_DRIVER); @@ -1042,10 +1117,20 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) for (int i = 0; render_drivers[i]; i++) { const SDL_RenderDriver *driver = render_drivers[i]; if ((driver_attempt_len == SDL_strlen(driver->name)) && (SDL_strncasecmp(driver->name, driver_attempt, driver_attempt_len) == 0)) { + if (driver_error) { + // Free any previous driver error + SDL_free(driver_error); + driver_error = NULL; + } + rc = driver->CreateRenderer(renderer, window, props); if (rc) { break; } + driver_error = SDL_strdup(SDL_GetError()); + SDL_LogWarn(SDL_LOG_CATEGORY_RENDER, "Couldn't create renderer %s: %s\n", driver->name, driver_error); + SDL_DestroyRendererWithoutFreeing(renderer); + SDL_zerop(renderer); // make sure we don't leave function pointers from a previous CreateRenderer() in this struct. } } @@ -1063,12 +1148,20 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) } } - if (!rc) { + if (rc) { + SDL_DebugLogBackend("render", renderer->name); + SDL_free(driver_error); + } else { if (driver_name) { - SDL_SetError("%s not available", driver_name); + if (driver_error) { + SDL_SetError("%s", driver_error); + } else { + SDL_SetError("%s not available", driver_name); + } } else { SDL_SetError("Couldn't find matching render driver"); } + SDL_free(driver_error); goto error; } } @@ -1096,6 +1189,11 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) UpdatePixelClipRect(renderer, &renderer->main_view); UpdateMainViewDimensions(renderer); + renderer->palettes = SDL_CreateHashTable(0, false, SDL_HashPointer, SDL_KeyMatchPointer, SDL_DestroyHashValue, NULL); + if (!renderer->palettes) { + goto error; + } + // new textures start at zero, so we start at 1 so first render doesn't flush by accident. renderer->render_command_generation = 1; @@ -1132,9 +1230,10 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) SDL_SetPointerProperty(new_props, SDL_PROP_RENDERER_SURFACE_POINTER, surface); } SDL_SetNumberProperty(new_props, SDL_PROP_RENDERER_OUTPUT_COLORSPACE_NUMBER, renderer->output_colorspace); - UpdateHDRProperties(renderer); + SDL_SetBooleanProperty(new_props, SDL_PROP_RENDERER_TEXTURE_WRAPPING_BOOLEAN, !renderer->npot_texture_wrap_unsupported); if (window) { + UpdateHDRProperties(renderer); SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_RENDERER_POINTER, renderer); SDL_AddWindowRenderer(window, renderer); } @@ -1190,43 +1289,38 @@ SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, const char *name) return renderer; } -SDL_Renderer *SDL_CreateGPURenderer(SDL_Window *window, SDL_GPUShaderFormat format_flags, SDL_GPUDevice **device) +SDL_Renderer *SDL_CreateGPURenderer(SDL_GPUDevice *device, SDL_Window *window) { - if (!device) { - SDL_InvalidParamError("device"); - return NULL; - } - - *device = NULL; SDL_Renderer *renderer; SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_GPU_DEVICE_POINTER, device); SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, window); - if (format_flags & SDL_GPU_SHADERFORMAT_SPIRV) { - SDL_SetBooleanProperty(props, SDL_PROP_RENDERER_CREATE_GPU_SHADERS_SPIRV_BOOLEAN, true); - } - if (format_flags & SDL_GPU_SHADERFORMAT_DXIL) { - SDL_SetBooleanProperty(props, SDL_PROP_RENDERER_CREATE_GPU_SHADERS_DXIL_BOOLEAN, true); - } - if (format_flags & SDL_GPU_SHADERFORMAT_MSL) { - SDL_SetBooleanProperty(props, SDL_PROP_RENDERER_CREATE_GPU_SHADERS_MSL_BOOLEAN, true); - } - SDL_SetStringProperty(props, SDL_PROP_RENDERER_CREATE_NAME_STRING, "gpu"); + SDL_SetStringProperty(props, SDL_PROP_RENDERER_CREATE_NAME_STRING, SDL_GPU_RENDERER); renderer = SDL_CreateRendererWithProperties(props); - if (renderer) { - *device = (SDL_GPUDevice *)SDL_GetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_GPU_DEVICE_POINTER, NULL); - } SDL_DestroyProperties(props); return renderer; } +SDL_GPUDevice *SDL_GetGPURendererDevice(SDL_Renderer *renderer) +{ + CHECK_RENDERER_MAGIC(renderer, NULL); + + SDL_GPUDevice *device = SDL_GetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_GPU_DEVICE_POINTER, NULL); + if (!device) { + SDL_SetError("Renderer isn't a GPU renderer"); + return NULL; + } + return device; +} + SDL_Renderer *SDL_CreateSoftwareRenderer(SDL_Surface *surface) { #ifdef SDL_VIDEO_RENDER_SW SDL_Renderer *renderer; - if (!surface) { + CHECK_PARAM(!surface) { SDL_InvalidParamError("surface"); return NULL; } @@ -1239,7 +1333,7 @@ SDL_Renderer *SDL_CreateSoftwareRenderer(SDL_Surface *surface) #else SDL_SetError("SDL not built with rendering support"); return NULL; -#endif // !SDL_RENDER_DISABLED +#endif // SDL_VIDEO_RENDER_SW } SDL_Renderer *SDL_GetRenderer(SDL_Window *window) @@ -1286,8 +1380,8 @@ bool SDL_GetRenderOutputSize(SDL_Renderer *renderer, int *w, int *h) } else if (renderer->window) { return SDL_GetWindowSizeInPixels(renderer->window, w, h); } else { - SDL_assert(!"This should never happen"); - return SDL_SetError("Renderer doesn't support querying output size"); + // We don't have any output size, this might be an offscreen-only renderer + return true; } } @@ -1380,11 +1474,15 @@ static SDL_PixelFormat GetClosestSupportedFormat(SDL_Renderer *renderer, SDL_Pix } } else { bool hasAlpha = SDL_ISPIXELFORMAT_ALPHA(format); + bool isIndexed = SDL_ISPIXELFORMAT_INDEXED(format); + int size = SDL_BYTESPERPIXEL(format); // We just want to match the first format that has the same channels for (i = 0; i < renderer->num_texture_formats; ++i) { if (!SDL_ISPIXELFORMAT_FOURCC(renderer->texture_formats[i]) && - SDL_ISPIXELFORMAT_ALPHA(renderer->texture_formats[i]) == hasAlpha) { + SDL_BYTESPERPIXEL(renderer->texture_formats[i]) == size && + SDL_ISPIXELFORMAT_ALPHA(renderer->texture_formats[i]) == hasAlpha && + SDL_ISPIXELFORMAT_INDEXED(renderer->texture_formats[i]) == isIndexed) { return renderer->texture_formats[i]; } } @@ -1399,6 +1497,7 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert SDL_TextureAccess access = (SDL_TextureAccess)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STATIC); int w = (int)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, 0); int h = (int)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, 0); + SDL_Palette *palette = (SDL_Palette *)SDL_GetPointerProperty(props, SDL_PROP_TEXTURE_CREATE_PALETTE_POINTER, NULL); SDL_Colorspace default_colorspace; bool texture_is_fourcc_and_target; @@ -1407,22 +1506,21 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert if (!format) { format = renderer->texture_formats[0]; } - if (SDL_BYTESPERPIXEL(format) == 0) { + + CHECK_PARAM(SDL_BYTESPERPIXEL(format) == 0) { SDL_SetError("Invalid texture format"); return NULL; } - if (SDL_ISPIXELFORMAT_INDEXED(format)) { - if (!IsSupportedFormat(renderer, format)) { - SDL_SetError("Palettized textures are not supported"); - return NULL; - } + CHECK_PARAM(SDL_ISPIXELFORMAT_INDEXED(format) && access == SDL_TEXTUREACCESS_TARGET) { + SDL_SetError("Palettized textures can't be render targets"); + return NULL; } - if (w <= 0 || h <= 0) { + CHECK_PARAM(w <= 0 || h <= 0) { SDL_SetError("Texture dimensions can't be 0"); return NULL; } int max_texture_size = (int)SDL_GetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 0); - if (max_texture_size && (w > max_texture_size || h > max_texture_size)) { + CHECK_PARAM(max_texture_size && (w > max_texture_size || h > max_texture_size)) { SDL_SetError("Texture dimensions are limited to %dx%d", max_texture_size, max_texture_size); return NULL; } @@ -1491,14 +1589,20 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, SDL_COLORSPACE_JPEG); } else { default_colorspace = SDL_GetDefaultColorspaceForFormat(closest_format); - if (SDL_COLORSPACETYPE(texture->colorspace) == SDL_COLORSPACETYPE(default_colorspace)) { + if (SDL_COLORSPACETYPE(texture->colorspace) == SDL_COLORSPACETYPE(default_colorspace) && + SDL_COLORSPACETRANSFER(texture->colorspace) == SDL_COLORSPACETRANSFER(default_colorspace)) { SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, texture->colorspace); } else { SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, default_colorspace); } } SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, closest_format); - SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, texture->access); + if (SDL_ISPIXELFORMAT_INDEXED(texture->format)) { + // We're going to be uploading pixels frequently as the palette changes + SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING); + } else { + SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, texture->access); + } SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, texture->w); SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, texture->h); @@ -1524,6 +1628,8 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert texture->next = texture->native; renderer->textures = texture; + SDL_SetTextureScaleMode(texture->native, texture->scaleMode); + if (texture->format == SDL_PIXELFORMAT_MJPG) { // We have a custom decode + upload path for this } else if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) { @@ -1536,6 +1642,12 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert SDL_DestroyTexture(texture); return NULL; } + } else if (SDL_ISPIXELFORMAT_INDEXED(texture->format)) { + texture->palette_surface = SDL_CreateSurface(w, h, texture->format); + if (!texture->palette_surface) { + SDL_DestroyTexture(texture); + return NULL; + } } else if (access == SDL_TEXTUREACCESS_STREAMING) { // The pitch is 4 byte aligned texture->pitch = (((w * SDL_BYTESPERPIXEL(format)) + 3) & ~3); @@ -1547,6 +1659,10 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert } } + if (SDL_ISPIXELFORMAT_INDEXED(texture->format) && palette) { + SDL_SetTexturePalette(texture, palette); + } + // Now set the properties for the new texture props = SDL_GetTextureProperties(texture); SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_COLORSPACE_NUMBER, texture->colorspace); @@ -1576,50 +1692,10 @@ SDL_Texture *SDL_CreateTexture(SDL_Renderer *renderer, SDL_PixelFormat format, S static bool SDL_UpdateTextureFromSurface(SDL_Texture *texture, SDL_Rect *rect, SDL_Surface *surface) { - SDL_TextureAccess access; bool direct_update; - SDL_PixelFormat tex_format; - SDL_PropertiesID surface_props; - SDL_PropertiesID tex_props; - SDL_Colorspace surface_colorspace = SDL_COLORSPACE_UNKNOWN; - SDL_Colorspace texture_colorspace = SDL_COLORSPACE_UNKNOWN; - if (texture == NULL || surface == NULL) { - return false; - } - - tex_props = SDL_GetTextureProperties(texture); - if (!tex_props) { - return false; - } - - surface_props = SDL_GetSurfaceProperties(surface); - if (!surface_props) { - return false; - } - - tex_format = (SDL_PixelFormat)SDL_GetNumberProperty(tex_props, SDL_PROP_TEXTURE_FORMAT_NUMBER, 0); - access = (SDL_TextureAccess)SDL_GetNumberProperty(tex_props, SDL_PROP_TEXTURE_ACCESS_NUMBER, 0); - - if (access != SDL_TEXTUREACCESS_STATIC && access != SDL_TEXTUREACCESS_STREAMING) { - return false; - } - - surface_colorspace = SDL_GetSurfaceColorspace(surface); - texture_colorspace = surface_colorspace; - - if (surface_colorspace == SDL_COLORSPACE_SRGB_LINEAR || - SDL_COLORSPACETRANSFER(surface_colorspace) == SDL_TRANSFER_CHARACTERISTICS_PQ) { - if (SDL_ISPIXELFORMAT_FLOAT(tex_format)) { - texture_colorspace = SDL_COLORSPACE_SRGB_LINEAR; - } else if (SDL_ISPIXELFORMAT_10BIT(tex_format)) { - texture_colorspace = SDL_COLORSPACE_HDR10; - } else { - texture_colorspace = SDL_COLORSPACE_SRGB; - } - } - - if (tex_format == surface->format && texture_colorspace == surface_colorspace) { + if (surface->format == texture->format && + SDL_GetSurfaceColorspace(surface) == texture->colorspace) { if (SDL_ISPIXELFORMAT_ALPHA(surface->format) && SDL_SurfaceHasColorKey(surface)) { /* Surface and Renderer formats are identical. * Intermediate conversion is needed to convert color key to alpha (SDL_ConvertColorkeyToAlpha()). */ @@ -1635,17 +1711,16 @@ static bool SDL_UpdateTextureFromSurface(SDL_Texture *texture, SDL_Rect *rect, S if (direct_update) { if (SDL_MUSTLOCK(surface)) { - SDL_LockSurface(surface); - SDL_UpdateTexture(texture, rect, surface->pixels, surface->pitch); - SDL_UnlockSurface(surface); + if (SDL_LockSurface(surface)) { + SDL_UpdateTexture(texture, rect, surface->pixels, surface->pitch); + SDL_UnlockSurface(surface); + } } else { SDL_UpdateTexture(texture, rect, surface->pixels, surface->pitch); } } else { - SDL_Surface *temp = NULL; - // Set up a destination surface for the texture update - temp = SDL_ConvertSurfaceAndColorspace(surface, tex_format, NULL, texture_colorspace, surface_props); + SDL_Surface *temp = SDL_ConvertSurfaceAndColorspace(surface, texture->format, texture->public_palette, texture->colorspace, SDL_GetSurfaceProperties(surface)); if (temp) { SDL_UpdateTexture(texture, NULL, temp->pixels, temp->pitch); SDL_DestroySurface(temp); @@ -1654,6 +1729,21 @@ static bool SDL_UpdateTextureFromSurface(SDL_Texture *texture, SDL_Rect *rect, S } } + if (texture->format == surface->format && surface->palette) { + // Copy the palette to the new texture + SDL_Palette *existing = surface->palette; + SDL_Palette *palette = SDL_CreatePalette(existing->ncolors); + if (palette && + SDL_SetPaletteColors(palette, existing->colors, 0, existing->ncolors) && + SDL_SetTexturePalette(texture, palette)) { + // The texture has a reference to the palette now + SDL_DestroyPalette(palette); + } else { + SDL_DestroyPalette(palette); + return false; + } + } + { Uint8 r, g, b, a; SDL_BlendMode blendMode; @@ -1678,10 +1768,8 @@ static bool SDL_UpdateTextureFromSurface(SDL_Texture *texture, SDL_Rect *rect, S SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *surface) { - bool needAlpha; int i; SDL_PixelFormat format = SDL_PIXELFORMAT_UNKNOWN; - SDL_Palette *palette; SDL_Texture *texture; SDL_PropertiesID props; SDL_Colorspace surface_colorspace = SDL_COLORSPACE_UNKNOWN; @@ -1689,28 +1777,11 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s CHECK_RENDERER_MAGIC(renderer, NULL); - if (!SDL_SurfaceValid(surface)) { + CHECK_PARAM(!SDL_SurfaceValid(surface)) { SDL_InvalidParamError("SDL_CreateTextureFromSurface(): surface"); return NULL; } - // See what the best texture format is - if (SDL_ISPIXELFORMAT_ALPHA(surface->format) || SDL_SurfaceHasColorKey(surface)) { - needAlpha = true; - } else { - needAlpha = false; - } - - // If Palette contains alpha values, promotes to alpha format - palette = SDL_GetSurfacePalette(surface); - if (palette) { - bool is_opaque, has_alpha_channel; - SDL_DetectPalette(palette, &is_opaque, &has_alpha_channel); - if (!is_opaque) { - needAlpha = true; - } - } - // Try to have the best pixel format for the texture // No alpha, but a colorkey => promote to alpha if (!SDL_ISPIXELFORMAT_ALPHA(surface->format) && SDL_SurfaceHasColorKey(surface)) { @@ -1763,9 +1834,33 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s // Fallback, choose a valid pixel format if (format == SDL_PIXELFORMAT_UNKNOWN) { format = renderer->texture_formats[0]; + + // See what the best texture format is + bool needAlpha; + if (SDL_ISPIXELFORMAT_ALPHA(surface->format) || SDL_SurfaceHasColorKey(surface)) { + needAlpha = true; + } else { + needAlpha = false; + } + + // If palette contains alpha values, promotes to alpha format + if (surface->palette) { + bool is_opaque, has_alpha_channel; + SDL_DetectPalette(surface->palette, &is_opaque, &has_alpha_channel); + if (!is_opaque) { + needAlpha = true; + } + } + + // Indexed formats don't support the transparency needed for color-keyed surfaces + bool preferIndexed = SDL_ISPIXELFORMAT_INDEXED(surface->format) && !needAlpha; + int size = SDL_BYTESPERPIXEL(format); + for (i = 0; i < renderer->num_texture_formats; ++i) { if (!SDL_ISPIXELFORMAT_FOURCC(renderer->texture_formats[i]) && - SDL_ISPIXELFORMAT_ALPHA(renderer->texture_formats[i]) == needAlpha) { + SDL_BYTESPERPIXEL(renderer->texture_formats[i]) == size && + SDL_ISPIXELFORMAT_ALPHA(renderer->texture_formats[i]) == needAlpha && + SDL_ISPIXELFORMAT_INDEXED(renderer->texture_formats[i]) == preferIndexed) { format = renderer->texture_formats[i]; break; } @@ -1849,6 +1944,85 @@ bool SDL_GetTextureSize(SDL_Texture *texture, float *w, float *h) return true; } +bool SDL_SetTexturePalette(SDL_Texture *texture, SDL_Palette *palette) +{ + CHECK_TEXTURE_MAGIC(texture, false); + + CHECK_PARAM(!SDL_ISPIXELFORMAT_INDEXED(texture->format)) { + return SDL_SetError("Texture isn't palettized format"); + } + + CHECK_PARAM(palette && palette->ncolors > (1 << SDL_BITSPERPIXEL(texture->format))) { + return SDL_SetError("Palette doesn't match surface format"); + } + + if (palette != texture->public_palette) { + SDL_Renderer *renderer = texture->renderer; + + if (texture->public_palette) { + SDL_DestroyPalette(texture->public_palette); + + if (!texture->native) { + // Clean up the texture palette + --texture->palette->refcount; + if (texture->palette->refcount == 0) { + FlushRenderCommandsIfPaletteNeeded(renderer, texture->palette); + renderer->DestroyPalette(renderer, texture->palette); + SDL_RemoveFromHashTable(renderer->palettes, texture->public_palette); + } + texture->palette = NULL; + } + } + + texture->public_palette = palette; + texture->palette_version = 0; + + if (texture->public_palette) { + ++texture->public_palette->refcount; + + if (!texture->native) { + if (SDL_FindInHashTable(renderer->palettes, palette, (const void **)&texture->palette)) { + ++texture->palette->refcount; + } else { + SDL_TexturePalette *texture_palette = (SDL_TexturePalette *)SDL_calloc(1, sizeof(*texture_palette)); + if (!texture_palette) { + SDL_SetTexturePalette(texture, NULL); + return false; + } + if (!renderer->CreatePalette(renderer, texture_palette)) { + renderer->DestroyPalette(renderer, texture_palette); + SDL_SetTexturePalette(texture, NULL); + return false; + } + texture->palette = texture_palette; + texture->palette->refcount = 1; + + if (!SDL_InsertIntoHashTable(renderer->palettes, palette, texture->palette, false)) { + SDL_SetTexturePalette(texture, NULL); + return false; + } + } + } + + if (!texture->native && renderer->ChangeTexturePalette) { + renderer->ChangeTexturePalette(renderer, texture); + } + } + + if (texture->palette_surface) { + SDL_SetSurfacePalette(texture->palette_surface, palette); + } + } + return true; +} + +SDL_Palette *SDL_GetTexturePalette(SDL_Texture *texture) +{ + CHECK_TEXTURE_MAGIC(texture, NULL); + + return texture->public_palette; +} + bool SDL_SetTextureColorMod(SDL_Texture *texture, Uint8 r, Uint8 g, Uint8 b) { const float fR = (float)r / 255.0f; @@ -1985,7 +2159,7 @@ bool SDL_SetTextureBlendMode(SDL_Texture *texture, SDL_BlendMode blendMode) CHECK_TEXTURE_MAGIC(texture, false); - if (blendMode == SDL_BLENDMODE_INVALID) { + CHECK_PARAM(blendMode == SDL_BLENDMODE_INVALID) { return SDL_InvalidParamError("blendMode"); } @@ -2020,8 +2194,8 @@ bool SDL_SetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode scaleMode) switch (scaleMode) { case SDL_SCALEMODE_NEAREST: - case SDL_SCALEMODE_LINEAR: case SDL_SCALEMODE_PIXELART: + case SDL_SCALEMODE_LINEAR: break; default: return SDL_InvalidParamError("scaleMode"); @@ -2038,7 +2212,7 @@ bool SDL_SetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode scaleMode) bool SDL_GetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode *scaleMode) { if (scaleMode) { - *scaleMode = SDL_SCALEMODE_LINEAR; + *scaleMode = SDL_SCALEMODE_INVALID; } CHECK_TEXTURE_MAGIC(texture, false); @@ -2055,6 +2229,7 @@ static bool SDL_UpdateTextureYUV(SDL_Texture *texture, const SDL_Rect *rect, { SDL_Texture *native = texture->native; SDL_Rect full_rect; + bool result = true; if (!SDL_SW_UpdateYUVTexture(texture->yuv, rect, pixels, pitch)) { return false; @@ -2074,8 +2249,7 @@ static bool SDL_UpdateTextureYUV(SDL_Texture *texture, const SDL_Rect *rect, if (!SDL_LockTexture(native, rect, &native_pixels, &native_pitch)) { return false; } - SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, - rect->w, rect->h, native_pixels, native_pitch); + result = SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, native_pixels, native_pitch); SDL_UnlockTexture(native); } else { // Use a temporary buffer for updating @@ -2086,25 +2260,38 @@ static bool SDL_UpdateTextureYUV(SDL_Texture *texture, const SDL_Rect *rect, if (!temp_pixels) { return false; } - SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, - rect->w, rect->h, temp_pixels, temp_pitch); - SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch); + result = SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, temp_pixels, temp_pitch); + if (result) { + SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch); + } SDL_free(temp_pixels); } } - return true; + return result; } #endif // SDL_HAVE_YUV -static bool SDL_UpdateTextureNative(SDL_Texture *texture, const SDL_Rect *rect, - const void *pixels, int pitch) +static bool SDL_UpdateTexturePaletteSurface(SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch) +{ + SDL_Surface *surface = texture->palette_surface; + + const Uint8 *src = (const Uint8 *)pixels; + Uint8 *dst = ((Uint8 *)surface->pixels) + rect->y * surface->pitch + (rect->x * SDL_BITSPERPIXEL(texture->format)) / 8; + int w = ((rect->w * SDL_BITSPERPIXEL(texture->format)) + 7) / 8; + int h = rect->h; + while (h--) { + SDL_memcpy(dst, src, w); + src += pitch; + dst += surface->pitch; + } + texture->palette_version = 0; + return true; +} + +static bool SDL_UpdateTextureNative(SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch) { SDL_Texture *native = texture->native; - if (!rect->w || !rect->h) { - return true; // nothing to do. - } - if (texture->access == SDL_TEXTUREACCESS_STREAMING) { // We can lock the texture and copy to it void *native_pixels = NULL; @@ -2142,10 +2329,10 @@ bool SDL_UpdateTexture(SDL_Texture *texture, const SDL_Rect *rect, const void *p CHECK_TEXTURE_MAGIC(texture, false); - if (!pixels) { + CHECK_PARAM(!pixels) { return SDL_InvalidParamError("pixels"); } - if (!pitch) { + CHECK_PARAM(!pitch) { return SDL_InvalidParamError("pitch"); } @@ -2165,6 +2352,8 @@ bool SDL_UpdateTexture(SDL_Texture *texture, const SDL_Rect *rect, const void *p } else if (texture->yuv) { return SDL_UpdateTextureYUV(texture, &real_rect, pixels, pitch); #endif + } else if (texture->palette_surface) { + return SDL_UpdateTexturePaletteSurface(texture, &real_rect, pixels, pitch); } else if (texture->native) { return SDL_UpdateTextureNative(texture, &real_rect, pixels, pitch); } else { @@ -2184,6 +2373,7 @@ static bool SDL_UpdateTextureYUVPlanar(SDL_Texture *texture, const SDL_Rect *rec { SDL_Texture *native = texture->native; SDL_Rect full_rect; + bool result = true; if (!SDL_SW_UpdateYUVTexturePlanar(texture->yuv, rect, Yplane, Ypitch, Uplane, Upitch, Vplane, Vpitch)) { return false; @@ -2207,8 +2397,7 @@ static bool SDL_UpdateTextureYUVPlanar(SDL_Texture *texture, const SDL_Rect *rec if (!SDL_LockTexture(native, rect, &native_pixels, &native_pitch)) { return false; } - SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, - rect->w, rect->h, native_pixels, native_pitch); + result = SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, native_pixels, native_pitch); SDL_UnlockTexture(native); } else { // Use a temporary buffer for updating @@ -2219,13 +2408,14 @@ static bool SDL_UpdateTextureYUVPlanar(SDL_Texture *texture, const SDL_Rect *rec if (!temp_pixels) { return false; } - SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, - rect->w, rect->h, temp_pixels, temp_pitch); - SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch); + result = SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, temp_pixels, temp_pitch); + if (result) { + SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch); + } SDL_free(temp_pixels); } } - return true; + return result; } static bool SDL_UpdateTextureNVPlanar(SDL_Texture *texture, const SDL_Rect *rect, @@ -2234,6 +2424,7 @@ static bool SDL_UpdateTextureNVPlanar(SDL_Texture *texture, const SDL_Rect *rect { SDL_Texture *native = texture->native; SDL_Rect full_rect; + bool result = true; if (!SDL_SW_UpdateNVTexturePlanar(texture->yuv, rect, Yplane, Ypitch, UVplane, UVpitch)) { return false; @@ -2257,8 +2448,7 @@ static bool SDL_UpdateTextureNVPlanar(SDL_Texture *texture, const SDL_Rect *rect if (!SDL_LockTexture(native, rect, &native_pixels, &native_pitch)) { return false; } - SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, - rect->w, rect->h, native_pixels, native_pitch); + result = SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, native_pixels, native_pitch); SDL_UnlockTexture(native); } else { // Use a temporary buffer for updating @@ -2269,13 +2459,14 @@ static bool SDL_UpdateTextureNVPlanar(SDL_Texture *texture, const SDL_Rect *rect if (!temp_pixels) { return false; } - SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, - rect->w, rect->h, temp_pixels, temp_pitch); - SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch); + result = SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, temp_pixels, temp_pitch); + if (result) { + SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch); + } SDL_free(temp_pixels); } } - return true; + return result; } #endif // SDL_HAVE_YUV @@ -2291,28 +2482,28 @@ bool SDL_UpdateYUVTexture(SDL_Texture *texture, const SDL_Rect *rect, CHECK_TEXTURE_MAGIC(texture, false); - if (!Yplane) { + CHECK_PARAM(!Yplane) { return SDL_InvalidParamError("Yplane"); } - if (!Ypitch) { + CHECK_PARAM(!Ypitch) { return SDL_InvalidParamError("Ypitch"); } - if (!Uplane) { + CHECK_PARAM(!Uplane) { return SDL_InvalidParamError("Uplane"); } - if (!Upitch) { + CHECK_PARAM(!Upitch) { return SDL_InvalidParamError("Upitch"); } - if (!Vplane) { + CHECK_PARAM(!Vplane) { return SDL_InvalidParamError("Vplane"); } - if (!Vpitch) { + CHECK_PARAM(!Vpitch) { return SDL_InvalidParamError("Vpitch"); } - if (texture->format != SDL_PIXELFORMAT_YV12 && - texture->format != SDL_PIXELFORMAT_IYUV) { - return SDL_SetError("Texture format must by YV12 or IYUV"); + CHECK_PARAM(texture->format != SDL_PIXELFORMAT_YV12 && + texture->format != SDL_PIXELFORMAT_IYUV) { + return SDL_SetError("Texture format must be YV12 or IYUV"); } real_rect.x = 0; @@ -2357,22 +2548,23 @@ bool SDL_UpdateNVTexture(SDL_Texture *texture, const SDL_Rect *rect, CHECK_TEXTURE_MAGIC(texture, false); - if (!Yplane) { + CHECK_PARAM(!Yplane) { return SDL_InvalidParamError("Yplane"); } - if (!Ypitch) { + CHECK_PARAM(!Ypitch) { return SDL_InvalidParamError("Ypitch"); } - if (!UVplane) { + CHECK_PARAM(!UVplane) { return SDL_InvalidParamError("UVplane"); } - if (!UVpitch) { + CHECK_PARAM(!UVpitch) { return SDL_InvalidParamError("UVpitch"); } - if (texture->format != SDL_PIXELFORMAT_NV12 && - texture->format != SDL_PIXELFORMAT_NV21) { - return SDL_SetError("Texture format must by NV12 or NV21"); + CHECK_PARAM(texture->format != SDL_PIXELFORMAT_NV12 && + texture->format != SDL_PIXELFORMAT_NV21 && + texture->format != SDL_PIXELFORMAT_P010) { + return SDL_SetError("Texture format must be NV12, NV21, or P010"); } real_rect.x = 0; @@ -2415,8 +2607,16 @@ static bool SDL_LockTextureYUV(SDL_Texture *texture, const SDL_Rect *rect, } #endif // SDL_HAVE_YUV -static bool SDL_LockTextureNative(SDL_Texture *texture, const SDL_Rect *rect, - void **pixels, int *pitch) +static bool SDL_LockTexturePaletteSurface(SDL_Texture *texture, const SDL_Rect *rect, void **pixels, int *pitch) +{ + SDL_Surface *surface = texture->palette_surface; + + *pixels = ((Uint8 *)surface->pixels) + rect->y * surface->pitch + (rect->x * SDL_BITSPERPIXEL(texture->format)) / 8; + *pitch = surface->pitch; + return true; +} + +static bool SDL_LockTextureNative(SDL_Texture *texture, const SDL_Rect *rect, void **pixels, int *pitch) { texture->locked_rect = *rect; *pixels = (void *)((Uint8 *)texture->pixels + @@ -2432,7 +2632,7 @@ bool SDL_LockTexture(SDL_Texture *texture, const SDL_Rect *rect, void **pixels, CHECK_TEXTURE_MAGIC(texture, false); - if (texture->access != SDL_TEXTUREACCESS_STREAMING) { + CHECK_PARAM(texture->access != SDL_TEXTUREACCESS_STREAMING) { return SDL_SetError("SDL_LockTexture(): texture must be streaming"); } @@ -2452,7 +2652,9 @@ bool SDL_LockTexture(SDL_Texture *texture, const SDL_Rect *rect, void **pixels, return SDL_LockTextureYUV(texture, rect, pixels, pitch); } else #endif - if (texture->native) { + if (texture->palette_surface) { + return SDL_LockTexturePaletteSurface(texture, rect, pixels, pitch); + } else if (texture->native) { // Calls a real SDL_LockTexture/SDL_UnlockTexture on unlock, flushing then. return SDL_LockTextureNative(texture, rect, pixels, pitch); } else { @@ -2470,8 +2672,10 @@ bool SDL_LockTextureToSurface(SDL_Texture *texture, const SDL_Rect *rect, SDL_Su void *pixels = NULL; int pitch = 0; // fix static analysis - if (!texture || !surface) { - return false; + CHECK_TEXTURE_MAGIC(texture, false); + + CHECK_PARAM(!surface) { + return SDL_InvalidParamError("surface"); } real_rect.x = 0; @@ -2491,6 +2695,9 @@ bool SDL_LockTextureToSurface(SDL_Texture *texture, const SDL_Rect *rect, SDL_Su SDL_UnlockTexture(texture); return false; } + if (texture->public_palette) { + SDL_SetSurfacePalette(texture->locked_surface, texture->public_palette); + } *surface = texture->locked_surface; return true; @@ -2518,6 +2725,11 @@ static void SDL_UnlockTextureYUV(SDL_Texture *texture) } #endif // SDL_HAVE_YUV +static void SDL_UnlockTexturePaletteSurface(SDL_Texture *texture) +{ + texture->palette_version = 0; +} + static void SDL_UnlockTextureNative(SDL_Texture *texture) { SDL_Texture *native = texture->native; @@ -2545,33 +2757,42 @@ void SDL_UnlockTexture(SDL_Texture *texture) if (texture->access != SDL_TEXTUREACCESS_STREAMING) { return; } + #ifdef SDL_HAVE_YUV if (texture->yuv) { SDL_UnlockTextureYUV(texture); } else #endif - if (texture->native) { + if (texture->palette_surface) { + SDL_UnlockTexturePaletteSurface(texture); + } else if (texture->native) { SDL_UnlockTextureNative(texture); } else { SDL_Renderer *renderer = texture->renderer; renderer->UnlockTexture(renderer, texture); } - SDL_DestroySurface(texture->locked_surface); - texture->locked_surface = NULL; + if (texture->locked_surface) { + SDL_DestroySurface(texture->locked_surface); + texture->locked_surface = NULL; + } } bool SDL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) { + CHECK_RENDERER_MAGIC(renderer, false); + // texture == NULL is valid and means reset the target to the window if (texture) { CHECK_TEXTURE_MAGIC(texture, false); - if (renderer != texture->renderer) { + + CHECK_PARAM(renderer != texture->renderer) { return SDL_SetError("Texture was not created with this renderer"); } - if (texture->access != SDL_TEXTUREACCESS_TARGET) { + CHECK_PARAM(texture->access != SDL_TEXTUREACCESS_TARGET) { return SDL_SetError("Texture not created with SDL_TEXTUREACCESS_TARGET"); } + if (texture->native) { // Always render to the native texture texture = texture->native; @@ -2616,6 +2837,7 @@ bool SDL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) SDL_Texture *SDL_GetRenderTarget(SDL_Renderer *renderer) { CHECK_RENDERER_MAGIC(renderer, NULL); + if (!renderer->target) { return NULL; } @@ -2743,6 +2965,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) @@ -2750,9 +2974,14 @@ bool SDL_SetRenderLogicalPresentation(SDL_Renderer *renderer, int w, int h, SDL_ CHECK_RENDERER_MAGIC(renderer, false); SDL_RenderViewState *view = renderer->view; + if (mode == SDL_LOGICAL_PRESENTATION_DISABLED) { + view->logical_w = 0; + view->logical_h = 0; + } else { + view->logical_w = w; + view->logical_h = h; + } view->logical_presentation_mode = mode; - view->logical_w = w; - view->logical_h = h; UpdateLogicalPresentation(renderer); @@ -2793,94 +3022,6 @@ bool SDL_GetRenderLogicalPresentationRect(SDL_Renderer *renderer, SDL_FRect *rec return true; } -static void SDL_RenderLogicalBorders(SDL_Renderer *renderer, const SDL_FRect *dst) -{ - const SDL_RenderViewState *view = renderer->view; - - if (dst->x > 0.0f || dst->y > 0.0f) { - SDL_BlendMode saved_blend_mode = renderer->blendMode; - SDL_FColor saved_color = renderer->color; - - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); - SDL_SetRenderDrawColorFloat(renderer, 0.0f, 0.0f, 0.0f, 1.0f); - - if (dst->x > 0.0f) { - SDL_FRect rect; - - rect.x = 0.0f; - rect.y = 0.0f; - rect.w = dst->x; - rect.h = (float)view->pixel_h; - SDL_RenderFillRect(renderer, &rect); - - rect.x = dst->x + dst->w; - rect.w = (float)view->pixel_w - rect.x; - SDL_RenderFillRect(renderer, &rect); - } - - if (dst->y > 0.0f) { - SDL_FRect rect; - - rect.x = 0.0f; - rect.y = 0.0f; - rect.w = (float)view->pixel_w; - rect.h = dst->y; - SDL_RenderFillRect(renderer, &rect); - - rect.y = dst->y + dst->h; - rect.h = (float)view->pixel_h - rect.y; - SDL_RenderFillRect(renderer, &rect); - } - - SDL_SetRenderDrawBlendMode(renderer, saved_blend_mode); - SDL_SetRenderDrawColorFloat(renderer, saved_color.r, saved_color.g, saved_color.b, saved_color.a); - } -} - -static void SDL_RenderLogicalPresentation(SDL_Renderer *renderer) -{ - SDL_assert(renderer->view == &renderer->main_view); - - SDL_RenderViewState *view = &renderer->main_view; - const SDL_RendererLogicalPresentation mode = view->logical_presentation_mode; - if (mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) { - // save off some state we're going to trample. - const int logical_w = view->logical_w; - const int logical_h = view->logical_h; - const float scale_x = view->scale.x; - const float scale_y = view->scale.y; - const bool clipping_enabled = view->clipping_enabled; - SDL_Rect orig_viewport, orig_cliprect; - const SDL_FRect logical_dst_rect = view->logical_dst_rect; - - SDL_copyp(&orig_viewport, &view->viewport); - if (clipping_enabled) { - SDL_copyp(&orig_cliprect, &view->clip_rect); - } - - // trample some state. - SDL_SetRenderLogicalPresentation(renderer, logical_w, logical_h, SDL_LOGICAL_PRESENTATION_DISABLED); - SDL_SetRenderViewport(renderer, NULL); - if (clipping_enabled) { - SDL_SetRenderClipRect(renderer, NULL); - } - SDL_SetRenderScale(renderer, 1.0f, 1.0f); - - // draw the borders. - SDL_RenderLogicalBorders(renderer, &logical_dst_rect); - - // now set everything back. - view->logical_presentation_mode = mode; - SDL_SetRenderViewport(renderer, &orig_viewport); - if (clipping_enabled) { - SDL_SetRenderClipRect(renderer, &orig_cliprect); - } - SDL_SetRenderScale(renderer, scale_x, scale_y); - - SDL_SetRenderLogicalPresentation(renderer, logical_w, logical_h, mode); - } -} - static bool SDL_RenderVectorFromWindow(SDL_Renderer *renderer, float window_dx, float window_dy, float *dx, float *dy) { // Convert from window coordinates to pixels within the window @@ -3358,11 +3499,7 @@ bool SDL_SetRenderDrawBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode) { CHECK_RENDERER_MAGIC(renderer, false); - if (blendMode == SDL_BLENDMODE_INVALID) { - return SDL_InvalidParamError("blendMode"); - } - - if (blendMode == SDL_BLENDMODE_INVALID) { + CHECK_PARAM(blendMode == SDL_BLENDMODE_INVALID) { return SDL_InvalidParamError("blendMode"); } @@ -3442,20 +3579,14 @@ bool SDL_RenderPoints(SDL_Renderer *renderer, const SDL_FPoint *points, int coun CHECK_RENDERER_MAGIC(renderer, false); - if (!points) { + CHECK_PARAM(!points) { return SDL_InvalidParamError("SDL_RenderPoints(): points"); } + if (count < 1) { return true; } -#if DONT_DRAW_WHILE_HIDDEN - // Don't draw while we're hidden - if (renderer->hidden) { - return true; - } -#endif - const SDL_RenderViewState *view = renderer->view; if ((view->current_scale.x != 1.0f) || (view->current_scale.y != 1.0f)) { result = RenderPointsWithRects(renderer, points, count); @@ -3648,22 +3779,16 @@ bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count CHECK_RENDERER_MAGIC(renderer, false); - if (!points) { + CHECK_PARAM(!points) { return SDL_InvalidParamError("SDL_RenderLines(): points"); } + if (count < 2) { return true; } -#if DONT_DRAW_WHILE_HIDDEN - // Don't draw while we're hidden - if (renderer->hidden) { - return true; - } -#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; @@ -3682,7 +3807,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 @@ -3714,7 +3839,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) } @@ -3827,20 +3952,14 @@ bool SDL_RenderRects(SDL_Renderer *renderer, const SDL_FRect *rects, int count) CHECK_RENDERER_MAGIC(renderer, false); - if (!rects) { + CHECK_PARAM(!rects) { return SDL_InvalidParamError("SDL_RenderRects(): rects"); } + if (count < 1) { return true; } -#if DONT_DRAW_WHILE_HIDDEN - // Don't draw while we're hidden - if (renderer->hidden) { - return true; - } -#endif - for (i = 0; i < count; ++i) { if (!SDL_RenderRect(renderer, &rects[i])) { return false; @@ -3872,20 +3991,14 @@ bool SDL_RenderFillRects(SDL_Renderer *renderer, const SDL_FRect *rects, int cou CHECK_RENDERER_MAGIC(renderer, false); - if (!rects) { + CHECK_PARAM(!rects) { return SDL_InvalidParamError("SDL_RenderFillRects(): rects"); } + if (count < 1) { return true; } -#if DONT_DRAW_WHILE_HIDDEN - // Don't draw while we're hidden - if (renderer->hidden) { - return true; - } -#endif - frects = SDL_small_alloc(SDL_FRect, count, &isstack); if (!frects) { return false; @@ -3972,17 +4085,10 @@ bool SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_F CHECK_RENDERER_MAGIC(renderer, false); CHECK_TEXTURE_MAGIC(texture, false); - if (renderer != texture->renderer) { + CHECK_PARAM(renderer != texture->renderer) { return SDL_SetError("Texture was not created with this renderer"); } -#if DONT_DRAW_WHILE_HIDDEN - // Don't draw while we're hidden - if (renderer->hidden) { - return true; - } -#endif - SDL_FRect real_srcrect; real_srcrect.x = 0.0f; real_srcrect.y = 0.0f; @@ -4000,6 +4106,10 @@ bool SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_F dstrect = &full_dstrect; } + if (!UpdateTexturePalette(texture)) { + return false; + } + if (texture->native) { texture = texture->native; } @@ -4019,20 +4129,13 @@ bool SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture, CHECK_RENDERER_MAGIC(renderer, false); CHECK_TEXTURE_MAGIC(texture, false); - if (renderer != texture->renderer) { + CHECK_PARAM(renderer != texture->renderer) { return SDL_SetError("Texture was not created with this renderer"); } if (!renderer->QueueCopyEx && !renderer->QueueGeometry) { return SDL_SetError("Renderer does not support RenderCopyEx"); } -#if DONT_DRAW_WHILE_HIDDEN - // Don't draw while we're hidden - if (renderer->hidden) { - return true; - } -#endif - real_srcrect.x = 0.0f; real_srcrect.y = 0.0f; real_srcrect.w = (float)texture->w; @@ -4045,6 +4148,10 @@ bool SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture, GetRenderViewportSize(renderer, &real_dstrect); + if (!UpdateTexturePalette(texture)) { + return false; + } + if (texture->native) { texture = texture->native; } @@ -4142,20 +4249,13 @@ bool SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture, CHECK_RENDERER_MAGIC(renderer, false); CHECK_TEXTURE_MAGIC(texture, false); - if (renderer != texture->renderer) { + CHECK_PARAM(renderer != texture->renderer) { return SDL_SetError("Texture was not created with this renderer"); } if (!renderer->QueueCopyEx && !renderer->QueueGeometry) { return SDL_SetError("Renderer does not support RenderCopyEx"); } -#if DONT_DRAW_WHILE_HIDDEN - // Don't draw while we're hidden - if (renderer->hidden) { - return true; - } -#endif - real_srcrect.x = 0.0f; real_srcrect.y = 0.0f; real_srcrect.w = (float)texture->w; @@ -4173,6 +4273,10 @@ bool SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture, dstrect = &full_dstrect; } + if (!UpdateTexturePalette(texture)) { + return false; + } + if (texture->native) { texture = texture->native; } @@ -4386,6 +4490,11 @@ static bool SDL_RenderTextureTiled_Iterate(SDL_Renderer *renderer, SDL_Texture * return true; } +static bool IsNPOT(int x) +{ + return (x <= 0) || ((x & (x - 1)) != 0); +} + bool SDL_RenderTextureTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float scale, const SDL_FRect *dstrect) { SDL_FRect real_srcrect; @@ -4393,21 +4502,14 @@ bool SDL_RenderTextureTiled(SDL_Renderer *renderer, SDL_Texture *texture, const CHECK_RENDERER_MAGIC(renderer, false); CHECK_TEXTURE_MAGIC(texture, false); - if (renderer != texture->renderer) { + CHECK_PARAM(renderer != texture->renderer) { return SDL_SetError("Texture was not created with this renderer"); } - if (scale <= 0.0f) { + CHECK_PARAM(scale <= 0.0f) { return SDL_InvalidParamError("scale"); } -#if DONT_DRAW_WHILE_HIDDEN - // Don't draw while we're hidden - if (renderer->hidden) { - return true; - } -#endif - real_srcrect.x = 0.0f; real_srcrect.y = 0.0f; real_srcrect.w = (float)texture->w; @@ -4424,17 +4526,28 @@ bool SDL_RenderTextureTiled(SDL_Renderer *renderer, SDL_Texture *texture, const dstrect = &full_dstrect; } + if (!UpdateTexturePalette(texture)) { + return false; + } + if (texture->native) { texture = texture->native; } texture->last_command_generation = renderer->render_command_generation; + bool do_wrapping = !renderer->software && + (!srcrect || + (real_srcrect.x == 0.0f && real_srcrect.y == 0.0f && + real_srcrect.w == (float)texture->w && real_srcrect.h == (float)texture->h)); + if (do_wrapping && renderer->npot_texture_wrap_unsupported) { + if (IsNPOT(texture->w) || IsNPOT(texture->h)) { + do_wrapping = false; + } + } + // See if we can use geometry with repeating texture coordinates - if (!renderer->software && - (!srcrect || - (real_srcrect.x == 0.0f && real_srcrect.y == 0.0f && - real_srcrect.w == (float)texture->w && real_srcrect.h == (float)texture->h))) { + if (do_wrapping) { return SDL_RenderTextureTiled_Wrap(renderer, texture, &real_srcrect, scale, dstrect); } else { return SDL_RenderTextureTiled_Iterate(renderer, texture, &real_srcrect, scale, dstrect); @@ -4453,7 +4566,7 @@ bool SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const CHECK_RENDERER_MAGIC(renderer, false); CHECK_TEXTURE_MAGIC(texture, false); - if (renderer != texture->renderer) { + CHECK_PARAM(renderer != texture->renderer) { return SDL_SetError("Texture was not created with this renderer"); } @@ -4590,7 +4703,7 @@ bool SDL_RenderTexture9GridTiled(SDL_Renderer *renderer, SDL_Texture *texture, c CHECK_RENDERER_MAGIC(renderer, false); CHECK_TEXTURE_MAGIC(texture, false); - if (renderer != texture->renderer) { + CHECK_PARAM(renderer != texture->renderer) { return SDL_SetError("Texture was not created with this renderer"); } @@ -4868,7 +4981,7 @@ static bool SDLCALL SDL_SW_RenderGeometryRaw(SDL_Renderer *renderer, continue; } - /* Two triangles forming a quadialateral, + /* Two triangles forming a quadrilateral, * prev and current triangles must have exactly 2 common vertices */ { int cnt = 0, j = 3; @@ -4997,6 +5110,22 @@ static bool SDLCALL SDL_SW_RenderGeometryRaw(SDL_Renderer *renderer, } } + // Check if UVs within range + if (is_quad && uv) { + const float *uv0_ = (const float *)((const char *)uv + A * color_stride); + const float *uv1_ = (const float *)((const char *)uv + B * color_stride); + const float *uv2_ = (const float *)((const char *)uv + C * color_stride); + const float *uv3_ = (const float *)((const char *)uv + C2 * color_stride); + if (uv0_[0] >= 0.0f && uv0_[0] <= 1.0f && + uv1_[0] >= 0.0f && uv1_[0] <= 1.0f && + uv2_[0] >= 0.0f && uv2_[0] <= 1.0f && + uv3_[0] >= 0.0f && uv3_[0] <= 1.0f) { + // ok + } else { + is_quad = 0; + } + } + // Start rendering rect if (is_quad) { SDL_FRect s; @@ -5116,93 +5245,103 @@ bool SDL_RenderGeometryRaw(SDL_Renderer *renderer, { int i; int count = indices ? num_indices : num_vertices; - SDL_TextureAddressMode texture_address_mode_u; - SDL_TextureAddressMode texture_address_mode_v; + SDL_TextureAddressMode texture_address_mode_u = SDL_TEXTURE_ADDRESS_CLAMP; + SDL_TextureAddressMode texture_address_mode_v = SDL_TEXTURE_ADDRESS_CLAMP; CHECK_RENDERER_MAGIC(renderer, false); + if (texture) { + CHECK_TEXTURE_MAGIC(texture, false); + + CHECK_PARAM(renderer != texture->renderer) { + return SDL_SetError("Texture was not created with this renderer"); + } + } + + CHECK_PARAM(!xy) { + return SDL_InvalidParamError("xy"); + } + + CHECK_PARAM(!color) { + return SDL_InvalidParamError("color"); + } + + CHECK_PARAM(texture && !uv) { + return SDL_InvalidParamError("uv"); + } + + (void)count; // In case parameter checking is disabled + CHECK_PARAM(count % 3 != 0) { + return SDL_InvalidParamError(indices ? "num_indices" : "num_vertices"); + } + + if (indices) { + CHECK_PARAM(size_indices != 1 && size_indices != 2 && size_indices != 4) { + return SDL_InvalidParamError("size_indices"); + } + } + + if (!indices) { + size_indices = 0; + } + if (!renderer->QueueGeometry) { return SDL_Unsupported(); } - if (texture) { - CHECK_TEXTURE_MAGIC(texture, false); - - if (renderer != texture->renderer) { - return SDL_SetError("Texture was not created with this renderer"); - } - } - - if (!xy) { - return SDL_InvalidParamError("xy"); - } - - if (!color) { - return SDL_InvalidParamError("color"); - } - - if (texture && !uv) { - return SDL_InvalidParamError("uv"); - } - - if (count % 3 != 0) { - return SDL_InvalidParamError(indices ? "num_indices" : "num_vertices"); - } - - if (indices) { - if (size_indices != 1 && size_indices != 2 && size_indices != 4) { - return SDL_InvalidParamError("size_indices"); - } - } else { - size_indices = 0; - } - -#if DONT_DRAW_WHILE_HIDDEN - // Don't draw while we're hidden - if (renderer->hidden) { - return true; - } -#endif - if (num_vertices < 3) { return true; } - if (texture && texture->native) { - texture = texture->native; - } + if (texture) { + if (!UpdateTexturePalette(texture)) { + return false; + } - texture_address_mode_u = renderer->texture_address_mode_u; - texture_address_mode_v = renderer->texture_address_mode_v; - if (texture && - (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO || - texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO)) { - for (i = 0; i < num_vertices; ++i) { - const float *uv_ = (const float *)((const char *)uv + i * uv_stride); - float u = uv_[0]; - float v = uv_[1]; - if (u < 0.0f || u > 1.0f) { - if (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO) { - texture_address_mode_u = SDL_TEXTURE_ADDRESS_WRAP; - if (texture_address_mode_v != SDL_TEXTURE_ADDRESS_AUTO) { - break; - } - } - } - if (v < 0.0f || v > 1.0f) { - if (texture_address_mode_v == SDL_TEXTURE_ADDRESS_AUTO) { - texture_address_mode_v = SDL_TEXTURE_ADDRESS_WRAP; - if (texture_address_mode_u != SDL_TEXTURE_ADDRESS_AUTO) { - break; - } - } - } + if (texture->native) { + texture = texture->native; } - if (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO) { + + if (renderer->npot_texture_wrap_unsupported && IsNPOT(texture->w)) { texture_address_mode_u = SDL_TEXTURE_ADDRESS_CLAMP; + } else { + texture_address_mode_u = renderer->texture_address_mode_u; } - if (texture_address_mode_v == SDL_TEXTURE_ADDRESS_AUTO) { + if (renderer->npot_texture_wrap_unsupported && IsNPOT(texture->h)) { texture_address_mode_v = SDL_TEXTURE_ADDRESS_CLAMP; + } else { + texture_address_mode_v = renderer->texture_address_mode_v; + } + + if (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO || + texture_address_mode_v == SDL_TEXTURE_ADDRESS_AUTO) { + for (i = 0; i < num_vertices; ++i) { + const float *uv_ = (const float *)((const char *)uv + i * uv_stride); + float u = uv_[0]; + float v = uv_[1]; + if (u < 0.0f || u > 1.0f) { + if (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO) { + texture_address_mode_u = SDL_TEXTURE_ADDRESS_WRAP; + if (texture_address_mode_v != SDL_TEXTURE_ADDRESS_AUTO) { + break; + } + } + } + if (v < 0.0f || v > 1.0f) { + if (texture_address_mode_v == SDL_TEXTURE_ADDRESS_AUTO) { + texture_address_mode_v = SDL_TEXTURE_ADDRESS_WRAP; + if (texture_address_mode_u != SDL_TEXTURE_ADDRESS_AUTO) { + break; + } + } + } + } + if (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO) { + texture_address_mode_u = SDL_TEXTURE_ADDRESS_CLAMP; + } + if (texture_address_mode_v == SDL_TEXTURE_ADDRESS_AUTO) { + texture_address_mode_v = SDL_TEXTURE_ADDRESS_CLAMP; + } } } @@ -5381,12 +5520,14 @@ bool SDL_RenderPresent(SDL_Renderer *renderer) CHECK_RENDERER_MAGIC(renderer, false); - if (renderer->target) { - return SDL_SetError("You can't present on a render target"); + CHECK_PARAM(renderer->target) { + if (!renderer->window && SDL_strcmp(renderer->name, SDL_GPU_RENDERER) == 0) { + // We're an offscreen renderer, we must submit the command queue + } else { + return SDL_SetError("You can't present on a render target"); + } } - SDL_RenderLogicalPresentation(renderer); - if (renderer->transparent_window) { SDL_RenderApplyWindowShape(renderer); } @@ -5414,6 +5555,10 @@ static void SDL_DestroyTextureInternal(SDL_Texture *texture, bool is_destroying) { SDL_Renderer *renderer; + if (texture->public_palette) { + SDL_SetTexturePalette(texture, NULL); + } + SDL_DestroyProperties(texture->props); renderer = texture->renderer; @@ -5450,8 +5595,14 @@ static void SDL_DestroyTextureInternal(SDL_Texture *texture, bool is_destroying) renderer->DestroyTexture(renderer, texture); - SDL_DestroySurface(texture->locked_surface); - texture->locked_surface = NULL; + if (texture->palette_surface) { + SDL_DestroySurface(texture->palette_surface); + texture->palette_surface = NULL; + } + if (texture->locked_surface) { + SDL_DestroySurface(texture->locked_surface); + texture->locked_surface = NULL; + } SDL_free(texture); } @@ -5525,6 +5676,13 @@ void SDL_DestroyRendererWithoutFreeing(SDL_Renderer *renderer) SDL_assert(tex != renderer->textures); // satisfy static analysis. } + // Free palette cache, which should be empty now + if (renderer->palettes) { + SDL_assert(SDL_HashTableEmpty(renderer->palettes)); + SDL_DestroyHashTable(renderer->palettes); + renderer->palettes = NULL; + } + // Clean up renderer-specific resources if (renderer->DestroyRenderer) { renderer->DestroyRenderer(renderer); @@ -5932,23 +6090,17 @@ bool SDL_GetDefaultTextureScaleMode(SDL_Renderer *renderer, SDL_ScaleMode *scale return true; } -SDL_GPURenderState *SDL_CreateGPURenderState(SDL_Renderer *renderer, SDL_GPURenderStateDesc *desc) +SDL_GPURenderState *SDL_CreateGPURenderState(SDL_Renderer *renderer, const SDL_GPURenderStateCreateInfo *createinfo) { CHECK_RENDERER_MAGIC(renderer, NULL); - if (!desc) { - SDL_InvalidParamError("desc"); + CHECK_PARAM(!createinfo) { + SDL_InvalidParamError("createinfo"); return NULL; } - if (desc->version < sizeof(*desc)) { - // Update this to handle older versions of this interface - SDL_SetError("Invalid desc, should be initialized with SDL_INIT_INTERFACE()"); - return NULL; - } - - if (!desc->fragment_shader) { - SDL_SetError("desc->fragment_shader is required"); + CHECK_PARAM(!createinfo->fragment_shader) { + SDL_SetError("A fragment_shader is required"); return NULL; } @@ -5964,36 +6116,36 @@ SDL_GPURenderState *SDL_CreateGPURenderState(SDL_Renderer *renderer, SDL_GPURend } state->renderer = renderer; - state->fragment_shader = desc->fragment_shader; + state->fragment_shader = createinfo->fragment_shader; - if (desc->num_sampler_bindings > 0) { - state->sampler_bindings = (SDL_GPUTextureSamplerBinding *)SDL_calloc(desc->num_sampler_bindings, sizeof(*state->sampler_bindings)); + if (createinfo->num_sampler_bindings > 0) { + state->sampler_bindings = (SDL_GPUTextureSamplerBinding *)SDL_calloc(createinfo->num_sampler_bindings, sizeof(*state->sampler_bindings)); if (!state->sampler_bindings) { SDL_DestroyGPURenderState(state); return NULL; } - SDL_memcpy(state->sampler_bindings, desc->sampler_bindings, desc->num_sampler_bindings * sizeof(*state->sampler_bindings)); - state->num_sampler_bindings = desc->num_sampler_bindings; + SDL_memcpy(state->sampler_bindings, createinfo->sampler_bindings, createinfo->num_sampler_bindings * sizeof(*state->sampler_bindings)); + state->num_sampler_bindings = createinfo->num_sampler_bindings; } - if (desc->num_storage_textures > 0) { - state->storage_textures = (SDL_GPUTexture **)SDL_calloc(desc->num_storage_textures, sizeof(*state->storage_textures)); + if (createinfo->num_storage_textures > 0) { + state->storage_textures = (SDL_GPUTexture **)SDL_calloc(createinfo->num_storage_textures, sizeof(*state->storage_textures)); if (!state->storage_textures) { SDL_DestroyGPURenderState(state); return NULL; } - SDL_memcpy(state->storage_textures, desc->storage_textures, desc->num_storage_textures * sizeof(*state->storage_textures)); - state->num_storage_textures = desc->num_storage_textures; + SDL_memcpy(state->storage_textures, createinfo->storage_textures, createinfo->num_storage_textures * sizeof(*state->storage_textures)); + state->num_storage_textures = createinfo->num_storage_textures; } - if (desc->num_storage_buffers > 0) { - state->storage_buffers = (SDL_GPUBuffer **)SDL_calloc(desc->num_storage_buffers, sizeof(*state->storage_buffers)); + if (createinfo->num_storage_buffers > 0) { + state->storage_buffers = (SDL_GPUBuffer **)SDL_calloc(createinfo->num_storage_buffers, sizeof(*state->storage_buffers)); if (!state->storage_buffers) { SDL_DestroyGPURenderState(state); return NULL; } - SDL_memcpy(state->storage_buffers, desc->storage_buffers, desc->num_storage_buffers * sizeof(*state->storage_buffers)); - state->num_storage_buffers = desc->num_storage_buffers; + SDL_memcpy(state->storage_buffers, createinfo->storage_buffers, createinfo->num_storage_buffers * sizeof(*state->storage_buffers)); + state->num_storage_buffers = createinfo->num_storage_buffers; } return state; @@ -6043,7 +6195,7 @@ bool SDL_SetGPURenderStateFragmentUniforms(SDL_GPURenderState *state, Uint32 slo return true; } -bool SDL_SetRenderGPUState(SDL_Renderer *renderer, SDL_GPURenderState *state) +bool SDL_SetGPURenderState(SDL_Renderer *renderer, SDL_GPURenderState *state) { CHECK_RENDERER_MAGIC(renderer, false); diff --git a/libs/SDL3/src/render/SDL_render_debug_font.h b/libs/SDL3/src/render/SDL_render_debug_font.h index ca296e8c..19963385 100644 --- a/libs/SDL3/src/render/SDL_render_debug_font.h +++ b/libs/SDL3/src/render/SDL_render_debug_font.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/render/SDL_render_unsupported.c b/libs/SDL3/src/render/SDL_render_unsupported.c index 5369fb44..f01e52ae 100644 --- a/libs/SDL3/src/render/SDL_render_unsupported.c +++ b/libs/SDL3/src/render/SDL_render_unsupported.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/render/SDL_sysrender.h b/libs/SDL3/src/render/SDL_sysrender.h index d42f5362..a1bb44a1 100644 --- a/libs/SDL3/src/render/SDL_sysrender.h +++ b/libs/SDL3/src/render/SDL_sysrender.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -70,6 +70,15 @@ typedef struct SDL_RenderViewState SDL_FPoint current_scale; // this is just `scale * logical_scale`, precalculated, since we use it a lot. } SDL_RenderViewState; +// Define the SDL texture palette structure +typedef struct SDL_TexturePalette +{ + int refcount; + Uint32 version; + Uint32 last_command_generation; // last command queue generation this palette was in. + void *internal; // Driver specific palette representation +} SDL_TexturePalette; + // Define the SDL texture structure struct SDL_Texture { @@ -81,6 +90,7 @@ struct SDL_Texture int refcount; /**< Application reference count, used when freeing texture */ // Private API definition + SDL_Renderer *renderer; SDL_Colorspace colorspace; // The colorspace of the texture float SDR_white_point; // The SDR white point for this content float HDR_headroom; // The HDR headroom needed by this content @@ -89,8 +99,10 @@ struct SDL_Texture SDL_ScaleMode scaleMode; // The texture scale mode SDL_FColor color; // Texture modulation values SDL_RenderViewState view; // Target texture view state - - SDL_Renderer *renderer; + SDL_Palette *public_palette; + SDL_TexturePalette *palette; + Uint32 palette_version; + SDL_Surface *palette_surface; // Support for formats not supported directly by the renderer SDL_Texture *native; @@ -233,6 +245,10 @@ struct SDL_Renderer void (*InvalidateCachedState)(SDL_Renderer *renderer); bool (*RunCommandQueue)(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize); + bool (*CreatePalette)(SDL_Renderer *renderer, SDL_TexturePalette *palette); + bool (*UpdatePalette)(SDL_Renderer *renderer, SDL_TexturePalette *palette, int ncolors, SDL_Color *colors); + void (*DestroyPalette)(SDL_Renderer *renderer, SDL_TexturePalette *palette); + bool (*ChangeTexturePalette)(SDL_Renderer *renderer, SDL_Texture *texture); bool (*UpdateTexture)(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch); @@ -269,6 +285,7 @@ struct SDL_Renderer SDL_PixelFormat *texture_formats; int num_texture_formats; bool software; + bool npot_texture_wrap_unsupported; // The window associated with the renderer SDL_Window *window; @@ -297,6 +314,9 @@ struct SDL_Renderer SDL_Texture *target; SDL_Mutex *target_mutex; + // The list of palettes + SDL_HashTable *palettes; + SDL_Colorspace output_colorspace; float SDR_white_point; float HDR_headroom; diff --git a/libs/SDL3/src/render/SDL_yuv_sw.c b/libs/SDL3/src/render/SDL_yuv_sw.c index abe9e162..a837870f 100644 --- a/libs/SDL3/src/render/SDL_yuv_sw.c +++ b/libs/SDL3/src/render/SDL_yuv_sw.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/render/SDL_yuv_sw_c.h b/libs/SDL3/src/render/SDL_yuv_sw_c.h index 807c44c5..12c79208 100644 --- a/libs/SDL3/src/render/SDL_yuv_sw_c.h +++ b/libs/SDL3/src/render/SDL_yuv_sw_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette.h b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette.h new file mode 100644 index 00000000..201c337c --- /dev/null +++ b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette.h @@ -0,0 +1,272 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// Parameters: +// +// sampler2D image; +// sampler1D palette; +// float4 texel_size; +// float texture_type; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// texture_type c0 1 +// texel_size c1 1 +// image s0 1 +// palette s1 1 +// + + ps_2_0 + def c2, -1, 255, 0.5, 0.00390625 + def c3, -2, 0, 0, 0 + def c4, 1, 0, 0, 1 + dcl v0 + dcl t0.xy + dcl_2d s0 + dcl_2d s1 + mov r0.xz, c2 + mad r1.x, t0.x, c1.z, r0.z + mad r1.y, t0.y, c1.w, r0.z + frc r0.yz, r1.zxyw + add r1.xy, -r0.yzxw, r1 + add r1.zw, r1.wzyx, -c2.z + add r1.xy, r1, c2.z + mul r1.xy, r1, c1 + mul r2.xy, r1.wzyx, c1 + mov r3.x, r2.x + mov r3.y, r1.y + mov r4.y, r2.y + mov r4.x, r1.x + texld r3, r3, s0 + texld r2, r2, s0 + texld r1, r1, s0 + texld r4, r4, s0 + texld r5, t0, s0 + mad r0.w, r3.x, c2.y, c2.z + mul r3.xy, r0.w, c2.w + mad r0.w, r2.x, c2.y, c2.z + mul r2.xy, r0.w, c2.w + mad r0.w, r1.x, c2.y, c2.z + mul r1.xy, r0.w, c2.w + mad r0.w, r4.x, c2.y, c2.z + mul r4.xy, r0.w, c2.w + mad r0.w, r5.x, c2.y, c2.z + mul r5.xy, r0.w, c2.w + texld r3, r3, s1 + texld r2, r2, s1 + texld r1, r1, s1 + texld r4, r4, s1 + texld r5, r5, s1 + lrp r6, r0.z, r3, r2 + lrp r2, r0.z, r1, r4 + lrp r1, r0.y, r2, r6 + mov r2.x, c0.x + add r0.y, r2.x, c3.x + mul r0.y, r0.y, r0.y + cmp r1, -r0.y, r1, c4 + add r0.x, r0.x, c0.x + mul r0.x, r0.x, r0.x + cmp r0, -r0.x, r5, r1 + mul r0, r0, v0 + mov oC0, r0 + +// approximately 45 instruction slots used (10 texture, 35 arithmetic) +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 67, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 223, 0, + 0, 0, 0, 2, 255, 255, + 4, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 216, 0, 0, 0, 108, 0, + 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 116, 0, + 0, 0, 0, 0, 0, 0, + 132, 0, 0, 0, 3, 0, + 1, 0, 1, 0, 0, 0, + 140, 0, 0, 0, 0, 0, + 0, 0, 156, 0, 0, 0, + 2, 0, 1, 0, 1, 0, + 0, 0, 168, 0, 0, 0, + 0, 0, 0, 0, 184, 0, + 0, 0, 2, 0, 0, 0, + 1, 0, 0, 0, 200, 0, + 0, 0, 0, 0, 0, 0, + 105, 109, 97, 103, 101, 0, + 171, 171, 4, 0, 12, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 97, 108, 101, 116, 116, + 101, 0, 4, 0, 11, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 116, 101, 120, 101, 108, 95, + 115, 105, 122, 101, 0, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 116, 101, + 120, 116, 117, 114, 101, 95, + 116, 121, 112, 101, 0, 171, + 171, 171, 0, 0, 3, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 115, 95, 50, 95, 48, + 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, + 49, 48, 46, 49, 0, 171, + 81, 0, 0, 5, 2, 0, + 15, 160, 0, 0, 128, 191, + 0, 0, 127, 67, 0, 0, + 0, 63, 0, 0, 128, 59, + 81, 0, 0, 5, 3, 0, + 15, 160, 0, 0, 0, 192, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 81, 0, 0, 5, 4, 0, + 15, 160, 0, 0, 128, 63, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 128, 63, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 144, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 3, 176, + 31, 0, 0, 2, 0, 0, + 0, 144, 0, 8, 15, 160, + 31, 0, 0, 2, 0, 0, + 0, 144, 1, 8, 15, 160, + 1, 0, 0, 2, 0, 0, + 5, 128, 2, 0, 228, 160, + 4, 0, 0, 4, 1, 0, + 1, 128, 0, 0, 0, 176, + 1, 0, 170, 160, 0, 0, + 170, 128, 4, 0, 0, 4, + 1, 0, 2, 128, 0, 0, + 85, 176, 1, 0, 255, 160, + 0, 0, 170, 128, 19, 0, + 0, 2, 0, 0, 6, 128, + 1, 0, 210, 128, 2, 0, + 0, 3, 1, 0, 3, 128, + 0, 0, 201, 129, 1, 0, + 228, 128, 2, 0, 0, 3, + 1, 0, 12, 128, 1, 0, + 27, 128, 2, 0, 170, 161, + 2, 0, 0, 3, 1, 0, + 3, 128, 1, 0, 228, 128, + 2, 0, 170, 160, 5, 0, + 0, 3, 1, 0, 3, 128, + 1, 0, 228, 128, 1, 0, + 228, 160, 5, 0, 0, 3, + 2, 0, 3, 128, 1, 0, + 27, 128, 1, 0, 228, 160, + 1, 0, 0, 2, 3, 0, + 1, 128, 2, 0, 0, 128, + 1, 0, 0, 2, 3, 0, + 2, 128, 1, 0, 85, 128, + 1, 0, 0, 2, 4, 0, + 2, 128, 2, 0, 85, 128, + 1, 0, 0, 2, 4, 0, + 1, 128, 1, 0, 0, 128, + 66, 0, 0, 3, 3, 0, + 15, 128, 3, 0, 228, 128, + 0, 8, 228, 160, 66, 0, + 0, 3, 2, 0, 15, 128, + 2, 0, 228, 128, 0, 8, + 228, 160, 66, 0, 0, 3, + 1, 0, 15, 128, 1, 0, + 228, 128, 0, 8, 228, 160, + 66, 0, 0, 3, 4, 0, + 15, 128, 4, 0, 228, 128, + 0, 8, 228, 160, 66, 0, + 0, 3, 5, 0, 15, 128, + 0, 0, 228, 176, 0, 8, + 228, 160, 4, 0, 0, 4, + 0, 0, 8, 128, 3, 0, + 0, 128, 2, 0, 85, 160, + 2, 0, 170, 160, 5, 0, + 0, 3, 3, 0, 3, 128, + 0, 0, 255, 128, 2, 0, + 255, 160, 4, 0, 0, 4, + 0, 0, 8, 128, 2, 0, + 0, 128, 2, 0, 85, 160, + 2, 0, 170, 160, 5, 0, + 0, 3, 2, 0, 3, 128, + 0, 0, 255, 128, 2, 0, + 255, 160, 4, 0, 0, 4, + 0, 0, 8, 128, 1, 0, + 0, 128, 2, 0, 85, 160, + 2, 0, 170, 160, 5, 0, + 0, 3, 1, 0, 3, 128, + 0, 0, 255, 128, 2, 0, + 255, 160, 4, 0, 0, 4, + 0, 0, 8, 128, 4, 0, + 0, 128, 2, 0, 85, 160, + 2, 0, 170, 160, 5, 0, + 0, 3, 4, 0, 3, 128, + 0, 0, 255, 128, 2, 0, + 255, 160, 4, 0, 0, 4, + 0, 0, 8, 128, 5, 0, + 0, 128, 2, 0, 85, 160, + 2, 0, 170, 160, 5, 0, + 0, 3, 5, 0, 3, 128, + 0, 0, 255, 128, 2, 0, + 255, 160, 66, 0, 0, 3, + 3, 0, 15, 128, 3, 0, + 228, 128, 1, 8, 228, 160, + 66, 0, 0, 3, 2, 0, + 15, 128, 2, 0, 228, 128, + 1, 8, 228, 160, 66, 0, + 0, 3, 1, 0, 15, 128, + 1, 0, 228, 128, 1, 8, + 228, 160, 66, 0, 0, 3, + 4, 0, 15, 128, 4, 0, + 228, 128, 1, 8, 228, 160, + 66, 0, 0, 3, 5, 0, + 15, 128, 5, 0, 228, 128, + 1, 8, 228, 160, 18, 0, + 0, 4, 6, 0, 15, 128, + 0, 0, 170, 128, 3, 0, + 228, 128, 2, 0, 228, 128, + 18, 0, 0, 4, 2, 0, + 15, 128, 0, 0, 170, 128, + 1, 0, 228, 128, 4, 0, + 228, 128, 18, 0, 0, 4, + 1, 0, 15, 128, 0, 0, + 85, 128, 2, 0, 228, 128, + 6, 0, 228, 128, 1, 0, + 0, 2, 2, 0, 1, 128, + 0, 0, 0, 160, 2, 0, + 0, 3, 0, 0, 2, 128, + 2, 0, 0, 128, 3, 0, + 0, 160, 5, 0, 0, 3, + 0, 0, 2, 128, 0, 0, + 85, 128, 0, 0, 85, 128, + 88, 0, 0, 4, 1, 0, + 15, 128, 0, 0, 85, 129, + 1, 0, 228, 128, 4, 0, + 228, 160, 2, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 0, 128, 0, 0, 0, 160, + 5, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 0, 0, 0, 128, 88, 0, + 0, 4, 0, 0, 15, 128, + 0, 0, 0, 129, 5, 0, + 228, 128, 1, 0, 228, 128, + 5, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 128, + 0, 0, 228, 144, 1, 0, + 0, 2, 0, 8, 15, 128, + 0, 0, 228, 128, 255, 255, + 0, 0 +}; diff --git a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette.hlsli b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette.hlsli new file mode 100644 index 00000000..5bd5a58e --- /dev/null +++ b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette.hlsli @@ -0,0 +1,49 @@ + +cbuffer Constants +{ + float4 texel_size; +}; + +uniform sampler2D image; +uniform sampler1D palette; + +struct PixelShaderInput +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +static const float TEXTURETYPE_NONE = 0; +static const float TEXTURETYPE_PALETTE_NEAREST = 1; +static const float TEXTURETYPE_PALETTE_LINEAR = 2; + +float4 SamplePaletteNearest(float2 uv) +{ + float index = tex2D(image, uv).r * 255; + return tex1D(palette, (index + 0.5) / 256); +} + +// Implementation with thanks from bgolus: +// https://discussions.unity.com/t/how-to-make-data-shader-support-bilinear-trilinear/598639/8 +float4 SamplePaletteLinear(float2 uv) +{ + // scale & offset uvs to integer values at texel centers + float2 uv_texels = uv * texel_size.zw + 0.5; + + // get uvs for the center of the 4 surrounding texels by flooring + float4 uv_min_max = float4((floor(uv_texels) - 0.5) * texel_size.xy, (floor(uv_texels) + 0.5) * texel_size.xy); + + // blend factor + float2 uv_frac = frac(uv_texels); + + // sample all 4 texels + float4 texelA = SamplePaletteNearest(uv_min_max.xy); + float4 texelB = SamplePaletteNearest(uv_min_max.xw); + float4 texelC = SamplePaletteNearest(uv_min_max.zy); + float4 texelD = SamplePaletteNearest(uv_min_max.zw); + + // bilinear interpolation + return lerp(lerp(texelA, texelB, uv_frac.y), lerp(texelC, texelD, uv_frac.y), uv_frac.x); +} + diff --git a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Linear.h b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Linear.h new file mode 100644 index 00000000..da73f860 --- /dev/null +++ b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Linear.h @@ -0,0 +1,209 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// Parameters: +// +// sampler2D image; +// sampler1D palette; +// float4 texel_size; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// texel_size c0 1 +// image s0 1 +// palette s1 1 +// + + ps_2_0 + def c1, 0.5, -0.5, 255, 0.00390625 + dcl t0.xy + dcl v0 + dcl_2d s0 + dcl_2d s1 + mov r0.w, c1.x + mad r0.x, t0.x, c0.z, r0.w + mad r0.y, t0.y, c0.w, r0.w + frc r0.zw, r0.wzyx + add r0.xy, -r0.wzyx, r0 + add r1.xy, r0, c1.y + add r0.xy, r0, c1.x + mul r0.xy, r0, c0 + mul r1.xy, r1, c0 + mov r2.x, r1.x + mov r2.y, r0.y + mov r3.y, r1.y + mov r3.x, r0.x + texld r2, r2, s0 + texld r1, r1, s0 + texld r4, r0, s0 + texld r3, r3, s0 + mad r0.x, r2.x, c1.z, c1.x + mul r0.xy, r0.x, c1.w + mad r1.x, r1.x, c1.z, c1.x + mul r1.xy, r1.x, c1.w + mad r1.z, r4.x, c1.z, c1.x + mul r2.xy, r1.z, c1.w + mad r1.z, r3.x, c1.z, c1.x + mul r3.xy, r1.z, c1.w + texld r4, r0, s1 + texld r1, r1, s1 + texld r2, r2, s1 + texld r3, r3, s1 + lrp r5, r0.z, r4, r1 + lrp r1, r0.z, r2, r3 + lrp r2, r0.w, r1, r5 + mul r0, r2, v0 + mov oC0, r0 + +// approximately 34 instruction slots used (8 texture, 26 arithmetic) +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 54, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 171, 0, + 0, 0, 0, 2, 255, 255, + 3, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 164, 0, 0, 0, 88, 0, + 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 96, 0, + 0, 0, 0, 0, 0, 0, + 112, 0, 0, 0, 3, 0, + 1, 0, 1, 0, 0, 0, + 120, 0, 0, 0, 0, 0, + 0, 0, 136, 0, 0, 0, + 2, 0, 0, 0, 1, 0, + 0, 0, 148, 0, 0, 0, + 0, 0, 0, 0, 105, 109, + 97, 103, 101, 0, 171, 171, + 4, 0, 12, 0, 1, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 112, 97, + 108, 101, 116, 116, 101, 0, + 4, 0, 11, 0, 1, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 116, 101, + 120, 101, 108, 95, 115, 105, + 122, 101, 0, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 112, 115, 95, 50, + 95, 48, 0, 77, 105, 99, + 114, 111, 115, 111, 102, 116, + 32, 40, 82, 41, 32, 72, + 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, + 111, 109, 112, 105, 108, 101, + 114, 32, 49, 48, 46, 49, + 0, 171, 81, 0, 0, 5, + 1, 0, 15, 160, 0, 0, + 0, 63, 0, 0, 0, 191, + 0, 0, 127, 67, 0, 0, + 128, 59, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 3, 176, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 0, 0, 0, 144, 0, 8, + 15, 160, 31, 0, 0, 2, + 0, 0, 0, 144, 1, 8, + 15, 160, 1, 0, 0, 2, + 0, 0, 8, 128, 1, 0, + 0, 160, 4, 0, 0, 4, + 0, 0, 1, 128, 0, 0, + 0, 176, 0, 0, 170, 160, + 0, 0, 255, 128, 4, 0, + 0, 4, 0, 0, 2, 128, + 0, 0, 85, 176, 0, 0, + 255, 160, 0, 0, 255, 128, + 19, 0, 0, 2, 0, 0, + 12, 128, 0, 0, 27, 128, + 2, 0, 0, 3, 0, 0, + 3, 128, 0, 0, 27, 129, + 0, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 3, 128, + 0, 0, 228, 128, 1, 0, + 85, 160, 2, 0, 0, 3, + 0, 0, 3, 128, 0, 0, + 228, 128, 1, 0, 0, 160, + 5, 0, 0, 3, 0, 0, + 3, 128, 0, 0, 228, 128, + 0, 0, 228, 160, 5, 0, + 0, 3, 1, 0, 3, 128, + 1, 0, 228, 128, 0, 0, + 228, 160, 1, 0, 0, 2, + 2, 0, 1, 128, 1, 0, + 0, 128, 1, 0, 0, 2, + 2, 0, 2, 128, 0, 0, + 85, 128, 1, 0, 0, 2, + 3, 0, 2, 128, 1, 0, + 85, 128, 1, 0, 0, 2, + 3, 0, 1, 128, 0, 0, + 0, 128, 66, 0, 0, 3, + 2, 0, 15, 128, 2, 0, + 228, 128, 0, 8, 228, 160, + 66, 0, 0, 3, 1, 0, + 15, 128, 1, 0, 228, 128, + 0, 8, 228, 160, 66, 0, + 0, 3, 4, 0, 15, 128, + 0, 0, 228, 128, 0, 8, + 228, 160, 66, 0, 0, 3, + 3, 0, 15, 128, 3, 0, + 228, 128, 0, 8, 228, 160, + 4, 0, 0, 4, 0, 0, + 1, 128, 2, 0, 0, 128, + 1, 0, 170, 160, 1, 0, + 0, 160, 5, 0, 0, 3, + 0, 0, 3, 128, 0, 0, + 0, 128, 1, 0, 255, 160, + 4, 0, 0, 4, 1, 0, + 1, 128, 1, 0, 0, 128, + 1, 0, 170, 160, 1, 0, + 0, 160, 5, 0, 0, 3, + 1, 0, 3, 128, 1, 0, + 0, 128, 1, 0, 255, 160, + 4, 0, 0, 4, 1, 0, + 4, 128, 4, 0, 0, 128, + 1, 0, 170, 160, 1, 0, + 0, 160, 5, 0, 0, 3, + 2, 0, 3, 128, 1, 0, + 170, 128, 1, 0, 255, 160, + 4, 0, 0, 4, 1, 0, + 4, 128, 3, 0, 0, 128, + 1, 0, 170, 160, 1, 0, + 0, 160, 5, 0, 0, 3, + 3, 0, 3, 128, 1, 0, + 170, 128, 1, 0, 255, 160, + 66, 0, 0, 3, 4, 0, + 15, 128, 0, 0, 228, 128, + 1, 8, 228, 160, 66, 0, + 0, 3, 1, 0, 15, 128, + 1, 0, 228, 128, 1, 8, + 228, 160, 66, 0, 0, 3, + 2, 0, 15, 128, 2, 0, + 228, 128, 1, 8, 228, 160, + 66, 0, 0, 3, 3, 0, + 15, 128, 3, 0, 228, 128, + 1, 8, 228, 160, 18, 0, + 0, 4, 5, 0, 15, 128, + 0, 0, 170, 128, 4, 0, + 228, 128, 1, 0, 228, 128, + 18, 0, 0, 4, 1, 0, + 15, 128, 0, 0, 170, 128, + 2, 0, 228, 128, 3, 0, + 228, 128, 18, 0, 0, 4, + 2, 0, 15, 128, 0, 0, + 255, 128, 1, 0, 228, 128, + 5, 0, 228, 128, 5, 0, + 0, 3, 0, 0, 15, 128, + 2, 0, 228, 128, 0, 0, + 228, 144, 1, 0, 0, 2, + 0, 8, 15, 128, 0, 0, + 228, 128, 255, 255, 0, 0 +}; diff --git a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Linear.hlsl b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Linear.hlsl new file mode 100644 index 00000000..63aeaa00 --- /dev/null +++ b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Linear.hlsl @@ -0,0 +1,7 @@ + +#include "D3D9_PixelShader_Palette.hlsli" + +float4 main(PixelShaderInput input) : SV_TARGET +{ + return SamplePaletteLinear(input.tex) * input.color; +} diff --git a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Nearest.h b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Nearest.h new file mode 100644 index 00000000..6a36bb75 --- /dev/null +++ b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Nearest.h @@ -0,0 +1,95 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// Parameters: +// +// sampler2D image; +// sampler1D palette; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// image s0 1 +// palette s1 1 +// + + ps_2_0 + def c0, 255, 0.5, 0.00390625, 0 + dcl t0.xy + dcl v0 + dcl_2d s0 + dcl_2d s1 + texld r0, t0, s0 + mad r0.x, r0.x, c0.x, c0.y + mul r0.xy, r0.x, c0.z + texld r0, r0, s1 + mul r0, r0, v0 + mov oC0, r0 + +// approximately 6 instruction slots used (2 texture, 4 arithmetic) +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 42, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 123, 0, + 0, 0, 0, 2, 255, 255, + 2, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 116, 0, 0, 0, 68, 0, + 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 76, 0, + 0, 0, 0, 0, 0, 0, + 92, 0, 0, 0, 3, 0, + 1, 0, 1, 0, 0, 0, + 100, 0, 0, 0, 0, 0, + 0, 0, 105, 109, 97, 103, + 101, 0, 171, 171, 4, 0, + 12, 0, 1, 0, 1, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 112, 97, 108, 101, + 116, 116, 101, 0, 4, 0, + 11, 0, 1, 0, 1, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 112, 115, 95, 50, + 95, 48, 0, 77, 105, 99, + 114, 111, 115, 111, 102, 116, + 32, 40, 82, 41, 32, 72, + 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, + 111, 109, 112, 105, 108, 101, + 114, 32, 49, 48, 46, 49, + 0, 171, 81, 0, 0, 5, + 0, 0, 15, 160, 0, 0, + 127, 67, 0, 0, 0, 63, + 0, 0, 128, 59, 0, 0, + 0, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 3, 176, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 0, 0, 0, 144, 0, 8, + 15, 160, 31, 0, 0, 2, + 0, 0, 0, 144, 1, 8, + 15, 160, 66, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 176, 0, 8, 228, 160, + 4, 0, 0, 4, 0, 0, + 1, 128, 0, 0, 0, 128, + 0, 0, 0, 160, 0, 0, + 85, 160, 5, 0, 0, 3, + 0, 0, 3, 128, 0, 0, + 0, 128, 0, 0, 170, 160, + 66, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 128, + 1, 8, 228, 160, 5, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 228, 128, 0, 0, + 228, 144, 1, 0, 0, 2, + 0, 8, 15, 128, 0, 0, + 228, 128, 255, 255, 0, 0 +}; diff --git a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Nearest.hlsl b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Nearest.hlsl new file mode 100644 index 00000000..6131f7bf --- /dev/null +++ b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_Palette_Nearest.hlsl @@ -0,0 +1,7 @@ + +#include "D3D9_PixelShader_Palette.hlsli" + +float4 main(PixelShaderInput input) : SV_TARGET +{ + return SamplePaletteNearest(input.tex) * input.color; +} diff --git a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_YUV.h b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_YUV.h index e6d91300..57a0a775 100644 --- a/libs/SDL3/src/render/direct3d/D3D9_PixelShader_YUV.h +++ b/libs/SDL3/src/render/direct3d/D3D9_PixelShader_YUV.h @@ -51,114 +51,114 @@ const BYTE g_ps20_main[] = { - 0, 2, 255, 255, 254, 255, - 97, 0, 67, 84, 65, 66, - 28, 0, 0, 0, 87, 1, - 0, 0, 0, 2, 255, 255, - 7, 0, 0, 0, 28, 0, - 0, 0, 0, 1, 0, 0, - 80, 1, 0, 0, 168, 0, - 0, 0, 2, 0, 3, 0, - 1, 0, 0, 0, 176, 0, - 0, 0, 0, 0, 0, 0, - 192, 0, 0, 0, 2, 0, - 2, 0, 1, 0, 0, 0, - 176, 0, 0, 0, 0, 0, - 0, 0, 199, 0, 0, 0, - 2, 0, 1, 0, 1, 0, - 0, 0, 176, 0, 0, 0, - 0, 0, 0, 0, 206, 0, - 0, 0, 2, 0, 0, 0, - 1, 0, 0, 0, 176, 0, - 0, 0, 0, 0, 0, 0, - 214, 0, 0, 0, 3, 0, - 1, 0, 1, 0, 0, 0, - 240, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, - 3, 0, 2, 0, 1, 0, - 0, 0, 24, 1, 0, 0, - 0, 0, 0, 0, 40, 1, - 0, 0, 3, 0, 0, 0, - 1, 0, 0, 0, 64, 1, - 0, 0, 0, 0, 0, 0, - 66, 99, 111, 101, 102, 102, - 0, 171, 1, 0, 3, 0, - 1, 0, 4, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 71, 99, 111, 101, 102, 102, - 0, 82, 99, 111, 101, 102, - 102, 0, 89, 111, 102, 102, - 115, 101, 116, 0, 116, 104, - 101, 83, 97, 109, 112, 108, - 101, 114, 43, 116, 104, 101, - 84, 101, 120, 116, 117, 114, - 101, 85, 0, 171, 171, 171, - 4, 0, 7, 0, 1, 0, - 4, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 116, 104, - 101, 83, 97, 109, 112, 108, - 101, 114, 43, 116, 104, 101, - 84, 101, 120, 116, 117, 114, - 101, 86, 0, 171, 4, 0, - 7, 0, 1, 0, 4, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 116, 104, 101, 83, - 97, 109, 112, 108, 101, 114, - 43, 116, 104, 101, 84, 101, - 120, 116, 117, 114, 101, 89, - 0, 171, 4, 0, 7, 0, - 1, 0, 4, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 112, 115, 95, 50, 95, 48, - 0, 77, 105, 99, 114, 111, - 115, 111, 102, 116, 32, 40, - 82, 41, 32, 72, 76, 83, - 76, 32, 83, 104, 97, 100, - 101, 114, 32, 67, 111, 109, - 112, 105, 108, 101, 114, 32, - 49, 48, 46, 49, 0, 171, - 81, 0, 0, 5, 4, 0, - 15, 160, 0, 0, 128, 63, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 31, 0, 0, 2, 0, 0, - 0, 128, 0, 0, 3, 176, - 31, 0, 0, 2, 0, 0, - 0, 128, 0, 0, 15, 144, - 31, 0, 0, 2, 0, 0, - 0, 144, 0, 8, 15, 160, - 31, 0, 0, 2, 0, 0, - 0, 144, 1, 8, 15, 160, - 31, 0, 0, 2, 0, 0, - 0, 144, 2, 8, 15, 160, - 66, 0, 0, 3, 0, 0, - 15, 128, 0, 0, 228, 176, - 0, 8, 228, 160, 66, 0, - 0, 3, 1, 0, 15, 128, - 0, 0, 228, 176, 1, 8, - 228, 160, 66, 0, 0, 3, - 2, 0, 15, 128, 0, 0, - 228, 176, 2, 8, 228, 160, - 1, 0, 0, 2, 0, 0, - 2, 128, 1, 0, 0, 128, - 1, 0, 0, 2, 0, 0, - 4, 128, 2, 0, 0, 128, - 2, 0, 0, 3, 0, 0, - 7, 128, 0, 0, 228, 128, - 0, 0, 228, 160, 8, 0, - 0, 3, 1, 0, 1, 128, - 0, 0, 228, 128, 1, 0, - 228, 160, 8, 0, 0, 3, - 1, 0, 2, 128, 0, 0, - 228, 128, 2, 0, 228, 160, - 8, 0, 0, 3, 1, 0, - 4, 128, 0, 0, 228, 128, - 3, 0, 228, 160, 1, 0, - 0, 2, 1, 0, 8, 128, - 4, 0, 0, 160, 5, 0, - 0, 3, 0, 0, 15, 128, - 1, 0, 228, 128, 0, 0, - 228, 144, 1, 0, 0, 2, - 0, 8, 15, 128, 0, 0, + 0, 2, 255, 255, 254, 255, + 97, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 87, 1, + 0, 0, 0, 2, 255, 255, + 7, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 80, 1, 0, 0, 168, 0, + 0, 0, 2, 0, 3, 0, + 1, 0, 0, 0, 176, 0, + 0, 0, 0, 0, 0, 0, + 192, 0, 0, 0, 2, 0, + 2, 0, 1, 0, 0, 0, + 176, 0, 0, 0, 0, 0, + 0, 0, 199, 0, 0, 0, + 2, 0, 1, 0, 1, 0, + 0, 0, 176, 0, 0, 0, + 0, 0, 0, 0, 206, 0, + 0, 0, 2, 0, 0, 0, + 1, 0, 0, 0, 176, 0, + 0, 0, 0, 0, 0, 0, + 214, 0, 0, 0, 3, 0, + 1, 0, 1, 0, 0, 0, + 240, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, + 3, 0, 2, 0, 1, 0, + 0, 0, 24, 1, 0, 0, + 0, 0, 0, 0, 40, 1, + 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 64, 1, + 0, 0, 0, 0, 0, 0, + 66, 99, 111, 101, 102, 102, + 0, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 71, 99, 111, 101, 102, 102, + 0, 82, 99, 111, 101, 102, + 102, 0, 89, 111, 102, 102, + 115, 101, 116, 0, 116, 104, + 101, 83, 97, 109, 112, 108, + 101, 114, 43, 116, 104, 101, + 84, 101, 120, 116, 117, 114, + 101, 85, 0, 171, 171, 171, + 4, 0, 7, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 116, 104, + 101, 83, 97, 109, 112, 108, + 101, 114, 43, 116, 104, 101, + 84, 101, 120, 116, 117, 114, + 101, 86, 0, 171, 4, 0, + 7, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 116, 104, 101, 83, + 97, 109, 112, 108, 101, 114, + 43, 116, 104, 101, 84, 101, + 120, 116, 117, 114, 101, 89, + 0, 171, 4, 0, 7, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 115, 95, 50, 95, 48, + 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, + 49, 48, 46, 49, 0, 171, + 81, 0, 0, 5, 4, 0, + 15, 160, 0, 0, 128, 63, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 3, 176, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 144, + 31, 0, 0, 2, 0, 0, + 0, 144, 0, 8, 15, 160, + 31, 0, 0, 2, 0, 0, + 0, 144, 1, 8, 15, 160, + 31, 0, 0, 2, 0, 0, + 0, 144, 2, 8, 15, 160, + 66, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 176, + 0, 8, 228, 160, 66, 0, + 0, 3, 1, 0, 15, 128, + 0, 0, 228, 176, 1, 8, + 228, 160, 66, 0, 0, 3, + 2, 0, 15, 128, 0, 0, + 228, 176, 2, 8, 228, 160, + 1, 0, 0, 2, 0, 0, + 2, 128, 1, 0, 0, 128, + 1, 0, 0, 2, 0, 0, + 4, 128, 2, 0, 0, 128, + 2, 0, 0, 3, 0, 0, + 7, 128, 0, 0, 228, 128, + 0, 0, 228, 160, 8, 0, + 0, 3, 1, 0, 1, 128, + 0, 0, 228, 128, 1, 0, + 228, 160, 8, 0, 0, 3, + 1, 0, 2, 128, 0, 0, + 228, 128, 2, 0, 228, 160, + 8, 0, 0, 3, 1, 0, + 4, 128, 0, 0, 228, 128, + 3, 0, 228, 160, 1, 0, + 0, 2, 1, 0, 8, 128, + 4, 0, 0, 160, 5, 0, + 0, 3, 0, 0, 15, 128, + 1, 0, 228, 128, 0, 0, + 228, 144, 1, 0, 0, 2, + 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0 }; diff --git a/libs/SDL3/src/render/direct3d/SDL_render_d3d.c b/libs/SDL3/src/render/direct3d/SDL_render_d3d.c index 5132a232..3a7b8af0 100644 --- a/libs/SDL3/src/render/direct3d/SDL_render_d3d.c +++ b/libs/SDL3/src/render/direct3d/SDL_render_d3d.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -46,8 +46,29 @@ typedef struct bool cliprect_dirty; D3D9_Shader shader; const float *shader_params; + bool texture_state_dirty; } D3D_DrawStateCache; +typedef struct +{ + bool dirty; + int w, h; + DWORD usage; + Uint32 format; + D3DFORMAT d3dfmt; + IDirect3DTexture9 *texture; + IDirect3DTexture9 *staging; +} D3D_TextureRep; + +struct D3D_PaletteData +{ + D3D_TextureRep texture; + + struct D3D_PaletteData *prev; + struct D3D_PaletteData *next; +}; +typedef struct D3D_PaletteData D3D_PaletteData; + // Direct3D renderer implementation typedef struct @@ -66,32 +87,21 @@ typedef struct IDirect3DSurface9 *defaultRenderTarget; IDirect3DSurface9 *currentRenderTarget; void *d3dxDLL; -#ifdef SDL_HAVE_YUV LPDIRECT3DPIXELSHADER9 shaders[NUM_SHADERS]; -#endif LPDIRECT3DVERTEXBUFFER9 vertexBuffers[8]; size_t vertexBufferSize[8]; int currentVertexBuffer; bool reportedVboProblem; D3D_DrawStateCache drawstate; + D3D_PaletteData *palettes; } D3D_RenderData; -typedef struct -{ - bool dirty; - int w, h; - DWORD usage; - Uint32 format; - D3DFORMAT d3dfmt; - IDirect3DTexture9 *texture; - IDirect3DTexture9 *staging; -} D3D_TextureRep; - typedef struct { D3D_TextureRep texture; - D3D9_Shader shader; + UINT shader_params_length; const float *shader_params; + float palette_shader_params[4]; #ifdef SDL_HAVE_YUV // YV12 texture support @@ -198,6 +208,7 @@ static D3DFORMAT PixelFormatToD3DFMT(Uint32 format) return D3DFMT_X8R8G8B8; case SDL_PIXELFORMAT_ARGB8888: return D3DFMT_A8R8G8B8; + case SDL_PIXELFORMAT_INDEX8: case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: case SDL_PIXELFORMAT_NV12: @@ -530,6 +541,99 @@ static void D3D_DestroyTextureRep(D3D_TextureRep *texture) } } +static bool D3D_CreatePalette(SDL_Renderer *renderer, SDL_TexturePalette *palette) +{ + D3D_RenderData *data = (D3D_RenderData *)renderer->internal; + D3D_PaletteData *palettedata = (D3D_PaletteData *)SDL_calloc(1, sizeof(*palettedata)); + if (!palettedata) { + return false; + } + palette->internal = palettedata; + + if (!D3D_CreateTextureRep(data->device, &palettedata->texture, 0, SDL_PIXELFORMAT_ARGB8888, D3DFMT_A8R8G8B8, 256, 1)) { + SDL_free(palettedata); + return false; + } + + // Keep a reference to the palette so we can restore the texture if we lose the D3D device + if (data->palettes) { + palettedata->next = data->palettes; + data->palettes->prev = palettedata; + } + data->palettes = palettedata; + return true; +} + +static bool D3D_UpdatePalette(SDL_Renderer *renderer, SDL_TexturePalette *palette, int ncolors, SDL_Color *colors) +{ + D3D_RenderData *data = (D3D_RenderData *)renderer->internal; + D3D_PaletteData *palettedata = (D3D_PaletteData *)palette->internal; + bool retval; + + Uint32 *entries = SDL_stack_alloc(Uint32, ncolors); + if (!entries) { + return false; + } + for (int i = 0; i < ncolors; ++i) { + entries[i] = (colors[i].a << 24) | (colors[i].r << 16) | (colors[i].g << 8) | colors[i].b; + } + retval = D3D_UpdateTextureRep(data->device, &palettedata->texture, 0, 0, ncolors, 1, entries, ncolors * sizeof(*entries)); + SDL_stack_free(entries); + return retval; +} + +static void D3D_DestroyPalette(SDL_Renderer *renderer, SDL_TexturePalette *palette) +{ + D3D_RenderData *data = (D3D_RenderData *)renderer->internal; + D3D_PaletteData *palettedata = (D3D_PaletteData *)palette->internal; + + if (palettedata) { + D3D_DestroyTextureRep(&palettedata->texture); + + if (data->palettes == palettedata) { + data->palettes = palettedata->next; + } else if (palettedata->prev) { + palettedata->prev->next = palettedata->next; + } + if (palettedata->next) { + palettedata->next->prev = palettedata->prev; + } + SDL_free(palettedata); + } +} + +static bool D3D_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, + const SDL_Rect *rect, const void *pixels, int pitch) +{ + D3D_RenderData *data = (D3D_RenderData *)renderer->internal; + D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal; + + if (!texturedata) { + return SDL_SetError("Texture is not currently available"); + } + + if (!D3D_UpdateTextureRep(data->device, &texturedata->texture, rect->x, rect->y, rect->w, rect->h, pixels, pitch)) { + return false; + } +#ifdef SDL_HAVE_YUV + if (texturedata->yuv) { + // Skip to the correct offset into the next texture + pixels = (const void *)((const Uint8 *)pixels + rect->h * pitch); + + if (!D3D_UpdateTextureRep(data->device, texture->format == SDL_PIXELFORMAT_YV12 ? &texturedata->vtexture : &texturedata->utexture, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, pixels, (pitch + 1) / 2)) { + return false; + } + + // Skip to the correct offset into the next texture + pixels = (const void *)((const Uint8 *)pixels + ((rect->h + 1) / 2) * ((pitch + 1) / 2)); + if (!D3D_UpdateTextureRep(data->device, texture->format == SDL_PIXELFORMAT_YV12 ? &texturedata->utexture : &texturedata->vtexture, rect->x / 2, (rect->y + 1) / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, pixels, (pitch + 1) / 2)) { + return false; + } + } +#endif + return true; +} + static bool D3D_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { D3D_RenderData *data = (D3D_RenderData *)renderer->internal; @@ -552,6 +656,14 @@ static bool D3D_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ if (!D3D_CreateTextureRep(data->device, &texturedata->texture, usage, texture->format, PixelFormatToD3DFMT(texture->format), texture->w, texture->h)) { return false; } + if (texture->format == SDL_PIXELFORMAT_INDEX8) { + texturedata->shader_params_length = 1; // The palette shader takes 1 float4 parameters + texturedata->shader_params = texturedata->palette_shader_params; + texturedata->palette_shader_params[0] = 1.0f / texture->w; + texturedata->palette_shader_params[1] = 1.0f / texture->h; + texturedata->palette_shader_params[2] = texture->w; + texturedata->palette_shader_params[3] = texture->h; + } #ifdef SDL_HAVE_YUV if (texture->format == SDL_PIXELFORMAT_YV12 || texture->format == SDL_PIXELFORMAT_IYUV) { @@ -565,7 +677,7 @@ static bool D3D_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ return false; } - texturedata->shader = SHADER_YUV; + texturedata->shader_params_length = 4; // The YUV shader takes 4 float4 parameters texturedata->shader_params = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8); if (texturedata->shader_params == NULL) { return SDL_SetError("Unsupported YUV colorspace"); @@ -601,38 +713,6 @@ static bool D3D_RecreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) return true; } -static bool D3D_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, - const SDL_Rect *rect, const void *pixels, int pitch) -{ - D3D_RenderData *data = (D3D_RenderData *)renderer->internal; - D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal; - - if (!texturedata) { - return SDL_SetError("Texture is not currently available"); - } - - if (!D3D_UpdateTextureRep(data->device, &texturedata->texture, rect->x, rect->y, rect->w, rect->h, pixels, pitch)) { - return false; - } -#ifdef SDL_HAVE_YUV - if (texturedata->yuv) { - // Skip to the correct offset into the next texture - pixels = (const void *)((const Uint8 *)pixels + rect->h * pitch); - - if (!D3D_UpdateTextureRep(data->device, texture->format == SDL_PIXELFORMAT_YV12 ? &texturedata->vtexture : &texturedata->utexture, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, pixels, (pitch + 1) / 2)) { - return false; - } - - // Skip to the correct offset into the next texture - pixels = (const void *)((const Uint8 *)pixels + ((rect->h + 1) / 2) * ((pitch + 1) / 2)); - if (!D3D_UpdateTextureRep(data->device, texture->format == SDL_PIXELFORMAT_YV12 ? &texturedata->utexture : &texturedata->vtexture, rect->x / 2, (rect->y + 1) / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, pixels, (pitch + 1) / 2)) { - return false; - } - } -#endif - return true; -} - #ifdef SDL_HAVE_YUV static bool D3D_UpdateTextureYUV(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, @@ -736,6 +816,7 @@ static void D3D_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture) data->drawstate.texture = NULL; data->drawstate.shader = SHADER_NONE; data->drawstate.shader_params = NULL; + data->drawstate.texture_state_dirty = false; IDirect3DDevice9_SetPixelShader(data->device, NULL); IDirect3DDevice9_SetTexture(data->device, 0, NULL); } @@ -966,7 +1047,7 @@ static void UpdateTextureAddressMode(D3D_RenderData *data, SDL_TextureAddressMod } } -static bool SetupTextureState(D3D_RenderData *data, SDL_Texture *texture, D3D9_Shader *shader, const float **shader_params) +static bool SetupTextureState(D3D_RenderData *data, SDL_Texture *texture, SDL_ScaleMode scale_mode, D3D9_Shader *shader, const float **shader_params) { D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal; @@ -974,12 +1055,28 @@ static bool SetupTextureState(D3D_RenderData *data, SDL_Texture *texture, D3D9_S return SDL_SetError("Texture is not currently available"); } - *shader = texturedata->shader; + if (texture->format == SDL_PIXELFORMAT_INDEX8) { + if (scale_mode == SDL_SCALEMODE_LINEAR) { + *shader = SHADER_PALETTE_LINEAR; + } else { + *shader = SHADER_PALETTE_NEAREST; + } +#ifdef SDL_HAVE_YUV + } else if (texturedata->yuv) { + *shader = SHADER_YUV; +#endif // SDL_HAVE_YUV + } *shader_params = texturedata->shader_params; if (!BindTextureRep(data->device, &texturedata->texture, 0)) { return false; } + if (texture->palette) { + D3D_PaletteData *palette = (D3D_PaletteData *)texture->palette->internal; + if (!BindTextureRep(data->device, &palette->texture, 1)) { + return false; + } + } #ifdef SDL_HAVE_YUV if (texturedata->yuv) { if (!BindTextureRep(data->device, &texturedata->utexture, 1)) { @@ -998,11 +1095,9 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd) SDL_Texture *texture = cmd->data.draw.texture; const SDL_BlendMode blend = cmd->data.draw.blend; - if (texture != data->drawstate.texture) { -#ifdef SDL_HAVE_YUV + if (texture != data->drawstate.texture || data->drawstate.texture_state_dirty) { D3D_TextureData *oldtexturedata = data->drawstate.texture ? (D3D_TextureData *)data->drawstate.texture->internal : NULL; D3D_TextureData *newtexturedata = texture ? (D3D_TextureData *)texture->internal : NULL; -#endif D3D9_Shader shader = SHADER_NONE; const float *shader_params = NULL; @@ -1010,18 +1105,21 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd) if (!texture) { IDirect3DDevice9_SetTexture(data->device, 0, NULL); } + if ((!newtexturedata || !texture->palette) && + ((oldtexturedata && data->drawstate.texture->palette) || data->drawstate.texture_state_dirty)) { + IDirect3DDevice9_SetTexture(data->device, 1, NULL); + } #ifdef SDL_HAVE_YUV - if ((!newtexturedata || !newtexturedata->yuv) && (oldtexturedata && oldtexturedata->yuv)) { + if ((!newtexturedata || !newtexturedata->yuv) && ((oldtexturedata && oldtexturedata->yuv) || data->drawstate.texture_state_dirty)) { IDirect3DDevice9_SetTexture(data->device, 1, NULL); IDirect3DDevice9_SetTexture(data->device, 2, NULL); } #endif - if (texture && !SetupTextureState(data, texture, &shader, &shader_params)) { + if (texture && !SetupTextureState(data, texture, cmd->data.draw.texture_scale_mode, &shader, &shader_params)) { return false; } -#ifdef SDL_HAVE_YUV - if (shader != data->drawstate.shader) { + if (shader != data->drawstate.shader || data->drawstate.texture_state_dirty) { const HRESULT result = IDirect3DDevice9_SetPixelShader(data->device, data->shaders[shader]); if (FAILED(result)) { return D3D_SetError("IDirect3DDevice9_SetPixelShader()", result); @@ -1029,7 +1127,7 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd) data->drawstate.shader = shader; } - if (shader_params != data->drawstate.shader_params) { + if (shader_params != data->drawstate.shader_params || data->drawstate.texture_state_dirty) { if (shader_params) { const UINT shader_params_length = 4; // The YUV shader takes 4 float4 parameters const HRESULT result = IDirect3DDevice9_SetPixelShaderConstantF(data->device, 0, shader_params, shader_params_length); @@ -1039,13 +1137,17 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd) } data->drawstate.shader_params = shader_params; } -#endif // SDL_HAVE_YUV data->drawstate.texture = texture; + data->drawstate.texture_state_dirty = false; } else if (texture) { D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal; if (texturedata) { UpdateDirtyTexture(data->device, &texturedata->texture); + if (texture->palette) { + D3D_PaletteData *palettedata = (D3D_PaletteData *)texture->palette->internal; + UpdateDirtyTexture(data->device, &palettedata->texture); + } #ifdef SDL_HAVE_YUV if (texturedata->yuv) { UpdateDirtyTexture(data->device, &texturedata->utexture); @@ -1151,6 +1253,7 @@ static void D3D_InvalidateCachedState(SDL_Renderer *renderer) data->drawstate.texture = NULL; data->drawstate.shader = SHADER_NONE; data->drawstate.shader_params = NULL; + data->drawstate.texture_state_dirty = true; } static bool D3D_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) @@ -1214,10 +1317,7 @@ static bool D3D_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, switch (cmd->command) { case SDL_RENDERCMD_SETDRAWCOLOR: { - /* currently this is sent with each vertex, but if we move to - shaders, we can put this in a uniform here and reduce vertex - buffer bandwidth */ - break; + break; // this isn't currently used in this render backend. } case SDL_RENDERCMD_SETVIEWPORT: @@ -1278,43 +1378,74 @@ static bool D3D_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, break; } - case SDL_RENDERCMD_DRAW_POINTS: - { - const size_t count = cmd->data.draw.count; - const size_t first = cmd->data.draw.first; - SetDrawState(data, cmd); - if (vbo) { - IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)(first / sizeof(Vertex)), (UINT)count); - } else { - const Vertex *verts = (Vertex *)(((Uint8 *)vertices) + first); - IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, (UINT)count, verts, sizeof(Vertex)); - } - break; - } - case SDL_RENDERCMD_DRAW_LINES: { - const size_t count = cmd->data.draw.count; + size_t count = cmd->data.draw.count; const size_t first = cmd->data.draw.first; + const size_t start = first / sizeof(Vertex); const Vertex *verts = (Vertex *)(((Uint8 *)vertices) + first); - /* DirectX 9 has the same line rasterization semantics as GDI, - so we need to close the endpoint of the line with a second draw call. - NOLINTNEXTLINE(clang-analyzer-core.NullDereference): FIXME: Can verts truly not be NULL ? */ - const bool close_endpoint = ((count == 2) || (verts[0].x != verts[count - 1].x) || (verts[0].y != verts[count - 1].y)); - SetDrawState(data, cmd); - if (vbo) { - IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_LINESTRIP, (UINT)(first / sizeof(Vertex)), (UINT)(count - 1)); - if (close_endpoint) { - IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)((first / sizeof(Vertex)) + (count - 1)), 1); + // Add the final point in the line + size_t line_start = 0; + size_t line_end = line_start + count - 1; + if (count == 2 || verts[line_start].x != verts[line_end].x || verts[line_start].y != verts[line_end].y) { + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)(start + line_end), 1); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, 1, &verts[line_end], sizeof(Vertex)); + } + } + + if (count > 2) { + // joined lines cannot be grouped + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_LINESTRIP, (UINT)start, (UINT)(count - 1)); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_LINESTRIP, (UINT)(count - 1), verts, sizeof(Vertex)); } } else { - IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_LINESTRIP, (UINT)(count - 1), verts, sizeof(Vertex)); - if (close_endpoint) { - IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, 1, &verts[count - 1], sizeof(Vertex)); + // let's group non joined lines + SDL_RenderCommand *finalcmd = cmd; + SDL_RenderCommand *nextcmd; + SDL_BlendMode thisblend = cmd->data.draw.blend; + + for (nextcmd = cmd->next; nextcmd; nextcmd = nextcmd->next) { + const SDL_RenderCommandType nextcmdtype = nextcmd->command; + if (nextcmdtype != SDL_RENDERCMD_DRAW_LINES) { + if (nextcmdtype == SDL_RENDERCMD_SETDRAWCOLOR) { + // The vertex data has the draw color built in, ignore this + continue; + } + break; // can't go any further on this draw call, different render command up next. + } else if (nextcmd->data.draw.count != 2) { + break; // can't go any further on this draw call, those are joined lines + } else if (nextcmd->data.draw.blend != thisblend) { + break; // can't go any further on this draw call, different blendmode copy up next. + } else { + finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command. + + // Add the final point in the line + line_start = count; + line_end = line_start + nextcmd->data.draw.count - 1; + if (verts[line_start].x != verts[line_end].x || verts[line_start].y != verts[line_end].y) { + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)(start + line_end), 1); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, 1, &verts[line_end], sizeof(Vertex)); + } + } + count += nextcmd->data.draw.count; + } } + + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_LINELIST, (UINT)start, (UINT)(count - 1)); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_LINELIST, (UINT)(count - 1), verts, sizeof(Vertex)); + } + cmd = finalcmd; // skip any copy commands we just combined in here. } break; } @@ -1328,17 +1459,59 @@ static bool D3D_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, case SDL_RENDERCMD_COPY_EX: // unused break; + case SDL_RENDERCMD_DRAW_POINTS: case SDL_RENDERCMD_GEOMETRY: { - const size_t count = cmd->data.draw.count; + /* as long as we have the same copy command in a row, with the + same texture, we can combine them all into a single draw call. */ + SDL_Texture *thistexture = cmd->data.draw.texture; + SDL_BlendMode thisblend = cmd->data.draw.blend; + SDL_ScaleMode thisscalemode = cmd->data.draw.texture_scale_mode; + SDL_TextureAddressMode thisaddressmode_u = cmd->data.draw.texture_address_mode_u; + SDL_TextureAddressMode thisaddressmode_v = cmd->data.draw.texture_address_mode_v; + const SDL_RenderCommandType thiscmdtype = cmd->command; + SDL_RenderCommand *finalcmd = cmd; + SDL_RenderCommand *nextcmd; + size_t count = cmd->data.draw.count; const size_t first = cmd->data.draw.first; - SetDrawState(data, cmd); - if (vbo) { - IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_TRIANGLELIST, (UINT)(first / sizeof(Vertex)), (UINT)count / 3); - } else { - const Vertex *verts = (Vertex *)(((Uint8 *)vertices) + first); - IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_TRIANGLELIST, (UINT)count / 3, verts, sizeof(Vertex)); + const size_t start = first / sizeof(Vertex); + const Vertex *verts = (Vertex *)(((Uint8 *)vertices) + first); + for (nextcmd = cmd->next; nextcmd; nextcmd = nextcmd->next) { + const SDL_RenderCommandType nextcmdtype = nextcmd->command; + if (nextcmdtype != thiscmdtype) { + if (nextcmdtype == SDL_RENDERCMD_SETDRAWCOLOR) { + // The vertex data has the draw color built in, ignore this + continue; + } + break; // can't go any further on this draw call, different render command up next. + } else if (nextcmd->data.draw.texture != thistexture || + nextcmd->data.draw.texture_scale_mode != thisscalemode || + nextcmd->data.draw.texture_address_mode_u != thisaddressmode_u || + nextcmd->data.draw.texture_address_mode_v != thisaddressmode_v || + nextcmd->data.draw.blend != thisblend) { + break; // can't go any further on this draw call, different texture/blendmode copy up next. + } else { + finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command. + count += nextcmd->data.draw.count; + } } + + SetDrawState(data, cmd); + + if (thiscmdtype == SDL_RENDERCMD_GEOMETRY) { + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_TRIANGLELIST, (UINT)start, (UINT)count / 3); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_TRIANGLELIST, (UINT)count / 3, verts, sizeof(Vertex)); + } + } else { + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)start, (UINT)count); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, (UINT)count, verts, sizeof(Vertex)); + } + } + cmd = finalcmd; // skip any copy commands we just combined in here. break; } @@ -1443,10 +1616,14 @@ static void D3D_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture) renderdata->drawstate.texture = NULL; renderdata->drawstate.shader = SHADER_NONE; renderdata->drawstate.shader_params = NULL; + renderdata->drawstate.texture_state_dirty = false; IDirect3DDevice9_SetPixelShader(renderdata->device, NULL); IDirect3DDevice9_SetTexture(renderdata->device, 0, NULL); + if (texture->palette) { + IDirect3DDevice9_SetTexture(renderdata->device, 1, NULL); + } #ifdef SDL_HAVE_YUV - if (data->yuv) { + if (data && data->yuv) { IDirect3DDevice9_SetTexture(renderdata->device, 1, NULL); IDirect3DDevice9_SetTexture(renderdata->device, 2, NULL); } @@ -1474,6 +1651,9 @@ static void D3D_DestroyRenderer(SDL_Renderer *renderer) if (data) { int i; + // Make sure the palettes have been freed + SDL_assert(!data->palettes); + // Release the render target if (data->defaultRenderTarget) { IDirect3DSurface9_Release(data->defaultRenderTarget); @@ -1483,14 +1663,12 @@ static void D3D_DestroyRenderer(SDL_Renderer *renderer) IDirect3DSurface9_Release(data->currentRenderTarget); data->currentRenderTarget = NULL; } -#ifdef SDL_HAVE_YUV for (i = 0; i < SDL_arraysize(data->shaders); ++i) { if (data->shaders[i]) { IDirect3DPixelShader9_Release(data->shaders[i]); data->shaders[i] = NULL; } } -#endif // Release all vertex buffers for (i = 0; i < SDL_arraysize(data->vertexBuffers); ++i) { if (data->vertexBuffers[i]) { @@ -1516,6 +1694,7 @@ static bool D3D_Reset(SDL_Renderer *renderer) const Float4X4 d3dmatrix = MatrixIdentity(); HRESULT result; SDL_Texture *texture; + D3D_PaletteData *palette; int i; // Cancel any scene that we've started @@ -1543,6 +1722,11 @@ static bool D3D_Reset(SDL_Renderer *renderer) } } + // Release all palettes + for (palette = data->palettes; palette; palette = palette->next) { + D3D_RecreateTextureRep(data->device, &palette->texture); + } + // Release all vertex buffers for (i = 0; i < SDL_arraysize(data->vertexBuffers); ++i) { if (data->vertexBuffers[i]) { @@ -1667,6 +1851,9 @@ static bool D3D_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P renderer->WindowEvent = D3D_WindowEvent; renderer->SupportsBlendMode = D3D_SupportsBlendMode; + renderer->CreatePalette = D3D_CreatePalette; + renderer->UpdatePalette = D3D_UpdatePalette; + renderer->DestroyPalette = D3D_DestroyPalette; renderer->CreateTexture = D3D_CreateTexture; renderer->UpdateTexture = D3D_UpdateTexture; #ifdef SDL_HAVE_YUV @@ -1731,6 +1918,13 @@ static bool D3D_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P } else { device_flags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING; } + if (caps.TextureCaps & D3DPTEXTURECAPS_POW2) { + if (caps.TextureCaps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL) { + renderer->npot_texture_wrap_unsupported = true; + } else { + return SDL_SetError("Non-power-of-two textures are not supported"); + } + } if (SDL_GetHintBoolean(SDL_HINT_RENDER_DIRECT3D_THREADSAFE, false)) { device_flags |= D3DCREATE_MULTITHREADED; @@ -1771,19 +1965,21 @@ static bool D3D_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P // Set up parameters for rendering D3D_InitRenderState(data); + for (int i = SHADER_NONE + 1; i < SDL_arraysize(data->shaders); ++i) { + result = D3D9_CreatePixelShader(data->device, (D3D9_Shader)i, &data->shaders[i]); + if (FAILED(result)) { + D3D_SetError("CreatePixelShader()", result); + } + } + if (caps.MaxSimultaneousTextures >= 2 && + data->shaders[SHADER_PALETTE_NEAREST] && + data->shaders[SHADER_PALETTE_LINEAR]) { + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_INDEX8); + } #ifdef SDL_HAVE_YUV - if (caps.MaxSimultaneousTextures >= 3) { - int i; - for (i = SHADER_NONE + 1; i < SDL_arraysize(data->shaders); ++i) { - result = D3D9_CreatePixelShader(data->device, (D3D9_Shader)i, &data->shaders[i]); - if (FAILED(result)) { - D3D_SetError("CreatePixelShader()", result); - } - } - if (data->shaders[SHADER_YUV]) { - SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_YV12); - SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_IYUV); - } + if (caps.MaxSimultaneousTextures >= 3 && data->shaders[SHADER_YUV]) { + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_YV12); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_IYUV); } #endif diff --git a/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.c b/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.c index 9b1b4b51..d2611ed0 100644 --- a/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.c +++ b/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -30,12 +30,22 @@ // The shaders here were compiled with compile_shaders.bat +#define g_ps20_main D3D9_PixelShader_Palette_Nearest +#include "D3D9_PixelShader_Palette_Nearest.h" +#undef g_ps20_main + +#define g_ps20_main D3D9_PixelShader_Palette_Linear +#include "D3D9_PixelShader_Palette_Linear.h" +#undef g_ps20_main + #define g_ps20_main D3D9_PixelShader_YUV #include "D3D9_PixelShader_YUV.h" #undef g_ps20_main static const BYTE *D3D9_shaders[] = { NULL, + D3D9_PixelShader_Palette_Nearest, + D3D9_PixelShader_Palette_Linear, D3D9_PixelShader_YUV }; SDL_COMPILE_TIME_ASSERT(D3D9_shaders, SDL_arraysize(D3D9_shaders) == NUM_SHADERS); diff --git a/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.h b/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.h index 90ef7a9b..c6fba5c9 100644 --- a/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.h +++ b/libs/SDL3/src/render/direct3d/SDL_shaders_d3d.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -25,6 +25,8 @@ typedef enum { SHADER_NONE, + SHADER_PALETTE_NEAREST, + SHADER_PALETTE_LINEAR, SHADER_YUV, NUM_SHADERS } D3D9_Shader; diff --git a/libs/SDL3/src/render/direct3d/compile_shaders.bat b/libs/SDL3/src/render/direct3d/compile_shaders.bat index 81d513e2..fb375367 100644 --- a/libs/SDL3/src/render/direct3d/compile_shaders.bat +++ b/libs/SDL3/src/render/direct3d/compile_shaders.bat @@ -1 +1,3 @@ +fxc /T ps_2_0 /Fh D3D9_PixelShader_Palette_Nearest.h D3D9_PixelShader_Palette_Nearest.hlsl +fxc /T ps_2_0 /Fh D3D9_PixelShader_Palette_Linear.h D3D9_PixelShader_Palette_Linear.hlsl fxc /T ps_2_0 /Fh D3D9_PixelShader_YUV.h D3D9_PixelShader_YUV.hlsl diff --git a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Advanced.h b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Advanced.h index 59c90ae6..1697e57d 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Advanced.h +++ b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Advanced.h @@ -30,6 +30,7 @@ // Name Type Format Dim HLSL Bind Count // ------------------------------ ---------- ------- ----------- -------------- ------ // sampler0 sampler NA NA s0 1 +// sampler1 sampler NA NA s1 1 // texture0 texture float4 2d t0 1 // texture1 texture float4 2d t1 1 // texture2 texture float4 2d t2 1 @@ -56,13 +57,14 @@ ps_5_0 dcl_globalFlags refactoringAllowed dcl_constantbuffer CB0[7], immediateIndexed dcl_sampler s0, mode_default +dcl_sampler s1, mode_default dcl_resource_texture2d (float,float,float,float) t0 dcl_resource_texture2d (float,float,float,float) t1 dcl_resource_texture2d (float,float,float,float) t2 dcl_input_ps linear v1.xy dcl_input_ps linear v2.xyzw dcl_output o0.xyzw -dcl_temps 7 +dcl_temps 8 eq r0.xyzw, cb0[0].yzzz, l(0.000000, 3.000000, 2.000000, 1.000000) if_nz r0.x mov r1.xyzw, l(1.000000,1.000000,1.000000,1.000000) @@ -97,37 +99,132 @@ else else eq r0.x, cb0[0].y, l(3.000000) if_nz r0.x - sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 - sample_indexable(texture2d)(float,float,float,float) r2.yz, v1.xyxx, t1.zxyw, s0 - add r2.xyz, r2.xyzx, cb0[3].xyzx - dp3 r1.x, r2.xyzx, cb0[4].xyzx - dp3 r1.y, r2.xyzx, cb0[5].xyzx - dp3 r1.z, r2.xyzx, cb0[6].xyzx + sample_indexable(texture2d)(float,float,float,float) r0.x, v1.xyxx, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r2.x, r0.x, l(0.003906) + mov r2.y, l(0.500000) + sample_indexable(texture2d)(float,float,float,float) r1.xyzw, r2.xyxx, t1.xyzw, s1 else eq r0.x, cb0[0].y, l(4.000000) if_nz r0.x - sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 - sample_indexable(texture2d)(float,float,float,float) r2.yz, v1.xyxx, t1.zyxw, s0 - add r2.xyz, r2.xyzx, cb0[3].xyzx - dp3 r1.x, r2.xyzx, cb0[4].xyzx - dp3 r1.y, r2.xyzx, cb0[5].xyzx - dp3 r1.z, r2.xyzx, cb0[6].xyzx + mad r2.xy, v1.xyxx, cb0[1].zwzz, l(0.500000, 0.500000, 0.000000, 0.000000) + round_ni r3.xyzw, r2.xyxy + add r3.xyzw, r3.xyzw, l(-0.500000, -0.500000, 0.500000, 0.500000) + mul r3.xyzw, r3.xyzw, cb0[1].xyxy + frc r2.xy, r2.xyxx + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.xyxx, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r4.x, r0.x, l(0.003906) + mov r4.yw, l(0,0.500000,0,0.500000) + sample_indexable(texture2d)(float,float,float,float) r5.xyzw, r4.xyxx, t1.xyzw, s1 + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.xwxx, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r4.z, r0.x, l(0.003906) + sample_indexable(texture2d)(float,float,float,float) r4.xyzw, r4.zwzz, t1.xyzw, s1 + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.zyzz, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r6.x, r0.x, l(0.003906) + mov r6.yw, l(0,0.500000,0,0.500000) + sample_indexable(texture2d)(float,float,float,float) r7.xyzw, r6.xyxx, t1.xyzw, s1 + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.zwzz, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r6.z, r0.x, l(0.003906) + sample_indexable(texture2d)(float,float,float,float) r3.xyzw, r6.zwzz, t1.xyzw, s1 + add r4.xyzw, -r5.xyzw, r4.xyzw + mad r4.xyzw, r2.yyyy, r4.xyzw, r5.xyzw + add r3.xyzw, -r7.xyzw, r3.xyzw + mad r3.xyzw, r2.yyyy, r3.xyzw, r7.xyzw + add r3.xyzw, -r4.xyzw, r3.xyzw + mad r1.xyzw, r2.xxxx, r3.xyzw, r4.xyzw else eq r0.x, cb0[0].y, l(5.000000) if_nz r0.x - sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 - sample_indexable(texture2d)(float,float,float,float) r2.y, v1.xyxx, t1.yxzw, s0 - sample_indexable(texture2d)(float,float,float,float) r2.z, v1.xyxx, t2.yzxw, s0 - add r2.xyz, r2.xyzx, cb0[3].xyzx - dp3 r1.x, r2.xyzx, cb0[4].xyzx - dp3 r1.y, r2.xyzx, cb0[5].xyzx - dp3 r1.z, r2.xyzx, cb0[6].xyzx + deriv_rtx_coarse r2.xy, v1.xyxx + deriv_rty_coarse r2.zw, v1.xxxy + add r2.xy, |r2.zwzz|, |r2.xyxx| + mul r2.xy, r2.xyxx, cb0[1].zwzz + max r2.xy, r2.xyxx, l(0.000010, 0.000010, 0.000000, 0.000000) + min r2.xy, r2.xyxx, l(1.000000, 1.000000, 0.000000, 0.000000) + mul r2.zw, r2.xxxy, l(0.000000, 0.000000, 0.500000, 0.500000) + mad r2.zw, v1.xxxy, cb0[1].zzzw, -r2.zzzw + add r2.xy, -r2.xyxx, l(1.000000, 1.000000, 0.000000, 0.000000) + frc r3.xy, r2.zwzz + add r3.zw, -r2.xxxy, l(0.000000, 0.000000, 1.000000, 1.000000) + add r2.xy, -r2.xyxx, r3.xyxx + div r3.xy, l(1.000000, 1.000000, 1.000000, 1.000000), r3.zwzz + mul_sat r2.xy, r2.xyxx, r3.xyxx + mad r3.xy, r2.xyxx, l(-2.000000, -2.000000, 0.000000, 0.000000), l(3.000000, 3.000000, 0.000000, 0.000000) + mul r2.xy, r2.xyxx, r2.xyxx + round_ni r2.zw, r2.zzzw + mad r2.xy, r3.xyxx, r2.xyxx, r2.zwzz + add r2.xy, r2.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) + mul r2.xy, r2.xyxx, cb0[1].xyxx + mad r2.xy, r2.xyxx, cb0[1].zwzz, l(0.500000, 0.500000, 0.000000, 0.000000) + round_ni r3.xyzw, r2.xyxy + add r3.xyzw, r3.xyzw, l(-0.500000, -0.500000, 0.500000, 0.500000) + mul r3.xyzw, r3.xyzw, cb0[1].xyxy + frc r2.xy, r2.xyxx + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.xyxx, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r4.x, r0.x, l(0.003906) + mov r4.yw, l(0,0.500000,0,0.500000) + sample_indexable(texture2d)(float,float,float,float) r5.xyzw, r4.xyxx, t1.xyzw, s1 + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.xwxx, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r4.z, r0.x, l(0.003906) + sample_indexable(texture2d)(float,float,float,float) r4.xyzw, r4.zwzz, t1.xyzw, s1 + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.zyzz, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r6.x, r0.x, l(0.003906) + mov r6.yw, l(0,0.500000,0,0.500000) + sample_indexable(texture2d)(float,float,float,float) r7.xyzw, r6.xyxx, t1.xyzw, s1 + sample_indexable(texture2d)(float,float,float,float) r0.x, r3.zwzz, t0.xyzw, s0 + mad r0.x, r0.x, l(255.000000), l(0.500000) + mul r6.z, r0.x, l(0.003906) + sample_indexable(texture2d)(float,float,float,float) r3.xyzw, r6.zwzz, t1.xyzw, s1 + add r4.xyzw, -r5.xyzw, r4.xyzw + mad r4.xyzw, r2.yyyy, r4.xyzw, r5.xyzw + add r3.xyzw, -r7.xyzw, r3.xyzw + mad r3.xyzw, r2.yyyy, r3.xyzw, r7.xyzw + add r3.xyzw, -r4.xyzw, r3.xyzw + mad r1.xyzw, r2.xxxx, r3.xyzw, r4.xyzw else - mov r1.xyz, l(1.000000,0,0,0) + eq r0.x, cb0[0].y, l(6.000000) + if_nz r0.x + sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 + sample_indexable(texture2d)(float,float,float,float) r2.yz, v1.xyxx, t1.zxyw, s0 + add r2.xyz, r2.xyzx, cb0[3].xyzx + dp3 r1.x, r2.xyzx, cb0[4].xyzx + dp3 r1.y, r2.xyzx, cb0[5].xyzx + dp3 r1.z, r2.xyzx, cb0[6].xyzx + else + eq r0.x, cb0[0].y, l(7.000000) + if_nz r0.x + sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 + sample_indexable(texture2d)(float,float,float,float) r2.yz, v1.xyxx, t1.zyxw, s0 + add r2.xyz, r2.xyzx, cb0[3].xyzx + dp3 r1.x, r2.xyzx, cb0[4].xyzx + dp3 r1.y, r2.xyzx, cb0[5].xyzx + dp3 r1.z, r2.xyzx, cb0[6].xyzx + else + eq r0.x, cb0[0].y, l(8.000000) + if_nz r0.x + sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 + sample_indexable(texture2d)(float,float,float,float) r2.y, v1.xyxx, t1.yxzw, s0 + sample_indexable(texture2d)(float,float,float,float) r2.z, v1.xyxx, t2.yzxw, s0 + add r2.xyz, r2.xyzx, cb0[3].xyzx + dp3 r1.x, r2.xyzx, cb0[4].xyzx + dp3 r1.y, r2.xyzx, cb0[5].xyzx + dp3 r1.z, r2.xyzx, cb0[6].xyzx + else + mov r1.xyz, l(1.000000,0,1.000000,0) + endif + endif + endif + mov r1.w, l(1.000000) endif endif endif - mov r1.w, l(1.000000) endif endif endif @@ -214,425 +311,949 @@ else endif mul o0.xyzw, r1.xyzw, v2.xyzw ret -// Approximately 151 instruction slots used +// Approximately 246 instruction slots used #endif const BYTE g_main[] = { - 68, 88, 66, 67, 211, 185, - 112, 94, 11, 90, 193, 206, - 105, 12, 109, 71, 22, 113, - 149, 88, 1, 0, 0, 0, - 152, 23, 0, 0, 5, 0, + 68, 88, 66, 67, 218, 34, + 85, 97, 34, 23, 186, 108, + 107, 10, 84, 63, 204, 132, + 47, 109, 1, 0, 0, 0, + 236, 35, 0, 0, 5, 0, 0, 0, 52, 0, 0, 0, - 128, 4, 0, 0, 244, 4, - 0, 0, 40, 5, 0, 0, - 252, 22, 0, 0, 82, 68, - 69, 70, 68, 4, 0, 0, - 1, 0, 0, 0, 12, 1, - 0, 0, 5, 0, 0, 0, + 168, 4, 0, 0, 28, 5, + 0, 0, 80, 5, 0, 0, + 80, 35, 0, 0, 82, 68, + 69, 70, 108, 4, 0, 0, + 1, 0, 0, 0, 52, 1, + 0, 0, 6, 0, 0, 0, 60, 0, 0, 0, 0, 5, 255, 255, 0, 1, 0, 0, - 28, 4, 0, 0, 82, 68, + 68, 4, 0, 0, 82, 68, 49, 49, 60, 0, 0, 0, 24, 0, 0, 0, 32, 0, 0, 0, 40, 0, 0, 0, 36, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, - 220, 0, 0, 0, 3, 0, + 252, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, - 0, 0, 229, 0, 0, 0, - 2, 0, 0, 0, 5, 0, - 0, 0, 4, 0, 0, 0, - 255, 255, 255, 255, 0, 0, + 0, 0, 5, 1, 0, 0, + 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 13, 0, 0, 0, 238, 0, + 1, 0, 0, 0, 14, 1, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, - 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, 0, - 247, 0, 0, 0, 2, 0, + 23, 1, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, - 255, 255, 2, 0, 0, 0, + 255, 255, 1, 0, 0, 0, 1, 0, 0, 0, 13, 0, - 0, 0, 0, 1, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, + 0, 0, 32, 1, 0, 0, + 2, 0, 0, 0, 5, 0, + 0, 0, 4, 0, 0, 0, + 255, 255, 255, 255, 2, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 115, 97, - 109, 112, 108, 101, 114, 48, - 0, 116, 101, 120, 116, 117, - 114, 101, 48, 0, 116, 101, - 120, 116, 117, 114, 101, 49, - 0, 116, 101, 120, 116, 117, - 114, 101, 50, 0, 67, 111, - 110, 115, 116, 97, 110, 116, - 115, 0, 171, 171, 0, 1, - 0, 0, 13, 0, 0, 0, - 36, 1, 0, 0, 112, 0, + 13, 0, 0, 0, 41, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 44, 3, 0, 0, 0, 0, 0, 0, - 4, 0, 0, 0, 2, 0, - 0, 0, 64, 3, 0, 0, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 100, 3, 0, 0, - 4, 0, 0, 0, 4, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, + 115, 97, 109, 112, 108, 101, + 114, 48, 0, 115, 97, 109, + 112, 108, 101, 114, 49, 0, + 116, 101, 120, 116, 117, 114, + 101, 48, 0, 116, 101, 120, + 116, 117, 114, 101, 49, 0, + 116, 101, 120, 116, 117, 114, + 101, 50, 0, 67, 111, 110, + 115, 116, 97, 110, 116, 115, + 0, 171, 41, 1, 0, 0, + 13, 0, 0, 0, 76, 1, + 0, 0, 112, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 84, 3, 0, 0, + 0, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, - 64, 3, 0, 0, 0, 0, + 104, 3, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, - 113, 3, 0, 0, 8, 0, + 140, 3, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 2, 0, 0, 0, 64, 3, + 2, 0, 0, 0, 104, 3, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 124, 3, - 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 153, 3, + 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 2, 0, - 0, 0, 64, 3, 0, 0, + 0, 0, 104, 3, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, - 0, 0, 136, 3, 0, 0, - 16, 0, 0, 0, 16, 0, + 0, 0, 164, 3, 0, 0, + 12, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, - 156, 3, 0, 0, 0, 0, + 104, 3, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, - 192, 3, 0, 0, 32, 0, - 0, 0, 4, 0, 0, 0, - 2, 0, 0, 0, 64, 3, - 0, 0, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 207, 3, - 0, 0, 36, 0, 0, 0, - 4, 0, 0, 0, 2, 0, - 0, 0, 64, 3, 0, 0, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 223, 3, 0, 0, - 40, 0, 0, 0, 4, 0, - 0, 0, 2, 0, 0, 0, - 64, 3, 0, 0, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 239, 3, 0, 0, 44, 0, - 0, 0, 4, 0, 0, 0, - 2, 0, 0, 0, 64, 3, - 0, 0, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 255, 3, - 0, 0, 48, 0, 0, 0, - 16, 0, 0, 0, 2, 0, - 0, 0, 156, 3, 0, 0, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 7, 4, 0, 0, - 64, 0, 0, 0, 16, 0, - 0, 0, 2, 0, 0, 0, - 156, 3, 0, 0, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 14, 4, 0, 0, 80, 0, + 176, 3, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, - 2, 0, 0, 0, 156, 3, + 2, 0, 0, 0, 196, 3, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 21, 4, - 0, 0, 96, 0, 0, 0, - 16, 0, 0, 0, 2, 0, - 0, 0, 156, 3, 0, 0, + 0, 0, 0, 0, 232, 3, + 0, 0, 32, 0, 0, 0, + 4, 0, 0, 0, 2, 0, + 0, 0, 104, 3, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, - 0, 0, 115, 99, 82, 71, - 66, 95, 111, 117, 116, 112, - 117, 116, 0, 102, 108, 111, - 97, 116, 0, 171, 0, 0, - 3, 0, 1, 0, 1, 0, + 0, 0, 247, 3, 0, 0, + 36, 0, 0, 0, 4, 0, + 0, 0, 2, 0, 0, 0, + 104, 3, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 7, 4, 0, 0, 40, 0, + 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 104, 3, + 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 23, 4, + 0, 0, 44, 0, 0, 0, + 4, 0, 0, 0, 2, 0, + 0, 0, 104, 3, 0, 0, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 39, 4, 0, 0, + 48, 0, 0, 0, 16, 0, + 0, 0, 2, 0, 0, 0, + 196, 3, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 47, 4, 0, 0, 64, 0, + 0, 0, 16, 0, 0, 0, + 2, 0, 0, 0, 196, 3, + 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 54, 4, + 0, 0, 80, 0, 0, 0, + 16, 0, 0, 0, 2, 0, + 0, 0, 196, 3, 0, 0, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 61, 4, 0, 0, + 96, 0, 0, 0, 16, 0, + 0, 0, 2, 0, 0, 0, + 196, 3, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 115, 99, 82, 71, 66, 95, + 111, 117, 116, 112, 117, 116, + 0, 102, 108, 111, 97, 116, + 0, 171, 0, 0, 3, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 97, 3, + 0, 0, 116, 101, 120, 116, + 117, 114, 101, 95, 116, 121, + 112, 101, 0, 105, 110, 112, + 117, 116, 95, 116, 121, 112, + 101, 0, 99, 111, 108, 111, + 114, 95, 115, 99, 97, 108, + 101, 0, 116, 101, 120, 101, + 108, 95, 115, 105, 122, 101, + 0, 102, 108, 111, 97, 116, + 52, 0, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 57, 3, 0, 0, 116, 101, - 120, 116, 117, 114, 101, 95, - 116, 121, 112, 101, 0, 105, - 110, 112, 117, 116, 95, 116, - 121, 112, 101, 0, 99, 111, - 108, 111, 114, 95, 115, 99, - 97, 108, 101, 0, 116, 101, - 120, 101, 108, 95, 115, 105, - 122, 101, 0, 102, 108, 111, - 97, 116, 52, 0, 171, 171, - 1, 0, 3, 0, 1, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 147, 3, 0, 0, - 116, 111, 110, 101, 109, 97, - 112, 95, 109, 101, 116, 104, - 111, 100, 0, 116, 111, 110, - 101, 109, 97, 112, 95, 102, - 97, 99, 116, 111, 114, 49, + 187, 3, 0, 0, 116, 111, + 110, 101, 109, 97, 112, 95, + 109, 101, 116, 104, 111, 100, 0, 116, 111, 110, 101, 109, 97, 112, 95, 102, 97, 99, - 116, 111, 114, 50, 0, 115, - 100, 114, 95, 119, 104, 105, - 116, 101, 95, 112, 111, 105, - 110, 116, 0, 89, 111, 102, - 102, 115, 101, 116, 0, 82, + 116, 111, 114, 49, 0, 116, + 111, 110, 101, 109, 97, 112, + 95, 102, 97, 99, 116, 111, + 114, 50, 0, 115, 100, 114, + 95, 119, 104, 105, 116, 101, + 95, 112, 111, 105, 110, 116, + 0, 89, 111, 102, 102, 115, + 101, 116, 0, 82, 99, 111, + 101, 102, 102, 0, 71, 99, + 111, 101, 102, 102, 0, 66, 99, 111, 101, 102, 102, 0, - 71, 99, 111, 101, 102, 102, - 0, 66, 99, 111, 101, 102, - 102, 0, 77, 105, 99, 114, - 111, 115, 111, 102, 116, 32, - 40, 82, 41, 32, 72, 76, - 83, 76, 32, 83, 104, 97, - 100, 101, 114, 32, 67, 111, - 109, 112, 105, 108, 101, 114, - 32, 49, 48, 46, 49, 0, - 73, 83, 71, 78, 108, 0, - 0, 0, 3, 0, 0, 0, - 8, 0, 0, 0, 80, 0, - 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 3, 0, - 0, 0, 0, 0, 0, 0, - 15, 0, 0, 0, 92, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 3, 0, - 0, 0, 1, 0, 0, 0, - 3, 3, 0, 0, 101, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 3, 0, - 0, 0, 2, 0, 0, 0, - 15, 15, 0, 0, 83, 86, - 95, 80, 79, 83, 73, 84, - 73, 79, 78, 0, 84, 69, - 88, 67, 79, 79, 82, 68, - 0, 67, 79, 76, 79, 82, - 0, 171, 79, 83, 71, 78, - 44, 0, 0, 0, 1, 0, - 0, 0, 8, 0, 0, 0, - 32, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 0, 0, - 0, 0, 15, 0, 0, 0, - 83, 86, 95, 84, 65, 82, - 71, 69, 84, 0, 171, 171, - 83, 72, 69, 88, 204, 17, + 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, + 41, 32, 72, 76, 83, 76, + 32, 83, 104, 97, 100, 101, + 114, 32, 67, 111, 109, 112, + 105, 108, 101, 114, 32, 49, + 48, 46, 49, 0, 73, 83, + 71, 78, 108, 0, 0, 0, + 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, - 115, 4, 0, 0, 106, 8, - 0, 1, 89, 0, 0, 4, - 70, 142, 32, 0, 0, 0, - 0, 0, 7, 0, 0, 0, - 90, 0, 0, 3, 0, 96, - 16, 0, 0, 0, 0, 0, - 88, 24, 0, 4, 0, 112, - 16, 0, 0, 0, 0, 0, - 85, 85, 0, 0, 88, 24, + 0, 0, 0, 0, 1, 0, + 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 15, 0, + 0, 0, 92, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 3, 3, + 0, 0, 101, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 2, 0, 0, 0, 15, 15, + 0, 0, 83, 86, 95, 80, + 79, 83, 73, 84, 73, 79, + 78, 0, 84, 69, 88, 67, + 79, 79, 82, 68, 0, 67, + 79, 76, 79, 82, 0, 171, + 79, 83, 71, 78, 44, 0, + 0, 0, 1, 0, 0, 0, + 8, 0, 0, 0, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 83, 86, + 95, 84, 65, 82, 71, 69, + 84, 0, 171, 171, 83, 72, + 69, 88, 248, 29, 0, 0, + 80, 0, 0, 0, 126, 7, + 0, 0, 106, 8, 0, 1, + 89, 0, 0, 4, 70, 142, + 32, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 90, 0, + 0, 3, 0, 96, 16, 0, + 0, 0, 0, 0, 90, 0, + 0, 3, 0, 96, 16, 0, + 1, 0, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, - 1, 0, 0, 0, 85, 85, + 0, 0, 0, 0, 85, 85, 0, 0, 88, 24, 0, 4, - 0, 112, 16, 0, 2, 0, + 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, - 98, 16, 0, 3, 50, 16, - 16, 0, 1, 0, 0, 0, - 98, 16, 0, 3, 242, 16, + 88, 24, 0, 4, 0, 112, 16, 0, 2, 0, 0, 0, - 101, 0, 0, 3, 242, 32, + 85, 85, 0, 0, 98, 16, + 0, 3, 50, 16, 16, 0, + 1, 0, 0, 0, 98, 16, + 0, 3, 242, 16, 16, 0, + 2, 0, 0, 0, 101, 0, + 0, 3, 242, 32, 16, 0, + 0, 0, 0, 0, 104, 0, + 0, 2, 8, 0, 0, 0, + 24, 0, 0, 11, 242, 0, 16, 0, 0, 0, 0, 0, - 104, 0, 0, 2, 7, 0, - 0, 0, 24, 0, 0, 11, - 242, 0, 16, 0, 0, 0, - 0, 0, 150, 138, 32, 0, + 150, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 64, 64, + 0, 0, 0, 64, 0, 0, + 128, 63, 31, 0, 4, 3, + 10, 0, 16, 0, 0, 0, + 0, 0, 54, 0, 0, 8, + 242, 0, 16, 0, 1, 0, 0, 0, 2, 64, 0, 0, - 0, 0, 0, 0, 0, 0, - 64, 64, 0, 0, 0, 64, - 0, 0, 128, 63, 31, 0, - 4, 3, 10, 0, 16, 0, - 0, 0, 0, 0, 54, 0, - 0, 8, 242, 0, 16, 0, - 1, 0, 0, 0, 2, 64, - 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, + 0, 0, 128, 63, 18, 0, + 0, 1, 24, 0, 0, 8, + 18, 0, 16, 0, 0, 0, + 0, 0, 26, 128, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 128, 63, 31, 0, + 4, 3, 10, 0, 16, 0, + 0, 0, 0, 0, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 242, 0, + 16, 0, 1, 0, 0, 0, + 70, 16, 16, 0, 1, 0, + 0, 0, 70, 126, 16, 0, + 0, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, 18, 0, 0, 1, 24, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 26, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 0, 64, + 31, 0, 4, 3, 10, 0, + 16, 0, 0, 0, 0, 0, + 122, 0, 0, 5, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 16, 16, 0, 1, 0, + 0, 0, 124, 0, 0, 5, + 194, 0, 16, 0, 2, 0, + 0, 0, 6, 20, 16, 0, + 1, 0, 0, 0, 0, 0, + 0, 9, 50, 0, 16, 0, + 3, 0, 0, 0, 230, 10, + 16, 128, 129, 0, 0, 0, + 2, 0, 0, 0, 70, 0, + 16, 128, 129, 0, 0, 0, + 2, 0, 0, 0, 56, 0, + 0, 8, 50, 0, 16, 0, + 3, 0, 0, 0, 70, 0, + 16, 0, 3, 0, 0, 0, + 230, 138, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 52, 0, 0, 10, 50, 0, + 16, 0, 3, 0, 0, 0, + 70, 0, 16, 0, 3, 0, + 0, 0, 2, 64, 0, 0, + 172, 197, 39, 55, 172, 197, + 39, 55, 0, 0, 0, 0, + 0, 0, 0, 0, 51, 0, + 0, 10, 50, 0, 16, 0, + 3, 0, 0, 0, 70, 0, + 16, 0, 3, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 128, 63, 0, 0, 128, 63, + 0, 0, 0, 0, 0, 0, + 0, 0, 56, 0, 0, 10, + 194, 0, 16, 0, 3, 0, + 0, 0, 6, 4, 16, 0, + 3, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 63, 0, 0, 0, 63, + 50, 0, 0, 11, 194, 0, + 16, 0, 3, 0, 0, 0, + 6, 20, 16, 0, 1, 0, + 0, 0, 166, 142, 32, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 166, 14, 16, 128, + 65, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 11, + 50, 0, 16, 0, 3, 0, + 0, 0, 70, 0, 16, 128, + 65, 0, 0, 0, 3, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 26, 0, + 0, 5, 50, 0, 16, 0, + 4, 0, 0, 0, 230, 10, + 16, 0, 3, 0, 0, 0, + 0, 0, 0, 11, 194, 0, + 16, 0, 4, 0, 0, 0, + 6, 4, 16, 128, 65, 0, + 0, 0, 3, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 0, 8, + 50, 0, 16, 0, 3, 0, + 0, 0, 70, 0, 16, 128, + 65, 0, 0, 0, 3, 0, + 0, 0, 70, 0, 16, 0, + 4, 0, 0, 0, 14, 0, + 0, 10, 50, 0, 16, 0, + 4, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 128, 63, + 230, 10, 16, 0, 4, 0, + 0, 0, 56, 32, 0, 7, + 50, 0, 16, 0, 3, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 70, 0, + 16, 0, 4, 0, 0, 0, + 50, 0, 0, 15, 50, 0, + 16, 0, 4, 0, 0, 0, + 70, 0, 16, 0, 3, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 192, 0, 0, + 0, 192, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 64, 64, + 0, 0, 64, 64, 0, 0, + 0, 0, 0, 0, 0, 0, + 56, 0, 0, 7, 50, 0, + 16, 0, 3, 0, 0, 0, + 70, 0, 16, 0, 3, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 65, 0, + 0, 5, 194, 0, 16, 0, + 3, 0, 0, 0, 166, 14, + 16, 0, 3, 0, 0, 0, + 50, 0, 0, 9, 50, 0, + 16, 0, 3, 0, 0, 0, + 70, 0, 16, 0, 4, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 230, 10, + 16, 0, 3, 0, 0, 0, + 0, 0, 0, 10, 50, 0, + 16, 0, 3, 0, 0, 0, + 70, 0, 16, 0, 3, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 63, 0, 0, + 0, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 56, 0, + 0, 8, 50, 0, 16, 0, + 3, 0, 0, 0, 70, 0, + 16, 0, 3, 0, 0, 0, + 70, 128, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 73, 0, 0, 143, 194, 0, + 0, 128, 67, 85, 21, 0, + 242, 0, 16, 0, 1, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 70, 0, 16, 0, + 2, 0, 0, 0, 230, 10, + 16, 0, 2, 0, 0, 0, + 18, 0, 0, 1, 24, 0, + 0, 8, 18, 0, 16, 0, + 0, 0, 0, 0, 26, 128, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 64, 64, 31, 0, 4, 3, 10, 0, 16, 0, 0, 0, 0, 0, 69, 0, 0, 139, 194, 0, 0, 128, 67, 85, 21, 0, - 242, 0, 16, 0, 1, 0, + 18, 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, - 0, 0, 18, 0, 0, 1, - 24, 0, 0, 8, 18, 0, - 16, 0, 0, 0, 0, 0, - 26, 128, 32, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 64, 0, 0, 0, 0, - 0, 64, 31, 0, 4, 3, - 10, 0, 16, 0, 0, 0, - 0, 0, 122, 0, 0, 5, - 50, 0, 16, 0, 2, 0, - 0, 0, 70, 16, 16, 0, - 1, 0, 0, 0, 124, 0, - 0, 5, 194, 0, 16, 0, - 2, 0, 0, 0, 6, 20, - 16, 0, 1, 0, 0, 0, - 0, 0, 0, 9, 50, 0, - 16, 0, 3, 0, 0, 0, - 230, 10, 16, 128, 129, 0, - 0, 0, 2, 0, 0, 0, - 70, 0, 16, 128, 129, 0, - 0, 0, 2, 0, 0, 0, - 56, 0, 0, 8, 50, 0, - 16, 0, 3, 0, 0, 0, - 70, 0, 16, 0, 3, 0, - 0, 0, 230, 138, 32, 0, - 0, 0, 0, 0, 1, 0, - 0, 0, 52, 0, 0, 10, - 50, 0, 16, 0, 3, 0, - 0, 0, 70, 0, 16, 0, - 3, 0, 0, 0, 2, 64, - 0, 0, 172, 197, 39, 55, - 172, 197, 39, 55, 0, 0, - 0, 0, 0, 0, 0, 0, - 51, 0, 0, 10, 50, 0, - 16, 0, 3, 0, 0, 0, - 70, 0, 16, 0, 3, 0, - 0, 0, 2, 64, 0, 0, - 0, 0, 128, 63, 0, 0, - 128, 63, 0, 0, 0, 0, - 0, 0, 0, 0, 56, 0, - 0, 10, 194, 0, 16, 0, - 3, 0, 0, 0, 6, 4, - 16, 0, 3, 0, 0, 0, - 2, 64, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 63, 0, 0, - 0, 63, 50, 0, 0, 11, - 194, 0, 16, 0, 3, 0, - 0, 0, 6, 20, 16, 0, - 1, 0, 0, 0, 166, 142, - 32, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 166, 14, - 16, 128, 65, 0, 0, 0, - 3, 0, 0, 0, 0, 0, - 0, 11, 50, 0, 16, 0, - 3, 0, 0, 0, 70, 0, - 16, 128, 65, 0, 0, 0, - 3, 0, 0, 0, 2, 64, - 0, 0, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, - 0, 0, 0, 0, 0, 0, - 26, 0, 0, 5, 50, 0, - 16, 0, 4, 0, 0, 0, - 230, 10, 16, 0, 3, 0, - 0, 0, 0, 0, 0, 11, - 194, 0, 16, 0, 4, 0, - 0, 0, 6, 4, 16, 128, - 65, 0, 0, 0, 3, 0, - 0, 0, 2, 64, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, - 0, 8, 50, 0, 16, 0, - 3, 0, 0, 0, 70, 0, - 16, 128, 65, 0, 0, 0, - 3, 0, 0, 0, 70, 0, - 16, 0, 4, 0, 0, 0, - 14, 0, 0, 10, 50, 0, - 16, 0, 4, 0, 0, 0, - 2, 64, 0, 0, 0, 0, - 128, 63, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, - 128, 63, 230, 10, 16, 0, - 4, 0, 0, 0, 56, 32, - 0, 7, 50, 0, 16, 0, - 3, 0, 0, 0, 70, 0, - 16, 0, 3, 0, 0, 0, - 70, 0, 16, 0, 4, 0, - 0, 0, 50, 0, 0, 15, - 50, 0, 16, 0, 4, 0, - 0, 0, 70, 0, 16, 0, - 3, 0, 0, 0, 2, 64, - 0, 0, 0, 0, 0, 192, - 0, 0, 0, 192, 0, 0, - 0, 0, 0, 0, 0, 0, - 2, 64, 0, 0, 0, 0, - 64, 64, 0, 0, 64, 64, - 0, 0, 0, 0, 0, 0, - 0, 0, 56, 0, 0, 7, - 50, 0, 16, 0, 3, 0, - 0, 0, 70, 0, 16, 0, - 3, 0, 0, 0, 70, 0, - 16, 0, 3, 0, 0, 0, - 65, 0, 0, 5, 194, 0, - 16, 0, 3, 0, 0, 0, - 166, 14, 16, 0, 3, 0, 0, 0, 50, 0, 0, 9, - 50, 0, 16, 0, 3, 0, - 0, 0, 70, 0, 16, 0, - 4, 0, 0, 0, 70, 0, - 16, 0, 3, 0, 0, 0, - 230, 10, 16, 0, 3, 0, - 0, 0, 0, 0, 0, 10, - 50, 0, 16, 0, 3, 0, - 0, 0, 70, 0, 16, 0, - 3, 0, 0, 0, 2, 64, - 0, 0, 0, 0, 0, 63, - 0, 0, 0, 63, 0, 0, - 0, 0, 0, 0, 0, 0, - 56, 0, 0, 8, 50, 0, - 16, 0, 3, 0, 0, 0, - 70, 0, 16, 0, 3, 0, - 0, 0, 70, 128, 32, 0, - 0, 0, 0, 0, 1, 0, - 0, 0, 73, 0, 0, 143, + 18, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 127, 67, + 1, 64, 0, 0, 0, 0, + 0, 63, 56, 0, 0, 7, + 18, 0, 16, 0, 2, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 59, + 54, 0, 0, 5, 34, 0, + 16, 0, 2, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 63, 69, 0, 0, 139, 194, 0, 0, 128, 67, 85, 21, 0, 242, 0, 16, 0, 1, 0, 0, 0, 70, 0, + 16, 0, 2, 0, 0, 0, + 70, 126, 16, 0, 1, 0, + 0, 0, 0, 96, 16, 0, + 1, 0, 0, 0, 18, 0, + 0, 1, 24, 0, 0, 8, + 18, 0, 16, 0, 0, 0, + 0, 0, 26, 128, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 128, 64, 31, 0, + 4, 3, 10, 0, 16, 0, + 0, 0, 0, 0, 50, 0, + 0, 13, 50, 0, 16, 0, + 2, 0, 0, 0, 70, 16, + 16, 0, 1, 0, 0, 0, + 230, 138, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 63, 0, 0, 0, 63, + 0, 0, 0, 0, 0, 0, + 0, 0, 65, 0, 0, 5, + 242, 0, 16, 0, 3, 0, + 0, 0, 70, 4, 16, 0, + 2, 0, 0, 0, 0, 0, + 0, 10, 242, 0, 16, 0, + 3, 0, 0, 0, 70, 14, + 16, 0, 3, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 191, 0, 0, 0, 191, + 0, 0, 0, 63, 0, 0, + 0, 63, 56, 0, 0, 8, + 242, 0, 16, 0, 3, 0, + 0, 0, 70, 14, 16, 0, + 3, 0, 0, 0, 70, 132, + 32, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 26, 0, + 0, 5, 50, 0, 16, 0, + 2, 0, 0, 0, 70, 0, + 16, 0, 2, 0, 0, 0, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 18, 0, 16, 0, 0, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 50, 0, 0, 9, + 18, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 127, 67, + 1, 64, 0, 0, 0, 0, + 0, 63, 56, 0, 0, 7, + 18, 0, 16, 0, 4, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 59, + 54, 0, 0, 8, 162, 0, + 16, 0, 4, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 63, + 0, 0, 0, 0, 0, 0, + 0, 63, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 242, 0, 16, 0, + 5, 0, 0, 0, 70, 0, + 16, 0, 4, 0, 0, 0, + 70, 126, 16, 0, 1, 0, + 0, 0, 0, 96, 16, 0, + 1, 0, 0, 0, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 18, 0, + 16, 0, 0, 0, 0, 0, + 198, 0, 16, 0, 3, 0, + 0, 0, 70, 126, 16, 0, + 0, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, + 50, 0, 0, 9, 18, 0, + 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 127, 67, 1, 64, + 0, 0, 0, 0, 0, 63, + 56, 0, 0, 7, 66, 0, + 16, 0, 4, 0, 0, 0, + 10, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 128, 59, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 242, 0, + 16, 0, 4, 0, 0, 0, + 230, 10, 16, 0, 4, 0, + 0, 0, 70, 126, 16, 0, + 1, 0, 0, 0, 0, 96, + 16, 0, 1, 0, 0, 0, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 18, 0, 16, 0, 0, 0, + 0, 0, 102, 10, 16, 0, + 3, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 50, 0, 0, 9, + 18, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 127, 67, + 1, 64, 0, 0, 0, 0, + 0, 63, 56, 0, 0, 7, + 18, 0, 16, 0, 6, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 59, + 54, 0, 0, 8, 162, 0, + 16, 0, 6, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 63, + 0, 0, 0, 0, 0, 0, + 0, 63, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 242, 0, 16, 0, + 7, 0, 0, 0, 70, 0, + 16, 0, 6, 0, 0, 0, + 70, 126, 16, 0, 1, 0, + 0, 0, 0, 96, 16, 0, + 1, 0, 0, 0, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 18, 0, + 16, 0, 0, 0, 0, 0, + 230, 10, 16, 0, 3, 0, + 0, 0, 70, 126, 16, 0, + 0, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, + 50, 0, 0, 9, 18, 0, + 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 127, 67, 1, 64, + 0, 0, 0, 0, 0, 63, + 56, 0, 0, 7, 66, 0, + 16, 0, 6, 0, 0, 0, + 10, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 128, 59, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 242, 0, + 16, 0, 3, 0, 0, 0, + 230, 10, 16, 0, 6, 0, + 0, 0, 70, 126, 16, 0, + 1, 0, 0, 0, 0, 96, + 16, 0, 1, 0, 0, 0, + 0, 0, 0, 8, 242, 0, + 16, 0, 4, 0, 0, 0, + 70, 14, 16, 128, 65, 0, + 0, 0, 5, 0, 0, 0, + 70, 14, 16, 0, 4, 0, + 0, 0, 50, 0, 0, 9, + 242, 0, 16, 0, 4, 0, + 0, 0, 86, 5, 16, 0, + 2, 0, 0, 0, 70, 14, + 16, 0, 4, 0, 0, 0, + 70, 14, 16, 0, 5, 0, + 0, 0, 0, 0, 0, 8, + 242, 0, 16, 0, 3, 0, + 0, 0, 70, 14, 16, 128, + 65, 0, 0, 0, 7, 0, + 0, 0, 70, 14, 16, 0, + 3, 0, 0, 0, 50, 0, + 0, 9, 242, 0, 16, 0, + 3, 0, 0, 0, 86, 5, + 16, 0, 2, 0, 0, 0, + 70, 14, 16, 0, 3, 0, + 0, 0, 70, 14, 16, 0, + 7, 0, 0, 0, 0, 0, + 0, 8, 242, 0, 16, 0, + 3, 0, 0, 0, 70, 14, + 16, 128, 65, 0, 0, 0, + 4, 0, 0, 0, 70, 14, + 16, 0, 3, 0, 0, 0, + 50, 0, 0, 9, 242, 0, + 16, 0, 1, 0, 0, 0, + 6, 0, 16, 0, 2, 0, + 0, 0, 70, 14, 16, 0, + 3, 0, 0, 0, 70, 14, + 16, 0, 4, 0, 0, 0, + 18, 0, 0, 1, 24, 0, + 0, 8, 18, 0, 16, 0, + 0, 0, 0, 0, 26, 128, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 160, 64, + 31, 0, 4, 3, 10, 0, + 16, 0, 0, 0, 0, 0, + 122, 0, 0, 5, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 16, 16, 0, 1, 0, + 0, 0, 124, 0, 0, 5, + 194, 0, 16, 0, 2, 0, + 0, 0, 6, 20, 16, 0, + 1, 0, 0, 0, 0, 0, + 0, 9, 50, 0, 16, 0, + 2, 0, 0, 0, 230, 10, + 16, 128, 129, 0, 0, 0, + 2, 0, 0, 0, 70, 0, + 16, 128, 129, 0, 0, 0, + 2, 0, 0, 0, 56, 0, + 0, 8, 50, 0, 16, 0, + 2, 0, 0, 0, 70, 0, + 16, 0, 2, 0, 0, 0, + 230, 138, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 52, 0, 0, 10, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 0, 16, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 172, 197, 39, 55, 172, 197, + 39, 55, 0, 0, 0, 0, + 0, 0, 0, 0, 51, 0, + 0, 10, 50, 0, 16, 0, + 2, 0, 0, 0, 70, 0, + 16, 0, 2, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 128, 63, 0, 0, 128, 63, + 0, 0, 0, 0, 0, 0, + 0, 0, 56, 0, 0, 10, + 194, 0, 16, 0, 2, 0, + 0, 0, 6, 4, 16, 0, + 2, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 63, 0, 0, 0, 63, + 50, 0, 0, 11, 194, 0, + 16, 0, 2, 0, 0, 0, + 6, 20, 16, 0, 1, 0, + 0, 0, 166, 142, 32, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 166, 14, 16, 128, + 65, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 11, + 50, 0, 16, 0, 2, 0, + 0, 0, 70, 0, 16, 128, + 65, 0, 0, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 26, 0, + 0, 5, 50, 0, 16, 0, + 3, 0, 0, 0, 230, 10, + 16, 0, 2, 0, 0, 0, + 0, 0, 0, 11, 194, 0, + 16, 0, 3, 0, 0, 0, + 6, 4, 16, 128, 65, 0, + 0, 0, 2, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 0, 8, + 50, 0, 16, 0, 2, 0, + 0, 0, 70, 0, 16, 128, + 65, 0, 0, 0, 2, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 14, 0, + 0, 10, 50, 0, 16, 0, + 3, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 128, 63, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 128, 63, + 230, 10, 16, 0, 3, 0, + 0, 0, 56, 32, 0, 7, + 50, 0, 16, 0, 2, 0, + 0, 0, 70, 0, 16, 0, + 2, 0, 0, 0, 70, 0, + 16, 0, 3, 0, 0, 0, + 50, 0, 0, 15, 50, 0, + 16, 0, 3, 0, 0, 0, + 70, 0, 16, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 192, 0, 0, + 0, 192, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 64, 64, + 0, 0, 64, 64, 0, 0, + 0, 0, 0, 0, 0, 0, + 56, 0, 0, 7, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 0, 16, 0, 2, 0, + 0, 0, 70, 0, 16, 0, + 2, 0, 0, 0, 65, 0, + 0, 5, 194, 0, 16, 0, + 2, 0, 0, 0, 166, 14, + 16, 0, 2, 0, 0, 0, + 50, 0, 0, 9, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 0, 16, 0, 3, 0, + 0, 0, 70, 0, 16, 0, + 2, 0, 0, 0, 230, 10, + 16, 0, 2, 0, 0, 0, + 0, 0, 0, 10, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 0, 16, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 63, 0, 0, + 0, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 56, 0, + 0, 8, 50, 0, 16, 0, + 2, 0, 0, 0, 70, 0, + 16, 0, 2, 0, 0, 0, + 70, 128, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 50, 0, 0, 13, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 0, 16, 0, 2, 0, + 0, 0, 230, 138, 32, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 63, 0, 0, + 0, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 65, 0, + 0, 5, 242, 0, 16, 0, + 3, 0, 0, 0, 70, 4, + 16, 0, 2, 0, 0, 0, + 0, 0, 0, 10, 242, 0, + 16, 0, 3, 0, 0, 0, + 70, 14, 16, 0, 3, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 191, 0, 0, + 0, 191, 0, 0, 0, 63, + 0, 0, 0, 63, 56, 0, + 0, 8, 242, 0, 16, 0, + 3, 0, 0, 0, 70, 14, + 16, 0, 3, 0, 0, 0, + 70, 132, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 26, 0, 0, 5, 50, 0, + 16, 0, 2, 0, 0, 0, + 70, 0, 16, 0, 2, 0, + 0, 0, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 18, 0, 16, 0, + 0, 0, 0, 0, 70, 0, 16, 0, 3, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, - 0, 0, 0, 0, 70, 0, + 0, 0, 0, 0, 50, 0, + 0, 9, 18, 0, 16, 0, + 0, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 127, 67, 1, 64, 0, 0, + 0, 0, 0, 63, 56, 0, + 0, 7, 18, 0, 16, 0, + 4, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 128, 59, 54, 0, 0, 8, + 162, 0, 16, 0, 4, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 63, 0, 0, 0, 0, + 0, 0, 0, 63, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 242, 0, + 16, 0, 5, 0, 0, 0, + 70, 0, 16, 0, 4, 0, + 0, 0, 70, 126, 16, 0, + 1, 0, 0, 0, 0, 96, + 16, 0, 1, 0, 0, 0, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 18, 0, 16, 0, 0, 0, + 0, 0, 198, 0, 16, 0, + 3, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 50, 0, 0, 9, + 18, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 127, 67, + 1, 64, 0, 0, 0, 0, + 0, 63, 56, 0, 0, 7, + 66, 0, 16, 0, 4, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 59, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 242, 0, 16, 0, 4, 0, + 0, 0, 230, 10, 16, 0, + 4, 0, 0, 0, 70, 126, + 16, 0, 1, 0, 0, 0, + 0, 96, 16, 0, 1, 0, + 0, 0, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 18, 0, 16, 0, + 0, 0, 0, 0, 102, 10, + 16, 0, 3, 0, 0, 0, + 70, 126, 16, 0, 0, 0, + 0, 0, 0, 96, 16, 0, + 0, 0, 0, 0, 50, 0, + 0, 9, 18, 0, 16, 0, + 0, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 127, 67, 1, 64, 0, 0, + 0, 0, 0, 63, 56, 0, + 0, 7, 18, 0, 16, 0, + 6, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 128, 59, 54, 0, 0, 8, + 162, 0, 16, 0, 6, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 63, 0, 0, 0, 0, + 0, 0, 0, 63, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 242, 0, + 16, 0, 7, 0, 0, 0, + 70, 0, 16, 0, 6, 0, + 0, 0, 70, 126, 16, 0, + 1, 0, 0, 0, 0, 96, + 16, 0, 1, 0, 0, 0, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 18, 0, 16, 0, 0, 0, + 0, 0, 230, 10, 16, 0, + 3, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 50, 0, 0, 9, + 18, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 127, 67, + 1, 64, 0, 0, 0, 0, + 0, 63, 56, 0, 0, 7, + 66, 0, 16, 0, 6, 0, + 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 59, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 242, 0, 16, 0, 3, 0, + 0, 0, 230, 10, 16, 0, + 6, 0, 0, 0, 70, 126, + 16, 0, 1, 0, 0, 0, + 0, 96, 16, 0, 1, 0, + 0, 0, 0, 0, 0, 8, + 242, 0, 16, 0, 4, 0, + 0, 0, 70, 14, 16, 128, + 65, 0, 0, 0, 5, 0, + 0, 0, 70, 14, 16, 0, + 4, 0, 0, 0, 50, 0, + 0, 9, 242, 0, 16, 0, + 4, 0, 0, 0, 86, 5, 16, 0, 2, 0, 0, 0, - 230, 10, 16, 0, 2, 0, + 70, 14, 16, 0, 4, 0, + 0, 0, 70, 14, 16, 0, + 5, 0, 0, 0, 0, 0, + 0, 8, 242, 0, 16, 0, + 3, 0, 0, 0, 70, 14, + 16, 128, 65, 0, 0, 0, + 7, 0, 0, 0, 70, 14, + 16, 0, 3, 0, 0, 0, + 50, 0, 0, 9, 242, 0, + 16, 0, 3, 0, 0, 0, + 86, 5, 16, 0, 2, 0, + 0, 0, 70, 14, 16, 0, + 3, 0, 0, 0, 70, 14, + 16, 0, 7, 0, 0, 0, + 0, 0, 0, 8, 242, 0, + 16, 0, 3, 0, 0, 0, + 70, 14, 16, 128, 65, 0, + 0, 0, 4, 0, 0, 0, + 70, 14, 16, 0, 3, 0, + 0, 0, 50, 0, 0, 9, + 242, 0, 16, 0, 1, 0, + 0, 0, 6, 0, 16, 0, + 2, 0, 0, 0, 70, 14, + 16, 0, 3, 0, 0, 0, + 70, 14, 16, 0, 4, 0, 0, 0, 18, 0, 0, 1, 24, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 26, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, - 64, 64, 31, 0, 4, 3, + 192, 64, 31, 0, 4, 3, 10, 0, 16, 0, 0, 0, 0, 0, 69, 0, 0, 139, 194, 0, 0, 128, 67, 85, @@ -676,7 +1297,7 @@ const BYTE g_main[] = 26, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, - 128, 64, 31, 0, 4, 3, + 224, 64, 31, 0, 4, 3, 10, 0, 16, 0, 0, 0, 0, 0, 69, 0, 0, 139, 194, 0, 0, 128, 67, 85, @@ -720,7 +1341,7 @@ const BYTE g_main[] = 26, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, - 160, 64, 31, 0, 4, 3, + 0, 65, 31, 0, 4, 3, 10, 0, 16, 0, 0, 0, 0, 0, 69, 0, 0, 139, 194, 0, 0, 128, 67, 85, @@ -770,7 +1391,7 @@ const BYTE g_main[] = 114, 0, 16, 0, 1, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, - 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 21, 0, 0, 1, 21, 0, 0, 1, 21, 0, 0, 1, 54, 0, @@ -779,6 +1400,8 @@ const BYTE g_main[] = 0, 0, 0, 0, 128, 63, 21, 0, 0, 1, 21, 0, 0, 1, 21, 0, 0, 1, + 21, 0, 0, 1, 21, 0, + 0, 1, 21, 0, 0, 1, 47, 0, 0, 6, 114, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 128, 129, 0, @@ -1201,21 +1824,21 @@ const BYTE g_main[] = 16, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 148, 0, 0, 0, - 151, 0, 0, 0, 7, 0, + 246, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 92, 0, + 3, 0, 0, 0, 155, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 10, 0, - 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 13, 0, + 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 8, 0, 0, 0, + 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 3, 0, + 1, 0, 0, 0, 8, 0, 0, 0, 10, 0, 0, 0, - 5, 0, 0, 0, 0, 0, + 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Common.hlsli b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Common.hlsli index b47a5e12..4a4cedd0 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Common.hlsli +++ b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Common.hlsli @@ -3,6 +3,7 @@ Texture2D texture0 : register(t0); Texture2D texture1 : register(t1); Texture2D texture2 : register(t2); SamplerState sampler0 : register(s0); +SamplerState sampler1 : register(s1); struct PixelShaderInput { @@ -19,9 +20,12 @@ static const float TONEMAP_CHROME = 2; static const float TEXTURETYPE_NONE = 0; static const float TEXTURETYPE_RGB = 1; static const float TEXTURETYPE_RGB_PIXELART = 2; -static const float TEXTURETYPE_NV12 = 3; -static const float TEXTURETYPE_NV21 = 4; -static const float TEXTURETYPE_YUV = 5; +static const float TEXTURETYPE_PALETTE_NEAREST = 3; +static const float TEXTURETYPE_PALETTE_LINEAR = 4; +static const float TEXTURETYPE_PALETTE_PIXELART = 5; +static const float TEXTURETYPE_NV12 = 6; +static const float TEXTURETYPE_NV21 = 7; +static const float TEXTURETYPE_YUV = 8; static const float INPUTTYPE_UNSPECIFIED = 0; static const float INPUTTYPE_SRGB = 1; @@ -116,6 +120,50 @@ float3 ApplyTonemap(float3 v) return v; } +float4 SamplePaletteNearest(float2 uv) +{ + float index = texture0.Sample(sampler0, uv).r * 255; + return texture1.Sample(sampler1, float2((index + 0.5) / 256, 0.5)); +} + +// Implementation with thanks from bgolus: +// https://discussions.unity.com/t/how-to-make-data-shader-support-bilinear-trilinear/598639/8 +float4 SamplePaletteLinear(float2 uv) +{ + // scale & offset uvs to integer values at texel centers + float2 uv_texels = uv * texel_size.zw + 0.5; + + // get uvs for the center of the 4 surrounding texels by flooring + float4 uv_min_max = float4((floor(uv_texels) - 0.5) * texel_size.xy, (floor(uv_texels) + 0.5) * texel_size.xy); + + // blend factor + float2 uv_frac = frac(uv_texels); + + // sample all 4 texels + float4 texelA = SamplePaletteNearest(uv_min_max.xy); + float4 texelB = SamplePaletteNearest(uv_min_max.xw); + float4 texelC = SamplePaletteNearest(uv_min_max.zy); + float4 texelD = SamplePaletteNearest(uv_min_max.zw); + + // bilinear interpolation + return lerp(lerp(texelA, texelB, uv_frac.y), lerp(texelC, texelD, uv_frac.y), uv_frac.x); +} + +float2 GetPixelArtUV(float2 uv) +{ + // box filter size in texel units + float2 boxSize = clamp(fwidth(uv) * texel_size.zw, 1e-5, 1); + + // scale uv by texture size to get texel coordinate + float2 tx = uv * texel_size.zw - 0.5 * boxSize; + + // compute offset for pixel-sized box filter + float2 txOffset = smoothstep(1 - boxSize, 1, frac(tx)); + + // compute bilinear sample uv coordinates + return (floor(tx) + 0.5 + txOffset) * texel_size.xy; +} + float4 GetInputColor(PixelShaderInput input) { float4 rgba; @@ -125,20 +173,15 @@ float4 GetInputColor(PixelShaderInput input) } else if (texture_type == TEXTURETYPE_RGB) { rgba = texture0.Sample(sampler0, input.tex); } else if (texture_type == TEXTURETYPE_RGB_PIXELART) { - // box filter size in texel units - float2 boxSize = clamp(fwidth(input.tex) * texel_size.zw, 1e-5, 1); - - // scale uv by texture size to get texel coordinate - float2 tx = input.tex * texel_size.zw - 0.5 * boxSize; - - // compute offset for pixel-sized box filter - float2 txOffset = smoothstep(1 - boxSize, 1, frac(tx)); - - // compute bilinear sample uv coordinates - float2 uv = (floor(tx) + 0.5 + txOffset) * texel_size.xy; - - // sample the texture + float2 uv = GetPixelArtUV(input.tex); rgba = texture0.SampleGrad(sampler0, uv, ddx(input.tex), ddy(input.tex)); + } else if (texture_type == TEXTURETYPE_PALETTE_NEAREST) { + rgba = SamplePaletteNearest(input.tex); + } else if (texture_type == TEXTURETYPE_PALETTE_LINEAR) { + rgba = SamplePaletteLinear(input.tex); + } else if (texture_type == TEXTURETYPE_PALETTE_PIXELART) { + float2 uv = GetPixelArtUV(input.tex); + rgba = SamplePaletteLinear(uv); } else if (texture_type == TEXTURETYPE_NV12) { float3 yuv; yuv.x = texture0.Sample(sampler0, input.tex).r; @@ -174,7 +217,7 @@ float4 GetInputColor(PixelShaderInput input) // Error! rgba.r = 1.0; rgba.g = 0.0; - rgba.b = 0.0; + rgba.b = 1.0; rgba.a = 1.0; } return rgba; diff --git a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.h b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.h index 4b06c09c..7e2c7c11 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.h +++ b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.h @@ -29,8 +29,8 @@ // // Name Type Format Dim HLSL Bind Count // ------------------------------ ---------- ------- ----------- -------------- ------ -// theSampler sampler NA NA s0 1 -// theTexture texture float4 2d t0 1 +// sampler0 sampler NA NA s0 1 +// texture0 texture float4 2d t0 1 // Constants cbuffer NA NA cb0 1 // // @@ -94,15 +94,15 @@ ret const BYTE g_main[] = { - 68, 88, 66, 67, 59, 235, - 166, 93, 21, 32, 225, 129, - 78, 220, 127, 97, 218, 34, - 222, 113, 1, 0, 0, 0, - 164, 5, 0, 0, 6, 0, + 68, 88, 66, 67, 98, 171, + 127, 123, 23, 170, 245, 47, + 35, 199, 24, 186, 200, 109, + 190, 201, 1, 0, 0, 0, + 160, 5, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 220, 0, 0, 0, 168, 1, 0, 0, 36, 2, 0, 0, - 252, 4, 0, 0, 112, 5, + 248, 4, 0, 0, 108, 5, 0, 0, 65, 111, 110, 57, 156, 0, 0, 0, 156, 0, 0, 0, 0, 2, 255, 255, @@ -186,153 +186,152 @@ const BYTE g_main[] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, - 208, 2, 0, 0, 1, 0, - 0, 0, 156, 0, 0, 0, + 204, 2, 0, 0, 1, 0, + 0, 0, 152, 0, 0, 0, 3, 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, - 0, 1, 0, 0, 168, 2, + 0, 1, 0, 0, 164, 2, 0, 0, 124, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 135, 0, + 1, 0, 0, 0, 133, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, 0, - 146, 0, 0, 0, 0, 0, + 142, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, - 0, 0, 116, 104, 101, 83, - 97, 109, 112, 108, 101, 114, - 0, 116, 104, 101, 84, 101, - 120, 116, 117, 114, 101, 0, - 67, 111, 110, 115, 116, 97, - 110, 116, 115, 0, 146, 0, - 0, 0, 13, 0, 0, 0, - 180, 0, 0, 0, 112, 0, + 0, 0, 115, 97, 109, 112, + 108, 101, 114, 48, 0, 116, + 101, 120, 116, 117, 114, 101, + 48, 0, 67, 111, 110, 115, + 116, 97, 110, 116, 115, 0, + 142, 0, 0, 0, 13, 0, + 0, 0, 176, 0, 0, 0, + 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 236, 1, - 0, 0, 0, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 252, 1, 0, 0, - 0, 0, 0, 0, 12, 2, + 232, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 252, 1, 0, 0, - 0, 0, 0, 0, 25, 2, - 0, 0, 8, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 252, 1, 0, 0, - 0, 0, 0, 0, 36, 2, - 0, 0, 12, 0, 0, 0, - 4, 0, 0, 0, 2, 0, - 0, 0, 252, 1, 0, 0, - 0, 0, 0, 0, 48, 2, - 0, 0, 16, 0, 0, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 60, 2, 0, 0, - 0, 0, 0, 0, 76, 2, - 0, 0, 32, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 252, 1, 0, 0, - 0, 0, 0, 0, 91, 2, - 0, 0, 36, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 252, 1, 0, 0, - 0, 0, 0, 0, 107, 2, - 0, 0, 40, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 252, 1, 0, 0, - 0, 0, 0, 0, 123, 2, - 0, 0, 44, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 252, 1, 0, 0, - 0, 0, 0, 0, 139, 2, - 0, 0, 48, 0, 0, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 60, 2, 0, 0, - 0, 0, 0, 0, 147, 2, - 0, 0, 64, 0, 0, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 60, 2, 0, 0, - 0, 0, 0, 0, 154, 2, - 0, 0, 80, 0, 0, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 60, 2, 0, 0, - 0, 0, 0, 0, 161, 2, - 0, 0, 96, 0, 0, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 60, 2, 0, 0, - 0, 0, 0, 0, 115, 99, - 82, 71, 66, 95, 111, 117, - 116, 112, 117, 116, 0, 171, - 171, 171, 0, 0, 3, 0, - 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 248, 1, 0, 0, 0, 0, 0, 0, - 116, 101, 120, 116, 117, 114, - 101, 95, 116, 121, 112, 101, - 0, 105, 110, 112, 117, 116, - 95, 116, 121, 112, 101, 0, - 99, 111, 108, 111, 114, 95, - 115, 99, 97, 108, 101, 0, - 116, 101, 120, 101, 108, 95, - 115, 105, 122, 101, 0, 171, - 1, 0, 3, 0, 1, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 116, 111, - 110, 101, 109, 97, 112, 95, - 109, 101, 116, 104, 111, 100, + 8, 2, 0, 0, 4, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 21, 2, 0, 0, 8, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 32, 2, 0, 0, 12, 0, + 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 44, 2, 0, 0, 16, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 56, 2, + 0, 0, 0, 0, 0, 0, + 72, 2, 0, 0, 32, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 87, 2, 0, 0, 36, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 103, 2, 0, 0, 40, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 119, 2, 0, 0, 44, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 248, 1, + 0, 0, 0, 0, 0, 0, + 135, 2, 0, 0, 48, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 56, 2, + 0, 0, 0, 0, 0, 0, + 143, 2, 0, 0, 64, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 56, 2, + 0, 0, 0, 0, 0, 0, + 150, 2, 0, 0, 80, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 56, 2, + 0, 0, 0, 0, 0, 0, + 157, 2, 0, 0, 96, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 56, 2, + 0, 0, 0, 0, 0, 0, + 115, 99, 82, 71, 66, 95, + 111, 117, 116, 112, 117, 116, + 0, 171, 171, 171, 0, 0, + 3, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 116, 101, 120, 116, + 117, 114, 101, 95, 116, 121, + 112, 101, 0, 105, 110, 112, + 117, 116, 95, 116, 121, 112, + 101, 0, 99, 111, 108, 111, + 114, 95, 115, 99, 97, 108, + 101, 0, 116, 101, 120, 101, + 108, 95, 115, 105, 122, 101, + 0, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 116, 111, 110, 101, 109, 97, + 112, 95, 109, 101, 116, 104, + 111, 100, 0, 116, 111, 110, + 101, 109, 97, 112, 95, 102, + 97, 99, 116, 111, 114, 49, 0, 116, 111, 110, 101, 109, 97, 112, 95, 102, 97, 99, - 116, 111, 114, 49, 0, 116, - 111, 110, 101, 109, 97, 112, - 95, 102, 97, 99, 116, 111, - 114, 50, 0, 115, 100, 114, - 95, 119, 104, 105, 116, 101, - 95, 112, 111, 105, 110, 116, - 0, 89, 111, 102, 102, 115, - 101, 116, 0, 82, 99, 111, - 101, 102, 102, 0, 71, 99, - 111, 101, 102, 102, 0, 66, + 116, 111, 114, 50, 0, 115, + 100, 114, 95, 119, 104, 105, + 116, 101, 95, 112, 111, 105, + 110, 116, 0, 89, 111, 102, + 102, 115, 101, 116, 0, 82, 99, 111, 101, 102, 102, 0, - 77, 105, 99, 114, 111, 115, - 111, 102, 116, 32, 40, 82, - 41, 32, 72, 76, 83, 76, - 32, 83, 104, 97, 100, 101, - 114, 32, 67, 111, 109, 112, - 105, 108, 101, 114, 32, 49, - 48, 46, 49, 0, 73, 83, - 71, 78, 108, 0, 0, 0, - 3, 0, 0, 0, 8, 0, - 0, 0, 80, 0, 0, 0, - 0, 0, 0, 0, 1, 0, + 71, 99, 111, 101, 102, 102, + 0, 66, 99, 111, 101, 102, + 102, 0, 77, 105, 99, 114, + 111, 115, 111, 102, 116, 32, + 40, 82, 41, 32, 72, 76, + 83, 76, 32, 83, 104, 97, + 100, 101, 114, 32, 67, 111, + 109, 112, 105, 108, 101, 114, + 32, 49, 48, 46, 49, 0, + 73, 83, 71, 78, 108, 0, 0, 0, 3, 0, 0, 0, - 0, 0, 0, 0, 15, 0, - 0, 0, 92, 0, 0, 0, + 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 1, 0, 0, 0, 3, 3, - 0, 0, 101, 0, 0, 0, + 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 2, 0, 0, 0, 15, 15, - 0, 0, 83, 86, 95, 80, - 79, 83, 73, 84, 73, 79, - 78, 0, 84, 69, 88, 67, - 79, 79, 82, 68, 0, 67, - 79, 76, 79, 82, 0, 171, - 79, 83, 71, 78, 44, 0, - 0, 0, 1, 0, 0, 0, - 8, 0, 0, 0, 32, 0, + 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 0, 0, + 3, 3, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, - 15, 0, 0, 0, 83, 86, - 95, 84, 65, 82, 71, 69, - 84, 0, 171, 171 + 0, 0, 0, 0, 3, 0, + 0, 0, 2, 0, 0, 0, + 15, 15, 0, 0, 83, 86, + 95, 80, 79, 83, 73, 84, + 73, 79, 78, 0, 84, 69, + 88, 67, 79, 79, 82, 68, + 0, 67, 79, 76, 79, 82, + 0, 171, 79, 83, 71, 78, + 44, 0, 0, 0, 1, 0, + 0, 0, 8, 0, 0, 0, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, + 0, 0, 15, 0, 0, 0, + 83, 86, 95, 84, 65, 82, + 71, 69, 84, 0, 171, 171 }; diff --git a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.hlsl b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.hlsl index f2ecf18a..9031e0fa 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.hlsl +++ b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.hlsl @@ -1,9 +1,7 @@ -Texture2D theTexture : register(t0); -SamplerState theSampler : register(s0); #include "D3D11_PixelShader_Common.hlsli" float4 main(PixelShaderInput input) : SV_TARGET { - return GetOutputColor(theTexture.Sample(theSampler, input.tex)) * input.color; + return GetOutputColor(texture0.Sample(sampler0, input.tex)) * input.color; } diff --git a/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c b/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c index 12fd5abc..5f2f4cd7 100644 --- a/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c +++ b/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -30,17 +30,15 @@ #include "../../video/SDL_pixels_c.h" #include +#ifdef HAVE_DXGI1_5_H +#include +#else #include +#endif #include #include "SDL_shaders_d3d11.h" -#if defined(_MSC_VER) && !defined(__clang__) -#define SDL_COMPOSE_ERROR(str) __FUNCTION__ ", " str -#else -#define SDL_COMPOSE_ERROR(str) SDL_STRINGIFY_ARG(__FUNCTION__) ", " str -#endif - #define SAFE_RELEASE(X) \ if ((X)) { \ IUnknown_Release(SDL_static_cast(IUnknown *, X)); \ @@ -58,16 +56,19 @@ typedef struct } D3D11_VertexShaderConstants; // These should mirror the definitions in D3D11_PixelShader_Common.hlsli -//static const float TONEMAP_NONE = 0; +static const float TONEMAP_NONE = 0; //static const float TONEMAP_LINEAR = 1; static const float TONEMAP_CHROME = 2; //static const float TEXTURETYPE_NONE = 0; static const float TEXTURETYPE_RGB = 1; static const float TEXTURETYPE_RGB_PIXELART = 2; -static const float TEXTURETYPE_NV12 = 3; -static const float TEXTURETYPE_NV21 = 4; -static const float TEXTURETYPE_YUV = 5; +static const float TEXTURETYPE_PALETTE_NEAREST = 3; +static const float TEXTURETYPE_PALETTE_LINEAR = 4; +static const float TEXTURETYPE_PALETTE_PIXELART = 5; +static const float TEXTURETYPE_NV12 = 6; +static const float TEXTURETYPE_NV21 = 7; +static const float TEXTURETYPE_YUV = 8; static const float INPUTTYPE_UNSPECIFIED = 0; static const float INPUTTYPE_SRGB = 1; @@ -108,6 +109,13 @@ typedef struct SDL_FColor color; } D3D11_VertexPositionColor; +// Per-palette data +typedef struct +{ + ID3D11Texture2D *texture; + ID3D11ShaderResourceView *resourceView; +} D3D11_PaletteData; + // Per-texture data typedef struct { @@ -118,7 +126,6 @@ typedef struct ID3D11Texture2D *stagingTexture; int lockedTexturePositionX; int lockedTexturePositionY; - D3D11_Shader shader; const float *YCbCr_matrix; #ifdef SDL_HAVE_YUV // YV12 texture support @@ -151,12 +158,12 @@ typedef struct SDL_SharedObject *hDXGIMod; SDL_SharedObject *hD3D11Mod; IDXGIFactory2 *dxgiFactory; - IDXGIAdapter *dxgiAdapter; IDXGIDebug *dxgiDebug; ID3D11Device1 *d3dDevice; ID3D11DeviceContext1 *d3dContext; IDXGISwapChain1 *swapChain; DXGI_SWAP_EFFECT swapEffect; + UINT swapChainFlags; UINT syncInterval; UINT presentFlags; ID3D11RenderTargetView *mainRenderTargetView; @@ -187,8 +194,10 @@ typedef struct ID3D11BlendState *currentBlendState; D3D11_Shader currentShader; D3D11_PixelShaderState currentShaderState[NUM_SHADERS]; + int numCurrentShaderResources; ID3D11ShaderResourceView *currentShaderResource; - ID3D11SamplerState *currentSampler; + int numCurrentShaderSamplers; + ID3D11SamplerState *currentShaderSampler; bool cliprectDirty; bool currentCliprectEnabled; SDL_Rect currentCliprect; @@ -206,6 +215,9 @@ typedef struct #pragma GCC diagnostic ignored "-Wunused-const-variable" #endif +#ifdef HAVE_DXGI1_5_H +static const GUID SDL_IID_IDXGIFactory5 = { 0x7632e1f5, 0xee65, 0x4dca, { 0x87, 0xfd, 0x84, 0xcd, 0x75, 0xf8, 0x83, 0x8d } }; +#endif static const GUID SDL_IID_IDXGIFactory2 = { 0x50c83a1c, 0xe072, 0x4c48, { 0x87, 0xb0, 0x36, 0x30, 0xfa, 0x36, 0xa6, 0xd0 } }; static const GUID SDL_IID_IDXGIDevice1 = { 0x77db970f, 0x6276, 0x48ba, { 0xba, 0x28, 0x07, 0x01, 0x43, 0xb4, 0x39, 0x2c } }; static const GUID SDL_IID_ID3D11Texture2D = { 0x6f15aaf2, 0xd208, 0x4e89, { 0x9a, 0xb4, 0x48, 0x95, 0x35, 0xd3, 0x4f, 0x9c } }; @@ -220,6 +232,8 @@ static const GUID SDL_DXGI_DEBUG_ALL = { 0xe48ae283, 0xda80, 0x490b, { 0x87, 0xe #pragma GCC diagnostic pop #endif +static bool D3D11_UpdateTextureInternal(D3D11_RenderData *rendererData, ID3D11Texture2D *texture, int bpp, int x, int y, int w, int h, const void *pixels, int pitch); + SDL_PixelFormat D3D11_DXGIFormatToSDLPixelFormat(DXGI_FORMAT dxgiFormat) { switch (dxgiFormat) { @@ -263,6 +277,7 @@ static DXGI_FORMAT SDLPixelFormatToDXGITextureFormat(Uint32 format, Uint32 outpu return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB; } return DXGI_FORMAT_B8G8R8X8_UNORM; + case SDL_PIXELFORMAT_INDEX8: case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: return DXGI_FORMAT_R8_UNORM; @@ -271,6 +286,10 @@ static DXGI_FORMAT SDLPixelFormatToDXGITextureFormat(Uint32 format, Uint32 outpu return DXGI_FORMAT_NV12; case SDL_PIXELFORMAT_P010: return DXGI_FORMAT_P010; + case SDL_PIXELFORMAT_RGB565: + return DXGI_FORMAT_B5G6R5_UNORM; + case SDL_PIXELFORMAT_ARGB1555: + return DXGI_FORMAT_B5G5R5A1_UNORM; default: return DXGI_FORMAT_UNKNOWN; } @@ -362,7 +381,6 @@ static void D3D11_ReleaseAll(SDL_Renderer *renderer) SAFE_RELEASE(data->d3dContext); SAFE_RELEASE(data->d3dDevice); - SAFE_RELEASE(data->dxgiAdapter); SAFE_RELEASE(data->dxgiFactory); data->swapEffect = (DXGI_SWAP_EFFECT)0; @@ -373,8 +391,10 @@ static void D3D11_ReleaseAll(SDL_Renderer *renderer) data->currentBlendState = NULL; data->currentShader = SHADER_NONE; SDL_zero(data->currentShaderState); + data->numCurrentShaderResources = 0; data->currentShaderResource = NULL; - data->currentSampler = NULL; + data->numCurrentShaderSamplers = 0; + data->currentShaderSampler = NULL; // Check for any leaks if in debug mode if (data->dxgiDebug) { @@ -479,7 +499,7 @@ static ID3D11BlendState *D3D11_CreateBlendState(SDL_Renderer *renderer, SDL_Blen blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; result = ID3D11Device_CreateBlendState(data->d3dDevice, &blendDesc, &blendState); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateBlendState"), result); + WIN_SetErrorFromHRESULT("ID3D11Device1::CreateBlendState", result); return NULL; } @@ -499,32 +519,32 @@ static ID3D11BlendState *D3D11_CreateBlendState(SDL_Renderer *renderer, SDL_Blen // Create resources that depend on the device. static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) { - typedef HRESULT(WINAPI * PFN_CREATE_DXGI_FACTORY)(REFIID riid, void **ppFactory); - typedef HRESULT(WINAPI * PFN_CREATE_DXGI_FACTORY2)(UINT flags, REFIID riid, void **ppFactory); - PFN_CREATE_DXGI_FACTORY CreateDXGIFactoryFunc = NULL; - PFN_CREATE_DXGI_FACTORY2 CreateDXGIFactory2Func = NULL; + typedef HRESULT (WINAPI *pfnCreateDXGIFactory)(REFIID riid, void **ppFactory); + typedef HRESULT (WINAPI *pfnCreateDXGIFactory2)(UINT flags, REFIID riid, void **ppFactory); + pfnCreateDXGIFactory pCreateDXGIFactory = NULL; + pfnCreateDXGIFactory2 pCreateDXGIFactory2 = NULL; D3D11_RenderData *data = (D3D11_RenderData *)renderer->internal; - PFN_D3D11_CREATE_DEVICE D3D11CreateDeviceFunc; + PFN_D3D11_CREATE_DEVICE pD3D11CreateDevice; + IDXGIAdapter *dxgiAdapter = NULL; ID3D11Device *d3dDevice = NULL; ID3D11DeviceContext *d3dContext = NULL; IDXGIDevice1 *dxgiDevice = NULL; HRESULT result = S_OK; + D3D_DRIVER_TYPE driverType = D3D_DRIVER_TYPE_UNKNOWN; UINT creationFlags = 0; bool createDebug; +#ifdef HAVE_DXGI1_5_H + IDXGIFactory5 *dxgiFactory5 = NULL; +#endif /* This array defines the set of DirectX hardware feature levels this app will support. * Note the ordering should be preserved. * Don't forget to declare your application's minimum required feature level in its - * description. All applications are assumed to support 9.1 unless otherwise stated. + * description. All applications are assumed to support 11.0 unless otherwise stated. */ D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_1, - D3D_FEATURE_LEVEL_11_0, - D3D_FEATURE_LEVEL_10_1, - D3D_FEATURE_LEVEL_10_0, - D3D_FEATURE_LEVEL_9_3, - D3D_FEATURE_LEVEL_9_2, - D3D_FEATURE_LEVEL_9_1 + D3D_FEATURE_LEVEL_11_0 }; D3D11_BUFFER_DESC constantBufferDesc; @@ -539,10 +559,10 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) goto done; } - CreateDXGIFactory2Func = (PFN_CREATE_DXGI_FACTORY2)SDL_LoadFunction(data->hDXGIMod, "CreateDXGIFactory2"); - if (!CreateDXGIFactory2Func) { - CreateDXGIFactoryFunc = (PFN_CREATE_DXGI_FACTORY)SDL_LoadFunction(data->hDXGIMod, "CreateDXGIFactory"); - if (!CreateDXGIFactoryFunc) { + pCreateDXGIFactory2 = (pfnCreateDXGIFactory2)SDL_LoadFunction(data->hDXGIMod, "CreateDXGIFactory2"); + if (!pCreateDXGIFactory2) { + pCreateDXGIFactory = (pfnCreateDXGIFactory)SDL_LoadFunction(data->hDXGIMod, "CreateDXGIFactory"); + if (!pCreateDXGIFactory) { result = E_FAIL; goto done; } @@ -554,8 +574,8 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) goto done; } - D3D11CreateDeviceFunc = (PFN_D3D11_CREATE_DEVICE)SDL_LoadFunction(data->hD3D11Mod, "D3D11CreateDevice"); - if (!D3D11CreateDeviceFunc) { + pD3D11CreateDevice = (PFN_D3D11_CREATE_DEVICE)SDL_LoadFunction(data->hD3D11Mod, "D3D11CreateDevice"); + if (!pD3D11CreateDevice) { result = E_FAIL; goto done; } @@ -563,24 +583,24 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) if (createDebug) { #ifdef __IDXGIInfoQueue_INTERFACE_DEFINED__ IDXGIInfoQueue *dxgiInfoQueue = NULL; - PFN_CREATE_DXGI_FACTORY2 DXGIGetDebugInterfaceFunc; + pfnCreateDXGIFactory2 pDXGIGetDebugInterface1; // If the debug hint is set, also create the DXGI factory in debug mode - DXGIGetDebugInterfaceFunc = (PFN_CREATE_DXGI_FACTORY2)SDL_LoadFunction(data->hDXGIMod, "DXGIGetDebugInterface1"); - if (!DXGIGetDebugInterfaceFunc) { + pDXGIGetDebugInterface1 = (pfnCreateDXGIFactory2)SDL_LoadFunction(data->hDXGIMod, "DXGIGetDebugInterface1"); + if (!pDXGIGetDebugInterface1) { result = E_FAIL; goto done; } - result = DXGIGetDebugInterfaceFunc(0, &SDL_IID_IDXGIDebug1, (void **)&data->dxgiDebug); + result = pDXGIGetDebugInterface1(0, &SDL_IID_IDXGIDebug1, (void **)&data->dxgiDebug); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("DXGIGetDebugInterface1"), result); + WIN_SetErrorFromHRESULT("DXGIGetDebugInterface1", result); goto done; } - result = DXGIGetDebugInterfaceFunc(0, &SDL_IID_IDXGIInfoQueue, (void **)&dxgiInfoQueue); + result = pDXGIGetDebugInterface1(0, &SDL_IID_IDXGIInfoQueue, (void **)&dxgiInfoQueue); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("DXGIGetDebugInterface1"), result); + WIN_SetErrorFromHRESULT("DXGIGetDebugInterface1", result); goto done; } @@ -591,21 +611,44 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) creationFlags = DXGI_CREATE_FACTORY_DEBUG; } - if (CreateDXGIFactory2Func) { - result = CreateDXGIFactory2Func(creationFlags, &SDL_IID_IDXGIFactory2, (void **)&data->dxgiFactory); + if (pCreateDXGIFactory2) { + result = pCreateDXGIFactory2(creationFlags, &SDL_IID_IDXGIFactory2, (void **)&data->dxgiFactory); } else { - result = CreateDXGIFactoryFunc(&SDL_IID_IDXGIFactory2, (void **)&data->dxgiFactory); + result = pCreateDXGIFactory(&SDL_IID_IDXGIFactory2, (void **)&data->dxgiFactory); } if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("CreateDXGIFactory"), result); + WIN_SetErrorFromHRESULT("CreateDXGIFactory", result); goto done; } - // FIXME: Should we use the default adapter? - result = IDXGIFactory2_EnumAdapters(data->dxgiFactory, 0, &data->dxgiAdapter); - if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("D3D11CreateDevice"), result); - goto done; +#ifdef HAVE_DXGI1_5_H + // Check for tearing support, which requires the IDXGIFactory5 interface. + data->swapChainFlags = 0; + if (!(SDL_GetWindowFlags(renderer->window) & SDL_WINDOW_TRANSPARENT)) { + result = IDXGIFactory2_QueryInterface(data->dxgiFactory, &SDL_IID_IDXGIFactory5, (void **)&dxgiFactory5); + if (SUCCEEDED(result)) { + BOOL allowTearing = FALSE; + result = IDXGIFactory5_CheckFeatureSupport(dxgiFactory5, DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing)); + if (SUCCEEDED(result) && allowTearing) { + data->swapChainFlags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + } + IDXGIFactory5_Release(dxgiFactory5); + } + } +#endif // HAVE_DXGI1_5_H + + if (SDL_GetHintBoolean(SDL_HINT_RENDER_DIRECT3D11_WARP, false)) { + driverType = D3D_DRIVER_TYPE_WARP; + dxgiAdapter = NULL; + } else { + driverType = D3D_DRIVER_TYPE_UNKNOWN; + + // FIXME: Should we use the default adapter? + result = IDXGIFactory2_EnumAdapters(data->dxgiFactory, 0, &dxgiAdapter); + if (FAILED(result)) { + WIN_SetErrorFromHRESULT("EnumAdapters", result); + goto done; + } } /* This flag adds support for surfaces with a different color channel ordering @@ -624,9 +667,9 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) } // Create the Direct3D 11 API device object and a corresponding context. - result = D3D11CreateDeviceFunc( - data->dxgiAdapter, - D3D_DRIVER_TYPE_UNKNOWN, + result = pD3D11CreateDevice( + dxgiAdapter, + driverType, NULL, creationFlags, // Set set debug and Direct2D compatibility flags. featureLevels, // List of feature levels this app can support. @@ -637,25 +680,25 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) &d3dContext // Returns the device immediate context. ); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("D3D11CreateDevice"), result); + WIN_SetErrorFromHRESULT("D3D11CreateDevice", result); goto done; } result = ID3D11Device_QueryInterface(d3dDevice, &SDL_IID_ID3D11Device1, (void **)&data->d3dDevice); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device to ID3D11Device1"), result); + WIN_SetErrorFromHRESULT("ID3D11Device to ID3D11Device1", result); goto done; } result = ID3D11DeviceContext_QueryInterface(d3dContext, &SDL_IID_ID3D11DeviceContext1, (void **)&data->d3dContext); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext to ID3D11DeviceContext1"), result); + WIN_SetErrorFromHRESULT("ID3D11DeviceContext to ID3D11DeviceContext1", result); goto done; } result = ID3D11Device_QueryInterface(d3dDevice, &SDL_IID_IDXGIDevice1, (void **)&dxgiDevice); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device to IDXGIDevice1"), result); + WIN_SetErrorFromHRESULT("ID3D11Device to IDXGIDevice1", result); goto done; } @@ -664,7 +707,7 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) */ result = IDXGIDevice1_SetMaximumFrameLatency(dxgiDevice, 1); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGIDevice1::SetMaximumFrameLatency"), result); + WIN_SetErrorFromHRESULT("IDXGIDevice1::SetMaximumFrameLatency", result); goto done; } @@ -672,31 +715,7 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) * Max texture sizes are documented on MSDN, at: * http://msdn.microsoft.com/en-us/library/windows/apps/ff476876.aspx */ - switch (data->featureLevel) { - case D3D_FEATURE_LEVEL_11_1: - case D3D_FEATURE_LEVEL_11_0: - SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 16384); - break; - - case D3D_FEATURE_LEVEL_10_1: - case D3D_FEATURE_LEVEL_10_0: - SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 8192); - break; - - case D3D_FEATURE_LEVEL_9_3: - SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 4096); - break; - - case D3D_FEATURE_LEVEL_9_2: - case D3D_FEATURE_LEVEL_9_1: - SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 2048); - break; - - default: - SDL_SetError("%s, Unexpected feature level: %d", __FUNCTION__, data->featureLevel); - result = E_FAIL; - goto done; - } + SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 16384); if (!D3D11_CreateVertexShader(data->d3dDevice, &data->vertexShader, &data->inputLayout)) { goto done; @@ -712,7 +731,7 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) NULL, &data->vertexShaderConstants); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateBuffer [vertex shader constants]"), result); + WIN_SetErrorFromHRESULT("ID3D11Device1::CreateBuffer [vertex shader constants]", result); goto done; } @@ -730,14 +749,14 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) rasterDesc.SlopeScaledDepthBias = 0.0f; result = ID3D11Device_CreateRasterizerState(data->d3dDevice, &rasterDesc, &data->mainRasterizer); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateRasterizerState [main rasterizer]"), result); + WIN_SetErrorFromHRESULT("ID3D11Device1::CreateRasterizerState [main rasterizer]", result); goto done; } rasterDesc.ScissorEnable = TRUE; result = ID3D11Device_CreateRasterizerState(data->d3dDevice, &rasterDesc, &data->clippedRasterizer); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateRasterizerState [clipped rasterizer]"), result); + WIN_SetErrorFromHRESULT("ID3D11Device1::CreateRasterizerState [clipped rasterizer]", result); goto done; } @@ -755,6 +774,7 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) SDL_SetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_D3D11_DEVICE_POINTER, data->d3dDevice); done: + SAFE_RELEASE(dxgiAdapter); SAFE_RELEASE(d3dDevice); SAFE_RELEASE(d3dContext); SAFE_RELEASE(dxgiDevice); @@ -870,7 +890,7 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h) } else { swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Windows Store apps must use this SwapEffect. } - swapChainDesc.Flags = 0; + swapChainDesc.Flags = data->swapChainFlags; if (coreWindow) { result = IDXGIFactory2_CreateSwapChainForCoreWindow(data->dxgiFactory, @@ -880,7 +900,7 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h) NULL, // Allow on all displays. &data->swapChain); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGIFactory2::CreateSwapChainForCoreWindow"), result); + WIN_SetErrorFromHRESULT("IDXGIFactory2::CreateSwapChainForCoreWindow", result); goto done; } } else { @@ -900,13 +920,13 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h) NULL, // Allow on all displays. &data->swapChain); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGIFactory2::CreateSwapChainForHwnd"), result); + WIN_SetErrorFromHRESULT("IDXGIFactory2::CreateSwapChainForHwnd", result); goto done; } IDXGIFactory_MakeWindowAssociation(data->dxgiFactory, hwnd, DXGI_MWA_NO_WINDOW_CHANGES); #else - SDL_SetError(__FUNCTION__ ", Unable to find something to attach a swap chain to"); + SDL_SetError("%s, Unable to find something to attach a swap chain to", SDL_FUNCTION); goto done; #endif // defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) / else } @@ -931,7 +951,7 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h) (colorspace_support & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) { result = IDXGISwapChain3_SetColorSpace1(swapChain3, colorspace); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain3::SetColorSpace1"), result); + WIN_SetErrorFromHRESULT("IDXGISwapChain3::SetColorSpace1", result); goto done; } } else if (colorspace != DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709) { @@ -956,6 +976,28 @@ static void D3D11_ReleaseMainRenderTargetView(SDL_Renderer *renderer) SAFE_RELEASE(data->mainRenderTargetView); } +static void D3D11_UpdatePresentFlags(SDL_Renderer *renderer) +{ + D3D11_RenderData *data = (D3D11_RenderData *)renderer->internal; + + if (data->syncInterval > 0) { + data->presentFlags = 0; + } else { + data->presentFlags = DXGI_PRESENT_DO_NOT_WAIT; +#ifdef HAVE_DXGI1_5_H + // Present tearing requires sync interval 0, a swap chain flag, and not in exclusive fullscreen mode. + if ((data->swapChainFlags & DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING)) { + HRESULT result = S_OK; + BOOL fullscreenState = FALSE; + result = IDXGISwapChain_GetFullscreenState(data->swapChain, &fullscreenState, NULL); + if (SUCCEEDED(result) && !fullscreenState) { + data->presentFlags = DXGI_PRESENT_ALLOW_TEARING; + } + } +#endif // HAVE_DXGI1_5_H + } +} + // Initialize all resources that change when the window's size changes. static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) { @@ -972,7 +1014,7 @@ static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) */ SDL_GetWindowSizeInPixels(renderer->window, &w, &h); data->rotation = D3D11_GetCurrentRotation(); - // SDL_Log("%s: windowSize={%d,%d}, orientation=%d", __FUNCTION__, w, h, (int)data->rotation); + // SDL_Log("%s: windowSize={%d,%d}, orientation=%d", SDL_FUNCTION, w, h, (int)data->rotation); if (D3D11_IsDisplayRotated90Degrees(data->rotation)) { int tmp = w; w = h; @@ -985,9 +1027,9 @@ static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) 0, w, h, DXGI_FORMAT_UNKNOWN, - 0); + data->swapChainFlags); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain::ResizeBuffers"), result); + WIN_SetErrorFromHRESULT("IDXGISwapChain::ResizeBuffers", result); goto done; } } else { @@ -997,12 +1039,14 @@ static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) } } + D3D11_UpdatePresentFlags(renderer); + // Set the proper rotation for the swap chain. if (WIN_IsWindows8OrGreater()) { if (data->swapEffect == DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL) { result = IDXGISwapChain1_SetRotation(data->swapChain, data->rotation); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain1::SetRotation"), result); + WIN_SetErrorFromHRESULT("IDXGISwapChain1::SetRotation", result); goto done; } } @@ -1013,7 +1057,7 @@ static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) &SDL_IID_ID3D11Texture2D, (void **)&backBuffer); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain::GetBuffer [back-buffer]"), result); + WIN_SetErrorFromHRESULT("IDXGISwapChain::GetBuffer [back-buffer]", result); goto done; } @@ -1023,7 +1067,7 @@ static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) NULL, &data->mainRenderTargetView); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device::CreateRenderTargetView"), result); + WIN_SetErrorFromHRESULT("ID3D11Device::CreateRenderTargetView", result); goto done; } @@ -1112,6 +1156,73 @@ static bool GetTextureProperty(SDL_PropertiesID props, const char *name, ID3D11T return true; } +static bool D3D11_CreatePalette(SDL_Renderer *renderer, SDL_TexturePalette *palette) +{ + D3D11_RenderData *data = (D3D11_RenderData *)renderer->internal; + D3D11_PaletteData *palettedata = (D3D11_PaletteData *)SDL_calloc(1, sizeof(*palettedata)); + if (!palettedata) { + return false; + } + palette->internal = palettedata; + + if (!data->d3dDevice) { + return SDL_SetError("Device lost and couldn't be recovered"); + } + + D3D11_TEXTURE2D_DESC textureDesc; + SDL_zero(textureDesc); + textureDesc.Width = 256; + textureDesc.Height = 1; + textureDesc.MipLevels = 1; + textureDesc.ArraySize = 1; + textureDesc.Format = SDLPixelFormatToDXGITextureFormat(SDL_PIXELFORMAT_RGBA32, renderer->output_colorspace); + textureDesc.SampleDesc.Count = 1; + textureDesc.SampleDesc.Quality = 0; + textureDesc.MiscFlags = 0; + textureDesc.Usage = D3D11_USAGE_DEFAULT; + textureDesc.CPUAccessFlags = 0; + textureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + HRESULT result = ID3D11Device_CreateTexture2D(data->d3dDevice, &textureDesc, NULL, &palettedata->texture); + if (FAILED(result)) { + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D", result); + } + + D3D11_SHADER_RESOURCE_VIEW_DESC resourceViewDesc; + SDL_zero(resourceViewDesc); + resourceViewDesc.Format = SDLPixelFormatToDXGIMainResourceViewFormat(SDL_PIXELFORMAT_RGBA32, renderer->output_colorspace); + resourceViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + resourceViewDesc.Texture2D.MostDetailedMip = 0; + resourceViewDesc.Texture2D.MipLevels = textureDesc.MipLevels; + result = ID3D11Device_CreateShaderResourceView(data->d3dDevice, + (ID3D11Resource *)palettedata->texture, + &resourceViewDesc, + &palettedata->resourceView); + if (FAILED(result)) { + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateShaderResourceView", result); + } + return true; +} + +static bool D3D11_UpdatePalette(SDL_Renderer *renderer, SDL_TexturePalette *palette, int ncolors, SDL_Color *colors) +{ + D3D11_RenderData *data = (D3D11_RenderData *)renderer->internal; + D3D11_PaletteData *palettedata = (D3D11_PaletteData *)palette->internal; + + return D3D11_UpdateTextureInternal(data, palettedata->texture, 4, 0, 0, ncolors, 1, colors, ncolors * sizeof(*colors)); +} + +static void D3D11_DestroyPalette(SDL_Renderer *renderer, SDL_TexturePalette *palette) +{ + D3D11_PaletteData *palettedata = (D3D11_PaletteData *)palette->internal; + + if (palettedata) { + SAFE_RELEASE(palettedata->texture); + SAFE_RELEASE(palettedata->resourceView); + SDL_free(palettedata); + } +} + static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->internal; @@ -1127,7 +1238,7 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD if (textureFormat == DXGI_FORMAT_UNKNOWN) { return SDL_SetError("%s, An unsupported SDL pixel format (0x%x) was specified", - __FUNCTION__, texture->format); + SDL_FUNCTION, texture->format); } textureData = (D3D11_TextureData *)SDL_calloc(1, sizeof(*textureData)); @@ -1156,11 +1267,6 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD } textureData->w = (int)textureDesc.Width; textureData->h = (int)textureDesc.Height; - if (SDL_COLORSPACETRANSFER(texture->colorspace) == SDL_TRANSFER_CHARACTERISTICS_SRGB) { - textureData->shader = SHADER_RGB; - } else { - textureData->shader = SHADER_ADVANCED; - } if (texture->access == SDL_TEXTUREACCESS_STREAMING) { textureDesc.Usage = D3D11_USAGE_DYNAMIC; @@ -1185,10 +1291,11 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD NULL, &textureData->mainTexture); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D", result); } } SDL_SetPointerProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_D3D11_TEXTURE_POINTER, textureData->mainTexture); + #ifdef SDL_HAVE_YUV if (texture->format == SDL_PIXELFORMAT_YV12 || texture->format == SDL_PIXELFORMAT_IYUV) { @@ -1206,7 +1313,7 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD NULL, &textureData->mainTextureU); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D", result); } } SDL_SetPointerProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_D3D11_TEXTURE_U_POINTER, textureData->mainTextureU); @@ -1220,7 +1327,7 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD NULL, &textureData->mainTextureV); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D", result); } } SDL_SetPointerProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_D3D11_TEXTURE_V_POINTER, textureData->mainTextureV); @@ -1261,8 +1368,9 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD &resourceViewDesc, &textureData->mainTextureResourceView); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateShaderResourceView"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateShaderResourceView", result); } + #ifdef SDL_HAVE_YUV if (textureData->yuv) { result = ID3D11Device_CreateShaderResourceView(rendererData->d3dDevice, @@ -1270,14 +1378,14 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD &resourceViewDesc, &textureData->mainTextureResourceViewU); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateShaderResourceView"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateShaderResourceView", result); } result = ID3D11Device_CreateShaderResourceView(rendererData->d3dDevice, (ID3D11Resource *)textureData->mainTextureV, &resourceViewDesc, &textureData->mainTextureResourceViewV); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateShaderResourceView"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateShaderResourceView", result); } } @@ -1295,7 +1403,7 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD &nvResourceViewDesc, &textureData->mainTextureResourceViewNV); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateShaderResourceView"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateShaderResourceView", result); } } #endif // SDL_HAVE_YUV @@ -1312,7 +1420,7 @@ static bool D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD &renderTargetViewDesc, &textureData->mainTextureRenderTargetView); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateRenderTargetView"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateRenderTargetView", result); } } @@ -1373,7 +1481,7 @@ static bool D3D11_UpdateTextureInternal(D3D11_RenderData *rendererData, ID3D11Te NULL, &stagingTexture); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D [create staging texture]"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D [create staging texture]", result); } // Get a write-only pointer to data in the staging texture: @@ -1385,7 +1493,7 @@ static bool D3D11_UpdateTextureInternal(D3D11_RenderData *rendererData, ID3D11Te &textureMemory); if (FAILED(result)) { SAFE_RELEASE(stagingTexture); - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext1::Map [map staging texture]"), result); + return WIN_SetErrorFromHRESULT("ID3D11DeviceContext1::Map [map staging texture]", result); } src = (const Uint8 *)pixels; @@ -1473,22 +1581,25 @@ static bool D3D11_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, #ifdef SDL_HAVE_YUV if (textureData->nv12) { - const Uint8 *Yplane = (const Uint8 *)srcPixels; - const Uint8 *UVplane = Yplane + rect->h * srcPitch; + int UVbpp = SDL_BYTESPERPIXEL(texture->format) * 2; + int Ypitch = srcPitch; + int UVpitch = (srcPitch + (UVbpp - 1)) & ~(UVbpp - 1); + const Uint8 *plane0 = (const Uint8 *)srcPixels; + const Uint8 *plane1 = plane0 + rect->h * srcPitch; - return D3D11_UpdateTextureNV(renderer, texture, rect, Yplane, srcPitch, UVplane, srcPitch); + return D3D11_UpdateTextureNV(renderer, texture, rect, plane0, Ypitch, plane1, UVpitch); } else if (textureData->yuv) { int Ypitch = srcPitch; int UVpitch = ((Ypitch + 1) / 2); - const Uint8 *Yplane = (const Uint8 *)srcPixels; - const Uint8 *Uplane = Yplane + rect->h * Ypitch; - const Uint8 *Vplane = Uplane + ((rect->h + 1) / 2) * UVpitch; + const Uint8 *plane0 = (const Uint8 *)srcPixels; + const Uint8 *plane1 = plane0 + rect->h * Ypitch; + const Uint8 *plane2 = plane1 + ((rect->h + 1) / 2) * UVpitch; if (texture->format == SDL_PIXELFORMAT_YV12) { - return D3D11_UpdateTextureYUV(renderer, texture, rect, Yplane, Ypitch, Vplane, UVpitch, Uplane, UVpitch); + return D3D11_UpdateTextureYUV(renderer, texture, rect, plane0, Ypitch, plane2, UVpitch, plane1, UVpitch); } else { - return D3D11_UpdateTextureYUV(renderer, texture, rect, Yplane, Ypitch, Uplane, UVpitch, Vplane, UVpitch); + return D3D11_UpdateTextureYUV(renderer, texture, rect, plane0, Ypitch, plane1, UVpitch, plane2, UVpitch); } } #endif @@ -1540,6 +1651,7 @@ static bool D3D11_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture, HRESULT result; D3D11_TEXTURE2D_DESC stagingTextureDesc; D3D11_MAPPED_SUBRESOURCE textureMemory; + int bpp = SDL_BYTESPERPIXEL(texture->format); if (!textureData) { return SDL_SetError("Texture is not currently available"); @@ -1566,7 +1678,7 @@ static bool D3D11_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture, NULL, &stagingTexture); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D [create staging texture]"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D [create staging texture]", result); } // Get a write-only pointer to data in the staging texture: @@ -1578,12 +1690,12 @@ static bool D3D11_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture, &textureMemory); if (FAILED(result)) { SAFE_RELEASE(stagingTexture); - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext1::Map [map staging texture]"), result); + return WIN_SetErrorFromHRESULT("ID3D11DeviceContext1::Map [map staging texture]", result); } src = Yplane; dst = (Uint8 *)textureMemory.pData; - length = w; + length = w * bpp; if (length == (UINT)Ypitch && length == textureMemory.RowPitch) { SDL_memcpy(dst, src, (size_t)length * h); } else { @@ -1601,7 +1713,7 @@ static bool D3D11_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture, } src = UVplane; - length = w; + length = ((w + 1) / 2) * 2 * bpp; h = (h + 1) / 2; if (stagingTextureDesc.Format == DXGI_FORMAT_P010) { length = (length + 3) & ~3; @@ -1691,7 +1803,7 @@ static bool D3D11_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, NULL, &textureData->stagingTexture); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D [create staging texture]"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D [create staging texture]", result); } // Get a write-only pointer to data in the staging texture: @@ -1703,7 +1815,7 @@ static bool D3D11_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, &textureMemory); if (FAILED(result)) { SAFE_RELEASE(textureData->stagingTexture); - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext1::Map [map staging texture]"), result); + return WIN_SetErrorFromHRESULT("ID3D11DeviceContext1::Map [map staging texture]", result); } /* Make note of where the staging texture will be written to @@ -1890,7 +2002,7 @@ static bool D3D11_UpdateVertexBuffer(SDL_Renderer *renderer, 0, &mappedResource); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext1::Map [vertex buffer]"), result); + return WIN_SetErrorFromHRESULT("ID3D11DeviceContext1::Map [vertex buffer]", result); } SDL_memcpy(mappedResource.pData, vertexData, dataSizeInBytes); ID3D11DeviceContext_Unmap(rendererData->d3dContext, (ID3D11Resource *)rendererData->vertexBuffers[vbidx], 0); @@ -1916,7 +2028,7 @@ static bool D3D11_UpdateVertexBuffer(SDL_Renderer *renderer, &vertexBufferData, &rendererData->vertexBuffers[vbidx]); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateBuffer [vertex buffer]"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateBuffer [vertex buffer]", result); } rendererData->vertexBufferSizes[vbidx] = dataSizeInBytes; @@ -1953,7 +2065,7 @@ static bool D3D11_UpdateViewport(SDL_Renderer *renderer) * SDL_CreateRenderer is calling it, and will call it again later * with a non-empty viewport. */ - // SDL_Log("%s, no viewport was set!", __FUNCTION__); + // SDL_Log("%s, no viewport was set!", SDL_FUNCTION); return false; } @@ -2020,7 +2132,7 @@ static bool D3D11_UpdateViewport(SDL_Renderer *renderer) d3dviewport.Height = orientationAlignedViewport.h; d3dviewport.MinDepth = 0.0f; d3dviewport.MaxDepth = 1.0f; - // SDL_Log("%s: D3D viewport = {%f,%f,%f,%f}", __FUNCTION__, d3dviewport.TopLeftX, d3dviewport.TopLeftY, d3dviewport.Width, d3dviewport.Height); + // SDL_Log("%s: D3D viewport = {%f,%f,%f,%f}", SDL_FUNCTION, d3dviewport.TopLeftX, d3dviewport.TopLeftY, d3dviewport.Width, d3dviewport.Height); ID3D11DeviceContext_RSSetViewports(data->d3dContext, 1, &d3dviewport); data->viewportDirty = false; @@ -2051,6 +2163,22 @@ static void D3D11_SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderC D3D11_TextureData *textureData = (D3D11_TextureData *)texture->internal; switch (texture->format) { + case SDL_PIXELFORMAT_INDEX8: + switch (cmd->data.draw.texture_scale_mode) { + case SDL_SCALEMODE_NEAREST: + constants->texture_type = TEXTURETYPE_PALETTE_NEAREST; + break; + case SDL_SCALEMODE_LINEAR: + constants->texture_type = TEXTURETYPE_PALETTE_LINEAR; + break; + case SDL_SCALEMODE_PIXELART: + constants->texture_type = TEXTURETYPE_PALETTE_PIXELART; + break; + default: + SDL_assert(!"Unknown scale mode"); + break; + } + break; case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: constants->texture_type = TEXTURETYPE_YUV; @@ -2071,10 +2199,6 @@ static void D3D11_SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderC default: if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART) { constants->texture_type = TEXTURETYPE_RGB_PIXELART; - constants->texture_width = texture->w; - constants->texture_height = texture->h; - constants->texel_width = 1.0f / constants->texture_width; - constants->texel_height = 1.0f / constants->texture_height; } else { constants->texture_type = TEXTURETYPE_RGB; } @@ -2089,6 +2213,15 @@ static void D3D11_SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderC break; } + if (constants->texture_type == TEXTURETYPE_PALETTE_LINEAR || + constants->texture_type == TEXTURETYPE_PALETTE_PIXELART || + constants->texture_type == TEXTURETYPE_RGB_PIXELART) { + constants->texture_width = texture->w; + constants->texture_height = texture->h; + constants->texel_width = 1.0f / constants->texture_width; + constants->texel_height = 1.0f / constants->texture_height; + } + constants->sdr_white_point = texture->SDR_white_point; if (renderer->target) { @@ -2097,7 +2230,7 @@ static void D3D11_SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderC output_headroom = renderer->HDR_headroom; } - if (texture->HDR_headroom > output_headroom) { + if (texture->HDR_headroom > output_headroom && output_headroom > 0.0f) { constants->tonemap_method = TONEMAP_CHROME; constants->tonemap_factor1 = (output_headroom / (texture->HDR_headroom * texture->HDR_headroom)); constants->tonemap_factor2 = (1.0f / output_headroom); @@ -2109,34 +2242,54 @@ static void D3D11_SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderC } } +static D3D11_Shader SelectShader(const D3D11_PixelShaderConstants *shader_constants) +{ + if (!shader_constants) { + return SHADER_SOLID; + } + + if (shader_constants->texture_type == TEXTURETYPE_RGB && + shader_constants->input_type == INPUTTYPE_UNSPECIFIED && + shader_constants->tonemap_method == TONEMAP_NONE) { + return SHADER_RGB; + } + + return SHADER_ADVANCED; +} + static bool D3D11_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, - D3D11_Shader shader, const D3D11_PixelShaderConstants *shader_constants, - const int numShaderResources, ID3D11ShaderResourceView **shaderResources, - ID3D11SamplerState *sampler, const Float4X4 *matrix) + const D3D11_PixelShaderConstants *shader_constants, + int numShaderResources, ID3D11ShaderResourceView **shaderResources, + int numShaderSamplers, ID3D11SamplerState **shaderSamplers, const Float4X4 *matrix) { D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->internal; const Float4X4 *newmatrix = matrix ? matrix : &rendererData->identity; ID3D11RasterizerState *rasterizerState; ID3D11RenderTargetView *renderTargetView = D3D11_GetCurrentRenderTargetView(renderer); - ID3D11ShaderResourceView *shaderResource; const SDL_BlendMode blendMode = cmd->data.draw.blend; ID3D11BlendState *blendState = NULL; bool updateSubresource = false; + D3D11_Shader shader = SelectShader(shader_constants); D3D11_PixelShaderState *shader_state = &rendererData->currentShaderState[shader]; D3D11_PixelShaderConstants solid_constants; - if (numShaderResources > 0) { - shaderResource = shaderResources[0]; - } else { - shaderResource = NULL; + bool shaderResourcesChanged = false; + if (numShaderResources != rendererData->numCurrentShaderResources || + (numShaderResources > 0 && shaderResources[0] != rendererData->currentShaderResource)) { + shaderResourcesChanged = true; + } + + bool shaderSamplersChanged = false; + if (numShaderSamplers != rendererData->numCurrentShaderSamplers || + (numShaderSamplers > 0 && shaderSamplers[0] != rendererData->currentShaderSampler)) { + shaderSamplersChanged = true; } // Make sure the render target isn't bound to a shader - if (shaderResource != rendererData->currentShaderResource) { + if (shaderResourcesChanged) { ID3D11ShaderResourceView *pNullResource = NULL; ID3D11DeviceContext_PSSetShaderResources(rendererData->d3dContext, 0, 1, &pNullResource); - rendererData->currentShaderResource = NULL; } if (renderTargetView != rendererData->currentRenderTargetView) { @@ -2219,7 +2372,7 @@ static bool D3D11_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand * HRESULT result = ID3D11Device_CreateBuffer(rendererData->d3dDevice, &desc, &data, &shader_state->constants); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device::CreateBuffer [create shader constants]"), result); + WIN_SetErrorFromHRESULT("ID3D11Device::CreateBuffer [create shader constants]", result); return false; } SDL_memcpy(&shader_state->shader_constants, shader_constants, sizeof(*shader_constants)); @@ -2239,13 +2392,19 @@ static bool D3D11_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand * } rendererData->currentShader = shader; } - if (shaderResource != rendererData->currentShaderResource) { + if (shaderResourcesChanged) { ID3D11DeviceContext_PSSetShaderResources(rendererData->d3dContext, 0, numShaderResources, shaderResources); - rendererData->currentShaderResource = shaderResource; + rendererData->numCurrentShaderResources = numShaderResources; + if (numShaderResources > 0) { + rendererData->currentShaderResource = shaderResources[0]; + } } - if (sampler != rendererData->currentSampler) { - ID3D11DeviceContext_PSSetSamplers(rendererData->d3dContext, 0, 1, &sampler); - rendererData->currentSampler = sampler; + if (shaderSamplersChanged) { + ID3D11DeviceContext_PSSetSamplers(rendererData->d3dContext, 0, numShaderSamplers, shaderSamplers); + rendererData->numCurrentShaderSamplers = numShaderSamplers; + if (numShaderSamplers) { + rendererData->currentShaderSampler = shaderSamplers[0]; + } } if (updateSubresource == true || SDL_memcmp(&rendererData->vertexShaderConstantsData.model, newmatrix, sizeof(*newmatrix)) != 0) { @@ -2262,8 +2421,13 @@ static bool D3D11_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand * return true; } -static ID3D11SamplerState *D3D11_GetSamplerState(D3D11_RenderData *data, SDL_ScaleMode scale_mode, SDL_TextureAddressMode address_u, SDL_TextureAddressMode address_v) +static ID3D11SamplerState *D3D11_GetSamplerState(D3D11_RenderData *data, SDL_PixelFormat format, SDL_ScaleMode scale_mode, SDL_TextureAddressMode address_u, SDL_TextureAddressMode address_v) { + if (format == SDL_PIXELFORMAT_INDEX8) { + // We'll do linear sampling in the shader if needed + scale_mode = SDL_SCALEMODE_NEAREST; + } + Uint32 key = RENDER_SAMPLER_HASHKEY(scale_mode, address_u, address_v); SDL_assert(key < SDL_arraysize(data->samplers)); if (!data->samplers[key]) { @@ -2325,7 +2489,10 @@ static bool D3D11_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand * SDL_Texture *texture = cmd->data.draw.texture; D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->internal; D3D11_TextureData *textureData = (D3D11_TextureData *)texture->internal; - ID3D11SamplerState *textureSampler; + int numShaderResources = 0; + ID3D11ShaderResourceView *shaderResources[3]; + int numShaderSamplers = 0; + ID3D11SamplerState *shaderSamplers[2]; D3D11_PixelShaderConstants constants; if (!textureData) { @@ -2334,34 +2501,35 @@ static bool D3D11_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand * D3D11_SetupShaderConstants(renderer, cmd, texture, &constants); - textureSampler = D3D11_GetSamplerState(rendererData, cmd->data.draw.texture_scale_mode, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v); - if (!textureSampler) { + shaderResources[numShaderResources++] = textureData->mainTextureResourceView; + + shaderSamplers[numShaderSamplers] = D3D11_GetSamplerState(rendererData, texture->format, cmd->data.draw.texture_scale_mode, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v); + if (!shaderSamplers[numShaderSamplers]) { return false; } + ++numShaderSamplers; + + if (texture->palette) { + D3D11_PaletteData *palette = (D3D11_PaletteData *)texture->palette->internal; + + shaderResources[numShaderResources++] = palette->resourceView; + + shaderSamplers[numShaderSamplers] = D3D11_GetSamplerState(rendererData, SDL_PIXELFORMAT_UNKNOWN, SDL_SCALEMODE_NEAREST, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP); + if (!shaderSamplers[numShaderSamplers]) { + return false; + } + ++numShaderSamplers; + } #ifdef SDL_HAVE_YUV if (textureData->yuv) { - ID3D11ShaderResourceView *shaderResources[3]; - - shaderResources[0] = textureData->mainTextureResourceView; - shaderResources[1] = textureData->mainTextureResourceViewU; - shaderResources[2] = textureData->mainTextureResourceViewV; - - return D3D11_SetDrawState(renderer, cmd, textureData->shader, &constants, - SDL_arraysize(shaderResources), shaderResources, textureSampler, matrix); - + shaderResources[numShaderResources++] = textureData->mainTextureResourceViewU; + shaderResources[numShaderResources++] = textureData->mainTextureResourceViewV; } else if (textureData->nv12) { - ID3D11ShaderResourceView *shaderResources[2]; - - shaderResources[0] = textureData->mainTextureResourceView; - shaderResources[1] = textureData->mainTextureResourceViewNV; - - return D3D11_SetDrawState(renderer, cmd, textureData->shader, &constants, - SDL_arraysize(shaderResources), shaderResources, textureSampler, matrix); + shaderResources[numShaderResources++] = textureData->mainTextureResourceViewNV; } #endif // SDL_HAVE_YUV - return D3D11_SetDrawState(renderer, cmd, textureData->shader, &constants, - 1, &textureData->mainTextureResourceView, textureSampler, matrix); + return D3D11_SetDrawState(renderer, cmd, &constants, numShaderResources, shaderResources, numShaderSamplers, shaderSamplers, matrix); } static void D3D11_DrawPrimitives(SDL_Renderer *renderer, D3D11_PRIMITIVE_TOPOLOGY primitiveTopology, const size_t vertexStart, const size_t vertexCount) @@ -2378,8 +2546,10 @@ static void D3D11_InvalidateCachedState(SDL_Renderer *renderer) data->currentRasterizerState = NULL; data->currentBlendState = NULL; data->currentShader = SHADER_NONE; + data->numCurrentShaderResources = 0; data->currentShaderResource = NULL; - data->currentSampler = NULL; + data->numCurrentShaderSamplers = 0; + data->currentShaderSampler = NULL; data->cliprectDirty = true; data->viewportDirty = true; } @@ -2453,26 +2623,60 @@ static bool D3D11_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd break; } - case SDL_RENDERCMD_DRAW_POINTS: - { - const size_t count = cmd->data.draw.count; - const size_t first = cmd->data.draw.first; - const size_t start = first / sizeof(D3D11_VertexPositionColor); - D3D11_SetDrawState(renderer, cmd, SHADER_SOLID, NULL, 0, NULL, NULL, NULL); - D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start, count); - break; - } - case SDL_RENDERCMD_DRAW_LINES: { - const size_t count = cmd->data.draw.count; + size_t count = cmd->data.draw.count; const size_t first = cmd->data.draw.first; const size_t start = first / sizeof(D3D11_VertexPositionColor); const D3D11_VertexPositionColor *verts = (D3D11_VertexPositionColor *)(((Uint8 *)vertices) + first); - D3D11_SetDrawState(renderer, cmd, SHADER_SOLID, NULL, 0, NULL, NULL, NULL); - D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP, start, count); - if (verts[0].pos.x != verts[count - 1].pos.x || verts[0].pos.y != verts[count - 1].pos.y) { - D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start + (count - 1), 1); + + D3D11_SetDrawState(renderer, cmd, NULL, 0, NULL, 0, NULL, NULL); + + // Add the final point in the line + size_t line_start = 0; + size_t line_end = line_start + count - 1; + if (verts[line_start].pos.x != verts[line_end].pos.x || verts[line_start].pos.y != verts[line_end].pos.y) { + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start + line_end, 1); + } + + if (count > 2) { + // joined lines cannot be grouped + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP, start, count); + } else { + // let's group non joined lines + SDL_RenderCommand *finalcmd = cmd; + SDL_RenderCommand *nextcmd; + float thiscolorscale = cmd->data.draw.color_scale; + SDL_BlendMode thisblend = cmd->data.draw.blend; + + for (nextcmd = cmd->next; nextcmd; nextcmd = nextcmd->next) { + const SDL_RenderCommandType nextcmdtype = nextcmd->command; + if (nextcmdtype != SDL_RENDERCMD_DRAW_LINES) { + if (nextcmdtype == SDL_RENDERCMD_SETDRAWCOLOR) { + // The vertex data has the draw color built in, ignore this + continue; + } + break; // can't go any further on this draw call, different render command up next. + } else if (nextcmd->data.draw.count != 2) { + break; // can't go any further on this draw call, those are joined lines + } else if (nextcmd->data.draw.blend != thisblend || + nextcmd->data.draw.color_scale != thiscolorscale) { + break; // can't go any further on this draw call, different blendmode copy up next. + } else { + finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command. + + // Add the final point in the line + line_start = count; + line_end = line_start + nextcmd->data.draw.count - 1; + if (verts[line_start].pos.x != verts[line_end].pos.x || verts[line_start].pos.y != verts[line_end].pos.y) { + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start + line_end, 1); + } + count += nextcmd->data.draw.count; + } + } + + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_LINELIST, start, count); + cmd = finalcmd; // skip any copy commands we just combined in here. } break; } @@ -2486,20 +2690,56 @@ static bool D3D11_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd case SDL_RENDERCMD_COPY_EX: // unused break; + case SDL_RENDERCMD_DRAW_POINTS: case SDL_RENDERCMD_GEOMETRY: { - SDL_Texture *texture = cmd->data.draw.texture; - const size_t count = cmd->data.draw.count; + /* as long as we have the same copy command in a row, with the + same texture, we can combine them all into a single draw call. */ + float thiscolorscale = cmd->data.draw.color_scale; + SDL_Texture *thistexture = cmd->data.draw.texture; + SDL_BlendMode thisblend = cmd->data.draw.blend; + SDL_ScaleMode thisscalemode = cmd->data.draw.texture_scale_mode; + SDL_TextureAddressMode thisaddressmode_u = cmd->data.draw.texture_address_mode_u; + SDL_TextureAddressMode thisaddressmode_v = cmd->data.draw.texture_address_mode_v; + const SDL_RenderCommandType thiscmdtype = cmd->command; + SDL_RenderCommand *finalcmd = cmd; + SDL_RenderCommand *nextcmd; + size_t count = cmd->data.draw.count; const size_t first = cmd->data.draw.first; const size_t start = first / sizeof(D3D11_VertexPositionColor); - - if (texture) { - D3D11_SetCopyState(renderer, cmd, NULL); - } else { - D3D11_SetDrawState(renderer, cmd, SHADER_SOLID, NULL, 0, NULL, NULL, NULL); + for (nextcmd = cmd->next; nextcmd; nextcmd = nextcmd->next) { + const SDL_RenderCommandType nextcmdtype = nextcmd->command; + if (nextcmdtype != thiscmdtype) { + if (nextcmdtype == SDL_RENDERCMD_SETDRAWCOLOR) { + // The vertex data has the draw color built in, ignore this + continue; + } + break; // can't go any further on this draw call, different render command up next. + } else if (nextcmd->data.draw.texture != thistexture || + nextcmd->data.draw.texture_scale_mode != thisscalemode || + nextcmd->data.draw.texture_address_mode_u != thisaddressmode_u || + nextcmd->data.draw.texture_address_mode_v != thisaddressmode_v || + nextcmd->data.draw.blend != thisblend || + nextcmd->data.draw.color_scale != thiscolorscale) { + break; // can't go any further on this draw call, different texture/blendmode copy up next. + } else { + finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command. + count += nextcmd->data.draw.count; + } } - D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST, start, count); + if (thistexture) { + D3D11_SetCopyState(renderer, cmd, NULL); + } else { + D3D11_SetDrawState(renderer, cmd, NULL, 0, NULL, 0, NULL, NULL); + } + + if (thiscmdtype == SDL_RENDERCMD_GEOMETRY) { + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST, start, count); + } else { + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start, count); + } + cmd = finalcmd; // skip any copy commands we just combined in here. break; } @@ -2528,13 +2768,13 @@ static SDL_Surface *D3D11_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rec renderTargetView = D3D11_GetCurrentRenderTargetView(renderer); if (!renderTargetView) { - SDL_SetError("%s, ID3D11DeviceContext::OMGetRenderTargets failed", __FUNCTION__); + SDL_SetError("%s, ID3D11DeviceContext::OMGetRenderTargets failed", SDL_FUNCTION); goto done; } ID3D11View_GetResource(renderTargetView, (ID3D11Resource **)&backBuffer); if (!backBuffer) { - SDL_SetError("%s, ID3D11View::GetResource failed", __FUNCTION__); + SDL_SetError("%s, ID3D11View::GetResource failed", SDL_FUNCTION); goto done; } @@ -2551,7 +2791,7 @@ static SDL_Surface *D3D11_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rec NULL, &stagingTexture); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D [create staging texture]"), result); + WIN_SetErrorFromHRESULT("ID3D11Device1::CreateTexture2D [create staging texture]", result); goto done; } @@ -2583,7 +2823,7 @@ static SDL_Surface *D3D11_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rec 0, &textureMemory); if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext1::Map [map staging texture]"), result); + WIN_SetErrorFromHRESULT("ID3D11DeviceContext1::Map [map staging texture]", result); goto done; } @@ -2622,12 +2862,6 @@ static bool D3D11_RenderPresent(SDL_Renderer *renderer) */ result = IDXGISwapChain1_Present1(data->swapChain, data->syncInterval, data->presentFlags, ¶meters); - /* Discard the contents of the render target. - * This is a valid operation only when the existing contents will be entirely - * overwritten. If dirty or scroll rects are used, this call should be removed. - */ - ID3D11DeviceContext1_DiscardView(data->d3dContext, (ID3D11View *)data->mainRenderTargetView); - // When the present flips, it unbinds the current view, so bind it again on the next draw call data->currentRenderTargetView = NULL; @@ -2644,9 +2878,9 @@ static bool D3D11_RenderPresent(SDL_Renderer *renderer) } else if (result == DXGI_ERROR_INVALID_CALL) { // We probably went through a fullscreen <-> windowed transition D3D11_CreateWindowSizeDependentResources(renderer); - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain::Present"), result); + WIN_SetErrorFromHRESULT("IDXGISwapChain::Present", result); } else { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain::Present"), result); + WIN_SetErrorFromHRESULT("IDXGISwapChain::Present", result); } return false; } @@ -2663,11 +2897,10 @@ static bool D3D11_SetVSync(SDL_Renderer *renderer, const int vsync) if (vsync > 0) { data->syncInterval = vsync; - data->presentFlags = 0; } else { data->syncInterval = 0; - data->presentFlags = DXGI_PRESENT_DO_NOT_WAIT; } + D3D11_UpdatePresentFlags(renderer); return true; } @@ -2697,6 +2930,9 @@ static bool D3D11_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL renderer->WindowEvent = D3D11_WindowEvent; renderer->SupportsBlendMode = D3D11_SupportsBlendMode; + renderer->CreatePalette = D3D11_CreatePalette; + renderer->UpdatePalette = D3D11_UpdatePalette; + renderer->DestroyPalette = D3D11_DestroyPalette; renderer->CreateTexture = D3D11_CreateTexture; renderer->UpdateTexture = D3D11_UpdateTexture; #ifdef SDL_HAVE_YUV @@ -2727,12 +2963,14 @@ static bool D3D11_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XRGB8888); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR2101010); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA64_FLOAT); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_INDEX8); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_YV12); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_IYUV); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV12); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV21); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_P010); + data->swapChainFlags = 0; data->syncInterval = 0; data->presentFlags = DXGI_PRESENT_DO_NOT_WAIT; @@ -2749,6 +2987,12 @@ static bool D3D11_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL return false; } + // DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_B5G5R5A1_UNORM is supported since Direct3D 11.1 on Windows 8 and later + if (data->featureLevel >= D3D_FEATURE_LEVEL_11_1 && WIN_IsWindows8OrGreater()) { + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGB565); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB1555); + } + return true; } diff --git a/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.c b/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.c index 18965e2d..d360d64e 100644 --- a/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.c +++ b/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -28,8 +28,6 @@ #include "SDL_shaders_d3d11.h" -#define SDL_COMPOSE_ERROR(str) SDL_STRINGIFY_ARG(__FUNCTION__) ", " str - #if SDL_WINAPI_FAMILY_PHONE #error Need to build shaders with level_9_3 #endif @@ -82,7 +80,7 @@ bool D3D11_CreateVertexShader(ID3D11Device1 *d3dDevice, ID3D11VertexShader **ver NULL, vertexShader); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateVertexShader"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateVertexShader", result); } // Create an input layout for SDL's vertex shader: @@ -93,7 +91,7 @@ bool D3D11_CreateVertexShader(ID3D11Device1 *d3dDevice, ID3D11VertexShader **ver sizeof(D3D11_VertexShader), inputLayout); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateInputLayout"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreateInputLayout", result); } return true; } @@ -108,7 +106,7 @@ bool D3D11_CreatePixelShader(ID3D11Device1 *d3dDevice, D3D11_Shader shader, ID3D NULL, pixelShader); if (FAILED(result)) { - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreatePixelShader"), result); + return WIN_SetErrorFromHRESULT("ID3D11Device1::CreatePixelShader", result); } return true; } diff --git a/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.h b/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.h index 2279fb0c..7c389250 100644 --- a/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.h +++ b/libs/SDL3/src/render/direct3d11/SDL_shaders_d3d11.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h b/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h index 8c03218a..812a3a5d 100644 --- a/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h +++ b/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h @@ -4,8 +4,8 @@ ; ; Name Index Mask Register SysValue Format Used ; -------------------- ----- ------ -------- -------- ------- ------ -; SV_Position 0 xyzw 0 POS float -; TEXCOORD 0 xy 1 NONE float xy +; SV_Position 0 xyzw 0 POS float +; TEXCOORD 0 xy 1 NONE float xy ; COLOR 0 xyzw 2 NONE float xyzw ; ; @@ -15,9 +15,9 @@ ; -------------------- ----- ------ -------- -------- ------- ------ ; SV_Target 0 xyzw 0 TARGET float xyzw ; -; shader hash: d37677952ab198af41892c76b2b3f57f +; shader hash: f246f76536638c165bb49487d071f8f4 ; -; Pipeline Runtime Information: +; Pipeline Runtime Information: ; ; Pixel Shader ; DepthOutput=0 @@ -28,15 +28,15 @@ ; ; Name Index InterpMode DynIdx ; -------------------- ----- ---------------------- ------ -; SV_Position 0 noperspective -; TEXCOORD 0 linear -; COLOR 0 linear +; SV_Position 0 noperspective +; TEXCOORD 0 linear +; COLOR 0 linear ; ; Output signature: ; ; Name Index InterpMode DynIdx ; -------------------- ----- ---------------------- ------ -; SV_Target 0 +; SV_Target 0 ; ; Buffer Definitions: ; @@ -59,7 +59,7 @@ ; float4 Rcoeff; ; Offset: 64 ; float4 Gcoeff; ; Offset: 80 ; float4 Bcoeff; ; Offset: 96 -; +; ; } Constants; ; Offset: 0 Size: 112 ; ; } @@ -71,6 +71,7 @@ ; ------------------------------ ---------- ------- ----------- ------- -------------- ------ ; Constants cbuffer NA NA CB0 cb1 1 ; sampler0 sampler NA NA S0 s0 1 +; sampler1 sampler NA NA S1 s1 1 ; texture0 texture f32 2d T0 t0 1 ; texture1 texture f32 2d T1 t1 1 ; texture2 texture f32 2d T2 t2 1 @@ -101,584 +102,849 @@ define void @main() { %1 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 0, i32 2, i32 2, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) %2 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 0, i32 1, i32 1, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) %3 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 0, i32 0, i32 0, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) - %4 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 3, i32 0, i32 0, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) - %5 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 2, i32 0, i32 1, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) - %6 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 0, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %7 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 1, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %8 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 2, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %9 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 3, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %10 = call float @dx.op.loadInput.f32(i32 4, i32 1, i32 0, i8 0, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %11 = call float @dx.op.loadInput.f32(i32 4, i32 1, i32 0, i8 1, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %12 = call %dx.types.CBufRet.f32 @dx.op.cbufferLoadLegacy.f32(i32 59, %dx.types.Handle %5, i32 0) ; CBufferLoadLegacy(handle,regIndex) - %13 = extractvalue %dx.types.CBufRet.f32 %12, 1 - %14 = fcmp fast oeq float %13, 0.000000e+00 - br i1 %14, label %175, label %15 + %4 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 3, i32 1, i32 1, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) + %5 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 3, i32 0, i32 0, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) + %6 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 2, i32 0, i32 1, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) + %7 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 0, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) + %8 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 1, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) + %9 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 2, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) + %10 = call float @dx.op.loadInput.f32(i32 4, i32 2, i32 0, i8 3, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) + %11 = call float @dx.op.loadInput.f32(i32 4, i32 1, i32 0, i8 0, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) + %12 = call float @dx.op.loadInput.f32(i32 4, i32 1, i32 0, i8 1, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) + %13 = call %dx.types.CBufRet.f32 @dx.op.cbufferLoadLegacy.f32(i32 59, %dx.types.Handle %6, i32 0) ; CBufferLoadLegacy(handle,regIndex) + %14 = extractvalue %dx.types.CBufRet.f32 %13, 1 + %15 = fcmp fast oeq float %14, 0.000000e+00 + br i1 %15, label %428, label %16 -;