From 32fc5d370babaa4d24b08449bd49174485db8b48 Mon Sep 17 00:00:00 2001 From: Dwedit Date: Mon, 23 Apr 2018 20:16:53 -0500 Subject: [PATCH] Fixes for Savestates * Savestate Performance Fix - no longer does giant memory allocations and copies * Loadstate Performance Fix - no longer does N^2 string comparisons * Fast Savestates (excludes text labels from the savestates, 20% speedup) * Minor Savestate Performance Fix, no longer uses snprintf when strncpy will do. Only applies to the text labels, no effect on fast savestates. --- libretro.cpp | 111 +++++++++++++++++++++++++++++++++------------ mednafen/state.cpp | 82 +++++++++++++++++++++++---------- 2 files changed, 141 insertions(+), 52 deletions(-) diff --git a/libretro.cpp b/libretro.cpp index b4ab633c..d7b05403 100644 --- a/libretro.cpp +++ b/libretro.cpp @@ -27,6 +27,10 @@ #include #define ISHEXDEC ((codeLine[cursor]>='0') && (codeLine[cursor]<='9')) || ((codeLine[cursor]>='a') && (codeLine[cursor]<='f')) || ((codeLine[cursor]>='A') && (codeLine[cursor]<='F')) +//Fast Save States exclude string labels from variables in the savestate, and are at least 20% faster. +extern bool FastSaveStates; +const int DEFAULT_STATE_SIZE = 16 * 1024 * 1024; + struct retro_perf_callback perf_cb; retro_get_cpu_features_t perf_get_cpu_features_cb = NULL; retro_log_printf_t log_cb; @@ -3432,6 +3436,17 @@ static uint64_t video_frames, audio_frames; void retro_run(void) { bool updated = false; + //code to implement audio and video disable is not yet implemented + //bool disableVideo = false; + //bool disableAudio = false; + //bool hardDisableAudio = false; + //int flags = 3; + //if (environ_cb(RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE, &flags)) + //{ + // disableVideo = !(flags & 1); + // disableAudio = !(flags & 2); + // hardDisableAudio = !!(flags & 8); + //} rsx_intf_prepare_frame(); @@ -3539,6 +3554,11 @@ void retro_run(void) spec.VideoFormatChanged = false; spec.SoundFormatChanged = false; + //if (disableVideo) + //{ + // spec.skip = true; + //} + EmulateSpecStruct *espec = (EmulateSpecStruct*)&spec; /* start of Emulate */ int32_t timestamp = 0; @@ -3901,41 +3921,72 @@ size_t retro_serialize_size(void) return serialize_size = st.len; } - return serialize_size = 16777216; // 16MB + return serialize_size = DEFAULT_STATE_SIZE; // 16MB +} + +bool UsingFastSavestates() +{ + int flags; + if (environ_cb(RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE, &flags)) + { + return flags & 4; + } + return false; } bool retro_serialize(void *data, size_t size) { - /* it seems that mednafen can realloc pointers sent to it? - since we don't know the disposition of void* data (is it safe to realloc?) we have to manage a new buffer here */ - static bool logged; - StateMem st; - bool ret = false; - uint8_t *_dat = (uint8_t*)malloc(size); - - if (!_dat) - return false; - - st.data = _dat; - st.loc = 0; - st.len = 0; - st.malloced = size; - st.initial_malloc = 0; - - /* there are still some errors with the save states, - * the size seems to change on some games for now - * just log when this happens */ - if (!logged && st.len != size) + if (size == DEFAULT_STATE_SIZE) //16MB buffer reserved { - log_cb(RETRO_LOG_WARN, "warning, save state size has changed\n"); - logged = true; + //actual size is around 3.75MB (3.67MB for fast savestates) rather than 16MB, so 16MB will hold a savestate without worrying about realloc + + //save state in place + StateMem st; + st.data = (uint8_t*)data; + st.len = 0; + st.loc = 0; + st.malloced = size; + st.initial_malloc = 0; + + //fast save states are at least 20% faster + FastSaveStates = UsingFastSavestates(); + bool ret = MDFNSS_SaveSM(&st, 0, 0, NULL, NULL, NULL); + FastSaveStates = false; + return ret; } + else + { + /* it seems that mednafen can realloc pointers sent to it? + since we don't know the disposition of void* data (is it safe to realloc?) we have to manage a new buffer here */ + static bool logged; + StateMem st; + bool ret = false; + uint8_t *_dat = (uint8_t*)malloc(size); - ret = MDFNSS_SaveSM(&st, 0, 0, NULL, NULL, NULL); + if (!_dat) + return false; - memcpy(data,st.data,size); - free(st.data); - return ret; + st.data = _dat; + st.loc = 0; + st.len = 0; + st.malloced = size; + st.initial_malloc = 0; + + /* there are still some errors with the save states, + * the size seems to change on some games for now + * just log when this happens */ + if (!logged && st.len != size) + { + log_cb(RETRO_LOG_WARN, "warning, save state size has changed\n"); + logged = true; + } + + ret = MDFNSS_SaveSM(&st, 0, 0, NULL, NULL, NULL); + + memcpy(data, st.data, size); + free(st.data); + return ret; + } } bool retro_unserialize(const void *data, size_t size) @@ -3948,7 +3999,11 @@ bool retro_unserialize(const void *data, size_t size) st.malloced = 0; st.initial_malloc = 0; - return MDFNSS_LoadSM(&st, 0, 0); + //fast save states are at least 20% faster + FastSaveStates = UsingFastSavestates(); + bool okay = MDFNSS_LoadSM(&st, 0, 0); + FastSaveStates = false; + return okay; } void *retro_get_memory_data(unsigned type) diff --git a/mednafen/state.cpp b/mednafen/state.cpp index d9b6c868..ac7def4f 100644 --- a/mednafen/state.cpp +++ b/mednafen/state.cpp @@ -28,6 +28,10 @@ #define RLSB MDFNSTATE_RLSB //0x80000000 +//Fast Save States exclude string labels from variables in the savestate, and are at least 20% faster. +//Only used for internal savestates which will not be written to a file. +bool FastSaveStates = false; + int32_t smem_read(StateMem *st, void *buffer, uint32_t len) { if ((len + st->loc) > st->len) @@ -134,19 +138,33 @@ static bool SubWrite(StateMem *st, SFORMAT *sf, const char *name_prefix = NULL) int32_t bytesize = sf->size; - char nameo[1 + 256]; - int slen; - - slen = snprintf(nameo + 1, 256, "%s%s", name_prefix ? name_prefix : "", sf->name); - nameo[0] = slen; - - if(slen >= 255) + //exclude text labels from fast savestates + if (!FastSaveStates) { - printf("Warning: state variable name possibly too long: %s %s %s %d\n", sf->name, name_prefix, nameo, slen); - slen = 255; - } + char nameo[1 + 256]; + int slen; - smem_write(st, nameo, 1 + nameo[0]); + //Replace snprintf with strncpy for most cases + if (name_prefix != NULL) + { + slen = snprintf(nameo + 1, 256, "%s%s", name_prefix ? name_prefix : "", sf->name); + } + else + { + slen = strlen(sf->name); + strncpy(nameo + 1, sf->name, 255); + nameo[256] = 0; + } + nameo[0] = slen; + + if (slen >= 255) + { + printf("Warning: state variable name possibly too long: %s %s %s %d\n", sf->name, name_prefix, nameo, slen); + slen = 255; + } + + smem_write(st, nameo, 1 + nameo[0]); + } smem_write32le(st, bytesize); #ifdef MSB_FIRST @@ -250,6 +268,11 @@ static SFORMAT *FindSF(const char *name, SFORMAT *sf) } else { + //for fast savestates, we no longer have the text label in the state, and need to assume that it is the correct one. + if (FastSaveStates) + { + return sf; + } assert(sf->name); if (!strcmp(sf->name, name)) return sf; @@ -297,28 +320,39 @@ static int ReadStateChunk(StateMem *st, SFORMAT *sf, int size) { int temp = st->loc; + uint32_t recorded_size; // In bytes + uint8_t toa[1 + 256]; // Don't change to char unless cast toa[0] to unsigned to smem_read() and other places. + toa[0] = 0; + toa[1] = 0; + while (st->loc < (temp + size)) { - uint32_t recorded_size; // In bytes - uint8_t toa[1 + 256]; // Don't change to char unless cast toa[0] to unsigned to smem_read() and other places. - - if(smem_read(st, toa, 1) != 1) + //exclude text labels from fast savestates + if (!FastSaveStates) { - puts("Unexpected EOF"); - return(0); - } + if (smem_read(st, toa, 1) != 1) + { + puts("Unexpected EOF"); + return(0); + } - if(smem_read(st, toa + 1, toa[0]) != toa[0]) - { - puts("Unexpected EOF?"); - return 0; - } + if (smem_read(st, toa + 1, toa[0]) != toa[0]) + { + puts("Unexpected EOF?"); + return 0; + } - toa[1 + toa[0]] = 0; + toa[1 + toa[0]] = 0; + } smem_read32le(st, &recorded_size); SFORMAT *tmp = FindSF((char*)toa + 1, sf); + //Fix for unnecessary name checks, when we find it in the first slot, don't recheck that slot again. Also necessary for fast savestates to work. + if (tmp == sf) + { + sf++; + } if(tmp) {