RetroArch/rewind.c

777 lines
20 KiB
C
Raw Normal View History

2012-04-21 21:13:50 +00:00
/* RetroArch - A frontend for libretro.
2014-01-01 00:50:59 +00:00
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
2016-01-10 03:06:50 +00:00
* Copyright (C) 2011-2016 - Daniel De Matteis
2015-01-07 16:46:50 +00:00
* Copyright (C) 2014-2015 - Alfred Agrell
*
2012-04-21 21:13:50 +00:00
* RetroArch is free software: you can redistribute it and/or modify it under the terms
2011-01-31 15:06:46 +00:00
* 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.
*
2012-04-21 21:13:50 +00:00
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
2011-01-31 15:06:46 +00:00
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
2012-04-21 21:31:57 +00:00
* You should have received a copy of the GNU General Public License along with RetroArch.
2011-01-31 15:06:46 +00:00
* If not, see <http://www.gnu.org/licenses/>.
*/
#define __STDC_LIMIT_MACROS
2011-01-31 15:06:46 +00:00
#include <stdlib.h>
#include <string.h>
2015-09-04 19:11:00 +00:00
#include <retro_inline.h>
2015-09-04 19:11:00 +00:00
#include "rewind.h"
2016-02-07 12:18:48 +00:00
#include "configuration.h"
#include "msg_hash.h"
2015-12-01 00:47:26 +00:00
#include "movie.h"
2015-12-05 15:54:03 +00:00
#include "libretro_version_1.h"
2016-02-07 12:18:48 +00:00
#include "runloop.h"
2015-09-04 19:11:00 +00:00
#include "performance.h"
2015-11-23 11:03:38 +00:00
#include "verbosity.h"
2015-11-23 18:40:09 +00:00
#include "audio/audio_driver.h"
2011-01-31 15:06:46 +00:00
/* This makes Valgrind throw errors if a core overflows its savestate size. */
/* Keep it off unless you're chasing a core bug, it slows things down. */
#define STRICT_BUF_SIZE 0
2015-09-15 17:02:39 +00:00
#ifndef UINT16_MAX
#define UINT16_MAX 0xffff
2011-11-01 20:28:32 +00:00
#endif
2015-07-02 16:24:55 +00:00
2015-09-15 17:02:39 +00:00
#ifndef UINT32_MAX
#define UINT32_MAX 0xffffffffu
2011-11-01 20:28:32 +00:00
#endif
#undef CPU_X86
#if defined(__x86_64__) || defined(__i386__) || defined(__i486__) || defined(__i686__)
#define CPU_X86
2011-11-01 20:28:32 +00:00
#endif
2014-09-09 16:31:44 +00:00
/* Other arches SIGBUS (usually) on unaligned accesses. */
#ifndef CPU_X86
#define NO_UNALIGNED_MEM
#endif
2015-09-27 00:28:21 +00:00
struct state_manager
{
uint8_t *data;
size_t capacity;
/* Reading and writing is done here here. */
uint8_t *head;
/* If head comes close to this, discard a frame. */
uint8_t *tail;
uint8_t *thisblock;
uint8_t *nextblock;
/* This one is rounded up from reset::blocksize. */
size_t blocksize;
/* size_t + (blocksize + 131071) / 131072 *
* (blocksize + u16 + u16) + u16 + u32 + size_t
* (yes, the math is a bit ugly). */
size_t maxcompsize;
unsigned entries;
bool thisblock_valid;
#if STRICT_BUF_SIZE
size_t debugsize;
uint8_t *debugblock;
#endif
2015-09-27 00:28:21 +00:00
};
2014-09-09 16:31:44 +00:00
/* Format per frame (pseudocode): */
#if 0
size nextstart;
repeat {
uint16 numchanged; /* everything is counted in units of uint16 */
if (numchanged)
{
uint16 numunchanged; /* skip these before handling numchanged */
uint16[numchanged] changeddata;
}
else
{
uint32 numunchanged;
if (!numunchanged)
break;
}
}
size thisstart;
#endif
2015-11-30 21:29:16 +00:00
struct state_manager_rewind_state
{
/* Rewind support. */
state_manager_t *state;
size_t size;
};
static struct state_manager_rewind_state rewind_state;
2015-11-30 21:25:13 +00:00
static bool frame_is_reversed;
/* Returns the maximum compressed size of a savestate.
* It is very likely to compress to far less. */
static size_t state_manager_raw_maxsize(size_t uncomp)
{
2015-07-02 03:18:41 +00:00
/* bytes covered by a compressed block */
2015-09-15 17:02:39 +00:00
const int maxcblkcover = UINT16_MAX * sizeof(uint16_t);
2015-07-02 03:18:41 +00:00
/* uncompressed size, rounded to 16 bits */
size_t uncomp16 = (uncomp + sizeof(uint16_t) - 1) & -sizeof(uint16_t);
2015-07-02 03:18:41 +00:00
/* number of blocks */
size_t maxcblks = (uncomp + maxcblkcover - 1) / maxcblkcover;
return uncomp16 + maxcblks * sizeof(uint16_t) * 2 /* two u16 overhead per block */ + sizeof(uint16_t) *
3; /* three u16 to end it */
}
/*
* See state_manager_raw_compress for information about this.
* When you're done with it, send it to free().
*/
static void *state_manager_raw_alloc(size_t len, uint16_t uniq)
{
size_t len16 = (len + sizeof(uint16_t) - 1) & -sizeof(uint16_t);
uint16_t *ret = (uint16_t*)calloc(len16 + sizeof(uint16_t) * 4 + 16, 1);
/* Force in a different byte at the end, so we don't need to check
* bounds in the innermost loop (it's expensive).
*
* There is also a large amount of data that's the same, to stop
* the other scan.
*
* There is also some padding at the end. This is so we don't
* read outside the buffer end if we're reading in large blocks;
*
* It doesn't make any difference to us, but sacrificing 16 bytes to get
* Valgrind happy is worth it. */
ret[len16/sizeof(uint16_t) + 3] = uniq;
return ret;
}
#if __SSE2__
#if defined(__GNUC__)
static INLINE int compat_ctz(unsigned x)
{
return __builtin_ctz(x);
}
#else
/* Only checks at nibble granularity,
* because that's what we need. */
static INLINE int compat_ctz(unsigned x)
{
if (x & 0x000f)
return 0;
if (x & 0x00f0)
return 4;
if (x & 0x0f00)
return 8;
if (x & 0xf000)
return 12;
return 16;
}
#endif
#include <emmintrin.h>
/* There's no equivalent in libc, you'd think so ...
* std::mismatch exists, but it's not optimized at all. */
static INLINE size_t find_change(const uint16_t *a, const uint16_t *b)
{
const __m128i *a128 = (const __m128i*)a;
const __m128i *b128 = (const __m128i*)b;
for (;;)
{
__m128i v0 = _mm_loadu_si128(a128);
__m128i v1 = _mm_loadu_si128(b128);
__m128i c = _mm_cmpeq_epi32(v0, v1);
uint32_t mask = _mm_movemask_epi8(c);
if (mask != 0xffff) /* Something has changed, figure out where. */
{
size_t ret = (((uint8_t*)a128 - (uint8_t*)a) |
(compat_ctz(~mask))) >> 1;
return ret | (a[ret] == b[ret]);
}
a128++;
b128++;
}
}
#else
static INLINE size_t find_change(const uint16_t *a, const uint16_t *b)
{
const uint16_t *a_org = a;
#ifdef NO_UNALIGNED_MEM
while (((uintptr_t)a & (sizeof(size_t) - 1)) && *a == *b)
{
a++;
b++;
}
if (*a == *b)
#endif
{
const size_t *a_big = (const size_t*)a;
const size_t *b_big = (const size_t*)b;
while (*a_big == *b_big)
{
a_big++;
b_big++;
}
a = (const uint16_t*)a_big;
b = (const uint16_t*)b_big;
while (*a == *b)
{
a++;
b++;
}
}
return a - a_org;
}
#endif
static INLINE size_t find_same(const uint16_t *a, const uint16_t *b)
{
const uint16_t *a_org = a;
#ifdef NO_UNALIGNED_MEM
if (((uintptr_t)a & (sizeof(uint32_t) - 1)) && *a != *b)
{
a++;
b++;
}
if (*a != *b)
#endif
{
/* With this, it's random whether two consecutive identical
* words are caught.
*
* Luckily, compression rate is the same for both cases, and
* three is always caught.
*
* (We prefer to miss two-word blocks, anyways; fewer iterations
* of the outer loop, as well as in the decompressor.) */
const uint32_t *a_big = (const uint32_t*)a;
const uint32_t *b_big = (const uint32_t*)b;
while (*a_big != *b_big)
{
a_big++;
b_big++;
}
a = (const uint16_t*)a_big;
b = (const uint16_t*)b_big;
if (a != a_org && a[-1] == b[-1])
{
a--;
b--;
}
}
return a - a_org;
}
/*
* Takes two savestates and creates a patch that turns 'src' into 'dst'.
* Both 'src' and 'dst' must be returned from state_manager_raw_alloc(),
* with the same 'len', and different 'uniq'.
*
* 'patch' must be size 'state_manager_raw_maxsize(len)' or more.
* Returns the number of bytes actually written to 'patch'.
*/
static size_t state_manager_raw_compress(const void *src,
2015-07-02 16:24:55 +00:00
const void *dst, size_t len, void *patch)
{
2015-07-02 03:18:41 +00:00
const uint16_t *old16 = (const uint16_t*)src;
const uint16_t *new16 = (const uint16_t*)dst;
uint16_t *compressed16 = (uint16_t*)patch;
2015-07-02 16:24:55 +00:00
size_t num16s = (len + sizeof(uint16_t) - 1)
/ sizeof(uint16_t);
while (num16s)
{
2015-06-29 19:39:00 +00:00
size_t i, changed;
size_t skip = find_change(old16, new16);
if (skip >= num16s)
break;
old16 += skip;
new16 += skip;
num16s -= skip;
2015-09-15 17:02:39 +00:00
if (skip > UINT16_MAX)
{
2015-09-15 17:02:39 +00:00
if (skip > UINT32_MAX)
{
/* This will make it scan the entire thing again,
* but it only hits on 8GB unchanged data anyways,
* and if you're doing that, you've got bigger problems. */
skip = UINT32_MAX;
}
*compressed16++ = 0;
*compressed16++ = skip;
*compressed16++ = skip >> 16;
skip = 0;
continue;
}
2015-06-29 19:39:00 +00:00
changed = find_same(old16, new16);
2015-09-15 17:02:39 +00:00
if (changed > UINT16_MAX)
changed = UINT16_MAX;
*compressed16++ = changed;
*compressed16++ = skip;
for (i = 0; i < changed; i++)
compressed16[i] = old16[i];
old16 += changed;
new16 += changed;
num16s -= changed;
compressed16 += changed;
}
compressed16[0] = 0;
compressed16[1] = 0;
compressed16[2] = 0;
return (uint8_t*)(compressed16+3) - (uint8_t*)patch;
}
/*
* Takes 'patch' from a previous call to 'state_manager_raw_compress'
* and applies it to 'data' ('src' from that call),
* yielding 'dst' in that call.
*
* If the given arguments do not match a previous call to
* state_manager_raw_compress(), anything at all can happen.
*/
static void state_manager_raw_decompress(const void *patch,
2015-07-02 03:18:41 +00:00
size_t patchlen, void *data, size_t datalen)
{
2015-07-02 03:18:41 +00:00
uint16_t *out16 = (uint16_t*)data;
const uint16_t *patch16 = (const uint16_t*)patch;
(void)patchlen;
(void)datalen;
for (;;)
{
uint16_t numchanged = *(patch16++);
if (numchanged)
{
uint16_t i;
out16 += *patch16++;
/* We could do memcpy, but it seems that memcpy has a
* constant-per-call overhead that actually shows up.
*
* Our average size in here seems to be 8 or something.
* Therefore, we do something with lower overhead. */
for (i = 0; i < numchanged; i++)
out16[i] = patch16[i];
patch16 += numchanged;
out16 += numchanged;
}
else
{
uint32_t numunchanged = patch16[0] | (patch16[1] << 16);
if (!numunchanged)
break;
patch16 += 2;
out16 += numunchanged;
}
}
}
2014-09-09 16:31:44 +00:00
/* The start offsets point to 'nextstart' of any given compressed frame.
* Each uint16 is stored native endian; anything that claims any other
* endianness refers to the endianness of this specific item.
* The uint32 is stored little endian.
*
* Each size value is stored native endian if alignment is not enforced;
* if it is, they're little endian.
*
* The start of the buffer contains a size pointing to the end of the
* buffer; the end points to its start.
*
* Wrapping is handled by returning to the start of the buffer if the
* compressed data could potentially hit the edge;
*
* if the compressed data could potentially overwrite the tail pointer,
* the tail retreats until it can no longer collide.
*
* This means that on average, ~2 * maxcompsize is
* unused at any given moment. */
2014-09-09 16:31:44 +00:00
/* These are called very few constant times per frame,
* keep it as simple as possible. */
static INLINE void write_size_t(void *ptr, size_t val)
{
memcpy(ptr, &val, sizeof(val));
}
2014-09-09 16:31:44 +00:00
static INLINE size_t read_size_t(const void *ptr)
{
size_t ret;
2015-01-09 20:39:44 +00:00
memcpy(&ret, ptr, sizeof(ret));
return ret;
}
2016-02-01 11:46:46 +00:00
static void state_manager_free(state_manager_t *state)
{
if (!state)
return;
free(state->data);
free(state->thisblock);
free(state->nextblock);
#if STRICT_BUF_SIZE
free(state->debugblock);
#endif
free(state);
}
2016-02-01 11:46:46 +00:00
static state_manager_t *state_manager_new(size_t state_size, size_t buffer_size)
{
2011-12-24 12:46:12 +00:00
state_manager_t *state = (state_manager_t*)calloc(1, sizeof(*state));
2015-01-09 20:39:44 +00:00
2011-01-31 15:06:46 +00:00
if (!state)
return NULL;
2015-07-02 03:18:41 +00:00
state->blocksize = (state_size + sizeof(uint16_t) - 1) & ~sizeof(uint16_t);
/* the compressed data is surrounded by pointers to the other side */
state->maxcompsize = state_manager_raw_maxsize(state_size) + sizeof(size_t) * 2;
2015-07-02 03:18:41 +00:00
state->data = (uint8_t*)malloc(buffer_size);
2016-02-01 11:46:46 +00:00
if (!state->data)
goto error;
2015-07-02 03:18:41 +00:00
state->thisblock = (uint8_t*)state_manager_raw_alloc(state_size, 0);
state->nextblock = (uint8_t*)state_manager_raw_alloc(state_size, 1);
2016-02-01 11:46:46 +00:00
if (!state->thisblock || !state->nextblock)
2011-01-31 15:06:46 +00:00
goto error;
state->capacity = buffer_size;
state->head = state->data + sizeof(size_t);
state->tail = state->data + sizeof(size_t);
2011-01-31 15:06:46 +00:00
#if STRICT_BUF_SIZE
state->debugsize = state_size;
state->debugblock = (uint8_t*)malloc(state_size);
#endif
2011-01-31 15:06:46 +00:00
return state;
error:
state_manager_free(state);
2016-02-01 11:46:46 +00:00
2011-01-31 15:06:46 +00:00
return NULL;
}
static bool state_manager_pop(state_manager_t *state, const void **data)
{
2015-01-09 20:39:44 +00:00
size_t start;
2015-06-12 15:10:16 +00:00
uint8_t *out = NULL;
const uint8_t *compressed = NULL;
2015-01-09 20:39:44 +00:00
*data = NULL;
if (state->thisblock_valid)
2011-02-26 00:13:52 +00:00
{
state->thisblock_valid = false;
state->entries--;
*data = state->thisblock;
2011-02-26 00:13:52 +00:00
return true;
}
2011-01-31 15:06:46 +00:00
if (state->head == state->tail)
2011-01-31 15:06:46 +00:00
return false;
2015-01-09 20:39:44 +00:00
start = read_size_t(state->head - sizeof(size_t));
state->head = state->data + start;
2011-01-31 15:06:46 +00:00
2015-01-09 20:39:44 +00:00
compressed = state->data + start + sizeof(size_t);
out = state->thisblock;
2016-01-26 04:56:53 +00:00
state_manager_raw_decompress(compressed,
state->maxcompsize, out, state->blocksize);
2011-01-31 15:06:46 +00:00
state->entries--;
*data = state->thisblock;
2011-01-31 15:06:46 +00:00
return true;
}
static void state_manager_push_where(state_manager_t *state, void **data)
2011-01-31 15:06:46 +00:00
{
2014-09-02 03:57:53 +00:00
/* We need to ensure we have an uncompressed copy of the last
* pushed state, or we could end up applying a 'patch' to wrong
* savestate, and that'd blow up rather quickly. */
if (!state->thisblock_valid)
{
const void *ignored;
if (state_manager_pop(state, &ignored))
{
state->thisblock_valid = true;
state->entries++;
}
}
*data = state->nextblock;
#if STRICT_BUF_SIZE
*data = state->debugblock;
#endif
2011-01-31 15:06:46 +00:00
}
static void state_manager_push_do(state_manager_t *state)
{
#if STRICT_BUF_SIZE
memcpy(state->nextblock, state->debugblock, state->debugsize);
#endif
2015-09-20 08:02:47 +00:00
static struct retro_perf_counter gen_deltas = {0};
2015-06-26 17:55:09 +00:00
uint8_t *swap = NULL;
if (state->thisblock_valid)
2011-01-31 15:06:46 +00:00
{
2015-06-26 17:55:09 +00:00
const uint8_t *oldb, *newb;
uint8_t *compressed;
size_t headpos, tailpos, remaining;
if (state->capacity < sizeof(size_t) + state->maxcompsize)
return;
recheckcapacity:;
2015-06-26 17:55:09 +00:00
headpos = state->head - state->data;
tailpos = state->tail - state->data;
remaining = (tailpos + state->capacity -
2014-09-02 03:57:53 +00:00
sizeof(size_t) - headpos - 1) % state->capacity + 1;
if (remaining <= state->maxcompsize)
{
state->tail = state->data + read_size_t(state->tail);
state->entries--;
goto recheckcapacity;
}
2015-09-20 08:02:47 +00:00
rarch_perf_init(&gen_deltas, "gen_deltas");
retro_perf_start(&gen_deltas);
2015-06-26 17:55:09 +00:00
oldb = state->thisblock;
newb = state->nextblock;
compressed = state->head + sizeof(size_t);
2016-01-26 04:56:53 +00:00
compressed += state_manager_raw_compress(oldb, newb,
state->blocksize, compressed);
if (compressed - state->data + state->maxcompsize > state->capacity)
{
compressed = state->data;
if (state->tail == state->data + sizeof(size_t))
state->tail = state->data + read_size_t(state->tail);
}
write_size_t(compressed, state->head-state->data);
compressed += sizeof(size_t);
write_size_t(state->head, compressed-state->data);
state->head = compressed;
2015-09-20 08:02:47 +00:00
retro_perf_stop(&gen_deltas);
2011-01-31 15:06:46 +00:00
}
else
state->thisblock_valid = true;
2011-01-31 15:06:46 +00:00
2015-06-26 17:55:09 +00:00
swap = state->thisblock;
state->thisblock = state->nextblock;
state->nextblock = swap;
state->entries++;
2011-01-31 15:06:46 +00:00
}
#if 0
static void state_manager_capacity(state_manager_t *state,
2014-09-02 03:57:53 +00:00
unsigned *entries, size_t *bytes, bool *full)
2011-01-31 15:06:46 +00:00
{
2015-01-09 20:39:44 +00:00
size_t headpos = state->head - state->data;
size_t tailpos = state->tail - state->data;
2014-09-02 03:57:53 +00:00
size_t remaining = (tailpos + state->capacity -
sizeof(size_t) - headpos - 1) % state->capacity + 1;
if (entries)
*entries = state->entries;
if (bytes)
*bytes = state->capacity-remaining;
if (full)
*full = remaining <= state->maxcompsize * 2;
2011-01-31 15:06:46 +00:00
}
#endif
2015-04-11 11:36:33 +00:00
void init_rewind(void)
{
2016-01-27 06:23:50 +00:00
retro_ctx_serialize_info_t serial_info;
retro_ctx_size_info_t info;
2015-04-11 11:36:33 +00:00
void *state = NULL;
settings_t *settings = config_get_ptr();
2015-11-30 21:29:16 +00:00
if (!settings->rewind_enable || rewind_state.state)
2015-04-11 11:36:33 +00:00
return;
2015-11-22 14:20:10 +00:00
if (audio_driver_ctl(RARCH_AUDIO_CTL_HAS_CALLBACK, NULL))
2015-04-11 11:36:33 +00:00
{
2015-07-02 16:23:18 +00:00
RARCH_ERR("%s.\n", msg_hash_to_str(MSG_REWIND_INIT_FAILED));
2015-04-11 11:36:33 +00:00
return;
}
core_ctl(CORE_CTL_RETRO_SERIALIZE_SIZE, &info);
rewind_state.size = info.size;
2015-04-11 11:36:33 +00:00
2015-11-30 21:29:16 +00:00
if (!rewind_state.size)
2015-04-11 11:36:33 +00:00
{
2015-07-02 16:23:18 +00:00
RARCH_ERR("%s.\n",
msg_hash_to_str(MSG_REWIND_INIT_FAILED_THREADED_AUDIO));
2015-04-11 11:36:33 +00:00
return;
}
2015-07-02 16:23:18 +00:00
RARCH_LOG("%s: %u MB\n",
msg_hash_to_str(MSG_REWIND_INIT),
2015-04-11 11:36:33 +00:00
(unsigned)(settings->rewind_buffer_size / 1000000));
2015-11-30 21:29:16 +00:00
rewind_state.state = state_manager_new(rewind_state.size,
2015-04-11 11:36:33 +00:00
settings->rewind_buffer_size);
2015-11-30 21:29:16 +00:00
if (!rewind_state.state)
2015-07-02 16:23:18 +00:00
RARCH_WARN("%s.\n", msg_hash_to_str(MSG_REWIND_INIT_FAILED));
2015-04-11 11:36:33 +00:00
2015-11-30 21:29:16 +00:00
state_manager_push_where(rewind_state.state, &state);
2016-01-27 06:23:50 +00:00
serial_info.data = state;
serial_info.size = rewind_state.size;
core_ctl(CORE_CTL_RETRO_SERIALIZE, &serial_info);
2015-11-30 21:29:16 +00:00
state_manager_push_do(rewind_state.state);
2015-04-11 11:36:33 +00:00
}
bool state_manager_frame_is_reversed(void)
{
return frame_is_reversed;
}
2016-01-21 02:39:05 +00:00
static void state_manager_set_frame_is_reversed(bool value)
{
frame_is_reversed = value;
}
2015-11-30 21:25:13 +00:00
void state_manager_event_deinit(void)
{
2015-11-30 21:29:16 +00:00
if (rewind_state.state)
state_manager_free(rewind_state.state);
rewind_state.state = NULL;
rewind_state.size = 0;
2015-11-30 21:25:13 +00:00
}
/**
* check_rewind:
* @pressed : was rewind key pressed or held?
*
* Checks if rewind toggle/hold was being pressed and/or held.
**/
void state_manager_check_rewind(bool pressed)
{
static bool first = true;
settings_t *settings = config_get_ptr();
if (state_manager_frame_is_reversed())
{
audio_driver_ctl(RARCH_AUDIO_CTL_FRAME_IS_REVERSE, NULL);
state_manager_set_frame_is_reversed(false);
}
if (first)
{
first = false;
return;
}
2015-11-30 21:29:16 +00:00
if (!rewind_state.state)
2015-11-30 21:25:13 +00:00
return;
if (pressed)
{
const void *buf = NULL;
2015-11-30 21:29:16 +00:00
if (state_manager_pop(rewind_state.state, &buf))
2015-11-30 21:25:13 +00:00
{
2016-01-27 06:23:50 +00:00
retro_ctx_serialize_info_t serial_info;
2015-11-30 21:25:13 +00:00
state_manager_set_frame_is_reversed(true);
audio_driver_ctl(RARCH_AUDIO_CTL_SETUP_REWIND, NULL);
runloop_msg_queue_push(
msg_hash_to_str(MSG_REWINDING), 0,
2016-01-26 04:56:53 +00:00
runloop_ctl(RUNLOOP_CTL_IS_PAUSED, NULL)
? 1 : 30, true);
2016-01-27 06:23:50 +00:00
serial_info.data_const = buf;
serial_info.size = rewind_state.size;
core_ctl(CORE_CTL_RETRO_UNSERIALIZE, &serial_info);
2015-11-30 21:25:13 +00:00
2015-11-30 23:08:02 +00:00
if (bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL))
bsv_movie_ctl(BSV_MOVIE_CTL_FRAME_REWIND, NULL);
2015-11-30 21:25:13 +00:00
}
else
runloop_msg_queue_push(
msg_hash_to_str(MSG_REWIND_REACHED_END),
2015-11-30 21:25:13 +00:00
0, 30, true);
}
else
{
static unsigned cnt = 0;
cnt = (cnt + 1) % (settings->rewind_granularity ?
settings->rewind_granularity : 1); /* Avoid possible SIGFPE. */
2015-11-30 23:08:02 +00:00
if ((cnt == 0) || bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL))
2015-11-30 21:25:13 +00:00
{
2016-01-27 06:23:50 +00:00
retro_ctx_serialize_info_t serial_info;
2015-11-30 21:25:13 +00:00
static struct retro_perf_counter rewind_serialize = {0};
void *state = NULL;
2015-11-30 21:29:16 +00:00
state_manager_push_where(rewind_state.state, &state);
2015-11-30 21:25:13 +00:00
rarch_perf_init(&rewind_serialize, "rewind_serialize");
retro_perf_start(&rewind_serialize);
2016-01-27 06:23:50 +00:00
serial_info.data = state;
serial_info.size = rewind_state.size;
core_ctl(CORE_CTL_RETRO_SERIALIZE, &serial_info);
2015-11-30 21:25:13 +00:00
retro_perf_stop(&rewind_serialize);
2015-11-30 21:29:16 +00:00
state_manager_push_do(rewind_state.state);
2015-11-30 21:25:13 +00:00
}
}
2016-01-27 02:18:18 +00:00
core_ctl(CORE_CTL_SET_CBS_REWIND, NULL);
2015-11-30 21:25:13 +00:00
}