Преглед на файлове

N3DS port (squashed)

A dedicated renderer using Citro3D would likely allow for better
much better graphical performances.
Pierre Wendling преди 4 години
родител
ревизия
655275378d
променени са 50 файла, в които са добавени 2663 реда и са изтрити 5 реда
  1. 40 0
      .github/workflows/n3ds.yml
  2. 72 2
      CMakeLists.txt
  3. 27 0
      docs/README-n3ds.md
  4. 7 0
      include/SDL_config.h.cmake
  5. 9 0
      include/SDL_main.h
  6. 5 0
      include/SDL_platform.h
  7. 1 1
      include/SDL_stdinc.h
  8. 1 0
      include/SDL_video.h
  9. 2 0
      src/SDL.c
  10. 7 0
      src/SDL_log.c
  11. 3 0
      src/audio/SDL_audio.c
  12. 1 0
      src/audio/SDL_sysaudio.h
  13. 363 0
      src/audio/n3ds/SDL_n3dsaudio.c
  14. 50 0
      src/audio/n3ds/SDL_n3dsaudio.h
  15. 2 0
      src/cpuinfo/SDL_cpuinfo.c
  16. 2 0
      src/dynapi/SDL_dynapi.h
  17. 6 0
      src/file/SDL_rwops.c
  18. 56 0
      src/file/n3ds/SDL_rwopsromfs.c
  19. 30 0
      src/file/n3ds/SDL_rwopsromfs.h
  20. 86 0
      src/filesystem/n3ds/SDL_sysfilesystem.c
  21. 3 0
      src/joystick/SDL_gamecontrollerdb.h
  22. 3 0
      src/joystick/SDL_joystick.c
  23. 1 0
      src/joystick/SDL_sysjoystick.h
  24. 367 0
      src/joystick/n3ds/SDL_sysjoystick.c
  25. 1 1
      src/libm/math_private.h
  26. 59 0
      src/locale/n3ds/SDL_syslocale.c
  27. 82 0
      src/main/n3ds/SDL_n3ds_main.c
  28. 3 0
      src/power/SDL_power.c
  29. 1 0
      src/power/SDL_syspower.h
  30. 111 0
      src/power/n3ds/SDL_syspower.c
  31. 2 0
      src/thread/SDL_thread_c.h
  32. 133 0
      src/thread/n3ds/SDL_syscond.c
  33. 93 0
      src/thread/n3ds/SDL_sysmutex.c
  34. 37 0
      src/thread/n3ds/SDL_sysmutex_c.h
  35. 134 0
      src/thread/n3ds/SDL_syssem.c
  36. 148 0
      src/thread/n3ds/SDL_systhread.c
  37. 32 0
      src/thread/n3ds/SDL_systhread_c.h
  38. 81 0
      src/timer/n3ds/SDL_systimer.c
  39. 1 0
      src/video/SDL_sysvideo.h
  40. 4 1
      src/video/SDL_video.c
  41. 45 0
      src/video/n3ds/SDL_n3dsevents.c
  42. 31 0
      src/video/n3ds/SDL_n3dsevents_c.h
  43. 148 0
      src/video/n3ds/SDL_n3dsframebuffer.c
  44. 33 0
      src/video/n3ds/SDL_n3dsframebuffer_c.h
  45. 76 0
      src/video/n3ds/SDL_n3dsswkb.c
  46. 38 0
      src/video/n3ds/SDL_n3dsswkb.h
  47. 163 0
      src/video/n3ds/SDL_n3dsvideo.c
  48. 38 0
      src/video/n3ds/SDL_n3dsvideo.h
  49. 25 0
      test/CMakeLists.txt
  50. BIN
      test/n3ds/logo48x48.png

+ 40 - 0
.github/workflows/n3ds.yml

@@ -0,0 +1,40 @@
+name: Build (Nintendo 3DS)
+
+on: [push, pull_request]
+
+jobs:
+  n3ds:
+    runs-on: ubuntu-latest
+    container:
+      image: devkitpro/devkitarm:latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Install build requirements
+        run: |
+          apt update
+          apt install ninja-build
+      - name: Configure CMake
+        run: |
+          cmake -S . -B build -G Ninja \
+            -DCMAKE_TOOLCHAIN_FILE=${DEVKITPRO}/cmake/3DS.cmake \
+            -DSDL_TESTS=ON \
+            -DSDL_INSTALL_TESTS=ON \
+            -DCMAKE_BUILD_TYPE=Release \
+            -DCMAKE_INSTALL_PREFIX=prefix
+      - name: Build
+        run: cmake --build build --verbose
+      - name: Install CMake
+        run: |
+          echo "SDL2_DIR=$(pwd)/prefix" >> $GITHUB_ENV
+          cmake --install build/
+          ( cd prefix; find ) | LC_ALL=C sort -u
+      - name: Verify CMake configuration files
+        run: |
+          cmake -S cmake/test -B cmake_config_build -G Ninja \
+            -DCMAKE_TOOLCHAIN_FILE=${DEVKITPRO}/cmake/3DS.cmake \
+            -DTEST_SHARED=FALSE \
+            -DCMAKE_PREFIX_PATH=${{ env.SDL2_DIR }} \
+            -DCMAKE_BUILD_TYPE=Release
+          cmake --build cmake_config_build --verbose
+      # Not running test_pkgconfig.sh and test_sdlconfig.sh
+      # as invoking the compiler manually is not supported

+ 72 - 2
CMakeLists.txt

@@ -196,6 +196,8 @@ elseif(CMAKE_SYSTEM_NAME MATCHES "BeOS.*")
   message_error("BeOS support has been removed as of SDL 2.0.2.")
 elseif(CMAKE_SYSTEM_NAME MATCHES "Haiku.*")
   set(HAIKU TRUE)
+elseif(NINTENDO_3DS)
+  set(N3DS TRUE)
 endif()
 
 # Don't mistake osx for unix
@@ -277,7 +279,7 @@ if(APPLE OR ARCH_64 OR MSVC_CLANG)
     set(OPT_DEF_SSEMATH ON)
   endif()
 endif()
-if(UNIX OR MINGW OR MSYS OR (USE_CLANG AND NOT WINDOWS) OR VITA OR PSP OR PS2)
+if(UNIX OR MINGW OR MSYS OR (USE_CLANG AND NOT WINDOWS) OR VITA OR PSP OR PS2 OR N3DS)
   set(OPT_DEF_LIBC ON)
 endif()
 
@@ -381,7 +383,7 @@ if(EMSCRIPTEN)
   set(SDL_TEST_ENABLED_BY_DEFAULT OFF)
 endif()
 
-if(VITA OR PSP OR PS2)
+if(VITA OR PSP OR PS2 OR N3DS)
   set(SDL_SHARED_ENABLED_BY_DEFAULT OFF)
   set(SDL_LOADSO_ENABLED_BY_DEFAULT OFF)
 endif()
@@ -2734,6 +2736,74 @@ elseif(OS2)
   if(SDL_HIDAPI)
     CheckHIDAPI()
   endif()
