mirror of https://github.com/bsnes-emu/bsnes.git
Fix SNES auto-joypad polling
anomie's notes indicate that polling begins between cycles 32.5-95.5, but his notes were slightly off. 32.5-95.5 is referring to the PPU dot clock. If you multiply by 4 you get the cycle count, 130-382. It's pretty clear that in 256-cycle units, that would need to be 130-384 or you'd have no event when it lands on cycle 384. Jonas Quinn confirmed that with his testing in any case. I chose to split the cycle range in half and run things every 128 clocks. So that reduces us to 32.5-64.0, or 130 to 256 + 258-384. The reason I chose 128 over 256 was so I could emulate the time it takes to perform the joypad latching. I don't believe you can set the controller latch to 1 and then 0 immediately. It needs time to let the controllers see this happening and then release the latch, and time to let that take effect. Polling the controllers is only one operation so it makes sense that happens once every 256 cycles, which is why I have the (counter&1) test for that. Think about it this way: we know the polling sequence takes 4224 cycles, and 4224/256 is 16.5. Isn't that a little strange to have half a cycle? But if we consider the latching is actually twice as fast, then it's 2x128 + 16x256. If we run on a 128-clock counter, it becomes 2x128 + 16x2x128, or 34 total states. But now since we don't actually poll on the second half of each 16x2x256 states, we don't need to bother running the event there, so we can stop at 33 instead of 34 and it's the same thing. And by doing that, it turns our polling duration into 4224 cycles, just like we've observed. Fixes #61.
This commit is contained in:
parent
9d262ed113
commit
39c37ec2d1
|
@ -120,7 +120,7 @@ private:
|
|||
|
||||
bool autoJoypadActive = 0;
|
||||
bool autoJoypadLatch = 0;
|
||||
uint autoJoypadCounter = 0;
|
||||
uint autoJoypadCounter = 33; //state machine; 4224 / 128 = 33 (inactive)
|
||||
} status;
|
||||
|
||||
struct IO {
|
||||
|
|
|
@ -5,7 +5,7 @@ auto CPU::dmaCounter() const -> uint {
|
|||
|
||||
//joypad auto-poll clock divider
|
||||
auto CPU::joypadCounter() const -> uint {
|
||||
return counter.cpu & 255;
|
||||
return counter.cpu & 127;
|
||||
}
|
||||
|
||||
auto CPU::stepOnce() -> void {
|
||||
|
@ -105,7 +105,7 @@ auto CPU::scanline() -> void {
|
|||
status.hdmaSetupPosition = (version == 1 ? 12 + 8 - dmaCounter() : 12 + dmaCounter());
|
||||
status.hdmaSetupTriggered = false;
|
||||
|
||||
status.autoJoypadCounter = 0;
|
||||
status.autoJoypadCounter = 33; //33 = inactive
|
||||
}
|
||||
|
||||
//DRAM refresh occurs once every scanline
|
||||
|
@ -200,65 +200,39 @@ auto CPU::dmaEdge() -> void {
|
|||
}
|
||||
}
|
||||
|
||||
//called every 256 clocks; see CPU::step()
|
||||
//called every 128 clocks; see CPU::step()
|
||||
auto CPU::joypadEdge() -> void {
|
||||
//fast joypad polling is a hack to work around edge cases not currently emulated in auto-joypad polling.
|
||||
//below is a list of games that have had input issues over the years.
|
||||
//Nuke (PD): inputs do not work
|
||||
//Super Conflict: sends random inputs even with no buttons pressed
|
||||
//Super Star Wars: Start button auto-unpauses
|
||||
//Taikyoku Igo - Goliath: start button not acknowledged
|
||||
//Tatakae Genshijin 2: attract sequence ends early
|
||||
//Williams Arcade's Greatest Hits: inputs fire on their own; or menu items sometimes skipped
|
||||
//World Masters Golf: inputs fail to register; or holding D-pad should only move the cursor once, not continuously
|
||||
//it is not yet confirmed if polling can be stopped early and/or (re)started later
|
||||
if(!io.autoJoypadPoll) return;
|
||||
|
||||
if(configuration.hacks.cpu.fastJoypadPolling) {
|
||||
//Taikyoku Igo - Goliath
|
||||
//Williams Arcade's Greatest Hits
|
||||
//World Masters Golf
|
||||
if(!status.autoJoypadCounter && vcounter() >= ppu.vdisp()) {
|
||||
controllerPort1.device->latch(1);
|
||||
controllerPort2.device->latch(1);
|
||||
controllerPort1.device->latch(0);
|
||||
controllerPort2.device->latch(0);
|
||||
|
||||
io.joy1 = 0;
|
||||
io.joy2 = 0;
|
||||
io.joy3 = 0;
|
||||
io.joy4 = 0;
|
||||
|
||||
for(uint index : range(16)) {
|
||||
uint2 port0 = controllerPort1.device->data();
|
||||
uint2 port1 = controllerPort2.device->data();
|
||||
|
||||
io.joy1 = io.joy1 << 1 | port0.bit(0);
|
||||
io.joy2 = io.joy2 << 1 | port1.bit(0);
|
||||
io.joy3 = io.joy3 << 1 | port0.bit(1);
|
||||
io.joy4 = io.joy4 << 1 | port1.bit(1);
|
||||
if(vcounter() == ppu.vdisp() && hcounter() >= 130 && hcounter() <= 256) {
|
||||
//begin new polling sequence
|
||||
status.autoJoypadCounter = 0;
|
||||
}
|
||||
|
||||
status.autoJoypadCounter = 16;
|
||||
}
|
||||
} else {
|
||||
if(vcounter() >= ppu.vdisp()) {
|
||||
//cache enable state at first iteration
|
||||
if(status.autoJoypadCounter == 0) status.autoJoypadLatch = io.autoJoypadPoll;
|
||||
status.autoJoypadActive = status.autoJoypadCounter <= 15;
|
||||
//stop after polling has been completed for this frame
|
||||
if(status.autoJoypadCounter >= 33) return;
|
||||
|
||||
if(status.autoJoypadActive && status.autoJoypadLatch) {
|
||||
if(status.autoJoypadCounter == 0) {
|
||||
//latch controller states on the first polling cycle
|
||||
controllerPort1.device->latch(1);
|
||||
controllerPort2.device->latch(1);
|
||||
}
|
||||
|
||||
if(status.autoJoypadCounter == 1) {
|
||||
//release latch and begin reading on the second cycle
|
||||
controllerPort1.device->latch(0);
|
||||
controllerPort2.device->latch(0);
|
||||
|
||||
//shift registers are cleared at start of auto joypad polling
|
||||
//shift registers are cleared to zero at start of auto-joypad polling
|
||||
io.joy1 = 0;
|
||||
io.joy2 = 0;
|
||||
io.joy3 = 0;
|
||||
io.joy4 = 0;
|
||||
}
|
||||
|
||||
if(status.autoJoypadCounter >= 2 && !(status.autoJoypadCounter & 1)) {
|
||||
//sixteen bits are shifted into joy{1-4}, one bit per 256 clocks
|
||||
uint2 port0 = controllerPort1.device->data();
|
||||
uint2 port1 = controllerPort2.device->data();
|
||||
|
||||
|
@ -270,5 +244,3 @@ auto CPU::joypadEdge() -> void {
|
|||
|
||||
status.autoJoypadCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ auto Configuration::process(Markup::Node document, bool load) -> void {
|
|||
bind(text, "Hacks/Entropy", hacks.entropy);
|
||||
bind(natural, "Hacks/CPU/Overclock", hacks.cpu.overclock);
|
||||
bind(boolean, "Hacks/CPU/FastMath", hacks.cpu.fastMath);
|
||||
bind(boolean, "Hacks/CPU/FastJoypadPolling", hacks.cpu.fastJoypadPolling);
|
||||
bind(boolean, "Hacks/PPU/Fast", hacks.ppu.fast);
|
||||
bind(boolean, "Hacks/PPU/Deinterlace", hacks.ppu.deinterlace);
|
||||
bind(natural, "Hacks/PPU/RenderCycle", hacks.ppu.renderCycle);
|
||||
|
|
|
@ -33,7 +33,6 @@ struct Configuration {
|
|||
struct CPU {
|
||||
uint overclock = 100;
|
||||
bool fastMath = false;
|
||||
bool fastJoypadPolling = false;
|
||||
} cpu;
|
||||
struct PPU {
|
||||
bool fast = true;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
auto Program::hackCompatibility() -> void {
|
||||
string entropy = settings.emulator.hack.entropy;
|
||||
bool fastJoypadPolling = false;
|
||||
bool fastPPU = settings.emulator.hack.ppu.fast;
|
||||
bool fastPPUNoSpriteLimit = settings.emulator.hack.ppu.noSpriteLimit;
|
||||
bool fastDSP = settings.emulator.hack.dsp.fast;
|
||||
|
@ -10,15 +9,6 @@ auto Program::hackCompatibility() -> void {
|
|||
auto title = superFamicom.title;
|
||||
auto region = superFamicom.region;
|
||||
|
||||
//sometimes menu options are skipped over in the main menu with cycle-based joypad polling
|
||||
if(title == "Arcades Greatest Hits") fastJoypadPolling = true;
|
||||
|
||||
//the start button doesn't work in this game with cycle-based joypad polling
|
||||
if(title == "TAIKYOKU-IGO Goliath") fastJoypadPolling = true;
|
||||
|
||||
//holding up or down on the menu quickly cycles through options instead of stopping after each button press
|
||||
if(title == "WORLD MASTERS GOLF") fastJoypadPolling = true;
|
||||
|
||||
//relies on mid-scanline rendering techniques
|
||||
if(title == "AIR STRIKE PATROL" || title == "DESERT FIGHTER") fastPPU = false;
|
||||
|
||||
|
@ -67,7 +57,6 @@ auto Program::hackCompatibility() -> void {
|
|||
}
|
||||
|
||||
emulator->configure("Hacks/Entropy", entropy);
|
||||
emulator->configure("Hacks/CPU/FastJoypadPolling", fastJoypadPolling);
|
||||
emulator->configure("Hacks/PPU/Fast", fastPPU);
|
||||
emulator->configure("Hacks/PPU/NoSpriteLimit", fastPPUNoSpriteLimit);
|
||||
emulator->configure("Hacks/PPU/RenderCycle", renderCycle);
|
||||
|
|
|
@ -165,15 +165,6 @@ auto Program::load() -> void {
|
|||
auto title = superFamicom.title;
|
||||
auto region = superFamicom.region;
|
||||
|
||||
//sometimes menu options are skipped over in the main menu with cycle-based joypad polling
|
||||
if(title == "Arcades Greatest Hits") emulator->configure("Hacks/CPU/FastJoypadPolling", true);
|
||||
|
||||
//the start button doesn't work in this game with cycle-based joypad polling
|
||||
if(title == "TAIKYOKU-IGO Goliath") emulator->configure("Hacks/CPU/FastJoypadPolling", true);
|
||||
|
||||
//holding up or down on the menu quickly cycles through options instead of stopping after each button press
|
||||
if(title == "WORLD MASTERS GOLF") emulator->configure("Hacks/CPU/FastJoypadPolling", true);
|
||||
|
||||
//relies on mid-scanline rendering techniques
|
||||
if(title == "AIR STRIKE PATROL" || title == "DESERT FIGHTER") emulator->configure("Hacks/PPU/Fast", false);
|
||||
|
||||
|
|
Loading…
Reference in New Issue