Save state improvements: rewind should be fully stable now.
Before, Star Ocean and Tales of Phantasia would rarely hang with rewind.
This commit is contained in:
byuu 2019-10-07 13:32:21 +09:00
parent 0c82cc325e
commit e78aca34b9
26 changed files with 155 additions and 63 deletions

View File

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

View File

@ -7,12 +7,16 @@ namespace SuperFamicom {
ArmDSP armdsp; ArmDSP armdsp;
auto ArmDSP::synchronizeCPU() -> void { auto ArmDSP::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(clock >= 0) co_switch(cpu.thread);
} }
auto ArmDSP::Enter() -> void { auto ArmDSP::Enter() -> void {
armdsp.boot(); armdsp.boot();
while(true) scheduler.synchronize(), armdsp.main(); while(true) {
scheduler.synchronizeAll();
armdsp.main();
}
} }
auto ArmDSP::boot() -> void { auto ArmDSP::boot() -> void {

View File

@ -8,11 +8,15 @@ namespace SuperFamicom {
EpsonRTC epsonrtc; EpsonRTC epsonrtc;
auto EpsonRTC::synchronizeCPU() -> void { auto EpsonRTC::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(clock >= 0) co_switch(cpu.thread);
} }
auto EpsonRTC::Enter() -> void { auto EpsonRTC::Enter() -> void {
while(true) scheduler.synchronize(), epsonrtc.main(); while(true) {
scheduler.synchronizeAll();
epsonrtc.main();
}
} }
auto EpsonRTC::main() -> void { auto EpsonRTC::main() -> void {

View File

@ -6,11 +6,15 @@ namespace SuperFamicom {
Event event; Event event;
auto Event::synchronizeCPU() -> void { auto Event::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(clock >= 0) co_switch(cpu.thread);
} }
auto Event::Enter() -> void { auto Event::Enter() -> void {
while(true) scheduler.synchronize(), event.main(); while(true) {
scheduler.synchronizeAll();
event.main();
}
} }
auto Event::main() -> void { auto Event::main() -> void {

View File

@ -9,11 +9,15 @@ namespace SuperFamicom {
HitachiDSP hitachidsp; HitachiDSP hitachidsp;
auto HitachiDSP::synchronizeCPU() -> void { auto HitachiDSP::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(clock >= 0) co_switch(cpu.thread);
} }
auto HitachiDSP::Enter() -> void { auto HitachiDSP::Enter() -> void {
while(true) scheduler.synchronize(), hitachidsp.main(); while(true) {
scheduler.synchronizeAll();
hitachidsp.main();
}
} }
auto HitachiDSP::step(uint clocks) -> void { auto HitachiDSP::step(uint clocks) -> void {

View File

@ -48,12 +48,13 @@ namespace SameBoy {
} }
auto ICD::synchronizeCPU() -> void { auto ICD::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(clock >= 0) co_switch(cpu.thread);
} }
auto ICD::Enter() -> void { auto ICD::Enter() -> void {
while(true) { while(true) {
scheduler.synchronize(); scheduler.synchronizeAll();
icd.main(); icd.main();
} }
} }

View File

@ -7,12 +7,16 @@ MSU1 msu1;
#include "serialization.cpp" #include "serialization.cpp"
auto MSU1::synchronizeCPU() -> void { auto MSU1::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(clock >= 0) co_switch(cpu.thread);
} }
auto MSU1::Enter() -> void { auto MSU1::Enter() -> void {
while(true) scheduler.synchronize(), msu1.main(); while(true) {
scheduler.synchronizeAll();
msu1.main();
}
} }
auto MSU1::main() -> void { auto MSU1::main() -> void {

View File

@ -7,11 +7,15 @@ namespace SuperFamicom {
NECDSP necdsp; NECDSP necdsp;
auto NECDSP::synchronizeCPU() -> void { auto NECDSP::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(clock >= 0) co_switch(cpu.thread);
} }
auto NECDSP::Enter() -> void { auto NECDSP::Enter() -> void {
while(true) scheduler.synchronize(), necdsp.main(); while(true) {
scheduler.synchronizeAll();
necdsp.main();
}
} }
auto NECDSP::main() -> void { auto NECDSP::main() -> void {

View File

@ -12,11 +12,15 @@ namespace SuperFamicom {
SA1 sa1; SA1 sa1;
auto SA1::synchronizeCPU() -> void { auto SA1::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(clock >= 0) co_switch(cpu.thread);
} }
auto SA1::Enter() -> void { auto SA1::Enter() -> void {
while(true) scheduler.synchronize(), sa1.main(); while(true) {
scheduler.synchronizeAll();
sa1.main();
}
} }
auto SA1::main() -> void { auto SA1::main() -> void {

View File

@ -1,7 +1,7 @@
//Super Accelerator (SA-1) //Super Accelerator (SA-1)
struct SA1 : Processor::WDC65816, Thread { struct SA1 : Processor::WDC65816, Thread {
inline auto synchronizing() const -> bool override { return scheduler.mode == Scheduler::Mode::SynchronizeAll; } inline auto synchronizing() const -> bool override { return scheduler.synchronizingAll(); }
//sa1.cpp //sa1.cpp
auto synchronizeCPU() -> void; auto synchronizeCPU() -> void;

View File

@ -8,11 +8,15 @@ namespace SuperFamicom {
SharpRTC sharprtc; SharpRTC sharprtc;
auto SharpRTC::synchronizeCPU() -> void { auto SharpRTC::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(clock >= 0) co_switch(cpu.thread);
} }
auto SharpRTC::Enter() -> void { auto SharpRTC::Enter() -> void {
while(true) scheduler.synchronize(), sharprtc.main(); while(true) {
scheduler.synchronizeAll();
sharprtc.main();
}
} }
auto SharpRTC::main() -> void { auto SharpRTC::main() -> void {

View File

@ -17,11 +17,15 @@ SPC7110::~SPC7110() {
} }
auto SPC7110::synchronizeCPU() -> void { auto SPC7110::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(clock >= 0) co_switch(cpu.thread);
} }
auto SPC7110::Enter() -> void { auto SPC7110::Enter() -> void {
while(true) scheduler.synchronize(), spc7110.main(); while(true) {
scheduler.synchronizeAll();
spc7110.main();
}
} }
auto SPC7110::main() -> void { auto SPC7110::main() -> void {

View File

@ -12,11 +12,15 @@ namespace SuperFamicom {
SuperFX superfx; SuperFX superfx;
auto SuperFX::synchronizeCPU() -> void { auto SuperFX::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(clock >= 0) co_switch(cpu.thread);
} }
auto SuperFX::Enter() -> void { auto SuperFX::Enter() -> void {
while(true) scheduler.synchronize(), superfx.main(); while(true) {
scheduler.synchronizeAll();
superfx.main();
}
} }
auto SuperFX::main() -> void { auto SuperFX::main() -> void {

View File

@ -2,7 +2,7 @@ struct SuperFX : Processor::GSU, Thread {
ReadableMemory rom; ReadableMemory rom;
WritableMemory ram; WritableMemory ram;
inline auto synchronizing() const -> bool { return scheduler.mode == Scheduler::Mode::SynchronizeAll; } inline auto synchronizing() const -> bool { return scheduler.synchronizingAll(); }
//superfx.cpp //superfx.cpp
auto synchronizeCPU() -> void; auto synchronizeCPU() -> void;

View File

@ -11,14 +11,17 @@ CPU cpu;
#include "serialization.cpp" #include "serialization.cpp"
auto CPU::synchronizeSMP() -> void { auto CPU::synchronizeSMP() -> void {
if(scheduler.synchronizingAll()) return;
if(smp.clock < 0) co_switch(smp.thread); if(smp.clock < 0) co_switch(smp.thread);
} }
auto CPU::synchronizePPU() -> void { auto CPU::synchronizePPU() -> void {
if(scheduler.synchronizingAll()) return;
if(ppu.clock < 0) co_switch(ppu.thread); if(ppu.clock < 0) co_switch(ppu.thread);
} }
auto CPU::synchronizeCoprocessors() -> void { auto CPU::synchronizeCoprocessors() -> void {
if(scheduler.synchronizingAll()) return;
for(auto coprocessor : coprocessors) { for(auto coprocessor : coprocessors) {
if(coprocessor->clock < 0) co_switch(coprocessor->thread); if(coprocessor->clock < 0) co_switch(coprocessor->thread);
} }
@ -26,9 +29,8 @@ auto CPU::synchronizeCoprocessors() -> void {
auto CPU::Enter() -> void { auto CPU::Enter() -> void {
while(true) { while(true) {
if(scheduler.mode == Scheduler::Mode::SynchronizeCPU) { scheduler.synchronizeCPU();
scheduler.leave(Scheduler::Event::Synchronize); scheduler.synchronizeAll();
}
cpu.main(); cpu.main();
} }
} }

View File

@ -69,12 +69,13 @@ PPU::~PPU() {
} }
auto PPU::synchronizeCPU() -> void { auto PPU::synchronizeCPU() -> void {
if(ppubase.clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(ppubase.clock >= 0) co_switch(cpu.thread);
} }
auto PPU::Enter() -> void { auto PPU::Enter() -> void {
while(true) { while(true) {
scheduler.synchronize(); scheduler.synchronizeAll();
ppu.main(); ppu.main();
} }
} }
@ -122,10 +123,14 @@ auto PPU::scanline() -> void {
if(!io.displayDisable) oamAddressReset(); if(!io.displayDisable) oamAddressReset();
} }
if(vcounter() == 240) { if(vcounter() == vdisp()) { //240
Line::flush(); Line::flush();
scheduler.leave(Scheduler::Event::Frame); scheduler.leave(Scheduler::Event::Frame);
} }
if(vcounter() == 240) {
Line::flush();
}
} }
auto PPU::refresh() -> void { auto PPU::refresh() -> void {

View File

@ -21,7 +21,7 @@ auto PPU::main() -> void {
if(auto device = controllerPort2.device) device->latch(); //light guns if(auto device = controllerPort2.device) device->latch(); //light guns
} }
if(vcounter() == 240) { if(vcounter() == vdisp()) { //240
scheduler.leave(Scheduler::Event::Frame); scheduler.leave(Scheduler::Event::Frame);
} }

View File

@ -39,7 +39,8 @@ PPU::~PPU() {
} }
auto PPU::synchronizeCPU() -> void { auto PPU::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(clock >= 0) co_switch(cpu.thread);
} }
auto PPU::step() -> void { auto PPU::step() -> void {
@ -59,7 +60,7 @@ auto PPU::step(uint clocks) -> void {
auto PPU::Enter() -> void { auto PPU::Enter() -> void {
while(true) { while(true) {
scheduler.synchronize(); scheduler.synchronizeAll();
ppu.main(); ppu.main();
} }
} }

View File

@ -45,7 +45,19 @@ namespace SuperFamicom {
co_switch(host); co_switch(host);
} }
auto synchronize() -> void { inline auto synchronizingCPU() const -> bool {
return mode == Mode::SynchronizeCPU;
}
inline auto synchronizingAll() const -> bool {
return mode == Mode::SynchronizeAll;
}
inline auto synchronizeCPU() -> void {
if(mode == Mode::SynchronizeCPU) leave(Event::Synchronize);
}
inline auto synchronizeAll() -> void {
if(mode == Mode::SynchronizeAll) leave(Event::Synchronize); if(mode == Mode::SynchronizeAll) leave(Event::Synchronize);
} }
}; };

View File

@ -13,11 +13,15 @@ BSMemory::BSMemory() {
} }
auto BSMemory::synchronizeCPU() -> void { auto BSMemory::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(clock >= 0) co_switch(cpu.thread);
} }
auto BSMemory::Enter() -> void { auto BSMemory::Enter() -> void {
while(true) scheduler.synchronize(), bsmemory.main(); while(true) {
scheduler.synchronizeAll();
bsmemory.main();
}
} }
auto BSMemory::main() -> void { auto BSMemory::main() -> void {

View File

@ -9,7 +9,8 @@ SMP smp;
#include "serialization.cpp" #include "serialization.cpp"
auto SMP::synchronizeCPU() -> void { auto SMP::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread); if(scheduler.synchronizingAll()) return;
if(clock >= 0) co_switch(cpu.thread);
} }
auto SMP::synchronizeDSP() -> void { auto SMP::synchronizeDSP() -> void {
@ -18,7 +19,7 @@ auto SMP::synchronizeDSP() -> void {
auto SMP::Enter() -> void { auto SMP::Enter() -> void {
while(true) { while(true) {
scheduler.synchronize(); scheduler.synchronizeAll();
smp.main(); smp.main();
} }
} }

View File

@ -1,7 +1,7 @@
//Sony CXP1100Q-1 //Sony CXP1100Q-1
struct SMP : Processor::SPC700, Thread { struct SMP : Processor::SPC700, Thread {
inline auto synchronizing() const -> bool override { return scheduler.mode == Scheduler::Mode::SynchronizeAll; } inline auto synchronizing() const -> bool override { return scheduler.synchronizingAll(); }
//io.cpp //io.cpp
auto portRead(uint2 port) const -> uint8; auto portRead(uint2 port) const -> uint8;

View File

@ -33,6 +33,7 @@ auto System::unserialize(serializer& s) -> bool {
power(/* reset = */ false); power(/* reset = */ false);
serializeAll(s); serializeAll(s);
serializeInit(); //hacks.fastPPU setting changes serializeSize serializeInit(); //hacks.fastPPU setting changes serializeSize
return true; return true;
} }

View File

@ -11,39 +11,61 @@ Cheat cheat;
auto System::run() -> void { auto System::run() -> void {
scheduler.mode = Scheduler::Mode::Run; scheduler.mode = Scheduler::Mode::Run;
scheduler.enter(); scheduler.enter();
if(scheduler.event == Scheduler::Event::Frame) { if(scheduler.event == Scheduler::Event::Frame) frameEvent();
ppu.refresh();
//refresh all cheat codes once per frame
Memory::GlobalWriteEnable = true;
for(auto& code : cheat.codes) {
if(code.enable) {
bus.write(code.address, code.data);
}
}
Memory::GlobalWriteEnable = false;
}
} }
auto System::runToSave() -> void { auto System::runToSave() -> void {
//run the CPU thread normally, exiting once we reach the next synchronization point.
//the CPU thread may run in the pathological case for up to 10 frames for a long 8-channel x 64KB DMA transfer.
//in practice, this will typically be at most one DMA transfer.
scheduler.mode = Scheduler::Mode::SynchronizeCPU; scheduler.mode = Scheduler::Mode::SynchronizeCPU;
while(true) { scheduler.enter(); if(scheduler.event == Scheduler::Event::Synchronize) break; } runToSynchronize();
//now synchronize every other thread to their synchronization points.
//stop after each thread is very slightly ahead of the CPU thread.
scheduler.mode = Scheduler::Mode::SynchronizeAll; scheduler.mode = Scheduler::Mode::SynchronizeAll;
scheduler.active = smp.thread; runToThread(smp);
while(true) { scheduler.enter(); if(scheduler.event == Scheduler::Event::Synchronize) break; } runToThread(ppu);
for(auto coprocessor : cpu.coprocessors) runToThread(*coprocessor);
scheduler.mode = Scheduler::Mode::SynchronizeAll; //at this point, the CPU thread is the furthest behind in time.
scheduler.active = ppu.thread; //all threads are now at their synchronization points and can be serialized safely.
while(true) { scheduler.enter(); if(scheduler.event == Scheduler::Event::Synchronize) break; } scheduler.mode = Scheduler::Mode::Run;
scheduler.active = cpu.thread;
}
for(auto coprocessor : cpu.coprocessors) { auto System::runToSynchronize() -> void {
scheduler.mode = Scheduler::Mode::SynchronizeAll; while(true) {
scheduler.active = coprocessor->thread; scheduler.enter();
while(true) { scheduler.enter(); if(scheduler.event == Scheduler::Event::Synchronize) break; } if(scheduler.event == Scheduler::Event::Frame) frameEvent();
if(scheduler.event == Scheduler::Event::Synchronize) break;
} }
} }
auto System::runToThread(Thread& aux) -> void {
//first, ensure that the CPU is ahead of the thread we want to synchronize to.
//because if it isn't, and the other thread is ahead, it will run even more ahead to synchronize itself.
scheduler.active = cpu.thread;
while(aux.clock >= 0) runToSynchronize();
//now that it is, run the other thread until it's just barely surpassed the CPU thread.
scheduler.active = aux.thread;
do runToSynchronize(); while(aux.clock < 0);
}
auto System::frameEvent() -> void {
ppu.refresh();
//refresh all cheat codes once per frame
Memory::GlobalWriteEnable = true;
for(auto& code : cheat.codes) {
if(code.enable) {
bus.write(code.address, code.data);
}
}
Memory::GlobalWriteEnable = false;
}
auto System::load(Emulator::Interface* interface) -> bool { auto System::load(Emulator::Interface* interface) -> bool {
information = {}; information = {};

View File

@ -10,6 +10,9 @@ struct System {
auto run() -> void; auto run() -> void;
auto runToSave() -> void; auto runToSave() -> void;
auto runToSynchronize() -> void;
auto runToThread(Thread&) -> void;
auto frameEvent() -> void;
auto load(Emulator::Interface*) -> bool; auto load(Emulator::Interface*) -> bool;
auto save() -> void; auto save() -> void;

View File

@ -48,7 +48,7 @@ auto Presentation::create() -> void {
settings.video.overscan = showOverscanArea.checked(); settings.video.overscan = showOverscanArea.checked();
resizeWindow(); resizeWindow();
}); });
blurEmulation.setText("Blur Emulation").setChecked(settings.video.blur).onToggle([&] { blurEmulation.setText("Hires Blur Emulation").setChecked(settings.video.blur).onToggle([&] {
settings.video.blur = blurEmulation.checked(); settings.video.blur = blurEmulation.checked();
emulator->configure("Video/BlurEmulation", settings.video.blur); emulator->configure("Video/BlurEmulation", settings.video.blur);
}).doToggle(); }).doToggle();