+
+elseif(N3DS)
+  file(GLOB N3DS_MAIN_SOURCES ${SDL2_SOURCE_DIR}/src/main/n3ds/*.c)
+  set(SDLMAIN_SOURCES ${SDLMAIN_SOURCES} ${N3DS_MAIN_SOURCES})
+
+  if(SDL_AUDIO)
+    set(SDL_AUDIO_DRIVER_N3DS 1)
+    file(GLOB N3DS_AUDIO_SOURCES ${SDL2_SOURCE_DIR}/src/audio/n3ds/*.c)
+    list(APPEND SOURCE_FILES ${N3DS_AUDIO_SOURCES})
+    set(HAVE_SDL_AUDIO TRUE)
+  endif()
+
+  if(SDL_FILESYSTEM)
+    set(SDL_FILESYSTEM_N3DS 1)
+    file(GLOB N3DS_FILESYSTEM_SOURCES ${SDL2_SOURCE_DIR}/src/filesystem/n3ds/*.c)
+    list(APPEND SOURCE_FILES ${N3DS_FILESYSTEM_SOURCES})
+    set(HAVE_SDL_FILESYSTEM TRUE)
+  endif()
+
+  if(SDL_JOYSTICK)
+    set(SDL_JOYSTICK_N3DS 1)
+    file(GLOB N3DS_JOYSTICK_SOURCES ${SDL2_SOURCE_DIR}/src/joystick/n3ds/*.c)
+    list(APPEND SOURCE_FILES ${N3DS_JOYSTICK_SOURCES})
+    set(HAVE_SDL_JOYSTICK TRUE)
+  endif()
+
+  if(SDL_POWER)
+    set(SDL_POWER_N3DS 1)
+    file(GLOB N3DS_POWER_SOURCES ${SDL2_SOURCE_DIR}/src/power/n3ds/*.c)
+    list(APPEND SOURCE_FILES ${N3DS_POWER_SOURCES})
+    set(HAVE_SDL_POWER TRUE)
+  endif()
+
+  if(SDL_THREADS)
+    set(SDL_THREAD_N3DS 1)
+    file(GLOB N3DS_THREAD_SOURCES ${SDL2_SOURCE_DIR}/src/thread/n3ds/*.c)
+    list(APPEND SOURCE_FILES ${N3DS_THREAD_SOURCES} ${SDL2_SOURCE_DIR}/src/thread/generic/SDL_systls.c)
+    set(HAVE_SDL_THREADS TRUE)
+  endif()
+
+  if(SDL_TIMERS)
+    set(SDL_TIMER_N3DS 1)
+    file(GLOB TIMER_SOURCES ${SDL2_SOURCE_DIR}/src/timer/n3ds/*.c)
+    list(APPEND SOURCE_FILES ${TIMER_SOURCES})
+    set(HAVE_SDL_TIMERS TRUE)
+  endif()
+
+  if(SDL_VIDEO)
+    set(SDL_VIDEO_DRIVER_N3DS 1)
+    file(GLOB N3DS_VIDEO_SOURCES ${SDL2_SOURCE_DIR}/src/video/n3ds/*.c)
+    list(APPEND SOURCE_FILES ${N3DS_VIDEO_SOURCES})
+    set(HAVE_SDL_VIDEO TRUE)
+  endif()
+
+  if(SDL_LOCALE)
+    file(GLOB N3DS_LOCALE_SOURCES ${SDL2_SOURCE_DIR}/src/locale/n3ds/*.c)
+    list(APPEND SOURCE_FILES ${N3DS_LOCALE_SOURCES})
+    set(HAVE_SDL_LOCALE TRUE)
+  endif()
+
+  # Requires the n3ds file implementation
+  if(SDL_FILE)
+    file(GLOB N3DS_FILE_SOURCES ${SDL2_SOURCE_DIR}/src/file/n3ds/*.c)
+    list(APPEND SOURCE_FILES ${N3DS_FILE_SOURCES})
+    set(HAVE_SDL_FILE TRUE)
+  else()
+    message_error("SDL_FILE must be enabled to build on N3DS")
+  endif()
 endif()
 
 if(HAVE_VULKAN AND NOT SDL_LOADSO)

+ 27 - 0
docs/README-n3ds.md

@@ -0,0 +1,27 @@
+# Nintendo 3DS
+
+SDL port for the Nintendo 3DS [Homebrew toolchain](https://devkitpro.org/) contributed by:
+
+-   [Pierre Wendling](https://github.com/FtZPetruska)
+
+Credits to:
+
+-   The awesome people who ported SDL to other homebrew platforms.
+-   The Devkitpro team for making all the tools necessary to achieve this.
+
+## Building
+
+To build for the Nintendo 3DS, make sure you have devkitARM and cmake installed and run:
+
+```bash
+cmake -S. -Bbuild -DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/3DS.cmake" -DCMAKE_BUILD_TYPE=Release
+cmake --build build
+cmake --install build
+```
+
+## Notes
+
+-   Currently only software rendering is supported.
+-   Window are created on the top screen by default, use the `SDL_WINDOW_N3DS_BOTTOM` flag to put them on the bottom screen.
+-   SDL2main should be used to ensure all the necessary services are initialised.
+-   By default, the extra L2 cache and higher clock speeds of the New 2/3DS lineup are enabled. If you wish to turn it off, [use the PTMSYSM service](https://libctru.devkitpro.org/ptmsysm_8h.html#ae3a437bfd0de05fbc5ba9a460d148430) to turn it off in your program.

+ 7 - 0
include/SDL_config.h.cmake

@@ -325,6 +325,7 @@
 #cmakedefine SDL_AUDIO_DRIVER_VITA @SDL_AUDIO_DRIVER_VITA@
 #cmakedefine SDL_AUDIO_DRIVER_PSP @SDL_AUDIO_DRIVER_PSP@
 #cmakedefine SDL_AUDIO_DRIVER_PS2 @SDL_AUDIO_DRIVER_PS2@
+#cmakedefine SDL_AUDIO_DRIVER_N3DS @SDL_AUDIO_DRIVER_N3DS@
 
 /* Enable various input drivers */
 #cmakedefine SDL_INPUT_LINUXEV @SDL_INPUT_LINUXEV@
@@ -349,6 +350,7 @@
 #cmakedefine SDL_JOYSTICK_VITA @SDL_JOYSTICK_VITA@
 #cmakedefine SDL_JOYSTICK_PSP @SDL_JOYSTICK_PSP@
 #cmakedefine SDL_JOYSTICK_PS2 @SDL_JOYSTICK_PS2@
+#cmakedefine SDL_JOYSTICK_N3DS @SDL_JOYSTICK_N3DS@
 #cmakedefine SDL_HAPTIC_DUMMY @SDL_HAPTIC_DUMMY@
 #cmakedefine SDL_HAPTIC_LINUX @SDL_HAPTIC_LINUX@
 #cmakedefine SDL_HAPTIC_IOKIT @SDL_HAPTIC_IOKIT@
@@ -381,6 +383,7 @@
 #cmakedefine SDL_THREAD_VITA @SDL_THREAD_VITA@
 #cmakedefine SDL_THREAD_PSP @SDL_THREAD_PSP@
 #cmakedefine SDL_THREAD_PS2 @SDL_THREAD_PS2@
+#cmakedefine SDL_THREAD_N3DS @SDL_THREAD_N3DS@
 
 /* Enable various timer systems */
 #cmakedefine SDL_TIMER_HAIKU @SDL_TIMER_HAIKU@
@@ -391,6 +394,7 @@
 #cmakedefine SDL_TIMER_VITA @SDL_TIMER_VITA@
 #cmakedefine SDL_TIMER_PSP @SDL_TIMER_PSP@
 #cmakedefine SDL_TIMER_PS2 @SDL_TIMER_PS2@
+#cmakedefine SDL_TIMER_N3DS @SDL_TIMER_N3DS@
 
 /* Enable various video drivers */
 #cmakedefine SDL_VIDEO_DRIVER_ANDROID @SDL_VIDEO_DRIVER_ANDROID@
@@ -444,6 +448,7 @@
 #cmakedefine SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS @SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS@
 #cmakedefine SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM @SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM@
 #cmakedefine SDL_VIDEO_DRIVER_VITA @SDL_VIDEO_DRIVER_VITA@
+#cmakedefine SDL_VIDEO_DRIVER_N3DS @SDL_VIDEO_DRIVER_N3DS@
 
 #cmakedefine SDL_VIDEO_RENDER_D3D @SDL_VIDEO_RENDER_D3D@
 #cmakedefine SDL_VIDEO_RENDER_D3D11 @SDL_VIDEO_RENDER_D3D11@
@@ -487,6 +492,7 @@
 #cmakedefine SDL_POWER_HARDWIRED @SDL_POWER_HARDWIRED@
 #cmakedefine SDL_POWER_VITA @SDL_POWER_VITA@
 #cmakedefine SDL_POWER_PSP @SDL_POWER_PSP@
+#cmakedefine SDL_POWER_N3DS @SDL_POWER_N3DS@
 
 /* Enable system filesystem support */
 #cmakedefine SDL_FILESYSTEM_ANDROID @SDL_FILESYSTEM_ANDROID@
@@ -501,6 +507,7 @@
 #cmakedefine SDL_FILESYSTEM_VITA @SDL_FILESYSTEM_VITA@
 #cmakedefine SDL_FILESYSTEM_PSP @SDL_FILESYSTEM_PSP@
 #cmakedefine SDL_FILESYSTEM_PS2 @SDL_FILESYSTEM_PS2@
+#cmakedefine SDL_FILESYSTEM_N3DS @SDL_FILESYSTEM_N3DS@
 
 /* Enable misc subsystem */
 #cmakedefine SDL_MISC_DUMMY @SDL_MISC_DUMMY@

+ 9 - 0
include/SDL_main.h

@@ -108,6 +108,15 @@
    void reset_IOP(); \
    void reset_IOP() {}
 
+#elif defined(__3DS__)
+/*
+  On N3DS, SDL provides a main function that sets up the screens
+  and storage.
+
+  If you provide this yourself, you may define SDL_MAIN_HANDLED
+*/
+#define SDL_MAIN_AVAILABLE
+
 #endif
 #endif /* SDL_MAIN_HANDLED */
 

+ 5 - 0
include/SDL_platform.h

@@ -221,6 +221,11 @@
 #define __VITA__ 1
 #endif
 
+#if defined(__3DS__)
+#undef __3DS__
+#define __3DS__ 1
+#endif
+
 #include "begin_code.h"
 /* Set up for C function definitions, even when using C++ */
 #ifdef __cplusplus

+ 1 - 1
include/SDL_stdinc.h

@@ -410,7 +410,7 @@ SDL_COMPILE_TIME_ASSERT(sint64, sizeof(Sint64) == 8);
 
 /** \cond */
 #ifndef DOXYGEN_SHOULD_IGNORE_THIS
-#if !defined(__ANDROID__) && !defined(__VITA__)
+#if !defined(__ANDROID__) && !defined(__VITA__) && !defined(__3DS__)
    /* TODO: include/SDL_stdinc.h:174: error: size of array 'SDL_dummy_enum' is negative */
 typedef enum
 {

+ 1 - 0
include/SDL_video.h

@@ -126,6 +126,7 @@ typedef enum
     SDL_WINDOW_KEYBOARD_GRABBED = 0x00100000,   /**< window has grabbed keyboard input */
     SDL_WINDOW_VULKAN           = 0x10000000,   /**< window usable for Vulkan surface */
     SDL_WINDOW_METAL            = 0x20000000,   /**< window usable for Metal view */
+    SDL_WINDOW_N3DS_BOTTOM      = 0x40000000,   /**< window should be on the bottom screen (N3DS only) */
 
     SDL_WINDOW_INPUT_GRABBED = SDL_WINDOW_MOUSE_GRABBED /**< equivalent to SDL_WINDOW_MOUSE_GRABBED for compatibility */
 } SDL_WindowFlags;

+ 2 - 0
src/SDL.c

@@ -609,6 +609,8 @@ SDL_GetPlatform(void)
     return "PlayStation Vita";
 #elif __NGAGE__
     return "Nokia N-Gage";
+#elif __3DS__
+    return "Nintendo 3DS";
 #else
     return "Unknown (see SDL_platform.h)";
 #endif

+ 7 - 0
src/SDL_log.c

@@ -485,6 +485,13 @@ SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority,
         fprintf(pFile, "%s: %s\n", SDL_priority_prefixes[priority], message);
         fclose (pFile);
     }
+#elif defined(__3DS__)
+    {
+        FILE*        pFile;
+        pFile = fopen ("/SDL_Log.txt", "a");
+        fprintf(pFile, "%s: %s\n", SDL_priority_prefixes[priority], message);
+        fclose (pFile);
+    }
 #endif
 #if HAVE_STDIO_H && \
     !(defined(__APPLE__) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT)))

+ 3 - 0
src/audio/SDL_audio.c

