diff --git a/Makefile.common b/Makefile.common
index c0e54a63c0..55321a8213 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -685,7 +685,8 @@ endif
ifeq ($(TARGET), retroarch_3ds)
OBJ += audio/drivers/ctr_csnd_audio.o \
- audio/drivers/ctr_dsp_audio.o
+ audio/drivers/ctr_dsp_audio.o \
+ audio/drivers/ctr_dsp_thread_audio.o \
endif
ifeq ($(HAVE_ALSA), 1)
diff --git a/Makefile.ctr b/Makefile.ctr
index 7a9b04414a..1e72406204 100644
--- a/Makefile.ctr
+++ b/Makefile.ctr
@@ -56,7 +56,8 @@ ifeq ($(GRIFFIN_BUILD), 1)
DEFINES += -DHAVE_PATCH -DHAVE_RWAV
DEFINES += -DHAVE_SCREENSHOTS
DEFINES += -DHAVE_REWIND
- #DEFINES += -DHAVE_SOCKET_LEGACY -DHAVE_THREADS
+ DEFINES += -DHAVE_THREADS
+ #DEFINES += -DHAVE_SOCKET_LEGACY
#-DHAVE_SSL -DHAVE_BUILTINMBEDTLS -DMBEDTLS_SSL_DEBUG_ALL
#ssl is currently incompatible with griffin due to use of the "static" flag on repeating functions that will conflict when included in one file
else
@@ -84,7 +85,7 @@ else
#HAVE_NETWORKING = 1
#HAVE_CHEEVOS = 1
#HAVE_SOCKET_LEGACY = 1
- #HAVE_THREADS = 1
+ HAVE_THREADS = 1
#HAVE_SSL = 1
#HAVE_BUILTINMBEDTLS = 1
diff --git a/audio/drivers/ctr_dsp_thread_audio.c b/audio/drivers/ctr_dsp_thread_audio.c
new file mode 100644
index 0000000000..08dad28654
--- /dev/null
+++ b/audio/drivers/ctr_dsp_thread_audio.c
@@ -0,0 +1,339 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2020 - Justin Weiss
+ *
+ * RetroArch is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with RetroArch.
+ * If not, see .
+ */
+
+#include <3ds.h>
+#include
+#include
+#include
+#include
+
+#include "../../retroarch.h"
+#include "../../ctr/ctr_debug.h"
+
+typedef struct
+{
+ fifo_buffer_t* fifo;
+ size_t fifo_size;
+
+ slock_t* fifo_lock;
+ scond_t* fifo_avail;
+ scond_t* fifo_done;
+ sthread_t* thread;
+
+ volatile bool running;
+
+ bool nonblocking;
+ bool playing;
+ retro_time_t frame_time;
+ int channel;
+ ndspWaveBuf dsp_buf;
+
+ uint32_t pos;
+} ctr_dsp_thread_audio_t;
+
+// PCM16 stereo
+#define DSP_BYTES_TO_SAMPLES(bytes) (bytes / (2 * sizeof(uint16_t)))
+#define DSP_SAMPLES_TO_BYTES(samples) (samples * 2 * sizeof(uint16_t))
+
+static void ctr_dsp_audio_loop(void* data)
+{
+ uint32_t pos, buf_pos;
+ ctr_dsp_thread_audio_t *ctr = (ctr_dsp_thread_audio_t*)data;
+
+ if (!ctr)
+ return;
+
+ while (1)
+ {
+ size_t buf_avail, avail, to_write;
+ slock_lock(ctr->fifo_lock);
+
+ do {
+ avail = FIFO_READ_AVAIL(ctr->fifo);
+
+ if (!avail) {
+ scond_wait(ctr->fifo_avail, ctr->fifo_lock);
+ }
+ } while (!avail && ctr->running);
+
+ slock_unlock(ctr->fifo_lock);
+
+ if (!ctr->running)
+ break;
+
+ pos = ctr->pos;
+ buf_pos = DSP_SAMPLES_TO_BYTES(ndspChnGetSamplePos(ctr->channel));
+
+ buf_avail = buf_pos >= pos ? buf_pos - pos : ctr->fifo_size - pos;
+ to_write = MIN(avail, buf_avail);
+
+ slock_lock(ctr->fifo_lock);
+
+ if (to_write > 0) {
+ fifo_read(ctr->fifo, ctr->dsp_buf.data_pcm8 + pos, to_write);
+ DSP_FlushDataCache(ctr->dsp_buf.data_pcm8 + pos, to_write);
+ scond_signal(ctr->fifo_done);
+ }
+
+ slock_unlock(ctr->fifo_lock);
+
+ if (buf_pos == pos) {
+ svcSleepThread(100000);
+ }
+
+ ctr->pos = (pos + to_write) % ctr->fifo_size;
+ }
+}
+
+static void ctr_dsp_thread_audio_free(void *data);
+
+static void *ctr_dsp_thread_audio_init(const char *device, unsigned rate, unsigned latency,
+ unsigned block_frames,
+ unsigned *new_rate)
+{
+ ctr_dsp_thread_audio_t *ctr = NULL;
+
+ (void)device;
+ (void)rate;
+
+ if (ndspInit() < 0)
+ return NULL;
+
+ ctr = (ctr_dsp_thread_audio_t*)calloc(1, sizeof(ctr_dsp_thread_audio_t));
+
+ if (!ctr)
+ return NULL;
+
+ *new_rate = 32728;
+
+ ctr->running = true;
+ ctr->channel = 0;
+
+ ndspSetOutputMode(NDSP_OUTPUT_STEREO);
+ ndspSetClippingMode(NDSP_CLIP_SOFT); /* ?? */
+ ndspSetOutputCount(2);
+ ndspChnReset(ctr->channel);
+ ndspChnSetFormat(ctr->channel, NDSP_FORMAT_STEREO_PCM16);
+ ndspChnSetInterp(ctr->channel, NDSP_INTERP_NONE);
+ ndspChnSetRate(ctr->channel, 32728.0f);
+ ndspChnWaveBufClear(ctr->channel);
+
+ ctr->fifo_size = DSP_SAMPLES_TO_BYTES((*new_rate * MAX(latency, 8)) / 1000);
+
+ ctr->dsp_buf.data_pcm16 = linearAlloc(ctr->fifo_size);
+ memset(ctr->dsp_buf.data_pcm16, 0, ctr->fifo_size);
+ DSP_FlushDataCache(ctr->dsp_buf.data_pcm16, ctr->fifo_size);
+
+ ctr->dsp_buf.looping = true;
+ ctr->dsp_buf.nsamples = DSP_BYTES_TO_SAMPLES(ctr->fifo_size);
+ ndspChnWaveBufAdd(ctr->channel, &ctr->dsp_buf);
+
+ ctr->fifo = fifo_new(ctr->fifo_size);
+
+ if (!(ctr->fifo_lock = slock_new()) ||
+ !(ctr->fifo_avail = scond_new()) ||
+ !(ctr->fifo_done = scond_new()) ||
+ !(ctr->thread = sthread_create(ctr_dsp_audio_loop, ctr)))
+ {
+ RARCH_LOG("[Audio]: thread creation failed.\n");
+ ctr->running = false;
+ ctr_dsp_thread_audio_free(ctr);
+ return NULL;
+ }
+
+ ctr->pos = 0;
+ ctr->playing = true;
+
+ ctr->frame_time = (retro_time_t)roundf(1000000 * 4481134.0 / (*new_rate * 8192.0));
+
+ ndspSetMasterVol(1.0);
+
+ return ctr;
+}
+
+static void ctr_dsp_thread_audio_free(void *data)
+{
+ ctr_dsp_thread_audio_t* ctr = (ctr_dsp_thread_audio_t*)data;
+ if (!ctr)
+ return;
+
+ if (ctr->running)
+ {
+ ctr->running = false;
+ scond_signal(ctr->fifo_avail);
+ }
+
+ if (ctr->thread)
+ sthread_join(ctr->thread);
+
+ scond_free(ctr->fifo_avail);
+ scond_free(ctr->fifo_done);
+ slock_free(ctr->fifo_lock);
+
+ if (ctr->fifo)
+ {
+ fifo_free(ctr->fifo);
+ ctr->fifo = NULL;
+ }
+
+ ndspChnWaveBufClear(ctr->channel);
+
+ linearFree(ctr->dsp_buf.data_pcm16);
+
+ free(ctr);
+ ndspExit();
+ ctr = NULL;
+}
+
+static ssize_t ctr_dsp_thread_audio_write(void *data, const void *buf, size_t size)
+{
+ size_t avail, written;
+ ctr_dsp_thread_audio_t * ctr = (ctr_dsp_thread_audio_t*)data;
+
+ if (!ctr || !ctr->running)
+ return 0;
+
+ if (ctr->nonblocking)
+ {
+ slock_lock(ctr->fifo_lock);
+ avail = FIFO_WRITE_AVAIL(ctr->fifo);
+ written = MIN(avail, size);
+ if (written > 0)
+ {
+ fifo_write(ctr->fifo, buf, written);
+ scond_signal(ctr->fifo_avail);
+ }
+ slock_unlock(ctr->fifo_lock);
+ }
+ else
+ {
+ written = 0;
+ while (written < size && ctr->running)
+ {
+ slock_lock(ctr->fifo_lock);
+ avail = FIFO_WRITE_AVAIL(ctr->fifo);
+ if (avail == 0)
+ {
+ if (ctr->running)
+ {
+ /* Wait a maximum of one frame, skip the write if the thread is still busy */
+ if (!scond_wait_timeout(ctr->fifo_done, ctr->fifo_lock, ctr->frame_time)) {
+ slock_unlock(ctr->fifo_lock);
+ break;
+ }
+ }
+ slock_unlock(ctr->fifo_lock);
+ }
+ else
+ {
+ size_t write_amt = MIN(size - written, avail);
+ fifo_write(ctr->fifo, (const char*)buf + written, write_amt);
+ scond_signal(ctr->fifo_avail);
+ slock_unlock(ctr->fifo_lock);
+ written += write_amt;
+ }
+ }
+ }
+
+ return written;
+}
+
+static bool ctr_dsp_thread_audio_stop(void *data)
+{
+ ctr_dsp_thread_audio_t* ctr = (ctr_dsp_thread_audio_t*)data;
+
+ if (!ctr)
+ return false;
+
+ ndspSetMasterVol(0.0);
+ ctr->playing = false;
+
+ return true;
+}
+
+static bool ctr_dsp_thread_audio_alive(void *data)
+{
+ ctr_dsp_thread_audio_t* ctr = (ctr_dsp_thread_audio_t*)data;
+
+ if (!ctr)
+ return false;
+
+ return ctr->playing;
+}
+
+static bool ctr_dsp_thread_audio_start(void *data, bool is_shutdown)
+{
+ ctr_dsp_thread_audio_t* ctr = (ctr_dsp_thread_audio_t*)data;
+
+ if (!ctr)
+ return false;
+
+ /* Prevents restarting audio when the menu
+ * is toggled off on shutdown */
+ if (is_shutdown)
+ return true;
+
+ ndspSetMasterVol(1.0);
+ ctr->playing = true;
+
+ return true;
+}
+
+static void ctr_dsp_thread_audio_set_nonblock_state(void *data, bool state)
+{
+ ctr_dsp_thread_audio_t* ctr = (ctr_dsp_thread_audio_t*)data;
+ if (ctr)
+ ctr->nonblocking = state;
+}
+
+static bool ctr_dsp_thread_audio_use_float(void *data)
+{
+ (void)data;
+ return false;
+}
+
+static size_t ctr_dsp_thread_audio_write_avail(void *data)
+{
+ size_t val;
+ ctr_dsp_thread_audio_t* ctr = (ctr_dsp_thread_audio_t*)data;
+
+ slock_lock(ctr->fifo_lock);
+ val = FIFO_WRITE_AVAIL(ctr->fifo);
+ slock_unlock(ctr->fifo_lock);
+
+ return val;
+}
+
+static size_t ctr_dsp_thread_audio_buffer_size(void *data)
+{
+ ctr_dsp_thread_audio_t* ctr = (ctr_dsp_thread_audio_t*)data;
+ return ctr->fifo_size;
+}
+
+audio_driver_t audio_ctr_dsp_thread = {
+ ctr_dsp_thread_audio_init,
+ ctr_dsp_thread_audio_write,
+ ctr_dsp_thread_audio_stop,
+ ctr_dsp_thread_audio_start,
+ ctr_dsp_thread_audio_alive,
+ ctr_dsp_thread_audio_set_nonblock_state,
+ ctr_dsp_thread_audio_free,
+ ctr_dsp_thread_audio_use_float,
+ "dsp_thread",
+ NULL,
+ NULL,
+ ctr_dsp_thread_audio_write_avail,
+ ctr_dsp_thread_audio_buffer_size
+};
diff --git a/frontend/drivers/platform_ctr.c b/frontend/drivers/platform_ctr.c
index c22b588279..d77d681712 100644
--- a/frontend/drivers/platform_ctr.c
+++ b/frontend/drivers/platform_ctr.c
@@ -482,8 +482,10 @@ static void frontend_ctr_init(void* data)
if (csndInit() != 0)
audio_ctr_csnd = audio_null;
ctr_check_dspfirm();
- if (ndspInit() != 0)
+ if (ndspInit() != 0) {
audio_ctr_dsp = audio_null;
+ audio_ctr_dsp_thread = audio_null;
+ }
cfguInit();
ptmuInit();
mcuHwcInit();
diff --git a/gfx/drivers/ctr_gfx.c b/gfx/drivers/ctr_gfx.c
index 6acf98a047..d9506c4216 100644
--- a/gfx/drivers/ctr_gfx.c
+++ b/gfx/drivers/ctr_gfx.c
@@ -567,12 +567,6 @@ static bool ctr_frame(void* data, const void* frame,
return true;
}
- if(!aptMainLoop())
- {
- command_event(CMD_EVENT_QUIT, NULL);
- return true;
- }
-
state_tmp = hidKeysDown();
hidTouchRead(&state_tmp_touch);
if((state_tmp & KEY_TOUCH) && (state_tmp_touch.py < 120))
diff --git a/gfx/video_thread_wrapper.c b/gfx/video_thread_wrapper.c
index faeaf028eb..1a9e47dac0 100644
--- a/gfx/video_thread_wrapper.c
+++ b/gfx/video_thread_wrapper.c
@@ -829,7 +829,11 @@ static bool video_thread_init(thread_video_t *thr,
max_size = info.input_scale * RARCH_SCALE_BASE;
max_size *= max_size;
max_size *= info.rgb32 ? sizeof(uint32_t) : sizeof(uint16_t);
+#ifdef _3DS
+ thr->frame.buffer = linearMemAlign(max_size, 0x80);
+#else
thr->frame.buffer = (uint8_t*)malloc(max_size);
+#endif
if (!thr->frame.buffer)
return false;
@@ -949,7 +953,11 @@ static void video_thread_free(void *data)
#if defined(HAVE_MENU)
free(thr->texture.frame);
#endif
+#ifdef _3DS
+ linearFree(thr->frame.buffer);
+#else
free(thr->frame.buffer);
+#endif
slock_free(thr->frame.lock);
slock_free(thr->lock);
scond_free(thr->cond_cmd);
diff --git a/griffin/griffin.c b/griffin/griffin.c
index 9d8c02a606..969f75f4d8 100644
--- a/griffin/griffin.c
+++ b/griffin/griffin.c
@@ -897,6 +897,7 @@ AUDIO
#elif defined(_3DS)
#include "../audio/drivers/ctr_csnd_audio.c"
#include "../audio/drivers/ctr_dsp_audio.c"
+#include "../audio/drivers/ctr_dsp_thread_audio.c"
#endif
#ifdef HAVE_XAUDIO
diff --git a/libretro-common/rthreads/ctr_pthread.h b/libretro-common/rthreads/ctr_pthread.h
index 3d5fd80943..985a3648ef 100644
--- a/libretro-common/rthreads/ctr_pthread.h
+++ b/libretro-common/rthreads/ctr_pthread.h
@@ -30,7 +30,7 @@
#include
#include
-#define STACKSIZE (4 * 1024)
+#define STACKSIZE (32 * 1024)
#ifndef PTHREAD_SCOPE_PROCESS
/* An earlier version of devkitARM does not define the pthread types. Can remove in r54+. */
@@ -39,10 +39,16 @@ typedef Thread pthread_t;
typedef LightLock pthread_mutex_t;
typedef void* pthread_mutexattr_t;
typedef int pthread_attr_t;
-typedef LightEvent pthread_cond_t;
+typedef uint32_t pthread_cond_t;
typedef int pthread_condattr_t;
#endif
+typedef struct {
+ uint32_t semaphore;
+ LightLock lock;
+ uint32_t waiting;
+} cond_t;
+
/* libctru threads return void but pthreads return void pointer */
static bool mutex_inited = false;
static LightLock safe_double_thread_launch;
@@ -60,11 +66,18 @@ static INLINE int pthread_create(pthread_t *thread,
{
s32 prio = 0;
Thread new_ctr_thread;
+ int procnum = -2; // use default cpu
+ bool isNew3DS;
+
+ APT_CheckNew3DS(&isNew3DS);
+
+ if (isNew3DS)
+ procnum = 2;
if (!mutex_inited)
{
- LightLock_Init(&safe_double_thread_launch);
- mutex_inited = true;
+ LightLock_Init(&safe_double_thread_launch);
+ mutex_inited = true;
}
/*Must wait if attempting to launch 2 threads at once to prevent corruption of function pointer*/
@@ -73,12 +86,12 @@ static INLINE int pthread_create(pthread_t *thread,
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
start_routine_jump = start_routine;
- new_ctr_thread = threadCreate(ctr_thread_launcher, arg, STACKSIZE, prio - 1, -1/*No affinity, use any CPU*/, false);
+ new_ctr_thread = threadCreate(ctr_thread_launcher, arg, STACKSIZE, prio - 1, procnum, false);
if (!new_ctr_thread)
{
- LightLock_Unlock(&safe_double_thread_launch);
- return EAGAIN;
+ LightLock_Unlock(&safe_double_thread_launch);
+ return EAGAIN;
}
*thread = (pthread_t)new_ctr_thread;
@@ -105,7 +118,8 @@ static INLINE int pthread_mutex_destroy(pthread_mutex_t *mutex)
static INLINE int pthread_mutex_lock(pthread_mutex_t *mutex)
{
- return LightLock_TryLock((LightLock *)mutex);
+ LightLock_Lock((LightLock *)mutex);
+ return 0;
}
static INLINE int pthread_mutex_unlock(pthread_mutex_t *mutex)
@@ -119,6 +133,8 @@ static INLINE void pthread_exit(void *retval)
/*Yes the pointer to int cast is not ideal*/
/*threadExit((int)retval);*/
(void)retval;
+
+ threadExit(0);
}
static INLINE int pthread_detach(pthread_t thread)
@@ -131,7 +147,12 @@ static INLINE int pthread_detach(pthread_t thread)
static INLINE int pthread_join(pthread_t thread, void **retval)
{
/*retval is ignored*/
- return threadJoin((Thread)thread, U64_MAX);
+ if(threadJoin((Thread)thread, INT64_MAX))
+ return -1;
+
+ threadFree((Thread)thread);
+
+ return 0;
}
static INLINE int pthread_mutex_trylock(pthread_mutex_t *mutex)
@@ -142,51 +163,109 @@ static INLINE int pthread_mutex_trylock(pthread_mutex_t *mutex)
static INLINE int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex)
{
- LightEvent_Wait((LightEvent *)cond);
+ cond_t *cond_data = (cond_t *)*cond;
+ LightLock_Lock(&cond_data->lock);
+ cond_data->waiting++;
+ LightLock_Unlock(mutex);
+ LightLock_Unlock(&cond_data->lock);
+ svcWaitSynchronization(cond_data->semaphore, INT64_MAX);
+ LightLock_Lock(mutex);
return 0;
}
static INLINE int pthread_cond_timedwait(pthread_cond_t *cond,
pthread_mutex_t *mutex, const struct timespec *abstime)
{
- for (;;)
- {
- struct timespec now = {0};
- /* Missing clock_gettime*/
- struct timeval tm;
+ struct timespec now = {0};
+ /* Missing clock_gettime*/
+ struct timeval tm;
+ int retval = 0;
- gettimeofday(&tm, NULL);
- now.tv_sec = tm.tv_sec;
- now.tv_nsec = tm.tv_usec * 1000;
- if (LightEvent_TryWait((LightEvent *)cond) != 0 || now.tv_sec > abstime->tv_sec || (now.tv_sec == abstime->tv_sec && now.tv_nsec > abstime->tv_nsec))
- break;
+ cond_t *cond_data = (cond_t *)*cond;
+ LightLock_Lock(&cond_data->lock);
+ cond_data->waiting++;
+
+ gettimeofday(&tm, NULL);
+ now.tv_sec = tm.tv_sec;
+ now.tv_nsec = tm.tv_usec * 1000;
+ s64 timeout = (abstime->tv_sec - now.tv_sec) * 1000000000 + (abstime->tv_nsec - now.tv_nsec);
+
+ if (timeout >= 0) {
+ LightLock_Unlock(mutex);
+ LightLock_Unlock(&cond_data->lock);
+ if (svcWaitSynchronization(cond_data->semaphore, timeout))
+ retval = ETIMEDOUT;
+
+ LightLock_Lock(mutex);
+ } else {
+ retval = ETIMEDOUT;
}
- return 0;
+ return retval;
}
static INLINE int pthread_cond_init(pthread_cond_t *cond,
const pthread_condattr_t *attr)
{
- LightEvent_Init((LightEvent *)cond, RESET_ONESHOT);
+ cond_t *cond_data = calloc(1, sizeof(cond_t));
+ if (!cond_data)
+ goto error;
+
+ if (svcCreateSemaphore(&cond_data->semaphore, 0, 1))
+ goto error;
+
+ LightLock_Init(&cond_data->lock);
+ cond_data->waiting = 0;
+ *cond = (pthread_cond_t)cond_data;
return 0;
+
+ error:
+ svcCloseHandle(cond_data->semaphore);
+ if (cond_data)
+ free(cond_data);
+ return -1;
}
static INLINE int pthread_cond_signal(pthread_cond_t *cond)
{
- LightEvent_Signal((LightEvent *)cond);
+ int32_t count;
+ cond_t *cond_data = (cond_t *)*cond;
+ LightLock_Lock(&cond_data->lock);
+ if (cond_data->waiting) {
+ cond_data->waiting--;
+ LightLock_Unlock(&cond_data->lock);
+ svcReleaseSemaphore(&count, cond_data->semaphore, 1);
+ } else {
+ LightLock_Unlock(&cond_data->lock);
+ }
return 0;
}
static INLINE int pthread_cond_broadcast(pthread_cond_t *cond)
{
- LightEvent_Signal((LightEvent *)cond);
+ int32_t count;
+ cond_t *cond_data = (cond_t *)*cond;
+ LightLock_Lock(&cond_data->lock);
+ while (cond_data->waiting) {
+ cond_data->waiting--;
+ LightLock_Unlock(&cond_data->lock);
+ svcReleaseSemaphore(&count, cond_data->semaphore, 1);
+ LightLock_Lock(&cond_data->lock);
+ }
+ LightLock_Unlock(&cond_data->lock);
+
return 0;
}
static INLINE int pthread_cond_destroy(pthread_cond_t *cond)
{
- /*nothing to destroy*/
+ if (*cond) {
+ cond_t *cond_data = (cond_t *)*cond;
+
+ svcCloseHandle(cond_data->semaphore);
+ free(cond_data);
+ }
+ *cond = 0;
return 0;
}
diff --git a/retroarch.c b/retroarch.c
index 3a6907b8d4..f183993631 100644
--- a/retroarch.c
+++ b/retroarch.c
@@ -379,6 +379,7 @@ static const audio_driver_t *audio_drivers[] = {
#ifdef _3DS
&audio_ctr_csnd,
&audio_ctr_dsp,
+ &audio_ctr_dsp_thread,
#endif
#ifdef SWITCH
&audio_switch,
@@ -38776,6 +38777,13 @@ static enum runloop_state runloop_check_state(
return RUNLOOP_STATE_QUIT;
#endif
+#ifdef _3DS
+ /* Should be called once per frame */
+ if (!aptMainLoop())
+ return RUNLOOP_STATE_QUIT;
+#endif
+
+
BIT256_CLEAR_ALL_PTR(¤t_bits);
p_rarch->input_driver_block_libretro_input = false;
diff --git a/retroarch.h b/retroarch.h
index 4021f75847..d4005c5eff 100644
--- a/retroarch.h
+++ b/retroarch.h
@@ -605,6 +605,7 @@ extern audio_driver_t audio_psp;
extern audio_driver_t audio_ps2;
extern audio_driver_t audio_ctr_csnd;
extern audio_driver_t audio_ctr_dsp;
+extern audio_driver_t audio_ctr_dsp_thread;
extern audio_driver_t audio_switch;
extern audio_driver_t audio_switch_thread;
extern audio_driver_t audio_switch_libnx_audren;