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.
This commit is contained in:
Dwedit 2018-04-23 20:16:53 -05:00
parent ea2d49b63d
commit 32fc5d370b
2 changed files with 141 additions and 52 deletions

View File

@ -27,6 +27,10 @@
#include <vector>
#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)

View File

@ -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)
{