Added fully working deterministic save state support (non-portable.)
Rewind is now 100% deterministic, disk save states are still portable.
Added run-ahead support.
This commit is contained in:
byuu 2019-10-15 22:12:10 +09:00
parent a32b6fae74
commit 19f3cdfd5e
23 changed files with 174 additions and 35 deletions

View File

@ -73,6 +73,8 @@ struct Stream {
write(samples);
}
auto serialize(serializer&) -> void;
private:
struct Channel {
vector<Filter> filters;

View File

@ -115,3 +115,11 @@ auto Stream::write(const double samples[]) -> void {
audio.process();
}
auto Stream::serialize(serializer& s) -> void {
for(auto& channel : channels) {
channel.resampler.serialize(s);
}
s.real(inputFrequency);
s.real(outputFrequency);
}

View File

@ -29,7 +29,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "bsnes";
static const string Version = "111.7";
static const string Version = "111.8";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org";

View File

@ -101,6 +101,9 @@ struct Interface {
virtual auto frameSkip() -> uint { return 0; }
virtual auto setFrameSkip(uint frameSkip) -> void {}
virtual auto runAhead() -> bool { return false; }
virtual auto setRunAhead(bool runAhead) -> void {}
};
}

View File

@ -67,7 +67,7 @@ auto CPU::load() -> bool {
auto CPU::power(bool reset) -> void {
WDC65816::power();
create(Enter, system.cpuFrequency());
Thread::create(Enter, system.cpuFrequency());
coprocessors.reset();
PPUcounter::reset();
PPUcounter::scanline = {&CPU::scanline, this};

View File

@ -18,6 +18,7 @@ auto DSP::main() -> void {
int count = spc_dsp.sample_count();
if(count > 0) {
if(!system.runAhead)
for(uint n = 0; n < count; n += 2) {
float left = samplebuffer[n + 0] / 32768.0f;
float right = samplebuffer[n + 1] / 32768.0f;

View File

@ -330,7 +330,15 @@ auto Interface::frameSkip() -> uint {
auto Interface::setFrameSkip(uint frameSkip) -> void {
system.frameSkip = frameSkip;
system.frameCounter = 0;
system.frameCounter = frameSkip;
}
auto Interface::runAhead() -> bool {
return system.runAhead;
}
auto Interface::setRunAhead(bool runAhead) -> void {
system.runAhead = runAhead;
}
}

View File

@ -71,6 +71,9 @@ struct Interface : Emulator::Interface {
auto frameSkip() -> uint override;
auto setFrameSkip(uint frameSkip) -> void override;
auto runAhead() -> bool override;
auto setRunAhead(bool runAhead) -> void override;
};
#include "configuration.hpp"

View File

@ -88,7 +88,7 @@ auto PPU::step(uint clocks) -> void {
auto PPU::main() -> void {
scanline();
if(system.frameCounter == 0) {
if(system.frameCounter == 0 && !system.runAhead) {
uint y = vcounter();
if(y >= 1 && y <= 239) {
step(renderCycle());
@ -127,7 +127,7 @@ auto PPU::scanline() -> void {
}
auto PPU::refresh() -> void {
if(system.frameCounter == 0) {
if(system.frameCounter == 0 && !system.runAhead) {
auto output = this->output;
uint pitch, width, height;
if(!hd()) {

View File

@ -189,6 +189,8 @@ auto PPU::refresh() -> void {
return ppufast.refresh();
}
if(system.runAhead) return;
auto output = this->output;
auto pitch = 512;
auto width = 512;

View File

@ -73,13 +73,13 @@ namespace SuperFamicom {
extern Scheduler scheduler;
struct Thread {
enum : uint { Size = 16_KiB * sizeof(void*) };
enum : uint { Size = 4_KiB * sizeof(void*) };
auto create(auto (*entrypoint)() -> void, uint frequency_) -> void {
if(!thread) {
thread = co_create(Thread::Size, entrypoint);
} else {
co_derive(thread, Thread::Size, entrypoint);
thread = co_derive(thread, Thread::Size, entrypoint);
}
frequency = frequency_;
clock = 0;
@ -95,23 +95,26 @@ namespace SuperFamicom {
}
auto serializeStack(serializer& s) -> void {
auto stack = new uint8_t[Thread::Size];
static uint8_t stack[Thread::Size];
bool active = co_active() == thread;
if(s.mode() == serializer::Size) {
s.array(stack, Thread::Size);
s.boolean(active);
}
if(s.mode() == serializer::Load) {
s.array(stack, Thread::Size);
s.boolean(active);
memory::copy(thread, stack, Thread::Size);
if(active) scheduler.active = thread;
}
if(s.mode() == serializer::Save) {
memory::copy(stack, thread, Thread::Size);
s.array(stack, Thread::Size);
s.boolean(active);
}
delete[] stack;
}
cothread_t thread = nullptr;

View File

@ -1,4 +1,5 @@
auto System::serialize(bool synchronize) -> serializer {
if(!information.serializeSize[synchronize]) return {}; //should never occur
if(synchronize) runToSave();
uint signature = 0x31545342;
@ -38,7 +39,7 @@ auto System::unserialize(serializer& s) -> bool {
if(string{version} != Emulator::SerializerVersion) return false;
if(fastPPU != hacks.fastPPU) return false;
power(/* reset = */ false);
if(synchronize) power(/* reset = */ false);
serializeAll(s, synchronize);
return true;
}

View File

@ -24,17 +24,54 @@ auto System::runToSave() -> void {
//fallback in case of unrecognized method specified
if(method != "Fast" && method != "Strict") method = "Fast";
auto synchronize = [&](cothread_t thread) -> bool {
scheduler.mode = Scheduler::Mode::Synchronize;
scheduler.mode = Scheduler::Mode::Synchronize;
if(method == "Fast") runToSaveFast();
if(method == "Strict") runToSaveStrict();
scheduler.mode = Scheduler::Mode::Run;
scheduler.active = cpu.thread;
}
auto System::runToSaveFast() -> void {
//run the emulator normally until the CPU thread naturally hits a synchronization point
while(true) {
scheduler.enter();
if(scheduler.event == Scheduler::Event::Frame) frameEvent();
if(scheduler.event == Scheduler::Event::Synchronized) {
if(scheduler.active != cpu.thread) continue;
break;
}
if(scheduler.event == Scheduler::Event::Desynchronized) continue;
}
//ignore any desynchronization events to force all other threads to their synchronization points
auto synchronize = [&](cothread_t thread) -> void {
scheduler.active = thread;
while(true) {
scheduler.enter();
if(scheduler.event == Scheduler::Event::Frame) frameEvent();
if(scheduler.event == Scheduler::Event::Synchronized) break;
if(scheduler.event == Scheduler::Event::Desynchronized) {
if(method == "Fast") continue;
if(method == "Strict") return false;
}
if(scheduler.event == Scheduler::Event::Desynchronized) continue;
}
};
synchronize(smp.thread);
synchronize(ppu.thread);
for(auto coprocessor : cpu.coprocessors) {
synchronize(coprocessor->thread);
}
}
auto System::runToSaveStrict() -> void {
//run every thread until it cleanly hits a synchronization point
//if it fails, start resynchronizing every thread again
auto synchronize = [&](cothread_t thread) -> bool {
scheduler.active = thread;
while(true) {
scheduler.enter();
if(scheduler.event == Scheduler::Event::Frame) frameEvent();
if(scheduler.event == Scheduler::Event::Synchronized) break;
if(scheduler.event == Scheduler::Event::Desynchronized) return false;
}
return true;
};
@ -55,9 +92,6 @@ auto System::runToSave() -> void {
break;
}
scheduler.mode = Scheduler::Mode::Run;
scheduler.active = cpu.thread;
}
auto System::frameEvent() -> void {
@ -97,8 +131,6 @@ auto System::load(Emulator::Interface* interface) -> bool {
}
if(cartridge.has.BSMemorySlot) bsmemory.load();
information.serializeSize[0] = serializeInit(0);
information.serializeSize[1] = serializeInit(1);
this->interface = interface;
return information.loaded = true;
}
@ -195,6 +227,9 @@ auto System::power(bool reset) -> void {
controllerPort1.connect(settings.controllerPort1);
controllerPort2.connect(settings.controllerPort2);
expansionPort.connect(settings.expansionPort);
information.serializeSize[0] = serializeInit(0);
information.serializeSize[1] = serializeInit(1);
}
}

View File

@ -10,6 +10,8 @@ struct System {
auto run() -> void;
auto runToSave() -> void;
auto runToSaveFast() -> void;
auto runToSaveStrict() -> void;
auto frameEvent() -> void;
auto load(Emulator::Interface*) -> bool;
@ -23,6 +25,7 @@ struct System {
uint frameSkip = 0;
uint frameCounter = 0;
bool runAhead = 0;
private:
Emulator::Interface* interface = nullptr;

View File

@ -8,8 +8,6 @@ auto InputManager::bindHotkeys() -> void {
static int stateSlot = 1;
static double frequency = 48000.0;
static double volume = 0.0;
static bool fastForwarding = false;
static bool rewinding = false;
hotkeys.append(InputHotkey("Toggle Fullscreen").onPress([] {
program.toggleVideoFullScreen();
@ -32,8 +30,8 @@ auto InputManager::bindHotkeys() -> void {
}));
hotkeys.append(InputHotkey("Rewind").onPress([&] {
if(!emulator->loaded() || fastForwarding) return;
rewinding = true;
if(!emulator->loaded() || program.fastForwarding) return;
program.rewinding = true;
if(program.rewind.frequency == 0) {
program.showMessage("Please enable rewind support in Settings->Emulator first");
} else {
@ -46,7 +44,7 @@ auto InputManager::bindHotkeys() -> void {
Emulator::audio.setVolume(volume * 0.65);
}
}).onRelease([&] {
rewinding = false;
program.rewinding = false;
if(!emulator->loaded()) return;
program.rewindMode(Program::Rewind::Mode::Playing);
program.mute &= ~Program::Mute::Rewind;
@ -84,8 +82,8 @@ auto InputManager::bindHotkeys() -> void {
}));
hotkeys.append(InputHotkey("Fast Forward").onPress([] {
if(!emulator->loaded() || rewinding) return;
fastForwarding = true;
if(!emulator->loaded() || program.rewinding) return;
program.fastForwarding = true;
emulator->setFrameSkip(emulator->configuration("Hacks/PPU/Fast") == "true" ? settings.fastForward.frameSkip : 0);
video.setBlocking(false);
audio.setBlocking(settings.fastForward.limiter != 0);
@ -101,7 +99,7 @@ auto InputManager::bindHotkeys() -> void {
Emulator::audio.setVolume(volume * 0.65);
}
}).onRelease([] {
fastForwarding = false;
program.fastForwarding = false;
if(!emulator->loaded()) return;
emulator->setFrameSkip(0);
video.setBlocking(settings.video.blocking);

View File

@ -93,7 +93,22 @@ auto Program::main() -> void {
}
rewindRun();
emulator->run();
if(!settings.emulator.runAhead.frames || fastForwarding || rewinding) {
emulator->run();
} else {
emulator->setRunAhead(true);
emulator->run();
auto state = emulator->serialize(0);
if(settings.emulator.runAhead.frames >= 2) emulator->run();
if(settings.emulator.runAhead.frames >= 3) emulator->run();
if(settings.emulator.runAhead.frames >= 4) emulator->run();
emulator->setRunAhead(false);
emulator->run();
state.setMode(serializer::Mode::Load);
emulator->unserialize(state);
}
if(emulatorSettings.autoSaveMemory.checked()) {
auto currentTime = chrono::timestamp();
if(currentTime - autoSaveTime >= settings.emulator.autoSaveMemory.interval) {

View File

@ -200,6 +200,9 @@ public:
Rewind = 1 << 4,
};};
uint mute = 0;
bool fastForwarding = false;
bool rewinding = false;
};
extern Program program;

View File

@ -35,7 +35,7 @@ auto Program::rewindRun() -> void {
if(rewind.history.size() >= rewind.length) {
rewind.history.takeFirst();
}
auto s = emulator->serialize(settings.emulator.serialization.synchronize);
auto s = emulator->serialize(0);
rewind.history.append(s);
return;
}

View File

@ -2,8 +2,30 @@ auto EnhancementSettings::create() -> void {
setCollapsible();
setVisible(false);
overclockingLabel.setText("Overclocking").setFont(Font().setBold());
runAheadLabel.setText("Run-Ahead").setFont(Font().setBold());
runAhead0.setText("Disabled").onActivate([&] {
settings.emulator.runAhead.frames = 0;
});
runAhead1.setText("One Frame").onActivate([&] {
settings.emulator.runAhead.frames = 1;
});
runAhead2.setText("Two Frames").onActivate([&] {
settings.emulator.runAhead.frames = 2;
});
runAhead3.setText("Three Frames").onActivate([&] {
settings.emulator.runAhead.frames = 3;
});
runAhead4.setText("Four Frames").onActivate([&] {
settings.emulator.runAhead.frames = 4;
});
if(settings.emulator.runAhead.frames == 0) runAhead0.setChecked();
if(settings.emulator.runAhead.frames == 1) runAhead1.setChecked();
if(settings.emulator.runAhead.frames == 2) runAhead2.setChecked();
if(settings.emulator.runAhead.frames == 3) runAhead3.setChecked();
if(settings.emulator.runAhead.frames == 4) runAhead4.setChecked();
runAheadSpacer.setColor({192, 192, 192});
overclockingLabel.setText("Overclocking").setFont(Font().setBold());
overclockingLayout.setSize({3, 3});
overclockingLayout.column(0).setAlignment(1.0);
overclockingLayout.column(1).setAlignment(0.5);

View File

@ -115,7 +115,7 @@ auto Settings::process(bool load) -> void {
bind(boolean, "Emulator/AutoSaveStateOnUnload", emulator.autoSaveStateOnUnload);
bind(boolean, "Emulator/AutoLoadStateOnLoad", emulator.autoLoadStateOnLoad);
bind(text, "Emulator/Serialization/Method", emulator.serialization.method);
bind(boolean, "Emulator/Serialization/Synchronize", emulator.serialization.synchronize);
bind(natural, "Emulator/RunAhead/Frames", emulator.runAhead.frames);
bind(boolean, "Emulator/Hack/Hotfixes", emulator.hack.hotfixes);
bind(text, "Emulator/Hack/Entropy", emulator.hack.entropy);
bind(natural, "Emulator/Hack/CPU/Overclock", emulator.hack.cpu.overclock);

View File

@ -96,8 +96,10 @@ struct Settings : Markup::Node {
bool autoLoadStateOnLoad = false;
struct Serialization {
string method = "Fast";
bool synchronize = true;
} serialization;
struct RunAhead {
uint frames = 0;
} runAhead;
struct Hack {
bool hotfixes = true;
string entropy = "Low";
@ -330,6 +332,16 @@ struct EnhancementSettings : VerticalLayout {
auto create() -> void;
public:
Label runAheadLabel{this, Size{~0, 0}, 2};
HorizontalLayout runAheadLayout{this, Size{~0, 0}};
RadioLabel runAhead0{&runAheadLayout, Size{0, 0}};
RadioLabel runAhead1{&runAheadLayout, Size{0, 0}};
RadioLabel runAhead2{&runAheadLayout, Size{0, 0}};
RadioLabel runAhead3{&runAheadLayout, Size{0, 0}};
RadioLabel runAhead4{&runAheadLayout, Size{0, 0}};
Group runAheadGroup{&runAhead0, &runAhead1, &runAhead2, &runAhead3, &runAhead4};
Canvas runAheadSpacer{this, Size{~0, 1}};
//
Label overclockingLabel{this, Size{~0, 0}, 2};
TableLayout overclockingLayout{this, Size{~0, 0}};
Label cpuLabel{&overclockingLayout, Size{0, 0}};

View File

@ -1,6 +1,7 @@
#pragma once
#include <nall/queue.hpp>
#include <nall/serializer.hpp>
#include <nall/dsp/dsp.hpp>
namespace nall::DSP::Resampler {
@ -11,6 +12,7 @@ struct Cubic {
inline auto pending() const -> uint;
inline auto read() -> double;
inline auto write(double sample) -> void;
inline auto serialize(serializer&) -> void;
private:
double inputFrequency;
@ -67,4 +69,13 @@ auto Cubic::write(double sample) -> void {
mu -= 1.0;
}
auto Cubic::serialize(serializer& s) -> void {
s.real(inputFrequency);
s.real(outputFrequency);
s.real(ratio);
s.real(fraction);
s.array(history);
samples.serialize(s);
}
}

View File

@ -51,6 +51,15 @@ struct serializer {
return _capacity;
}
auto setMode(Mode mode) -> bool {
if(_mode == Mode::Save && mode == Mode::Load) {
_mode = mode;
_size = 0;
return true;
}
return false;
}
template<typename T> auto real(T& value) -> serializer& {
enum : uint { size = sizeof(T) };
//this is rather dangerous, and not cross-platform safe;