From 655275378d22acbef4685d8992a5c7b370bab4fa Mon Sep 17 00:00:00 2001
From: Pierre Wendling <[EMAIL REDACTED]>
Date: Tue, 30 Mar 2021 04:32:39 -0400
Subject: [PATCH] N3DS port (squashed)
A dedicated renderer using Citro3D would likely allow for better
much better graphical performances.
---
.github/workflows/n3ds.yml | 40 +++
CMakeLists.txt | 74 ++++-
docs/README-n3ds.md | 27 ++
include/SDL_config.h.cmake | 7 +
include/SDL_main.h | 9 +
include/SDL_platform.h | 5 +
include/SDL_stdinc.h | 2 +-
include/SDL_video.h | 1 +
src/SDL.c | 2 +
src/SDL_log.c | 7 +
src/audio/SDL_audio.c | 3 +
src/audio/SDL_sysaudio.h | 1 +
src/audio/n3ds/SDL_n3dsaudio.c | 363 +++++++++++++++++++++++
src/audio/n3ds/SDL_n3dsaudio.h | 50 ++++
src/cpuinfo/SDL_cpuinfo.c | 2 +
src/dynapi/SDL_dynapi.h | 2 +
src/file/SDL_rwops.c | 6 +
src/file/n3ds/SDL_rwopsromfs.c | 56 ++++
src/file/n3ds/SDL_rwopsromfs.h | 30 ++
src/filesystem/n3ds/SDL_sysfilesystem.c | 86 ++++++
src/joystick/SDL_gamecontrollerdb.h | 3 +
src/joystick/SDL_joystick.c | 3 +
src/joystick/SDL_sysjoystick.h | 1 +
src/joystick/n3ds/SDL_sysjoystick.c | 367 ++++++++++++++++++++++++
src/libm/math_private.h | 2 +-
src/locale/n3ds/SDL_syslocale.c | 59 ++++
src/main/n3ds/SDL_n3ds_main.c | 82 ++++++
src/power/SDL_power.c | 3 +
src/power/SDL_syspower.h | 1 +
src/power/n3ds/SDL_syspower.c | 111 +++++++
src/thread/SDL_thread_c.h | 2 +
src/thread/n3ds/SDL_syscond.c | 133 +++++++++
src/thread/n3ds/SDL_sysmutex.c | 93 ++++++
src/thread/n3ds/SDL_sysmutex_c.h | 37 +++
src/thread/n3ds/SDL_syssem.c | 134 +++++++++
src/thread/n3ds/SDL_systhread.c | 148 ++++++++++
src/thread/n3ds/SDL_systhread_c.h | 32 +++
src/timer/n3ds/SDL_systimer.c | 81 ++++++
src/video/SDL_sysvideo.h | 1 +
src/video/SDL_video.c | 5 +-
src/video/n3ds/SDL_n3dsevents.c | 45 +++
src/video/n3ds/SDL_n3dsevents_c.h | 31 ++
src/video/n3ds/SDL_n3dsframebuffer.c | 148 ++++++++++
src/video/n3ds/SDL_n3dsframebuffer_c.h | 33 +++
src/video/n3ds/SDL_n3dsswkb.c | 76 +++++
src/video/n3ds/SDL_n3dsswkb.h | 38 +++
src/video/n3ds/SDL_n3dsvideo.c | 163 +++++++++++
src/video/n3ds/SDL_n3dsvideo.h | 38 +++
test/CMakeLists.txt | 25 ++
test/n3ds/logo48x48.png | Bin 0 -> 3069 bytes
50 files changed, 2663 insertions(+), 5 deletions(-)
create mode 100644 .github/workflows/n3ds.yml
create mode 100644 docs/README-n3ds.md
create mode 100644 src/audio/n3ds/SDL_n3dsaudio.c
create mode 100644 src/audio/n3ds/SDL_n3dsaudio.h
create mode 100644 src/file/n3ds/SDL_rwopsromfs.c
create mode 100644 src/file/n3ds/SDL_rwopsromfs.h
create mode 100644 src/filesystem/n3ds/SDL_sysfilesystem.c
create mode 100644 src/joystick/n3ds/SDL_sysjoystick.c
create mode 100644 src/locale/n3ds/SDL_syslocale.c
create mode 100644 src/main/n3ds/SDL_n3ds_main.c
create mode 100644 src/power/n3ds/SDL_syspower.c
create mode 100644 src/thread/n3ds/SDL_syscond.c
create mode 100644 src/thread/n3ds/SDL_sysmutex.c
create mode 100644 src/thread/n3ds/SDL_sysmutex_c.h
create mode 100644 src/thread/n3ds/SDL_syssem.c
create mode 100644 src/thread/n3ds/SDL_systhread.c
create mode 100644 src/thread/n3ds/SDL_systhread_c.h
create mode 100644 src/timer/n3ds/SDL_systimer.c
create mode 100644 src/video/n3ds/SDL_n3dsevents.c
create mode 100644 src/video/n3ds/SDL_n3dsevents_c.h
create mode 100644 src/video/n3ds/SDL_n3dsframebuffer.c
create mode 100644 src/video/n3ds/SDL_n3dsframebuffer_c.h
create mode 100644 src/video/n3ds/SDL_n3dsswkb.c
create mode 100644 src/video/n3ds/SDL_n3dsswkb.h
create mode 100644 src/video/n3ds/SDL_n3dsvideo.c
create mode 100644 src/video/n3ds/SDL_n3dsvideo.h
create mode 100644 test/n3ds/logo48x48.png
diff --git a/.github/workflows/n3ds.yml b/.github/workflows/n3ds.yml
new file mode 100644
index 000000000000..af985e4e368f
--- /dev/null
+++ b/.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
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 15a6cbf288bc..48cbb6174f41 100644
--- a/CMakeLists.txt
+++ b/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)
diff --git a/docs/README-n3ds.md b/docs/README-n3ds.md
new file mode 100644
index 000000000000..761c76dd6944
--- /dev/null
+++ b/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.
diff --git a/include/SDL_config.h.cmake b/include/SDL_config.h.cmake
index 15c5c9e1d93f..6dc89a4a37dc 100644
--- a/include/SDL_config.h.cmake
+++ b/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@
diff --git a/include/SDL_main.h b/include/SDL_main.h
index 8b267082f23f..113d11de06a5 100644
--- a/include/SDL_main.h
+++ b/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 */
diff --git a/include/SDL_platform.h b/include/SDL_platform.h
index f1f6f8b0666e..a8e3ac225ec7 100644
--- a/include/SDL_platform.h
+++ b/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
diff --git a/include/SDL_stdinc.h b/include/SDL_stdinc.h
index 5f79c95b3124..c60d6eebb28d 100644
--- a/include/SDL_stdinc.h
+++ b/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
{
diff --git a/include/SDL_video.h b/include/SDL_video.h
index d9dce43a96f4..60afda22a787 100644
--- a/include/SDL_video.h
+++ b/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;
diff --git a/src/SDL.c b/src/SDL.c
index 0e1c32a92c6a..93f7a7f6de17 100644
--- a/src/SDL.c
+++ b/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
diff --git a/src/SDL_log.c b/src/SDL_log.c
index 34c6fe8ae075..66191d2b413b 100644
--- a/src/SDL_log.c
+++ b/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)))
diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index e33e41dbc33e..11700d497d41 100644
--- a/src/audio/SDL_audio.c
+++ b/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
diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h
index 6afaae195c6c..a911de041e22 100644
--- a/src/audio/SDL_sysaudio.h
+++ b/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;
diff --git a/src/audio/n3ds/SDL_n3dsaudio.c b/src/audio/n3ds/SDL_n3dsaudio.c
new file mode 100644
index 000000000000..484bec8ae9e2
--- /dev/null
+++ b/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(¤t_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: */
diff --git a/src/audio/n3ds/SDL_n3dsaudio.h b/src/audio/n3ds/SDL_n3dsaudio.h
new file mode 100644
index 000000000000..d01f17f539a6
--- /dev/null
+++ b/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: */
diff --git a/src/cpuinfo/SDL_cpuinfo.c b/src/cpuinfo/SDL_cpuinfo.c
index 8221f7796713..6ba5c85eb478 100644
--- a/src/cpuinfo/SDL_cpuinfo.c
+++ b/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. */
diff --git a/src/dynapi/SDL_dynapi.h b/src/dynapi/SDL_dynapi.h
index dc53e58b2887..e3268697cd55 100644
--- a/src/dynapi/SDL_dynapi.h
+++ b/src/dynapi/SDL_dynapi.h
@@ -63,6 +63,8 @@
#define SDL_DYNAMIC_API 0 /* vitasdk doesn't support dynamic linking */
#elif defined(__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
diff --git a/src/file/SDL_rwops.c b/src/file/SDL_rwops.c
index a93c2fb3ce00..ead504c10a78 100644
--- a/src/file/SDL_rwops.c
+++ b/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
diff --git a/src/file/n3ds/SDL_rwopsromfs.c b/src/file/n3ds/SDL_rwopsromfs.c
new file mode 100644
index 000000000000..fe92c3d3be76
--- /dev/null
+++ b/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 a
(Patch may be truncated, please check the link at the top of this post.)