From 679b71e250ce6557f05b57882747029ab6af7edc Mon Sep 17 00:00:00 2001 From: Justin Weiss Date: Fri, 21 Feb 2020 21:17:31 -0800 Subject: [PATCH] Add async CD access --- Makefile | 2 +- frontend/3ds/3ds_utils.h | 14 ++ frontend/3ds/pthread.h | 81 +++++++++- frontend/libretro.c | 12 ++ frontend/libretro_core_options.h | 14 +- libpcsxcore/cdriso.c | 251 +++++++++++++++++++++++++++++++ libpcsxcore/psxcommon.h | 1 + 7 files changed, 371 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index f633665e..98660501 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ CFLAGS += -Wall -Iinclude -ffast-math ifeq ($(DEBUG), 1) CFLAGS += -O0 -ggdb else -ifeq ($(platform), vita) +ifeq ($(platform), $(filter $(platform), vita ctr)) CFLAGS += -O3 -DNDEBUG else CFLAGS += -O2 -DNDEBUG diff --git a/frontend/3ds/3ds_utils.h b/frontend/3ds/3ds_utils.h index cad4cbe8..c9b9d7f4 100644 --- a/frontend/3ds/3ds_utils.h +++ b/frontend/3ds/3ds_utils.h @@ -24,6 +24,20 @@ void threadFree(int32_t thread); void threadExit(int32_t rc) __attribute__((noreturn)); int32_t svcGetSystemInfo(int64_t* out, uint32_t type, int32_t param); + +int32_t svcCreateSemaphore(uint32_t *sem, int32_t initial_count, uint32_t max_count); +int32_t svcReleaseSemaphore(int32_t *count, uint32_t sem, int32_t release_count); +int32_t svcWaitSynchronization(uint32_t handle, int64_t nanoseconds); + +typedef int32_t LightLock; + +void LightLock_Init(LightLock* lock); +void LightLock_Lock(LightLock* lock); +int LightLock_TryLock(LightLock* lock); +void LightLock_Unlock(LightLock* lock); + +int32_t APT_CheckNew3DS(bool *out); + int32_t svcBackdoor(int32_t (*callback)(void)); #define DEBUG_HOLD() do{printf("%s@%s:%d.\n",__FUNCTION__, __FILE__, __LINE__);fflush(stdout);wait_for_input();}while(0) diff --git a/frontend/3ds/pthread.h b/frontend/3ds/pthread.h index 9f43707e..cc3c9654 100644 --- a/frontend/3ds/pthread.h +++ b/frontend/3ds/pthread.h @@ -9,15 +9,34 @@ #include "3ds_utils.h" #define CTR_PTHREAD_STACK_SIZE 0x10000 +#define FALSE 0 typedef int32_t pthread_t; typedef int pthread_attr_t; +typedef LightLock pthread_mutex_t; +typedef int pthread_mutexattr_t; + +typedef struct { + uint32_t semaphore; + LightLock lock; + uint32_t waiting; +} pthread_cond_t; + +typedef int pthread_condattr_t; + static inline int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg) { - thread = threadCreate(start_routine, arg, CTR_PTHREAD_STACK_SIZE, 0x25, -2, FALSE); - return 1; + int procnum = -2; // use default cpu + bool isNew3DS; + APT_CheckNew3DS(&isNew3DS); + + if (isNew3DS) + procnum = 2; + + *thread = threadCreate(start_routine, arg, CTR_PTHREAD_STACK_SIZE, 0x25, procnum, FALSE); + return 0; } @@ -41,6 +60,64 @@ static inline void pthread_exit(void *retval) threadExit(0); } +static inline int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr) { + LightLock_Init(mutex); + return 0; +} + +static inline int pthread_mutex_lock(pthread_mutex_t *mutex) { + LightLock_Lock(mutex); + return 0; +} + +static inline int pthread_mutex_unlock(pthread_mutex_t *mutex) { + LightLock_Unlock(mutex); + return 0; +} + +static inline int pthread_mutex_destroy(pthread_mutex_t *mutex) { + return 0; +} + +static inline int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr) { + if (svcCreateSemaphore(&cond->semaphore, 0, 1)) + goto error; + + LightLock_Init(&cond->lock); + cond->waiting = 0; + return 0; + + error: + svcCloseHandle(cond->semaphore); + return -1; +} + +static inline int pthread_cond_signal(pthread_cond_t *cond) { + int32_t count; + LightLock_Lock(&cond->lock); + if (cond->waiting) { + cond->waiting--; + svcReleaseSemaphore(&count, cond->semaphore, 1); + } + LightLock_Unlock(&cond->lock); + return 0; +} + +static inline int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *lock) { + LightLock_Lock(&cond->lock); + cond->waiting++; + LightLock_Unlock(lock); + LightLock_Unlock(&cond->lock); + svcWaitSynchronization(cond->semaphore, INT64_MAX); + LightLock_Lock(lock); + return 0; +} + +static inline int pthread_cond_destroy(pthread_cond_t *cond) { + svcCloseHandle(cond->semaphore); + return 0; +} + #endif //_3DS_PTHREAD_WRAP__ diff --git a/frontend/libretro.c b/frontend/libretro.c index cc443fed..bd7dc438 100644 --- a/frontend/libretro.c +++ b/frontend/libretro.c @@ -1852,6 +1852,18 @@ static void update_variables(bool in_flight) Config.VSyncWA = 1; } +#ifndef _WIN32 + var.value = NULL; + var.key = "pcsx_rearmed_async_cd"; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || var.value) + { + if (strcmp(var.value, "async") == 0) + Config.AsyncCD = 1; + else + Config.AsyncCD = 0; + } +#endif + var.value = NULL; var.key = "pcsx_rearmed_noxadecoding"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || var.value) diff --git a/frontend/libretro_core_options.h b/frontend/libretro_core_options.h index badd856d..6f26c715 100644 --- a/frontend/libretro_core_options.h +++ b/frontend/libretro_core_options.h @@ -961,7 +961,19 @@ struct retro_core_option_definition option_defs_us[] = { }, "disabled", }, - +#ifndef _WIN32 + { + "pcsx_rearmed_async_cd", + "CD Access Method (Restart)", + "Select method used to read data from content disk images. 'Synchronous' mimics original hardware. 'Asynchronous' can reduce stuttering on devices with slow storage.", + { + { "sync", "Synchronous" }, + { "async", "Asynchronous" }, + { NULL, NULL}, + }, + "sync", + }, +#endif /* ADVANCED OPTIONS */ { "pcsx_rearmed_noxadecoding", diff --git a/libpcsxcore/cdriso.c b/libpcsxcore/cdriso.c index 8171674a..07adbe67 100644 --- a/libpcsxcore/cdriso.c +++ b/libpcsxcore/cdriso.c @@ -1163,6 +1163,194 @@ static int opensbifile(const char *isoname) { return LoadSBI(sbiname, s); } +#ifdef _WIN32 +static void readThreadStop() {} +static void readThreadStart() {} +#else +static pthread_t read_thread_id; + +static pthread_cond_t read_thread_msg_avail; +static pthread_cond_t read_thread_msg_done; +static pthread_mutex_t read_thread_msg_lock; + +static pthread_cond_t sectorbuffer_cond; +static pthread_mutex_t sectorbuffer_lock; + +static boolean read_thread_running = FALSE; +static int read_thread_sector_start = -1; +static int read_thread_sector_end = -1; + +typedef struct { + int sector; + long ret; + unsigned char data[CD_FRAMESIZE_RAW]; +} SectorBufferEntry; + +#define SECTOR_BUFFER_SIZE 4096 + +static SectorBufferEntry *sectorbuffer; +static size_t sectorbuffer_index; + +int (*sync_cdimg_read_func)(FILE *f, unsigned int base, void *dest, int sector); +unsigned char *(*sync_CDR_getBuffer)(void); + +static unsigned char * CALLBACK ISOgetBuffer_async(void); +static int cdread_async(FILE *f, unsigned int base, void *dest, int sector); + +static void *readThreadMain(void *param) { + int max_sector = -1; + int requested_sector_start = -1; + int requested_sector_end = -1; + int last_read_sector = -1; + int index = 0; + + int ra_sector = -1; + int max_ra = 128; + int initial_ra = 1; + int speedmult_ra = 4; + + int ra_count = 0; + int how_far_ahead = 0; + + unsigned char tmpdata[CD_FRAMESIZE_RAW]; + long ret; + + max_sector = msf2sec(ti[numtracks].start) + msf2sec(ti[numtracks].length); + + while(1) { + pthread_mutex_lock(&read_thread_msg_lock); + + // If we don't have readahead and we don't have a sector request, wait for one. + // If we still have readahead to go, don't block, just keep going. + // And if we ever have a sector request pending, acknowledge and reset it. + + if (!ra_count) { + if (read_thread_sector_start == -1 && read_thread_running) { + pthread_cond_wait(&read_thread_msg_avail, &read_thread_msg_lock); + } + } + + if (read_thread_sector_start != -1) { + requested_sector_start = read_thread_sector_start; + requested_sector_end = read_thread_sector_end; + read_thread_sector_start = -1; + read_thread_sector_end = -1; + pthread_cond_signal(&read_thread_msg_done); + } + + pthread_mutex_unlock(&read_thread_msg_lock); + + if (!read_thread_running) + break; + + // Readahead code, based on the implementation in mednafen psx's cdromif.cpp + if (requested_sector_start != -1) { + if (last_read_sector != -1 && last_read_sector == (requested_sector_start - 1)) { + how_far_ahead = ra_sector - requested_sector_end; + + if(how_far_ahead <= max_ra) + ra_count = (max_ra - how_far_ahead + 1 ? max_ra - how_far_ahead + 1 : speedmult_ra); + else + ra_count++; + } else if (requested_sector_end != last_read_sector) { + ra_sector = requested_sector_end; + ra_count = initial_ra; + } + + last_read_sector = requested_sector_end; + } + + // check for end of CD + if (ra_count && ra_sector >= max_sector) { + ra_count = 0; + } + + if (ra_count) { + + index = ra_sector % SECTOR_BUFFER_SIZE; + pthread_mutex_lock(§orbuffer_lock); + if (sectorbuffer[index].sector != ra_sector) { + pthread_mutex_unlock(§orbuffer_lock); + + ret = sync_cdimg_read_func(cdHandle, 0, tmpdata, ra_sector); + + pthread_mutex_lock(§orbuffer_lock); + sectorbuffer[index].ret = ret; + sectorbuffer[index].sector = ra_sector; + memcpy(sectorbuffer[index].data, tmpdata, CD_FRAMESIZE_RAW); + } + pthread_cond_signal(§orbuffer_cond); + pthread_mutex_unlock(§orbuffer_lock); + + ra_sector++; + ra_count--; + } + } + + return NULL; +} + +static void readThreadStop() { + if (read_thread_running == TRUE) { + read_thread_running = FALSE; + pthread_cond_signal(&read_thread_msg_avail); + pthread_join(read_thread_id, NULL); + } + + pthread_cond_destroy(&read_thread_msg_done); + pthread_cond_destroy(&read_thread_msg_avail); + pthread_mutex_destroy(&read_thread_msg_lock); + + pthread_cond_destroy(§orbuffer_cond); + pthread_mutex_destroy(§orbuffer_lock); + + CDR_getBuffer = sync_CDR_getBuffer; + cdimg_read_func = sync_cdimg_read_func; + + free(sectorbuffer); + sectorbuffer = NULL; +} + +static void readThreadStart() { + SysPrintf("Starting async CD thread\n"); + + if (read_thread_running == TRUE) + return; + + read_thread_running = TRUE; + read_thread_sector_start = -1; + read_thread_sector_end = -1; + sectorbuffer_index = 0; + + sectorbuffer = calloc(SECTOR_BUFFER_SIZE, sizeof(SectorBufferEntry)); + if(!sectorbuffer) + goto error; + + sectorbuffer[0].sector = -1; // Otherwise we might think we've already fetched sector 0! + + sync_CDR_getBuffer = CDR_getBuffer; + CDR_getBuffer = ISOgetBuffer_async; + sync_cdimg_read_func = cdimg_read_func; + cdimg_read_func = cdread_async; + + if (pthread_cond_init(&read_thread_msg_avail, NULL) || + pthread_cond_init(&read_thread_msg_done, NULL) || + pthread_mutex_init(&read_thread_msg_lock, NULL) || + pthread_cond_init(§orbuffer_cond, NULL) || + pthread_mutex_init(§orbuffer_lock, NULL) || + pthread_create(&read_thread_id, NULL, readThreadMain, NULL)) + goto error; + + return; + + error: + SysPrintf("Error starting async CD thread\n"); + SysPrintf("Falling back to sync\n"); + + readThreadStop(); +} +#endif + static int cdread_normal(FILE *f, unsigned int base, void *dest, int sector) { fseek(f, base + sector * CD_FRAMESIZE_RAW, SEEK_SET); @@ -1323,6 +1511,54 @@ static int cdread_2048(FILE *f, unsigned int base, void *dest, int sector) return ret; } +#ifndef _WIN32 + +static int cdread_async(FILE *f, unsigned int base, void *dest, int sector) { + boolean found = FALSE; + int i = sector % SECTOR_BUFFER_SIZE; + long ret; + + if (f != cdHandle || base != 0 || dest != cdbuffer) { + // Async reads are only supported for cdbuffer, so call the sync + // function directly. + return sync_cdimg_read_func(f, base, dest, sector); + } + + pthread_mutex_lock(&read_thread_msg_lock); + + // Only wait if we're not trying to read the next sector and + // sector_start is set (meaning the last request hasn't been + // processed yet) + while(read_thread_sector_start != -1 && read_thread_sector_end + 1 != sector) { + pthread_cond_wait(&read_thread_msg_done, &read_thread_msg_lock); + } + + if (read_thread_sector_start == -1) + read_thread_sector_start = sector; + + read_thread_sector_end = sector; + pthread_cond_signal(&read_thread_msg_avail); + pthread_mutex_unlock(&read_thread_msg_lock); + + do { + pthread_mutex_lock(§orbuffer_lock); + if (sectorbuffer[i].sector == sector) { + sectorbuffer_index = i; + ret = sectorbuffer[i].ret; + found = TRUE; + } + + if (!found) { + pthread_cond_wait(§orbuffer_cond, §orbuffer_lock); + } + pthread_mutex_unlock(§orbuffer_lock); + } while (!found); + + return ret; +} + +#endif + static unsigned char * CALLBACK ISOgetBuffer_compr(void) { return compr_img->buff_raw[compr_img->sector_in_blk] + 12; } @@ -1333,6 +1569,17 @@ static unsigned char * CALLBACK ISOgetBuffer_chd(void) { } #endif +#ifndef _WIN32 +static unsigned char * CALLBACK ISOgetBuffer_async(void) { + unsigned char *buffer; + pthread_mutex_lock(§orbuffer_lock); + buffer = sectorbuffer[sectorbuffer_index].data; + pthread_mutex_unlock(§orbuffer_lock); + return buffer + 12; +} + +#endif + static unsigned char * CALLBACK ISOgetBuffer(void) { return cdbuffer + 12; } @@ -1473,6 +1720,9 @@ static long CALLBACK ISOopen(void) { cdda_cur_sector = 0; cdda_file_offset = 0; + if (Config.AsyncCD) { + readThreadStart(); + } return 0; } @@ -1518,6 +1768,7 @@ static long CALLBACK ISOclose(void) { memset(cdbuffer, 0, sizeof(cdbuffer)); CDR_getBuffer = ISOgetBuffer; + readThreadStop(); return 0; } diff --git a/libpcsxcore/psxcommon.h b/libpcsxcore/psxcommon.h index 19c8d771..cb417d47 100644 --- a/libpcsxcore/psxcommon.h +++ b/libpcsxcore/psxcommon.h @@ -120,6 +120,7 @@ typedef struct { boolean Mdec; boolean PsxAuto; boolean Cdda; + boolean AsyncCD; boolean HLE; boolean SlowBoot; boolean Debug; -- 2.39.2