@@ -105,6 +105,9 @@ static const AudioBootStrap *const bootstrap[] = {
 #if SDL_AUDIO_DRIVER_VITA
     &VITAAUD_bootstrap,
 #endif
+#if SDL_AUDIO_DRIVER_N3DS
+    &N3DSAUDIO_bootstrap,
+#endif
 #if SDL_AUDIO_DRIVER_EMSCRIPTEN
     &EMSCRIPTENAUDIO_bootstrap,
 #endif

+ 1 - 0
src/audio/SDL_sysaudio.h

@@ -209,6 +209,7 @@ extern AudioBootStrap ANDROIDAUDIO_bootstrap;
 extern AudioBootStrap PS2AUDIO_bootstrap;
 extern AudioBootStrap PSPAUDIO_bootstrap;
 extern AudioBootStrap VITAAUD_bootstrap;
+extern AudioBootStrap N3DSAUDIO_bootstrap;
 extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap;
 extern AudioBootStrap OS2AUDIO_bootstrap;
 

+ 363 - 0
src/audio/n3ds/SDL_n3dsaudio.c

@@ -0,0 +1,363 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_N3DS
+
+#include "SDL_audio.h"
+
+/* N3DS Audio driver */
+
+#include "../SDL_sysaudio.h"
+#include "SDL_n3dsaudio.h"
+#include "SDL_timer.h"
+
+#define N3DSAUDIO_DRIVER_NAME "n3ds"
+
+static dspHookCookie dsp_hook;
+static SDL_AudioDevice *audio_device;
+
+static void FreePrivateData(_THIS);
+static int FindAudioFormat(_THIS);
+
+static SDL_INLINE void
+contextLock(_THIS)
+{
+    LightLock_Lock(&this->hidden->lock);
+}
+
+static SDL_INLINE void
+contextUnlock(_THIS)
+{
+    LightLock_Unlock(&this->hidden->lock);
+}
+
+static void
+N3DSAUD_LockAudio(_THIS)
+{
+    contextLock(this);
+}
+
+static void
+N3DSAUD_UnlockAudio(_THIS)
+{
+    contextUnlock(this);
+}
+
+static void
+N3DSAUD_DspHook(DSP_HookType hook)
+{
+    if (hook == DSPHOOK_ONCANCEL) {
+        contextLock(audio_device);
+        audio_device->hidden->isCancelled = SDL_TRUE;
+        SDL_AtomicSet(&audio_device->enabled, SDL_FALSE);
+        CondVar_Broadcast(&audio_device->hidden->cv);
+        contextUnlock(audio_device);
+    }
+}
+
+static void
+AudioFrameFinished(void *device)
+{
+    bool shouldBroadcast = false;
+    unsigned i;
+    SDL_AudioDevice *this = (SDL_AudioDevice *) device;
+
+    contextLock(this);
+
+    for (i = 0; i < NUM_BUFFERS; i++) {
+        if (this->hidden->waveBuf[i].status == NDSP_WBUF_DONE) {
+            this->hidden->waveBuf[i].status = NDSP_WBUF_FREE;
+            shouldBroadcast = SDL_TRUE;
+        }
+    }
+
+    if (shouldBroadcast) {
+        CondVar_Broadcast(&this->hidden->cv);
+    }
+
+    contextUnlock(this);
+}
+
+static int
+N3DSAUDIO_OpenDevice(_THIS, const char *devname)
+{
+    Result ndsp_init_res;
+    Uint8 *data_vaddr;
+    float mix[12];
+    this->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof *this->hidden);
+
+    if (this->hidden == NULL) {
+        return SDL_OutOfMemory();
+    }
+
+    /* Initialise the DSP service */
+    ndsp_init_res = ndspInit();
+    if (R_FAILED(ndsp_init_res)) {
+        if ((R_SUMMARY(ndsp_init_res) == RS_NOTFOUND) && (R_MODULE(ndsp_init_res) == RM_DSP)) {
+            SDL_SetError("DSP init failed: dspfirm.cdc missing!");
+        } else {
+            SDL_SetError("DSP init failed. Error code: 0x%lX", ndsp_init_res);
+        }
+        return -1;
+    }
+
+    /* Initialise internal state */
+    LightLock_Init(&this->hidden->lock);
+    CondVar_Init(&this->hidden->cv);
+
+    if (this->spec.channels > 2) {
+        this->spec.channels = 2;
+    }
+
+    /* Should not happen but better be safe. */
+    if (FindAudioFormat(this) < 0) {
+        return SDL_SetError("No supported audio format found.");
+    }
+
+    /* Update the fragment size as size in bytes */
+    SDL_CalculateAudioSpec(&this->spec);
+
+    /* Allocate mixing buffer */
+    if (this->spec.size >= SDL_MAX_UINT32 / 2) {
+        return SDL_SetError("Mixing buffer is too large.");
+    }
+
+    this->hidden->mixlen = this->spec.size;
+    this->hidden->mixbuf = (Uint8 *) SDL_malloc(this->spec.size);
+    if (this->hidden->mixbuf == NULL) {
+        return SDL_OutOfMemory();
+    }
+
+    SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
+
+    data_vaddr = (Uint8 *) linearAlloc(this->hidden->mixlen * NUM_BUFFERS);
+    if (data_vaddr == NULL) {
+        return SDL_OutOfMemory();
+    }
+
+    SDL_memset(data_vaddr, 0, this->hidden->mixlen * NUM_BUFFERS);
+    DSP_FlushDataCache(data_vaddr, this->hidden->mixlen * NUM_BUFFERS);
+
+    this->hidden->nextbuf = 0;
+    this->hidden->channels = this->spec.channels;
+    this->hidden->samplerate = this->spec.freq;
+
+    ndspChnReset(0);
+
+    ndspChnSetInterp(0, NDSP_INTERP_LINEAR);
+    ndspChnSetRate(0, this->spec.freq);
+    ndspChnSetFormat(0, this->hidden->format);
+
+    SDL_memset(mix, 0, sizeof(mix));
+    mix[0] = 1.0;
+    mix[1] = 1.0;
+    ndspChnSetMix(0, mix);
+
+    SDL_memset(this->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS);
+
+    for (unsigned i = 0; i < NUM_BUFFERS; i++) {
+        this->hidden->waveBuf[i].data_vaddr = data_vaddr;
+        this->hidden->waveBuf[i].nsamples = this->hidden->mixlen / this->hidden->bytePerSample;
+        data_vaddr += this->hidden->mixlen;
+    }
+
+    /* Setup callback */
+    audio_device = this;
+    ndspSetCallback(AudioFrameFinished, this);
+    dspHook(&dsp_hook, N3DSAUD_DspHook);
+
+    return 0;
+}
+
+static int
+N3DSAUDIO_CaptureFromDevice(_THIS, void *buffer, int buflen)
+{
+    /* Delay to make this sort of simulate real audio input. */
+    SDL_Delay((this->spec.samples * 1000) / this->spec.freq);
+
+    /* always return a full buffer of silence. */
+    SDL_memset(buffer, this->spec.silence, buflen);
+    return buflen;
+}
+
+static void
+N3DSAUDIO_PlayDevice(_THIS)
+{
+    size_t nextbuf;
+    size_t sampleLen;
+    contextLock(this);
+
+    nextbuf = this->hidden->nextbuf;
+    sampleLen = this->hidden->mixlen;
+
+    if (this->hidden->isCancelled ||
+        this->hidden->waveBuf[nextbuf].status != NDSP_WBUF_FREE) {
+        contextUnlock(this);
+        return;
+    }
+
+    this->hidden->nextbuf = (nextbuf + 1) % NUM_BUFFERS;
+
+    contextUnlock(this);
+
+    memcpy((void *) this->hidden->waveBuf[nextbuf].data_vaddr,
+           this->hidden->mixbuf, sampleLen);
+    DSP_FlushDataCache(this->hidden->waveBuf[nextbuf].data_vaddr, sampleLen);
+
+    ndspChnWaveBufAdd(0, &this->hidden->waveBuf[nextbuf]);
+}
+
+static void
+N3DSAUDIO_WaitDevice(_THIS)
+{
+    contextLock(this);
+    while (!this->hidden->isCancelled &&
+           this->hidden->waveBuf[this->hidden->nextbuf].status != NDSP_WBUF_FREE) {
+        CondVar_Wait(&this->hidden->cv, &this->hidden->lock);
+    }
+    contextUnlock(this);
+}
+
+static Uint8 *
+N3DSAUDIO_GetDeviceBuf(_THIS)
+{
+    return this->hidden->mixbuf;
+}
+
+static void
+N3DSAUDIO_CloseDevice(_THIS)
+{
+    contextLock(this);
+
+    dspUnhook(&dsp_hook);
+    ndspSetCallback(NULL, NULL);
+
+    if (!this->hidden->isCancelled) {
+        ndspChnReset(0);
+        memset(this->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS);
+        CondVar_Broadcast(&this->hidden->cv);
+    }
+
+    contextUnlock(this);
+
+    ndspExit();
+
+    FreePrivateData(this);
+}
+
+static void
+N3DSAUDIO_ThreadInit(_THIS)
+{
+    s32 current_priority;
+    svcGetThreadPriority(&current_priority, CUR_THREAD_HANDLE);
+    current_priority--;
+    /* 0x18 is reserved for video, 0x30 is the default for main thread */
+    current_priority = SDL_clamp(current_priority, 0x19, 0x2F);
+    svcSetThreadPriority(CUR_THREAD_HANDLE, current_priority);
+}
+
+static SDL_bool
+N3DSAUDIO_Init(SDL_AudioDriverImpl *impl)
+{
+    /* Set the function pointers */
+    impl->OpenDevice = N3DSAUDIO_OpenDevice;
+    impl->PlayDevice = N3DSAUDIO_PlayDevice;
+    impl->WaitDevice = N3DSAUDIO_WaitDevice;
+    impl->GetDeviceBuf = N3DSAUDIO_GetDeviceBuf;
+    impl->CloseDevice = N3DSAUDIO_CloseDevice;
+    impl->ThreadInit = N3DSAUDIO_ThreadInit;
+    impl->LockDevice = N3DSAUD_LockAudio;
+    impl->UnlockDevice = N3DSAUD_UnlockAudio;
+    impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
+
+    /* Should be possible, but micInit would fail */
+    impl->HasCaptureSupport = SDL_FALSE;
+    impl->CaptureFromDevice = N3DSAUDIO_CaptureFromDevice;
+
+    return SDL_TRUE; /* this audio target is available. */
+}
+
+AudioBootStrap N3DSAUDIO_bootstrap = {
+    N3DSAUDIO_DRIVER_NAME,
+    "SDL N3DS audio driver",
+    N3DSAUDIO_Init,
+    0
+};
+
+/**
+ * Cleans up all allocated memory, safe to call with null pointers
+ */
+static void
+FreePrivateData(_THIS)
+{
+    if (!this->hidden) {
+        return;
+    }
+
+    if (this->hidden->waveBuf[0].data_vaddr) {
+        linearFree((void *) this->hidden->waveBuf[0].data_vaddr);
+    }
+
+    if (this->hidden->mixbuf) {
+        SDL_free(this->hidden->mixbuf);
+        this->hidden->mixbuf = NULL;
+    }
+
+    SDL_free(this->hidden);
+    this->hidden = NULL;
+}
+
+static int
+FindAudioFormat(_THIS)
+{
+    SDL_bool found_valid_format = SDL_FALSE;
+    Uint16 test_format = SDL_FirstAudioFormat(this->spec.format);
+
+    while (!found_valid_format && test_format) {
+        this->spec.format = test_format;
+        switch (test_format) {
+        case AUDIO_S8:
+            /* Signed 8-bit audio supported */
+            this->hidden->format = (this->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM8 : NDSP_FORMAT_MONO_PCM8;
+            this->hidden->isSigned = 1;
+            this->hidden->bytePerSample = this->spec.channels;
+            found_valid_format = SDL_TRUE;
+            break;
+        case AUDIO_S16:
+            /* Signed 16-bit audio supported */
+            this->hidden->format = (this->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM16 : NDSP_FORMAT_MONO_PCM16;
+            this->hidden->isSigned = 1;
+            this->hidden->bytePerSample = this->spec.channels * 2;
+            found_valid_format = SDL_TRUE;
+            break;
+        default:
+            test_format = SDL_NextAudioFormat();
+            break;
+        }
+    }
+
+    return found_valid_format ? 0 : -1;
+}
+
+#endif /* SDL_AUDIO_DRIVER_N3DS */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 50 - 0
src/audio/n3ds/SDL_n3dsaudio.h

@@ -0,0 +1,50 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_n3dsaudio_h_
+#define _SDL_n3dsaudio_h_
+
+#include <3ds.h>
+
+/* Hidden "this" pointer for the audio functions */
+#define _THIS SDL_AudioDevice *this
+
+#define NUM_BUFFERS 2 /* -- Don't lower this! */
+
+struct SDL_PrivateAudioData
+{
+    /* Speaker data */
+    Uint8 *mixbuf;
+    Uint32 mixlen;
+    Uint32 format;
+    Uint32 samplerate;
+    Uint32 channels;
+    Uint8 bytePerSample;
+    Uint32 isSigned;
+    Uint32 nextbuf;
+    ndspWaveBuf waveBuf[NUM_BUFFERS];
+    LightLock lock;
+    CondVar cv;
+    SDL_bool isCancelled;
+};
+
+#endif /* _SDL_n3dsaudio_h_ */
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 2 - 0
src/cpuinfo/SDL_cpuinfo.c

@@ -468,6 +468,8 @@ CPU_haveNEON(void)
     return 1;  /* ARMv8 always has non-optional NEON support. */
 #elif __VITA__
     return 1;
+#elif __3DS__
+    return 1;
 #elif defined(__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. */

+ 2 - 0
src/dynapi/SDL_dynapi.h

@@ -63,6 +63,8 @@
 #define SDL_DYNAMIC_API 0  /* vitasdk doesn't support dynamic linking */
 #elif defined(__NGAGE__)
 #define SDL_DYNAMIC_API 0  /* The N-Gage doesn't support dynamic linking either */
+#elif defined(__3DS__)
+#define SDL_DYNAMIC_API 0  /* devkitARM doesn't support dynamic linking */
 #elif defined(DYNAPI_NEEDS_DLOPEN) && !defined(HAVE_DLOPEN)
 #define SDL_DYNAMIC_API 0  /* we need dlopen(), but don't have it.... */
 #endif

+ 6 - 0
src/file/SDL_rwops.c

@@ -53,6 +53,10 @@
 #include "cocoa/SDL_rwopsbundlesupport.h"
 #endif /* __APPLE__ */
 
+#ifdef __3DS__
+#include "n3ds/SDL_rwopsromfs.h"
+#endif /* __3DS__ */
+
 #ifdef __ANDROID__
 #include "../core/android/SDL_android.h"
 #include "SDL_system.h"
@@ -601,6 +605,8 @@ SDL_RWFromFile(const char *file, const char *mode)
         #elif __WINRT__
         FILE *fp = NULL;
         fopen_s(&fp, file, mode);
+        #elif __3DS__
+        FILE *fp = N3DS_FileOpen(file, mode);
         #else
         FILE *fp = fopen(file, mode);
         #endif

+ 56 - 0
src/file/n3ds/SDL_rwopsromfs.c

@@ -0,0 +1,56 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_rwopsromfs.h"
+
+/* Nintendo 3DS applications may embed resources in the executable. The
+  resources are stored in a special read-only partition prefixed with
+  'romfs:/'. As such, when opening a file, we should first try the romfs
+  unless sdmc is specifically mentionned.
+*/
+FILE *
+N3DS_FileOpen(const char *file, const char *mode)
+{
+    FILE *fp = NULL;
+    char romfs_path[4096];
+
+    /* romfs are read-only */
+    if (SDL_strchr(mode, 'r') == NULL) {
+        return fopen(file, mode);
+    }
+
+    /* If the path has an explicit prefix, we skip the guess work */
+    if (SDL_strncmp("romfs:/", file, 7) == 0 ||
+        SDL_strncmp("sdmc:/", file, 6) == 0) {
+        return fopen(file, mode);
+    }
+
+    SDL_snprintf(romfs_path, 4096, "romfs:/%s", file);
+
+    fp = fopen(romfs_path, mode);
+    if (fp == NULL) {
+        fp = fopen(file, mode);
+    }
+
+    return fp;
+}
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 30 - 0
src/file/n3ds/SDL_rwopsromfs.h

@@ -0,0 +1,30 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_rwopsromfs_h_
+#define SDL_rwopsromfs_h_
+
+FILE *N3DS_FileOpen(const char *file, const char *mode);
+
+#endif /* SDL_rwopsromfs_h_ */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 86 - 0
src/filesystem/n3ds/SDL_sysfilesystem.c

@@ -0,0 +1,86 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_FILESYSTEM_N3DS
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+/* System dependent filesystem routines                                */
+
+#include <3ds.h>
+#include <dirent.h>
+#include <errno.h>
+
+#include "SDL_error.h"
+#include "SDL_filesystem.h"
+
+SDL_FORCE_INLINE char *MakePrefPath(const char *app);
+SDL_FORCE_INLINE int CreatePrefPathDir(const char *pref);
+
+char *
+SDL_GetBasePath(void)
+{
+    char *base_path = SDL_strdup("romfs:/");
+    return base_path;
+}
+
+char *
+SDL_GetPrefPath(const char *org, const char *app)
+{
+    char *pref_path = NULL;
+    if (app == NULL) {
+        SDL_InvalidParamError("app");
+        return NULL;
+    }
+
+    pref_path = MakePrefPath(app);
+    if (CreatePrefPathDir(pref_path) < 0) {
+        SDL_free(pref_path);
+        return NULL;
+    }
+
+    return pref_path;
+}
+
+SDL_FORCE_INLINE char *
+MakePrefPath(const char *app)
+{
+    static const char *FMT = "/3ds/%s/";
+    size_t length = SDL_snprintf(NULL, 0, FMT, app) + 1;
+    char *pref_path = (char *) SDL_calloc(length, sizeof(char));
+    SDL_snprintf(pref_path, length, FMT, app);
+    return pref_path;
+}
+
+SDL_FORCE_INLINE int
+CreatePrefPathDir(const char *pref)
+{
+    int result = mkdir(pref, 0666);
+
+    if (result == -1 && errno != EEXIST) {
+        return SDL_SetError("Failed to create '%s' (%s)", pref, strerror(errno));
+    }
+    return 0;
+}
+
+#endif /* SDL_FILESYSTEM_N3DS */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 3 - 0
src/joystick/SDL_gamecontrollerdb.h

@@ -977,6 +977,9 @@ static const char *s_ControllerMappings [] =
 #endif
 #if SDL_JOYSTICK_VITA
     "0000000050535669746120436f6e7400,PSVita Controller,crc:d598,a:b2,b:b1,back:b10,dpdown:b6,dpleft:b7,dpright:b9,dpup:b8,leftshoulder:b4,leftstick:b14,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,",
+#endif
+#if SDL_JOYSTICK_N3DS
+    "4e696e74656e646f20334453206d6170,Nintendo 3DS,a:b0,b:b1,back:b2,dpdown:b7,dpleft:b5,dpright:b4,dpup:b6,leftshoulder:b9,leftstick:b14,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b15,rightx:a2,righty:a3,start:b3,x:b10,y:b11,",
 #endif
     "hidapi,*,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
     NULL

+ 3 - 0
src/joystick/SDL_joystick.c

@@ -102,6 +102,9 @@ static SDL_JoystickDriver *SDL_joystick_drivers[] = {
 #ifdef SDL_JOYSTICK_VITA
     &SDL_VITA_JoystickDriver,
 #endif
+#ifdef SDL_JOYSTICK_N3DS
+    &SDL_N3DS_JoystickDriver
+#endif
 #if defined(SDL_JOYSTICK_DUMMY) || defined(SDL_JOYSTICK_DISABLED)
     &SDL_DUMMY_JoystickDriver
 #endif

+ 1 - 0
src/joystick/SDL_sysjoystick.h

@@ -238,6 +238,7 @@ extern SDL_JoystickDriver SDL_OS2_JoystickDriver;
 extern SDL_JoystickDriver SDL_PS2_JoystickDriver;
 extern SDL_JoystickDriver SDL_PSP_JoystickDriver;
 extern SDL_JoystickDriver SDL_VITA_JoystickDriver;
+extern SDL_JoystickDriver SDL_N3DS_JoystickDriver;
 
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus

+ 367 - 0
src/joystick/n3ds/SDL_sysjoystick.c

@@ -0,0 +1,367 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "../../SDL_internal.h"
+
+#ifdef SDL_JOYSTICK_N3DS
+
+/* This is the N3DS implementation of the SDL joystick API */
+
+#include <3ds.h>
+
+#include "../SDL_sysjoystick.h"
+#include "SDL_events.h"
+
+#define NB_BUTTONS 23
+
+/*
+  N3DS sticks values are roughly within +/-160
+  which is too small to pass the jitter tolerance.
+  This correction factor is applied to axis values
+  so they fit better in SDL's value range.
+*/
+#define CORRECTION_FACTOR_X SDL_JOYSTICK_AXIS_MAX / 160
+
+/*
+  The Y axis needs to be flipped because SDL's "up"
+  is reversed compared to libctru's "up"
+*/
+#define CORRECTION_FACTOR_Y -CORRECTION_FACTOR_X
+
+/*
+  Factors used to convert touchscreen coordinates to
+  SDL's 0-1 values. Note that the N3DS's screen is
+  internally in a portrait disposition so the
+  GSP_SCREEN constants are flipped.
+*/
+#define TOUCHPAD_SCALE_X 1.0f / GSP_SCREEN_HEIGHT_BOTTOM
+#define TOUCHPAD_SCALE_Y 1.0f / GSP_SCREEN_WIDTH
+
+typedef struct N3DSJoystickState
+{
+    u32 kDown;
+    u32 kUp;
+    circlePosition circlePos;
+    circlePosition cStickPos;
+    accelVector acceleration;
+    angularRate rate;
+} N3DSJoystickState;
+
+SDL_FORCE_INLINE void UpdateAxis(SDL_Joystick *joystick, N3DSJoystickState *previous_state);
+SDL_FORCE_INLINE void UpdateButtons(SDL_Joystick *joystick, N3DSJoystickState *previous_state);
+SDL_FORCE_INLINE void UpdateSensors(SDL_Joystick *joystick, N3DSJoystickState *previous_state);
+SDL_FORCE_INLINE void UpdateTouch(SDL_Joystick *joystick);
+
+static N3DSJoystickState current_state;
+static SDL_bool sensors_enabled = SDL_FALSE;
+
+static int
+N3DS_JoystickInit(void)
+{
+    hidInit();
+    HIDUSER_EnableAccelerometer();
+    HIDUSER_EnableGyroscope();
+    return 0;
+}
+
+static const char *
+N3DS_JoystickGetDeviceName(int device_index)
+{
+    return "Nintendo 3DS";
+}
+
+static int
+N3DS_JoystickGetCount(void)
+{
+    return 1;
+}
+
+static SDL_JoystickGUID
+N3DS_JoystickGetDeviceGUID(int device_index)
+{
+    /* GUID corresponds to the name "Nintendo 3DS map" */
+    SDL_JoystickGUID guid = { { 0x4e, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x6f, 0x20, 0x33, 0x44, 0x53, 0x20, 0x6d, 0x61, 0x70 } };
+    return guid;
+}
+
+static SDL_JoystickID
+N3DS_JoystickGetDeviceInstanceID(int device_index)
+{
+    return device_index;
+}
+
+static int
+N3DS_JoystickOpen(SDL_Joystick *joystick, int device_index)
+{
+    joystick->nbuttons = NB_BUTTONS;
+    joystick->naxes = 4;
+    joystick->nhats = 0;
+    joystick->instance_id = device_index;
+
+    SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 0.0f);
+    SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 0.0f);
+    SDL_PrivateJoystickAddTouchpad(joystick, 1);
+
+    return 0;
+}
+
+static int
+N3DS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
+{
+    sensors_enabled = enabled;
+    return 0;
+}
+
+static void
+N3DS_JoystickUpdate(SDL_Joystick *joystick)
+{
+    N3DSJoystickState previous_state = current_state;
+    hidScanInput();
+
+    UpdateAxis(joystick, &previous_state);
+    UpdateButtons(joystick, &previous_state);
+    UpdateTouch(joystick);
+
+    if (sensors_enabled) {
+        UpdateSensors(joystick, &previous_state);
+    }
+}
+
+SDL_FORCE_INLINE void
+UpdateAxis(SDL_Joystick *joystick, N3DSJoystickState *previous_state)
+{
+    hidCircleRead(&current_state.circlePos);
+    if (previous_state->circlePos.dx != current_state.circlePos.dx) {
+        SDL_PrivateJoystickAxis(joystick,
+                                0,
+                                current_state.circlePos.dx * CORRECTION_FACTOR_X);
+    }
+    if (previous_state->circlePos.dy != current_state.circlePos.dy) {
+        SDL_PrivateJoystickAxis(joystick,
+                                1,
+                                current_state.circlePos.dy * CORRECTION_FACTOR_Y);
+    }
+
+    hidCstickRead(&current_state.cStickPos);
+    if (previous_state->cStickPos.dx != current_state.cStickPos.dx) {
+        SDL_PrivateJoystickAxis(joystick,
+                                2,
+                                current_state.cStickPos.dx * CORRECTION_FACTOR_X);
+    }
+    if (previous_state->cStickPos.dy != current_state.cStickPos.dy) {
+        SDL_PrivateJoystickAxis(joystick,
+                                3,
+                                current_state.cStickPos.dy * CORRECTION_FACTOR_Y);
+    }
+}
+
+SDL_FORCE_INLINE void
+UpdateButtons(SDL_Joystick *joystick, N3DSJoystickState *previous_state)
+{
+    u32 updated_down, updated_up;
+
+    current_state.kDown = hidKeysDown();
+    updated_down = previous_state->kDown ^ current_state.kDown;
+    if (updated_down) {
+        for (Uint8 i = 0; i < joystick->nbuttons; i++) {
+            if (current_state.kDown & BIT(i) & updated_down) {
+                SDL_PrivateJoystickButton(joystick, i, SDL_PRESSED);
+            }
+        }
+    }
+
+    current_state.kUp = hidKeysUp();
+    updated_up = previous_state->kUp ^ current_state.kUp;
+    if (updated_up) {
+        for (Uint8 i = 0; i < joystick->nbuttons; i++) {
+            if (current_state.kUp & BIT(i) & updated_up) {
+                SDL_PrivateJoystickButton(joystick, i, SDL_RELEASED);
+            }
+        }
+    }
+}
+
+SDL_FORCE_INLINE void
+UpdateTouch(SDL_Joystick *joystick)
+{
+    touchPosition touch;
+    Uint8 state;
+    hidTouchRead(&touch);
+    state = (touch.px == 0 && touch.py == 0) ? SDL_RELEASED : SDL_PRESSED;
+
+    SDL_PrivateJoystickTouchpad(joystick,
+                                0,
+                                0,
+                                state,
+                                touch.px * TOUCHPAD_SCALE_X,
+                                touch.py * TOUCHPAD_SCALE_Y,
+                                state == SDL_PRESSED ? 1.0f : 0.0f);
+}
+
+SDL_FORCE_INLINE void
+UpdateSensors(SDL_Joystick *joystick, N3DSJoystickState *previous_state)
+{
+    float data[3];
+
+    hidAccelRead(&current_state.acceleration);
+    if (SDL_memcmp(&previous_state->acceleration, &current_state.acceleration, sizeof(accelVector)) != 0) {
+        SDL_memcpy(&previous_state->acceleration, &current_state.acceleration, sizeof(accelVector));
+        data[0] = (float) current_state.acceleration.x * SDL_STANDARD_GRAVITY;
+        data[1] = (float) current_state.acceleration.y * SDL_STANDARD_GRAVITY;
+        data[2] = (float) current_state.acceleration.z * SDL_STANDARD_GRAVITY;
+        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, data, sizeof data);
+    }
+
+    hidGyroRead(&current_state.rate);
+    if (SDL_memcmp(&previous_state->rate, &current_state.rate, sizeof(angularRate)) != 0) {
+        SDL_memcpy(&previous_state->rate, &current_state.rate, sizeof(angularRate));
+        data[0] = (float) current_state.rate.y;
+        data[1] = (float) current_state.rate.z;
+        data[2] = (float) current_state.rate.x;
+        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, data, sizeof data);
+    }
+}
+
+static void
+N3DS_JoystickClose(SDL_Joystick *joystick)
+{
+}
+
+static void
+N3DS_JoystickQuit(void)
+{
+    HIDUSER_DisableGyroscope();
+    HIDUSER_DisableAccelerometer();
+    hidExit();
+}
+
+static SDL_bool
+N3DS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
+{
+    /* There is only one possible mapping. */
+    *out = (SDL_GamepadMapping){
+        .a = { EMappingKind_Button, 0 },
+        .b = { EMappingKind_Button, 1 },
+        .x = { EMappingKind_Button, 10 },
+        .y = { EMappingKind_Button, 11 },
+        .back = { EMappingKind_Button, 2 },
+        .guide = { EMappingKind_None, 255 },
+        .start = { EMappingKind_Button, 3 },
+        .leftstick = { EMappingKind_None, 255 },
+        .rightstick = { EMappingKind_None, 255 },
+        .leftshoulder = { EMappingKind_Button, 9 },
+        .rightshoulder = { EMappingKind_Button, 8 },
+        .dpup = { EMappingKind_Button, 6 },
+        .dpdown = { EMappingKind_Button, 7 },
+        .dpleft = { EMappingKind_Button, 5 },
+        .dpright = { EMappingKind_Button, 4 },
+        .misc1 = { EMappingKind_None, 255 },
+        .paddle1 = { EMappingKind_None, 255 },
+        .paddle2 = { EMappingKind_None, 255 },
+        .paddle3 = { EMappingKind_None, 255 },
+        .paddle4 = { EMappingKind_None, 255 },
+        .leftx = { EMappingKind_Axis, 0 },
+        .lefty = { EMappingKind_Axis, 1 },
+        .rightx = { EMappingKind_Axis, 2 },
+        .righty = { EMappingKind_Axis, 3 },
+        .lefttrigger = { EMappingKind_Button, 14 },
+        .righttrigger = { EMappingKind_Button, 15 },
+    };
+    return SDL_TRUE;
+}
+
+static void
+N3DS_JoystickDetect(void)
+{
+}
+
+static const char *
+N3DS_JoystickGetDevicePath(int device_index)
+{
+    return NULL;
+}
+
+static int
+N3DS_JoystickGetDevicePlayerIndex(int device_index)
+{
+    return -1;
+}
+
+static void
+N3DS_JoystickSetDevicePlayerIndex(int device_index, int player_index)
+{
+}
+
+static Uint32
+N3DS_JoystickGetCapabilities(SDL_Joystick *joystick)
+{
+    return 0;
+}
+
+static int
+N3DS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+{
+    return SDL_Unsupported();
+}
+
+static int
+N3DS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
+{
+    return SDL_Unsupported();
+}
+
+static int
+N3DS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
+{
+    return SDL_Unsupported();
+}
+
+static int
+N3DS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
+SDL_JoystickDriver SDL_N3DS_JoystickDriver = {
+    N3DS_JoystickInit,
+    N3DS_JoystickGetCount,
+    N3DS_JoystickDetect,
+    N3DS_JoystickGetDeviceName,
+    N3DS_JoystickGetDevicePath,
+    N3DS_JoystickGetDevicePlayerIndex,
+    N3DS_JoystickSetDevicePlayerIndex,
+    N3DS_JoystickGetDeviceGUID,
+    N3DS_JoystickGetDeviceInstanceID,
+    N3DS_JoystickOpen,
+    N3DS_JoystickRumble,
+    N3DS_JoystickRumbleTriggers,
+    N3DS_JoystickGetCapabilities,
+    N3DS_JoystickSetLED,
+    N3DS_JoystickSendEffect,
+    N3DS_JoystickSetSensorsEnabled,
+    N3DS_JoystickUpdate,
+    N3DS_JoystickClose,
+    N3DS_JoystickQuit,
+    N3DS_JoystickGetGamepadMapping
+};
+
+#endif /* SDL_JOYSTICK_N3DS */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 1 - 1
src/libm/math_private.h

