diff --git a/Makefile b/Makefile index dd5454b..cad71fa 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ FRONTEND_SUPPORTS_RGB565=1 FORCE_32BIT_ARCH=0 HAVE_MMAP=0 HAVE_MMAP_WIN32=0 +USE_LIBCO=1 UNAME=$(shell uname -a) @@ -400,13 +401,13 @@ else ifeq ($(platform), emscripten) # GCW0 else ifeq ($(platform), gcw0) TARGET := $(TARGET_NAME)_libretro.so - CC ?= /opt/gcw0-toolchain/usr/bin/mipsel-linux-gcc - CXX ?= /opt/gcw0-toolchain/usr/bin/mipsel-linux-g++ - AR ?= /opt/gcw0-toolchain/usr/bin/mipsel-linux-ar + CC = /opt/gcw0-toolchain/usr/bin/mipsel-linux-gcc + CXX = /opt/gcw0-toolchain/usr/bin/mipsel-linux-g++ + AR = /opt/gcw0-toolchain/usr/bin/mipsel-linux-ar SHARED := -shared -nostdlib -Wl,--version-script=link.T fpic := -fPIC - CFLAGS += $(PTHREAD_FLAGS) -DHAVE_MKDIR - CFLAGS += -ffast-math -march=mips32 -mtune=mips32r2 -mhard-float + CFLAGS += -fomit-frame-pointer -ffast-math -march=mips32 -mtune=mips32r2 -mhard-float + USE_LIBCO = 0 # Windows else @@ -445,6 +446,12 @@ OBJECTS := $(SOURCES_C:.c=.o) $(SOURCES_ASM:.S=.o) DEFINES := -DHAVE_STRINGS_H -DHAVE_STDINT_H -DHAVE_INTTYPES_H -D__LIBRETRO__ -DINLINE=inline -Wall +ifeq ($(USE_LIBCO), 1) +DEFINES += -DUSE_LIBCO +else +LDFLAGS += -lpthread +endif + ifeq ($(HAVE_DYNAREC), 1) DEFINES += -DHAVE_DYNAREC endif diff --git a/Makefile.common b/Makefile.common index 92fd9fa..ea75b78 100644 --- a/Makefile.common +++ b/Makefile.common @@ -13,9 +13,13 @@ SOURCES_C := $(CORE_DIR)/main.c \ $(CORE_DIR)/sound.c \ $(CORE_DIR)/cheats.c \ $(CORE_DIR)/libretro.c \ - $(CORE_DIR)/libco/libco.c \ $(CORE_DIR)/gba_cc_lut.c +ifeq ($(USE_LIBCO), 1) +SOURCES_C += $(CORE_DIR)/libco/libco.c +else +SOURCES_C += $(CORE_DIR)/retro_emu_thread.c +endif ifeq ($(HAVE_DYNAREC), 1) SOURCES_C += $(CORE_DIR)/cpu_threaded.c diff --git a/libretro.c b/libretro.c index 6790982..51c40e7 100644 --- a/libretro.c +++ b/libretro.c @@ -5,6 +5,7 @@ #include #include "common.h" #include "libco.h" +#include "retro_emu_thread.h" #include "libretro.h" #include "libretro_core_options.h" #include "memmap.h" @@ -73,8 +74,10 @@ static retro_environment_t environ_cb; struct retro_perf_callback perf_cb; +#if defined(USE_LIBCO) static cothread_t main_thread; static cothread_t cpu_thread; +#endif int dynarec_enable; int use_libretro_save_method = 0; @@ -92,14 +95,23 @@ static bool post_process_mix = false; void switch_to_main_thread(void) { +#if defined(USE_LIBCO) co_switch(main_thread); +#else + retro_switch_thread(); +#endif } static inline void switch_to_cpu_thread(void) { +#if defined(USE_LIBCO) co_switch(cpu_thread); +#else + retro_switch_thread(); +#endif } +#if defined(USE_LIBCO) static void cpu_thread_entry(void) { #ifdef HAVE_DYNAREC @@ -108,16 +120,29 @@ static void cpu_thread_entry(void) #endif execute_arm(execute_cycles); } +#endif static inline void init_context_switch(void) { +#if defined(USE_LIBCO) main_thread = co_active(); cpu_thread = co_create(0x20000, cpu_thread_entry); +#else + if (!retro_init_emu_thread(dynarec_enable, execute_cycles)) + if (log_cb) + log_cb(RETRO_LOG_ERROR, "[gpSP]: Failed to initialize emulation thread!\n"); +#endif } static inline void deinit_context_switch(void) { +#if defined(USE_LIBCO) co_delete(cpu_thread); +#else + retro_cancel_emu_thread(); + retro_join_emu_thread(); + retro_deinit_emu_thread(); +#endif } #if defined(PSP) @@ -933,6 +958,20 @@ void retro_run(void) { bool updated = false; +#if !defined(USE_LIBCO) + if (!retro_is_emu_thread_initialized()) + { + environ_cb(RETRO_ENVIRONMENT_SHUTDOWN, NULL); + return; + } + if (retro_emu_thread_exited()) + { + environ_cb(RETRO_ENVIRONMENT_SHUTDOWN, NULL); + retro_join_emu_thread(); + return; + } +#endif + update_input(); input_poll_cb(); diff --git a/retro_emu_thread.c b/retro_emu_thread.c new file mode 100644 index 0000000..2df7db7 --- /dev/null +++ b/retro_emu_thread.c @@ -0,0 +1,175 @@ +// This is copyrighted software. More information is at the end of this file. +#include "retro_emu_thread.h" + +#include + +static pthread_t main_thread; +static pthread_t emu_thread; +static pthread_mutex_t emu_mutex; +static pthread_mutex_t main_mutex; +static pthread_cond_t emu_cv; +static pthread_cond_t main_cv; +static bool emu_keep_waiting = true; +static bool main_keep_waiting = true; +static bool emu_has_exited = false; +static bool emu_thread_canceled = false; +static bool emu_thread_initialized = false; + +static void* retro_run_emulator(void *args) +{ + char *args_str = (char *)args; + bool dynarec = (*args_str++ == 1) ? true : false; + u32 cycles = strtol(args_str, NULL, 10); + + emu_has_exited = false; + emu_thread_canceled = false; + +#if defined(HAVE_DYNAREC) + if (dynarec) + execute_arm_translate(cycles); +#endif + execute_arm(cycles); + + emu_has_exited = true; + return NULL; +} + +static void retro_switch_to_emu_thread() +{ + pthread_mutex_lock(&emu_mutex); + emu_keep_waiting = false; + pthread_mutex_unlock(&emu_mutex); + pthread_mutex_lock(&main_mutex); + pthread_cond_signal(&emu_cv); + + main_keep_waiting = true; + while (main_keep_waiting) + { + pthread_cond_wait(&main_cv, &main_mutex); + } + pthread_mutex_unlock(&main_mutex); +} + +static void retro_switch_to_main_thread() +{ + pthread_mutex_lock(&main_mutex); + main_keep_waiting = false; + pthread_mutex_unlock(&main_mutex); + pthread_mutex_lock(&emu_mutex); + pthread_cond_signal(&main_cv); + + emu_keep_waiting = true; + while (emu_keep_waiting) + { + pthread_cond_wait(&emu_cv, &emu_mutex); + } + pthread_mutex_unlock(&emu_mutex); +} + +void retro_switch_thread() +{ + if (pthread_self() == main_thread) + retro_switch_to_emu_thread(); + else + retro_switch_to_main_thread(); +} + +bool retro_init_emu_thread(bool dynarec, u32 cycles) +{ + char args[256]; + args[0] = '\0'; + + if (emu_thread_initialized) + return true; + + /* Keep this very simple: + * - First character: dynarec, 0/1 + * - Remaining characters: cycles */ + snprintf(args, sizeof(args), " %u", cycles); + args[0] = dynarec ? 1 : 0; + + main_thread = pthread_self(); + if (pthread_mutex_init(&main_mutex, NULL)) + goto main_mutex_error; + if (pthread_mutex_init(&emu_mutex, NULL)) + goto emu_mutex_error; + if (pthread_cond_init(&main_cv, NULL)) + goto main_cv_error; + if (pthread_cond_init(&emu_cv, NULL)) + goto emu_cv_error; + if (pthread_create(&emu_thread, NULL, retro_run_emulator, args)) + goto emu_thread_error; + + emu_thread_initialized = true; + return true; + +emu_thread_error: + pthread_cond_destroy(&emu_cv); +emu_cv_error: + pthread_cond_destroy(&main_cv); +main_cv_error: + pthread_mutex_destroy(&emu_mutex); +emu_mutex_error: + pthread_mutex_destroy(&main_mutex); +main_mutex_error: + return false; +} + +void retro_deinit_emu_thread() +{ + if (!emu_thread_initialized) + return; + + pthread_mutex_destroy(&main_mutex); + pthread_mutex_destroy(&emu_mutex); + pthread_cond_destroy(&main_cv); + pthread_cond_destroy(&emu_cv); + emu_thread_initialized = false; +} + +bool retro_is_emu_thread_initialized() +{ + return emu_thread_initialized; +} + +void retro_join_emu_thread() +{ + static bool is_joined = false; + if (is_joined) + return; + + pthread_join(emu_thread, NULL); + is_joined = true; +} + +void retro_cancel_emu_thread() +{ + if (emu_thread_canceled) + return; + + pthread_cancel(emu_thread); + emu_thread_canceled = true; +} + +bool retro_emu_thread_exited() +{ + return emu_has_exited; +} + +/* + +Copyright (C) 2020 Nikos Chantziaras + +This program 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 +Foundation, either version 2 of the License, or (at your option) any later +version. + +This program 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 +this program. If not, see . + +*/ diff --git a/retro_emu_thread.h b/retro_emu_thread.h new file mode 100644 index 0000000..472ae8b --- /dev/null +++ b/retro_emu_thread.h @@ -0,0 +1,51 @@ +#ifndef EMU_THREAD_H +#define EMU_THREAD_H + +#include "common.h" + +/* gpSP doesn't have a top-level main loop that we can use, so instead we run it in its own thread + * and switch between it and the main thread. Calling this function will block the current thread + * and unblock the other. + * + * This function can be called from either the main or the emulation thread. + */ +void retro_switch_thread(void); + +/* Initialize the emulation thread and any related resources. + * + * Only call this function from the main thread. + */ +bool retro_init_emu_thread(bool dynarec, u32 cycles); + +/* Destroy the emulation thread and any related resources. Only call this after the emulation thread + * has finished (or canceled) and joined. + * + * Only call this function from the main thread. + */ +void retro_deinit_emu_thread(void); + +/* Returns true if the emulation thread was initialized successfully. + * + * This function can be called from either the main or the emulation thread. + */ +bool retro_is_emu_thread_initialized(void); + +/* Join the emulation thread. The thread must have exited naturally or been canceled. + * + * Only call this function from the main thread. + */ +void retro_join_emu_thread(void); + +/* Cancel the emulation thread. + * + * Only call this function from the main thread. + */ +void retro_cancel_emu_thread(void); + +/* Returns true if the emulation thread has exited naturally. + * + * This function can be called from either the main or the emulation thread. + */ +bool retro_emu_thread_exited(void); + +#endif