mirror of
https://github.com/libretro/Mesen.git
synced 2024-11-23 09:09:45 +00:00
commit
d25d60fc19
@ -154,18 +154,13 @@ void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint1
|
||||
uint32_t pageCount = 0;
|
||||
uint32_t pageSize = 0;
|
||||
uint8_t defaultAccessType = MemoryAccessType::Read;
|
||||
|
||||
if(type == ChrMemoryType::Default) {
|
||||
type = _chrRomSize > 0 ? ChrMemoryType::ChrRom : ChrMemoryType::ChrRam;
|
||||
}
|
||||
|
||||
switch(type) {
|
||||
case ChrMemoryType::Default:
|
||||
pageSize = InternalGetChrPageSize();
|
||||
if(pageSize == 0) {
|
||||
return;
|
||||
}
|
||||
pageCount = GetCHRPageCount();
|
||||
if(_onlyChrRam) {
|
||||
defaultAccessType |= MemoryAccessType::Write;
|
||||
}
|
||||
break;
|
||||
|
||||
case ChrMemoryType::ChrRom:
|
||||
pageSize = InternalGetChrPageSize();
|
||||
if(pageSize == 0)
|
||||
@ -194,7 +189,6 @@ void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint1
|
||||
pageNumber = pageNumber % pageCount;
|
||||
|
||||
if((uint16_t)(endAddr - startAddr) >= pageSize) {
|
||||
|
||||
uint32_t addr = startAddr;
|
||||
while(addr <= endAddr - pageSize + 1) {
|
||||
SetPpuMemoryMapping(addr, addr + pageSize - 1, type, pageNumber * pageSize, accessType);
|
||||
@ -209,17 +203,22 @@ void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint1
|
||||
void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, ChrMemoryType type, uint32_t sourceOffset, int8_t accessType)
|
||||
{
|
||||
uint8_t* sourceMemory = nullptr;
|
||||
|
||||
if(type == ChrMemoryType::Default) {
|
||||
type = _chrRomSize > 0 ? ChrMemoryType::ChrRom : ChrMemoryType::ChrRam;
|
||||
}
|
||||
|
||||
switch(type) {
|
||||
default:
|
||||
case ChrMemoryType::Default:
|
||||
sourceMemory = _onlyChrRam ? _chrRam : _chrRom;
|
||||
type = _onlyChrRam ? ChrMemoryType::ChrRam : ChrMemoryType::ChrRom;
|
||||
case ChrMemoryType::ChrRom:
|
||||
sourceMemory = _chrRom;
|
||||
break;
|
||||
|
||||
case ChrMemoryType::ChrRom: sourceMemory = _chrRom; break;
|
||||
case ChrMemoryType::ChrRam: sourceMemory = _chrRam; break;
|
||||
case ChrMemoryType::NametableRam: sourceMemory = _nametableRam; break;
|
||||
}
|
||||
|
||||
int firstSlot = startAddr >> 8;
|
||||
int slotCount = (endAddr - startAddr + 1) >> 8;
|
||||
for(int i = 0; i < slotCount; i++) {
|
||||
@ -323,6 +322,9 @@ void BaseMapper::SelectCHRPage(uint16_t slot, uint16_t page, ChrMemoryType memor
|
||||
if(memoryType == ChrMemoryType::NametableRam) {
|
||||
pageSize = BaseMapper::NametableSize;
|
||||
} else {
|
||||
if(memoryType == ChrMemoryType::Default) {
|
||||
memoryType = _chrRomSize > 0 ? ChrMemoryType::ChrRom : ChrMemoryType::ChrRam;
|
||||
}
|
||||
pageSize = memoryType == ChrMemoryType::ChrRam ? InternalGetChrRamPageSize() : InternalGetChrPageSize();
|
||||
}
|
||||
|
||||
@ -415,7 +417,7 @@ bool BaseMapper::HasChrRam()
|
||||
|
||||
bool BaseMapper::HasChrRom()
|
||||
{
|
||||
return !_onlyChrRam;
|
||||
return _chrRomSize > 0;
|
||||
}
|
||||
|
||||
void BaseMapper::AddRegisterRange(uint16_t startAddr, uint16_t endAddr, MemoryOperation operation)
|
||||
@ -516,6 +518,7 @@ void BaseMapper::Initialize(RomData &romData)
|
||||
|
||||
_prgRom = new uint8_t[_prgSize];
|
||||
_chrRom = new uint8_t[_chrRomSize];
|
||||
|
||||
memcpy(_prgRom, romData.PrgRom.data(), _prgSize);
|
||||
if(_chrRomSize > 0) {
|
||||
memcpy(_chrRom, romData.ChrRom.data(), _chrRomSize);
|
||||
@ -554,12 +557,10 @@ void BaseMapper::Initialize(RomData &romData)
|
||||
|
||||
if(_chrRomSize == 0) {
|
||||
//Assume there is CHR RAM if no CHR ROM exists
|
||||
_onlyChrRam = true;
|
||||
InitializeChrRam(romData.ChrRamSize);
|
||||
|
||||
//Map CHR RAM to 0x0000-0x1FFF by default when no CHR ROM exists
|
||||
SetPpuMemoryMapping(0x0000, 0x1FFF, 0, ChrMemoryType::ChrRam);
|
||||
_chrRomSize = _chrRamSize;
|
||||
} else if(romData.ChrRamSize >= 0) {
|
||||
InitializeChrRam(romData.ChrRamSize);
|
||||
} else if(GetChrRamSize()) {
|
||||
@ -852,7 +853,7 @@ uint32_t BaseMapper::GetMemorySize(DebugMemoryType type)
|
||||
{
|
||||
switch(type) {
|
||||
default: return 0;
|
||||
case DebugMemoryType::ChrRom: return _onlyChrRam ? 0 : _chrRomSize;
|
||||
case DebugMemoryType::ChrRom: return _chrRomSize;
|
||||
case DebugMemoryType::ChrRam: return _chrRamSize;
|
||||
case DebugMemoryType::NametableRam: return _nametableCount * BaseMapper::NametableSize;
|
||||
case DebugMemoryType::SaveRam: return _saveRamSize;
|
||||
@ -1016,7 +1017,7 @@ int32_t BaseMapper::ToAbsoluteChrRomAddress(uint16_t addr)
|
||||
|
||||
int32_t BaseMapper::FromAbsoluteChrAddress(uint32_t addr)
|
||||
{
|
||||
uint8_t* ptrAddress = (_onlyChrRam ? _chrRam : _chrRom) + (addr & 0x3FFF);
|
||||
uint8_t* ptrAddress = _chrRom + (addr & 0x3FFF);
|
||||
|
||||
for(int i = 0; i < 64; i++) {
|
||||
uint8_t* pageAddress = _chrPages[i];
|
||||
@ -1095,14 +1096,14 @@ CartridgeState BaseMapper::GetState()
|
||||
state.HasBattery = _romInfo.HasBattery;
|
||||
|
||||
state.PrgRomSize = _prgSize;
|
||||
state.ChrRomSize = _onlyChrRam ? 0 : _chrRomSize;
|
||||
state.ChrRomSize = _chrRomSize;
|
||||
state.ChrRamSize = _chrRamSize;
|
||||
|
||||
state.PrgPageCount = GetPRGPageCount();
|
||||
state.PrgPageSize = InternalGetPrgPageSize();
|
||||
state.ChrPageCount = GetCHRPageCount();
|
||||
state.ChrPageSize = InternalGetChrPageSize();
|
||||
state.ChrRamPageSize = _onlyChrRam ? InternalGetChrPageSize() : InternalGetChrRamPageSize();
|
||||
state.ChrRamPageSize = InternalGetChrRamPageSize();
|
||||
for(int i = 0; i < 0x100; i++) {
|
||||
state.PrgMemoryOffset[i] = _prgMemoryOffset[i];
|
||||
state.PrgType[i] = _prgMemoryType[i];
|
||||
@ -1157,20 +1158,16 @@ void BaseMapper::GetRomFileData(vector<uint8_t> &out, bool asIpsFile, uint8_t* h
|
||||
vector<uint8_t> BaseMapper::GetPrgChrCopy()
|
||||
{
|
||||
vector<uint8_t> data;
|
||||
data.resize(_prgSize + (_onlyChrRam ? 0 : _chrRomSize));
|
||||
data.resize(_prgSize + _chrRomSize);
|
||||
memcpy(data.data(), _prgRom, _prgSize);
|
||||
if(!_onlyChrRam) {
|
||||
memcpy(data.data() + _prgSize, _chrRom, _chrRomSize);
|
||||
}
|
||||
memcpy(data.data() + _prgSize, _chrRom, _chrRomSize);
|
||||
return data;
|
||||
}
|
||||
|
||||
void BaseMapper::RestorePrgChrBackup(vector<uint8_t> &backupData)
|
||||
{
|
||||
memcpy(_prgRom, backupData.data(), _prgSize);
|
||||
if(!_onlyChrRam) {
|
||||
memcpy(_chrRom, backupData.data() + _prgSize, _chrRomSize);
|
||||
}
|
||||
memcpy(_chrRom, backupData.data() + _prgSize, _chrRomSize);
|
||||
}
|
||||
|
||||
void BaseMapper::RevertPrgChrChanges()
|
||||
@ -1198,8 +1195,6 @@ void BaseMapper::CopyPrgChrRom(shared_ptr<BaseMapper> mapper)
|
||||
{
|
||||
if(_prgSize == mapper->_prgSize && _chrRomSize == mapper->_chrRomSize) {
|
||||
memcpy(_prgRom, mapper->_prgRom, _prgSize);
|
||||
if(!_onlyChrRam) {
|
||||
memcpy(_chrRom, mapper->_chrRom, _chrRomSize);
|
||||
}
|
||||
memcpy(_chrRom, mapper->_chrRom, _chrRomSize);
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ private:
|
||||
uint8_t *_nametableRam = nullptr;
|
||||
uint8_t _nametableCount = 2;
|
||||
|
||||
bool _onlyChrRam = false;
|
||||
bool _hasBusConflicts = false;
|
||||
|
||||
bool _allowRegisterRead = false;
|
||||
@ -78,7 +77,7 @@ protected:
|
||||
|
||||
bool IsNes20();
|
||||
|
||||
virtual uint16_t GetChrRamPageSize() { return 0x2000; }
|
||||
virtual uint16_t GetChrRamPageSize() { return GetCHRPageSize(); }
|
||||
|
||||
//Save ram is battery backed and saved to disk
|
||||
virtual uint32_t GetSaveRamSize() { return HasBattery() ? 0x2000 : 0; }
|
||||
|
98
Core/CPU.cpp
98
Core/CPU.cpp
@ -6,6 +6,7 @@
|
||||
#include "APU.h"
|
||||
#include "DeltaModulationChannel.h"
|
||||
#include "MemoryManager.h"
|
||||
#include "ControlManager.h"
|
||||
#include "Console.h"
|
||||
|
||||
CPU::CPU(shared_ptr<Console> console)
|
||||
@ -306,14 +307,26 @@ void CPU::ProcessPendingDma(uint16_t readAddress)
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t prevReadAddress = readAddress;
|
||||
bool enableInternalRegReads = (readAddress & 0xFFE0) == 0x4000;
|
||||
bool skipFirstInputClock = false;
|
||||
if(enableInternalRegReads && _dmcDmaRunning && (readAddress == 0x4016 || readAddress == 0x4017)) {
|
||||
uint16_t dmcAddress = _console->GetApu()->GetDmcReadAddress();
|
||||
if((dmcAddress & 0x1F) == (readAddress & 0x1F)) {
|
||||
//DMC will cause a read on the same address as the CPU was reading from
|
||||
//This will hide the reads from the controllers because /OE will be active the whole time
|
||||
skipFirstInputClock = true;
|
||||
}
|
||||
}
|
||||
|
||||
//On PAL, the dummy/idle reads done by the DMA don't appear to be done on the
|
||||
//address that the CPU was about to read. This prevents the 2+x reads on registers issues.
|
||||
//The exact specifics of where the CPU reads instead aren't known yet - so just disable read side-effects entirely on PAL
|
||||
bool allowReadSideEffects = _console->GetModel() != NesModel::PAL;
|
||||
bool isNtscInputBehavior = _console->GetModel() != NesModel::PAL;
|
||||
|
||||
//"If this cycle is a read, hijack the read, discard the value, and prevent all other actions that occur on this cycle (PC not incremented, etc)"
|
||||
StartCpuCycle(true);
|
||||
if(allowReadSideEffects) {
|
||||
if(isNtscInputBehavior && !skipFirstInputClock) {
|
||||
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
|
||||
}
|
||||
EndCpuCycle(true);
|
||||
@ -327,7 +340,7 @@ void CPU::ProcessPendingDma(uint16_t readAddress)
|
||||
//On NES (or AV Famicom), only the first dummy/idle read causes side effects (e.g only a single bit is lost)
|
||||
ConsoleType type = _console->GetSettings()->GetConsoleType();
|
||||
bool isNesBehavior = type != ConsoleType::Famicom;
|
||||
bool skipDummyReads = !allowReadSideEffects || (isNesBehavior && (readAddress == 0x4016 || readAddress == 0x4017));
|
||||
bool skipDummyReads = !isNtscInputBehavior || (isNesBehavior && (readAddress == 0x4016 || readAddress == 0x4017));
|
||||
|
||||
auto processCycle = [this] {
|
||||
//Sprite DMA cycles count as halt/dummy cycles for the DMC DMA when both run at the same time
|
||||
@ -345,14 +358,14 @@ void CPU::ProcessPendingDma(uint16_t readAddress)
|
||||
if(_dmcDmaRunning && !_needHalt && !_needDummyRead) {
|
||||
//DMC DMA is ready to read a byte (both halt and dummy read cycles were performed before this)
|
||||
processCycle();
|
||||
readValue = _memoryManager->Read(_console->GetApu()->GetDmcReadAddress(), MemoryOperationType::DmcRead);
|
||||
readValue = ProcessDmaRead(_console->GetApu()->GetDmcReadAddress(), prevReadAddress, enableInternalRegReads, isNesBehavior);
|
||||
EndCpuCycle(true);
|
||||
_console->GetApu()->SetDmcReadBuffer(readValue);
|
||||
_dmcDmaRunning = false;
|
||||
} else if(_spriteDmaTransfer) {
|
||||
//DMC DMA is not running, or not ready, run sprite DMA
|
||||
processCycle();
|
||||
readValue = _memoryManager->Read(_spriteDmaOffset * 0x100 + spriteReadAddr);
|
||||
readValue = ProcessDmaRead(_spriteDmaOffset * 0x100 + spriteReadAddr, prevReadAddress, enableInternalRegReads, isNesBehavior);
|
||||
EndCpuCycle(true);
|
||||
spriteReadAddr++;
|
||||
spriteDmaCounter++;
|
||||
@ -361,7 +374,7 @@ void CPU::ProcessPendingDma(uint16_t readAddress)
|
||||
assert(_needHalt || _needDummyRead);
|
||||
processCycle();
|
||||
if(!skipDummyReads) {
|
||||
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
|
||||
ProcessDmaRead(readAddress, prevReadAddress, enableInternalRegReads, isNesBehavior);
|
||||
}
|
||||
EndCpuCycle(true);
|
||||
}
|
||||
@ -379,7 +392,7 @@ void CPU::ProcessPendingDma(uint16_t readAddress)
|
||||
//Align to read cycle before starting sprite DMA (or align to perform DMC read)
|
||||
processCycle();
|
||||
if(!skipDummyReads) {
|
||||
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
|
||||
ProcessDmaRead(readAddress, prevReadAddress, enableInternalRegReads, isNesBehavior);
|
||||
}
|
||||
EndCpuCycle(true);
|
||||
}
|
||||
@ -387,6 +400,77 @@ void CPU::ProcessPendingDma(uint16_t readAddress)
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t CPU::ProcessDmaRead(uint16_t addr, uint16_t& prevReadAddress, bool enableInternalRegReads, bool isNesBehavior)
|
||||
{
|
||||
//This is to reproduce a CPU bug that can occur during DMA which can cause the 2A03 to read from
|
||||
//its internal registers (4015, 4016, 4017) at the same time as the DMA unit reads a byte from
|
||||
//the bus. This bug occurs if the CPU is halted while it's reading a value in the $4000-$401F range.
|
||||
//
|
||||
//This has a number of side effects:
|
||||
// -It can cause a read of $4015 to occur without the program's knowledge, which would clear the frame counter's IRQ flag
|
||||
// -It can cause additional bit deletions while reading the input (e.g more than the DMC glitch usually causes)
|
||||
// -It can also *prevent* bit deletions from occurring at all in another scenario
|
||||
// -It can replace/corrupt the byte that the DMA is reading, causing DMC to play the wrong sample
|
||||
|
||||
uint8_t val;
|
||||
if(!enableInternalRegReads) {
|
||||
if(addr >= 0x4000 && addr <= 0x401F) {
|
||||
//Nothing will respond on $4000-$401F on the external bus - return open bus value
|
||||
val = _memoryManager->GetOpenBus();
|
||||
} else {
|
||||
val = _memoryManager->Read(addr, MemoryOperationType::DmcRead);
|
||||
|
||||
}
|
||||
prevReadAddress = addr;
|
||||
return val;
|
||||
} else {
|
||||
//This glitch causes the CPU to read from the internal APU/Input registers
|
||||
//regardless of the address the DMA unit is trying to read
|
||||
uint16_t internalAddr = 0x4000 | (addr & 0x1F);
|
||||
bool isSameAddress = internalAddr == addr;
|
||||
|
||||
switch(internalAddr) {
|
||||
case 0x4015:
|
||||
val = _memoryManager->Read(internalAddr, MemoryOperationType::DmcRead);
|
||||
if(!isSameAddress) {
|
||||
//Also trigger a read from the actual address the CPU was supposed to read from (external bus)
|
||||
_memoryManager->Read(addr, MemoryOperationType::DmcRead);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x4016:
|
||||
case 0x4017:
|
||||
if(_console->GetModel() == NesModel::PAL || (isNesBehavior && prevReadAddress == internalAddr)) {
|
||||
//Reading from the same input register twice in a row, skip the read entirely to avoid
|
||||
//triggering a bit loss from the read, since the controller won't react to this read
|
||||
//Return the same value as the last read, instead
|
||||
//On PAL, the behavior is unknown - for now, don't cause any bit deletions
|
||||
val = _memoryManager->GetOpenBus();
|
||||
} else {
|
||||
val = _memoryManager->Read(internalAddr, MemoryOperationType::DmcRead);
|
||||
}
|
||||
|
||||
if(!isSameAddress) {
|
||||
//The DMA unit is reading from a different address, read from it too (external bus)
|
||||
uint8_t obMask = _console->GetControlManager()->GetOpenBusMask(internalAddr - 0x4016);
|
||||
uint8_t externalValue = _memoryManager->Read(addr, MemoryOperationType::DmcRead);
|
||||
|
||||
//Merge values, keep the external value for all open bus pins on the 4016/4017 port
|
||||
//AND all other bits together (bus conflict)
|
||||
val = (externalValue & obMask) | ((val & ~obMask) & (externalValue & ~obMask));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
val = _memoryManager->Read(addr, MemoryOperationType::DmcRead);
|
||||
break;
|
||||
}
|
||||
|
||||
prevReadAddress = internalAddr;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::RunDMATransfer(uint8_t offsetValue)
|
||||
{
|
||||
_spriteDmaTransfer = true;
|
||||
|
@ -80,6 +80,7 @@ private:
|
||||
|
||||
__forceinline void StartCpuCycle(bool forRead);
|
||||
__forceinline void ProcessPendingDma(uint16_t readAddress);
|
||||
uint8_t ProcessDmaRead(uint16_t addr, uint16_t& prevReadAddress, bool enableInternalRegReads, bool isNesBehavior);
|
||||
__forceinline uint16_t FetchOperand();
|
||||
__forceinline void EndCpuCycle(bool forRead);
|
||||
void IRQ();
|
||||
|
@ -39,12 +39,13 @@ protected:
|
||||
virtual void StreamState(bool saving) override;
|
||||
virtual ControllerType GetControllerType(uint8_t port);
|
||||
virtual void RemapControllerButtons();
|
||||
virtual uint8_t GetOpenBusMask(uint8_t port);
|
||||
|
||||
public:
|
||||
ControlManager(std::shared_ptr<Console> console, std::shared_ptr<BaseControlDevice> systemActionManager, std::shared_ptr<BaseControlDevice> mapperControlDevice);
|
||||
virtual ~ControlManager();
|
||||
|
||||
virtual uint8_t GetOpenBusMask(uint8_t port);
|
||||
|
||||
virtual void UpdateControlDevices();
|
||||
void UpdateInputState();
|
||||
|
||||
|
103
Core/PPU.cpp
103
Core/PPU.cpp
@ -983,19 +983,16 @@ void PPU::ProcessScanline()
|
||||
}
|
||||
}
|
||||
} else if(_cycle >= 321 && _cycle <= 336) {
|
||||
LoadTileInfo();
|
||||
if(_cycle == 321) {
|
||||
if(IsRenderingEnabled()) {
|
||||
LoadExtraSprites();
|
||||
_oamCopybuffer = _secondarySpriteRAM[0];
|
||||
}
|
||||
LoadTileInfo();
|
||||
} else if(_prevRenderingEnabled && (_cycle == 328 || _cycle == 336)) {
|
||||
LoadTileInfo();
|
||||
_state.LowBitShift <<= 8;
|
||||
_state.HighBitShift <<= 8;
|
||||
IncHorizontalScrolling();
|
||||
} else {
|
||||
LoadTileInfo();
|
||||
}
|
||||
} else if(_cycle == 337 || _cycle == 339) {
|
||||
if(IsRenderingEnabled()) {
|
||||
@ -1247,52 +1244,8 @@ void PPU::ProcessOamCorruption()
|
||||
|
||||
void PPU::Exec()
|
||||
{
|
||||
if(_cycle > 339) {
|
||||
_cycle = 0;
|
||||
if(++_scanline > _vblankEnd) {
|
||||
_lastUpdatedPixel = -1;
|
||||
_scanline = -1;
|
||||
|
||||
//Force prerender scanline sprite fetches to load the dummy $FF tiles (fixes shaking in Ninja Gaiden 3 stage 1 after beating boss)
|
||||
_spriteCount = 0;
|
||||
|
||||
if(_renderingEnabled) {
|
||||
ProcessOamCorruption();
|
||||
}
|
||||
|
||||
UpdateMinimumDrawCycles();
|
||||
}
|
||||
|
||||
UpdateApuStatus();
|
||||
|
||||
if(_scanline == _settings->GetInputPollScanline()) {
|
||||
_console->GetControlManager()->UpdateInputState();
|
||||
}
|
||||
|
||||
//Cycle = 0
|
||||
if(_scanline < 240) {
|
||||
if(_scanline == -1) {
|
||||
_statusFlags.SpriteOverflow = false;
|
||||
_statusFlags.Sprite0Hit = false;
|
||||
|
||||
//Switch to alternate output buffer (VideoDecoder may still be decoding the last frame buffer)
|
||||
_currentOutputBuffer = (_currentOutputBuffer == _outputBuffers[0]) ? _outputBuffers[1] : _outputBuffers[0];
|
||||
} else if(_prevRenderingEnabled) {
|
||||
if(_scanline > 0 || (!(_frameCount & 0x01) || _nesModel != NesModel::NTSC || _settings->GetPpuModel() != PpuModel::Ppu2C02)) {
|
||||
//Set bus address to the tile address calculated from the unused NT fetches at the end of the previous scanline
|
||||
//This doesn't happen on scanline 0 if the last dot of the previous frame was skipped
|
||||
SetBusAddress((_nextTile.TileAddr << 4) | (_state.VideoRamAddr >> 12) | _flags.BackgroundPatternAddr);
|
||||
}
|
||||
}
|
||||
} else if(_scanline == 240) {
|
||||
//At the start of vblank, the bus address is set back to VideoRamAddr.
|
||||
//According to Visual NES, this occurs on scanline 240, cycle 1, but is done here on cycle for performance reasons
|
||||
SetBusAddress(_state.VideoRamAddr);
|
||||
SendFrame();
|
||||
_frameCount++;
|
||||
}
|
||||
} else {
|
||||
//Cycle > 0
|
||||
if(_cycle < 340) {
|
||||
//Process cycles 1 to 340
|
||||
_cycle++;
|
||||
|
||||
if(_scanline < 240) {
|
||||
@ -1314,6 +1267,9 @@ void PPU::Exec()
|
||||
_state.SpriteRamAddr = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//Process cycle 0
|
||||
ProcessScanlineFirstCycle();
|
||||
}
|
||||
|
||||
if(_needStateUpdate) {
|
||||
@ -1321,6 +1277,53 @@ void PPU::Exec()
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::ProcessScanlineFirstCycle()
|
||||
{
|
||||
_cycle = 0;
|
||||
if(++_scanline > _vblankEnd) {
|
||||
_lastUpdatedPixel = -1;
|
||||
_scanline = -1;
|
||||
|
||||
//Force prerender scanline sprite fetches to load the dummy $FF tiles (fixes shaking in Ninja Gaiden 3 stage 1 after beating boss)
|
||||
_spriteCount = 0;
|
||||
|
||||
if(_renderingEnabled) {
|
||||
ProcessOamCorruption();
|
||||
}
|
||||
|
||||
UpdateMinimumDrawCycles();
|
||||
}
|
||||
|
||||
UpdateApuStatus();
|
||||
|
||||
if(_scanline == _settings->GetInputPollScanline()) {
|
||||
_console->GetControlManager()->UpdateInputState();
|
||||
}
|
||||
|
||||
//Cycle = 0
|
||||
if(_scanline < 240) {
|
||||
if(_scanline == -1) {
|
||||
_statusFlags.SpriteOverflow = false;
|
||||
_statusFlags.Sprite0Hit = false;
|
||||
|
||||
//Switch to alternate output buffer (VideoDecoder may still be decoding the last frame buffer)
|
||||
_currentOutputBuffer = (_currentOutputBuffer == _outputBuffers[0]) ? _outputBuffers[1] : _outputBuffers[0];
|
||||
} else if(_prevRenderingEnabled) {
|
||||
if(_scanline > 0 || (!(_frameCount & 0x01) || _nesModel != NesModel::NTSC || _settings->GetPpuModel() != PpuModel::Ppu2C02)) {
|
||||
//Set bus address to the tile address calculated from the unused NT fetches at the end of the previous scanline
|
||||
//This doesn't happen on scanline 0 if the last dot of the previous frame was skipped
|
||||
SetBusAddress((_nextTile.TileAddr << 4) | (_state.VideoRamAddr >> 12) | _flags.BackgroundPatternAddr);
|
||||
}
|
||||
}
|
||||
} else if(_scanline == 240) {
|
||||
//At the start of vblank, the bus address is set back to VideoRamAddr.
|
||||
//According to Visual NES, this occurs on scanline 240, cycle 1, but is done here on cycle for performance reasons
|
||||
SetBusAddress(_state.VideoRamAddr);
|
||||
SendFrame();
|
||||
_frameCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::UpdateState()
|
||||
{
|
||||
_needStateUpdate = false;
|
||||
|
@ -129,16 +129,17 @@ class PPU : public IMemoryHandler, public Snapshotable
|
||||
uint16_t GetNameTableAddr();
|
||||
uint16_t GetAttributeAddr();
|
||||
|
||||
void ProcessScanlineFirstCycle();
|
||||
__forceinline void ProcessScanline();
|
||||
__forceinline void ProcessSpriteEvaluation();
|
||||
|
||||
void BeginVBlank();
|
||||
void TriggerNmi();
|
||||
|
||||
void LoadTileInfo();
|
||||
void LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uint8_t spriteX, bool extraSprite);
|
||||
void LoadSpriteTileInfo();
|
||||
void LoadExtraSprites();
|
||||
__forceinline void LoadTileInfo();
|
||||
__forceinline void LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uint8_t spriteX, bool extraSprite);
|
||||
__forceinline void LoadSpriteTileInfo();
|
||||
__forceinline void LoadExtraSprites();
|
||||
__forceinline void ShiftTileRegisters();
|
||||
|
||||
__forceinline uint8_t ReadSpriteRam(uint8_t addr);
|
||||
|
@ -73,8 +73,8 @@ private:
|
||||
|
||||
double _previousTargetRate;
|
||||
|
||||
double GetChannelOutput(AudioChannel channel, bool forRightChannel);
|
||||
int16_t GetOutputVolume(bool forRightChannel);
|
||||
__forceinline double GetChannelOutput(AudioChannel channel, bool forRightChannel);
|
||||
__forceinline int16_t GetOutputVolume(bool forRightChannel);
|
||||
void EndFrame(uint32_t time);
|
||||
|
||||
void UpdateRates(bool forceUpdate);
|
||||
|
Loading…
Reference in New Issue
Block a user