diff --git a/bsnes/sfc/cpu/cpu.hpp b/bsnes/sfc/cpu/cpu.hpp index fa69da40..912ec6b5 100644 --- a/bsnes/sfc/cpu/cpu.hpp +++ b/bsnes/sfc/cpu/cpu.hpp @@ -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 { diff --git a/bsnes/sfc/cpu/timing.cpp b/bsnes/sfc/cpu/timing.cpp index 987c0dda..d3f78a22 100644 --- a/bsnes/sfc/cpu/timing.cpp +++ b/bsnes/sfc/cpu/timing.cpp @@ -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,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 { - //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); - } - - 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++; - } + if(vcounter() == ppu.vdisp() && hcounter() >= 130 && hcounter() <= 256) { + //begin new polling sequence + status.autoJoypadCounter = 0; } + + //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++; } diff --git a/bsnes/sfc/interface/configuration.cpp b/bsnes/sfc/interface/configuration.cpp index c689ad11..769b8b0c 100644 --- a/bsnes/sfc/interface/configuration.cpp +++ b/bsnes/sfc/interface/configuration.cpp @@ -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); diff --git a/bsnes/sfc/interface/configuration.hpp b/bsnes/sfc/interface/configuration.hpp index bcf6345c..5f4bd683 100644 --- a/bsnes/sfc/interface/configuration.hpp +++ b/bsnes/sfc/interface/configuration.hpp @@ -33,7 +33,6 @@ struct Configuration { struct CPU { uint overclock = 100; bool fastMath = false; - bool fastJoypadPolling = false; } cpu; struct PPU { bool fast = true; diff --git a/bsnes/target-bsnes/program/hacks.cpp b/bsnes/target-bsnes/program/hacks.cpp index 92721faa..b3750703 100644 --- a/bsnes/target-bsnes/program/hacks.cpp +++ b/bsnes/target-bsnes/program/hacks.cpp @@ -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); diff --git a/bsnes/target-libretro/program.cpp b/bsnes/target-libretro/program.cpp index 1a0034b1..9f101908 100644 --- a/bsnes/target-libretro/program.cpp +++ b/bsnes/target-libretro/program.cpp @@ -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);