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 autoJoypadActive = 0;
|
||||||
bool autoJoypadLatch = 0;
|
bool autoJoypadLatch = 0;
|
||||||
uint autoJoypadCounter = 0;
|
uint autoJoypadCounter = 33; //state machine; 4224 / 128 = 33 (inactive)
|
||||||
} status;
|
} status;
|
||||||
|
|
||||||
struct IO {
|
struct IO {
|
||||||
|
|
|
@ -5,7 +5,7 @@ auto CPU::dmaCounter() const -> uint {
|
||||||
|
|
||||||
//joypad auto-poll clock divider
|
//joypad auto-poll clock divider
|
||||||
auto CPU::joypadCounter() const -> uint {
|
auto CPU::joypadCounter() const -> uint {
|
||||||
return counter.cpu & 255;
|
return counter.cpu & 127;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::stepOnce() -> void {
|
auto CPU::stepOnce() -> void {
|
||||||
|
@ -105,7 +105,7 @@ auto CPU::scanline() -> void {
|
||||||
status.hdmaSetupPosition = (version == 1 ? 12 + 8 - dmaCounter() : 12 + dmaCounter());
|
status.hdmaSetupPosition = (version == 1 ? 12 + 8 - dmaCounter() : 12 + dmaCounter());
|
||||||
status.hdmaSetupTriggered = false;
|
status.hdmaSetupTriggered = false;
|
||||||
|
|
||||||
status.autoJoypadCounter = 0;
|
status.autoJoypadCounter = 33; //33 = inactive
|
||||||
}
|
}
|
||||||
|
|
||||||
//DRAM refresh occurs once every scanline
|
//DRAM refresh occurs once every scanline
|
||||||
|
@ -200,75 +200,47 @@ auto CPU::dmaEdge() -> void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//called every 256 clocks; see CPU::step()
|
//called every 128 clocks; see CPU::step()
|
||||||
auto CPU::joypadEdge() -> void {
|
auto CPU::joypadEdge() -> void {
|
||||||
//fast joypad polling is a hack to work around edge cases not currently emulated in auto-joypad polling.
|
//it is not yet confirmed if polling can be stopped early and/or (re)started later
|
||||||
//below is a list of games that have had input issues over the years.
|
if(!io.autoJoypadPoll) return;
|
||||||
//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
|
|
||||||
|
|
||||||
if(configuration.hacks.cpu.fastJoypadPolling) {
|
if(vcounter() == ppu.vdisp() && hcounter() >= 130 && hcounter() <= 256) {
|
||||||
//Taikyoku Igo - Goliath
|
//begin new polling sequence
|
||||||
//Williams Arcade's Greatest Hits
|
status.autoJoypadCounter = 0;
|
||||||
//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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
if(status.autoJoypadActive && status.autoJoypadLatch) {
|
|
||||||
if(status.autoJoypadCounter == 0) {
|
|
||||||
controllerPort1.device->latch(1);
|
|
||||||
controllerPort2.device->latch(1);
|
|
||||||
controllerPort1.device->latch(0);
|
|
||||||
controllerPort2.device->latch(0);
|
|
||||||
|
|
||||||
//shift registers are cleared at start of auto joypad polling
|
|
||||||
io.joy1 = 0;
|
|
||||||
io.joy2 = 0;
|
|
||||||
io.joy3 = 0;
|
|
||||||
io.joy4 = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
status.autoJoypadCounter++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//stop after polling has been completed for this frame
|
||||||
|
if(status.autoJoypadCounter >= 33) return;
|
||||||
|
|
||||||
|
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 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();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
status.autoJoypadCounter++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ auto Configuration::process(Markup::Node document, bool load) -> void {
|
||||||
bind(text, "Hacks/Entropy", hacks.entropy);
|
bind(text, "Hacks/Entropy", hacks.entropy);
|
||||||
bind(natural, "Hacks/CPU/Overclock", hacks.cpu.overclock);
|
bind(natural, "Hacks/CPU/Overclock", hacks.cpu.overclock);
|
||||||
bind(boolean, "Hacks/CPU/FastMath", hacks.cpu.fastMath);
|
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/Fast", hacks.ppu.fast);
|
||||||
bind(boolean, "Hacks/PPU/Deinterlace", hacks.ppu.deinterlace);
|
bind(boolean, "Hacks/PPU/Deinterlace", hacks.ppu.deinterlace);
|
||||||
bind(natural, "Hacks/PPU/RenderCycle", hacks.ppu.renderCycle);
|
bind(natural, "Hacks/PPU/RenderCycle", hacks.ppu.renderCycle);
|
||||||
|
|
|
@ -33,7 +33,6 @@ struct Configuration {
|
||||||
struct CPU {
|
struct CPU {
|
||||||
uint overclock = 100;
|
uint overclock = 100;
|
||||||
bool fastMath = false;
|
bool fastMath = false;
|
||||||
bool fastJoypadPolling = false;
|
|
||||||
} cpu;
|
} cpu;
|
||||||
struct PPU {
|
struct PPU {
|
||||||
bool fast = true;
|
bool fast = true;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
auto Program::hackCompatibility() -> void {
|
auto Program::hackCompatibility() -> void {
|
||||||
string entropy = settings.emulator.hack.entropy;
|
string entropy = settings.emulator.hack.entropy;
|
||||||
bool fastJoypadPolling = false;
|
|
||||||
bool fastPPU = settings.emulator.hack.ppu.fast;
|
bool fastPPU = settings.emulator.hack.ppu.fast;
|
||||||
bool fastPPUNoSpriteLimit = settings.emulator.hack.ppu.noSpriteLimit;
|
bool fastPPUNoSpriteLimit = settings.emulator.hack.ppu.noSpriteLimit;
|
||||||
bool fastDSP = settings.emulator.hack.dsp.fast;
|
bool fastDSP = settings.emulator.hack.dsp.fast;
|
||||||
|
@ -10,15 +9,6 @@ auto Program::hackCompatibility() -> void {
|
||||||
auto title = superFamicom.title;
|
auto title = superFamicom.title;
|
||||||
auto region = superFamicom.region;
|
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
|
//relies on mid-scanline rendering techniques
|
||||||
if(title == "AIR STRIKE PATROL" || title == "DESERT FIGHTER") fastPPU = false;
|
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/Entropy", entropy);
|
||||||
emulator->configure("Hacks/CPU/FastJoypadPolling", fastJoypadPolling);
|
|
||||||
emulator->configure("Hacks/PPU/Fast", fastPPU);
|
emulator->configure("Hacks/PPU/Fast", fastPPU);
|
||||||
emulator->configure("Hacks/PPU/NoSpriteLimit", fastPPUNoSpriteLimit);
|
emulator->configure("Hacks/PPU/NoSpriteLimit", fastPPUNoSpriteLimit);
|
||||||
emulator->configure("Hacks/PPU/RenderCycle", renderCycle);
|
emulator->configure("Hacks/PPU/RenderCycle", renderCycle);
|
||||||
|
|
|
@ -165,15 +165,6 @@ auto Program::load() -> void {
|
||||||
auto title = superFamicom.title;
|
auto title = superFamicom.title;
|
||||||
auto region = superFamicom.region;
|
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
|
//relies on mid-scanline rendering techniques
|
||||||
if(title == "AIR STRIKE PATROL" || title == "DESERT FIGHTER") emulator->configure("Hacks/PPU/Fast", false);
|
if(title == "AIR STRIKE PATROL" || title == "DESERT FIGHTER") emulator->configure("Hacks/PPU/Fast", false);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue