diff --git a/.github/actions/setup-ngage-sdk/action.yml b/.github/actions/setup-ngage-sdk/action.yml new file mode 100644 index 0000000000000..fa83418ba21c6 --- /dev/null +++ b/.github/actions/setup-ngage-sdk/action.yml @@ -0,0 +1,102 @@ +name: 'Setup Nonka N-Gage SDK' +description: 'Download and setup Nokia N-Gage SDK' +inputs: + path: + description: 'Installation path' + default: 'default' +runs: + using: 'composite' + steps: + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: 'Verify platform' + id: calc + shell: sh + run: | + case "${{ runner.os }}-${{ runner.arch }}" in + "Windows-X86" | "Windows-X64") + echo "ok!" + echo "cache-key=ngage-sdk-windows" >> ${GITHUB_OUTPUT} + default_install_path="C:/ngagesdk" + ;; + *) + echo "Unsupported ${{ runner.os }}-${{ runner.arch }}" + exit 1; + ;; + esac + install_path="${{ inputs.path }}" + if [ "x$install_path" = "xdefault" ]; then + install_path="$default_install_path" + fi + echo "install-path=$install_path" >> ${GITHUB_OUTPUT} + + toolchain_repo="https://github.com/ngagesdk/ngage-toolchain" + toolchain_branch="main" + echo "toolchain-repo=${toolchain_repo}" >> ${GITHUB_OUTPUT} + echo "toolchain-branch=${toolchain_branch}" >> ${GITHUB_OUTPUT} + + sdk_repo="https://github.com/ngagesdk/sdk" + sdk_branch="main" + echo "sdk-repo=${sdk_repo}" >> ${GITHUB_OUTPUT} + echo "sdk-branch=${sdk_branch}" >> ${GITHUB_OUTPUT} + + tools_repo="https://github.com/ngagesdk/tools" + tools_branch="main" + echo "tools-repo=${tools_repo}" >> ${GITHUB_OUTPUT} + echo "tools-branch=${tools_branch}" >> ${GITHUB_OUTPUT} + + extras_repo="https://github.com/ngagesdk/extras" + extras_branch="main" + echo "extras-repo=${extras_repo}" >> ${GITHUB_OUTPUT} + echo "extras-branch=${extras_branch}" >> ${GITHUB_OUTPUT} +# - name: 'Restore cached ${{ steps.calc.outputs.archive }}' +# id: cache-restore +# uses: actions/cache/restore@v4 +# with: +# path: '${{ runner.temp }}' +# key: ${{ steps.calc.outputs.cache-key }} + - name: 'Download N-Gage SDK' +# if: ${{ !steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false' }} + shell: pwsh + run: | + + Invoke-WebRequest "${{ steps.calc.outputs.toolchain-repo }}/archive/refs/heads/${{ steps.calc.outputs.toolchain-branch }}.zip" -OutFile "${{ runner.temp }}/ngage-toolchain.zip" + Invoke-WebRequest "${{ steps.calc.outputs.sdk-repo }}/archive/refs/heads/${{ steps.calc.outputs.sdk-branch }}.zip" -OutFile "${{ runner.temp }}/sdk.zip" + Invoke-WebRequest "${{ steps.calc.outputs.tools-repo }}/archive/refs/heads/${{ steps.calc.outputs.tools-branch }}.zip" -OutFile "${{ runner.temp }}/tools.zip" + Invoke-WebRequest "${{ steps.calc.outputs.extras-repo }}/archive/refs/heads/${{ steps.calc.outputs.extras-branch }}.zip" -OutFile "${{ runner.temp }}/extras.zip" + +# - name: 'Cache ${{ steps.calc.outputs.archive }}' +# if: ${{ !steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false' }} +# uses: actions/cache/save@v4 +# with: +# path: | +# ${{ runner.temp }}/apps.zip +# ${{ runner.temp }}/sdk.zip +# ${{ runner.temp }}/tools.zip +# key: ${{ steps.calc.outputs.cache-key }} + - name: 'Extract N-Gage SDK' + shell: pwsh + run: | + New-Item -ItemType Directory -Path "${{ steps.calc.outputs.install-path }}" -Force + + New-Item -ItemType Directory -Path "${{ runner.temp }}/ngage-toolchain-temp" -Force + 7z "-o${{ runner.temp }}/ngage-toolchain-temp" x "${{ runner.temp }}/ngage-toolchain.zip" + Move-Item -Path "${{ runner.temp }}/ngage-toolchain-temp/ngage-toolchain-${{ steps.calc.outputs.toolchain-branch }}/*" -Destination "${{ steps.calc.outputs.install-path }}" + + 7z "-o${{ steps.calc.outputs.install-path }}/sdk" x "${{ runner.temp }}/sdk.zip" + Move-Item -Path "${{ steps.calc.outputs.install-path }}/sdk/sdk-${{ steps.calc.outputs.sdk-branch }}" -Destination "${{ steps.calc.outputs.install-path }}/sdk/sdk" + + 7z "-o${{ steps.calc.outputs.install-path }}/sdk" x "${{ runner.temp }}/tools.zip" + Move-Item -Path "${{ steps.calc.outputs.install-path }}/sdk/tools-${{ steps.calc.outputs.tools-branch }}" -Destination "${{ steps.calc.outputs.install-path }}/sdk/tools" + + 7z "-o${{ steps.calc.outputs.install-path }}/sdk" x "${{ runner.temp }}/extras.zip" + Move-Item -Path "${{ steps.calc.outputs.install-path }}/sdk/extras-${{ steps.calc.outputs.extras-branch }}" -Destination "${{ steps.calc.outputs.install-path }}/sdk/extras" + - name: 'Set output variables' + id: final + shell: sh + run: | + echo "${{ steps.calc.outputs.install-path }}/sdk/sdk/6.1/Shared/EPOC32/gcc/bin" >> $GITHUB_PATH + echo "${{ steps.calc.outputs.install-path }}/sdk/sdk/6.1/Shared/EPOC32/ngagesdk/bin" >> $GITHUB_PATH + echo "NGAGESDK=${{ steps.calc.outputs.install-path }}" >> $GITHUB_ENV + echo "CMAKE_TOOLCHAIN_FILE=${{ steps.calc.outputs.install-path }}/cmake/ngage-toolchain.cmake" >> $GITHUB_ENV diff --git a/.github/workflows/create-test-plan.py b/.github/workflows/create-test-plan.py index 8048e2bc3229f..5523aeafae23a 100755 --- a/.github/workflows/create-test-plan.py +++ b/.github/workflows/create-test-plan.py @@ -54,6 +54,7 @@ class SdlPlatform(Enum): Riscos = "riscos" FreeBSD = "freebsd" NetBSD = "netbsd" + NGage = "ngage" class Msys2Platform(Enum): @@ -138,6 +139,7 @@ class JobSpec: "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", ), } @@ -163,6 +165,7 @@ class JobDetails: artifact: str no_cmake: bool ccache: bool = False + continue_on_error: bool = False build_tests: bool = True container: str = "" cmake_build_type: str = "RelWithDebInfo" @@ -222,6 +225,7 @@ class JobDetails: check_sources: bool = False setup_python: bool = False pypi_packages: list[str] = dataclasses.field(default_factory=list) + setup_gage_sdk_path: str = "" def to_workflow(self, enable_artifacts: bool) -> dict[str, str|bool]: data = { @@ -231,6 +235,7 @@ def to_workflow(self, enable_artifacts: bool) -> dict[str, str|bool]: "ccache": self.ccache, "container": self.container if self.container else "", "platform": self.platform, + "continue-on-error": self.continue_on_error, "artifact": self.artifact, "enable-artifacts": enable_artifacts, "shell": self.shell, @@ -289,6 +294,7 @@ def to_workflow(self, enable_artifacts: bool) -> dict[str, str|bool]: "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 != ""} @@ -739,6 +745,20 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDeta job.cpactions_arch = "x86-64" job.cpactions_setup_cmd = "export PATH=\"/usr/pkg/sbin:/usr/pkg/bin:/sbin:$PATH\"; export PKG_CONFIG_PATH=\"/usr/pkg/lib/pkgconfig\";export PKG_PATH=\"https://cdn.netBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r|cut -f \"1 2\" -d.)/All/\";echo \"PKG_PATH=$PKG_PATH\";echo \"uname -a -> \"$(uname -a)\"\";sudo -E sysctl -w security.pax.aslr.enabled=0;sudo -E sysctl -w security.pax.aslr.global=0;sudo -E pkgin clean;sudo -E pkgin update" job.cpactions_install_cmd = "sudo -E pkgin -y install cmake dbus pkgconf ninja-build pulseaudio libxkbcommon wayland wayland-protocols libinotify libusb1" + case SdlPlatform.NGage: + job.cmake_arguments.extend([ + "-DBUILD_FOR_NOKIA_NGAGE=ON", # FIXME: remove this + ]) + job.continue_on_error = True # FIXME: remove this + job.setup_ninja = True + job.static_lib = None # FIXME: should be StaticLibType.A + 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" case _: raise ValueError(f"Unsupported platform={spec.platform}") diff --git a/.github/workflows/generic.yml b/.github/workflows/generic.yml index 9776431e3da83..8b3e45bec49d1 100644 --- a/.github/workflows/generic.yml +++ b/.github/workflows/generic.yml @@ -14,6 +14,7 @@ jobs: name: ${{ matrix.platform.name }} runs-on: ${{ matrix.platform.os }} container: ${{ matrix.platform.container }} + continue-on-error: ${{ matrix.platform.continue-on-error }} defaults: run: shell: ${{ matrix.platform.shell }} @@ -93,6 +94,11 @@ jobs: with: arch: ${{ matrix.platform.msvc-vcvars-arch }} sdk: ${{ matrix.platform.msvc-vcvars-sdk }} + - name: 'Set up Nokia N-Gage SDK' + uses: ./.github/actions/setup-ngage-sdk + if: ${{ matrix.platform.setup-ngage-sdk-path != '' }} + with: + path: '${{ matrix.platform.setup-ngage-sdk-path }}' - name: 'Set up Windows GDK Desktop' uses: ./.github/actions/setup-gdk-desktop if: ${{ matrix.platform.setup-gdk-folder != '' }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 0db59b3eec21c..67448d05212ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ include("${SDL3_SOURCE_DIR}/cmake/GetGitRevisionDescription.cmake") include("${SDL3_SOURCE_DIR}/cmake/3rdparty.cmake") include("${SDL3_SOURCE_DIR}/cmake/PreseedMSVCCache.cmake") include("${SDL3_SOURCE_DIR}/cmake/PreseedEmscriptenCache.cmake") +include("${SDL3_SOURCE_DIR}/cmake/PreseedNokiaNGageCache.cmake") SDL_DetectCompiler() SDL_DetectTargetCPUArchitectures(SDL_CPUS) @@ -155,7 +156,7 @@ endif() # The hidraw support doesn't catch Xbox, PS4 and Nintendo controllers, # so we'll just use libusb when it's available. libusb does not support iOS, # so we default to yes on iOS. -if(IOS OR TVOS OR VISIONOS OR WATCHOS OR ANDROID) +if(IOS OR TVOS OR VISIONOS OR WATCHOS OR ANDROID OR NGAGESDK) set(SDL_HIDAPI_LIBUSB_AVAILABLE FALSE) else() set(SDL_HIDAPI_LIBUSB_AVAILABLE TRUE) @@ -219,7 +220,7 @@ if(EMSCRIPTEN) set(SDL_SHARED_AVAILABLE OFF) endif() -if(VITA OR PSP OR PS2 OR N3DS OR RISCOS) +if(VITA OR PSP OR PS2 OR N3DS OR RISCOS OR NGAGESDK) set(SDL_SHARED_AVAILABLE OFF) endif() @@ -414,6 +415,26 @@ if(VITA) set_option(VIDEO_VITA_PVR "Build with PSVita PVR gles/gles2 support" OFF) endif() +if (NGAGESDK) + enable_language(CXX) + + set(SDL_GPU OFF) + set(SDL_CAMERA OFF) + set(SDL_JOYSTICK OFF) + set(SDL_HAPTIC OFF) + set(SDL_HIDAPI OFF) + set(SDL_POWER OFF) + set(SDL_SENSOR OFF) + set(SDL_DIALOG OFF) + set(SDL_DISKAUDIO OFF) + set(SDL_DUMMYAUDIO OFF) + set(SDL_DUMMYCAMERA OFF) + set(SDL_DUMMYVIDEO OFF) + set(SDL_OFFSCREEN OFF) + set(SDL_RENDER_GPU OFF) + set(SDL_VIRTUAL_JOYSTICK OFF) +endif() + if(NOT (SDL_SHARED OR SDL_STATIC)) message(FATAL_ERROR "SDL_SHARED and SDL_STATIC cannot both be disabled") endif() @@ -2930,6 +2951,57 @@ elseif(N3DS) set(HAVE_SDL_LOCALE TRUE) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/io/n3ds/*.c") + +elseif(NGAGESDK) + + 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/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/ngage/*.cpp") + + if(SDL_AUDIO) + set(SDL_AUDIO_DRIVER_NGAGE 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/ngage/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/ngage/*.cpp") + set(HAVE_SDL_AUDIO TRUE) + endif() + + set(SDL_FILESYSTEM_NGAGE 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/ngage/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/ngage/*.cpp") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/*.c") + set(HAVE_SDL_FILESYSTEM TRUE) + + set(SDL_THREADS_DISABLED 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/thread/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/thread/generic/*.c") + set(HAVE_SDL_THREADS TRUE) + + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/ngage/*.cpp") + + 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/*.cpp") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/ngage/*.cpp") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c") + + set(SDL_TIMER_NGAGE 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/ngage/*.cpp") + + set(SDL_VIDEO_DRIVER_NGAGE 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/ngage/*.c") + set(HAVE_SDL_TIMERS TRUE) + + set(SDL_CAMERA_DISABLED 1) + set(SDL_ASSERT_LEVEL 0) + set(SDL_FSOPS_POSIX 1) + set(SDL_GPU_DISABLED 1) + set(SDL_HAPTIC_DISABLED 1) + set(SDL_JOYSTICK_DISABLED 1) + set(SDL_LEAN_AND_MEAN 1) + set(SDL_SENSOR_DISABLED 1) endif() sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/SDL_dialog.c) diff --git a/cmake/PreseedNokiaNGageCache.cmake b/cmake/PreseedNokiaNGageCache.cmake new file mode 100644 index 0000000000000..6a6b9d223778b --- /dev/null +++ b/cmake/PreseedNokiaNGageCache.cmake @@ -0,0 +1,187 @@ +if(NGAGESDK) + function(SDL_Preseed_CMakeCache) + set(COMPILER_SUPPORTS_ARMNEON "" CACHE INTERNAL "Test COMPILER_SUPPORTS_ARMNEON") + set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS "" CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS") + set(COMPILER_SUPPORTS_SYNC_LOCK_TEST_AND_SET "" CACHE INTERNAL "Test COMPILER_SUPPORTS_SYNC_LOCK_TEST_AND_SET") + set(HAVE_CLANG_COMMENT_BLOCK_COMMANDS "" CACHE INTERNAL "Test HAVE_CLANG_COMMENT_BLOCK_COMMANDS") + set(HAVE_ALLOCA_H "" CACHE INTERNAL "Have include alloca.h") + set(HAVE_LIBM "1" CACHE INTERNAL "Have library m") + set(HAVE_POSIX_SPAWN "" CACHE INTERNAL "Have symbol posix_spawn") + set(HAVE_MALLOC "1" CACHE INTERNAL "Have include malloc.h") + set(LIBC_HAS_ABS "1" CACHE INTERNAL "Have symbol abs") + set(LIBC_HAS_ACOS "1" CACHE INTERNAL "Have symbol acos") + set(LIBC_HAS_ACOSF "" CACHE INTERNAL "Have symbol acosf") + set(LIBC_HAS_ASIN "1" CACHE INTERNAL "Have symbol asin") + set(LIBC_HAS_ASINF "" CACHE INTERNAL "Have symbol asinf") + set(LIBC_HAS_ATAN "1" CACHE INTERNAL "Have symbol atan") + set(LIBC_HAS_ATAN2 "1" CACHE INTERNAL "Have symbol atan2") + set(LIBC_HAS_ATAN2F "" CACHE INTERNAL "Have symbol atan2f") + set(LIBC_HAS_ATANF "" CACHE INTERNAL "Have symbol atanf") + set(LIBC_HAS_ATOF "" CACHE INTERNAL "Have symbol atof") + set(LIBC_HAS_ATOI "" CACHE INTERNAL "Have symbol atoi") + set(LIBC_HAS_BCOPY "1" CACHE INTERNAL "Have symbol bcopy") + set(LIBC_HAS_CALLOC "" CACHE INTERNAL "Have symbol calloc") + set(LIBC_HAS_CEIL "1" CACHE INTERNAL "Have symbol ceil") + set(LIBC_HAS_CEILF "" CACHE INTERNAL "Have symbol ceilf") + set(LIBC_HAS_COPYSIGN "1" CACHE INTERNAL "Have symbol copysign") + set(LIBC_HAS_COPYSIGNF "1" CACHE INTERNAL "Have symbol copysignf") + set(LIBC_HAS_COS "1" CACHE INTERNAL "Have symbol cos") + set(LIBC_HAS_COSF "" CACHE INTERNAL "Have symbol cosf") + set(LIBC_HAS_EXP "1" CACHE INTERNAL "Have symbol exp") + set(LIBC_HAS_EXPF "" CACHE INTERNAL "Have symbol expf") + set(LIBC_HAS_FABS "1" CACHE INTERNAL "Have symbol fabs") + set(LIBC_HAS_FABSF "1" CACHE INTERNAL "Have symbol fabsf") + set(LIBC_HAS_FLOAT_H "1" CACHE INTERNAL "Have include float.h") + set(LIBC_HAS_FLOOR "1" CACHE INTERNAL "Have symbol floor") + set(LIBC_HAS_FLOORF "" CACHE INTERNAL "Have symbol floorf") + set(LIBC_HAS_FMOD "" CACHE INTERNAL "Have symbol fmod") + set(LIBC_HAS_FMODF "" CACHE INTERNAL "Have symbol fmodf") + set(LIBC_HAS_FOPEN64 "" CACHE INTERNAL "Have symbol fopen64") + set(LIBC_HAS_FREE "1" CACHE INTERNAL "Have symbol free") + set(LIBC_HAS_FSEEKO "" CACHE INTERNAL "Have symbol fseeko") + set(LIBC_HAS_FSEEKO64 "" CACHE INTERNAL "Have symbol fseeko64") + set(LIBC_HAS_GETENV "" CACHE INTERNAL "Have symbol getenv") + set(LIBC_HAS_ICONV_H "" CACHE INTERNAL "Have include iconv.h") + set(LIBC_HAS_INDEX "1" CACHE INTERNAL "Have symbol index") + set(LIBC_HAS_INTTYPES_H "1" CACHE INTERNAL "Have include inttypes.h") + set(LIBC_HAS_ISINF "1" CACHE INTERNAL "Have include isinf(double)") + set(LIBC_ISINF_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isinf(float)") + set(LIBC_HAS_ISINFF "1" CACHE INTERNAL "Have include isinff(float)") + set(LIBC_HAS_ISNAN "1" CACHE INTERNAL "Have include isnan(double)") + set(LIBC_ISNAN_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isnan(float)") + set(LIBC_HAS_ISNANF "1" CACHE INTERNAL "Have include isnanf(float)") + set(LIBC_HAS_ITOA "" CACHE INTERNAL "Have symbol itoa") + set(LIBC_HAS_LIMITS_H "1" CACHE INTERNAL "Have include limits.h") + set(LIBC_HAS_LOG "1" CACHE INTERNAL "Have symbol log") + set(LIBC_HAS_LOG10 "" CACHE INTERNAL "Have symbol log10") + set(LIBC_HAS_LOG10F "" CACHE INTERNAL "Have symbol log10f") + set(LIBC_HAS_LOGF "1" CACHE INTERNAL "Have symbol logf") + set(LIBC_HAS_LROUND "" CACHE INTERNAL "Have symbol lround") + set(LIBC_HAS_LROUNDF "" CACHE INTERNAL "Have symbol lroundf") + set(LIBC_HAS_MALLOC "1" CACHE INTERNAL "Have symbol malloc") + set(LIBC_HAS_MALLOC_H "" CACHE INTERNAL "Have include malloc.h") + set(LIBC_HAS_MATH_H "1" CACHE INTERNAL "Have include math.h") + set(LIBC_HAS_MEMCMP "1" CACHE INTERNAL "Have symbol memcmp") + set(LIBC_HAS_MEMCPY "" CACHE INTERNAL "Have symbol memcpy") + set(LIBC_HAS_MEMMOVE "" CACHE INTERNAL "Have symbol memmove") + set(LIBC_HAS_MEMORY_H "" CACHE INTERNAL "Have include memory.h") + set(LIBC_HAS_MEMSET "" CACHE INTERNAL "Have symbol memset") + set(LIBC_HAS_MODF "1" CACHE INTERNAL "Have symbol modf") + set(LIBC_HAS_MODFF "" CACHE INTERNAL "Have symbol modff") + set(LIBC_HAS_POW "1" CACHE INTERNAL "Have symbol pow") + set(LIBC_HAS_POWF "" CACHE INTERNAL "Have symbol powf") + set(LIBC_HAS_PUTENV "" CACHE INTERNAL "Have symbol putenv") + set(LIBC_HAS_REALLOC "" CACHE INTERNAL "Have symbol realloc") + set(LIBC_HAS_RINDEX "1" CACHE INTERNAL "Have symbol rindex") + set(LIBC_HAS_ROUND "" CACHE INTERNAL "Have symbol round") + set(LIBC_HAS_ROUNDF "" CACHE INTERNAL "Have symbol roundf") + set(LIBC_HAS_SCALBN "1" CACHE INTERNAL "Have symbol scalbn") + set(LIBC_HAS_SCALBNF "" CACHE INTERNAL "Have symbol scalbnf") + set(LIBC_HAS_SETENV "" CACHE INTERNAL "Have symbol setenv") + set(LIBC_HAS_SIGNAL_H "" CACHE INTERNAL "Have include signal.h") + set(LIBC_HAS_SIN "1" CACHE INTERNAL "Have symbol sin") + set(LIBC_HAS_SINF "" CACHE INTERNAL "Have symbol sinf") + set(LIBC_HAS_SQR "" CACHE INTERNAL "Have symbol sqr") + set(LIBC_HAS_SQRT "1" CACHE INTERNAL "Have symbol sqrt") + set(LIBC_HAS_SQRTF "" CACHE INTERNAL "Have symbol sqrtf") + set(LIBC_HAS_SSCANF "1" CACHE INTERNAL "Have symbol sscanf") + set(LIBC_HAS_STDARG_H "1" CACHE INTERNAL "Have include stdarg.h") + set(LIBC_HAS_STDBOOL_H "1" CACHE INTERNAL "Have include stdbool.h") + set(LIBC_HAS_STDDEF_H "1" CACHE INTERNAL "Have include stddef.h") + set(LIBC_HAS_STDINT_H "1" CACHE INTERNAL "Have include stdint.h") + set(LIBC_HAS_STDIO_H "1" CACHE INTERNAL "Have include stdio.h") + set(LIBC_HAS_STDLIB_H "1" CACHE INTERNAL "Have include stdlib.h") + set(LIBC_HAS_STRCASESTR "" CACHE INTERNAL "Have symbol strcasestr") + set(LIBC_HAS_STRCHR "1" CACHE INTERNAL "Have symbol strchr") + set(LIBC_HAS_STRCMP "1" CACHE INTERNAL "Have symbol strcmp") + set(LIBC_HAS_STRINGS_H "" CACHE INTERNAL "Have include strings.h") + set(LIBC_HAS_STRING_H "1" CACHE INTERNAL "Have include string.h") + set(LIBC_HAS_STRLCAT "" CACHE INTERNAL "Have symbol strlcat") + set(LIBC_HAS_STRLCPY "" CACHE INTERNAL "Have symbol strlcpy") + set(LIBC_HAS_STRLEN "1" CACHE INTERNAL "Have symbol strlen") + set(LIBC_HAS_STRNCMP "1" CACHE INTERNAL "Have symbol strncmp") + set(LIBC_HAS_STRNLEN "" CACHE INTERNAL "Have symbol strnlen") + set(LIBC_HAS_STRNSTR "" CACHE INTERNAL "Have symbol strnstr") + set(LIBC_HAS_STRPBRK "1" CACHE INTERNAL "Have symbol strpbrk") + set(LIBC_HAS_STRRCHR "1" CACHE INTERNAL "Have symbol strrchr") + set(LIBC_HAS_STRSTR "1" CACHE INTERNAL "Have symbol strstr") + set(LIBC_HAS_STRTOD "" CACHE INTERNAL "Have symbol strtod") + set(LIBC_HAS_STRTOK_R "" CACHE INTERNAL "Have symbol strtok_r") + set(LIBC_HAS_STRTOL "" CACHE INTERNAL "Have symbol strtol") + set(LIBC_HAS_STRTOLL "" CACHE INTERNAL "Have symbol strtoll") + set(LIBC_HAS_STRTOUL "" CACHE INTERNAL "Have symbol strtoul") + set(LIBC_HAS_STRTOULL "" CACHE INTERNAL "Have symbol strtoull") + set(LIBC_HAS_SYS_TYPES_H "1" CACHE INTERNAL "Have include sys/types.h") + set(LIBC_HAS_TAN "1" CACHE INTERNAL "Have symbol tan") + set(LIBC_HAS_TANF "" CACHE INTERNAL "Have symbol tanf") + set(LIBC_HAS_TIME_H "1" CACHE INTERNAL "Have include time.h") + set(LIBC_HAS_TRUNC "" CACHE INTERNAL "Have symbol trunc") + set(LIBC_HAS_TRUNCF "" CACHE INTERNAL "Have symbol truncf") + set(LIBC_HAS_UNSETENV "" CACHE INTERNAL "Have symbol unsetenv") + set(LIBC_HAS_VSNPRINTF "" CACHE INTERNAL "Have symbol vsnprintf") + set(LIBC_HAS_VSSCANF "" CACHE INTERNAL "Have symbol vsscanf") + set(LIBC_HAS_WCHAR_H "1" CACHE INTERNAL "Have include wchar.h") + set(LIBC_HAS_WCSCMP "" CACHE INTERNAL "Have symbol wcscmp") + set(LIBC_HAS_WCSDUP "" CACHE INTERNAL "Have symbol wcsdup") + set(LIBC_HAS_WCSLCAT "" CACHE INTERNAL "Have symbol wcslcat") + set(LIBC_HAS_WCSLCPY "" CACHE INTERNAL "Have symbol wcslcpy") + set(LIBC_HAS_WCSLEN "" CACHE INTERNAL "Have symbol wcslen") + set(LIBC_HAS_WCSNCMP "" CACHE INTERNAL "Have symbol wcsncmp") + set(LIBC_HAS_WCSNLEN "" CACHE INTERNAL "Have symbol wcsnlen") + set(LIBC_HAS_WCSSTR "" CACHE INTERNAL "Have symbol wcsstr") + set(LIBC_HAS_WCSTOL "" CACHE INTERNAL "Have symbol wcstol") + set(LIBC_HAS__EXIT "" CACHE INTERNAL "Have symbol _Exit") + set(LIBC_HAS__I64TOA "" CACHE INTERNAL "Have symbol _i64toa") + set(LIBC_HAS__LTOA "" CACHE INTERNAL "Have symbol _ltoa") + set(LIBC_HAS__STRREV "" CACHE INTERNAL "Have symbol _strrev") + set(LIBC_HAS__UI64TOA "" CACHE INTERNAL "Have symbol _ui64toa") + set(LIBC_HAS__UITOA "" CACHE INTERNAL "Have symbol _uitoa") + set(LIBC_HAS__ULTOA "" CACHE INTERNAL "Have symbol _ultoa") + set(LIBC_HAS__WCSDUP "" CACHE INTERNAL "Have symbol _wcsdup") + set(LIBC_IS_GLIBC "" CACHE INTERNAL "Have symbol __GLIBC__") + set(_ALLOCA_IN_MALLOC_H "" CACHE INTERNAL "Have symbol _alloca") + set(HAVE_GCC_WALL "1" CACHE INTERNAL "Test HAVE_GCC_WALL") + set(HAVE_GCC_WUNDEF "1" CACHE INTERNAL "Test HAVE_GCC_WUNDEF") + set(HAVE_GCC_WFLOAT_CONVERSION "" CACHE INTERNAL "Test HAVE_GCC_WFLOAT_CONVERSION") + set(HAVE_GCC_NO_STRICT_ALIASING "1" CACHE INTERNAL "Test HAVE_GCC_NO_STRICT_ALIASING") + set(HAVE_GCC_WDOCUMENTATION "" CACHE INTERNAL "Test HAVE_GCC_WDOCUMENTATION") + set(HAVE_GCC_WDOCUMENTATION_UNKNOWN_COMMAND "" CACHE INTERNAL "Test HAVE_GCC_WDOCUMENTATION_UNKNOWN_COMMAND") + set(HAVE_GCC_COMMENT_BLOCK_COMMANDS "" CACHE INTERNAL "Test HAVE_GCC_COMMENT_BLOCK_COMMANDS") + set(HAVE_GCC_WSHADOW "1" CACHE INTERNAL "Test HAVE_GCC_WSHADOW") + set(HAVE_GCC_WUNUSED_LOCAL_TYPEDEFS "" CACHE INTERNAL "Test HAVE_GCC_WUNUSED_LOCAL_TYPEDEFS") + set(HAVE_GCC_WIMPLICIT_FALLTHROUGH "" CACHE INTERNAL "Test HAVE_GCC_WIMPLICIT_FALLTHROUGH") + set(HAVE_GCC_FVISIBILITY "1" CACHE INTERNAL "Test HAVE_GCC_FVISIBILITY") + set(HAVE_ST_MTIM "" CACHE INTERNAL "Test HAVE_ST_MTIM") + #set(HAVE_O_CLOEXEC "" CACHE INTERNAL "Test HAVE_O_CLOEXEC") + #set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR "" CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR") + set(COMPILER_SUPPORTS_GCC_ATOMICS "" CACHE INTERNAL "Test COMPILER_SUPPORTS_GCC_ATOMICS") + set(LINKER_SUPPORTS_VERSION_SCRIPT "" CACHE INTERNAL "Test LINKER_SUPPORTS_VERSION_SCRIPT") + set(LINKER_SUPPORTS_WL_NO_UNDEFINED "" CACHE INTERNAL "Test LINKER_SUPPORTS_WL_NO_UNDEFINED") + set(ICONV_IN_LIBC "" CACHE INTERNAL "Test ICONV_IN_LIBC") + set(ICONV_IN_LIBICONV "" CACHE INTERNAL "Test ICONV_IN_LIBICONV") + #set(LIBC_HAS_WORKING_LIBUNWIND "" CACHE INTERNAL "Test LIBC_HAS_WORKING_LIBUNWIND") + #set(LIBUNWIND_HAS_WORKINGLIBUNWIND "" CACHE INTERNAL "Test LIBUNWIND_HAS_WORKINGLIBUNWIND") + set(HAVE_GETPAGESIZE "" CACHE INTERNAL "Have symbol getpagesize") + set(HAVE_SIGACTION "" CACHE INTERNAL "Have symbol sigaction") + set(HAVE_SA_SIGACTION "" CACHE INTERNAL "Have symbol sa_sigaction") + set(HAVE_SETJMP "" CACHE INTERNAL "Have symbol setjmp") + set(HAVE_NANOSLEEP "" CACHE INTERNAL "Have symbol nanosleep") + set(HAVE_GMTIME_R "" CACHE INTERNAL "Have symbol gmtime_r") + set(HAVE_LOCALTIME_R "" CACHE INTERNAL "Have symbol localtime_r") + set(HAVE_NL_LANGINFO "" CACHE INTERNAL "Have symbol nl_langinfo") + set(HAVE_SYSCONF "" CACHE INTERNAL "Have symbol sysconf") + set(HAVE_SYSCTLBYNAME "" CACHE INTERNAL "Have symbol sysctlbyname") + set(HAVE_GETAUXVAL "" CACHE INTERNAL "Have symbol getauxval") + set(HAVE_ELF_AUX_INFO "" CACHE INTERNAL "Have symbol elf_aux_info") + set(HAVE_POLL "" CACHE INTERNAL "Have symbol poll") + set(HAVE_MEMFD_CREATE "" CACHE INTERNAL "Have symbol memfd_create") + set(HAVE_POSIX_FALLOCATE "" CACHE INTERNAL "Have symbol posix_fallocate") + set(HAVE_DLOPEN_IN_LIBC "" CACHE INTERNAL "Have symbol dlopen") + + set(HAVE_GETHOSTNAME "" CACHE INTERNAL "Have symbol gethostname") + set(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR "" CACHE INTERNAL "Have symbol addchdir") + set(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP "" CACHE INTERNAL "Have symbol addchdir_np") + set(HAVE_FDATASYNC "" CACHE INTERNAL "Have symbol fdatasync") + + endfunction() +endif() diff --git a/cmake/nokia_ngage.cmake b/cmake/nokia_ngage.cmake new file mode 100644 index 0000000000000..8dfad60c36dc0 --- /dev/null +++ b/cmake/nokia_ngage.cmake @@ -0,0 +1,125 @@ +cmake_minimum_required(VERSION 3.16) + +enable_language(CXX) + +file(GLOB SDL3_sources + "${SDL3_SOURCE_DIR}/src/*.c" + "${SDL3_SOURCE_DIR}/src/atomic/*.c" + "${SDL3_SOURCE_DIR}/src/audio/*.c" + "${SDL3_SOURCE_DIR}/src/audio/ngage/*.c" + "${SDL3_SOURCE_DIR}/src/audio/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/core/*.c" + "${SDL3_SOURCE_DIR}/src/core/ngage/*.c" + "${SDL3_SOURCE_DIR}/src/core/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/cpuinfo/*.c" + "${SDL3_SOURCE_DIR}/src/dynapi/*.c" + "${SDL3_SOURCE_DIR}/src/events/*.c" + "${SDL3_SOURCE_DIR}/src/file/*.c" + "${SDL3_SOURCE_DIR}/src/file/generic/*.c" + "${SDL3_SOURCE_DIR}/src/filesystem/*.c" + "${SDL3_SOURCE_DIR}/src/filesystem/ngage/*.c" + "${SDL3_SOURCE_DIR}/src/filesystem/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/filesystem/posix/*.c" + "${SDL3_SOURCE_DIR}/src/hidapi/*.c" + "${SDL3_SOURCE_DIR}/src/io/*.c" + "${SDL3_SOURCE_DIR}/src/io/generic/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/libm/s_isinff.c" + "${SDL3_SOURCE_DIR}/src/libm/s_isnanf.c" + "${SDL3_SOURCE_DIR}/src/locale/*.c" + "${SDL3_SOURCE_DIR}/src/locale/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/main/*.c" + "${SDL3_SOURCE_DIR}/src/main/ngage/*.c" + "${SDL3_SOURCE_DIR}/src/main/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/misc/*.c" + "${SDL3_SOURCE_DIR}/src/misc/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/power/*.c" + "${SDL3_SOURCE_DIR}/src/render/*.c" + "${SDL3_SOURCE_DIR}/src/render/ngage/*.c" + "${SDL3_SOURCE_DIR}/src/render/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/sensor/*.c" + "${SDL3_SOURCE_DIR}/src/sensor/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/stdlib/*.c" + "${SDL3_SOURCE_DIR}/src/storage/*.c" + "${SDL3_SOURCE_DIR}/src/storage/generic/*.c" + "${SDL3_SOURCE_DIR}/src/thread/*.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/*.c" + "${SDL3_SOURCE_DIR}/src/time/*.c" + "${SDL3_SOURCE_DIR}/src/time/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/time/unix/*.c" + "${SDL3_SOURCE_DIR}/src/timer/*.c" + "${SDL3_SOURCE_DIR}/src/timer/ngage/*.cpp" + "${SDL3_SOURCE_DIR}/src/tray/*.c" + "${SDL3_SOURCE_DIR}/src/tray/dummy/*.c" + "${SDL3_SOURCE_DIR}/src/video/*.c" + "${SDL3_SOURCE_DIR}/src/video/ngage/*.c" + "${SDL3_SOURCE_DIR}/src/video/yuv2rgb/*.c") + +add_library(${PROJECT_NAME} STATIC ${SDL3_sources}) + +target_include_directories( + ${PROJECT_NAME} + PUBLIC + "${SDL3_SOURCE_DIR}/include" + PRIVATE + "${SDL3_SOURCE_DIR}/src" + "${SDL3_SOURCE_DIR}/include/build_config") + +target_compile_definitions( + ${PROJECT_NAME} + PUBLIC + SDL_STATIC_LIB) + +set(test_static_libs + ${CMAKE_CURRENT_BINARY_DIR}/libSDL3.a) + +set(test_libs + ${CMAKE_CURRENT_BINARY_DIR}/libSDL3.a + ${EPOC_LIB}/NRenderer.lib + ${EPOC_LIB}/3dtypes.a + ${EPOC_LIB}/cone.lib + ${EPOC_PLATFORM}/gcc/lib/gcc-lib/arm-epoc-pe/2.9-psion-98r2/libgcc.a + ${EPOC_PLATFORM}/ngagesdk/lib/gcc/arm-epoc-pe/4.6.4/libgcc_ngage.a + ${EPOC_PLATFORM}/ngagesdk/lib/gcc/arm-epoc-pe/4.6.4/libc_ngage.a + ${EPOC_LIB}/mediaclientaudiostream.lib + ${EPOC_LIB}/charconv.lib + ${EPOC_LIB}/bitgdi.lib + ${EPOC_LIB}/euser.lib + ${EPOC_LIB}/estlib.lib + ${EPOC_LIB}/ws32.lib + ${EPOC_LIB}/hal.lib + ${EPOC_LIB}/fbscli.lib + ${EPOC_LIB}/efsrv.lib + ${EPOC_LIB}/scdv.lib + ${EPOC_LIB}/gdi.lib) + +set(UID1 0x1000007a) # KExecutableImageUidValue, e32uid.h +set(UID2 0x100039ce) # KAppUidValue16, apadef.h +set(UID3 0x10005799) # ngage_test.exe UID + +add_library(ngage_test STATIC "${CMAKE_CURRENT_SOURCE_DIR}/cmake/test/ngagetest.c") + +build_exe_static(ngage_test exe ${UID1} ${UID2} ${UID3} "${test_static_libs}" "${test_libs}") + +add_dependencies( + ngage_test.exe + ngage_test) + +add_dependencies( + ngage_test + ${PROJECT_NAME}) + +target_compile_definitions( + ngage_test + PUBLIC + __EXE__ + UID1=${UID1} + UID2=${UID2} + UID3=${UID3}) + +target_include_directories( + ngage_test + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/include/SDL3) diff --git a/cmake/test/ngagetest.c b/cmake/test/ngagetest.c new file mode 100644 index 0000000000000..0215af1a0d331 --- /dev/null +++ b/cmake/test/ngagetest.c @@ -0,0 +1,33 @@ +// The purpose of this test is to validate that there are no undefined references +// when building with the N-Gage SDK since SDL can only be linked statically. + +#define IMPORT_C +#include + +int SDL_main(int argc, char* argv[]) +{ + for (int i = 0; i < argc; i++) { // C99 check. + /* Nothing to do here. */ + } + + return 0; +} + +SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) +{ + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) +{ + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppIterate(void* appstate) +{ + return SDL_APP_CONTINUE; +} + +void SDL_AppQuit(void* appstate, SDL_AppResult result) +{ +} diff --git a/docs/README-ngage.md b/docs/README-ngage.md index 84192b01abaee..eb14bf68d0de1 100644 --- a/docs/README-ngage.md +++ b/docs/README-ngage.md @@ -1,5 +1,44 @@ -Support for the Nokia N-Gage has been removed from SDL3 (but will make a -comeback when newer compilers are available for the platform). +# Nokia N-Gage -SDL2 still supports this platform. +SDL port for the Nokia N-Gage [Homebrew toolchain](https://github.com/ngagesdk/ngage-toolchain) contributed by: +- [Michael Fitzmayer](https://github.com/mupfdev) + +Many thanks to: + +- icculus and slouken, who faithfully kept us a place by the fireside! +- The awesome people who ported SDL to other homebrew platforms. +- The Nokia N-Gage [Discord community](https://discord.gg/dbUzqJ26vs) + who keeps the platform alive. +- To all the staff and supporters of the + [Suomen pelimuseo](https://www.vapriikki.fi/nayttelyt/fantastinen-floppi/) + and Heikki Jungmann. You guys are great! + +## History + +After SDL support was removed because there was no support for C99 at that +time, this version was developed from scratch after the compiler problems +were resolved. + +Unlike the previous SDL2 port, this version has a dedicated rendering +backend and a limited audio interface that works, even if it is limited. +Support for the software renderer has been removed. + +The result is a much leaner, and much more performant port of SDL that +will hopefully breathe some new life into this obscure platform - that +we love so much. + +## Future prospects + +The revised toolchain includes EGL 1.3 and OpenGL ES 1.1. A native +integration with SDL is being considered. + +## Existing Issues + +- If the application is put in the background while sound is playing, + some of the audio is looped until the app is back in focus. + +- It is recommended initialising SDLs audio sub-system even when it + is not required. The backend is started at a higher level. + Initialising SDLs audio sub-system ensures that the backend is + properly deinitialised. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 58406f6221bd4..6efc021546f96 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -141,6 +141,7 @@ add_sdl_example_executable(audio-simple-playback SOURCES audio/01-simple-playbac add_sdl_example_executable(audio-simple-playback-callback SOURCES audio/02-simple-playback-callback/simple-playback-callback.c) add_sdl_example_executable(audio-load-wav SOURCES audio/03-load-wav/load-wav.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.wav) add_sdl_example_executable(audio-multiple-streams SOURCES audio/04-multiple-streams/multiple-streams.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.wav ${CMAKE_CURRENT_SOURCE_DIR}/../test/sword.wav) +add_sdl_example_executable(audio-planar-data SOURCES audio/05-planar-data/planar-data.c) add_sdl_example_executable(input-joystick-polling SOURCES input/01-joystick-polling/joystick-polling.c) add_sdl_example_executable(input-joystick-events SOURCES input/02-joystick-events/joystick-events.c) add_sdl_example_executable(camera-read-and-draw SOURCES camera/01-read-and-draw/read-and-draw.c) diff --git a/examples/audio/04-multiple-streams/README.txt b/examples/audio/04-multiple-streams/README.txt index 9ef2275bd6d3d..ad7f5f8a65320 100644 --- a/examples/audio/04-multiple-streams/README.txt +++ b/examples/audio/04-multiple-streams/README.txt @@ -1,5 +1,5 @@ If you're running this in a web browser, you need to click the window before you'll hear anything! -This example code loads two .wav files, puts them an audio streams and binds +This example code loads two .wav files, puts them in audio streams and binds them for playback, repeating both sounds on loop. This shows several streams mixing into a single playback device. diff --git a/examples/audio/04-multiple-streams/multiple-streams.c b/examples/audio/04-multiple-streams/multiple-streams.c index 8d3bfaa0906d3..188023c8cf5d3 100644 --- a/examples/audio/04-multiple-streams/multiple-streams.c +++ b/examples/audio/04-multiple-streams/multiple-streams.c @@ -1,5 +1,5 @@ /* - * This example code loads two .wav files, puts them an audio streams and + * This example code loads two .wav files, puts them in audio streams and * binds them for playback, repeating both sounds on loop. This shows several * streams mixing into a single playback device. * diff --git a/examples/audio/05-planar-data/README.txt b/examples/audio/05-planar-data/README.txt new file mode 100644 index 0000000000000..c7bd0ab5e862b --- /dev/null +++ b/examples/audio/05-planar-data/README.txt @@ -0,0 +1,7 @@ +This example code draws two clickable buttons. Each causes a sound to play, +fed to either the left or right audio channel through separate (planar) +arrays. + +Planar audio can feed both channels at the same time from different arrays, +as well, but this example only uses one channel at a time for clarity. A +NULL array will supply silence for that channel. diff --git a/examples/audio/05-planar-data/onmouseover.webp b/examples/audio/05-planar-data/onmouseover.webp new file mode 100644 index 0000000000000..90880c9e45448 Binary files /dev/null and b/examples/audio/05-planar-data/onmouseover.webp differ diff --git a/examples/audio/05-planar-data/planar-data.c b/examples/audio/05-planar-data/planar-data.c new file mode 100644 index 0000000000000..ab67999ab92bc --- /dev/null +++ b/examples/audio/05-planar-data/planar-data.c @@ -0,0 +1,366 @@ +/* + * This example code draws two clickable buttons. Each causes a sound to play, + * fed to either the left or right audio channel through separate ("planar") + * arrays. + * + * This code is public domain. Feel free to use it for any purpose! + */ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static SDL_AudioStream *stream = NULL; + +/* location of buttons on the screen. */ +static const SDL_FRect rect_left_button = { 100, 170, 100, 100 }; +static const SDL_FRect rect_right_button = { 440, 170, 100, 100 }; + +/* -1 if we're currently playing left, 1 if playing right, 0 if not playing. */ +static int playing_sound = 0; + +/* Raw audio data. These arrays are at the end of the source file. */ +static const Uint8 left[1870]; +static const Uint8 right[1777]; + + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + const SDL_AudioSpec spec = { SDL_AUDIO_U8, 2, 4000 }; /* Uint8 data, stereo, 4000Hz. */ + + SDL_SetAppMetadata("Example Audio Planar Data", "1.0", "com.example.audio-planar-data"); + + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_CreateWindowAndRenderer("examples/audio/planar-data", 640, 480, 0, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL, NULL); + if (!stream) { + SDL_Log("Couldn't open audio device stream: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + SDL_ResumeAudioStreamDevice(stream); /* SDL_OpenAudioDeviceStream starts the device paused. Resume it! */ + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) { + if (playing_sound == 0) { /* nothing currently playing? */ + const SDL_FPoint point = { event->button.x, event->button.y }; + if (SDL_PointInRectFloat(&point, &rect_left_button)) { /* clicked left button? */ + const Uint8 *planes[] = { left, NULL }; /* specify NULL to say "this specific channel is silent" */ + SDL_PutAudioStreamPlanarData(stream, (const void * const *) planes, SDL_arraysize(left)); + SDL_FlushAudioStream(stream); /* that's all we're playing until it completes. */ + playing_sound = -1; /* left is playing */ + } else if (SDL_PointInRectFloat(&point, &rect_right_button)) { /* clicked right button? */ + const Uint8 *planes[] = { NULL, right }; /* specify NULL to say "this specific channel is silent" */ + SDL_PutAudioStreamPlanarData(stream, (const void * const *) planes, SDL_arraysize(right)); + SDL_FlushAudioStream(stream); /* that's all we're playing until it completes. */ + playing_sound = 1; /* right is playing */ + } + } + } + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +static void render_button(const SDL_FRect *rect, const char *str, int button_value) +{ + float x, y; + + if (playing_sound == button_value) { + SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); /* green while playing */ + } else { + SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); /* blue while not playing */ + } + + SDL_RenderFillRect(renderer, rect); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + + x = rect->x + ((rect->w - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(str))) / 2.0f); + y = rect->y + ((rect->h - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) / 2.0f); + SDL_RenderDebugText(renderer, x, y, str); +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + if (playing_sound) { + if (SDL_GetAudioStreamQueued(stream) == 0) { /* sound is done? We can play a new sound now. */ + playing_sound = 0; + } + } + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + + render_button(&rect_left_button, "LEFT", -1); + render_button(&rect_right_button, "RIGHT", 1); + + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + SDL_DestroyAudioStream(stream); + /* SDL will clean up the window/renderer for us. */ +} + + + +/* This is the audio data, as raw PCM samples (Uint8, 1 channel, 4000Hz) packed into C byte arrays for convenience. */ + +static const Uint8 left[1870] = { + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x81, 0x80, 0x81, 0x82, 0x82, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x81, 0x80, 0x80, 0x80, 0x7f, 0x7e, 0x7e, 0x7e, 0x7d, + 0x7b, 0x7b, 0x7b, 0x7b, 0x7c, 0x7d, 0x7d, 0x7e, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x85, 0x84, + 0x84, 0x83, 0x81, 0x7f, 0x7d, 0x7c, 0x7a, 0x7a, 0x7a, 0x77, 0x77, 0x77, 0x76, 0x76, 0x76, 0x77, + 0x78, 0x7d, 0x82, 0x89, 0x8e, 0x92, 0x95, 0x95, 0x91, 0x8b, 0x84, 0x7d, 0x77, 0x73, 0x72, 0x72, + 0x74, 0x75, 0x75, 0x75, 0x76, 0x74, 0x73, 0x73, 0x74, 0x79, 0x81, 0x89, 0x8f, 0x96, 0x9b, 0x9c, + 0x98, 0x91, 0x88, 0x7e, 0x77, 0x74, 0x73, 0x74, 0x77, 0x7b, 0x7c, 0x7a, 0x77, 0x73, 0x6d, 0x69, + 0x68, 0x6a, 0x73, 0x7f, 0x87, 0x8e, 0x99, 0xa1, 0x9e, 0x97, 0x90, 0x86, 0x7c, 0x76, 0x77, 0x7b, + 0x80, 0x89, 0x91, 0x93, 0x91, 0x8e, 0x87, 0x7c, 0x71, 0x6b, 0x65, 0x60, 0x5d, 0x5f, 0x60, 0x61, + 0x6b, 0x7b, 0x84, 0x8d, 0xa0, 0xae, 0xae, 0xa8, 0xa1, 0x94, 0x81, 0x73, 0x6f, 0x70, 0x74, 0x7e, + 0x8d, 0x95, 0x97, 0x98, 0x92, 0x83, 0x72, 0x69, 0x61, 0x5a, 0x56, 0x59, 0x5d, 0x5f, 0x65, 0x75, + 0x82, 0x87, 0x95, 0xaa, 0xb4, 0xb0, 0xaa, 0xa0, 0x8d, 0x77, 0x6c, 0x6c, 0x6d, 0x72, 0x81, 0x91, + 0x98, 0x9a, 0x9a, 0x8f, 0x7a, 0x6a, 0x61, 0x58, 0x4f, 0x50, 0x57, 0x5b, 0x61, 0x74, 0x85, 0x8a, + 0x96, 0xab, 0xb4, 0xae, 0xa5, 0x9c, 0x88, 0x71, 0x67, 0x69, 0x6c, 0x73, 0x85, 0x96, 0x9d, 0xa1, + 0xa3, 0x96, 0x7f, 0x6e, 0x63, 0x56, 0x4c, 0x4d, 0x52, 0x53, 0x58, 0x6b, 0x80, 0x86, 0x92, 0xaa, + 0xb8, 0xb4, 0xac, 0xa5, 0x90, 0x75, 0x69, 0x6a, 0x6c, 0x73, 0x86, 0x98, 0x9c, 0xa2, 0xa7, 0x99, + 0x7f, 0x6e, 0x61, 0x54, 0x4c, 0x4b, 0x4d, 0x4f, 0x54, 0x66, 0x7c, 0x85, 0x90, 0xa9, 0xbc, 0xba, + 0xb4, 0xac, 0x95, 0x78, 0x69, 0x67, 0x67, 0x71, 0x86, 0x99, 0x9d, 0xa4, 0xab, 0x9b, 0x7f, 0x6e, + 0x5f, 0x50, 0x4b, 0x4e, 0x4e, 0x4e, 0x54, 0x60, 0x77, 0x86, 0x8e, 0xa4, 0xbb, 0xbf, 0xb9, 0xb3, + 0x9e, 0x7d, 0x68, 0x65, 0x63, 0x6b, 0x84, 0x9a, 0x9d, 0xa3, 0xb0, 0x9f, 0x83, 0x71, 0x5f, 0x4d, + 0x4c, 0x51, 0x51, 0x51, 0x56, 0x5a, 0x64, 0x7d, 0x90, 0x99, 0xad, 0xc3, 0xc2, 0xb5, 0xaa, 0x92, + 0x71, 0x62, 0x65, 0x6a, 0x78, 0x92, 0xa2, 0xa1, 0xa7, 0xa8, 0x91, 0x78, 0x66, 0x55, 0x4a, 0x50, + 0x54, 0x50, 0x50, 0x58, 0x5a, 0x65, 0x8b, 0x9b, 0x9b, 0xb7, 0xc9, 0xb3, 0xa6, 0xa2, 0x7d, 0x5a, + 0x66, 0x6f, 0x70, 0x94, 0xa2, 0x90, 0x9b, 0xa5, 0x8f, 0x82, 0x77, 0x5c, 0x58, 0x60, 0x50, 0x46, + 0x56, 0x49, 0x3a, 0x54, 0x97, 0xbe, 0xa9, 0xb0, 0xad, 0x91, 0xa7, 0xb3, 0x83, 0x6f, 0x6c, 0x5b, + 0x71, 0x91, 0x9c, 0xac, 0x98, 0x78, 0x8a, 0xa6, 0xad, 0x9e, 0x72, 0x4d, 0x4e, 0x4f, 0x4e, 0x4a, + 0x48, 0x46, 0x42, 0x4e, 0x99, 0xd5, 0xae, 0xb0, 0xb1, 0x8a, 0xb3, 0xbd, 0x82, 0x6b, 0x53, 0x56, + 0x8b, 0x97, 0xa7, 0xaf, 0x74, 0x6b, 0x92, 0xaf, 0xc1, 0x8f, 0x55, 0x47, 0x4e, 0x60, 0x5e, 0x45, + 0x4a, 0x4f, 0x3a, 0x44, 0x9f, 0xdf, 0xac, 0xa8, 0x93, 0x79, 0xbf, 0xc3, 0x92, 0x67, 0x36, 0x5a, + 0x90, 0x9b, 0xb6, 0xa1, 0x6b, 0x68, 0x8d, 0xc3, 0xca, 0x83, 0x4f, 0x3d, 0x53, 0x72, 0x63, 0x46, + 0x44, 0x55, 0x4f, 0x4c, 0x78, 0xcb, 0xbb, 0x93, 0x99, 0x79, 0xad, 0xd0, 0x9f, 0x70, 0x37, 0x4f, + 0x90, 0x9e, 0xaf, 0x94, 0x73, 0x71, 0x89, 0xc0, 0xc0, 0x8f, 0x5b, 0x45, 0x62, 0x79, 0x6f, 0x5b, + 0x46, 0x56, 0x54, 0x53, 0x59, 0x90, 0xd8, 0x95, 0x8c, 0x8c, 0x88, 0xd6, 0xb8, 0x83, 0x4c, 0x2f, + 0x80, 0xa2, 0xaa, 0x9c, 0x69, 0x74, 0x80, 0xb0, 0xc6, 0x99, 0x78, 0x54, 0x69, 0x80, 0x7c, 0x69, + 0x4b, 0x4e, 0x57, 0x4e, 0x4c, 0x5f, 0xae, 0xc3, 0x82, 0x86, 0x83, 0xac, 0xd9, 0xa3, 0x6a, 0x31, + 0x50, 0xa0, 0xad, 0xa6, 0x6d, 0x59, 0x7f, 0x9e, 0xc8, 0xaf, 0x81, 0x74, 0x70, 0x8b, 0x83, 0x76, + 0x58, 0x50, 0x56, 0x59, 0x58, 0x49, 0x62, 0x7c, 0xce, 0x99, 0x71, 0x9c, 0x8d, 0xd4, 0xb1, 0x6c, + 0x4f, 0x37, 0x95, 0xab, 0x9b, 0x7f, 0x4b, 0x82, 0xa2, 0xba, 0xb5, 0x7b, 0x7d, 0x7d, 0x8d, 0x8b, + 0x71, 0x62, 0x54, 0x5b, 0x4e, 0x5d, 0x4c, 0x5e, 0x57, 0x9c, 0xd4, 0x67, 0x94, 0x83, 0xa2, 0xd8, + 0x83, 0x70, 0x2e, 0x59, 0xb5, 0x9d, 0xa1, 0x51, 0x55, 0x97, 0xad, 0xcb, 0x86, 0x77, 0x78, 0x95, + 0xa1, 0x76, 0x6d, 0x58, 0x67, 0x5b, 0x4f, 0x66, 0x55, 0x67, 0x4e, 0x67, 0xd9, 0x88, 0x89, 0x86, + 0x6f, 0xcd, 0x9b, 0x89, 0x4e, 0x39, 0x9f, 0xa0, 0xa9, 0x7a, 0x47, 0x88, 0x99, 0xbe, 0xac, 0x6b, + 0x88, 0x87, 0xaf, 0x9a, 0x67, 0x71, 0x63, 0x74, 0x62, 0x55, 0x5c, 0x5e, 0x65, 0x5c, 0x54, 0xb1, + 0xb0, 0x79, 0x8d, 0x6f, 0xac, 0xb7, 0x8e, 0x73, 0x44, 0x7b, 0xa1, 0x99, 0x90, 0x5a, 0x70, 0x97, + 0xa0, 0xb4, 0x89, 0x83, 0x8e, 0x96, 0xa3, 0x7e, 0x6f, 0x6c, 0x6a, 0x6b, 0x5b, 0x5a, 0x61, 0x5e, + 0x5d, 0x63, 0x66, 0xa0, 0xa6, 0x7c, 0x8d, 0x83, 0xa4, 0xad, 0x88, 0x7b, 0x58, 0x75, 0x95, 0x91, + 0x92, 0x70, 0x75, 0x93, 0x9c, 0xab, 0x92, 0x84, 0x8d, 0x91, 0x96, 0x81, 0x70, 0x6b, 0x6c, 0x68, + 0x62, 0x59, 0x5e, 0x69, 0x5a, 0x5a, 0x68, 0x5f, 0xa2, 0xb0, 0x6d, 0x87, 0x7e, 0xa0, 0xba, 0x89, + 0x78, 0x53, 0x73, 0xa6, 0x9b, 0x95, 0x6c, 0x65, 0x8e, 0x9a, 0xab, 0x97, 0x7b, 0x85, 0x8e, 0x9a, + 0x91, 0x71, 0x6b, 0x68, 0x65, 0x6e, 0x58, 0x5d, 0x70, 0x5d, 0x6d, 0x67, 0x5e, 0x80, 0x78, 0x94, + 0x98, 0x7c, 0x96, 0x90, 0xa1, 0xa5, 0x82, 0x7f, 0x70, 0x7e, 0x94, 0x87, 0x87, 0x80, 0x88, 0x92, + 0x8e, 0x96, 0x8c, 0x89, 0x84, 0x73, 0x72, 0x6f, 0x71, 0x6d, 0x5e, 0x61, 0x6a, 0x70, 0x77, 0x6f, + 0x6d, 0x79, 0x76, 0x7f, 0x77, 0x75, 0x7e, 0x90, 0xa8, 0x8c, 0x85, 0x98, 0x9b, 0xa7, 0x93, 0x79, + 0x78, 0x79, 0x91, 0x94, 0x87, 0x86, 0x85, 0x86, 0x8b, 0x89, 0x82, 0x7c, 0x74, 0x6d, 0x6c, 0x75, + 0x75, 0x6f, 0x64, 0x69, 0x74, 0x7e, 0x83, 0x76, 0x75, 0x85, 0x8a, 0x89, 0x88, 0x78, 0x81, 0x88, + 0x83, 0x85, 0x7e, 0x80, 0x88, 0x89, 0x8c, 0x8d, 0x8a, 0x8b, 0x88, 0x88, 0x89, 0x85, 0x81, 0x81, + 0x7e, 0x7c, 0x7c, 0x77, 0x7d, 0x76, 0x6f, 0x7d, 0x7f, 0x78, 0x73, 0x76, 0x83, 0x84, 0x80, 0x7f, + 0x82, 0x86, 0x80, 0x81, 0x83, 0x81, 0x81, 0x7e, 0x7d, 0x7b, 0x83, 0x8b, 0x85, 0x7a, 0x76, 0x83, + 0x87, 0x82, 0x7d, 0x76, 0x7b, 0x80, 0x83, 0x81, 0x7a, 0x79, 0x7d, 0x82, 0x81, 0x82, 0x82, 0x83, + 0x86, 0x80, 0x80, 0x81, 0x7e, 0x80, 0x7d, 0x7a, 0x7e, 0x81, 0x7e, 0x7e, 0x80, 0x7f, 0x81, 0x82, + 0x80, 0x81, 0x82, 0x7f, 0x7f, 0x7d, 0x7c, 0x7f, 0x7b, 0x7b, 0x7d, 0x7a, 0x7a, 0x7e, 0x7e, 0x7c, + 0x7c, 0x7f, 0x80, 0x7f, 0x80, 0x82, 0x81, 0x81, 0x80, 0x7e, 0x80, 0x7f, 0x81, 0x7b, 0x7c, 0x7f, + 0x7f, 0x81, 0x7f, 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x83, 0x7e, 0x7f, 0x85, 0x81, 0x83, + 0x84, 0x80, 0x84, 0x81, 0x81, 0x83, 0x81, 0x83, 0x80, 0x84, 0x80, 0x80, 0x85, 0x80, 0x81, 0x7f, + 0x82, 0x82, 0x81, 0x81, 0x80, 0x81, 0x80, 0x87, 0x81, 0x7c, 0x80, 0x7f, 0x80, 0x7d, 0x7c, 0x7d, + 0x80, 0x80, 0x80, 0x82, 0x7d, 0x81, 0x82, 0x7e, 0x82, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x82, + 0x7f, 0x80, 0x7f, 0x7f, 0x81, 0x7f, 0x80, 0x7e, 0x81, 0x80, 0x7e, 0x80, 0x7e, 0x7f, 0x80, 0x80, + 0x82, 0x7f, 0x83, 0x83, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x81, 0x80, 0x7f, 0x7f, 0x7f, 0x7e, 0x7f, 0x7e, 0x7d, 0x7e, 0x7d, 0x7c, 0x7d, 0x7c, 0x7c, + 0x7d, 0x7c, 0x7d, 0x7e, 0x7f, 0x7e, 0x7e, 0x7f, 0x7d, 0x7f, 0x7f, 0x80, 0x7f, 0x7e, 0x7f, 0x80, + 0x7e, 0x80, 0x7e, 0x7e, 0x80, 0x7e, 0x80, 0x7e, 0x7f, 0x7e, 0x7d, 0x7f, 0x7d, 0x7d, 0x7d, 0x7d, + 0x7d, 0x7d, 0x7e, 0x7f, 0x7f, 0x7d, 0x7e, 0x7f, 0x7e, 0x80, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x80, + 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x81, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, + 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x80, 0x7f, 0x7f, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80, + 0x81, 0x80, 0x82, 0x83, 0x81, 0x82, 0x81, 0x82, 0x82, 0x82, 0x81, 0x81, 0x83, 0x82, 0x82, 0x82, + 0x81, 0x83, 0x82, 0x81, 0x81, 0x80, 0x80, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7e, 0x80, 0x7d, 0x80, + 0x81, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x81, 0x81, 0x80, 0x81, 0x81, 0x80, + 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7e, 0x7f, 0x81, 0x80, 0x7f, 0x81, 0x81, 0x82, 0x81, + 0x80, 0x82, 0x82, 0x80, 0x81, 0x81, 0x80, 0x80, 0x7e, 0x7d, 0x7f, 0x7e, 0x81, 0x81, 0x7e, 0x7f, + 0x82, 0x7f, 0x7d, 0x7f, 0x7d, 0x81, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x80, + 0x7f, 0x7e, 0x7f, 0x7f, 0x7e, 0x7c, 0x7d, 0x7e, 0x7d, 0x7d, 0x7e, 0x7d, 0x7e, 0x7c, 0x7e, 0x7e, + 0x7c, 0x7e, 0x7d, 0x7e, 0x7e, 0x7e, 0x7d, 0x7d, 0x7c, 0x7b, 0x7c, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, + 0x7b, 0x7c, 0x7c, 0x7d, 0x7c, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7c, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, + 0x7d, 0x7d, 0x7d, 0x7d, 0x7e, 0x7d, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x82, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x81, + 0x81, 0x81, 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x81, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x81, 0x80, 0x83, 0x80, 0x80, 0x80, 0x84, 0x84, + 0x7b, 0x7e, 0x80, 0x80, 0x7e, 0x80, 0x7e, 0x7f, 0x81, 0x81, 0x80, 0x7f, 0x80, 0x7f, 0x7e, 0x7e, + 0x7f, 0x80, 0x80, 0x7f, 0x81, 0x82, 0x80, 0x80, 0x81, 0x81, 0x80, 0x81, 0x7f, 0x80, 0x80, 0x81, + 0x81, 0x81, 0x81, 0x84, 0x83, 0x7f, 0x7f, 0x80, 0x80, 0x7f, 0x81, 0x7e, 0x7e, 0x7f, 0x81, 0x7f, + 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x81, 0x82, 0x82, 0x80, 0x7f, 0x80, + 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, 0x7d, 0x7e, 0x7e, 0x7f, 0x80, + 0x80, 0x80, 0x80, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, 0x80, 0x80, 0x81, 0x7f, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x7e, 0x7e, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7f, + 0x7e, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7e, 0x80, 0x7e, 0x7f, 0x7f, + 0x7e, 0x7f, 0x7e, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7e, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x81, 0x80, 0x7f, 0x80, 0x80, 0x82, + 0x81, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x81, 0x81, 0x80, 0x81, 0x80, 0x82, 0x7f, + 0x7f, 0x7e, 0x7e, 0x80, 0x7e, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x81, 0x80, + 0x81, 0x80, 0x80, 0x81, 0x80, 0x83, 0x80, 0x80, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7e, 0x80, 0x7f, + 0x7f, 0x80, 0x7f, 0x82, 0x80, 0x81, 0x7f, 0x7e, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, + 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x80, 0x7f, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x80, 0x81, 0x80, 0x80, 0x80, 0x81, 0x81, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7e, 0x7e, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x81, 0x7f, 0x80, 0x7e, 0x7f, 0x7f, + 0x7e, 0x80, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7d, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, 0x80, + 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x81, 0x81, 0x81, 0x80, 0x80, 0x7f, 0x7f, 0x80, 0x7f, + 0x7f, 0x7f, 0x81, 0x81, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x7f, 0x7f, + 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x80, 0x81, 0x80, 0x80, 0x7f, 0x7e, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x7e, 0x7f, 0x7e +}; + +static const Uint8 right[1777] = { + 0x7f, 0x7e, 0x7e, 0x7f, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x82, 0x83, 0x83, 0x83, + 0x82, 0x81, 0x81, 0x80, 0x7f, 0x7e, 0x7c, 0x7b, 0x7a, 0x7a, 0x79, 0x79, 0x79, 0x7a, 0x7a, 0x7b, + 0x7c, 0x7e, 0x80, 0x82, 0x84, 0x86, 0x88, 0x89, 0x89, 0x89, 0x88, 0x87, 0x84, 0x82, 0x80, 0x7e, + 0x7c, 0x7b, 0x7a, 0x7a, 0x79, 0x78, 0x77, 0x75, 0x76, 0x77, 0x78, 0x78, 0x78, 0x7b, 0x81, 0x87, + 0x8c, 0x8e, 0x90, 0x92, 0x91, 0x8d, 0x87, 0x81, 0x7d, 0x7b, 0x7a, 0x79, 0x79, 0x7a, 0x79, 0x78, + 0x75, 0x74, 0x75, 0x75, 0x75, 0x76, 0x76, 0x76, 0x76, 0x7b, 0x83, 0x88, 0x8b, 0x8f, 0x95, 0x98, + 0x95, 0x8d, 0x86, 0x83, 0x80, 0x7e, 0x7c, 0x7c, 0x7e, 0x7e, 0x7c, 0x79, 0x78, 0x76, 0x75, 0x72, + 0x73, 0x74, 0x72, 0x6f, 0x6d, 0x72, 0x7e, 0x87, 0x8b, 0x90, 0x98, 0x9f, 0x9b, 0x91, 0x85, 0x7f, + 0x7b, 0x78, 0x79, 0x7f, 0x87, 0x8b, 0x8a, 0x89, 0x89, 0x86, 0x81, 0x79, 0x75, 0x74, 0x73, 0x73, + 0x6f, 0x6d, 0x6e, 0x6e, 0x6e, 0x6f, 0x72, 0x77, 0x82, 0x8f, 0x95, 0x99, 0x9c, 0x9e, 0x99, 0x8c, + 0x7f, 0x74, 0x71, 0x70, 0x74, 0x7e, 0x8a, 0x92, 0x91, 0x8f, 0x8f, 0x8d, 0x85, 0x7c, 0x76, 0x75, + 0x76, 0x75, 0x71, 0x6d, 0x6b, 0x68, 0x64, 0x64, 0x66, 0x6e, 0x83, 0x8f, 0x93, 0x9b, 0xa3, 0xa4, + 0x97, 0x86, 0x76, 0x6f, 0x6d, 0x6e, 0x78, 0x87, 0x94, 0x98, 0x96, 0x94, 0x91, 0x89, 0x7e, 0x74, + 0x6f, 0x70, 0x74, 0x72, 0x6e, 0x6b, 0x67, 0x62, 0x60, 0x60, 0x69, 0x84, 0x91, 0x95, 0xa1, 0xae, + 0xb0, 0x9b, 0x84, 0x74, 0x6a, 0x65, 0x67, 0x78, 0x8b, 0x98, 0x9f, 0x9e, 0x9a, 0x90, 0x86, 0x7c, + 0x71, 0x6a, 0x6c, 0x73, 0x74, 0x6d, 0x69, 0x65, 0x5e, 0x5c, 0x60, 0x6f, 0x8b, 0x95, 0x9b, 0xac, + 0xb3, 0xa5, 0x89, 0x7a, 0x6b, 0x5c, 0x5f, 0x70, 0x88, 0x97, 0xa5, 0xac, 0xa1, 0x95, 0x8e, 0x86, + 0x76, 0x6a, 0x6b, 0x72, 0x72, 0x6c, 0x67, 0x5e, 0x55, 0x52, 0x56, 0x78, 0x9c, 0x91, 0x9c, 0xbc, + 0xb8, 0x98, 0x83, 0x7f, 0x5e, 0x4c, 0x6c, 0x83, 0x8a, 0x9a, 0xb7, 0xae, 0x8a, 0x8f, 0x93, 0x79, + 0x69, 0x76, 0x76, 0x69, 0x70, 0x70, 0x5b, 0x50, 0x51, 0x57, 0x52, 0x77, 0xb2, 0x90, 0x95, 0xc8, + 0xb1, 0x8d, 0x89, 0x8a, 0x55, 0x4e, 0x87, 0x7f, 0x82, 0xb3, 0xb9, 0x8f, 0x8c, 0x9d, 0x79, 0x71, + 0x80, 0x6a, 0x61, 0x7b, 0x70, 0x51, 0x63, 0x62, 0x3e, 0x50, 0x61, 0x9a, 0xad, 0x7e, 0xba, 0xb5, + 0x94, 0x9f, 0x93, 0x75, 0x4b, 0x7b, 0x79, 0x6c, 0xab, 0xaf, 0x9f, 0x93, 0x8e, 0x7a, 0x7f, 0x89, + 0x6a, 0x6e, 0x71, 0x66, 0x5e, 0x63, 0x5c, 0x53, 0x53, 0x50, 0x5a, 0xb8, 0xbd, 0x6d, 0xc3, 0xb2, + 0x8a, 0xa7, 0xa1, 0x70, 0x4c, 0x88, 0x63, 0x7d, 0xb1, 0xa1, 0xa6, 0x8e, 0x6a, 0x7c, 0x95, 0x8b, + 0x84, 0x72, 0x5c, 0x5c, 0x67, 0x64, 0x61, 0x56, 0x65, 0x52, 0x44, 0x80, 0xda, 0x8a, 0x88, 0xc9, + 0x89, 0x96, 0xb1, 0x92, 0x4a, 0x6f, 0x6d, 0x78, 0xa5, 0xa7, 0xa0, 0x98, 0x66, 0x6e, 0xa6, 0x9d, + 0x95, 0x70, 0x52, 0x57, 0x73, 0x69, 0x72, 0x5a, 0x55, 0x52, 0x50, 0x3d, 0xb8, 0xdb, 0x5d, 0xa9, + 0xab, 0x82, 0xad, 0xc3, 0x65, 0x4c, 0x6c, 0x6d, 0x98, 0xac, 0x9f, 0x97, 0x74, 0x5a, 0xa0, 0xb1, + 0x9e, 0x7e, 0x52, 0x54, 0x74, 0x71, 0x6a, 0x6a, 0x5a, 0x53, 0x4b, 0x46, 0x5e, 0xe5, 0xaa, 0x62, + 0xab, 0x8f, 0x97, 0xcb, 0xa5, 0x4b, 0x4f, 0x67, 0x88, 0xa6, 0xa4, 0x98, 0x84, 0x61, 0x80, 0xb7, + 0xb4, 0x98, 0x64, 0x4e, 0x64, 0x77, 0x72, 0x72, 0x55, 0x54, 0x4e, 0x52, 0x3c, 0x96, 0xf0, 0x69, + 0x7f, 0xa2, 0x80, 0xc1, 0xc8, 0x75, 0x46, 0x4d, 0x74, 0xa4, 0x9e, 0x95, 0x8a, 0x6a, 0x73, 0xa6, + 0xb7, 0xb4, 0x81, 0x60, 0x5e, 0x71, 0x7c, 0x74, 0x6b, 0x54, 0x54, 0x48, 0x4f, 0x44, 0xc3, 0xcb, + 0x5b, 0x9b, 0x86, 0x99, 0xd4, 0xa3, 0x71, 0x3e, 0x4b, 0x91, 0x99, 0x9d, 0x95, 0x70, 0x72, 0x85, + 0xb0, 0xbd, 0xa5, 0x7e, 0x67, 0x67, 0x78, 0x77, 0x6e, 0x63, 0x53, 0x5b, 0x39, 0x50, 0x48, 0xb5, + 0xc2, 0x6a, 0xa5, 0x77, 0xa8, 0xbd, 0x98, 0x89, 0x3a, 0x60, 0x83, 0x85, 0xa9, 0x87, 0x87, 0x74, + 0x77, 0xac, 0xa9, 0xb9, 0x8a, 0x71, 0x6b, 0x6d, 0x81, 0x6d, 0x66, 0x51, 0x60, 0x3c, 0x50, 0x4a, + 0x91, 0xbf, 0x83, 0xae, 0x7a, 0xa4, 0xa1, 0x97, 0x92, 0x4b, 0x73, 0x68, 0x86, 0x8e, 0x8c, 0x95, + 0x79, 0x83, 0x86, 0xa2, 0xab, 0xa6, 0x8d, 0x79, 0x6a, 0x75, 0x68, 0x74, 0x56, 0x5c, 0x4e, 0x4c, + 0x49, 0x5d, 0xb1, 0x88, 0xb9, 0x8d, 0xa4, 0x90, 0x94, 0x8b, 0x66, 0x72, 0x69, 0x83, 0x7c, 0x91, + 0x82, 0x89, 0x79, 0x87, 0x8a, 0xa1, 0x9f, 0xa5, 0x95, 0x8d, 0x7a, 0x6f, 0x6f, 0x61, 0x62, 0x58, + 0x5f, 0x52, 0x52, 0x4f, 0x80, 0x90, 0xa1, 0xa6, 0xa3, 0x9c, 0x90, 0x86, 0x74, 0x6d, 0x6c, 0x7a, + 0x83, 0x8a, 0x8c, 0x88, 0x7f, 0x80, 0x82, 0x8f, 0x99, 0x9e, 0xa3, 0x9a, 0x93, 0x84, 0x73, 0x68, + 0x5d, 0x5e, 0x5d, 0x5f, 0x5e, 0x5d, 0x52, 0x6a, 0x7d, 0x8d, 0x9f, 0xa6, 0xac, 0xa0, 0x95, 0x7d, + 0x6e, 0x64, 0x6a, 0x76, 0x81, 0x8e, 0x98, 0x94, 0x8e, 0x84, 0x84, 0x84, 0x86, 0x91, 0x98, 0x9d, + 0x9a, 0x8c, 0x7b, 0x67, 0x5c, 0x58, 0x58, 0x5e, 0x5e, 0x64, 0x60, 0x67, 0x75, 0x7f, 0x8e, 0x99, + 0xa2, 0xa5, 0xa2, 0x99, 0x87, 0x74, 0x6a, 0x67, 0x6e, 0x7b, 0x87, 0x96, 0x97, 0x97, 0x94, 0x8e, + 0x8c, 0x8a, 0x8b, 0x8a, 0x8a, 0x88, 0x84, 0x7b, 0x72, 0x66, 0x5e, 0x58, 0x57, 0x5a, 0x60, 0x64, + 0x6c, 0x78, 0x81, 0x8c, 0x96, 0x9d, 0x9f, 0xa1, 0x99, 0x8f, 0x80, 0x76, 0x70, 0x6c, 0x70, 0x76, + 0x81, 0x8a, 0x93, 0x97, 0x98, 0x94, 0x91, 0x8c, 0x8a, 0x87, 0x81, 0x7c, 0x74, 0x71, 0x6b, 0x68, + 0x65, 0x62, 0x60, 0x61, 0x63, 0x67, 0x6c, 0x77, 0x82, 0x8a, 0x96, 0x9c, 0xa4, 0xa5, 0xa0, 0x95, + 0x86, 0x7b, 0x71, 0x6e, 0x6e, 0x73, 0x79, 0x82, 0x8b, 0x94, 0x99, 0x98, 0x95, 0x8e, 0x88, 0x81, + 0x7b, 0x77, 0x73, 0x6f, 0x6c, 0x6a, 0x68, 0x67, 0x66, 0x69, 0x69, 0x6e, 0x70, 0x77, 0x81, 0x88, + 0x91, 0x97, 0x9f, 0xa1, 0xa2, 0x9b, 0x92, 0x82, 0x77, 0x6c, 0x6a, 0x6b, 0x71, 0x79, 0x83, 0x8d, + 0x93, 0x97, 0x96, 0x95, 0x8f, 0x8b, 0x84, 0x7d, 0x76, 0x71, 0x6a, 0x68, 0x67, 0x68, 0x6b, 0x6d, + 0x71, 0x73, 0x76, 0x79, 0x7e, 0x83, 0x8a, 0x8f, 0x94, 0x97, 0x97, 0x95, 0x8f, 0x87, 0x7f, 0x79, + 0x76, 0x76, 0x78, 0x7a, 0x7e, 0x81, 0x86, 0x8a, 0x8c, 0x8e, 0x8d, 0x8a, 0x86, 0x80, 0x7c, 0x78, + 0x74, 0x71, 0x70, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, 0x80, 0x82, 0x82, 0x81, 0x80, + 0x7e, 0x7f, 0x81, 0x84, 0x88, 0x8b, 0x8d, 0x8d, 0x8b, 0x87, 0x83, 0x7d, 0x7a, 0x77, 0x78, 0x7a, + 0x7e, 0x81, 0x83, 0x84, 0x84, 0x84, 0x82, 0x80, 0x7f, 0x7d, 0x7b, 0x7a, 0x78, 0x78, 0x77, 0x78, + 0x78, 0x79, 0x7c, 0x7e, 0x81, 0x83, 0x83, 0x84, 0x83, 0x82, 0x81, 0x80, 0x80, 0x7f, 0x7f, 0x80, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x84, 0x84, 0x83, 0x83, 0x81, 0x80, 0x7f, 0x7f, 0x80, 0x80, 0x80, + 0x7f, 0x7e, 0x7d, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7f, 0x80, 0x80, 0x81, 0x80, 0x7f, 0x7e, 0x7d, + 0x7d, 0x7d, 0x7e, 0x80, 0x80, 0x81, 0x82, 0x82, 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x81, 0x80, + 0x81, 0x80, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x81, 0x80, 0x80, 0x7e, 0x7d, 0x7d, 0x7e, 0x7e, 0x7f, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x80, 0x80, 0x80, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x82, 0x82, 0x82, 0x81, 0x80, + 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7e, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x82, + 0x81, 0x81, 0x80, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x81, 0x80, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7d, 0x7e, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x82, 0x82, 0x81, 0x81, 0x80, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x80, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x80, + 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7f, 0x87, 0x83, 0x7d, 0x81, 0x80, 0x7e, 0x81, 0x7b, + 0x7d, 0x84, 0x7f, 0x81, 0x83, 0x82, 0x7f, 0x80, 0x7c, 0x7b, 0x7d, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x7e, 0x7f, 0x7e, 0x7f, 0x80, 0x80, 0x81, 0x82, 0x82, 0x82, 0x82, 0x80, 0x80, 0x7f, 0x7f, + 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x82, 0x80, 0x7f, 0x80, 0x81, 0x80, 0x81, 0x7f, + 0x83, 0x85, 0x7f, 0x80, 0x84, 0x83, 0x7d, 0x7c, 0x7d, 0x80, 0x7d, 0x7d, 0x7e, 0x7e, 0x7d, 0x83, + 0x81, 0x7d, 0x7d, 0x81, 0x7f, 0x7c, 0x7c, 0x7c, 0x7d, 0x7c, 0x83, 0x80, 0x84, 0x84, 0x82, 0x7d, + 0x7f, 0x7d, 0x7c, 0x7e, 0x7e, 0x7f, 0x81, 0x84, 0x82, 0x81, 0x7e, 0x7f, 0x7f, 0x7f, 0x7e, 0x80, + 0x81, 0x80, 0x7f, 0x80, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, + 0x7f, 0x7f, 0x7f, 0x7f, 0x81, 0x80, 0x82, 0x81, 0x83, 0x81, 0x82, 0x80, 0x80, 0x7f, 0x7f, 0x80, + 0x7d, 0x80, 0x7e, 0x81, 0x7f, 0x81, 0x7f, 0x80, 0x7f, 0x7f, 0x7e, 0x7d, 0x81, 0x80, 0x82, 0x7f, + 0x81, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x7e, 0x80, 0x7f, 0x81, 0x7f, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x82, 0x81, 0x81, 0x80, 0x81, 0x7f, 0x80, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x7e, 0x7f, 0x7f, 0x7e, + 0x7f, 0x7e, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7e, 0x80, 0x7f, 0x82, 0x80, 0x81, 0x81, 0x80, + 0x81, 0x80, 0x81, 0x80, 0x81, 0x80, 0x81, 0x80, 0x80, 0x7e, 0x7f, 0x7e, 0x7d, 0x7e, 0x7e, 0x7f, + 0x7f, 0x7f, 0x7e, 0x7d, 0x80, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x81, 0x7e, 0x81, 0x81, 0x83, + 0x80, 0x81, 0x80, 0x80, 0x81, 0x7f, 0x80, 0x80, 0x80, 0x81, 0x7f, 0x80, 0x7f, 0x80, 0x7d, 0x80, + 0x7e, 0x7d, 0x80, 0x80, 0x83, 0x7f, 0x83, 0x7e, 0x83, 0x7f, 0x80, 0x7f, 0x7e, 0x81, 0x7f, 0x7f, + 0x80, 0x80, 0x81, 0x7e, 0x7f, 0x7f, 0x80, 0x81, 0x80, 0x83, 0x7f, 0x82, 0x7f, 0x82, 0x7f, 0x80, + 0x80, 0x7e, 0x7f, 0x7d, 0x7e, 0x7d, 0x7e, 0x7e, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x81, + 0x82, 0x80, 0x82, 0x80, 0x82, 0x80, 0x7f, 0x7e, 0x7f, 0x7e, 0x7f, 0x80, 0x7e, 0x80, 0x80, 0x81, + 0x7f, 0x7f, 0x7e, 0x80, 0x7d, 0x7e, 0x7e, 0x7f, 0x80, 0x7f, 0x80, 0x7e, 0x81, 0x7e, 0x81, 0x7f, + 0x80, 0x7f, 0x80, 0x81, 0x7f, 0x80, 0x7e, 0x81, 0x7e, 0x80, 0x7d, 0x80, 0x80, 0x80, 0x81, 0x80, + 0x82, 0x7e, 0x83, 0x7d, 0x80, 0x7c, 0x7d, 0x7e, 0x7c, 0x7e, 0x7d, 0x7e, 0x7f, 0x7e, 0x7e, 0x80, + 0x7e, 0x81, 0x7e, 0x81, 0x7f, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x83, 0x80, 0x81, 0x80, + 0x80, 0x80, 0x80, 0x7f, 0x80, 0x7e, 0x7f, 0x7f, 0x81, 0x7f, 0x80, 0x80, 0x7f, 0x7e, 0x80, 0x80, + 0x81, 0x82, 0x81, 0x82, 0x81, 0x81, 0x81, 0x82, 0x80, 0x80, 0x7e, 0x82, 0x80, 0x84, 0x81, 0x80, + 0x7f, 0x81, 0x80, 0x7f, 0x80, 0x7d, 0x80, 0x7d, 0x81, 0x7f, 0x81, 0x80, 0x81, 0x81, 0x80, 0x80, + 0x7e, 0x80, 0x7f, 0x81, 0x7f, 0x81, 0x81, 0x81, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x81, 0x80, 0x80, 0x7e, 0x81, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7e, 0x81, + 0x7e, 0x7f, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x81, 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x7f, 0x80, + 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7e, 0x7d, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x81, + 0x80 +}; + diff --git a/examples/audio/05-planar-data/thumbnail.png b/examples/audio/05-planar-data/thumbnail.png new file mode 100644 index 0000000000000..3a040dfaef38a Binary files /dev/null and b/examples/audio/05-planar-data/thumbnail.png differ diff --git a/include/SDL3/SDL_assert.h b/include/SDL3/SDL_assert.h index 6c90acc0290c9..ef5f85d0584fe 100644 --- a/include/SDL3/SDL_assert.h +++ b/include/SDL3/SDL_assert.h @@ -132,7 +132,7 @@ extern "C" { #define SDL_TriggerBreakpoint() __debugbreak() #elif defined(_MSC_VER) && defined(_M_IX86) #define SDL_TriggerBreakpoint() { _asm { int 0x03 } } -#elif defined(ANDROID) +#elif defined(ANDROID) || defined(__SYMBIAN32__) #include #define SDL_TriggerBreakpoint() assert(0) #elif SDL_HAS_BUILTIN(__builtin_debugtrap) diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index c6acf885f42a8..0213f0cea2ca1 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -1414,6 +1414,50 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetAudioStreamOutputChannelMap(SDL_AudioStr */ extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len); +/** + * Add data to the stream with each channel in a separate array. + * + * This data must match the format/channels/samplerate specified in the latest + * call to SDL_SetAudioStreamFormat, or the format specified when creating the + * stream if it hasn't been changed. + * + * The data will be interleaved and queued. Note that SDL_AudioStream only + * operates on interleaved data, so this is simply a convenience function for + * easily queueing data from sources that provide separate arrays. There is no + * equivalent function to retrieve planar data. + * + * The arrays in `channel_buffers` are ordered as they are to be interleaved; + * the first array will be the first sample in the interleaved data. Any + * individual array may be NULL; in this case, silence will be interleaved for + * that channel. + * + * Note that `num_samples` is the number of _samples per array_. This can also + * be thought of as the number of _sample frames_ to be queued. A value of 1 + * with stereo arrays will queue two samples to the stream. This is different + * than SDL_PutAudioStreamData, which wants the size of a single array in + * bytes. + * + * \param stream the stream the audio data is being added to. + * \param channel_buffers a pointer to an array of arrays, one array per + * channel. + * \param num_samples the number of _samples_ per array to write to the + * stream. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety It is safe to call this function from any thread, but if the + * stream has a callback set, the caller might need to manage + * extra locking. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_ClearAudioStream + * \sa SDL_FlushAudioStream + * \sa SDL_GetAudioStreamData + * \sa SDL_GetAudioStreamQueued + */ +extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *channel_buffers, int num_samples); + /** * Get converted/resampled data from the stream. * @@ -1583,8 +1627,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_PauseAudioStreamDevice(SDL_AudioStream *str * previously been paused. Once unpaused, any bound audio streams will begin * to progress again, and audio can be generated. * - * Remember, SDL_OpenAudioDeviceStream opens device in a paused state, so this - * function call is required for audio playback to begin on such device. + * SDL_OpenAudioDeviceStream opens audio devices in a paused state, so this + * function call is required for audio playback to begin on such devices. * * \param stream the audio stream associated with the audio device to resume. * \returns true on success or false on failure; call SDL_GetError() for more diff --git a/include/SDL3/SDL_begin_code.h b/include/SDL3/SDL_begin_code.h index a6b47cf4b916d..7adf1b9f59177 100644 --- a/include/SDL3/SDL_begin_code.h +++ b/include/SDL3/SDL_begin_code.h @@ -389,7 +389,7 @@ #endif /* SDL_FORCE_INLINE not defined */ #ifndef SDL_NORETURN -#ifdef __GNUC__ +#if defined(__GNUC__) #define SDL_NORETURN __attribute__((noreturn)) #elif defined(_MSC_VER) #define SDL_NORETURN __declspec(noreturn) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index 216a2ac14277f..d4b07dc6b10b9 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -211,9 +211,13 @@ * * ## System Requirements * - * **Vulkan:** Supported on Windows, Linux, Nintendo Switch, and certain - * Android devices. Requires Vulkan 1.0 with the following extensions and - * device features: + * ### Vulkan + * + * SDL driver name: "vulkan" (for use in SDL_CreateGPUDevice() and + * SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING) + * + * Supported on Windows, Linux, Nintendo Switch, and certain Android devices. + * Requires Vulkan 1.0 with the following extensions and device features: * * - `VK_KHR_swapchain` * - `VK_KHR_maintenance1` @@ -224,12 +228,19 @@ * - `drawIndirectFirstInstance` * - `sampleRateShading` * - * **D3D12:** Supported on Windows 10 or newer, Xbox One (GDK), and Xbox - * Series X|S (GDK). Requires a GPU that supports DirectX 12 Feature Level - * 11_1. + * ### D3D12 + * + * SDL driver name: "direct3d12" + * + * Supported on Windows 10 or newer, Xbox One (GDK), and Xbox Series X|S + * (GDK). Requires a GPU that supports DirectX 12 Feature Level 11_1. + * + * ### Metal * - * **Metal:** Supported on macOS 10.14+ and iOS/tvOS 13.0+. Hardware - * requirements vary by operating system: + * SDL driver name: "metal" + * + * Supported on macOS 10.14+ and iOS/tvOS 13.0+. Hardware requirements vary by + * operating system: * * - macOS requires an Apple Silicon or * [Intel Mac2 family](https://developer.apple.com/documentation/metal/mtlfeatureset/mtlfeatureset_macos_gpufamily2_v1?language=objc) @@ -240,14 +251,17 @@ * ## Coordinate System * * The GPU API uses a left-handed coordinate system, following the convention - * of D3D12 and Metal. Specifically: - **Normalized Device Coordinates:** The - * lower-left corner has an x,y coordinate of `(-1.0, -1.0)`. The upper-right - * corner is `(1.0, 1.0)`. Z values range from `[0.0, 1.0]` where 0 is the - * near plane. - **Viewport Coordinates:** The top-left corner has an x,y - * coordinate of `(0, 0)` and extends to the bottom-right corner at - * `(viewportWidth, viewportHeight)`. +Y is down. - **Texture Coordinates:** - * The top-left corner has an x,y coordinate of `(0, 0)` and extends to the - * bottom-right corner at `(1.0, 1.0)`. +Y is down. + * of D3D12 and Metal. Specifically: + * + * - **Normalized Device Coordinates:** The lower-left corner has an x,y + * coordinate of `(-1.0, -1.0)`. The upper-right corner is `(1.0, 1.0)`. Z + * values range from `[0.0, 1.0]` where 0 is the near plane. + * - **Viewport Coordinates:** The top-left corner has an x,y coordinate of + * `(0, 0)` and extends to the bottom-right corner at `(viewportWidth, + * viewportHeight)`. +Y is down. + * - **Texture Coordinates:** The top-left corner has an x,y coordinate of + * `(0, 0)` and extends to the bottom-right corner at `(1.0, 1.0)`. +Y is + * down. * * If the backend driver differs from this convention (e.g. Vulkan, which has * an NDC that assumes +Y is down), SDL will automatically convert the @@ -2135,6 +2149,13 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GPUSupportsProperties( /** * Creates a GPU context. * + * The GPU driver name can be one of the following: + * + * - "vulkan": [Vulkan](CategoryGPU#vulkan) + * - "direct3d12": [D3D12](CategoryGPU#d3d12) + * - "metal": [Metal](CategoryGPU#metal) + * - NULL: let SDL pick the optimal driver + * * \param format_flags a bitflag indicating which shader formats the app is * able to provide. * \param debug_mode enable debug mode properties and validations. @@ -2145,6 +2166,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GPUSupportsProperties( * * \since This function is available since SDL 3.2.0. * + * \sa SDL_CreateGPUDeviceWithProperties * \sa SDL_GetGPUShaderFormats * \sa SDL_GetGPUDeviceDriver * \sa SDL_DestroyGPUDevice @@ -2604,9 +2626,9 @@ extern SDL_DECLSPEC SDL_GPUShader * SDLCALL SDL_CreateGPUShader( * - `SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT`: (Direct3D 12 only) * if the texture usage is SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, clear * the texture to a depth of this value. Defaults to zero. - * - `SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_UINT8`: (Direct3D 12 + * - `SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_NUMBER`: (Direct3D 12 * only) if the texture usage is SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, - * clear the texture to a stencil of this value. Defaults to zero. + * clear the texture to a stencil of this Uint8 value. Defaults to zero. * - `SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING`: a name that can be displayed * in debugging tools. * @@ -2632,13 +2654,13 @@ extern SDL_DECLSPEC SDL_GPUTexture * SDLCALL SDL_CreateGPUTexture( SDL_GPUDevice *device, const SDL_GPUTextureCreateInfo *createinfo); -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_R_FLOAT "SDL.gpu.texture.create.d3d12.clear.r" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_G_FLOAT "SDL.gpu.texture.create.d3d12.clear.g" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_B_FLOAT "SDL.gpu.texture.create.d3d12.clear.b" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_A_FLOAT "SDL.gpu.texture.create.d3d12.clear.a" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT "SDL.gpu.texture.create.d3d12.clear.depth" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_UINT8 "SDL.gpu.texture.create.d3d12.clear.stencil" -#define SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING "SDL.gpu.texture.create.name" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_R_FLOAT "SDL.gpu.texture.create.d3d12.clear.r" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_G_FLOAT "SDL.gpu.texture.create.d3d12.clear.g" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_B_FLOAT "SDL.gpu.texture.create.d3d12.clear.b" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_A_FLOAT "SDL.gpu.texture.create.d3d12.clear.a" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT "SDL.gpu.texture.create.d3d12.clear.depth" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_NUMBER "SDL.gpu.texture.create.d3d12.clear.stencil" +#define SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING "SDL.gpu.texture.create.name" /** * Creates a buffer object to be used in graphics or compute workflows. diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index df35bd8799eff..9f1685de76993 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -1072,8 +1072,8 @@ extern "C" { * * By default, SDL will try all available GPU backends in a reasonable order * until it finds one that can work, but this hint allows the app or user to - * force a specific target, such as "direct3d11" if, say, your hardware - * supports D3D12 but want to try using D3D11 instead. + * force a specific target, such as "direct3d12" if, say, your hardware + * supports Vulkan but you want to try using D3D12 instead. * * This hint should be set before any GPU functions are called. * diff --git a/include/SDL3/SDL_mouse.h b/include/SDL3/SDL_mouse.h index 354771faffc2a..4e522251a6696 100644 --- a/include/SDL3/SDL_mouse.h +++ b/include/SDL3/SDL_mouse.h @@ -147,6 +147,19 @@ typedef enum SDL_MouseWheelDirection */ typedef Uint32 SDL_MouseButtonFlags; +#define SDL_BUTTON_LEFT 1 +#define SDL_BUTTON_MIDDLE 2 +#define SDL_BUTTON_RIGHT 3 +#define SDL_BUTTON_X1 4 +#define SDL_BUTTON_X2 5 + +#define SDL_BUTTON_MASK(X) (1u << ((X)-1)) +#define SDL_BUTTON_LMASK SDL_BUTTON_MASK(SDL_BUTTON_LEFT) +#define SDL_BUTTON_MMASK SDL_BUTTON_MASK(SDL_BUTTON_MIDDLE) +#define SDL_BUTTON_RMASK SDL_BUTTON_MASK(SDL_BUTTON_RIGHT) +#define SDL_BUTTON_X1MASK SDL_BUTTON_MASK(SDL_BUTTON_X1) +#define SDL_BUTTON_X2MASK SDL_BUTTON_MASK(SDL_BUTTON_X2) + /** * A callback used to transform mouse motion delta from raw values. * @@ -186,20 +199,6 @@ typedef void (SDLCALL *SDL_MouseMotionTransformCallback)( float *x, float *y ); -#define SDL_BUTTON_LEFT 1 -#define SDL_BUTTON_MIDDLE 2 -#define SDL_BUTTON_RIGHT 3 -#define SDL_BUTTON_X1 4 -#define SDL_BUTTON_X2 5 - -#define SDL_BUTTON_MASK(X) (1u << ((X)-1)) -#define SDL_BUTTON_LMASK SDL_BUTTON_MASK(SDL_BUTTON_LEFT) -#define SDL_BUTTON_MMASK SDL_BUTTON_MASK(SDL_BUTTON_MIDDLE) -#define SDL_BUTTON_RMASK SDL_BUTTON_MASK(SDL_BUTTON_RIGHT) -#define SDL_BUTTON_X1MASK SDL_BUTTON_MASK(SDL_BUTTON_X1) -#define SDL_BUTTON_X2MASK SDL_BUTTON_MASK(SDL_BUTTON_X2) - - /* Function prototypes */ /** diff --git a/include/SDL3/SDL_platform_defines.h b/include/SDL3/SDL_platform_defines.h index 6b240a8be4579..5bb82ba930738 100644 --- a/include/SDL3/SDL_platform_defines.h +++ b/include/SDL3/SDL_platform_defines.h @@ -317,7 +317,7 @@ #define SDL_PLATFORM_CYGWIN 1 #endif -#if defined(_WIN32) || defined(SDL_PLATFORM_CYGWIN) +#if (defined(_WIN32) || defined(SDL_PLATFORM_CYGWIN)) && !defined(__NGAGE__) /** * A preprocessor macro that is only defined if compiling for Windows. @@ -473,4 +473,14 @@ #define SDL_PLATFORM_3DS 1 #endif +#ifdef __NGAGE__ + +/** + * A preprocessor macro that is only defined if compiling for the Nokia N-Gage. + * + * \since This macro is available since SDL 3.1.3. + */ +#define SDL_PLATFORM_NGAGE 1 +#endif + #endif /* SDL_platform_defines_h_ */ diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h index dc5107d62570b..e2e42750c4926 100644 --- a/include/SDL3/SDL_render.h +++ b/include/SDL3/SDL_render.h @@ -1666,8 +1666,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderViewport(SDL_Renderer *renderer, S * Return whether an explicit rectangle was set as the viewport. * * This is useful if you're saving and restoring the viewport and want to know - * whether you should restore a specific rectangle or NULL. Note that the - * viewport is always reset when changing rendering targets. + * whether you should restore a specific rectangle or NULL. * * Each render target has its own viewport. This function checks the viewport * for the current render target. diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h index 7cd9d2b122760..86469802afdc8 100644 --- a/include/SDL3/SDL_surface.h +++ b/include/SDL3/SDL_surface.h @@ -1136,9 +1136,6 @@ extern SDL_DECLSPEC bool SDLCALL SDL_FillSurfaceRects(SDL_Surface *dst, const SD * If either `srcrect` or `dstrect` are NULL, the entire surface (`src` or * `dst`) is copied while ensuring clipping to `dst->clip_rect`. * - * The final blit rectangles are saved in `srcrect` and `dstrect` after all - * clipping is performed. - * * The blit function should not be called on a locked surface. * * The blit semantics for surfaces with and without blending and colorkey are diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index e3ea31e04aca2..aca10b6453928 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -1501,8 +1501,8 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *window) * * On OpenVR: * - * - `SDL_PROP_WINDOW_OPENVR_OVERLAY_ID`: the OpenVR Overlay Handle ID for the - * associated overlay window. + * - `SDL_PROP_WINDOW_OPENVR_OVERLAY_ID_NUMBER`: the OpenVR Overlay Handle ID + * for the associated overlay window. * * On Vivante: * @@ -1587,7 +1587,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window #define SDL_PROP_WINDOW_KMSDRM_GBM_DEVICE_POINTER "SDL.window.kmsdrm.gbm_dev" #define SDL_PROP_WINDOW_COCOA_WINDOW_POINTER "SDL.window.cocoa.window" #define SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER "SDL.window.cocoa.metal_view_tag" -#define SDL_PROP_WINDOW_OPENVR_OVERLAY_ID "SDL.window.openvr.overlay_id" +#define SDL_PROP_WINDOW_OPENVR_OVERLAY_ID_NUMBER "SDL.window.openvr.overlay_id" #define SDL_PROP_WINDOW_VIVANTE_DISPLAY_POINTER "SDL.window.vivante.display" #define SDL_PROP_WINDOW_VIVANTE_WINDOW_POINTER "SDL.window.vivante.window" #define SDL_PROP_WINDOW_VIVANTE_SURFACE_POINTER "SDL.window.vivante.surface" diff --git a/include/build_config/SDL_build_config.h b/include/build_config/SDL_build_config.h index 83031b70cc0e7..711399e4dcb23 100644 --- a/include/build_config/SDL_build_config.h +++ b/include/build_config/SDL_build_config.h @@ -45,6 +45,8 @@ #include "SDL_build_config_ios.h" #elif defined(SDL_PLATFORM_ANDROID) #include "SDL_build_config_android.h" +#elif defined(SDL_PLATFORM_NGAGE) +#include "SDL_build_config_ngage.h" #else /* This is a minimal configuration just to get SDL running on new platforms. */ #include "SDL_build_config_minimal.h" diff --git a/include/build_config/SDL_build_config_ngage.h b/include/build_config/SDL_build_config_ngage.h new file mode 100644 index 0000000000000..e52843735166a --- /dev/null +++ b/include/build_config/SDL_build_config_ngage.h @@ -0,0 +1,72 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_build_config_ngage_h_ +#define SDL_build_config_ngage_h_ +#define SDL_build_config_h_ + +#include +#include +#include +#include +#include +#include + +#define SDL_AUDIO_DRIVER_NGAGE 1 +#define SDL_CAMERA_DISABLED 1 +#define SDL_ASSERT_LEVEL 0 +#define SDL_FSOPS_POSIX 1 +#define SDL_GPU_DISABLED 1 +#define SDL_HAPTIC_DISABLED 1 +#define SDL_JOYSTICK_DISABLED 1 +#define SDL_LEAN_AND_MEAN 1 +#define SDL_MAIN_USE_CALLBACKS 1 +#define SDL_SENSOR_DISABLED 1 +#define SDL_THREADS_DISABLED 1 +#define SDL_VIDEO_DRIVER_NGAGE 1 +#define SDL_VIDEO_RENDER_NGAGE 1 + +#define HAVE_ATAN 1 +#define HAVE_ATAN2 1 +#define HAVE_COPYSIGN 1 +#define HAVE_COS 1 +#define HAVE_EXP 1 +#define HAVE_FABS 1 +#define HAVE_FLOOR 1 +#define HAVE_FMOD 1 +#define HAVE_ISINF 1 +#define HAVE_ISNAN 1 +#define HAVE_LOG 1 +#define HAVE_LOG10 1 +#define HAVE_MALLOC 1 +#define HAVE_MATH_H 1 +#define HAVE_MODF 1 +#define HAVE_POW 1 +#define HAVE_SCALBN 1 +#define HAVE_SIN 1 +#define HAVE_STDIO_H 1 +#define HAVE_SQRT 1 +#define HAVE_TAN 1 + +#define isnanf(x) SDL_uclibc_isnanf(x); +#define isinff(x) SDL_uclibc_isinff(x); + +#endif /* SDL_build_config_ngage_h_ */ diff --git a/src/SDL.c b/src/SDL.c index 502f6617a4f26..34e15251b14f7 100644 --- a/src/SDL.c +++ b/src/SDL.c @@ -728,6 +728,8 @@ const char *SDL_GetPlatform(void) return "macOS"; #elif defined(SDL_PLATFORM_NETBSD) return "NetBSD"; +#elif defined(SDL_PLATFORM_NGAGE) + return "Nokia N-Gage"; #elif defined(SDL_PLATFORM_OPENBSD) return "OpenBSD"; #elif defined(SDL_PLATFORM_OS2) diff --git a/src/SDL_error.c b/src/SDL_error.c index 3c62c8aff4870..3f4273b4f9376 100644 --- a/src/SDL_error.c +++ b/src/SDL_error.c @@ -20,6 +20,8 @@ */ #include "SDL_internal.h" +#include "stdlib/SDL_vacopy.h" + // Simple error handling in SDL #include "SDL_error_c.h" diff --git a/src/SDL_log.c b/src/SDL_log.c index 10a814ff1b272..da55dcf1c19ca 100644 --- a/src/SDL_log.c +++ b/src/SDL_log.c @@ -587,6 +587,25 @@ void SDL_LogMessageV(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_S return; } +#if defined(SDL_PLATFORM_NGAGE) + extern void NGAGE_vnprintf(char *buf, size_t size, const char *fmt, va_list ap); + char buf[1024]; + NGAGE_vnprintf(buf, sizeof(buf), fmt, ap); + +#ifdef ENABLE_FILE_LOG + FILE* file; + file = fopen("E:/SDL_Log.txt", "a"); + if (file) + { + vfprintf(file, fmt, ap); + fprintf(file, "\n"); + (void)fclose(file); + } +#endif + + return; +#endif + // Render into stack buffer va_copy(aq, ap); len = SDL_vsnprintf(stack_buf, sizeof(stack_buf), fmt, aq); @@ -767,9 +786,14 @@ static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority (void)fclose(pFile); } } +#elif defined(SDL_PLATFORM_NGAGE) + { + /* Nothing to do here. */ + } #endif #if defined(HAVE_STDIO_H) && \ !(defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))) && \ + !(defined(SDL_PLATFORM_NGAGE)) && \ !(defined(SDL_PLATFORM_WIN32)) (void)fprintf(stderr, "%s%s\n", GetLogPriorityPrefix(priority), message); #endif diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index cf817e4f19a12..a4473e24d289e 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -77,6 +77,9 @@ static const AudioBootStrap *const bootstrap[] = { #ifdef SDL_AUDIO_DRIVER_N3DS &N3DSAUDIO_bootstrap, #endif +#ifdef SDL_AUDIO_DRIVER_NGAGE + &NGAGEAUDIO_bootstrap, +#endif #ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN &EMSCRIPTENAUDIO_bootstrap, #endif diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index f751b0e580bdb..085308a73c6ad 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -768,64 +768,70 @@ bool SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain) static bool CheckAudioStreamIsFullySetup(SDL_AudioStream *stream) { - if (stream->src_spec.format == 0) { + if (stream->src_spec.format == SDL_AUDIO_UNKNOWN) { return SDL_SetError("Stream has no source format"); - } else if (stream->dst_spec.format == 0) { + } else if (stream->dst_spec.format == SDL_AUDIO_UNKNOWN) { return SDL_SetError("Stream has no destination format"); } return true; } -static bool PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void* userdata) +// you MUST hold `stream->lock` when calling this, and validate your parameters! +static bool PutAudioStreamBufferInternal(SDL_AudioStream *stream, const SDL_AudioSpec *spec, const int *chmap, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void* userdata) { -#if DEBUG_AUDIOSTREAM - SDL_Log("AUDIOSTREAM: wants to put %d bytes", len); -#endif - - SDL_LockMutex(stream->lock); - - if (!CheckAudioStreamIsFullySetup(stream)) { - SDL_UnlockMutex(stream->lock); - return false; - } - - if ((len % SDL_AUDIO_FRAMESIZE(stream->src_spec)) != 0) { - SDL_UnlockMutex(stream->lock); - return SDL_SetError("Can't add partial sample frames"); - } - SDL_AudioTrack* track = NULL; if (callback) { - track = SDL_CreateAudioTrack(stream->queue, &stream->src_spec, stream->src_chmap, (Uint8 *)buf, len, len, callback, userdata); - + track = SDL_CreateAudioTrack(stream->queue, spec, chmap, (Uint8 *)buf, len, len, callback, userdata); if (!track) { - SDL_UnlockMutex(stream->lock); return false; } } const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0; - bool result = true; + bool retval = true; if (track) { SDL_AddTrackToAudioQueue(stream->queue, track); } else { - result = SDL_WriteToAudioQueue(stream->queue, &stream->src_spec, stream->src_chmap, (const Uint8 *)buf, len); + retval = SDL_WriteToAudioQueue(stream->queue, spec, chmap, (const Uint8 *)buf, len); } - if (result) { + if (retval) { if (stream->put_callback) { const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available; stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail); } } + return retval; +} + +static bool PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void* userdata) +{ +#if DEBUG_AUDIOSTREAM + SDL_Log("AUDIOSTREAM: wants to put %d bytes", len); +#endif + + SDL_LockMutex(stream->lock); + + if (!CheckAudioStreamIsFullySetup(stream)) { + SDL_UnlockMutex(stream->lock); + return false; + } + + if ((len % SDL_AUDIO_FRAMESIZE(stream->src_spec)) != 0) { + SDL_UnlockMutex(stream->lock); + return SDL_SetError("Can't add partial sample frames"); + } + + const bool retval = PutAudioStreamBufferInternal(stream, &stream->src_spec, stream->src_chmap, buf, len, callback, userdata); + SDL_UnlockMutex(stream->lock); - return result; + return retval; } static void SDLCALL FreeAllocatedAudioBuffer(void *userdata, const void *buf, int len) @@ -857,9 +863,8 @@ bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) } SDL_memcpy(data, buf, len); - buf = data; - bool ret = PutAudioStreamBuffer(stream, buf, len, FreeAllocatedAudioBuffer, NULL); + bool ret = PutAudioStreamBuffer(stream, data, len, FreeAllocatedAudioBuffer, NULL); if (!ret) { SDL_free(data); } @@ -869,6 +874,144 @@ bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) return PutAudioStreamBuffer(stream, buf, len, NULL, NULL); } + +#define GENERIC_INTERLEAVE_FUNCTION(bits) \ + static void InterleaveAudioChannelsGeneric##bits(void *output, const void * const *channel_buffers, const int channels, int num_samples) { \ + Uint##bits *dst = (Uint##bits *) output; \ + const Uint##bits * const *srcs = (const Uint##bits * const *) channel_buffers; \ + for (int frame = 0; frame < num_samples; frame++) { \ + for (int channel = 0; channel < channels; channel++) { \ + *(dst++) = srcs[channel][frame]; \ + } \ + } \ + } + +GENERIC_INTERLEAVE_FUNCTION(8) +GENERIC_INTERLEAVE_FUNCTION(16) +GENERIC_INTERLEAVE_FUNCTION(32) +//GENERIC_INTERLEAVE_FUNCTION(64) (we don't have any 64-bit audio data types at the moment.) +#undef GENERIC_INTERLEAVE_FUNCTION + +#define GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(bits) \ + static void InterleaveAudioChannelsWithNullsGeneric##bits(void *output, const void * const *channel_buffers, const int channels, int num_samples, const int isilence) { \ + const Uint##bits silence = (Uint##bits) isilence; \ + Uint##bits *dst = (Uint##bits *) output; \ + const Uint##bits * const *srcs = (const Uint##bits * const *) channel_buffers; \ + for (int frame = 0; frame < num_samples; frame++) { \ + for (int channel = 0; channel < channels; channel++) { \ + *(dst++) = srcs[channel] ? srcs[channel][frame] : silence; \ + } \ + } \ + } + +GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(8) +GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(16) +GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(32) +//GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(64) (we don't have any 64-bit audio data types at the moment.) +#undef GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION + +static void InterleaveAudioChannels(void *output, const void * const *channel_buffers, int num_samples, const SDL_AudioSpec *spec) +{ + const int channels = spec->channels; + + bool have_null_channel = false; + for (int i = 0; i < channels; i++) { + if (channel_buffers[i] == NULL) { + have_null_channel = true; + break; + } + } + + if (have_null_channel) { + const int silence = SDL_GetSilenceValueForFormat(spec->format); + switch (SDL_AUDIO_BITSIZE(spec->format)) { + case 8: InterleaveAudioChannelsWithNullsGeneric8(output, channel_buffers, channels, num_samples, silence); break; + case 16: InterleaveAudioChannelsWithNullsGeneric16(output, channel_buffers, channels, num_samples, silence); break; + case 32: InterleaveAudioChannelsWithNullsGeneric32(output, channel_buffers, channels, num_samples, silence); break; + //case 64: InterleaveAudioChannelsGeneric64(output, channel_buffers, channels, num_samples); break; (we don't have any 64-bit audio data types at the moment.) + default: SDL_assert(!"Missing needed generic audio interleave function!"); SDL_memset(output, 0, SDL_AUDIO_FRAMESIZE(*spec) * num_samples); break; + } + } else { + // !!! FIXME: it would be possible to do this really well in SIMD for stereo data, using unpack (intel) or zip (arm) instructions, etc. + switch (SDL_AUDIO_BITSIZE(spec->format)) { + case 8: InterleaveAudioChannelsGeneric8(output, channel_buffers, channels, num_samples); break; + case 16: InterleaveAudioChannelsGeneric16(output, channel_buffers, channels, num_samples); break; + case 32: InterleaveAudioChannelsGeneric32(output, channel_buffers, channels, num_samples); break; + //case 64: InterleaveAudioChannelsGeneric64(output, channel_buffers, channels, num_samples); break; (we don't have any 64-bit audio data types at the moment.) + default: SDL_assert(!"Missing needed generic audio interleave function!"); SDL_memset(output, 0, SDL_AUDIO_FRAMESIZE(*spec) * num_samples); break; + } + } +} + +bool SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *channel_buffers, int num_samples) +{ + if (!stream) { + return SDL_InvalidParamError("stream"); + } else if (!channel_buffers) { + return SDL_InvalidParamError("channel_buffers"); + } else if (num_samples < 0) { + return SDL_InvalidParamError("num_samples"); + } else if (num_samples == 0) { + return true; // nothing to do. + } + + // we do the interleaving up front without the lock held, so the audio device doesn't starve while we work. + // but we _do_ need to know the current input spec. + SDL_AudioSpec spec; + int chmap_copy[SDL_MAX_CHANNELMAP_CHANNELS]; + int *chmap = NULL; + SDL_LockMutex(stream->lock); + if (!CheckAudioStreamIsFullySetup(stream)) { + SDL_UnlockMutex(stream->lock); + return false; + } + SDL_copyp(&spec, &stream->src_spec); + if (stream->src_chmap) { + chmap = chmap_copy; + SDL_memcpy(chmap, stream->src_chmap, sizeof (*chmap) * spec.channels); + } + SDL_UnlockMutex(stream->lock); + + if (spec.channels == 1) { // nothing to interleave, just use the usual function. + return SDL_PutAudioStreamData(stream, channel_buffers[0], SDL_AUDIO_FRAMESIZE(spec) * num_samples); + } + + bool retval = false; + + const int len = SDL_AUDIO_FRAMESIZE(spec) * num_samples; + #if DEBUG_AUDIOSTREAM + SDL_Log("AUDIOSTREAM: wants to put %d bytes of separated data", len); + #endif + + // Is the data small enough to just interleave it on the stack and put it through the normal interface? + #define INTERLEAVE_STACK_SIZE 1024 + Uint8 stackbuf[INTERLEAVE_STACK_SIZE]; + void *data = stackbuf; + SDL_ReleaseAudioBufferCallback callback = NULL; + + if (len > INTERLEAVE_STACK_SIZE) { + // too big for the stack? Just SDL_malloc a block and interleave into that. To avoid the extra copy, we'll just set it as a + // new track in the queue (the distinction is specifying a callback to PutAudioStreamBufferInternal, to release the buffer). + data = SDL_malloc(len); + if (!data) { + return false; + } + callback = FreeAllocatedAudioBuffer; + } + + InterleaveAudioChannels(data, channel_buffers, num_samples, &spec); + + // it's okay if the stream format changed on another thread while we didn't hold the lock; PutAudioStreamBufferInternal will notice + // and set up a new track with the right format, and the next SDL_PutAudioStreamData will notice that stream->src_spec doesn't + // match the new track and set up a new one again. It's a bad idea to change the format on another thread while putting here, + // but everything _will_ work out with the format that was (presumably) expected. + SDL_LockMutex(stream->lock); + retval = PutAudioStreamBufferInternal(stream, &spec, chmap, data, len, callback, NULL); + SDL_UnlockMutex(stream->lock); + + return retval; +} + bool SDL_FlushAudioStream(SDL_AudioStream *stream) { if (!stream) { diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 4a88bd2302410..6b8f1c9824f12 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -386,6 +386,7 @@ extern AudioBootStrap PS2AUDIO_bootstrap; extern AudioBootStrap PSPAUDIO_bootstrap; extern AudioBootStrap VITAAUD_bootstrap; extern AudioBootStrap N3DSAUDIO_bootstrap; +extern AudioBootStrap NGAGEAUDIO_bootstrap; extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap; extern AudioBootStrap QSAAUDIO_bootstrap; diff --git a/src/audio/ngage/SDL_ngageaudio.c b/src/audio/ngage/SDL_ngageaudio.c new file mode 100644 index 0000000000000..8bb6165571908 --- /dev/null +++ b/src/audio/ngage/SDL_ngageaudio.c @@ -0,0 +1,107 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifdef SDL_AUDIO_DRIVER_NGAGE + +#include "../SDL_sysaudio.h" +#include "SDL_ngageaudio.h" + +static SDL_AudioDevice *devptr = NULL; + +SDL_AudioDevice *NGAGE_GetAudioDeviceAddr() +{ + return devptr; +} + +static bool NGAGEAUDIO_OpenDevice(SDL_AudioDevice* device) +{ + SDL_PrivateAudioData *phdata = SDL_calloc(1, sizeof(SDL_PrivateAudioData)); + if (!phdata) + { + SDL_OutOfMemory(); + return false; + } + device->hidden = phdata; + + phdata->buffer = SDL_calloc(1, device->buffer_size); + if (!phdata->buffer) + { + SDL_OutOfMemory(); + SDL_free(phdata); + return false; + } + devptr = device; + + // Since the phone can change the sample rate during a phone call, + // we set the sample rate to 8KHz to be safe. Even though it + // might be possible to adjust the sample rate dynamically, it's + // not supported by the current implementation. + + device->spec.format = SDL_AUDIO_S16LE; + device->spec.channels = 1; + device->spec.freq = 8000; + + SDL_UpdatedAudioDeviceFormat(device); + + return true; +} + +static Uint8* NGAGEAUDIO_GetDeviceBuf(SDL_AudioDevice* device, int* buffer_size) +{ + SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden; + if (!phdata) + { + *buffer_size = 0; + return 0; + } + + *buffer_size = device->buffer_size; + return phdata->buffer; +} + +static void NGAGEAUDIO_CloseDevice(SDL_AudioDevice* device) +{ + if (device->hidden) + { + SDL_free(device->hidden->buffer); + SDL_free(device->hidden); + } + + return; +} + +static bool NGAGEAUDIO_Init(SDL_AudioDriverImpl *impl) +{ + impl->OpenDevice = NGAGEAUDIO_OpenDevice; + impl->GetDeviceBuf = NGAGEAUDIO_GetDeviceBuf; + impl->CloseDevice = NGAGEAUDIO_CloseDevice; + + impl->ProvidesOwnCallbackThread = true; + impl->OnlyHasDefaultPlaybackDevice = true; + + return true; +} + +AudioBootStrap NGAGEAUDIO_bootstrap = { "N-Gage", "N-Gage audio driver", NGAGEAUDIO_Init, false }; + +#endif // SDL_AUDIO_DRIVER_NGAGE diff --git a/src/audio/ngage/SDL_ngageaudio.cpp b/src/audio/ngage/SDL_ngageaudio.cpp new file mode 100644 index 0000000000000..f0a0385ff2b36 --- /dev/null +++ b/src/audio/ngage/SDL_ngageaudio.cpp @@ -0,0 +1,410 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "SDL_internal.h" +#include "SDL_ngageaudio.h" +#include "../SDL_sysaudio.h" + +#ifdef __cplusplus +} +#endif + +#ifdef SDL_AUDIO_DRIVER_NGAGE + +#include "SDL_ngageaudio.hpp" + +CAudio::CAudio() : CActive(EPriorityStandard), iBufDes(NULL, 0) {} + +CAudio* CAudio::NewL(TInt aLatency) +{ + CAudio* self = new (ELeave) CAudio(); + CleanupStack::PushL(self); + self->ConstructL(aLatency); + CleanupStack::Pop(self); + return self; +} + +void CAudio::ConstructL(TInt aLatency) +{ + CActiveScheduler::Add(this); + User::LeaveIfError(iTimer.CreateLocal()); + iTimerCreated = ETrue; + + iStream = CMdaAudioOutputStream::NewL(*this); + if (!iStream) + { + SDL_Log("Error: Failed to create audio stream"); + User::Leave(KErrNoMemory); + } + + iLatency = aLatency; + iLatencySamples = aLatency * 8; // 8kHz. + + // Determine minimum and maximum number of samples to write with one + // WriteL request. + iMinWrite = iLatencySamples / 8; + iMaxWrite = iLatencySamples / 2; + + // Set defaults. + iState = EStateNone; + iTimerCreated = EFalse; + iTimerActive = EFalse; +} + +CAudio::~CAudio() +{ + if (iStream) + { + iStream->Stop(); + + while (iState != EStateDone) + { + User::After(100000); // 100ms. + } + + delete iStream; + } +} + +void CAudio::Start() +{ + if (iStream) + { + // Set to 8kHz mono audio. + iStreamSettings.iChannels = TMdaAudioDataSettings::EChannelsMono; + iStreamSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate8000Hz; + iStream->Open(&iStreamSettings); + iState = EStateOpening; + } + else + { + SDL_Log("Error: Failed to open audio stream"); + } +} + +// Feeds more processed data to the audio stream. +void CAudio::Feed() +{ + // If a WriteL is already in progress, or we aren't even playing; + // do nothing! + if ((iState != EStateWriting) && (iState != EStatePlaying)) + { + return; + } + + // Figure out the number of samples that really have been played + // through the output. + TTimeIntervalMicroSeconds pos = iStream->Position(); + + TInt played = 8 * (pos.Int64() / TInt64(1000)).GetTInt(); // 8kHz. + + played += iBaseSamplesPlayed; + + // Determine the difference between the number of samples written to + // CMdaAudioOutputStream and the number of samples it has played. + // The difference is the amount of data in the buffers. + if ( played < 0 ) + { + played = 0; + } + + TInt buffered = iSamplesWritten - played; + if (buffered < 0) + { + buffered = 0; + } + + if (iState == EStateWriting) + { + return; + } + + // The trick for low latency: Do not let the buffers fill up beyond the + // latency desired! We write as many samples as the difference between + // the latency target (in samples) and the amount of data buffered. + TInt samplesToWrite = iLatencySamples - buffered; + + // Do not write very small blocks. This should improve efficiency, since + // writes to the streaming API are likely to be expensive. + if (samplesToWrite < iMinWrite) + { + // Not enough data to write, set up a timer to fire after a while. + // Try againwhen it expired. + if (iTimerActive) + { + return; + } + iTimerActive = ETrue; + SetActive(); + iTimer.After(iStatus, (1000 * iLatency) / 8); + return; + } + + // Do not write more than the set number of samples at once. + int numSamples = samplesToWrite; + if (numSamples > iMaxWrite) + { + numSamples = iMaxWrite; + } + + SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr(); + if (device) + { + SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden; + + iBufDes.Set(phdata->buffer, 2 * numSamples, 2 * numSamples); + iStream->WriteL(iBufDes); + iState = EStateWriting; + + // Keep track of the number of samples written (for latency calculations). + iSamplesWritten += numSamples; + } + else + { + // Output device not ready yet. Let's go for another round. + if (iTimerActive) + { + return; + } + iTimerActive = ETrue; + SetActive(); + iTimer.After(iStatus, (1000 * iLatency) / 8); + } +} + +void CAudio::RunL() +{ + iTimerActive = EFalse; + Feed(); +} + +void CAudio::DoCancel() +{ + iTimerActive = EFalse; + iTimer.Cancel(); +} + +void CAudio::StartThread() +{ + TInt heapMinSize = 8192; // 8 KB initial heap size. + TInt heapMaxSize = 1024 * 1024; // 1 MB maximum heap size. + + TInt err = iProcess.Create(_L("ProcessThread"), ProcessThreadCB, KDefaultStackSize * 2, heapMinSize, heapMaxSize, this); + if (err == KErrNone) + { + iProcess.SetPriority(EPriorityLess); + iProcess.Resume(); + } + else + { + SDL_Log("Error: Failed to create audio processing thread: %d", err); + } +} + +void CAudio::StopThread() +{ + if (iStreamStarted) + { + iProcess.Kill(KErrNone); + iProcess.Close(); + iStreamStarted = EFalse; + } +} + +TInt CAudio::ProcessThreadCB(TAny* aPtr) +{ + CAudio* self = static_cast(aPtr); + SDL_AudioDevice* device = NGAGE_GetAudioDeviceAddr(); + + while (self->iStreamStarted) + { + if (device) + { + SDL_PlaybackAudioThreadIterate(device); + } + else + { + device = NGAGE_GetAudioDeviceAddr(); + } + User::After(100000); // 100ms. + } + return KErrNone; +} + +void CAudio::MaoscOpenComplete(TInt aError) +{ + if (aError == KErrNone) + { + iStream->SetVolume(1); + iStreamStarted = ETrue; + StartThread(); + + } + else + { + SDL_Log("Error: Failed to open audio stream: %d", aError); + } +} + +void CAudio::MaoscBufferCopied(TInt aError, const TDesC8& /*aBuffer*/) +{ + if (aError == KErrNone) + { + iState = EStatePlaying; + Feed(); + } + else if (aError == KErrAbort) + { + // The stream has been stopped. + iState = EStateDone; + } + else + { + SDL_Log("Error: Failed to copy audio buffer: %d", aError); + } +} + +void CAudio::MaoscPlayComplete(TInt aError) +{ + // If we finish due to an underflow, we'll need to restart playback. + // Normally KErrUnderlow is raised at stream end, but in our case the API + // should never see the stream end -- we are continuously feeding it more + // data! Many underflow errors mean that the latency target is too low. + if (aError == KErrUnderflow) + { + // The number of samples played gets resetted to zero when we restart + // playback after underflow. + iBaseSamplesPlayed = iSamplesWritten; + + iStream->Stop(); + Cancel(); + + iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, TMdaAudioDataSettings::EChannelsMono); + + iState = EStatePlaying; + Feed(); + return; + + } + else if (aError != KErrNone) + { + // Handle error. + } + + // We shouldn't get here. + SDL_Log("%s: %d", __FUNCTION__, aError); +} + +static TBool gAudioRunning; + +TBool AudioIsReady() +{ + return gAudioRunning; +} + +TInt AudioThreadCB(TAny* aParams) +{ + CTrapCleanup* cleanup = CTrapCleanup::New(); + if (!cleanup) + { + return KErrNoMemory; + } + + CActiveScheduler* scheduler = new CActiveScheduler(); + if (!scheduler) + { + delete cleanup; + return KErrNoMemory; + } + + CActiveScheduler::Install(scheduler); + + TRAPD(err, + { + TInt latency = *(TInt*)aParams; + CAudio* audio = CAudio::NewL(latency); + CleanupStack::PushL(audio); + + gAudioRunning = ETrue; + audio->Start(); + TBool once = EFalse; + + while (gAudioRunning) + { + // Allow active scheduler to process any events. + TInt error; + CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle); + + if (!once) + { + SDL_AudioDevice* device = NGAGE_GetAudioDeviceAddr(); + if (device) + { + // Stream ready; start feeding audio data. + // After feeding it once, the callbacks will take over. + audio->iState = CAudio::EStatePlaying; + audio->Feed(); + once = ETrue; + } + } + + User::After(100000); // 100ms. + } + + CleanupStack::PopAndDestroy(audio); + }); + + delete scheduler; + delete cleanup; + return err; +} + +RThread audioThread; + +void InitAudio(TInt* aLatency) +{ + _LIT(KAudioThreadName, "AudioThread"); + + TInt err = audioThread.Create(KAudioThreadName, AudioThreadCB, KDefaultStackSize, 0, aLatency); + if (err != KErrNone) + { + User::Leave(err); + } + + audioThread.Resume(); +} + +void DeinitAudio() +{ + gAudioRunning = EFalse; + + TRequestStatus status; + audioThread.Logon(status); + User::WaitForRequest(status); + + audioThread.Close(); +} + +#endif // SDL_AUDIO_DRIVER_NGAGE diff --git a/src/audio/ngage/SDL_ngageaudio.h b/src/audio/ngage/SDL_ngageaudio.h new file mode 100644 index 0000000000000..647609280bd1d --- /dev/null +++ b/src/audio/ngage/SDL_ngageaudio.h @@ -0,0 +1,44 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_ngageaudio_h +#define SDL_ngageaudio_h + +typedef struct SDL_PrivateAudioData +{ + Uint8 *buffer; + +} SDL_PrivateAudioData; + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../SDL_sysaudio.h" + + SDL_AudioDevice* NGAGE_GetAudioDeviceAddr(); + +#ifdef __cplusplus +} +#endif + +#endif // SDL_ngageaudio_h diff --git a/src/audio/ngage/SDL_ngageaudio.hpp b/src/audio/ngage/SDL_ngageaudio.hpp new file mode 100644 index 0000000000000..11ebea074f0f7 --- /dev/null +++ b/src/audio/ngage/SDL_ngageaudio.hpp @@ -0,0 +1,98 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_ngageaudio_hpp +#define SDL_ngageaudio_hpp + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "SDL_ngageaudio.h" +#include "../SDL_sysaudio.h" + +#ifdef __cplusplus +} +#endif + +TBool AudioIsReady(); +void InitAudio(TInt* aLatency); +void DeinitAudio(); + +class CAudio : public CActive, public MMdaAudioOutputStreamCallback +{ +public: + static CAudio* NewL(TInt aLatency); + ~CAudio(); + + void ConstructL(TInt aLatency); + void Start(); + void Feed(); + + void RunL(); + void DoCancel(); + + static TInt ProcessThreadCB(TAny* /*aPtr*/); + + // From MMdaAudioOutputStreamCallback + void MaoscOpenComplete(TInt aError); + void MaoscBufferCopied(TInt aError, const TDesC8& aBuffer); + void MaoscPlayComplete(TInt aError); + + enum { + EStateNone = 0, + EStateOpening, + EStatePlaying, + EStateWriting, + EStateDone + } iState; + +private: + CAudio(); + void StartThread(); + void StopThread(); + + CMdaAudioOutputStream* iStream; + TMdaAudioDataSettings iStreamSettings; + TBool iStreamStarted; + + TPtr8 iBufDes; // Descriptor for the buffer. + TInt iLatency; // Latency target in ms + TInt iLatencySamples; // Latency target in samples. + TInt iMinWrite; // Min number of samples to write per turn. + TInt iMaxWrite; // Max number of samples to write per turn. + TInt iBaseSamplesPlayed; // amples played before last restart. + TInt iSamplesWritten; // Number of samples written so far. + + RTimer iTimer; + TBool iTimerCreated; + TBool iTimerActive; + + RThread iProcess; +}; + + +#endif // SDL_ngageaudio_hpp \ No newline at end of file diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index 5cb450c86f2e2..226a7f3293d56 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -49,6 +49,7 @@ static bool LoadDBUSSyms(void) SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusError *), bus_register); SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_add_match); SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_remove_match); + SDL_DBUS_SYM(const char *(*)(DBusConnection *), bus_get_unique_name); SDL_DBUS_SYM(DBusConnection *(*)(const char *, DBusError *), connection_open_private); SDL_DBUS_SYM(void (*)(DBusConnection *, dbus_bool_t), connection_set_exit_on_disconnect); SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *), connection_get_is_connected); diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h index b22a92af04379..097bc31eb35ab 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -47,6 +47,7 @@ typedef struct SDL_DBusContext dbus_bool_t (*bus_register)(DBusConnection *, DBusError *); void (*bus_add_match)(DBusConnection *, const char *, DBusError *); void (*bus_remove_match)(DBusConnection *, const char *, DBusError *); + const char *(*bus_get_unique_name)(DBusConnection *); DBusConnection *(*connection_open_private)(const char *, DBusError *); void (*connection_set_exit_on_disconnect)(DBusConnection *, dbus_bool_t); dbus_bool_t (*connection_get_is_connected)(DBusConnection *); diff --git a/src/core/ngage/SDL_ngage.c b/src/core/ngage/SDL_ngage.c new file mode 100644 index 0000000000000..f9d31befc570a --- /dev/null +++ b/src/core/ngage/SDL_ngage.c @@ -0,0 +1,34 @@ +/* + 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" + +const void* nullptr = ((void*)0); + +void va_copy(char *dest, char *src) +{ + dest = src; +} + +//int vsnprintf(char *str, size_t size, const char *format, va_list ap) +//{ +// // Safely do nothing. +// return 0; +//} diff --git a/src/core/ngage/SDL_ngage.cpp b/src/core/ngage/SDL_ngage.cpp new file mode 100644 index 0000000000000..4e541b4b1ce0e --- /dev/null +++ b/src/core/ngage/SDL_ngage.cpp @@ -0,0 +1,77 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool NGAGE_IsClassicModel() +{ + int phone_id; + HAL::Get(HALData::EMachineUid, phone_id); + + return (0x101f8c19 == phone_id); +} + +void NGAGE_printf(const char *fmt, ...) +{ + char buffer[512] = { 0 }; + + va_list ap; + va_start(ap, fmt); + vsprintf(buffer, fmt, ap); + va_end(ap); + + TBuf<512> buf; + buf.Copy(TPtrC8((TText8*)buffer)); + + RDebug::Print(_L("%S"), &buf); +} + +void NGAGE_vnprintf(char *buf, size_t size, const char *fmt, va_list ap) +{ + char buffer[512] = { 0 }; + + vsprintf(buffer, fmt, ap); + + TBuf<512> tbuf; + tbuf.Copy(TPtrC8((TText8*)buffer)); + + RDebug::Print(_L("%S"), &tbuf); + + strncpy(buf, buffer, size - 1); + buf[size - 1] = '\0'; +} + +TInt NGAGE_GetFreeHeapMemory() +{ + TInt free = 0; + return User::Available(free); +} + +#ifdef __cplusplus +} +#endif diff --git a/src/core/ngage/SDL_ngage.h b/src/core/ngage/SDL_ngage.h new file mode 100644 index 0000000000000..56ad555616a0a --- /dev/null +++ b/src/core/ngage/SDL_ngage.h @@ -0,0 +1,36 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_ngage_h +#define SDL_ngage_h + +#ifdef __cplusplus +extern "C" { +#endif + + bool NGAGE_IsClassicModel(); + +#ifdef __cplusplus +} +#endif + +#endif /* SDL_ngage_h */ diff --git a/src/cpuinfo/SDL_cpuinfo.c b/src/cpuinfo/SDL_cpuinfo.c index 81d1e914ce8cf..f955ecdfca450 100644 --- a/src/cpuinfo/SDL_cpuinfo.c +++ b/src/cpuinfo/SDL_cpuinfo.c @@ -415,6 +415,12 @@ static int CPU_haveARMSIMD(void) return regs.r[0]; } +#elif defined(SDL_PLATFORM_NGAGE) +static int CPU_haveARMSIMD(void) +{ + // The RM920T is based on the ARMv4T architecture and doesn't have SIMD. + return 0; +} #else static int CPU_haveARMSIMD(void) { @@ -462,6 +468,8 @@ static int CPU_haveNEON(void) return 1; #elif defined(SDL_PLATFORM_3DS) return 0; +#elif defined(SDL_PLATFORM_NGAGE) + return 0; // The ARM920T is based on the ARMv4T architecture and doesn't have NEON. #elif defined(SDL_PLATFORM_APPLE) && defined(__ARM_ARCH) && (__ARM_ARCH >= 7) // (note that sysctlbyname("hw.optional.neon") doesn't work!) return 1; // all Apple ARMv7 chips and later have NEON. diff --git a/src/dynapi/SDL_dynapi.h b/src/dynapi/SDL_dynapi.h index 99ef9a9e93f0f..e975be08e1ad5 100644 --- a/src/dynapi/SDL_dynapi.h +++ b/src/dynapi/SDL_dynapi.h @@ -63,6 +63,8 @@ #define SDL_DYNAMIC_API 0 // vitasdk doesn't support dynamic linking #elif defined(SDL_PLATFORM_3DS) #define SDL_DYNAMIC_API 0 // devkitARM doesn't support dynamic linking +#elif defined(SDL_PLATFORM_NGAGE) +#define SDL_DYNAMIC_API 0 #elif defined(DYNAPI_NEEDS_DLOPEN) && !defined(HAVE_DLOPEN) #define SDL_DYNAMIC_API 0 // we need dlopen(), but don't have it.... #endif diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 44f9d0e062a36..93d6771cd8c80 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1250,6 +1250,7 @@ SDL3_0.0.0 { SDL_GetRenderTextureAddressMode; SDL_GetGPUDeviceProperties; SDL_CreateGPURenderer; + SDL_PutAudioStreamPlanarData; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index a12f3fbf24b4a..2808e409b6124 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1275,3 +1275,4 @@ #define SDL_GetRenderTextureAddressMode SDL_GetRenderTextureAddressMode_REAL #define SDL_GetGPUDeviceProperties SDL_GetGPUDeviceProperties_REAL #define SDL_CreateGPURenderer SDL_CreateGPURenderer_REAL +#define SDL_PutAudioStreamPlanarData SDL_PutAudioStreamPlanarData_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index d7988ac2b0944..4b642c9103319 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1283,3 +1283,4 @@ SDL_DYNAPI_PROC(bool,SDL_SetRenderTextureAddressMode,(SDL_Renderer *a,SDL_Textur SDL_DYNAPI_PROC(bool,SDL_GetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode *b,SDL_TextureAddressMode *c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGPUDeviceProperties,(SDL_GPUDevice *a),(a),return) SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateGPURenderer,(SDL_Window *a,SDL_GPUShaderFormat b,SDL_GPUDevice **c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamPlanarData,(SDL_AudioStream *a,const void * const*b,int c),(a,b,c),return) diff --git a/src/events/SDL_clipboardevents.c b/src/events/SDL_clipboardevents.c index d5cf8ad7b8150..529af9202b208 100644 --- a/src/events/SDL_clipboardevents.c +++ b/src/events/SDL_clipboardevents.c @@ -29,17 +29,7 @@ void SDL_SendClipboardUpdate(bool owner, char **mime_types, size_t num_mime_types) { if (!owner) { - /* Clear our internal clipboard contents when external clipboard is set. - * - * Wayland recursively sends a data offer to the client from which the clipboard data originated, - * and as the client can't determine the origin of the offer, the clipboard must not be cleared, - * or the original data may be destroyed. Cleanup will be done in the backend when an offer - * cancellation event arrives. - */ - if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") != 0) { - SDL_CancelClipboardData(0); - } - + SDL_CancelClipboardData(0); SDL_SaveClipboardMimeTypes((const char **)mime_types, num_mime_types); } diff --git a/src/filesystem/ngage/SDL_sysfilesystem.c b/src/filesystem/ngage/SDL_sysfilesystem.c new file mode 100644 index 0000000000000..eb2429dd937a4 --- /dev/null +++ b/src/filesystem/ngage/SDL_sysfilesystem.c @@ -0,0 +1,67 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +extern void NGAGE_GetAppPath(char* path); + +char *SDL_SYS_GetBasePath(void) +{ + char app_path[512]; + NGAGE_GetAppPath(app_path); + char *base_path = SDL_strdup(app_path); + return base_path; +} + +char *SDL_SYS_GetPrefPath(const char *org, const char *app) +{ + char *pref_path; + if (SDL_asprintf(&pref_path, "C:/System/Apps/%s/%s/", org, app) < 0) + return NULL; + else + return pref_path; +} + +char *SDL_SYS_GetUserFolder(SDL_Folder folder) +{ + const char *folder_path = NULL; + switch (folder) + { + case SDL_FOLDER_HOME: + folder_path = "C:/"; + break; + case SDL_FOLDER_PICTURES: + folder_path = "C:/Nokia/Pictures/"; + break; + case SDL_FOLDER_SAVEDGAMES: + folder_path = "C:/"; + break; + case SDL_FOLDER_SCREENSHOTS: + folder_path = "C:/Nokia/Pictures/"; + break; + case SDL_FOLDER_VIDEOS: + folder_path = "C:/Nokia/Videos/"; + break; + default: + folder_path = "C:/Nokia/Others/"; + break; + } + return SDL_strdup(folder_path); +} diff --git a/src/filesystem/ngage/SDL_sysfilesystem.cpp b/src/filesystem/ngage/SDL_sysfilesystem.cpp new file mode 100644 index 0000000000000..311c22f7a9bb6 --- /dev/null +++ b/src/filesystem/ngage/SDL_sysfilesystem.cpp @@ -0,0 +1,68 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#ifdef __cplusplus +extern "C" { +#endif + +#include "SDL_internal.h" + +#ifdef __cplusplus +} +#endif + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void NGAGE_GetAppPath(char* path) +{ + TBuf<512> aPath; + + TFileName fullExePath = RProcess().FileName(); + + TParsePtrC parser(fullExePath); + aPath.Copy(parser.DriveAndPath()); + + TBuf8<512> utf8Path; // Temporary buffer for UTF-8 data. + CnvUtfConverter::ConvertFromUnicodeToUtf8(utf8Path, aPath); + + // Copy UTF-8 data to the provided char* buffer. + strncpy(path, (const char*)utf8Path.Ptr(), utf8Path.Length()); + path[utf8Path.Length()] = '\0'; + + // Replace backslashes with forward slashes. + for (int i = 0; i < utf8Path.Length(); i++) + { + if (path[i] == '\\') + { + path[i] = '/'; + } + } +} + +#ifdef __cplusplus +} +#endif diff --git a/src/gpu/d3d12/SDL_gpu_d3d12.c b/src/gpu/d3d12/SDL_gpu_d3d12.c index 1843acd0e70c6..b848790df0e20 100644 --- a/src/gpu/d3d12/SDL_gpu_d3d12.c +++ b/src/gpu/d3d12/SDL_gpu_d3d12.c @@ -2482,12 +2482,32 @@ static D3D12GraphicsRootSignature *D3D12_INTERNAL_CreateGraphicsRootSignature( return d3d12GraphicsRootSignature; } +static bool D3D12_INTERNAL_IsValidShaderBytecode( + const Uint8 *code, + size_t codeSize) +{ + // Both DXIL and DXBC bytecode have a 4 byte header containing `DXBC`. + if (codeSize < 4 || code == NULL) { + return false; + } + return SDL_memcmp(code, "DXBC", 4) == 0; +} + static bool D3D12_INTERNAL_CreateShaderBytecode( + D3D12Renderer *renderer, const Uint8 *code, size_t codeSize, + SDL_GPUShaderFormat format, void **pBytecode, size_t *pBytecodeSize) { + if (!D3D12_INTERNAL_IsValidShaderBytecode(code, codeSize)) { + if (format == SDL_GPU_SHADERFORMAT_DXBC) { + SET_STRING_ERROR_AND_RETURN("The provided shader code is not valid DXBC!", false); + } + SET_STRING_ERROR_AND_RETURN("The provided shader code is not valid DXIL!", false); + } + if (pBytecode != NULL) { *pBytecode = SDL_malloc(codeSize); if (!*pBytecode) { @@ -2705,8 +2725,10 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( ID3D12PipelineState *pipelineState; if (!D3D12_INTERNAL_CreateShaderBytecode( + renderer, createinfo->code, createinfo->code_size, + createinfo->format, &bytecode, &bytecodeSize)) { return NULL; @@ -3113,13 +3135,16 @@ static SDL_GPUShader *D3D12_CreateShader( SDL_GPURenderer *driverData, const SDL_GPUShaderCreateInfo *createinfo) { + D3D12Renderer *renderer = (D3D12Renderer *)driverData; void *bytecode; size_t bytecodeSize; D3D12Shader *shader; if (!D3D12_INTERNAL_CreateShaderBytecode( + renderer, createinfo->code, createinfo->code_size, + createinfo->format, &bytecode, &bytecodeSize)) { return NULL; @@ -3188,7 +3213,7 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; useClearValue = true; clearValue.DepthStencil.Depth = SDL_GetFloatProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT, 0); - clearValue.DepthStencil.Stencil = (UINT8)SDL_GetNumberProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_UINT8, 0); + clearValue.DepthStencil.Stencil = (UINT8)SDL_GetNumberProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_NUMBER, 0); format = SDLToD3D12_DepthFormat[createinfo->format]; } diff --git a/src/gpu/metal/SDL_gpu_metal.m b/src/gpu/metal/SDL_gpu_metal.m index d83079f5f581c..c8b894f3ba85c 100644 --- a/src/gpu/metal/SDL_gpu_metal.m +++ b/src/gpu/metal/SDL_gpu_metal.m @@ -836,6 +836,17 @@ static void METAL_INTERNAL_TrackUniformBuffer( id function; } MetalLibraryFunction; +static bool METAL_INTERNAL_IsValidMetalLibrary( + const Uint8 *code, + size_t codeSize) +{ + // Metal libraries have a 4 byte header containing `MTLB`. + if (codeSize < 4 || code == NULL) { + return false; + } + return SDL_memcmp(code, "MTLB", 4) == 0; +} + // This function assumes that it's called from within an autorelease pool static MetalLibraryFunction METAL_INTERNAL_CompileShader( MetalRenderer *renderer, @@ -864,6 +875,11 @@ static MetalLibraryFunction METAL_INTERNAL_CompileShader( options:nil error:&error]; } else if (format == SDL_GPU_SHADERFORMAT_METALLIB) { + if (!METAL_INTERNAL_IsValidMetalLibrary(code, codeSize)) { + SET_STRING_ERROR_AND_RETURN( + "The provided shader code is not a valid Metal library!", + libraryFunction); + } data = dispatch_data_create( code, codeSize, diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index ebfeb2dd7465a..5de1e62b8cfbe 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -6511,6 +6511,25 @@ static SDL_GPUGraphicsPipeline *VULKAN_CreateGraphicsPipeline( return (SDL_GPUGraphicsPipeline *)graphicsPipeline; } +static bool VULKAN_INTERNAL_IsValidShaderBytecode( + const Uint8 *code, + size_t codeSize) +{ + // SPIR-V bytecode has a 4 byte header containing 0x07230203. SPIR-V is + // defined as a stream of words and not a stream of bytes so both byte + // orders need to be considered. + // + // FIXME: It is uncertain if drivers are able to load both byte orders. If + // needed we may need to do an optional swizzle internally so apps can + // continue to treat shader code as an opaque blob. + if (codeSize < 4 || code == NULL) { + return false; + } + const Uint32 magic = 0x07230203; + const Uint32 magicInv = 0x03022307; + return SDL_memcmp(code, &magic, 4) == 0 || SDL_memcmp(code, &magicInv, 4) == 0; +} + static SDL_GPUComputePipeline *VULKAN_CreateComputePipeline( SDL_GPURenderer *driverData, const SDL_GPUComputePipelineCreateInfo *createinfo) @@ -6526,6 +6545,10 @@ static SDL_GPUComputePipeline *VULKAN_CreateComputePipeline( SET_STRING_ERROR_AND_RETURN("Incompatible shader format for Vulkan!", NULL); } + if (!VULKAN_INTERNAL_IsValidShaderBytecode(createinfo->code, createinfo->code_size)) { + SET_STRING_ERROR_AND_RETURN("The provided shader code is not valid SPIR-V!", NULL); + } + vulkanComputePipeline = SDL_malloc(sizeof(VulkanComputePipeline)); shaderModuleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; shaderModuleCreateInfo.pNext = NULL; @@ -6671,6 +6694,10 @@ static SDL_GPUShader *VULKAN_CreateShader( VkShaderModuleCreateInfo vkShaderModuleCreateInfo; VulkanRenderer *renderer = (VulkanRenderer *)driverData; + if (!VULKAN_INTERNAL_IsValidShaderBytecode(createinfo->code, createinfo->code_size)) { + SET_STRING_ERROR_AND_RETURN("The provided shader code is not valid SPIR-V!", NULL); + } + vulkanShader = SDL_malloc(sizeof(VulkanShader)); vkShaderModuleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; vkShaderModuleCreateInfo.pNext = NULL; diff --git a/src/locale/ngage/SDL_syslocale.cpp b/src/locale/ngage/SDL_syslocale.cpp new file mode 100644 index 0000000000000..b1ace6fd0d485 --- /dev/null +++ b/src/locale/ngage/SDL_syslocale.cpp @@ -0,0 +1,299 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" +#include "../SDL_syslocale.h" + +#include +#include +#include +#include + +bool SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) +{ + TLanguage language = User::Language(); + const char* locale; + + switch (language) { + case ELangFrench: + case ELangSwissFrench: + case ELangBelgianFrench: + case ELangInternationalFrench: + locale = "fr_FR"; + break; + case ELangGerman: + case ELangSwissGerman: + case ELangAustrian: + locale = "de_DE"; + break; + case ELangSpanish: + case ELangInternationalSpanish: + case ELangLatinAmericanSpanish: + locale = "es_ES"; + break; + case ELangItalian: + case ELangSwissItalian: + locale = "it_IT"; + break; + case ELangSwedish: + case ELangFinlandSwedish: + locale = "sv_SE"; + break; + case ELangDanish: + locale = "da_DK"; + break; + case ELangNorwegian: + case ELangNorwegianNynorsk: + locale = "no_NO"; + break; + case ELangFinnish: + locale = "fi_FI"; + break; + case ELangPortuguese: + case ELangBrazilianPortuguese: + locale = "pt_PT"; + break; + case ELangTurkish: + case ELangCyprusTurkish: + locale = "tr_TR"; + break; + case ELangIcelandic: + locale = "is_IS"; + break; + case ELangRussian: + locale = "ru_RU"; + break; + case ELangHungarian: + locale = "hu_HU"; + break; + case ELangDutch: + case ELangBelgianFlemish: + locale = "nl_NL"; + break; + case ELangAustralian: + case ELangNewZealand: + locale = "en_AU"; + break; + case ELangCzech: + locale = "cs_CZ"; + break; + case ELangSlovak: + locale = "sk_SK"; + break; + case ELangPolish: + locale = "pl_PL"; + break; + case ELangSlovenian: + locale = "sl_SI"; + break; + case ELangTaiwanChinese: + locale = "zh_TW"; + break; + case ELangHongKongChinese: + locale = "zh_HK"; + break; + case ELangPrcChinese: + locale = "zh_CN"; + break; + case ELangJapanese: + locale = "ja_JP"; + break; + case ELangThai: + locale = "th_TH"; + break; + case ELangAfrikaans: + locale = "af_ZA"; + break; + case ELangAlbanian: + locale = "sq_AL"; + break; + case ELangAmharic: + locale = "am_ET"; + break; + case ELangArabic: + locale = "ar_SA"; + break; + case ELangArmenian: + locale = "hy_AM"; + break; + case ELangAzerbaijani: + locale = "az_AZ"; + break; + case ELangBelarussian: + locale = "be_BY"; + break; + case ELangBengali: + locale = "bn_IN"; + break; + case ELangBulgarian: + locale = "bg_BG"; + break; + case ELangBurmese: + locale = "my_MM"; + break; + case ELangCatalan: + locale = "ca_ES"; + break; + case ELangCroatian: + locale = "hr_HR"; + break; + case ELangEstonian: + locale = "et_EE"; + break; + case ELangFarsi: + locale = "fa_IR"; + break; + case ELangCanadianFrench: + locale = "fr_CA"; + break; + case ELangScotsGaelic: + locale = "gd_GB"; + break; + case ELangGeorgian: + locale = "ka_GE"; + break; + case ELangGreek: + case ELangCyprusGreek: + locale = "el_GR"; + break; + case ELangGujarati: + locale = "gu_IN"; + break; + case ELangHebrew: + locale = "he_IL"; + break; + case ELangHindi: + locale = "hi_IN"; + break; + case ELangIndonesian: + locale = "id_ID"; + break; + case ELangIrish: + locale = "ga_IE"; + break; + case ELangKannada: + locale = "kn_IN"; + break; + case ELangKazakh: + locale = "kk_KZ"; + break; + case ELangKhmer: + locale = "km_KH"; + break; + case ELangKorean: + locale = "ko_KR"; + break; + case ELangLao: + locale = "lo_LA"; + break; + case ELangLatvian: + locale = "lv_LV"; + break; + case ELangLithuanian: + locale = "lt_LT"; + break; + case ELangMacedonian: + locale = "mk_MK"; + break; + case ELangMalay: + locale = "ms_MY"; + break; + case ELangMalayalam: + locale = "ml_IN"; + break; + case ELangMarathi: + locale = "mr_IN"; + break; + case ELangMoldavian: + locale = "ro_MD"; + break; + case ELangMongolian: + locale = "mn_MN"; + break; + case ELangPunjabi: + locale = "pa_IN"; + break; + case ELangRomanian: + locale = "ro_RO"; + break; + case ELangSerbian: + locale = "sr_RS"; + break; + case ELangSinhalese: + locale = "si_LK"; + break; + case ELangSomali: + locale = "so_SO"; + break; + case ELangSwahili: + locale = "sw_KE"; + break; + case ELangTajik: + locale = "tg_TJ"; + break; + case ELangTamil: + locale = "ta_IN"; + break; + case ELangTelugu: + locale = "te_IN"; + break; + case ELangTibetan: + locale = "bo_CN"; + break; + case ELangTigrinya: + locale = "ti_ET"; + break; + case ELangTurkmen: + locale = "tk_TM"; + break; + case ELangUkrainian: + locale = "uk_UA"; + break; + case ELangUrdu: + locale = "ur_PK"; + break; + case ELangUzbek: + locale = "uz_UZ"; + break; + case ELangVietnamese: + locale = "vi_VN"; + break; + case ELangWelsh: + locale = "cy_GB"; + break; + case ELangZulu: + locale = "zu_ZA"; + break; + case ELangEnglish: + locale = "en_GB"; + break; + case ELangAmerican: + case ELangCanadianEnglish: + case ELangInternationalEnglish: + case ELangSouthAfricanEnglish: + default: + locale = "en_US"; + break; + } + + SDL_strlcpy(buf, locale, buflen); + + return true; +} diff --git a/src/main/ngage/SDL_sysmain_callbacks.c b/src/main/ngage/SDL_sysmain_callbacks.c new file mode 100644 index 0000000000000..1e3fca5cdea09 --- /dev/null +++ b/src/main/ngage/SDL_sysmain_callbacks.c @@ -0,0 +1,31 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_PLATFORM_NGAGE + +int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +{ + // Intentionally does nothing; Callbacks are called using the RunL() method. + return 0; +} + +#endif // SDL_PLATFORM_NGAGE diff --git a/src/main/ngage/SDL_sysmain_main.cpp b/src/main/ngage/SDL_sysmain_main.cpp new file mode 100644 index 0000000000000..817f2ccea0331 --- /dev/null +++ b/src/main/ngage/SDL_sysmain_main.cpp @@ -0,0 +1,194 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#ifdef __cplusplus +extern "C" { +#endif + +#include "SDL_internal.h" + +#ifdef __cplusplus +} +#endif + +#include +#include +#include +#include + +#include "SDL_sysmain_main.hpp" +#include "../../audio/ngage/SDL_ngageaudio.hpp" +#include "../../render/ngage/SDL_render_ngage_c.hpp" + +CRenderer *gRenderer = 0; + +GLDEF_C TInt E32Main() +{ + // Get args and environment. + int argc = 1; + char* argv[] = { "game", NULL }; + char** envp = NULL; + + // Create lvalue variables for __crt0 arguments. + char** argv_lvalue = argv; + char** envp_lvalue = envp; + + CTrapCleanup* cleanup = CTrapCleanup::New(); + if (!cleanup) + { + return KErrNoMemory; + } + + TRAPD(err, + { + CActiveScheduler* scheduler = new (ELeave) CActiveScheduler(); + CleanupStack::PushL(scheduler); + CActiveScheduler::Install(scheduler); + + TInt posixErr = SpawnPosixServerThread(); + if (posixErr != KErrNone) + { + SDL_Log("Error: Failed to spawn POSIX server thread: %d", posixErr); + User::Leave(posixErr); + } + + __crt0(argc, argv_lvalue, envp_lvalue); + + // Increase heap size. + RHeap* newHeap = User::ChunkHeap(NULL, 7500000, 7500000, KMinHeapGrowBy); + if (!newHeap) + { + SDL_Log("Error: Failed to create new heap"); + User::Leave(KErrNoMemory); + } + CleanupStack::PushL(newHeap); + + RHeap* oldHeap = User::SwitchHeap(newHeap); + + TInt targetLatency = 225; + InitAudio(&targetLatency); + + // Wait until audio is ready. + while (!AudioIsReady()) + { + User::After(100000); // 100ms. + } + + // Create and start the rendering backend. + gRenderer = CRenderer::NewL(); + CleanupStack::PushL(gRenderer); + + // Create and start the SDL main runner. + CSDLmain* mainApp = CSDLmain::NewL(); + CleanupStack::PushL(mainApp); + mainApp->Start(); + + // Start the active scheduler to handle events. + CActiveScheduler::Start(); + + CleanupStack::PopAndDestroy(gRenderer); + CleanupStack::PopAndDestroy(mainApp); + + User::SwitchHeap(oldHeap); + + CleanupStack::PopAndDestroy(newHeap); + CleanupStack::PopAndDestroy(scheduler); + }); + + if (err != KErrNone) + { + SDL_Log("Error: %d", err); + } + + return err; +} + +CSDLmain* CSDLmain::NewL() +{ + CSDLmain* self = new (ELeave) CSDLmain(); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; +} + +CSDLmain::CSDLmain() : CActive(EPriorityLow) {} + +void CSDLmain::ConstructL() +{ + CActiveScheduler::Add(this); +} + +CSDLmain::~CSDLmain() +{ + Cancel(); +} + +void CSDLmain::Start() +{ + SetActive(); + TRequestStatus* status = &iStatus; + User::RequestComplete(status, KErrNone); +} + +void CSDLmain::DoCancel() {} + +static bool callbacks_initialized = false; + +void CSDLmain::RunL() +{ + if (callbacks_initialized) + { + SDL_Event event; + + iResult = SDL_AppIterate(NULL); + if (iResult != SDL_APP_CONTINUE) + { + DeinitAudio(); + SDL_AppQuit(NULL, iResult); + SDL_Quit(); + CActiveScheduler::Stop(); + return; + } + + SDL_PumpEvents(); + if (SDL_PollEvent(&event)) + { + iResult = SDL_AppEvent(NULL, &event); + if (iResult != SDL_APP_CONTINUE) + { + DeinitAudio(); + SDL_AppQuit(NULL, iResult); + SDL_Quit(); + CActiveScheduler::Stop(); + return; + } + } + + Start(); + } + else + { + SDL_SetMainReady(); + SDL_AppInit(NULL, 0, NULL); + callbacks_initialized = true; + Start(); + } +} diff --git a/src/main/ngage/SDL_sysmain_main.hpp b/src/main/ngage/SDL_sysmain_main.hpp new file mode 100644 index 0000000000000..e78df799665b6 --- /dev/null +++ b/src/main/ngage/SDL_sysmain_main.hpp @@ -0,0 +1,41 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include + +class CSDLmain : public CActive +{ +public: + static CSDLmain* NewL(); + ~CSDLmain(); + + void Start(); + +protected: + void DoCancel() ; + void RunL(); + +private: + CSDLmain(); + void ConstructL(); + SDL_AppResult iResult; +}; diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 3cb5c8d2b843e..c9b5f1399214e 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -120,6 +120,9 @@ static const SDL_RenderDriver *render_drivers[] = { #ifdef SDL_VIDEO_RENDER_METAL &METAL_RenderDriver, #endif +#ifdef SDL_VIDEO_RENDER_NGAGE + &NGAGE_RenderDriver, +#endif #ifdef SDL_VIDEO_RENDER_OGL &GL_RenderDriver, #endif @@ -3253,6 +3256,7 @@ bool SDL_SetRenderDrawColorFloat(SDL_Renderer *renderer, float r, float g, float renderer->color.g = g; renderer->color.b = b; renderer->color.a = a; + return true; } diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h index 5109b93451648..d42f536294559 100644 --- a/src/render/SDL_sysrender.h +++ b/src/render/SDL_sysrender.h @@ -357,6 +357,7 @@ extern SDL_RenderDriver D3D12_RenderDriver; extern SDL_RenderDriver GL_RenderDriver; extern SDL_RenderDriver GLES2_RenderDriver; extern SDL_RenderDriver METAL_RenderDriver; +extern SDL_RenderDriver NGAGE_RenderDriver; extern SDL_RenderDriver VULKAN_RenderDriver; extern SDL_RenderDriver PS2_RenderDriver; extern SDL_RenderDriver PSP_RenderDriver; diff --git a/src/render/ngage/SDL_render_ngage.c b/src/render/ngage/SDL_render_ngage.c new file mode 100644 index 0000000000000..6416605f33a7a --- /dev/null +++ b/src/render/ngage/SDL_render_ngage.c @@ -0,0 +1,581 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_RENDER_NGAGE + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#ifndef Int2Fix +#define Int2Fix(i) ((i)<<16) +#endif + +#ifndef Fix2Int +#define Fix2Int(i) ((((unsigned int)(i)>0xFFFF0000)?0:((i)>>16))) +#endif + +#ifndef Fix2Real +#define Fix2Real(i) ((i)/65536.0) +#endif + +#ifndef Real2Fix +#define Real2Fix(i) ((int)((i)*65536.0)) +#endif + +#include "SDL_render_ngage_c.h" +#include "../SDL_sysrender.h" + +static void NGAGE_WindowEvent(SDL_Renderer* renderer, const SDL_WindowEvent* event); +static bool NGAGE_GetOutputSize(SDL_Renderer* renderer, int* w, int* h); +static bool NGAGE_SupportsBlendMode(SDL_Renderer* renderer, SDL_BlendMode blendMode); +static bool NGAGE_CreateTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_PropertiesID create_props); +static bool NGAGE_QueueSetViewport(SDL_Renderer* renderer, SDL_RenderCommand* cmd); +static bool NGAGE_QueueSetDrawColor(SDL_Renderer* renderer, SDL_RenderCommand* cmd); +static bool NGAGE_QueueDrawVertices(SDL_Renderer* renderer, SDL_RenderCommand* cmd, const SDL_FPoint* points, int count); +static bool NGAGE_QueueFillRects(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FRect *rects, int count); +static bool NGAGE_QueueCopy(SDL_Renderer* renderer, SDL_RenderCommand* cmd, SDL_Texture* texture, const SDL_FRect* srcrect, const SDL_FRect* dstrect); +static bool NGAGE_QueueCopyEx(SDL_Renderer* renderer, SDL_RenderCommand* cmd, SDL_Texture* texture, const SDL_FRect* srcquad, const SDL_FRect* dstrect, const double angle, const SDL_FPoint* center, const SDL_FlipMode flip, float scale_x, float scale_y); +static bool NGAGE_QueueGeometry(SDL_Renderer* renderer, SDL_RenderCommand* cmd, SDL_Texture* texture, const float* xy, int xy_stride, const SDL_FColor* color, int color_stride, const float* uv, int uv_stride, int num_vertices, const void* indices, int num_indices, int size_indices, float scale_x, float scale_y); + +static void NGAGE_InvalidateCachedState(SDL_Renderer* renderer); +static bool NGAGE_RunCommandQueue(SDL_Renderer* renderer, SDL_RenderCommand* cmd, void* vertices, size_t vertsize); +static bool NGAGE_UpdateTexture(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Rect* rect, const void* pixels, int pitch); + +static bool NGAGE_LockTexture(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Rect* rect, void** pixels, int* pitch); +static void NGAGE_UnlockTexture(SDL_Renderer* renderer, SDL_Texture* texture); +static void NGAGE_SetTextureScaleMode(SDL_Renderer* renderer, SDL_Texture* texture, SDL_ScaleMode scaleMode); +static bool NGAGE_SetRenderTarget(SDL_Renderer* renderer, SDL_Texture* texture); +static SDL_Surface* NGAGE_RenderReadPixels(SDL_Renderer* renderer, const SDL_Rect* rect); +static bool NGAGE_RenderPresent(SDL_Renderer* renderer); +static void NGAGE_DestroyTexture(SDL_Renderer* renderer, SDL_Texture* texture); + +static void NGAGE_DestroyRenderer(SDL_Renderer* renderer); + +static bool NGAGE_SetVSync(SDL_Renderer* renderer, int vsync); + +static bool NGAGE_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_PropertiesID create_props) +{ + SDL_SetupRendererColorspace(renderer, create_props); + + if (renderer->output_colorspace != SDL_COLORSPACE_RGB_DEFAULT) + { + return SDL_SetError("Unsupported output colorspace"); + } + + NGAGE_RendererData *phdata = SDL_calloc(1, sizeof(NGAGE_RendererData)); + if (!phdata) + { + SDL_OutOfMemory(); + return false; + } + + renderer->WindowEvent = NGAGE_WindowEvent; + renderer->GetOutputSize = NGAGE_GetOutputSize; + renderer->SupportsBlendMode = NGAGE_SupportsBlendMode; + renderer->CreateTexture = NGAGE_CreateTexture; + renderer->QueueSetViewport = NGAGE_QueueSetViewport; + renderer->QueueSetDrawColor = NGAGE_QueueSetDrawColor; + renderer->QueueDrawPoints = NGAGE_QueueDrawVertices; + renderer->QueueDrawLines = NGAGE_QueueDrawVertices; + renderer->QueueFillRects = NGAGE_QueueFillRects; + renderer->QueueCopy = NGAGE_QueueCopy; + renderer->QueueCopyEx = NGAGE_QueueCopyEx; + renderer->QueueGeometry = NGAGE_QueueGeometry; + + renderer->InvalidateCachedState = NGAGE_InvalidateCachedState; + renderer->RunCommandQueue = NGAGE_RunCommandQueue; + renderer->UpdateTexture = NGAGE_UpdateTexture; + renderer->LockTexture = NGAGE_LockTexture; + renderer->UnlockTexture = NGAGE_UnlockTexture; + renderer->SetTextureScaleMode = NGAGE_SetTextureScaleMode; + renderer->SetRenderTarget = NGAGE_SetRenderTarget; + renderer->RenderReadPixels = NGAGE_RenderReadPixels; + renderer->RenderPresent = NGAGE_RenderPresent; + renderer->DestroyTexture = NGAGE_DestroyTexture; + + renderer->DestroyRenderer = NGAGE_DestroyRenderer; + + renderer->SetVSync = NGAGE_SetVSync; + + renderer->name = NGAGE_RenderDriver.name; + renderer->window = window; + renderer->internal = phdata; + + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB4444); + SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 256); + SDL_SetHintWithPriority(SDL_HINT_RENDER_LINE_METHOD, "2", SDL_HINT_OVERRIDE); + + return true; +} + +SDL_RenderDriver NGAGE_RenderDriver = +{ + NGAGE_CreateRenderer, + "N-Gage" +}; + +static void NGAGE_WindowEvent(SDL_Renderer* renderer, const SDL_WindowEvent* event) +{ + return; +} + +static bool NGAGE_GetOutputSize(SDL_Renderer* renderer, int* w, int* h) +{ + return true; +} + +static bool NGAGE_SupportsBlendMode(SDL_Renderer* renderer, SDL_BlendMode blendMode) +{ + switch (blendMode) + { + case SDL_BLENDMODE_NONE: + case SDL_BLENDMODE_MOD: + return true; + default: + return false; + } +} + +static bool NGAGE_CreateTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_PropertiesID create_props) +{ + NGAGE_TextureData *data = (NGAGE_TextureData *)SDL_calloc(1, sizeof(*data)); + if (!data) + { + return false; + } + + if (!NGAGE_CreateTextureData(data, texture->w, texture->h)) + { + SDL_free(data); + return false; + } + + SDL_Surface* surface = SDL_CreateSurface(texture->w, texture->h, texture->format); + if (!surface) + { + SDL_free(data); + return false; + } + + data->surface = surface; + texture->internal = data; + + return true; +} + +static bool NGAGE_QueueSetViewport(SDL_Renderer* renderer, SDL_RenderCommand* cmd) +{ + if (!cmd->data.viewport.rect.w && !cmd->data.viewport.rect.h) + { + SDL_Rect viewport = { 0, 0, NGAGE_SCREEN_WIDTH, NGAGE_SCREEN_HEIGHT }; + SDL_SetRenderViewport(renderer, &viewport); + } + + return true; +} + +static bool NGAGE_QueueSetDrawColor(SDL_Renderer* renderer, SDL_RenderCommand* cmd) +{ + return true; +} + +static bool NGAGE_QueueDrawVertices(SDL_Renderer* renderer, SDL_RenderCommand* cmd, const SDL_FPoint* points, int count) +{ + NGAGE_Vertex *verts = (NGAGE_Vertex*)SDL_AllocateRenderVertices(renderer, count * sizeof(NGAGE_Vertex), 0, &cmd->data.draw.first); + if (!verts) + { + return false; + } + + cmd->data.draw.count = count; + + for (int i = 0; i < count; i++, points++) + { + int fixed_x = Real2Fix(points->x); + int fixed_y = Real2Fix(points->y); + + verts[i].x = Fix2Int(fixed_x); + verts[i].y = Fix2Int(fixed_y); + + Uint32 color = NGAGE_ConvertColor(cmd->data.draw.color.r, cmd->data.draw.color.g, cmd->data.draw.color.b, cmd->data.draw.color.a, cmd->data.draw.color_scale); + + verts[i].color.a = (Uint8)(color >> 24); + verts[i].color.b = (Uint8)(color >> 16); + verts[i].color.g = (Uint8)(color >> 8); + verts[i].color.r = (Uint8)color; + } + + return true; +} + +static bool NGAGE_QueueFillRects(SDL_Renderer* renderer, SDL_RenderCommand* cmd, const SDL_FRect* rects, int count) +{ + NGAGE_Vertex *verts = (NGAGE_Vertex*)SDL_AllocateRenderVertices(renderer, count * 2 * sizeof(NGAGE_Vertex), 0, &cmd->data.draw.first); + if (!verts) + { + return false; + } + + cmd->data.draw.count = count; + + for (int i = 0; i < count; i++, rects++) + { + verts[i * 2].x = Real2Fix(rects->x); + verts[i * 2].y = Real2Fix(rects->y); + verts[i * 2 + 1].x = Real2Fix(rects->w); + verts[i * 2 + 1].y = Real2Fix(rects->h); + + verts[i * 2].x = Fix2Int(verts[i * 2].x); + verts[i * 2].y = Fix2Int(verts[i * 2].y); + verts[i * 2 + 1].x = Fix2Int(verts[i * 2 + 1].x); + verts[i * 2 + 1].y = Fix2Int(verts[i * 2 + 1].y); + + Uint32 color = NGAGE_ConvertColor(cmd->data.draw.color.r, cmd->data.draw.color.g, cmd->data.draw.color.b, cmd->data.draw.color.a, cmd->data.draw.color_scale); + + verts[i * 2].color.a = (Uint8)(color >> 24); + verts[i * 2].color.b = (Uint8)(color >> 16); + verts[i * 2].color.g = (Uint8)(color >> 8); + verts[i * 2].color.r = (Uint8)color; + } + + return true; +} + +static bool NGAGE_QueueCopy(SDL_Renderer* renderer, SDL_RenderCommand* cmd, SDL_Texture* texture, const SDL_FRect* srcrect, const SDL_FRect* dstrect) +{ + SDL_Rect *verts = (SDL_Rect *)SDL_AllocateRenderVertices(renderer, 2 * sizeof(SDL_Rect), 0, &cmd->data.draw.first); + + if (!verts) + { + return false; + } + + cmd->data.draw.count = 1; + + verts->x = (int)srcrect->x; + verts->y = (int)srcrect->y; + verts->w = (int)srcrect->w; + verts->h = (int)srcrect->h; + + verts++; + + verts->x = (int)dstrect->x; + verts->y = (int)dstrect->y; + verts->w = (int)dstrect->w; + verts->h = (int)dstrect->h; + + return true; +} + +static bool NGAGE_QueueCopyEx(SDL_Renderer* renderer, SDL_RenderCommand* cmd, SDL_Texture* texture, const SDL_FRect *srcquad, const SDL_FRect *dstrect, const double angle, const SDL_FPoint* center, const SDL_FlipMode flip, float scale_x, float scale_y) +{ + NGAGE_CopyExData* verts = (NGAGE_CopyExData*)SDL_AllocateRenderVertices(renderer, sizeof(NGAGE_CopyExData), 0, &cmd->data.draw.first); + + if (!verts) + { + return false; + } + + cmd->data.draw.count = 1; + + verts->srcrect.x = (int)srcquad->x; + verts->srcrect.y = (int)srcquad->y; + verts->srcrect.w = (int)srcquad->w; + verts->srcrect.h = (int)srcquad->h; + verts->dstrect.x = (int)dstrect->x; + verts->dstrect.y = (int)dstrect->y; + verts->dstrect.w = (int)dstrect->w; + verts->dstrect.h = (int)dstrect->h; + + verts->angle = Real2Fix(angle); + verts->center.x = Real2Fix(center->x); + verts->center.y = Real2Fix(center->y); + verts->scale_x = Real2Fix(scale_x); + verts->scale_y = Real2Fix(scale_y); + + verts->flip = flip; + + return true; +} + +static bool NGAGE_QueueGeometry(SDL_Renderer* renderer, SDL_RenderCommand* cmd, SDL_Texture* texture, const float* xy, int xy_stride, const SDL_FColor* color, int color_stride, const float* uv, int uv_stride, int num_vertices, const void* indices, int num_indices, int size_indices, float scale_x, float scale_y) +{ + return true; +} + +static void NGAGE_InvalidateCachedState(SDL_Renderer* renderer) +{ + return; +} + +static bool NGAGE_RunCommandQueue(SDL_Renderer* renderer, SDL_RenderCommand* cmd, void* vertices, size_t vertsize) +{ + NGAGE_RendererData *phdata = (NGAGE_RendererData *)renderer->internal; + if (!phdata) + { + return false; + } + phdata->viewport = 0; + + while (cmd) + { + switch (cmd->command) + { + case SDL_RENDERCMD_NO_OP: + { + break; + } + + case SDL_RENDERCMD_SETVIEWPORT: + { + phdata->viewport = &cmd->data.viewport.rect; + break; + } + + case SDL_RENDERCMD_SETCLIPRECT: + { + const SDL_Rect* rect = &cmd->data.cliprect.rect; + + if (cmd->data.cliprect.enabled) + { + NGAGE_SetClipRect(rect); + } + + break; + } + + case SDL_RENDERCMD_SETDRAWCOLOR: + { + break; + } + + case SDL_RENDERCMD_CLEAR: + { + Uint32 color = NGAGE_ConvertColor(cmd->data.color.color.r, cmd->data.color.color.g, cmd->data.color.color.b, cmd->data.color.color.a, cmd->data.color.color_scale); + + NGAGE_Clear(color); + break; + } + + case SDL_RENDERCMD_DRAW_POINTS: + { + NGAGE_Vertex* verts = (NGAGE_Vertex*)(((Uint8 *)vertices) + cmd->data.draw.first); + const int count = cmd->data.draw.count; + + // Apply viewport. + if (phdata->viewport && (phdata->viewport->x || phdata->viewport->y)) + { + for (int i = 0; i < count; i++) + { + verts[i].x += phdata->viewport->x; + verts[i].y += phdata->viewport->y; + } + } + + NGAGE_DrawPoints(verts, count); + break; + } + case SDL_RENDERCMD_DRAW_LINES: + { + NGAGE_Vertex* verts = (NGAGE_Vertex*)(((Uint8 *)vertices) + cmd->data.draw.first); + const int count = cmd->data.draw.count; + + // Apply viewport. + if (phdata->viewport && (phdata->viewport->x || phdata->viewport->y)) + { + for (int i = 0; i < count; i++) + { + verts[i].x += phdata->viewport->x; + verts[i].y += phdata->viewport->y; + } + } + + NGAGE_DrawLines(verts, count); + break; + } + + case SDL_RENDERCMD_FILL_RECTS: + { + NGAGE_Vertex* verts = (NGAGE_Vertex*)(((Uint8 *)vertices) + cmd->data.draw.first); + const int count = cmd->data.draw.count; + + // Apply viewport. + if (phdata->viewport && (phdata->viewport->x || phdata->viewport->y)) + { + for (int i = 0; i < count; i++) + { + verts[i].x += phdata->viewport->x; + verts[i].y += phdata->viewport->y; + } + } + + NGAGE_FillRects(verts, count); + break; + } + + case SDL_RENDERCMD_COPY: + { + SDL_Rect* verts = (SDL_Rect*)(((Uint8*)vertices) + cmd->data.draw.first); + SDL_Rect* srcrect = verts; + SDL_Rect* dstrect = verts + 1; + SDL_Texture* texture = cmd->data.draw.texture; + + // Apply viewport. + if (phdata->viewport && (phdata->viewport->x || phdata->viewport->y)) + { + dstrect->x += phdata->viewport->x; + dstrect->y += phdata->viewport->y; + } + + NGAGE_Copy(renderer, texture, srcrect, dstrect); + break; + } + + case SDL_RENDERCMD_COPY_EX: + { + NGAGE_CopyExData* copydata = (NGAGE_CopyExData*)(((Uint8*)vertices) + cmd->data.draw.first); + SDL_Texture* texture = cmd->data.draw.texture; + + // Apply viewport. + if (phdata->viewport && (phdata->viewport->x || phdata->viewport->y)) + { + copydata->dstrect.x += phdata->viewport->x; + copydata->dstrect.y += phdata->viewport->y; + } + + NGAGE_CopyEx(renderer, texture, copydata); + break; + } + + case SDL_RENDERCMD_GEOMETRY: + { + break; + } + } + cmd = cmd->next; + } + + return true; +} + +static bool NGAGE_UpdateTexture(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Rect* rect, const void* pixels, int pitch) +{ + NGAGE_TextureData *phdata = (NGAGE_TextureData *)texture->internal; + + SDL_Surface *surface = phdata->surface; + Uint8 *src, *dst; + int row; + size_t length; + + if (SDL_MUSTLOCK(surface)) + { + if (!SDL_LockSurface(surface)) + { + return false; + } + } + src = (Uint8 *)pixels; + dst = (Uint8 *)surface->pixels + + rect->y * surface->pitch + + rect->x * surface->fmt->bytes_per_pixel; + + length = (size_t)rect->w * surface->fmt->bytes_per_pixel; + for (row = 0; row < rect->h; ++row) + { + SDL_memcpy(dst, src, length); + src += pitch; + dst += surface->pitch; + } + if (SDL_MUSTLOCK(surface)) + { + SDL_UnlockSurface(surface); + } + + return true; +} + +static bool NGAGE_LockTexture(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Rect* rect, void** pixels, int* pitch) +{ + NGAGE_TextureData *phdata = (NGAGE_TextureData *)texture->internal; + SDL_Surface *surface = phdata->surface; + + *pixels = + (void *)((Uint8 *)surface->pixels + rect->y * surface->pitch + + rect->x * surface->fmt->bytes_per_pixel); + *pitch = surface->pitch; + return true; +} + +static void NGAGE_UnlockTexture(SDL_Renderer* renderer, SDL_Texture* texture) +{ +} + +static void NGAGE_SetTextureScaleMode(SDL_Renderer* renderer, SDL_Texture* texture, SDL_ScaleMode scaleMode) +{ +} + +static bool NGAGE_SetRenderTarget(SDL_Renderer* renderer, SDL_Texture* texture) +{ + return true; +} + +static SDL_Surface* NGAGE_RenderReadPixels(SDL_Renderer* renderer, const SDL_Rect* rect) +{ + return (SDL_Surface*)0; +} + +static bool NGAGE_RenderPresent(SDL_Renderer* renderer) +{ + NGAGE_Flip(); + + return true; +} + +static void NGAGE_DestroyTexture(SDL_Renderer* renderer, SDL_Texture* texture) +{ + NGAGE_TextureData *data = (NGAGE_TextureData *)texture->internal; + if (data) + { + SDL_DestroySurface(data->surface); + NGAGE_DestroyTextureData(data); + SDL_free(data); + texture->internal = 0; + } +} + +static void NGAGE_DestroyRenderer(SDL_Renderer* renderer) +{ + NGAGE_RendererData *phdata = (NGAGE_RendererData *)renderer->internal; + if (phdata) + { + SDL_free(phdata); + renderer->internal = 0; + } +} + +static bool NGAGE_SetVSync(SDL_Renderer* renderer, int vsync) +{ + return true; +} + +#endif // SDL_VIDEO_RENDER_NGAGE diff --git a/src/render/ngage/SDL_render_ngage.cpp b/src/render/ngage/SDL_render_ngage.cpp new file mode 100644 index 0000000000000..886d910d77489 --- /dev/null +++ b/src/render/ngage/SDL_render_ngage.cpp @@ -0,0 +1,804 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#ifdef __cplusplus +extern "C" { +#endif + +#include "SDL_internal.h" +#include "../SDL_sysrender.h" +#include "../../events/SDL_keyboard_c.h" +#include "SDL_render_ngage_c.h" + +#ifdef __cplusplus +} +#endif + +#ifdef SDL_VIDEO_RENDER_NGAGE + +#include "SDL_render_ngage_c.hpp" +#include "SDL_render_ops.hpp" + +const TUint32 WindowClientHandle = 0x571D0A; + +extern CRenderer* gRenderer; + +#ifdef __cplusplus +extern "C" { +#endif + + void NGAGE_Clear(const Uint32 color) + { + gRenderer->Clear(color); + } + + bool NGAGE_Copy(SDL_Renderer* renderer, SDL_Texture* texture, SDL_Rect* srcrect, SDL_Rect* dstrect) + { + return gRenderer->Copy(renderer, texture, srcrect, dstrect); + } + + bool NGAGE_CopyEx(SDL_Renderer* renderer, SDL_Texture* texture, NGAGE_CopyExData* copydata) + { + return gRenderer->CopyEx(renderer, texture, copydata); + } + + bool NGAGE_CreateTextureData(NGAGE_TextureData *data, const int width, const int height) + { + return gRenderer->CreateTextureData(data, width, height); + } + + void NGAGE_DestroyTextureData(NGAGE_TextureData *data) + { + if (data) + { + delete data->bitmap; + data->bitmap = NULL; + } + } + + void NGAGE_DrawLines(NGAGE_Vertex* verts, const int count) + { + gRenderer->DrawLines(verts, count); + } + + void NGAGE_DrawPoints(NGAGE_Vertex* verts, const int count) + { + gRenderer->DrawPoints(verts, count); + } + + void NGAGE_FillRects(NGAGE_Vertex* verts, const int count) + { + gRenderer->FillRects(verts, count); + } + + void NGAGE_Flip() + { + gRenderer->Flip(); + } + + void NGAGE_SetClipRect(const SDL_Rect* rect) + { + gRenderer->SetClipRect(rect->x, rect->y, rect->w, rect->h); + } + + void NGAGE_SetDrawColor(const Uint32 color) + { + if (gRenderer) + { + gRenderer->SetDrawColor(color); + } + } + + void NGAGE_PumpEventsInternal() + { + gRenderer->PumpEvents(); + } + + void NGAGE_SuspendScreenSaverInternal(bool suspend) + { + gRenderer->SuspendScreenSaver(suspend); + } + +#ifdef __cplusplus +} +#endif + +CRenderer* CRenderer::NewL() +{ + CRenderer* self = new (ELeave) CRenderer(); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; +} + +CRenderer::CRenderer() : iRenderer(0), iDirectScreen(0), iScreenGc(0), iWsSession(), iWsWindowGroup(), iWsWindowGroupID(0), iWsWindow(), iWsScreen(0), iWsEventStatus(), iWsEvent(), iShowFPS(EFalse), iFPS(0), iFont(0) {} + +CRenderer::~CRenderer() +{ + delete iRenderer; + iRenderer = 0; +} + +void CRenderer::ConstructL() +{ + TInt error = KErrNone; + + error = iWsSession.Connect(); + if (error != KErrNone) { + SDL_Log("Failed to connect to window server: %d", error); + User::Leave(error); + } + + iWsScreen = new(ELeave) CWsScreenDevice(iWsSession); + error = iWsScreen->Construct(); + if (error != KErrNone) { + SDL_Log("Failed to construct screen device: %d", error); + User::Leave(error); + } + + iWsWindowGroup = RWindowGroup(iWsSession); + error = iWsWindowGroup.Construct(WindowClientHandle); + if (error != KErrNone) { + SDL_Log("Failed to construct window group: %d", error); + User::Leave(error); + } + iWsWindowGroup.SetOrdinalPosition(0); + + RProcess thisProcess; + TParse exeName; + exeName.Set(thisProcess.FileName(), NULL, NULL); + TBuf<32> winGroupName; + winGroupName.Append(0); + winGroupName.Append(0); + winGroupName.Append(0); // UID + winGroupName.Append(0); + winGroupName.Append(exeName.Name()); // Caption + winGroupName.Append(0); + winGroupName.Append(0); // DOC name + iWsWindowGroup.SetName(winGroupName); + + iWsWindow = RWindow(iWsSession); + error = iWsWindow.Construct(iWsWindowGroup, WindowClientHandle - 1); + if (error != KErrNone) + { + SDL_Log("Failed to construct window: %d", error); + User::Leave(error); + } + iWsWindow.SetBackgroundColor(KRgbWhite); + iWsWindow.SetRequiredDisplayMode(EColor4K); + iWsWindow.Activate(); + iWsWindow.SetSize(iWsScreen->SizeInPixels()); + iWsWindow.SetVisible(ETrue); + + iWsWindowGroupID = iWsWindowGroup.Identifier(); + + TRAPD(errc, iRenderer = iRenderer->NewL()); + if (errc != KErrNone) + { + SDL_Log("Failed to create renderer: %d", errc); + return; + } + + iDirectScreen = CDirectScreenAccess::NewL( + iWsSession, + *(iWsScreen), + iWsWindow, *this); + + // Select font. + TFontSpec fontSpec(_L("LatinBold12"), 12); + TInt errd = iWsScreen->GetNearestFontInTwips((CFont*&)iFont, fontSpec); + if (errd != KErrNone) + { + SDL_Log("Failed to get font: %d", errd); + return; + } + + // Activate events. + iWsEventStatus = KRequestPending; + iWsSession.EventReady(&iWsEventStatus); + + DisableKeyBlocking(); + + iIsFocused = ETrue; + iShowFPS = EFalse; + iSuspendScreenSaver = EFalse; + + if (!iDirectScreen->IsActive()) + { + TRAPD(err, iDirectScreen->StartL()); + if (KErrNone != err) + { + return; + } + iDirectScreen->ScreenDevice()->SetAutoUpdate(ETrue); + } +} + +void CRenderer::Restart(RDirectScreenAccess::TTerminationReasons aReason) +{ + if (!iDirectScreen->IsActive()) + { + TRAPD(err, iDirectScreen->StartL()); + if (KErrNone != err) + { + return; + } + iDirectScreen->ScreenDevice()->SetAutoUpdate(ETrue); + } +} + +void CRenderer::AbortNow(RDirectScreenAccess::TTerminationReasons aReason) +{ + if (iDirectScreen->IsActive()) + { + iDirectScreen->Cancel(); + } +} + +void CRenderer::Clear(TUint32 iColor) +{ + if (iRenderer && iRenderer->Gc()) + { + iRenderer->Gc()->SetBrushColor(iColor); + iRenderer->Gc()->Clear(); + } +} + +#ifdef __cplusplus +extern "C" { +#endif + +Uint32 NGAGE_ConvertColor(float r, float g, float b, float a, float color_scale) +{ + TFixed ff = 255 << 16; // 255.f + + TFixed scalef = Real2Fix(color_scale); + TFixed rf = Real2Fix(r); + TFixed gf = Real2Fix(g); + TFixed bf = Real2Fix(b); + TFixed af = Real2Fix(a); + + rf = FixMul(rf, scalef); + gf = FixMul(gf, scalef); + bf = FixMul(bf, scalef); + + rf = SDL_clamp(rf, 0, ff); + gf = SDL_clamp(gf, 0, ff); + bf = SDL_clamp(bf, 0, ff); + af = SDL_clamp(af, 0, ff); + + rf = FixMul(rf, ff) >> 16; + gf = FixMul(gf, ff) >> 16; + bf = FixMul(bf, ff) >> 16; + af = FixMul(af, ff) >> 16; + + return (af << 24) | (bf << 16) | (gf << 8) | rf; +} + +#ifdef __cplusplus +} +#endif + +bool CRenderer::Copy(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect) +{ + if (!texture) + { + return false; + } + + NGAGE_TextureData *phdata = (NGAGE_TextureData *)texture->internal; + if (!phdata) + { + return false; + } + + SDL_FColor* c = &texture->color; + int w = phdata->surface->w; + int h = phdata->surface->h; + int pitch = phdata->surface->pitch; + void* source = phdata->surface->pixels; + void* dest; + + if (!source) + { + return false; + } + + void* pixel_buffer_a = SDL_calloc(1, pitch * h); + if (!pixel_buffer_a) + { + return false; + } + dest = pixel_buffer_a; + + void* pixel_buffer_b = SDL_calloc(1, pitch * h); + if (!pixel_buffer_b) + { + SDL_free(pixel_buffer_a); + return false; + } + + if (c->a != 1.f || c->r != 1.f || c->g != 1.f || c->b != 1.f) + { + ApplyColorMod(dest, source, pitch, w, h, texture->color); + + source = dest; + } + + float sx; + float sy; + SDL_GetRenderScale(renderer, &sx, &sy); + + if (sx != 1.f || sy != 1.f) + { + TFixed scale_x = Real2Fix(sx); + TFixed scale_y = Real2Fix(sy); + TFixed center_x = Int2Fix(w / 2); + TFixed center_y = Int2Fix(h / 2); + + dest == pixel_buffer_a ? dest = pixel_buffer_b : dest = pixel_buffer_a; + + ApplyScale(dest, source, pitch, w, h, center_x, center_y, scale_x, scale_y); + + source = dest; + } + + Mem::Copy(phdata->bitmap->DataAddress(), source, pitch * h); + SDL_free(pixel_buffer_a); + SDL_free(pixel_buffer_b); + + if (phdata->bitmap) + { + TRect aSource(TPoint(srcrect->x, srcrect->y), TSize(srcrect->w, srcrect->h)); + TPoint aDest(dstrect->x, dstrect->y); + iRenderer->Gc()->BitBlt(aDest, phdata->bitmap, aSource); + } + + return true; +} + +bool CRenderer::CopyEx(SDL_Renderer* renderer, SDL_Texture* texture, const NGAGE_CopyExData* copydata) +{ + NGAGE_TextureData *phdata = (NGAGE_TextureData *)texture->internal; + if (!phdata) + { + return false; + } + + SDL_FColor* c = &texture->color; + int w = phdata->surface->w; + int h = phdata->surface->h; + int pitch = phdata->surface->pitch; + void* source = phdata->surface->pixels; + void* dest; + + if (!source) + { + return false; + } + + void* pixel_buffer_a = SDL_calloc(1, pitch * h); + if (!pixel_buffer_a) + { + return false; + } + dest = pixel_buffer_a; + + void* pixel_buffer_b = SDL_calloc(1, pitch * h); + if (!pixel_buffer_a) + { + SDL_free(pixel_buffer_a); + return false; + } + + if (copydata->flip) + { + ApplyFlip(dest, source, pitch, w, h, copydata->flip); + source = dest; + } + + if (copydata->scale_x != 1.f || copydata->scale_y != 1.f) + { + dest == pixel_buffer_a ? dest = pixel_buffer_b : dest = pixel_buffer_a; + ApplyScale(dest, source, pitch, w, h, copydata->center.x, copydata->center.y, copydata->scale_x, copydata->scale_y); + source = dest; + } + + if (copydata->angle) + { + dest == pixel_buffer_a ? dest = pixel_buffer_b : dest = pixel_buffer_a; + ApplyRotation(dest, source, pitch, w, h, copydata->center.x, copydata->center.y, copydata->angle); + source = dest; + } + + if (c->a != 1.f || c->r != 1.f || c->g != 1.f || c->b != 1.f) + { + dest == pixel_buffer_a ? dest = pixel_buffer_b : dest = pixel_buffer_a; + ApplyColorMod(dest, source, pitch, w, h, texture->color); + source = dest; + } + + Mem::Copy(phdata->bitmap->DataAddress(), source, pitch * h); + SDL_free(pixel_buffer_a); + SDL_free(pixel_buffer_b); + + if (phdata->bitmap) + { + TRect aSource(TPoint(copydata->srcrect.x, copydata->srcrect.y), TSize(copydata->srcrect.w, copydata->srcrect.h)); + TPoint aDest(copydata->dstrect.x, copydata->dstrect.y); + iRenderer->Gc()->BitBlt(aDest, phdata->bitmap, aSource); + } + + return true; +} + +bool CRenderer::CreateTextureData(NGAGE_TextureData* aTextureData, const TInt aWidth, const TInt aHeight) +{ + if (!aTextureData) + { + return false; + } + + aTextureData->bitmap = new CFbsBitmap(); + if (!aTextureData->bitmap) + { + return false; + } + + TInt error = aTextureData->bitmap->Create(TSize(aWidth, aHeight), EColor4K); + if (error != KErrNone) + { + delete aTextureData->bitmap; + aTextureData->bitmap = NULL; + return false; + } + + return true; +} + +void CRenderer::DrawLines(NGAGE_Vertex* aVerts, const TInt aCount) +{ + if (iRenderer && iRenderer->Gc()) + { + TPoint* aPoints = new TPoint[aCount]; + + for (TInt i = 0; i < aCount; i++) + { + aPoints[i] = TPoint(aVerts[i].x, aVerts[i].y); + } + + TUint32 aColor = ( + ((TUint8)aVerts->color.a << 24) | + ((TUint8)aVerts->color.b << 16) | + ((TUint8)aVerts->color.g << 8) | + (TUint8)aVerts->color.r); + + iRenderer->Gc()->SetPenColor(aColor); + iRenderer->Gc()->DrawPolyLineNoEndPoint(aPoints, aCount); + + delete[] aPoints; + } +} + +void CRenderer::DrawPoints(NGAGE_Vertex* aVerts, const TInt aCount) +{ + if (iRenderer && iRenderer->Gc()) + { + for (TInt i = 0; i < aCount; i++, aVerts++) + { + TUint32 aColor = ( + ((TUint8)aVerts->color.a << 24) | + ((TUint8)aVerts->color.b << 16) | + ((TUint8)aVerts->color.g << 8) | + (TUint8)aVerts->color.r); + + iRenderer->Gc()->SetPenColor(aColor); + iRenderer->Gc()->Plot(TPoint(aVerts->x, aVerts->y)); + } + } +} + +void CRenderer::FillRects(NGAGE_Vertex* aVerts, const TInt aCount) +{ + if (iRenderer && iRenderer->Gc()) + { + for (TInt i = 0; i < aCount; i++, aVerts++) + { + TPoint pos(aVerts[i].x, aVerts[i].y); + TSize size( + aVerts[i + 1].x, + aVerts[i + 1].y); + TRect rect(pos, size); + + TUint32 aColor = ( + ((TUint8)aVerts->color.a << 24) | + ((TUint8)aVerts->color.b << 16) | + ((TUint8)aVerts->color.g << 8) | + (TUint8)aVerts->color.r); + + iRenderer->Gc()->SetPenColor(aColor); + iRenderer->Gc()->SetBrushColor(aColor); + iRenderer->Gc()->DrawRect(rect); + } + } +} + +void CRenderer::Flip() +{ + if (!iRenderer) + { + SDL_Log("iRenderer is NULL."); + return; + } + + if (!iIsFocused) + { + return; + } + + iRenderer->Gc()->UseFont(iFont); + + if (iShowFPS && iRenderer->Gc()) + { + UpdateFPS(); + + TBuf<64> info; + + iRenderer->Gc()->SetPenStyle(CGraphicsContext::ESolidPen); + iRenderer->Gc()->SetBrushStyle(CGraphicsContext::ENullBrush); + iRenderer->Gc()->SetPenColor(KRgbCyan); + + TRect aTextRect(TPoint(3, 203 - iFont->HeightInPixels()), TSize(45, iFont->HeightInPixels() + 2)); + iRenderer->Gc()->SetBrushStyle(CGraphicsContext::ESolidBrush); + iRenderer->Gc()->SetBrushColor(KRgbBlack); + iRenderer->Gc()->DrawRect(aTextRect); + + // Draw messages. + info.Format(_L("FPS: %d"), iFPS); + iRenderer->Gc()->DrawText(info, TPoint(5, 203)); + } + else + { + // This is a workaround that helps regulating the FPS. + iRenderer->Gc()->DrawText(_L(""), TPoint(0, 0)); + } + iRenderer->Gc()->DiscardFont(); + iRenderer->Flip(iDirectScreen); + + // Keep the backlight on. + if (iSuspendScreenSaver) + { + User::ResetInactivityTime(); + } + // Suspend the current thread for a short while. + // Give some time to other threads and active objects. + User::After(0); +} + +void CRenderer::SetDrawColor(TUint32 iColor) +{ + if (iRenderer && iRenderer->Gc()) + { + iRenderer->Gc()->SetPenColor(iColor); + iRenderer->Gc()->SetBrushColor(iColor); + iRenderer->Gc()->SetBrushStyle(CGraphicsContext::ESolidBrush); + + TRAPD(err, iRenderer->SetCurrentColor(iColor)); + if (err != KErrNone) + { + return; + } + } +} + +void CRenderer::SetClipRect(TInt aX, TInt aY, TInt aWidth, TInt aHeight) +{ + if (iRenderer && iRenderer->Gc()) + { + TRect viewportRect(aX, aY, aX + aWidth, aY + aHeight); + iRenderer->Gc()->SetClippingRect(viewportRect); + } +} + +void CRenderer::UpdateFPS() +{ + static TTime lastTime; + static TInt frameCount = 0; + TTime currentTime; + const TUint KOneSecond = 1000000; // 1s in ms. + + currentTime.HomeTime(); + ++frameCount; + + TTimeIntervalMicroSeconds timeDiff = currentTime.MicroSecondsFrom(lastTime); + + if (timeDiff.Int64() >= KOneSecond) + { + // Calculate FPS. + iFPS = frameCount; + + // Reset frame count and last time. + frameCount = 0; + lastTime = currentTime; + } +} + +void CRenderer::SuspendScreenSaver(TBool aSuspend) +{ + iSuspendScreenSaver = aSuspend; +} + +static SDL_Scancode ConvertScancode(int key) +{ + SDL_Keycode keycode; + + switch(key) + { + case EStdKeyBackspace: // Clear key + keycode = SDLK_BACKSPACE; + break; + case 0x31: // 1 + keycode = SDLK_1; + break; + case 0x32: // 2 + keycode = SDLK_2; + break; + case 0x33: // 3 + keycode = SDLK_3; + break; + case 0x34: // 4 + keycode = SDLK_4; + break; + case 0x35: // 5 + keycode = SDLK_5; + break; + case 0x36: // 6 + keycode = SDLK_6; + break; + case 0x37: // 7 + keycode = SDLK_7; + break; + case 0x38: // 8 + keycode = SDLK_8; + break; + case 0x39: // 9 + keycode = SDLK_9; + break; + case 0x30: // 0 + keycode = SDLK_0; + break; + case 0x2a: // Asterisk + keycode = SDLK_ASTERISK; + break; + case EStdKeyHash: // Hash + keycode = SDLK_HASH; + break; + case EStdKeyDevice0: // Left softkey + keycode = SDLK_SOFTLEFT; + break; + case EStdKeyDevice1: // Right softkey + keycode = SDLK_SOFTRIGHT; + break; + case EStdKeyApplication0: // Call softkey + keycode = SDLK_CALL; + break; + case EStdKeyApplication1: // End call softkey + keycode = SDLK_ENDCALL; + break; + case EStdKeyDevice3: // Middle softkey + keycode = SDLK_SELECT; + break; + case EStdKeyUpArrow: // Up arrow + keycode = SDLK_UP; + break; + case EStdKeyDownArrow: // Down arrow + keycode = SDLK_DOWN; + break; + case EStdKeyLeftArrow: // Left arrow + keycode = SDLK_LEFT; + break; + case EStdKeyRightArrow: // Right arrow + keycode = SDLK_RIGHT; + break; + default: + keycode = SDLK_UNKNOWN; + break; + } + + return SDL_GetScancodeFromKey(keycode, NULL); +} + +void CRenderer::HandleEvent(const TWsEvent& aWsEvent) +{ + Uint64 timestamp; + + switch (aWsEvent.Type()) + { + case EEventKeyDown: /* Key events */ + timestamp = SDL_GetPerformanceCounter(); + SDL_SendKeyboardKey(timestamp, 1, aWsEvent.Key()->iCode, ConvertScancode(aWsEvent.Key()->iScanCode), true); + + if (aWsEvent.Key()->iScanCode == EStdKeyHash) + { + if (iShowFPS) + { + iShowFPS = EFalse; + } + else + { + iShowFPS = ETrue; + } + } + + break; + case EEventKeyUp: /* Key events */ + timestamp = SDL_GetPerformanceCounter(); + SDL_SendKeyboardKey(timestamp, 1, aWsEvent.Key()->iCode, ConvertScancode(aWsEvent.Key()->iScanCode), false); + + case EEventFocusGained: + DisableKeyBlocking(); + if (!iDirectScreen->IsActive()) + { + TRAPD(err, iDirectScreen->StartL()); + if (KErrNone != err) + { + return; + } + iDirectScreen->ScreenDevice()->SetAutoUpdate(ETrue); + iIsFocused = ETrue; + } + Flip(); + break; + case EEventFocusLost: + { + if (iDirectScreen->IsActive()) + { + iDirectScreen->Cancel(); + } + + iIsFocused = EFalse; + break; + } + default: + break; + } +} + +void CRenderer::DisableKeyBlocking() +{ + TRawEvent aEvent; + + aEvent.Set((TRawEvent::TType) /*EDisableKeyBlock*/ 51); + iWsSession.SimulateRawEvent(aEvent); +} + +void CRenderer::PumpEvents() +{ + while (iWsEventStatus != KRequestPending) + { + iWsSession.GetEvent(iWsEvent); + HandleEvent(iWsEvent); + iWsEventStatus = KRequestPending; + iWsSession.EventReady(&iWsEventStatus); + } +} + +#endif // SDL_VIDEO_RENDER_NGAGE diff --git a/src/render/ngage/SDL_render_ngage_c.h b/src/render/ngage/SDL_render_ngage_c.h new file mode 100644 index 0000000000000..8093a06f53d9e --- /dev/null +++ b/src/render/ngage/SDL_render_ngage_c.h @@ -0,0 +1,105 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef ngage_video_render_ngage_c_h +#define ngage_video_render_ngage_c_h + +#define NGAGE_SCREEN_WIDTH 176 +#define NGAGE_SCREEN_HEIGHT 208 + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../SDL_sysrender.h" + + typedef struct NGAGE_RendererData + { + SDL_Rect* viewport; + + } NGAGE_RendererData; + + typedef struct NGAGE_Vertex + { + int x; + int y; + + struct + { + Uint8 a; + Uint8 r; + Uint8 g; + Uint8 b; + + } color; + + } NGAGE_Vertex; + + typedef struct CFbsBitmap CFbsBitmap; + + typedef struct NGAGE_TextureData + { + CFbsBitmap *bitmap; + SDL_Surface* surface; + + } NGAGE_TextureData; + + typedef struct NGAGE_CopyExData + { + SDL_Rect srcrect; + SDL_Rect dstrect; + + int angle; + + struct + { + int x; + int y; + + } center; + + SDL_FlipMode flip; + + int scale_x; + int scale_y; + + } NGAGE_CopyExData; + + void NGAGE_Clear(const Uint32 color); + Uint32 NGAGE_ConvertColor(float r, float g, float b, float a, float color_scale); + bool NGAGE_Copy(SDL_Renderer* renderer, SDL_Texture* texture, SDL_Rect* srcrect, SDL_Rect* dstrect); + bool NGAGE_CopyEx(SDL_Renderer *renderer, SDL_Texture *texture, NGAGE_CopyExData *copydata); + bool NGAGE_CreateTextureData(NGAGE_TextureData* data, const int width, const int height); + void NGAGE_DestroyTextureData(NGAGE_TextureData* data); + void NGAGE_DrawLines(NGAGE_Vertex* verts, const int count); + void NGAGE_DrawPoints(NGAGE_Vertex* verts, const int count); + void NGAGE_FillRects(NGAGE_Vertex* verts, const int count); + void NGAGE_Flip(); + void NGAGE_SetClipRect(const SDL_Rect* rect); + void NGAGE_SetDrawColor(const Uint32 color); + void NGAGE_PumpEventsInternal(); + void NGAGE_SuspendScreenSaverInternal(bool suspend); + +#ifdef __cplusplus +} +#endif + +#endif // ngage_video_render_ngage_c_h diff --git a/src/render/ngage/SDL_render_ngage_c.hpp b/src/render/ngage/SDL_render_ngage_c.hpp new file mode 100644 index 0000000000000..f0c2537e68551 --- /dev/null +++ b/src/render/ngage/SDL_render_ngage_c.hpp @@ -0,0 +1,91 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef ngage_video_render_ngage_c_hpp +#define ngage_video_render_ngage_c_hpp + +#include +#include +#include +#include "SDL_render_ngage_c.h" + +class CRenderer: public MDirectScreenAccess +{ +public: + static CRenderer* NewL(); + virtual ~CRenderer(); + + // Rendering functions. + void Clear(TUint32 iColor); + bool Copy(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect); + bool CopyEx(SDL_Renderer* renderer, SDL_Texture* texture, const NGAGE_CopyExData* copydata); + bool CreateTextureData(NGAGE_TextureData* aTextureData, const TInt aWidth, const TInt aHeight); + void DrawLines(NGAGE_Vertex* aVerts, const TInt aCount); + void DrawPoints(NGAGE_Vertex* aVerts, const TInt aCount); + void FillRects(NGAGE_Vertex* aVerts, const TInt aCount); + void Flip(); + void SetDrawColor(TUint32 iColor); + void SetClipRect(TInt aX, TInt aY, TInt aWidth, TInt aHeight); + void UpdateFPS(); + void SuspendScreenSaver(TBool aSuspend); + + // Event handling. + void DisableKeyBlocking(); + void HandleEvent(const TWsEvent& aWsEvent); + void PumpEvents(); + +private: + CRenderer(); + void ConstructL(void); + + // BackBuffer. + CNRenderer *iRenderer; + + // Direct screen access. + CDirectScreenAccess* iDirectScreen; + CFbsBitGc *iScreenGc; + TBool iIsFocused; + + // Window server session. + RWsSession iWsSession; + RWindowGroup iWsWindowGroup; + TInt iWsWindowGroupID; + RWindow iWsWindow; + CWsScreenDevice* iWsScreen; + + // Event handling. + TRequestStatus iWsEventStatus; + TWsEvent iWsEvent; + + // MDirectScreenAccess functions. + void Restart (RDirectScreenAccess::TTerminationReasons aReason); + void AbortNow (RDirectScreenAccess::TTerminationReasons aReason); + + // Frame per second. + TBool iShowFPS; + TUint iFPS; + const CFont* iFont; + + // Screen saver. + TBool iSuspendScreenSaver; +}; + +#endif // ngage_video_render_ngage_c_hpp diff --git a/src/render/ngage/SDL_render_ops.cpp b/src/render/ngage/SDL_render_ops.cpp new file mode 100644 index 0000000000000..4632bddcd0729 --- /dev/null +++ b/src/render/ngage/SDL_render_ops.cpp @@ -0,0 +1,152 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include <3dtypes.h> +#include "SDL_render_ops.hpp" + +void ApplyColorMod(void *dest, void *source, int pitch, int width, int height, SDL_FColor color) +{ + TUint16 *src_pixels = static_cast(source); + TUint16 *dst_pixels = static_cast(dest); + + TFixed rf = Real2Fix(color.r); + TFixed gf = Real2Fix(color.g); + TFixed bf = Real2Fix(color.b); + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + TUint16 pixel = src_pixels[y * pitch / 2 + x]; + TUint8 r = (pixel & 0xF800) >> 8; + TUint8 g = (pixel & 0x07E0) >> 3; + TUint8 b = (pixel & 0x001F) << 3; + r = FixMul(r, rf); + g = FixMul(g, gf); + b = FixMul(b, bf); + dst_pixels[y * pitch / 2 + x] = (r << 8) | (g << 3) | (b >> 3); + } + } +} + +void ApplyFlip(void* dest, void* source, int pitch, int width, int height, SDL_FlipMode flip) +{ + TUint16* src_pixels = static_cast(source); + TUint16* dst_pixels = static_cast(dest); + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + int src_x = x; + int src_y = y; + + if (flip & SDL_FLIP_HORIZONTAL) + { + src_x = width - 1 - x; + } + + if (flip & SDL_FLIP_VERTICAL) + { + src_y = height - 1 - y; + } + + dst_pixels[y * pitch / 2 + x] = src_pixels[src_y * pitch / 2 + src_x]; + } + } +} + +void ApplyRotation(void* dest, void* source, int pitch, int width, int height, TFixed center_x, TFixed center_y, TFixed angle) +{ + TUint16* src_pixels = static_cast(source); + TUint16* dst_pixels = static_cast(dest); + + TFixed cos_angle = 0; + TFixed sin_angle = 0; + + if (angle != 0) + { + FixSinCos(angle, sin_angle, cos_angle); + } + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + // Translate point to origin. + TFixed translated_x = Int2Fix(x) - center_x; + TFixed translated_y = Int2Fix(y) - center_y; + + // Rotate point (clockwise). + TFixed rotated_x = FixMul(translated_x, cos_angle) + FixMul(translated_y, sin_angle); + TFixed rotated_y = FixMul(translated_y, cos_angle) - FixMul(translated_x, sin_angle); + + // Translate point back. + int final_x = Fix2Int(rotated_x + center_x); + int final_y = Fix2Int(rotated_y + center_y); + + // Check bounds. + if (final_x >= 0 && final_x < width && final_y >= 0 && final_y < height) + { + dst_pixels[y * pitch / 2 + x] = src_pixels[final_y * pitch / 2 + final_x]; + } + else + { + dst_pixels[y * pitch / 2 + x] = 0; + } + } + } +} + +void ApplyScale(void* dest, void* source, int pitch, int width, int height, TFixed center_x, TFixed center_y, TFixed scale_x, TFixed scale_y) +{ + TUint16* src_pixels = static_cast(source); + TUint16* dst_pixels = static_cast(dest); + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + // Translate point to origin. + TFixed translated_x = Int2Fix(x) - center_x; + TFixed translated_y = Int2Fix(y) - center_y; + + // Scale point. + TFixed scaled_x = FixDiv(translated_x, scale_x); + TFixed scaled_y = FixDiv(translated_y, scale_y); + + // Translate point back. + int final_x = Fix2Int(scaled_x + center_x); + int final_y = Fix2Int(scaled_y + center_y); + + // Check bounds. + if (final_x >= 0 && final_x < width && final_y >= 0 && final_y < height) + { + dst_pixels[y * pitch / 2 + x] = src_pixels[final_y * pitch / 2 + final_x]; + } + else + { + dst_pixels[y * pitch / 2 + x] = 0; + } + } + } +} diff --git a/src/render/ngage/SDL_render_ops.hpp b/src/render/ngage/SDL_render_ops.hpp new file mode 100644 index 0000000000000..734fc244eb438 --- /dev/null +++ b/src/render/ngage/SDL_render_ops.hpp @@ -0,0 +1,32 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef ngage_video_render_ops_hpp +#define ngage_video_render_ops_hpp + +#include <3dtypes.h> + +void ApplyColorMod(void* dest, void* source, int pitch, int width, int height, SDL_FColor color); +void ApplyFlip(void* dest, void* source, int pitch, int width, int height, SDL_FlipMode flip); +void ApplyRotation(void* dest, void* source, int pitch, int width, int height, TFixed center_x, TFixed center_y, TFixed angle); +void ApplyScale(void* dest, void* source, int pitch, int width, int height, TFixed center_x, TFixed center_y, TFixed scale_x, TFixed scale_y); + +#endif // ngage_video_render_ops_hpp diff --git a/src/stdlib/SDL_string.c b/src/stdlib/SDL_string.c index 79679a1f44405..77b99b5f312c1 100644 --- a/src/stdlib/SDL_string.c +++ b/src/stdlib/SDL_string.c @@ -34,6 +34,8 @@ #if defined(__SIZEOF_WCHAR_T__) #define SDL_SIZEOF_WCHAR_T __SIZEOF_WCHAR_T__ +#elif defined(SDL_PLATFORM_NGAGE) +#define SDL_SIZEOF_WCHAR_T 2 #elif defined(SDL_PLATFORM_WINDOWS) #define SDL_SIZEOF_WCHAR_T 2 #else // assume everything else is UTF-32 (add more tests if compiler-assert fails below!) diff --git a/src/stdlib/SDL_vacopy.h b/src/stdlib/SDL_vacopy.h index fee560e4684cd..494409c125337 100644 --- a/src/stdlib/SDL_vacopy.h +++ b/src/stdlib/SDL_vacopy.h @@ -27,4 +27,7 @@ #elif defined(__GNUC__) && (__GNUC__ < 3) #define va_copy(dst, src) __va_copy(dst, src) + +#elif defined(__SYMBIAN32__) +extern void va_copy(char* dest, char* src); #endif diff --git a/src/time/ngage/SDL_systime.cpp b/src/time/ngage/SDL_systime.cpp new file mode 100644 index 0000000000000..5d8eaa4fe0847 --- /dev/null +++ b/src/time/ngage/SDL_systime.cpp @@ -0,0 +1,151 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void SDL_GetSystemTimeLocalePreferences(SDL_DateFormat *df, SDL_TimeFormat *tf) +{ + TLanguage language = User::Language(); + + switch (language) { + case ELangFrench: + case ELangSwissFrench: + case ELangBelgianFrench: + case ELangInternationalFrench: + case ELangGerman: + case ELangSwissGerman: + case ELangAustrian: + case ELangSpanish: + case ELangInternationalSpanish: + case ELangLatinAmericanSpanish: + case ELangItalian: + case ELangSwissItalian: + case ELangSwedish: + case ELangFinlandSwedish: + case ELangDanish: + case ELangNorwegian: + case ELangNorwegianNynorsk: + case ELangFinnish: + case ELangPortuguese: + case ELangBrazilianPortuguese: + case ELangTurkish: + case ELangCyprusTurkish: + case ELangIcelandic: + case ELangRussian: + case ELangHungarian: + case ELangDutch: + case ELangBelgianFlemish: + case ELangCzech: + case ELangSlovak: + case ELangPolish: + case ELangSlovenian: + case ELangTaiwanChinese: + case ELangHongKongChinese: + case ELangPrcChinese: + case ELangJapanese: + case ELangThai: + case ELangAfrikaans: + case ELangAlbanian: + case ELangAmharic: + case ELangArabic: + case ELangArmenian: + case ELangAzerbaijani: + case ELangBelarussian: + case ELangBengali: + case ELangBulgarian: + case ELangBurmese: + case ELangCatalan: + case ELangCroatian: + case ELangEstonian: + case ELangFarsi: + case ELangScotsGaelic: + case ELangGeorgian: + case ELangGreek: + case ELangCyprusGreek: + case ELangGujarati: + case ELangHebrew: + case ELangHindi: + case ELangIndonesian: + case ELangIrish: + case ELangKannada: + case ELangKazakh: + case ELangKhmer: + case ELangKorean: + case ELangLao: + case ELangLatvian: + case ELangLithuanian: + case ELangMacedonian: + case ELangMalay: + case ELangMalayalam: + case ELangMarathi: + case ELangMoldavian: + case ELangMongolian: + case ELangPunjabi: + case ELangRomanian: + case ELangSerbian: + case ELangSinhalese: + case ELangSomali: + case ELangSwahili: + case ELangTajik: + case ELangTamil: + case ELangTelugu: + case ELangTibetan: + case ELangTigrinya: + case ELangTurkmen: + case ELangUkrainian: + case ELangUrdu: + case ELangUzbek: + case ELangVietnamese: + case ELangWelsh: + case ELangZulu: + *df = SDL_DATE_FORMAT_DDMMYYYY; + *tf = SDL_TIME_FORMAT_24HR; + break; + case ELangAmerican: + case ELangCanadianEnglish: + case ELangInternationalEnglish: + case ELangSouthAfricanEnglish: + case ELangAustralian: + case ELangNewZealand: + case ELangCanadianFrench: + *df = SDL_DATE_FORMAT_MMDDYYYY; + *tf = SDL_TIME_FORMAT_12HR; + break; + case ELangEnglish: + case ELangOther: + default: + *df = SDL_DATE_FORMAT_DDMMYYYY; + *tf = SDL_TIME_FORMAT_24HR; + break; + } +} + +#ifdef __cplusplus +} +#endif diff --git a/src/timer/ngage/SDL_systimer.cpp b/src/timer/ngage/SDL_systimer.cpp new file mode 100644 index 0000000000000..07b257e8eee69 --- /dev/null +++ b/src/timer/ngage/SDL_systimer.cpp @@ -0,0 +1,47 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + Uint64 SDL_GetPerformanceCounter(void) + { + return (Uint64)User::TickCount(); + } + + Uint64 SDL_GetPerformanceFrequency(void) + { + return (Uint64)1000000u; + } + + void SDL_SYS_DelayNS(Uint64 ns) + { + User::After(SDL_NS_TO_US(ns)); + } + +#ifdef __cplusplus +} +#endif diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c index c5291ade8b730..f40a92d85e55d 100644 --- a/src/video/SDL_surface.c +++ b/src/video/SDL_surface.c @@ -1101,9 +1101,9 @@ bool SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surfac int dst_w, dst_h; // Make sure the surfaces aren't locked - if (!SDL_SurfaceValid(src)) { + if (!SDL_SurfaceValid(src) || !src->pixels) { return SDL_InvalidParamError("src"); - } else if (!SDL_SurfaceValid(dst)) { + } else if (!SDL_SurfaceValid(dst) || !dst->pixels) { return SDL_InvalidParamError("dst"); } else if ((src->flags & SDL_SURFACE_LOCKED) || (dst->flags & SDL_SURFACE_LOCKED)) { return SDL_SetError("Surfaces must not be locked during blit"); @@ -1142,6 +1142,13 @@ bool SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surfac return SDL_BlitSurface(src, srcrect, dst, dstrect); } + if (src_w == 0) { + src_w = 1; + } + if (src_h == 0) { + src_h = 1; + } + scaling_w = (double)dst_w / src_w; scaling_h = (double)dst_h / src_h; diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 5fe87a86d8b50..08867095406ca 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -527,6 +527,7 @@ extern VideoBootStrap PSP_bootstrap; extern VideoBootStrap VITA_bootstrap; extern VideoBootStrap RISCOS_bootstrap; extern VideoBootStrap N3DS_bootstrap; +extern VideoBootStrap NGAGE_bootstrap; extern VideoBootStrap RPI_bootstrap; extern VideoBootStrap KMSDRM_bootstrap; extern VideoBootStrap DUMMY_bootstrap; diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index db2637bce13c6..cc5f77ad092d6 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -119,6 +119,9 @@ static VideoBootStrap *bootstrap[] = { #ifdef SDL_VIDEO_DRIVER_N3DS &N3DS_bootstrap, #endif +#ifdef SDL_VIDEO_DRIVER_NGAGE + &NGAGE_bootstrap, +#endif #ifdef SDL_VIDEO_DRIVER_KMSDRM &KMSDRM_bootstrap, #endif diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m index 800db71fdbfae..b55e4f11ec681 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/src/video/cocoa/SDL_cocoawindow.m @@ -2532,8 +2532,8 @@ void Cocoa_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window) SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal; NSSize maxSize; - maxSize.width = window->max_w; - maxSize.height = window->max_h; + maxSize.width = window->max_w ? window->max_w : CGFLOAT_MAX; + maxSize.height = window->max_h ? window->max_h : CGFLOAT_MAX; [windata.nswindow setContentMaxSize:maxSize]; } diff --git a/src/video/ngage/SDL_ngagevideo.c b/src/video/ngage/SDL_ngagevideo.c new file mode 100644 index 0000000000000..a5a6ab1298b01 --- /dev/null +++ b/src/video/ngage/SDL_ngagevideo.c @@ -0,0 +1,183 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../SDL_sysvideo.h" + +#ifdef SDL_VIDEO_DRIVER_NGAGE + +#include "SDL_ngagevideo.h" + +#define NGAGE_VIDEO_DRIVER_NAME "N-Gage" + +static void NGAGE_DeleteDevice(SDL_VideoDevice *device); +static bool NGAGE_VideoInit(SDL_VideoDevice *device); +static void NGAGE_VideoQuit(SDL_VideoDevice *device); + +static bool NGAGE_GetDisplayBounds(SDL_VideoDevice* device, SDL_VideoDisplay* display, SDL_Rect* rect); +static bool NGAGE_GetDisplayModes(SDL_VideoDevice* device, SDL_VideoDisplay* display); + +static void NGAGE_PumpEvents(SDL_VideoDevice* device); + +static bool NGAGE_SuspendScreenSaver(SDL_VideoDevice* device); + +static SDL_VideoDevice* NGAGE_CreateDevice(void) +{ + SDL_VideoDevice *device; + SDL_VideoData *phdata; + + // Initialize all variables that we clean on shutdown. + device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); + if (!device) + { + SDL_OutOfMemory(); + return (SDL_VideoDevice*)0; + } + + // Initialize internal N-Gage specific data. + phdata = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); + if (!phdata) + { + SDL_OutOfMemory(); + SDL_free(device); + return (SDL_VideoDevice*)0; + } + + device->internal = phdata; + + device->name = "Nokia N-Gage"; + + device->VideoInit = NGAGE_VideoInit; + device->VideoQuit = NGAGE_VideoQuit; + + device->GetDisplayBounds = NGAGE_GetDisplayBounds; + device->GetDisplayModes = NGAGE_GetDisplayModes; + + device->PumpEvents = NGAGE_PumpEvents; + + device->SuspendScreenSaver = NGAGE_SuspendScreenSaver; + + device->free = NGAGE_DeleteDevice; + + device->device_caps = VIDEO_DEVICE_CAPS_FULLSCREEN_ONLY; + + return device; +} + +VideoBootStrap NGAGE_bootstrap = +{ + NGAGE_VIDEO_DRIVER_NAME, + "N-Gage Video Driver", + NGAGE_CreateDevice, + 0 +}; + +static void NGAGE_DeleteDevice(SDL_VideoDevice *device) +{ + SDL_free(device->internal); + SDL_free(device); +} + +static bool NGAGE_VideoInit(SDL_VideoDevice *device) +{ + SDL_VideoData *phdata = (SDL_VideoData*)device->internal; + + if (!phdata) + { + return false; + } + + SDL_zero(phdata->mode); + SDL_zero(phdata->display); + + phdata->mode.w = 176; + phdata->mode.h = 208; + phdata->mode.refresh_rate = 60.0f; + phdata->mode.format = SDL_PIXELFORMAT_ARGB4444; + + phdata->display.name = "N-Gage"; + phdata->display.desktop_mode = phdata->mode; + + if (SDL_AddVideoDisplay(&phdata->display, false) == 0) + { + return false; + } + + return true; +} + +static void NGAGE_VideoQuit(SDL_VideoDevice *device) +{ + SDL_VideoData *phdata = (SDL_VideoData*)device->internal; + + if (phdata) + { + SDL_zero(phdata->mode); + SDL_zero(phdata->display); + } +} + +static bool NGAGE_GetDisplayBounds(SDL_VideoDevice* device, SDL_VideoDisplay* display, SDL_Rect* rect) +{ + if (!display) + { + return false; + } + + rect->x = 0; + rect->y = 0; + rect->w = display->current_mode->w; + rect->h = display->current_mode->h; + + return true; +} + +static bool NGAGE_GetDisplayModes(SDL_VideoDevice* device, SDL_VideoDisplay* display) +{ + SDL_VideoData *phdata = (SDL_VideoData*)device->internal; + SDL_DisplayMode mode; + + SDL_zero(mode); + mode.w = phdata->mode.w; + mode.h = phdata->mode.h; + mode.refresh_rate = phdata->mode.refresh_rate; + mode.format = phdata->mode.format; + + if (!SDL_AddFullscreenDisplayMode(display, &mode)) + { + return false; + } + + return true; +} + +#include "../../render/ngage/SDL_render_ngage_c.h" + +static void NGAGE_PumpEvents(SDL_VideoDevice* device) +{ + NGAGE_PumpEventsInternal(); +} + +static bool NGAGE_SuspendScreenSaver(SDL_VideoDevice* device) +{ + NGAGE_SuspendScreenSaverInternal(device->suspend_screensaver); + return true; +} + +#endif // SDL_VIDEO_DRIVER_NGAGE diff --git a/src/video/ngage/SDL_ngagevideo.h b/src/video/ngage/SDL_ngagevideo.h new file mode 100644 index 0000000000000..3153d6be80e08 --- /dev/null +++ b/src/video/ngage/SDL_ngagevideo.h @@ -0,0 +1,39 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_DRIVER_NGAGE + +#include "../SDL_sysvideo.h" + +#ifndef _SDL_ngagevideo_h +#define _SDL_ngagevideo_h + +typedef struct SDL_VideoData +{ + SDL_DisplayMode mode; + SDL_VideoDisplay display; + +} SDL_VideoData; + +#endif // _SDL_ngagevideo_h + +#endif // SDL_VIDEO_DRIVER_NGAGE diff --git a/src/video/openvr/SDL_openvrvideo.c b/src/video/openvr/SDL_openvrvideo.c index 6387c374394b0..a89467cc1bb60 100644 --- a/src/video/openvr/SDL_openvrvideo.c +++ b/src/video/openvr/SDL_openvrvideo.c @@ -593,7 +593,7 @@ static bool OPENVR_InitializeOverlay(SDL_VideoDevice *_this,SDL_Window *window) return SDL_SetError("Could not create cursor overlay (%d)", result ); } SDL_PropertiesID props = SDL_GetWindowProperties(window); - SDL_SetNumberProperty(props, SDL_PROP_WINDOW_OPENVR_OVERLAY_ID, videodata->overlayID); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_OPENVR_OVERLAY_ID_NUMBER, videodata->overlayID); SDL_free(cursorname); videodata->bHasShownOverlay = false; } diff --git a/src/video/stb_image.h b/src/video/stb_image.h index 72e5f4e72c8d7..6c735a8187b83 100644 --- a/src/video/stb_image.h +++ b/src/video/stb_image.h @@ -100,7 +100,7 @@ RECENT REVISION HISTORY: Bug & warning fixes Marc LeBlanc David Woo Guillaume George Martins Mozeiko Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski - Phil Jordan Dave Moore Roy Eltham + Phil Jordan Henner Zeller Dave Moore Roy Eltham Hayaki Saito Nathan Reed Won Chun Luke Graham Johan Duparc Nick Verigakis the Horde3D community Thomas Ruf Ronny Chevalier github:rlyeh @@ -1914,6 +1914,7 @@ static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int r int i,j; unsigned char *good; + if (data == NULL) return data; if (req_comp == img_n) return data; STBI_ASSERT(req_comp >= 1 && req_comp <= 4); diff --git a/src/video/wayland/SDL_waylanddatamanager.c b/src/video/wayland/SDL_waylanddatamanager.c index d5e39a77e0195..3f4c0a2857479 100644 --- a/src/video/wayland/SDL_waylanddatamanager.c +++ b/src/video/wayland/SDL_waylanddatamanager.c @@ -148,8 +148,8 @@ static SDL_MimeDataList *mime_data_list_find(struct wl_list *list, } static bool mime_data_list_add(struct wl_list *list, - const char *mime_type, - const void *buffer, size_t length) + const char *mime_type, + const void *buffer, size_t length) { bool result = true; size_t mime_type_length = 0; @@ -231,7 +231,10 @@ ssize_t Wayland_data_source_send(SDL_WaylandDataSource *source, const char *mime const void *data = NULL; size_t length = 0; - if (source->callback) { + if (SDL_strcmp(mime_type, SDL_DATA_ORIGIN_MIME) == 0) { + data = source->data_device->id_str; + length = SDL_strlen(source->data_device->id_str); + } else if (source->callback) { data = source->callback(source->userdata.data, mime_type, &length); } @@ -263,8 +266,8 @@ void Wayland_data_source_set_callback(SDL_WaylandDataSource *source, } void Wayland_primary_selection_source_set_callback(SDL_WaylandPrimarySelectionSource *source, - SDL_ClipboardDataCallback callback, - void *userdata) + SDL_ClipboardDataCallback callback, + void *userdata) { if (source) { source->callback = callback; @@ -352,6 +355,113 @@ void Wayland_primary_selection_source_destroy(SDL_WaylandPrimarySelectionSource } } +static void offer_source_done_handler(void *data, struct wl_callback *callback, uint32_t callback_data) +{ + if (!callback) { + return; + } + + SDL_WaylandDataOffer *offer = data; + char *id = NULL; + size_t length = 0; + + wl_callback_destroy(offer->callback); + offer->callback = NULL; + + while (read_pipe(offer->read_fd, (void **)&id, &length) > 0) { + } + close(offer->read_fd); + offer->read_fd = -1; + + if (id) { + const bool source_is_external = SDL_strncmp(offer->data_device->id_str, id, length) != 0; + SDL_free(id); + if (source_is_external) { + Wayland_data_offer_notify_from_mimes(offer, false); + } + } +} + +static struct wl_callback_listener offer_source_listener = { + offer_source_done_handler +}; + +static void Wayland_data_offer_check_source(SDL_WaylandDataOffer *offer, const char *mime_type) +{ + SDL_WaylandDataDevice *data_device = NULL; + int pipefd[2]; + + if (!offer) { + SDL_SetError("Invalid data offer"); + } + data_device = offer->data_device; + if (!data_device) { + SDL_SetError("Data device not initialized"); + } else if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) == -1) { + SDL_SetError("Could not read pipe"); + } else { + if (offer->callback) { + wl_callback_destroy(offer->callback); + } + if (offer->read_fd >= 0) { + close(offer->read_fd); + } + + offer->read_fd = pipefd[0]; + + wl_data_offer_receive(offer->offer, mime_type, pipefd[1]); + close(pipefd[1]); + + offer->callback = wl_display_sync(offer->data_device->seat->display->display); + wl_callback_add_listener(offer->callback, &offer_source_listener, offer); + + WAYLAND_wl_display_flush(data_device->seat->display->display); + } +} + +void Wayland_data_offer_notify_from_mimes(SDL_WaylandDataOffer *offer, bool check_origin) +{ + int nformats = 0; + char **new_mime_types = NULL; + if (offer) { + size_t alloc_size = 0; + + // Do a first pass to compute allocation size. + SDL_MimeDataList *item = NULL; + wl_list_for_each(item, &offer->mimes, link) { + // If origin metadata is found, queue a check and wait for confirmation that this offer isn't recursive. + if (check_origin && SDL_strcmp(item->mime_type, SDL_DATA_ORIGIN_MIME) == 0) { + Wayland_data_offer_check_source(offer, item->mime_type); + return; + } + + ++nformats; + alloc_size += SDL_strlen(item->mime_type) + 1; + } + + alloc_size += (nformats + 1) * sizeof(char *); + + new_mime_types = SDL_AllocateTemporaryMemory(alloc_size); + if (!new_mime_types) { + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "unable to allocate new_mime_types"); + return; + } + + // Second pass to fill. + char *strPtr = (char *)(new_mime_types + nformats + 1); + item = NULL; + int i = 0; + wl_list_for_each(item, &offer->mimes, link) { + new_mime_types[i] = strPtr; + strPtr = stpcpy(strPtr, item->mime_type) + 1; + i++; + } + new_mime_types[nformats] = NULL; + } + + SDL_SendClipboardUpdate(false, new_mime_types, nformats); +} + void *Wayland_data_offer_receive(SDL_WaylandDataOffer *offer, const char *mime_type, size_t *length) { @@ -433,7 +543,7 @@ bool Wayland_primary_selection_offer_add_mime(SDL_WaylandPrimarySelectionOffer * } bool Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer, - const char *mime_type) + const char *mime_type) { bool found = false; @@ -444,7 +554,7 @@ bool Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer, } bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOffer *offer, - const char *mime_type) + const char *mime_type) { bool found = false; @@ -457,6 +567,12 @@ bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOffer * void Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer) { if (offer) { + if (offer->callback) { + wl_callback_destroy(offer->callback); + } + if (offer->read_fd >= 0) { + close(offer->read_fd); + } wl_data_offer_destroy(offer->offer); mime_data_list_free(&offer->mimes); SDL_free(offer); @@ -522,6 +638,9 @@ bool Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device, mime_type); } + // Advertise the data origin MIME + wl_data_source_offer(source->source, SDL_DATA_ORIGIN_MIME); + if (index == 0) { Wayland_data_device_clear_selection(data_device); result = SDL_SetError("No mime data"); diff --git a/src/video/wayland/SDL_waylanddatamanager.h b/src/video/wayland/SDL_waylanddatamanager.h index 276620f906276..bdde6a2345c3f 100644 --- a/src/video/wayland/SDL_waylanddatamanager.h +++ b/src/video/wayland/SDL_waylanddatamanager.h @@ -31,6 +31,10 @@ #define TEXT_MIME "text/plain;charset=utf-8" #define FILE_MIME "text/uri-list" #define FILE_PORTAL_MIME "application/vnd.portal.filetransfer" +#define SDL_DATA_ORIGIN_MIME "application/x-sdl3-source-id" + +typedef struct SDL_WaylandDataDevice SDL_WaylandDataDevice; +typedef struct SDL_WaylandPrimarySelectionDevice SDL_WaylandPrimarySelectionDevice; typedef struct { @@ -49,7 +53,7 @@ typedef struct SDL_WaylandUserdata typedef struct { struct wl_data_source *source; - void *data_device; + SDL_WaylandDataDevice *data_device; SDL_ClipboardDataCallback callback; SDL_WaylandUserdata userdata; } SDL_WaylandDataSource; @@ -57,8 +61,8 @@ typedef struct typedef struct { struct zwp_primary_selection_source_v1 *source; - void *data_device; - void *primary_selection_device; + SDL_WaylandDataDevice *data_device; + SDL_WaylandPrimarySelectionDevice *primary_selection_device; SDL_ClipboardDataCallback callback; SDL_WaylandUserdata userdata; } SDL_WaylandPrimarySelectionSource; @@ -67,20 +71,25 @@ typedef struct { struct wl_data_offer *offer; struct wl_list mimes; - void *data_device; + SDL_WaylandDataDevice *data_device; + + // Callback data for queued receive. + struct wl_callback *callback; + int read_fd; } SDL_WaylandDataOffer; typedef struct { struct zwp_primary_selection_offer_v1 *offer; struct wl_list mimes; - void *primary_selection_device; + SDL_WaylandPrimarySelectionDevice *primary_selection_device; } SDL_WaylandPrimarySelectionOffer; -typedef struct +struct SDL_WaylandDataDevice { struct wl_data_device *data_device; struct SDL_WaylandSeat *seat; + char *id_str; // Drag and Drop uint32_t drag_serial; @@ -93,9 +102,9 @@ typedef struct // Clipboard and Primary Selection uint32_t selection_serial; SDL_WaylandDataSource *selection_source; -} SDL_WaylandDataDevice; +}; -typedef struct +struct SDL_WaylandPrimarySelectionDevice { struct zwp_primary_selection_device_v1 *primary_selection_device; struct SDL_WaylandSeat *seat; @@ -103,7 +112,7 @@ typedef struct uint32_t selection_serial; SDL_WaylandPrimarySelectionSource *selection_source; SDL_WaylandPrimarySelectionOffer *selection_offer; -} SDL_WaylandPrimarySelectionDevice; +}; // Wayland Data Source / Primary Selection Source - (Sending) extern SDL_WaylandDataSource *Wayland_data_source_create(SDL_VideoDevice *_this); @@ -136,13 +145,15 @@ extern void *Wayland_primary_selection_offer_receive(SDL_WaylandPrimarySelection const char *mime_type, size_t *length); extern bool Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer, - const char *mime_type); + const char *mime_type); +extern void Wayland_data_offer_notify_from_mimes(SDL_WaylandDataOffer *offer, + bool check_origin); extern bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOffer *offer, - const char *mime_type); + const char *mime_type); extern bool Wayland_data_offer_add_mime(SDL_WaylandDataOffer *offer, - const char *mime_type); + const char *mime_type); extern bool Wayland_primary_selection_offer_add_mime(SDL_WaylandPrimarySelectionOffer *offer, - const char *mime_type); + const char *mime_type); extern void Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer); extern void Wayland_primary_selection_offer_destroy(SDL_WaylandPrimarySelectionOffer *offer); diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 00b77dd532c6e..7f0f5f01cfe50 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -179,18 +179,11 @@ static void Wayland_GetScaledMouseRect(SDL_Window *window, SDL_Rect *scaled_rect scaled_rect->h = (int)SDL_ceil(window->mouse_rect.h / window_data->pointer_scale.y); } -static Uint64 Wayland_GetEventTimestamp(Uint64 nsTimestamp) +static Uint64 Wayland_AdjustEventTimestampBase(Uint64 nsTimestamp) { - static Uint64 last; - static Uint64 timestamp_offset; + static Uint64 timestamp_offset = 0; const Uint64 now = SDL_GetTicksNS(); - if (nsTimestamp < last) { - // 32-bit timer rollover, bump the offset - timestamp_offset += SDL_MS_TO_NS(0x100000000LLU); - } - last = nsTimestamp; - if (!timestamp_offset) { timestamp_offset = (now - nsTimestamp); } @@ -204,52 +197,56 @@ static Uint64 Wayland_GetEventTimestamp(Uint64 nsTimestamp) return nsTimestamp; } -static void input_timestamp_listener(void *data, struct zwp_input_timestamps_v1 *zwp_input_timestamps_v1, - uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) +/* This should only be called with 32-bit millisecond timestamps received in Wayland events! + * No synthetic or high-res timestamps, as they can corrupt the rollover offset! + */ +static Uint64 Wayland_EventTimestampMSToNS(Uint32 wl_timestamp_ms) { - *((Uint64 *)data) = ((((Uint64)tv_sec_hi << 32) | (Uint64)tv_sec_lo) * SDL_NS_PER_SECOND) + tv_nsec; -} + static Uint64 timestamp_offset = 0; + static Uint32 last = 0; -static const struct zwp_input_timestamps_v1_listener timestamp_listener = { - input_timestamp_listener -}; - -static Uint64 Wayland_GetKeyboardTimestamp(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms) -{ - if (wl_timestamp_ms) { - return Wayland_GetEventTimestamp(seat->keyboard.highres_timestamp_ns ? seat->keyboard.highres_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms)); + // Handle 32-bit timer rollover. + if (wl_timestamp_ms < last) { + timestamp_offset += SDL_MS_TO_NS(SDL_UINT64_C(0x100000000)); } + last = wl_timestamp_ms; + wl_timestamp_ms += timestamp_offset; - return 0; + return SDL_MS_TO_NS(wl_timestamp_ms) + timestamp_offset; } +/* Even if high-res timestamps are available, the millisecond timestamps are still processed + * to accumulate the rollover offset if needed later. + */ + static Uint64 Wayland_GetKeyboardTimestampRaw(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms) { - if (wl_timestamp_ms) { - return seat->keyboard.highres_timestamp_ns ? seat->keyboard.highres_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms); - } - - return 0; + const Uint64 adjustedTimestampMS = Wayland_EventTimestampMSToNS(wl_timestamp_ms); + return seat->keyboard.timestamps ? seat->keyboard.highres_timestamp_ns : adjustedTimestampMS; } static Uint64 Wayland_GetPointerTimestamp(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms) { - if (wl_timestamp_ms) { - return Wayland_GetEventTimestamp(seat->pointer.highres_timestamp_ns ? seat->pointer.highres_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms)); - } - - return 0; + const Uint64 adjustedTimestampMS = Wayland_EventTimestampMSToNS(wl_timestamp_ms); + return Wayland_AdjustEventTimestampBase(seat->pointer.timestamps ? seat->pointer.highres_timestamp_ns : adjustedTimestampMS); } Uint64 Wayland_GetTouchTimestamp(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms) { - if (wl_timestamp_ms) { - return Wayland_GetEventTimestamp(seat->touch.highres_timestamp_ns ? seat->touch.highres_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms)); - } + const Uint64 adjustedTimestampMS = Wayland_EventTimestampMSToNS(wl_timestamp_ms); + return Wayland_AdjustEventTimestampBase(seat->touch.timestamps ? seat->touch.highres_timestamp_ns : adjustedTimestampMS); +} - return 0; +static void input_timestamp_listener(void *data, struct zwp_input_timestamps_v1 *zwp_input_timestamps_v1, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) +{ + *((Uint64 *)data) = ((((Uint64)tv_sec_hi << 32) | (Uint64)tv_sec_lo) * SDL_NS_PER_SECOND) + tv_nsec; } +static const struct zwp_input_timestamps_v1_listener timestamp_listener = { + input_timestamp_listener +}; + static void Wayland_SeatRegisterInputTimestampListeners(SDL_WaylandSeat *seat) { if (seat->display->input_timestamps_manager) { @@ -304,7 +301,7 @@ static bool keyboard_repeat_handle(SDL_WaylandKeyboardRepeat *repeat_info, Uint6 while (elapsed >= repeat_info->next_repeat_ns) { if (repeat_info->scancode != SDL_SCANCODE_UNKNOWN) { const Uint64 timestamp = repeat_info->wl_press_time_ns + repeat_info->next_repeat_ns; - SDL_SendKeyboardKeyIgnoreModifiers(Wayland_GetEventTimestamp(timestamp), repeat_info->keyboard_id, repeat_info->key, repeat_info->scancode, true); + SDL_SendKeyboardKeyIgnoreModifiers(Wayland_AdjustEventTimestampBase(timestamp), repeat_info->keyboard_id, repeat_info->key, repeat_info->scancode, true); } if (repeat_info->text[0]) { SDL_SendKeyboardText(repeat_info->text); @@ -570,17 +567,15 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this) } } -static void pointer_handle_motion(void *data, struct wl_pointer *pointer, - uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w) +static void pointer_handle_motion_common(SDL_WaylandSeat *seat, Uint64 nsTimestamp, wl_fixed_t sx_w, wl_fixed_t sy_w) { - SDL_WaylandSeat *seat = data; SDL_WindowData *window_data = seat->pointer.focus; SDL_Window *window = window_data ? window_data->sdlwindow : NULL; if (window_data) { const float sx = (float)(wl_fixed_to_double(sx_w) * window_data->pointer_scale.x); const float sy = (float)(wl_fixed_to_double(sy_w) * window_data->pointer_scale.y); - SDL_SendMouseMotion(Wayland_GetPointerTimestamp(seat, time), window_data->sdlwindow, seat->pointer.sdl_id, false, sx, sy); + SDL_SendMouseMotion(nsTimestamp, window_data->sdlwindow, seat->pointer.sdl_id, false, sx, sy); seat->pointer.last_motion.x = (int)SDL_floorf(sx); seat->pointer.last_motion.y = (int)SDL_floorf(sy); @@ -674,6 +669,13 @@ static void pointer_handle_motion(void *data, struct wl_pointer *pointer, } } +static void pointer_handle_motion(void *data, struct wl_pointer *pointer, + uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w) +{ + SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data; + pointer_handle_motion_common(seat, Wayland_GetPointerTimestamp(seat, time), sx_w, sy_w); +} + static void pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx_w, wl_fixed_t sy_w) @@ -702,7 +704,7 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer, * FIXME: This causes a movement event with an anomalous timestamp when * the cursor enters the window. */ - pointer_handle_motion(data, pointer, 0, sx_w, sy_w); + pointer_handle_motion_common(seat, 0, sx_w, sy_w); // Update the pointer grab state. Wayland_SeatUpdatePointerGrab(seat); @@ -738,11 +740,11 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer, SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data; seat->pointer.focus = NULL; seat->pointer.buttons_pressed = 0; - SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_LEFT, false); - SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_RIGHT, false); - SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_MIDDLE, false); - SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_X1, false); - SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_X2, false); + SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_LEFT, false); + SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_RIGHT, false); + SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_MIDDLE, false); + SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_X1, false); + SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_X2, false); /* A pointer leave event may be emitted if the compositor hides the pointer in response to receiving a touch event. * Don't relinquish focus if the surface has active touches, as the compositor is just transitioning from mouse to touch mode. @@ -841,11 +843,10 @@ static bool Wayland_ProcessHitTest(SDL_WaylandSeat *seat, Uint32 serial) } static void pointer_handle_button_common(SDL_WaylandSeat *seat, uint32_t serial, - uint32_t time, uint32_t button, uint32_t state_w) + Uint64 nsTimestamp, uint32_t button, uint32_t state_w) { SDL_WindowData *window = seat->pointer.focus; enum wl_pointer_button_state state = state_w; - Uint64 timestamp = Wayland_GetPointerTimestamp(seat, time); Uint8 sdl_button; const bool down = (state != 0); @@ -909,7 +910,7 @@ static void pointer_handle_button_common(SDL_WaylandSeat *seat, uint32_t serial, } if (!ignore_click) { - SDL_SendMouseButton(timestamp, window->sdlwindow, seat->pointer.sdl_id, sdl_button, down); + SDL_SendMouseButton(nsTimestamp, window->sdlwindow, seat->pointer.sdl_id, sdl_button, down); } } } @@ -918,15 +919,13 @@ static void pointer_handle_button(void *data, struct wl_pointer *pointer, uint32 uint32_t time, uint32_t button, uint32_t state_w) { SDL_WaylandSeat *seat = data; - - pointer_handle_button_common(seat, serial, time, button, state_w); + pointer_handle_button_common(seat, serial, Wayland_GetPointerTimestamp(seat, time), button, state_w); } static void pointer_handle_axis_common_v1(SDL_WaylandSeat *seat, - uint32_t time, uint32_t axis, wl_fixed_t value) + Uint64 nsTimestamp, uint32_t axis, wl_fixed_t value) { SDL_WindowData *window = seat->pointer.focus; - const Uint64 timestamp = Wayland_GetPointerTimestamp(seat, time); const enum wl_pointer_axis a = axis; if (seat->pointer.focus) { @@ -948,7 +947,7 @@ static void pointer_handle_axis_common_v1(SDL_WaylandSeat *seat, x /= WAYLAND_WHEEL_AXIS_UNIT; y /= WAYLAND_WHEEL_AXIS_UNIT; - SDL_SendMouseWheel(timestamp, window->sdlwindow, seat->pointer.sdl_id, x, y, SDL_MOUSEWHEEL_NORMAL); + SDL_SendMouseWheel(nsTimestamp, window->sdlwindow, seat->pointer.sdl_id, x, y, SDL_MOUSEWHEEL_NORMAL); } } @@ -1029,12 +1028,13 @@ static void pointer_handle_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { SDL_WaylandSeat *seat = data; + const Uint64 nsTimestamp = Wayland_GetPointerTimestamp(seat, time); if (wl_seat_get_version(seat->wl_seat) >= WL_POINTER_FRAME_SINCE_VERSION) { - seat->pointer.current_axis_info.timestamp_ns = Wayland_GetPointerTimestamp(seat, time); + seat->pointer.current_axis_info.timestamp_ns = nsTimestamp; pointer_handle_axis_common(seat, AXIS_EVENT_CONTINUOUS, axis, value); } else { - pointer_handle_axis_common_v1(seat, time, axis, value); + pointer_handle_axis_common_v1(seat, nsTimestamp, axis, value); } } @@ -1162,7 +1162,7 @@ static void relative_pointer_handle_relative_motion(void *data, SDL_Mouse *mouse = SDL_GetMouse(); // Relative pointer event times are in microsecond granularity. - const Uint64 timestamp = Wayland_GetEventTimestamp(SDL_US_TO_NS(((Uint64)time_hi << 32) | (Uint64)time_lo)); + const Uint64 timestamp = Wayland_AdjustEventTimestampBase(SDL_US_TO_NS(((Uint64)time_hi << 32) | (Uint64)time_lo)); double dx; double dy; @@ -1295,7 +1295,7 @@ static void touch_handler_motion(void *data, struct wl_touch *touch, uint32_t ti const float x = (float)wl_fixed_to_double(fx) / window_data->current.logical_width; const float y = (float)wl_fixed_to_double(fy) / window_data->current.logical_height; - SDL_SendTouchMotion(Wayland_GetPointerTimestamp(seat, timestamp), (SDL_TouchID)(uintptr_t)touch, + SDL_SendTouchMotion(Wayland_GetTouchTimestamp(seat, timestamp), (SDL_TouchID)(uintptr_t)touch, (SDL_FingerID)(id + 1), window_data->sdlwindow, x, y, 1.0f); } } @@ -2018,7 +2018,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, const SDL_Scancode scancode = Wayland_GetScancodeForKey(seat, key); Wayland_HandleModifierKeys(seat, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED); - Uint64 timestamp = Wayland_GetKeyboardTimestamp(seat, time); + Uint64 timestamp = Wayland_AdjustEventTimestampBase(timestamp_raw_ns); SDL_SendKeyboardKeyIgnoreModifiers(timestamp, seat->keyboard.sdl_id, key, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED); @@ -2518,6 +2518,7 @@ static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_ data_device->seat->display->last_incoming_data_offer_seat = data_device->seat; data_offer->offer = id; data_offer->data_device = data_device; + data_offer->read_fd = -1; WAYLAND_wl_list_init(&(data_offer->mimes)); wl_data_offer_set_user_data(id, data_offer); wl_data_offer_add_listener(id, &data_offer_listener, data_offer); @@ -2759,41 +2760,6 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_d data_device->drag_offer = NULL; } -static void notifyFromMimes(struct wl_list *mimes) -{ - int nformats = 0; - char **new_mime_types = NULL; - if (mimes) { - nformats = WAYLAND_wl_list_length(mimes); - size_t alloc_size = (nformats + 1) * sizeof(char *); - - /* do a first pass to compute allocation size */ - SDL_MimeDataList *item = NULL; - wl_list_for_each(item, mimes, link) { - alloc_size += SDL_strlen(item->mime_type) + 1; - } - - new_mime_types = SDL_AllocateTemporaryMemory(alloc_size); - if (!new_mime_types) { - SDL_LogError(SDL_LOG_CATEGORY_INPUT, "unable to allocate new_mime_types"); - return; - } - - /* second pass to fill*/ - char *strPtr = (char *)(new_mime_types + nformats + 1); - item = NULL; - int i = 0; - wl_list_for_each(item, mimes, link) { - new_mime_types[i] = strPtr; - strPtr = stpcpy(strPtr, item->mime_type) + 1; - i++; - } - new_mime_types[nformats] = NULL; - } - - SDL_SendClipboardUpdate(false, new_mime_types, nformats); -} - static void data_device_handle_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) { @@ -2812,7 +2778,7 @@ static void data_device_handle_selection(void *data, struct wl_data_device *wl_d data_device->selection_offer = offer; } - notifyFromMimes(offer ? &offer->mimes : NULL); + Wayland_data_offer_notify_from_mimes(offer, true); } static const struct wl_data_device_listener data_device_listener = { @@ -2942,6 +2908,29 @@ static const struct zwp_text_input_v3_listener text_input_listener = { text_input_done }; +static void Wayland_DataDeviceSetID(SDL_WaylandDataDevice *data_device) +{ + if (!data_device->id_str) +#ifdef SDL_USE_LIBDBUS + { + SDL_DBusContext *dbus = SDL_DBus_GetContext(); + if (dbus) { + const char *id = dbus->bus_get_unique_name(dbus->session_conn); + if (id) { + data_device->id_str = SDL_strdup(id); + } + } + } + if (!data_device->id_str) +#endif + { + char id[24]; + Uint64 pid = (Uint64)getpid(); + SDL_snprintf(id, sizeof(id), "%" SDL_PRIu64, pid); + data_device->id_str = SDL_strdup(id); + } +} + static void Wayland_SeatCreateDataDevice(SDL_WaylandSeat *seat) { if (!seat->display->data_device_manager) { @@ -2960,6 +2949,7 @@ static void Wayland_SeatCreateDataDevice(SDL_WaylandSeat *seat) if (!data_device->data_device) { SDL_free(data_device); } else { + Wayland_DataDeviceSetID(data_device); wl_data_device_set_user_data(data_device->data_device, data_device); wl_data_device_add_listener(data_device->data_device, &data_device_listener, data_device); @@ -3249,7 +3239,7 @@ static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool return; // Not a pen we report on. } - const Uint64 timestamp = Wayland_GetEventTimestamp(SDL_MS_TO_NS(time)); + const Uint64 timestamp = Wayland_AdjustEventTimestampBase(Wayland_EventTimestampMSToNS(time)); const SDL_PenID instance_id = sdltool->instance_id; SDL_Window *window = sdltool->tool_focus; @@ -3449,6 +3439,7 @@ void Wayland_SeatDestroy(SDL_WaylandSeat *seat, bool send_events) wl_data_device_destroy(seat->data_device->data_device); } } + SDL_free(seat->data_device->id_str); SDL_free(seat->data_device); } diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 96bf8c7a1b68d..8f0ecc9b1dbf6 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -987,29 +987,26 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_ } } - if (!handled_by_ime) { - if (pressed) { - X11_HandleModifierKeys(videodata, scancode, true, true); - SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true); - - if (*text && !(SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_ALT))) { - text[text_length] = '\0'; - X11_ClearComposition(windowdata); - SDL_SendKeyboardText(text); - } - } else { - if (X11_KeyRepeat(display, xevent)) { - // We're about to get a repeated key down, ignore the key up - return; - } + if (pressed) { + X11_HandleModifierKeys(videodata, scancode, true, true); + SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true); - X11_HandleModifierKeys(videodata, scancode, false, true); - SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, false); + // Synthesize a text event if the IME didn't consume a printable character + if (*text && !(SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_ALT))) { + text[text_length] = '\0'; + X11_ClearComposition(windowdata); + SDL_SendKeyboardText(text); } - } - if (pressed) { X11_UpdateUserTime(windowdata, xevent->xkey.time); + } else { + if (X11_KeyRepeat(display, xevent)) { + // We're about to get a repeated key down, ignore the key up + return; + } + + X11_HandleModifierKeys(videodata, scancode, false, true); + SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, false); } } diff --git a/test/testautomation_surface.c b/test/testautomation_surface.c index 3b840039d8573..d3382309229d7 100644 --- a/test/testautomation_surface.c +++ b/test/testautomation_surface.c @@ -959,6 +959,36 @@ static int SDLCALL surface_testBlitBlendMul(void *arg) return TEST_COMPLETED; } +/** + * Tests blitting invalid surfaces. + */ +static int SDLCALL surface_testBlitInvalid(void *arg) +{ + SDL_Surface *valid, *invalid; + bool result; + + valid = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA8888); + SDLTest_AssertCheck(valid != NULL, "Check surface creation"); + invalid = SDL_CreateSurface(0, 0, SDL_PIXELFORMAT_RGBA8888); + SDLTest_AssertCheck(invalid != NULL, "Check surface creation"); + SDLTest_AssertCheck(invalid->pixels == NULL, "Check surface pixels are NULL"); + + result = SDL_BlitSurface(invalid, NULL, valid, NULL); + SDLTest_AssertCheck(result == true, "SDL_BlitSurface(invalid, NULL, valid, NULL), result = %s\n", result ? "true" : "false"); + result = SDL_BlitSurface(valid, NULL, invalid, NULL); + SDLTest_AssertCheck(result == true, "SDL_BlitSurface(valid, NULL, invalid, NULL), result = %s\n", result ? "true" : "false"); + + result = SDL_BlitSurfaceScaled(invalid, NULL, valid, NULL, SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(result == false, "SDL_BlitSurfaceScaled(invalid, NULL, valid, NULL, SDL_SCALEMODE_NEAREST), result = %s\n", result ? "true" : "false"); + result = SDL_BlitSurfaceScaled(valid, NULL, invalid, NULL, SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(result == false, "SDL_BlitSurfaceScaled(valid, NULL, invalid, NULL, SDL_SCALEMODE_NEAREST), result = %s\n", result ? "true" : "false"); + + SDL_DestroySurface(valid); + SDL_DestroySurface(invalid); + + return TEST_COMPLETED; +} + static int SDLCALL surface_testOverflow(void *arg) { char buf[1024]; @@ -1632,6 +1662,10 @@ static const SDLTest_TestCaseReference surfaceTestBlitBlendMul = { surface_testBlitBlendMul, "surface_testBlitBlendMul", "Tests blitting routines with mul blending mode.", TEST_ENABLED }; +static const SDLTest_TestCaseReference surfaceTestBlitInvalid = { + surface_testBlitInvalid, "surface_testBlitInvalid", "Tests blitting routines with invalid surfaces.", TEST_ENABLED +}; + static const SDLTest_TestCaseReference surfaceTestOverflow = { surface_testOverflow, "surface_testOverflow", "Test overflow detection.", TEST_ENABLED }; @@ -1680,6 +1714,7 @@ static const SDLTest_TestCaseReference *surfaceTests[] = { &surfaceTestBlitBlendAddPremultiplied, &surfaceTestBlitBlendMod, &surfaceTestBlitBlendMul, + &surfaceTestBlitInvalid, &surfaceTestOverflow, &surfaceTestFlip, &surfaceTestPalette,