diff --git a/.github/workflows/n3ds.yml b/.github/workflows/n3ds.yml new file mode 100644 index 000000000..af985e4e3 --- /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 15a6cbf28..48cbb6174 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 000000000..761c76dd6 --- /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 15c5c9e1d..6dc89a4a3 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 8b267082f..113d11de0 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 f1f6f8b06..a8e3ac225 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 5f79c95b3..c60d6eebb 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 d9dce43a9..60afda22a 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 0e1c32a92..93f7a7f6d 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 34c6fe8ae..66191d2b4 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 e33e41dbc..11700d497 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 6afaae195..a911de041 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 000000000..484bec8ae --- /dev/null +++ b/src/audio/n3ds/SDL_n3dsaudio.c @@ -0,0 +1,363 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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 000000000..d01f17f53 --- /dev/null +++ b/src/audio/n3ds/SDL_n3dsaudio.h @@ -0,0 +1,50 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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 8221f7796..6ba5c85eb 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 dc53e58b2..e3268697c 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 a93c2fb3c..ead504c10 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 000000000..fe92c3d3b --- /dev/null +++ b/src/file/n3ds/SDL_rwopsromfs.c @@ -0,0 +1,56 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/file/n3ds/SDL_rwopsromfs.h b/src/file/n3ds/SDL_rwopsromfs.h new file mode 100644 index 000000000..9a6a73472 --- /dev/null +++ b/src/file/n3ds/SDL_rwopsromfs.h @@ -0,0 +1,30 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/filesystem/n3ds/SDL_sysfilesystem.c b/src/filesystem/n3ds/SDL_sysfilesystem.c new file mode 100644 index 000000000..e402e037c --- /dev/null +++ b/src/filesystem/n3ds/SDL_sysfilesystem.c @@ -0,0 +1,86 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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 +#include + +#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: */ diff --git a/src/joystick/SDL_gamecontrollerdb.h b/src/joystick/SDL_gamecontrollerdb.h index fd8534fa6..a39a46d37 100644 --- a/src/joystick/SDL_gamecontrollerdb.h +++ b/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 diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index a42951ce1..b22c8783a 100644 --- a/src/joystick/SDL_joystick.c +++ b/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 diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h index a0400c6ff..707ab4ae3 100644 --- a/src/joystick/SDL_sysjoystick.h +++ b/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 diff --git a/src/joystick/n3ds/SDL_sysjoystick.c b/src/joystick/n3ds/SDL_sysjoystick.c new file mode 100644 index 000000000..f2f15c16b --- /dev/null +++ b/src/joystick/n3ds/SDL_sysjoystick.c @@ -0,0 +1,367 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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(¤t_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(¤t_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(¤t_state.acceleration); + if (SDL_memcmp(&previous_state->acceleration, ¤t_state.acceleration, sizeof(accelVector)) != 0) { + SDL_memcpy(&previous_state->acceleration, ¤t_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(¤t_state.rate); + if (SDL_memcmp(&previous_state->rate, ¤t_state.rate, sizeof(angularRate)) != 0) { + SDL_memcpy(&previous_state->rate, ¤t_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: */ diff --git a/src/libm/math_private.h b/src/libm/math_private.h index f560cca6a..ee67c444e 100644 --- a/src/libm/math_private.h +++ b/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 diff --git a/src/locale/n3ds/SDL_syslocale.c b/src/locale/n3ds/SDL_syslocale.c new file mode 100644 index 000000000..bcfec8f1d --- /dev/null +++ b/src/locale/n3ds/SDL_syslocale.c @@ -0,0 +1,59 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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(¤t_locale))) { + return BAD_LOCALE; + } + cfguExit(); + return current_locale; +} + +/* vi: set sts=4 ts=4 sw=4 expandtab: */ diff --git a/src/main/n3ds/SDL_n3ds_main.c b/src/main/n3ds/SDL_n3ds_main.c new file mode 100644 index 000000000..982dcb3c6 --- /dev/null +++ b/src/main/n3ds/SDL_n3ds_main.c @@ -0,0 +1,82 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/power/SDL_power.c b/src/power/SDL_power.c index 625a8fa08..fbf8d5a65 100644 --- a/src/power/SDL_power.c +++ b/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 diff --git a/src/power/SDL_syspower.h b/src/power/SDL_syspower.h index d66e50f9c..2f81756c4 100644 --- a/src/power/SDL_syspower.h +++ b/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 *); diff --git a/src/power/n3ds/SDL_syspower.c b/src/power/n3ds/SDL_syspower.c new file mode 100644 index 000000000..fe167aff6 --- /dev/null +++ b/src/power/n3ds/SDL_syspower.c @@ -0,0 +1,111 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/thread/SDL_thread_c.h b/src/thread/SDL_thread_c.h index 55a4a8875..f3348eaa9 100644 --- a/src/thread/SDL_thread_c.h +++ b/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 diff --git a/src/thread/n3ds/SDL_syscond.c b/src/thread/n3ds/SDL_syscond.c new file mode 100644 index 000000000..9c671f554 --- /dev/null +++ b/src/thread/n3ds/SDL_syscond.c @@ -0,0 +1,133 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/thread/n3ds/SDL_sysmutex.c b/src/thread/n3ds/SDL_sysmutex.c new file mode 100644 index 000000000..59fa8a801 --- /dev/null +++ b/src/thread/n3ds/SDL_sysmutex.c @@ -0,0 +1,93 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/thread/n3ds/SDL_sysmutex_c.h b/src/thread/n3ds/SDL_sysmutex_c.h new file mode 100644 index 000000000..d7658e1fd --- /dev/null +++ b/src/thread/n3ds/SDL_sysmutex_c.h @@ -0,0 +1,37 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/thread/n3ds/SDL_syssem.c b/src/thread/n3ds/SDL_syssem.c new file mode 100644 index 000000000..08fbb6f42 --- /dev/null +++ b/src/thread/n3ds/SDL_syssem.c @@ -0,0 +1,134 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/thread/n3ds/SDL_systhread.c b/src/thread/n3ds/SDL_systhread.c new file mode 100644 index 000000000..a588400ad --- /dev/null +++ b/src/thread/n3ds/SDL_systhread.c @@ -0,0 +1,148 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/thread/n3ds/SDL_systhread_c.h b/src/thread/n3ds/SDL_systhread_c.h new file mode 100644 index 000000000..71b9a27eb --- /dev/null +++ b/src/thread/n3ds/SDL_systhread_c.h @@ -0,0 +1,32 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/timer/n3ds/SDL_systimer.c b/src/timer/n3ds/SDL_systimer.c new file mode 100644 index 000000000..6a4d29b04 --- /dev/null +++ b/src/timer/n3ds/SDL_systimer.c @@ -0,0 +1,81 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 7202f1d6b..4bc1f111f 100644 --- a/src/video/SDL_sysvideo.h +++ b/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; diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 139c550c5..fbeaaaaee 100644 --- a/src/video/SDL_video.c +++ b/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) diff --git a/src/video/n3ds/SDL_n3dsevents.c b/src/video/n3ds/SDL_n3dsevents.c new file mode 100644 index 000000000..f54d68ee7 --- /dev/null +++ b/src/video/n3ds/SDL_n3dsevents.c @@ -0,0 +1,45 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/video/n3ds/SDL_n3dsevents_c.h b/src/video/n3ds/SDL_n3dsevents_c.h new file mode 100644 index 000000000..418368c2b --- /dev/null +++ b/src/video/n3ds/SDL_n3dsevents_c.h @@ -0,0 +1,31 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/video/n3ds/SDL_n3dsframebuffer.c b/src/video/n3ds/SDL_n3dsframebuffer.c new file mode 100644 index 000000000..ba4ec51bf --- /dev/null +++ b/src/video/n3ds/SDL_n3dsframebuffer.c @@ -0,0 +1,148 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/video/n3ds/SDL_n3dsframebuffer_c.h b/src/video/n3ds/SDL_n3dsframebuffer_c.h new file mode 100644 index 000000000..3a4750c9d --- /dev/null +++ b/src/video/n3ds/SDL_n3dsframebuffer_c.h @@ -0,0 +1,33 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/video/n3ds/SDL_n3dsswkb.c b/src/video/n3ds/SDL_n3dsswkb.c new file mode 100644 index 000000000..b577a3639 --- /dev/null +++ b/src/video/n3ds/SDL_n3dsswkb.c @@ -0,0 +1,76 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/video/n3ds/SDL_n3dsswkb.h b/src/video/n3ds/SDL_n3dsswkb.h new file mode 100644 index 000000000..6a48ae8de --- /dev/null +++ b/src/video/n3ds/SDL_n3dsswkb.h @@ -0,0 +1,38 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/video/n3ds/SDL_n3dsvideo.c b/src/video/n3ds/SDL_n3dsvideo.c new file mode 100644 index 000000000..6d3babd2d --- /dev/null +++ b/src/video/n3ds/SDL_n3dsvideo.c @@ -0,0 +1,163 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/src/video/n3ds/SDL_n3dsvideo.h b/src/video/n3ds/SDL_n3dsvideo.h new file mode 100644 index 000000000..c1b15f712 --- /dev/null +++ b/src/video/n3ds/SDL_n3dsvideo.h @@ -0,0 +1,38 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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: */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ebea6deff..578a422be 100644 --- a/test/CMakeLists.txt +++ b/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) diff --git a/test/n3ds/logo48x48.png b/test/n3ds/logo48x48.png new file mode 100644 index 000000000..fb3338dfa Binary files /dev/null and b/test/n3ds/logo48x48.png differ