@@ -27,7 +27,7 @@
 #define libm_hidden_def(x)
 #define strong_alias(x, y)
 
-#if !defined(__HAIKU__) && !defined(__PSP__) && !defined(__PS2__) /* already defined in a system header. */
+#if !defined(__HAIKU__) && !defined(__PSP__) && !defined(__3DS__) && !defined(__PS2__) /* already defined in a system header. */
 typedef unsigned int u_int32_t;
 #endif
 

+ 59 - 0
src/locale/n3ds/SDL_syslocale.c

@@ -0,0 +1,59 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "../SDL_syslocale.h"
+#include "../../SDL_internal.h"
+
+#include <3ds.h>
+
+/* Used when the CFGU fails to work. */
+#define BAD_LOCALE 255
+
+SDL_FORCE_INLINE u8 GetLocaleIndex(void);
+
+void
+SDL_SYS_GetPreferredLocales(char *buf, size_t buflen)
+{
+    /* The 3DS only supports these 12 languages, only one can be active at a time */
+    static const char AVAILABLE_LOCALES[][6] = { "ja_JP", "en_US", "fr_FR", "de_DE",
+                                                 "it_IT", "es_ES", "zn_CN", "ko_KR",
+                                                 "nl_NL", "pt_PT", "ru_RU", "zh_TW" };
+    u8 current_locale = GetLocaleIndex();
+    if (current_locale != BAD_LOCALE) {
+        SDL_strlcpy(buf, AVAILABLE_LOCALES[current_locale], buflen);
+    }
+}
+
+SDL_FORCE_INLINE u8
+GetLocaleIndex(void)
+{
+    u8 current_locale;
+    if (R_FAILED(cfguInit())) {
+        return BAD_LOCALE;
+    }
+    if (R_FAILED(CFGU_GetSystemLanguage(&current_locale))) {
+        return BAD_LOCALE;
+    }
+    cfguExit();
+    return current_locale;
+}
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 82 - 0
src/main/n3ds/SDL_n3ds_main.c

