diff --git a/bsnes/emulator/audio/audio.hpp b/bsnes/emulator/audio/audio.hpp index 11a044ee..e1e20c65 100644 --- a/bsnes/emulator/audio/audio.hpp +++ b/bsnes/emulator/audio/audio.hpp @@ -73,6 +73,8 @@ struct Stream { write(samples); } + auto serialize(serializer&) -> void; + private: struct Channel { vector filters; diff --git a/bsnes/emulator/audio/stream.cpp b/bsnes/emulator/audio/stream.cpp index c0a4e923..3fe659f8 100644 --- a/bsnes/emulator/audio/stream.cpp +++ b/bsnes/emulator/audio/stream.cpp @@ -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); +} diff --git a/bsnes/emulator/emulator.hpp b/bsnes/emulator/emulator.hpp index 186587e9..75d7ebe3 100644 --- a/bsnes/emulator/emulator.hpp +++ b/bsnes/emulator/emulator.hpp @@ -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"; diff --git a/bsnes/emulator/interface.hpp b/bsnes/emulator/interface.hpp index 71284711..f21399fe 100644 --- a/bsnes/emulator/interface.hpp +++ b/bsnes/emulator/interface.hpp @@ -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 {} }; } diff --git a/bsnes/sfc/cpu/cpu.cpp b/bsnes/sfc/cpu/cpu.cpp index 45566eb1..b6fec167 100644 --- a/bsnes/sfc/cpu/cpu.cpp +++ b/bsnes/sfc/cpu/cpu.cpp @@ -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}; diff --git a/bsnes/sfc/dsp/dsp.cpp b/bsnes/sfc/dsp/dsp.cpp index c41a80c4..3d15e192 100644 --- a/bsnes/sfc/dsp/dsp.cpp +++ b/bsnes/sfc/dsp/dsp.cpp @@ -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; diff --git a/bsnes/sfc/interface/interface.cpp b/bsnes/sfc/interface/interface.cpp index ebeabdc2..51d3b9b1 100644 --- a/bsnes/sfc/interface/interface.cpp +++ b/bsnes/sfc/interface/interface.cpp @@ -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; } } diff --git a/bsnes/sfc/interface/interface.hpp b/bsnes/sfc/interface/interface.hpp index 2af1e7bb..d9437487 100644 --- a/bsnes/sfc/interface/interface.hpp +++ b/bsnes/sfc/interface/interface.hpp @@ -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" diff --git a/bsnes/sfc/ppu-fast/ppu.cpp b/bsnes/sfc/ppu-fast/ppu.cpp index 1be718b3..0701e8b6 100644 --- a/bsnes/sfc/ppu-fast/ppu.cpp +++ b/bsnes/sfc/ppu-fast/ppu.cpp @@ -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()) { diff --git a/bsnes/sfc/ppu/ppu.cpp b/bsnes/sfc/ppu/ppu.cpp index f5ede96d..6ec524ec 100644 --- a/bsnes/sfc/ppu/ppu.cpp +++ b/bsnes/sfc/ppu/ppu.cpp @@ -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; diff --git a/bsnes/sfc/sfc.hpp b/bsnes/sfc/sfc.hpp index b6d7dbbb..9ecf56de 100644 --- a/bsnes/sfc/sfc.hpp +++ b/bsnes/sfc/sfc.hpp @@ -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; diff --git a/bsnes/sfc/system/serialization.cpp b/bsnes/sfc/system/serialization.cpp index 277fa04c..25b1c5ac 100644 --- a/bsnes/sfc/system/serialization.cpp +++ b/bsnes/sfc/system/serialization.cpp @@ -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; } diff --git a/bsnes/sfc/system/system.cpp b/bsnes/sfc/system/system.cpp index 3bee3330..f6bcad51 100644 --- a/bsnes/sfc/system/system.cpp +++ b/bsnes/sfc/system/system.cpp @@ -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); } } diff --git a/bsnes/sfc/system/system.hpp b/bsnes/sfc/system/system.hpp index 0d8fb444..4642c1ad 100644 --- a/bsnes/sfc/system/system.hpp +++ b/bsnes/sfc/system/system.hpp @@ -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; diff --git a/bsnes/target-bsnes/input/hotkeys.cpp b/bsnes/target-bsnes/input/hotkeys.cpp index 9c658d09..92f07114 100644 --- a/bsnes/target-bsnes/input/hotkeys.cpp +++ b/bsnes/target-bsnes/input/hotkeys.cpp @@ -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); diff --git a/bsnes/target-bsnes/program/program.cpp b/bsnes/target-bsnes/program/program.cpp index 9bf9220b..1f66a0af 100644 --- a/bsnes/target-bsnes/program/program.cpp +++ b/bsnes/target-bsnes/program/program.cpp @@ -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) { diff --git a/bsnes/target-bsnes/program/program.hpp b/bsnes/target-bsnes/program/program.hpp index 88f17a25..24b3357d 100644 --- a/bsnes/target-bsnes/program/program.hpp +++ b/bsnes/target-bsnes/program/program.hpp @@ -200,6 +200,9 @@ public: Rewind = 1 << 4, };}; uint mute = 0; + + bool fastForwarding = false; + bool rewinding = false; }; extern Program program; diff --git a/bsnes/target-bsnes/program/rewind.cpp b/bsnes/target-bsnes/program/rewind.cpp index b148720e..827fd74a 100644 --- a/bsnes/target-bsnes/program/rewind.cpp +++ b/bsnes/target-bsnes/program/rewind.cpp @@ -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; } diff --git a/bsnes/target-bsnes/settings/enhancements.cpp b/bsnes/target-bsnes/settings/enhancements.cpp index aafa8d37..d3ed73df 100644 --- a/bsnes/target-bsnes/settings/enhancements.cpp +++ b/bsnes/target-bsnes/settings/enhancements.cpp @@ -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); diff --git a/bsnes/target-bsnes/settings/settings.cpp b/bsnes/target-bsnes/settings/settings.cpp index 0b061686..a7d03d2d 100644 --- a/bsnes/target-bsnes/settings/settings.cpp +++ b/bsnes/target-bsnes/settings/settings.cpp @@ -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); diff --git a/bsnes/target-bsnes/settings/settings.hpp b/bsnes/target-bsnes/settings/settings.hpp index 7e9dea71..e81fbaa3 100644 --- a/bsnes/target-bsnes/settings/settings.hpp +++ b/bsnes/target-bsnes/settings/settings.hpp @@ -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}}; diff --git a/nall/dsp/resampler/cubic.hpp b/nall/dsp/resampler/cubic.hpp index 362ec429..bd8cd5d3 100644 --- a/nall/dsp/resampler/cubic.hpp +++ b/nall/dsp/resampler/cubic.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include 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); +} + } diff --git a/nall/serializer.hpp b/nall/serializer.hpp index eb090510..1e8eeb2c 100755 --- a/nall/serializer.hpp +++ b/nall/serializer.hpp @@ -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 auto real(T& value) -> serializer& { enum : uint { size = sizeof(T) }; //this is rather dangerous, and not cross-platform safe;