@@ -0,0 +1,82 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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 __3DS__
+
+#include "SDL_main.h"
+#include <3ds.h>
+
+#ifdef main
+#undef main
+#endif
+
+SDL_FORCE_INLINE void N3DS_Init(void);
+SDL_FORCE_INLINE void N3DS_SetCPUSpeed(void);
+SDL_FORCE_INLINE void N3DS_Quit(void);
+
+#define HIGH_CLOCK 1
+#define L2_CACHE   2
+
+int
+main(int argc, char *argv[])
+{
+    int result;
+    N3DS_Init();
+    SDL_SetMainReady();
+    result = SDL_main(argc, argv);
+    N3DS_Quit();
+    return result;
+}
+
+SDL_FORCE_INLINE void
+N3DS_Init(void)
+{
+    N3DS_SetCPUSpeed();
+    romfsInit();
+    gfxInit(GSP_RGBA8_OES, GSP_RGBA8_OES, false);
+    hidInit();
+}
+
+/* If available, enable L2 cache and high CPU clock */
+SDL_FORCE_INLINE void
+N3DS_SetCPUSpeed(void)
+{
+    if (R_SUCCEEDED(ptmSysmInit())) {
+        if (R_SUCCEEDED(PTMSYSM_CheckNew3DS())) {
+            PTMSYSM_ConfigureNew3DSCPU(HIGH_CLOCK | L2_CACHE);
+        }
+        ptmSysmExit();
+    }
+}
+
+SDL_FORCE_INLINE void
+N3DS_Quit(void)
+{
+    hidExit();
+    gfxExit();
+    romfsExit();
+}
+
+#endif /* __3DS__ */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 3 - 0
src/power/SDL_power.c

@@ -71,6 +71,9 @@ static SDL_GetPowerInfo_Impl implementations[] = {
 #ifdef SDL_POWER_VITA        /* handles PSVita. */
     SDL_GetPowerInfo_VITA,
 #endif
+#ifdef SDL_POWER_N3DS        /* handles N3DS. */
+    SDL_GetPowerInfo_N3DS,
+#endif
 #ifdef SDL_POWER_WINRT          /* handles WinRT */
     SDL_GetPowerInfo_WinRT,
 #endif

+ 1 - 0
src/power/SDL_syspower.h

@@ -39,6 +39,7 @@ SDL_bool SDL_GetPowerInfo_Haiku(SDL_PowerState *, int *, int *);
 SDL_bool SDL_GetPowerInfo_Android(SDL_PowerState *, int *, int *);
 SDL_bool SDL_GetPowerInfo_PSP(SDL_PowerState *, int *, int *);
 SDL_bool SDL_GetPowerInfo_VITA(SDL_PowerState *, int *, int *);
+SDL_bool SDL_GetPowerInfo_N3DS(SDL_PowerState *, int *, int *);
 SDL_bool SDL_GetPowerInfo_WinRT(SDL_PowerState *, int *, int *);
 SDL_bool SDL_GetPowerInfo_Emscripten(SDL_PowerState *, int *, int *);
 

+ 111 - 0
src/power/n3ds/SDL_syspower.c

@@ -0,0 +1,111 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "../../SDL_internal.h"
+
+#if !defined(SDL_POWER_DISABLED) && defined(SDL_POWER_N3DS)
+
+#include <3ds.h>
+
+#include "SDL_error.h"
+#include "SDL_power.h"
+
+SDL_FORCE_INLINE SDL_PowerState GetPowerState(void);
+SDL_FORCE_INLINE int ReadStateFromPTMU(bool *is_plugged, u8 *is_charging);
+SDL_FORCE_INLINE int GetBatteryPercentage(void);
+
+#define BATTERY_PERCENT_REG      0xB
+#define BATTERY_PERCENT_REG_SIZE 2
+
+SDL_bool
+SDL_GetPowerInfo_N3DS(SDL_PowerState *state, int *seconds, int *percent)
+{
+    *state = GetPowerState();
+    *percent = GetBatteryPercentage();
+    *seconds = -1; /* libctru doesn't provide a way to estimate battery life */
+
+    return SDL_TRUE;
+}
+
+SDL_FORCE_INLINE SDL_PowerState
+GetPowerState(void)
+{
+    bool is_plugged;
+    u8 is_charging;
+
+    if (ReadStateFromPTMU(&is_plugged, &is_charging) < 0) {
+        return SDL_POWERSTATE_UNKNOWN;
+    }
+
+    if (is_charging) {
+        return SDL_POWERSTATE_CHARGING;
+    }
+
+    if (is_plugged) {
+        return SDL_POWERSTATE_CHARGED;
+    }
+
+    return SDL_POWERSTATE_ON_BATTERY;
+}
+
+SDL_FORCE_INLINE int
+ReadStateFromPTMU(bool *is_plugged, u8 *is_charging)
+{
+    if (R_FAILED(ptmuInit())) {
+        return SDL_SetError("Failed to initialise PTMU service");
+    }
+
+    if (R_FAILED(PTMU_GetAdapterState(is_plugged))) {
+        ptmuExit();
+        return SDL_SetError("Failed to read adapter state");
+    }
+
+    if (R_FAILED(PTMU_GetBatteryChargeState(is_charging))) {
+        ptmuExit();
+        return SDL_SetError("Failed to read battery charge state");
+    }
+
+    ptmuExit();
+    return 0;
+}
+
+SDL_FORCE_INLINE int
+GetBatteryPercentage(void)
+{
+    u8 data[BATTERY_PERCENT_REG_SIZE];
+
+    if (R_FAILED(mcuHwcInit())) {
+        return SDL_SetError("Failed to initialise mcuHwc service");
+    }
+
+    if (R_FAILED(MCUHWC_ReadRegister(BATTERY_PERCENT_REG, data, BATTERY_PERCENT_REG_SIZE))) {
+        mcuHwcExit();
+        return SDL_SetError("Failed to read battery register");
+    }
+
+    mcuHwcExit();
+
+    return (int) SDL_round(data[0] + data[1] / 256.0);
+}
+
+#endif /* !SDL_POWER_DISABLED && SDL_POWER_N3DS */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 2 - 0
src/thread/SDL_thread_c.h

@@ -38,6 +38,8 @@
 #include "psp/SDL_systhread_c.h"
 #elif SDL_THREAD_VITA
 #include "vita/SDL_systhread_c.h"
+#elif SDL_THREAD_N3DS
+#include "n3ds/SDL_systhread_c.h"
 #elif SDL_THREAD_STDCPP
 #include "stdcpp/SDL_systhread_c.h"
 #elif SDL_THREAD_OS2

+ 133 - 0
src/thread/n3ds/SDL_syscond.c

@@ -0,0 +1,133 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "../../SDL_internal.h"
+
+#ifdef SDL_THREAD_N3DS
+
+/* An implementation of condition variables using libctru's CondVar */
+
+#include "SDL_sysmutex_c.h"
+
+struct SDL_cond
+{
+    CondVar cond_variable;
+};
+
+/* Create a condition variable */
+SDL_cond *
+SDL_CreateCond(void)
+{
+    SDL_cond *cond = (SDL_cond *) SDL_malloc(sizeof(SDL_cond));
+    if (cond) {
+        CondVar_Init(&cond->cond_variable);
+    } else {
+        SDL_OutOfMemory();
+    }
+    return cond;
+}
+
+/* Destroy a condition variable */
+void
+SDL_DestroyCond(SDL_cond *cond)
+{
+    if (cond) {
+        SDL_free(cond);
+    }
+}
+
+/* Restart one of the threads that are waiting on the condition variable */
+int
+SDL_CondSignal(SDL_cond *cond)
+{
+    if (!cond) {
+        return SDL_SetError("Passed a NULL condition variable");
+    }
+
+    CondVar_Signal(&cond->cond_variable);
+    return 0;
+}
+
+/* Restart all threads that are waiting on the condition variable */
+int
+SDL_CondBroadcast(SDL_cond *cond)
+{
+    if (!cond) {
+        return SDL_SetError("Passed a NULL condition variable");
+    }
+
+    CondVar_Broadcast(&cond->cond_variable);
+    return 0;
+}
+
+/* Wait on the condition variable for at most 'ms' milliseconds.
+   The mutex must be locked before entering this function!
+   The mutex is unlocked during the wait, and locked again after the wait.
+
+Typical use:
+
+Thread A:
+    SDL_LockMutex(lock);
+    while ( ! condition ) {
+        SDL_CondWait(cond, lock);
+    }
+    SDL_UnlockMutex(lock);
+
+Thread B:
+    SDL_LockMutex(lock);
+    ...
+    condition = true;
+    ...
+    SDL_CondSignal(cond);
+    SDL_UnlockMutex(lock);
+ */
+int
+SDL_CondWaitTimeout(SDL_cond *cond, SDL_mutex *mutex, Uint32 ms)
+{
+    Result res;
+
+    if (!cond) {
+        return SDL_SetError("Passed a NULL condition variable");
+    }
+    if (!mutex) {
+        return SDL_SetError("Passed a NULL mutex");
+    }
+
+    res = 0;
+    if (ms == SDL_MUTEX_MAXWAIT) {
+        CondVar_Wait(&cond->cond_variable, &mutex->lock.lock);
+    } else {
+        res = CondVar_WaitTimeout(&cond->cond_variable, &mutex->lock.lock,
+                                  (s64) ms * 1000000LL);
+    }
+
+    return R_SUCCEEDED(res) ? 0 : SDL_MUTEX_TIMEDOUT;
+}
+
+/* Wait on the condition variable forever */
+int
+SDL_CondWait(SDL_cond *cond, SDL_mutex *mutex)
+{
+    return SDL_CondWaitTimeout(cond, mutex, SDL_MUTEX_MAXWAIT);
+}
+
+#endif /* SDL_THREAD_N3DS */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 93 - 0
src/thread/n3ds/SDL_sysmutex.c

@@ -0,0 +1,93 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "../../SDL_internal.h"
+
+#ifdef SDL_THREAD_N3DS
+
+/* An implementation of mutexes using libctru's RecursiveLock */
+
+#include "SDL_sysmutex_c.h"
+
+/* Create a mutex */
+SDL_mutex *
+SDL_CreateMutex(void)
+{
+    SDL_mutex *mutex;
+
+    /* Allocate mutex memory */
+    mutex = (SDL_mutex *) SDL_malloc(sizeof(*mutex));
+    if (mutex) {
+        RecursiveLock_Init(&mutex->lock);
+    } else {
+        SDL_OutOfMemory();
+    }
+    return mutex;
+}
+
+/* Free the mutex */
+void
+SDL_DestroyMutex(SDL_mutex *mutex)
+{
+    if (mutex) {
+        SDL_free(mutex);
+    }
+}
+
+/* Lock the mutex */
+int
+SDL_LockMutex(SDL_mutex *mutex)
+{
+    if (mutex == NULL) {
+        return SDL_SetError("Passed a NULL mutex");
+    }
+
+    RecursiveLock_Lock(&mutex->lock);
+
+    return 0;
+}
+
+/* try Lock the mutex */
+int
+SDL_TryLockMutex(SDL_mutex *mutex)
+{
+    if (mutex == NULL) {
+        return SDL_SetError("Passed a NULL mutex");
+    }
+
+    return RecursiveLock_TryLock(&mutex->lock);
+}
+
+/* Unlock the mutex */
+int
+SDL_mutexV(SDL_mutex *mutex)
+{
+    if (mutex == NULL) {
+        return SDL_SetError("Passed a NULL mutex");
+    }
+
+    RecursiveLock_Unlock(&mutex->lock);
+
+    return 0;
+}
+
+#endif /* SDL_THREAD_N3DS */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 37 - 0
src/thread/n3ds/SDL_sysmutex_c.h

@@ -0,0 +1,37 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_sysmutex_c_h_
+#define SDL_sysmutex_c_h_
+
+#include <3ds.h>
+
+#include "SDL_mutex.h"
+
+struct SDL_mutex
+{
+    RecursiveLock lock;
+};
+
+#endif /* SDL_sysmutex_c_h */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 134 - 0
src/thread/n3ds/SDL_syssem.c

@@ -0,0 +1,134 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "../../SDL_internal.h"
+
+#ifdef SDL_THREAD_N3DS
+
+/* An implementation of semaphores using libctru's LightSemaphore */
+
+#include <3ds.h>
+
+#include "SDL_thread.h"
+
+struct SDL_semaphore
+{
+    LightSemaphore semaphore;
+};
+
+SDL_sem *
+SDL_CreateSemaphore(Uint32 initial_value)
+{
+    SDL_sem *sem;
+
+    if (initial_value > SDL_MAX_SINT16) {
+        SDL_SetError("Initial semaphore value too high for this platform");
+        return NULL;
+    }
+
+    sem = (SDL_sem *) SDL_malloc(sizeof(*sem));
+    if (!sem) {
+        SDL_OutOfMemory();
+        return NULL;
+    }
+
+    LightSemaphore_Init(&sem->semaphore, initial_value, SDL_MAX_SINT16);
+
+    return sem;
+}
+
+/* WARNING:
+   You cannot call this function when another thread is using the semaphore.
+*/
+void
+SDL_DestroySemaphore(SDL_sem *sem)
+{
+    if (sem) {
+        SDL_free(sem);
+    }
+}
+
+int
+SDL_SemTryWait(SDL_sem *sem)
+{
+    if (!sem) {
+        return SDL_SetError("Passed a NULL semaphore");
+    }
+
+    return SDL_SemWaitTimeout(sem, 0);
+}
+
+int
+SDL_SemWaitTimeout(SDL_sem *sem, Uint32 timeout)
+{
+    int retval;
+
+    if (!sem) {
+        return SDL_SetError("Passed a NULL semaphore");
+    }
+
+    if (timeout == SDL_MUTEX_MAXWAIT) {
+        LightSemaphore_Acquire(&sem->semaphore, 1);
+        retval = 0;
+    } else {
+        int return_code = LightSemaphore_TryAcquire(&sem->semaphore, 1);
+        if (return_code != 0) {
+            for (u32 i = 0; i < timeout; i++) {
+                svcSleepThread(1000000LL);
+                return_code = LightSemaphore_TryAcquire(&sem->semaphore, 1);
+                if (return_code == 0) {
+                    break;
+                }
+            }
+        }
+        retval = return_code != 0 ? SDL_MUTEX_TIMEDOUT : 0;
+    }
+
+    return retval;
+}
+
+int
+SDL_SemWait(SDL_sem *sem)
+{
+    return SDL_SemWaitTimeout(sem, SDL_MUTEX_MAXWAIT);
+}
+
+Uint32
+SDL_SemValue(SDL_sem *sem)
+{
+    if (!sem) {
+        return SDL_SetError("Passed a NULL semaphore");
+    }
+    return sem->semaphore.current_count;
+}
+
+int
+SDL_SemPost(SDL_sem *sem)
+{
+    if (!sem) {
+        return SDL_SetError("Passed a NULL semaphore");
+    }
+    LightSemaphore_Release(&sem->semaphore, 1);
+    return 0;
+}
+
+#endif /* SDL_THREAD_N3DS */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 148 - 0
src/thread/n3ds/SDL_systhread.c

@@ -0,0 +1,148 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "../../SDL_internal.h"
+
+#ifdef SDL_THREAD_N3DS
+
+/* Thread management routines for SDL */
+
+#include "../SDL_systhread.h"
+
+/* N3DS has very limited RAM (128MB), so we put a limit on thread stack size. */
+#define N3DS_THREAD_STACK_SIZE_MAX     (16 * 1024)
+#define N3DS_THREAD_STACK_SIZE_DEFAULT (4 * 1024)
+
+#define N3DS_THREAD_PRIORITY_LOW           0x3F /**< Minimum priority */
+#define N3DS_THREAD_PRIORITY_MEDIUM        0x2F /**< Slightly higher than main thread (0x30) */
+#define N3DS_THREAD_PRIORITY_HIGH          0x19 /**< High priority for non-video work */
+#define N3DS_THREAD_PRIORITY_TIME_CRITICAL 0x18 /**< Highest priority */
+
+static size_t GetStackSize(size_t requested_size);
+
+static void
+ThreadEntry(void *arg)
+{
+    SDL_RunThread((SDL_Thread *) arg);
+    threadExit(0);
+}
+
+#ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
+#error "SDL_PASSED_BEGINTHREAD_ENDTHREAD is not supported on N3DS"
+#endif
+
+int
+SDL_SYS_CreateThread(SDL_Thread *thread)
+{
+    s32 priority = N3DS_THREAD_PRIORITY_MEDIUM;
+    size_t stack_size = GetStackSize(thread->stacksize);
+
+    thread->handle = threadCreate(ThreadEntry,
+                                  thread,
+                                  stack_size,
+                                  priority,
+                                  -1,
+                                  false);
+
+    if (thread->handle == NULL) {
+        return SDL_SetError("Couldn't create thread");
+    }
+
+    return 0;
+}
+
+static size_t
+GetStackSize(size_t requested_size)
+{
+    if (requested_size == 0) {
+        return N3DS_THREAD_STACK_SIZE_DEFAULT;
+    }
+
+    if (requested_size > N3DS_THREAD_STACK_SIZE_MAX) {
+        SDL_LogWarn(SDL_LOG_CATEGORY_SYSTEM,
+                    "Requested a thread size of %zu,"
+                    " falling back to the maximum supported of %zu\n",
+                    requested_size,
+                    N3DS_THREAD_STACK_SIZE_MAX);
+        requested_size = N3DS_THREAD_STACK_SIZE_MAX;
+    }
+    return requested_size;
+}
+
+void
+SDL_SYS_SetupThread(const char *name)
+{
+    return;
+}
+
+SDL_threadID
+SDL_ThreadID(void)
+{
+    u32 thread_ID = 0;
+    svcGetThreadId(&thread_ID, CUR_THREAD_HANDLE);
+    return (SDL_threadID) thread_ID;
+}
+
+int
+SDL_SYS_SetThreadPriority(SDL_ThreadPriority sdl_priority)
+{
+    s32 svc_priority;
+    switch (sdl_priority) {
+    case SDL_THREAD_PRIORITY_LOW:
+        svc_priority = N3DS_THREAD_PRIORITY_LOW;
+        break;
+    case SDL_THREAD_PRIORITY_NORMAL:
+        svc_priority = N3DS_THREAD_PRIORITY_MEDIUM;
+        break;
+    case SDL_THREAD_PRIORITY_HIGH:
+        svc_priority = N3DS_THREAD_PRIORITY_HIGH;
+        break;
+    case SDL_THREAD_PRIORITY_TIME_CRITICAL:
+        svc_priority = N3DS_THREAD_PRIORITY_TIME_CRITICAL;
+        break;
+    default:
+        svc_priority = N3DS_THREAD_PRIORITY_MEDIUM;
+    }
+    return (int) svcSetThreadPriority(CUR_THREAD_HANDLE, svc_priority);
+}
+
+void
+SDL_SYS_WaitThread(SDL_Thread *thread)
+{
+    Result res = threadJoin(thread->handle, U64_MAX);
+
+    /*
+      Detached threads can be waited on, but should NOT be cleaned manually
+      as it would result in a fatal error.
+    */
+    if (R_SUCCEEDED(res) && SDL_AtomicGet(&thread->state) != SDL_THREAD_STATE_DETACHED) {
+        threadFree(thread->handle);
+    }
+}
+
+void
+SDL_SYS_DetachThread(SDL_Thread *thread)
+{
+    threadDetach(thread->handle);
+}
+
+#endif /* SDL_THREAD_N3DS */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 32 - 0
src/thread/n3ds/SDL_systhread_c.h

@@ -0,0 +1,32 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_systhread_c_h_
+#define SDL_systhread_c_h_
+
+#include <3ds.h>
+
+typedef Thread SYS_ThreadHandle;
+
+#endif /* SDL_systhread_c_h_ */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 81 - 0
src/timer/n3ds/SDL_systimer.c

@@ -0,0 +1,81 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_TIMER_N3DS
+
+#include <3ds.h>
+
+static SDL_bool ticks_started = SDL_FALSE;
+static u64 start_tick;
+
+#define NSEC_PER_MSEC 1000000ULL
+
+void
+SDL_TicksInit(void)
+{
+    if (ticks_started) {
+        return;
+    }
+    ticks_started = SDL_TRUE;
+
+    start_tick = svcGetSystemTick();
+}
+
+void
+SDL_TicksQuit(void)
+{
+    ticks_started = SDL_FALSE;
+}
+
+Uint64
+SDL_GetTicks64(void)
+{
+    u64 elapsed;
+    if (!ticks_started) {
+        SDL_TicksInit();
+    }
+
+    elapsed = svcGetSystemTick() - start_tick;
+    return elapsed / CPU_TICKS_PER_MSEC;
+}
+
+Uint64
+SDL_GetPerformanceCounter(void)
+{
+    return svcGetSystemTick();
+}
+
+Uint64
+SDL_GetPerformanceFrequency(void)
+{
+    return SYSCLOCK_ARM11;
+}
+
+void
+SDL_Delay(Uint32 ms)
+{
+    svcSleepThread(ms * NSEC_PER_MSEC);
+}
+
+#endif /* SDL_TIMER_N3DS */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 1 - 0
src/video/SDL_sysvideo.h

@@ -461,6 +461,7 @@ extern VideoBootStrap PS2_bootstrap;
 extern VideoBootStrap PSP_bootstrap;
 extern VideoBootStrap VITA_bootstrap;
 extern VideoBootStrap RISCOS_bootstrap;
+extern VideoBootStrap N3DS_bootstrap;
 extern VideoBootStrap RPI_bootstrap;
 extern VideoBootStrap KMSDRM_bootstrap;
 extern VideoBootStrap KMSDRM_LEGACY_bootstrap;

+ 4 - 1
src/video/SDL_video.c

@@ -100,6 +100,9 @@ static VideoBootStrap *bootstrap[] = {
 #if SDL_VIDEO_DRIVER_VITA
     &VITA_bootstrap,
 #endif
+#if SDL_VIDEO_DRIVER_N3DS
+    &N3DS_bootstrap,
+#endif
 #if SDL_VIDEO_DRIVER_KMSDRM
     &KMSDRM_bootstrap,
 #endif
@@ -1498,7 +1501,7 @@ SDL_UpdateFullscreenMode(SDL_Window * window, SDL_bool fullscreen)
 }
 
 #define CREATE_FLAGS \
-    (SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_ALWAYS_ON_TOP | SDL_WINDOW_SKIP_TASKBAR | SDL_WINDOW_POPUP_MENU | SDL_WINDOW_UTILITY | SDL_WINDOW_TOOLTIP | SDL_WINDOW_VULKAN | SDL_WINDOW_MINIMIZED | SDL_WINDOW_METAL)
+    (SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_ALWAYS_ON_TOP | SDL_WINDOW_SKIP_TASKBAR | SDL_WINDOW_POPUP_MENU | SDL_WINDOW_UTILITY | SDL_WINDOW_TOOLTIP | SDL_WINDOW_VULKAN | SDL_WINDOW_MINIMIZED | SDL_WINDOW_METAL | SDL_WINDOW_N3DS_BOTTOM)
 
 static SDL_INLINE SDL_bool
 IsAcceptingDragAndDrop(void)

+ 45 - 0
src/video/n3ds/SDL_n3dsevents.c

@@ -0,0 +1,45 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_N3DS
+
+/* Pumping the events for the Home and Power buttons. */
+
+#include <3ds.h>
+
+#include "../../events/SDL_events_c.h"
+#include "SDL_n3dsevents_c.h"
+
+void
+N3DS_PumpEvents(_THIS)
+{
+    if (!aptMainLoop()) {
+        SDL_Event ev;
+        ev.type = SDL_QUIT;
+        SDL_PushEvent(&ev);
+        return;
+    }
+}
+
+#endif /* SDL_VIDEO_DRIVER_N3DS */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 31 - 0
src/video/n3ds/SDL_n3dsevents_c.h

@@ -0,0 +1,31 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_n3dsevents_c_h_
+#define SDL_n3dsevents_c_h_
+
+#include "../../SDL_internal.h"
+
+extern void N3DS_PumpEvents(_THIS);
+
+#endif /* SDL_n3dsevents_c_h_ */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 148 - 0
src/video/n3ds/SDL_n3dsframebuffer.c

@@ -0,0 +1,148 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_N3DS
+
+#include "../SDL_sysvideo.h"
+#include "SDL_n3dsframebuffer_c.h"
+#include "SDL_n3dsvideo.h"
+
+#define N3DS_SURFACE "_SDL_N3DSSurface"
+
+typedef struct
+{
+    int width, height;
+} Dimensions;
+
+SDL_FORCE_INLINE void FreePreviousWindowFramebuffer(SDL_Window *window);
+SDL_FORCE_INLINE SDL_Surface *CreateNewWindowFramebuffer(SDL_Window *window);
+SDL_FORCE_INLINE void CopyFramebuffertoN3DS(u32 *dest, const Dimensions dest_dim, const u32 *source, const Dimensions source_dim);
+SDL_FORCE_INLINE int GetDestOffset(int x, int y, int dest_width);
+SDL_FORCE_INLINE int GetSourceOffset(int x, int y, int source_width);
+SDL_FORCE_INLINE void FlushN3DSBuffer(const void *buffer, u32 bufsize, gfxScreen_t screen);
+
+int
+SDL_N3DS_CreateWindowFramebuffer(_THIS, SDL_Window *window, Uint32 *format, void **pixels, int *pitch)
+{
+    SDL_Surface *framebuffer;
+
+    FreePreviousWindowFramebuffer(window);
+    framebuffer = CreateNewWindowFramebuffer(window);
+
+    if (!framebuffer) {
+        return SDL_OutOfMemory();
+    }
+
+    SDL_SetWindowData(window, N3DS_SURFACE, framebuffer);
+    *format = FRAMEBUFFER_FORMAT;
+    *pixels = framebuffer->pixels;
+    *pitch = framebuffer->pitch;
+    return 0;
+}
+
+SDL_FORCE_INLINE void
+FreePreviousWindowFramebuffer(SDL_Window *window)
+{
+    SDL_Surface *surface = (SDL_Surface *) SDL_GetWindowData(window, N3DS_SURFACE);
+    SDL_FreeSurface(surface);
+}
+
+SDL_FORCE_INLINE SDL_Surface *
+CreateNewWindowFramebuffer(SDL_Window *window)
+{
+    int w, h, bpp;
+    Uint32 Rmask, Gmask, Bmask, Amask;
+    SDL_PixelFormatEnumToMasks(FRAMEBUFFER_FORMAT, &bpp, &Rmask, &Gmask, &Bmask, &Amask);
+    SDL_GetWindowSize(window, &w, &h);
+    return SDL_CreateRGBSurface(0, w, h, bpp, Rmask, Gmask, Bmask, Amask);
+}
+
+int
+SDL_N3DS_UpdateWindowFramebuffer(_THIS, SDL_Window *window, const SDL_Rect *rects, int numrects)
+{
+    SDL_WindowData *drv_data = (SDL_WindowData *) window->driverdata;
+    SDL_Surface *surface;
+    u16 width, height;
+    u32 *framebuffer;
+    u32 bufsize;
+
+    surface = (SDL_Surface *) SDL_GetWindowData(window, N3DS_SURFACE);
+    if (!surface) {
+        return SDL_SetError("%s: Unable to get the window surface.", __func__);
+    }
+
+    /* Get the N3DS internal framebuffer and its size */
+    framebuffer = (u32 *) gfxGetFramebuffer(drv_data->screen, GFX_LEFT, &width, &height);
+    bufsize = width * height * 4;
+
+    CopyFramebuffertoN3DS(framebuffer, (Dimensions){ width, height },
+                          surface->pixels, (Dimensions){ surface->w, surface->h });
+    FlushN3DSBuffer(framebuffer, bufsize, drv_data->screen);
+
+    return 0;
+}
+
+SDL_FORCE_INLINE void
+CopyFramebuffertoN3DS(u32 *dest, const Dimensions dest_dim, const u32 *source, const Dimensions source_dim)
+{
+    int rows = SDL_min(dest_dim.width, source_dim.height);
+    int cols = SDL_min(dest_dim.height, source_dim.width);
+    for (int y = 0; y < rows; ++y) {
+        for (int x = 0; x < cols; ++x) {
+            SDL_memcpy(
+                dest + GetDestOffset(x, y, dest_dim.width),
+                source + GetSourceOffset(x, y, source_dim.width),
+                4);
+        }
+    }
+}
+
+SDL_FORCE_INLINE int
+GetDestOffset(int x, int y, int dest_width)
+{
+    return dest_width - y - 1 + dest_width * x;
+}
+
+SDL_FORCE_INLINE int
+GetSourceOffset(int x, int y, int source_width)
+{
+    return x + y * source_width;
+}
+
+SDL_FORCE_INLINE void
+FlushN3DSBuffer(const void *buffer, u32 bufsize, gfxScreen_t screen)
+{
+    GSPGPU_FlushDataCache(buffer, bufsize);
+    gfxScreenSwapBuffers(screen, false);
+}
+
+void
+SDL_N3DS_DestroyWindowFramebuffer(_THIS, SDL_Window *window)
+{
+    SDL_Surface *surface;
+    surface = (SDL_Surface *) SDL_SetWindowData(window, N3DS_SURFACE, NULL);
+    SDL_FreeSurface(surface);
+}
+
+#endif /* SDL_VIDEO_DRIVER_N3DS */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 33 - 0
src/video/n3ds/SDL_n3dsframebuffer_c.h

@@ -0,0 +1,33 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_n3dsframebuffer_c_h_
+#define SDL_n3dsframebuffer_c_h_
+
+#include "../../SDL_internal.h"
+
+int SDL_N3DS_CreateWindowFramebuffer(_THIS, SDL_Window *window, Uint32 *format, void **pixels, int *pitch);
+int SDL_N3DS_UpdateWindowFramebuffer(_THIS, SDL_Window *window, const SDL_Rect *rects, int numrects);
+void SDL_N3DS_DestroyWindowFramebuffer(_THIS, SDL_Window *window);
+
+#endif /* SDL_n3dsframebuffer_c_h_ */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 76 - 0
src/video/n3ds/SDL_n3dsswkb.c

@@ -0,0 +1,76 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_N3DS
+
+#include <3ds.h>
+
+#include "SDL_n3dsswkb.h"
+
+static SwkbdState sw_keyboard;
+const static size_t BUFFER_SIZE = 256;
+
+void
+N3DS_SwkbInit()
+{
+    swkbdInit(&sw_keyboard, SWKBD_TYPE_NORMAL, 2, -1);
+}
+
+void
+N3DS_SwkbPoll()
+{
+    return;
+}
+
+void
+N3DS_SwkbQuit()
+{
+    return;
+}
+
+SDL_bool
+N3DS_HasScreenKeyboardSupport(_THIS)
+{
+    return SDL_TRUE;
+}
+
+void
+N3DS_StartTextInput(_THIS)
+{
+    char buffer[BUFFER_SIZE];
+    SwkbdButton button_pressed;
+    button_pressed = swkbdInputText(&sw_keyboard, buffer, BUFFER_SIZE);
+    if (button_pressed == SWKBD_BUTTON_CONFIRM) {
+        SDL_SendKeyboardText(buffer);
+    }
+}
+
+void
+N3DS_StopTextInput(_THIS)
+{
+    return;
+}
+
+#endif /* SDL_VIDEO_DRIVER_N3DS */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 38 - 0
src/video/n3ds/SDL_n3dsswkb.h

@@ -0,0 +1,38 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_n3dskeyboard_h_
+#define SDL_n3dskeyboard_h_
+
+#include "../../events/SDL_events_c.h"
+
+void N3DS_SwkbInit();
+void N3DS_SwkbPoll();
+void N3DS_SwkbQuit();
+
+SDL_bool N3DS_HasScreenKeyboardSupport(_THIS);
+
+void N3DS_StartTextInput(_THIS);
+void N3DS_StopTextInput(_THIS);
+
+#endif /* SDL_n3dskeyboard_h_ */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 163 - 0
src/video/n3ds/SDL_n3dsvideo.c

@@ -0,0 +1,163 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_N3DS
+
+#include "../SDL_sysvideo.h"
+#include "SDL_n3dsevents_c.h"
+#include "SDL_n3dsframebuffer_c.h"
+#include "SDL_n3dsswkb.h"
+#include "SDL_n3dsvideo.h"
+
+#define N3DSVID_DRIVER_NAME "n3ds"
+
+SDL_FORCE_INLINE void AddN3DSDisplay(gfxScreen_t screen);
+
+static int N3DS_VideoInit(_THIS);
+static void N3DS_VideoQuit(_THIS);
+static void N3DS_GetDisplayModes(_THIS, SDL_VideoDisplay *display);
+static int N3DS_CreateWindow(_THIS, SDL_Window *window);
+static void N3DS_DestroyWindow(_THIS, SDL_Window *window);
+
+/* N3DS driver bootstrap functions */
+
+static void
+N3DS_DeleteDevice(SDL_VideoDevice *device)
+{
+    SDL_free(device->displays);
+    SDL_free(device->driverdata);
+    SDL_free(device);
+}
+
+static SDL_VideoDevice *
+N3DS_CreateDevice(void)
+{
+    SDL_VideoDevice *device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));
+    if (!device) {
+        SDL_OutOfMemory();
+        return (0);
+    }
+
+    device->VideoInit = N3DS_VideoInit;
+    device->VideoQuit = N3DS_VideoQuit;
+
+    device->GetDisplayModes = N3DS_GetDisplayModes;
+
+    device->CreateSDLWindow = N3DS_CreateWindow;
+    device->DestroyWindow = N3DS_DestroyWindow;
+
+    device->HasScreenKeyboardSupport = N3DS_HasScreenKeyboardSupport;
+    device->StartTextInput = N3DS_StartTextInput;
+    device->StopTextInput = N3DS_StopTextInput;
+
+    device->PumpEvents = N3DS_PumpEvents;
+
+    device->CreateWindowFramebuffer = SDL_N3DS_CreateWindowFramebuffer;
+    device->UpdateWindowFramebuffer = SDL_N3DS_UpdateWindowFramebuffer;
+    device->DestroyWindowFramebuffer = SDL_N3DS_DestroyWindowFramebuffer;
+
+    device->num_displays = 2;
+
+    device->free = N3DS_DeleteDevice;
+
+    return device;
+}
+
+VideoBootStrap N3DS_bootstrap = { N3DSVID_DRIVER_NAME, "N3DS Video Driver", N3DS_CreateDevice };
+
+static int
+N3DS_VideoInit(_THIS)
+{
+    AddN3DSDisplay(GFX_TOP);
+    AddN3DSDisplay(GFX_BOTTOM);
+
+    N3DS_SwkbInit();
+
+    return 0;
+}
+
+SDL_FORCE_INLINE void
+AddN3DSDisplay(gfxScreen_t screen)
+{
+    SDL_DisplayMode mode;
+    SDL_VideoDisplay display;
+
+    SDL_zero(mode);
+    SDL_zero(display);
+
+    mode.w = (screen == GFX_TOP) ? GSP_SCREEN_HEIGHT_TOP : GSP_SCREEN_HEIGHT_BOTTOM;
+    mode.h = GSP_SCREEN_WIDTH;
+    mode.refresh_rate = 60;
+    mode.format = FRAMEBUFFER_FORMAT;
+    mode.driverdata = NULL;
+
+    display.desktop_mode = mode;
+    display.current_mode = mode;
+    display.driverdata = NULL;
+
+    SDL_AddVideoDisplay(&display, SDL_FALSE);
+}
+
+static void
+N3DS_VideoQuit(_THIS)
+{
+    N3DS_SwkbQuit();
+    return;
+}
+
+static void
+N3DS_GetDisplayModes(_THIS, SDL_VideoDisplay *display)
+{
+    /* Each display only has a single mode */
+    SDL_AddDisplayMode(display, &display->current_mode);
+}
+
+static int
+N3DS_CreateWindow(_THIS, SDL_Window *window)
+{
+    SDL_WindowData *drv_data = (SDL_WindowData *) SDL_calloc(1, sizeof(SDL_WindowData));
+    if (drv_data == NULL) {
+        return SDL_OutOfMemory();
+    }
+
+    if (window->flags & SDL_WINDOW_N3DS_BOTTOM) {
+        drv_data->screen = GFX_BOTTOM;
+    } else {
+        drv_data->screen = GFX_TOP;
+    }
+
+    window->driverdata = drv_data;
+    return 0;
+}
+
+static void
+N3DS_DestroyWindow(_THIS, SDL_Window *window)
+{
+    if (window == NULL) {
+        return;
+    }
+    SDL_free(window->driverdata);
+}
+
+#endif /* SDL_VIDEO_DRIVER_N3DS */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 38 - 0
src/video/n3ds/SDL_n3dsvideo.h

@@ -0,0 +1,38 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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_n3dsvideo_h_
+#define SDL_n3dsvideo_h_
+
+#include <3ds.h>
+
+#include "../SDL_sysvideo.h"
+typedef struct SDL_WindowData
+{
+    gfxScreen_t screen; /**< Keeps track of which N3DS screen is targetted */
+} SDL_WindowData;
+
+#define FRAMEBUFFER_FORMAT SDL_PIXELFORMAT_RGBA8888
+
+#endif /* SDL_n3dsvideo_h_ */
+
+/* vi: set sts=4 ts=4 sw=4 expandtab: */

+ 25 - 0
test/CMakeLists.txt

@@ -14,6 +14,10 @@ if(SDL_INSTALL_TESTS)
     include(GNUInstallDirs)
 endif()
 
+if(N3DS)
+    link_libraries(SDL2::SDL2main)
+endif()
+
 if(PSP)
     link_libraries(
         SDL2::SDL2main
@@ -430,6 +434,27 @@ if(PSP)
     endforeach()
 endif()
 
+if(N3DS)
+    set(ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/romfs")
+    file(COPY ${RESOURCE_FILES} DESTINATION "${ROMFS_DIR}")
+
+    foreach(APP IN LISTS ALL_TESTS)
+        get_target_property(TARGET_BINARY_DIR ${APP} BINARY_DIR)
+        set(SMDH_FILE "${TARGET_BINARY_DIR}/${APP}.smdh")
+        ctr_generate_smdh("${SMDH_FILE}" 
+            NAME "SDL-${APP}"
+            DESCRIPTION "SDL2 Test suite"
+            AUTHOR "SDL2 Contributors"
+            ICON "${CMAKE_CURRENT_SOURCE_DIR}/n3ds/logo48x48.png"
+        )
+        ctr_create_3dsx(
+            ${APP}
+            ROMFS "${ROMFS_DIR}"
+            SMDH "${SMDH_FILE}"
+        )
+    endforeach()
+endif()
+
 if(RISCOS)
     set(ALL_TESTS_AIF "")
     foreach(APP IN LISTS ALL_TESTS)

BIN
test/n3ds/logo48x48.png