GB: Various emulation improvements
-Better OAM DMA emulation -Better STAT IRQ emulation -Better LCD power on state -Moved DMA logic to GbDmaController
This commit is contained in:
parent
c32472913c
commit
10be89012c
13 changed files with 263 additions and 94 deletions
|
@ -73,6 +73,7 @@
|
|||
<ClInclude Include="GbCartFactory.h" />
|
||||
<ClInclude Include="GbCpu.h" />
|
||||
<ClInclude Include="GbDebugger.h" />
|
||||
<ClInclude Include="GbDmaController.h" />
|
||||
<ClInclude Include="GbEventManager.h" />
|
||||
<ClInclude Include="GbMbc1.h" />
|
||||
<ClInclude Include="GbMbc2.h" />
|
||||
|
@ -280,6 +281,7 @@
|
|||
<ClCompile Include="GbApu.cpp" />
|
||||
<ClCompile Include="GbCpu.cpp" />
|
||||
<ClCompile Include="GbDebugger.cpp" />
|
||||
<ClCompile Include="GbDmaController.cpp" />
|
||||
<ClCompile Include="GbEventManager.cpp" />
|
||||
<ClCompile Include="GbMemoryManager.cpp" />
|
||||
<ClCompile Include="GbPpu.cpp" />
|
||||
|
|
|
@ -587,6 +587,9 @@
|
|||
<ClInclude Include="BaseEventManager.h">
|
||||
<Filter>Debugger\EventManager</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GbDmaController.h">
|
||||
<Filter>GB</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="stdafx.cpp" />
|
||||
|
@ -932,6 +935,9 @@
|
|||
<ClCompile Include="GbEventManager.cpp">
|
||||
<Filter>Debugger\EventManager</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GbDmaController.cpp">
|
||||
<Filter>GB</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="SNES">
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "GbApu.h"
|
||||
#include "GbCart.h"
|
||||
#include "GbTimer.h"
|
||||
#include "GbDmaController.h"
|
||||
#include "DebugTypes.h"
|
||||
#include "GbMemoryManager.h"
|
||||
#include "GbCartFactory.h"
|
||||
|
@ -88,6 +89,7 @@ void Gameboy::PowerOn()
|
|||
|
||||
//VRAM is filled with 0s by the boot rom
|
||||
memset(_videoRam, 0, _videoRamSize);
|
||||
memset(_spriteRam, 0, Gameboy::SpriteRamSize);
|
||||
|
||||
LoadBattery();
|
||||
|
||||
|
@ -95,8 +97,9 @@ void Gameboy::PowerOn()
|
|||
_apu.reset(new GbApu(_console, this));
|
||||
_memoryManager.reset(new GbMemoryManager());
|
||||
_timer.reset(new GbTimer(_memoryManager.get(), _apu.get()));
|
||||
_dmaController.reset(new GbDmaController(_memoryManager.get()));
|
||||
_cart->Init(this, _memoryManager.get());
|
||||
_memoryManager->Init(_console, this, _cart.get(), _ppu.get(), _apu.get(), _timer.get());
|
||||
_memoryManager->Init(_console, this, _cart.get(), _ppu.get(), _apu.get(), _timer.get(), _dmaController.get());
|
||||
|
||||
_cpu.reset(new GbCpu(_console, this, _memoryManager.get()));
|
||||
_ppu->Init(_console, this, _memoryManager.get(), _videoRam, _spriteRam);
|
||||
|
@ -249,6 +252,7 @@ void Gameboy::Serialize(Serializer& s)
|
|||
s.Stream(_memoryManager.get());
|
||||
s.Stream(_cart.get());
|
||||
s.Stream(_timer.get());
|
||||
s.Stream(_dmaController.get());
|
||||
s.Stream(_hasBattery);
|
||||
|
||||
s.StreamArray(_cartRam, _cartRamSize);
|
||||
|
|
|
@ -10,6 +10,7 @@ class GbCpu;
|
|||
class GbCart;
|
||||
class GbTimer;
|
||||
class GbMemoryManager;
|
||||
class GbDmaController;
|
||||
class VirtualFile;
|
||||
|
||||
class Gameboy : public ISerializable
|
||||
|
@ -26,6 +27,7 @@ private:
|
|||
unique_ptr<GbApu> _apu;
|
||||
unique_ptr<GbCart> _cart;
|
||||
unique_ptr<GbTimer> _timer;
|
||||
unique_ptr<GbDmaController> _dmaController;
|
||||
|
||||
bool _hasBattery;
|
||||
bool _cgbMode;
|
||||
|
|
|
@ -1010,9 +1010,9 @@ void GbCpu::CALL(uint16_t dstAddr)
|
|||
void GbCpu::CALL(bool condition, uint16_t dstAddr)
|
||||
{
|
||||
if(condition) {
|
||||
IncCycleCount();
|
||||
PushWord(_state.PC);
|
||||
_state.PC = dstAddr;
|
||||
IncCycleCount();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
93
Core/GbDmaController.cpp
Normal file
93
Core/GbDmaController.cpp
Normal file
|
@ -0,0 +1,93 @@
|
|||
#include "stdafx.h"
|
||||
#include "GbDmaController.h"
|
||||
#include "MessageManager.h"
|
||||
#include "CpuTypes.h"
|
||||
#include "GbMemoryManager.h"
|
||||
|
||||
GbDmaController::GbDmaController(GbMemoryManager* memoryManager)
|
||||
{
|
||||
_memoryManager = memoryManager;
|
||||
_state = {};
|
||||
}
|
||||
|
||||
void GbDmaController::Exec()
|
||||
{
|
||||
if(_state.DmaCounter > 0) {
|
||||
if(_state.DmaCounter <= 160) {
|
||||
_memoryManager->Write(0xFE00 + (160 - _state.DmaCounter), _state.DmaReadBuffer);
|
||||
}
|
||||
|
||||
_state.DmaCounter--;
|
||||
_state.DmaReadBuffer = _memoryManager->ReadDma((_state.OamDmaDest << 8) + (160 - _state.DmaCounter));
|
||||
}
|
||||
|
||||
if(_state.DmaStartDelay > 0) {
|
||||
if(--_state.DmaStartDelay == 0) {
|
||||
_state.InternalDest = _state.OamDmaDest;
|
||||
_state.DmaCounter = 161;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GbDmaController::IsOamDmaRunning()
|
||||
{
|
||||
return _state.DmaCounter > 0 && _state.DmaCounter < 161;
|
||||
}
|
||||
|
||||
uint8_t GbDmaController::Read()
|
||||
{
|
||||
return _state.OamDmaDest;
|
||||
}
|
||||
|
||||
void GbDmaController::Write(uint8_t value)
|
||||
{
|
||||
_state.DmaStartDelay = 1;
|
||||
_state.OamDmaDest = value;
|
||||
}
|
||||
|
||||
uint8_t GbDmaController::ReadCgb(uint16_t addr)
|
||||
{
|
||||
switch(addr) {
|
||||
case 0xFF51: return _state.CgbDmaSource >> 8;
|
||||
case 0xFF52: return _state.CgbDmaSource & 0xFF;
|
||||
case 0xFF53: return _state.CgbDmaDest >> 8;
|
||||
case 0xFF54: return _state.CgbDmaDest & 0xFF;
|
||||
case 0xFF55: return _state.CgbDmaLength | (_state.CgbHdmaMode ? 0x80 : 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GbDmaController::WriteCgb(uint16_t addr, uint8_t value)
|
||||
{
|
||||
switch(addr) {
|
||||
case 0xFF51: _state.CgbDmaSource = (_state.CgbDmaSource & 0xFF) | (value << 8); break;
|
||||
case 0xFF52: _state.CgbDmaSource = (_state.CgbDmaSource & 0xFF00) | value; break;
|
||||
case 0xFF53: _state.CgbDmaDest = (_state.CgbDmaDest & 0xFF) | (value << 8); break;
|
||||
case 0xFF54: _state.CgbDmaDest = (_state.CgbDmaDest & 0xFF00) | value; break;
|
||||
case 0xFF55:
|
||||
_state.CgbDmaLength = value & 0x7F;
|
||||
_state.CgbHdmaMode = (value & 0x80) != 0;
|
||||
|
||||
if(!_state.CgbHdmaMode) {
|
||||
//TODO check invalid dma sources/etc.
|
||||
//TODO timing
|
||||
for(int i = 0; i < _state.CgbDmaLength * 16; i++) {
|
||||
uint16_t dst = 0x8000 | (((_state.CgbDmaDest & 0x1FF0) + i) & 0x1FFF);
|
||||
_memoryManager->Write(dst, _memoryManager->Read((_state.CgbDmaSource & 0xFFF0) + i, MemoryOperationType::DmaRead));
|
||||
}
|
||||
_state.CgbDmaLength = 0x7F;
|
||||
} else {
|
||||
MessageManager::Log("TODO HDMA");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GbDmaController::Serialize(Serializer& s)
|
||||
{
|
||||
s.Stream(
|
||||
_state.OamDmaDest, _state.DmaStartDelay, _state.InternalDest, _state.DmaCounter, _state.DmaReadBuffer,
|
||||
_state.CgbDmaDest, _state.CgbDmaLength, _state.CgbDmaSource, _state.CgbHdmaMode
|
||||
);
|
||||
}
|
27
Core/GbDmaController.h
Normal file
27
Core/GbDmaController.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "GbTypes.h"
|
||||
#include "../Utilities/ISerializable.h"
|
||||
|
||||
class GbMemoryManager;
|
||||
|
||||
class GbDmaController : public ISerializable
|
||||
{
|
||||
private:
|
||||
GbDmaControllerState _state;
|
||||
GbMemoryManager* _memoryManager;
|
||||
|
||||
public:
|
||||
GbDmaController(GbMemoryManager* memoryManager);
|
||||
void Exec();
|
||||
|
||||
bool IsOamDmaRunning();
|
||||
|
||||
uint8_t Read();
|
||||
void Write(uint8_t value);
|
||||
|
||||
uint8_t ReadCgb(uint16_t addr);
|
||||
void WriteCgb(uint16_t addr, uint8_t value);
|
||||
|
||||
void Serialize(Serializer& s) override;
|
||||
};
|
|
@ -7,6 +7,7 @@
|
|||
#include "GbTimer.h"
|
||||
#include "GbTypes.h"
|
||||
#include "GbCart.h"
|
||||
#include "GbDmaController.h"
|
||||
#include "EmuSettings.h"
|
||||
#include "ControlManager.h"
|
||||
#include "SnesController.h"
|
||||
|
@ -15,10 +16,9 @@
|
|||
#include "../Utilities/Serializer.h"
|
||||
#include "../Utilities/HexUtilities.h"
|
||||
|
||||
void GbMemoryManager::Init(Console* console, Gameboy* gameboy, GbCart* cart, GbPpu* ppu, GbApu* apu, GbTimer* timer)
|
||||
void GbMemoryManager::Init(Console* console, Gameboy* gameboy, GbCart* cart, GbPpu* ppu, GbApu* apu, GbTimer* timer, GbDmaController* dmaController)
|
||||
{
|
||||
_state = {};
|
||||
_state.DisableBootRom = true;
|
||||
_state.CgbWorkRamBank = 1;
|
||||
|
||||
_prgRom = gameboy->DebugGetMemory(SnesMemoryType::GbPrgRom);
|
||||
|
@ -29,12 +29,21 @@ void GbMemoryManager::Init(Console* console, Gameboy* gameboy, GbCart* cart, GbP
|
|||
_workRamSize = gameboy->DebugGetMemorySize(SnesMemoryType::GbWorkRam);
|
||||
_highRam = gameboy->DebugGetMemory(SnesMemoryType::GbHighRam);
|
||||
|
||||
#ifdef USEBOOTROM
|
||||
VirtualFile bootRom("Firmware\\boot.bin");
|
||||
bootRom.ReadFile(_bootRom, 256);
|
||||
_state.DisableBootRom = false;
|
||||
#else
|
||||
_state.DisableBootRom = true;
|
||||
#endif
|
||||
|
||||
_apu = apu;
|
||||
_ppu = ppu;
|
||||
_gameboy = gameboy;
|
||||
_cart = cart;
|
||||
_timer = timer;
|
||||
_console = console;
|
||||
_dmaController = dmaController;
|
||||
_controlManager = console->GetControlManager().get();
|
||||
_settings = console->GetSettings().get();
|
||||
|
||||
|
@ -56,17 +65,16 @@ GbMemoryManagerState GbMemoryManager::GetState()
|
|||
|
||||
void GbMemoryManager::RefreshMappings()
|
||||
{
|
||||
if(!_state.DisableBootRom) {
|
||||
//TODO
|
||||
//Map(0x0000, 0x00FF, GbMemoryType::WorkRam, true);
|
||||
}
|
||||
|
||||
Map(0xC000, 0xCFFF, GbMemoryType::WorkRam, 0, false);
|
||||
Map(0xD000, 0xDFFF, GbMemoryType::WorkRam, _state.CgbWorkRamBank * 0x1000, false);
|
||||
Map(0xE000, 0xEFFF, GbMemoryType::WorkRam, 0, false);
|
||||
Map(0xF000, 0xFCFF, GbMemoryType::WorkRam, _state.CgbWorkRamBank * 0x1000, false);
|
||||
Map(0xF000, 0xFFFF, GbMemoryType::WorkRam, _state.CgbWorkRamBank * 0x1000, false);
|
||||
|
||||
_cart->RefreshMappings();
|
||||
|
||||
if(!_state.DisableBootRom) {
|
||||
_reads[0] = _bootRom;
|
||||
}
|
||||
}
|
||||
|
||||
void GbMemoryManager::Exec()
|
||||
|
@ -74,6 +82,7 @@ void GbMemoryManager::Exec()
|
|||
_state.ApuCycleCount += _state.CgbHighSpeed ? 2 : 4;
|
||||
_timer->Exec();
|
||||
_ppu->Exec();
|
||||
_dmaController->Exec();
|
||||
}
|
||||
|
||||
void GbMemoryManager::MapRegisters(uint16_t start, uint16_t end, RegisterAccess access)
|
||||
|
@ -137,6 +146,23 @@ uint8_t GbMemoryManager::Read(uint16_t addr, MemoryOperationType opType)
|
|||
return value;
|
||||
}
|
||||
|
||||
bool GbMemoryManager::IsOamDmaRunning()
|
||||
{
|
||||
return _dmaController->IsOamDmaRunning();
|
||||
}
|
||||
|
||||
uint8_t GbMemoryManager::ReadDma(uint16_t addr)
|
||||
{
|
||||
uint8_t value = 0;
|
||||
if(_reads[addr >> 8]) {
|
||||
value = _reads[addr >> 8][(uint8_t)addr];
|
||||
} else if(addr >= 0x8000 && addr <= 0x9FFF) {
|
||||
value = ReadRegister(addr);
|
||||
}
|
||||
_console->ProcessMemoryRead<CpuType::Gameboy>(addr, value, MemoryOperationType::DmaRead);
|
||||
return value;
|
||||
}
|
||||
|
||||
void GbMemoryManager::Write(uint16_t addr, uint8_t value)
|
||||
{
|
||||
_console->ProcessMemoryWrite<CpuType::Gameboy>(addr, value, MemoryOperationType::Write);
|
||||
|
@ -184,6 +210,8 @@ uint8_t GbMemoryManager::ReadRegister(uint16_t addr)
|
|||
if(addr >= 0xFF00) {
|
||||
if(addr == 0xFFFF) {
|
||||
return _state.IrqEnabled; //IE - Interrupt Enable (R/W)
|
||||
} else if(addr == 0xFF46) {
|
||||
return _dmaController->Read();
|
||||
} else if(addr >= 0xFF80) {
|
||||
return _highRam[addr & 0x7F]; //80-FE
|
||||
} else if(addr >= 0xFF4D) {
|
||||
|
@ -191,9 +219,11 @@ uint8_t GbMemoryManager::ReadRegister(uint16_t addr)
|
|||
switch(addr) {
|
||||
//FF4D - KEY1 - CGB Mode Only - Prepare Speed Switch
|
||||
case 0xFF4D: return _state.CgbHighSpeed ? 0x80 : 0;
|
||||
|
||||
case 0xFF51: case 0xFF52: case 0xFF53: case 0xFF54: case 0xFF55: //CGB - DMA
|
||||
return _dmaController->ReadCgb(addr);
|
||||
|
||||
case 0xFF4F: //CGB - VRAM bank
|
||||
case 0xFF51: case 0xFF52: case 0xFF53: case 0xFF54: case 0xFF55: //CGB - DMA
|
||||
case 0xFF68: case 0xFF69: case 0xFF6A: case 0xFF6B: //CGB - Palette
|
||||
return _ppu->ReadCgbRegister(addr);
|
||||
|
||||
|
@ -202,7 +232,7 @@ uint8_t GbMemoryManager::ReadRegister(uint16_t addr)
|
|||
}
|
||||
}
|
||||
LogDebug("[Debug] GB - Missing read handler: $" + HexUtilities::ToHex(addr));
|
||||
return 0; //4D-7F
|
||||
return 0xFF; //4D-7F, open bus
|
||||
} else if(addr >= 0xFF40) {
|
||||
return _ppu->Read(addr); //40-4C
|
||||
} else if(addr >= 0xFF10) {
|
||||
|
@ -211,12 +241,16 @@ uint8_t GbMemoryManager::ReadRegister(uint16_t addr)
|
|||
//00-0F
|
||||
switch(addr) {
|
||||
case 0xFF00: return ReadInputPort(); break;
|
||||
case 0xFF01: break; //Serial
|
||||
|
||||
case 0xFF01: return 0; //SB - Serial transfer data (R/W)
|
||||
case 0xFF02: return 0x7E; //SC - Serial Transfer Control (R/W)
|
||||
|
||||
case 0xFF04: case 0xFF05: case 0xFF06: case 0xFF07:
|
||||
return _timer->Read(addr);
|
||||
|
||||
case 0xFF0F: return _state.IrqRequests; break; //IF - Interrupt flags (R/W)
|
||||
|
||||
default: return 0xFF; //Open bus
|
||||
}
|
||||
}
|
||||
} else if(addr >= 0xFE00) {
|
||||
|
@ -233,6 +267,8 @@ void GbMemoryManager::WriteRegister(uint16_t addr, uint8_t value)
|
|||
if(addr >= 0xFF00) {
|
||||
if(addr == 0xFFFF) {
|
||||
_state.IrqEnabled = value; //IE register
|
||||
} else if(addr == 0xFF46) {
|
||||
_dmaController->Write(value);
|
||||
} else if(addr >= 0xFF80) {
|
||||
_highRam[addr & 0x7F] = value; //80-FE
|
||||
} else if(addr >= 0xFF4D) {
|
||||
|
@ -249,8 +285,11 @@ void GbMemoryManager::WriteRegister(uint16_t addr, uint8_t value)
|
|||
_state.CgbSwitchSpeedRequest = (value & 0x01) != 0;
|
||||
break;
|
||||
|
||||
case 0xFF4F: //CGB - VRAM banking
|
||||
case 0xFF51: case 0xFF52: case 0xFF53: case 0xFF54: case 0xFF55: //CGB - DMA
|
||||
_dmaController->WriteCgb(addr, value);
|
||||
break;
|
||||
|
||||
case 0xFF4F: //CGB - VRAM banking
|
||||
case 0xFF68: case 0xFF69: case 0xFF6A: case 0xFF6B: //CGB - Palette
|
||||
_ppu->WriteCgbRegister(addr, value);
|
||||
break;
|
||||
|
@ -364,7 +403,7 @@ uint8_t GbMemoryManager::ReadInputPort()
|
|||
}
|
||||
}
|
||||
|
||||
return result | (_state.InputSelect & 0x30);
|
||||
return result | (_state.InputSelect & 0x30) | 0xC0;
|
||||
}
|
||||
|
||||
void GbMemoryManager::Serialize(Serializer& s)
|
||||
|
@ -384,5 +423,6 @@ void GbMemoryManager::Serialize(Serializer& s)
|
|||
for(int i = 0; i < 0x100; i++) {
|
||||
Map(i*0x100, i*0x100+0xFF, _state.MemoryType[i], _state.MemoryOffset[i], _state.MemoryAccessType[i] == RegisterAccess::ReadWrite ? false : true);
|
||||
}
|
||||
RefreshMappings();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ class GbCart;
|
|||
class GbPpu;
|
||||
class GbApu;
|
||||
class GbTimer;
|
||||
class GbDmaController;
|
||||
|
||||
class EmuSettings;
|
||||
class Console;
|
||||
|
@ -25,7 +26,9 @@ private:
|
|||
GbApu* _apu = nullptr;
|
||||
GbPpu* _ppu = nullptr;
|
||||
GbTimer* _timer;
|
||||
GbDmaController* _dmaController;
|
||||
|
||||
uint8_t _bootRom[256];
|
||||
uint8_t* _prgRom = nullptr;
|
||||
uint32_t _prgRomSize = 0;
|
||||
uint8_t* _workRam = nullptr;
|
||||
|
@ -44,7 +47,7 @@ public:
|
|||
|
||||
GbMemoryManagerState GetState();
|
||||
|
||||
void Init(Console* console, Gameboy* gameboy, GbCart* cart, GbPpu* ppu, GbApu* apu, GbTimer* timer);
|
||||
void Init(Console* console, Gameboy* gameboy, GbCart* cart, GbPpu* ppu, GbApu* apu, GbTimer* timer, GbDmaController* dmaController);
|
||||
void MapRegisters(uint16_t start, uint16_t end, RegisterAccess access);
|
||||
void Map(uint16_t start, uint16_t end, GbMemoryType type, uint32_t offset, bool readonly);
|
||||
void Unmap(uint16_t start, uint16_t end);
|
||||
|
@ -53,6 +56,8 @@ public:
|
|||
void Exec();
|
||||
|
||||
uint8_t Read(uint16_t addr, MemoryOperationType opType);
|
||||
bool IsOamDmaRunning();
|
||||
uint8_t ReadDma(uint16_t addr);
|
||||
void Write(uint16_t addr, uint8_t value);
|
||||
|
||||
uint8_t ReadRegister(uint16_t addr);
|
||||
|
|
119
Core/GbPpu.cpp
119
Core/GbPpu.cpp
|
@ -21,7 +21,7 @@ void GbPpu::Init(Console* console, Gameboy* gameboy, GbMemoryManager* memoryMana
|
|||
_oam = oam;
|
||||
|
||||
_state = {};
|
||||
_state.Mode = PpuMode::OamEvaluation;
|
||||
_state.Mode = PpuMode::HBlank;
|
||||
_lastFrameTime = 0;
|
||||
|
||||
_outputBuffers[0] = new uint16_t[256 * 240];
|
||||
|
@ -92,13 +92,20 @@ void GbPpu::Exec()
|
|||
void GbPpu::ExecCycle()
|
||||
{
|
||||
_state.Cycle++;
|
||||
|
||||
PpuMode mode = _state.Mode;
|
||||
bool lyCoincidenceFlag = _state.LyCoincidenceFlag;
|
||||
_state.LyCoincidenceFlag = (_state.LyCompare == _state.Scanline);
|
||||
|
||||
if(_state.Cycle == 456) {
|
||||
_state.Cycle = 0;
|
||||
|
||||
_state.Scanline++;
|
||||
_spriteCount = 0;
|
||||
|
||||
if(_state.Scanline == 144) {
|
||||
if(_state.Scanline < 144) {
|
||||
ChangeMode(PpuMode::OamEvaluation);
|
||||
} else if(_state.Scanline == 144) {
|
||||
ChangeMode(PpuMode::VBlank);
|
||||
_memoryManager->RequestIrq(GbIrqSource::VerticalBlank);
|
||||
|
||||
|
@ -113,23 +120,17 @@ void GbPpu::ExecCycle()
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(_state.Scanline < 144) {
|
||||
ChangeMode(PpuMode::OamEvaluation);
|
||||
}
|
||||
|
||||
if(_state.LyCompare == _state.Scanline && (_state.Status & GbPpuStatusFlags::CoincidenceIrq)) {
|
||||
_memoryManager->RequestIrq(GbIrqSource::LcdStat);
|
||||
}
|
||||
if(mode != _state.Mode || _state.LyCoincidenceFlag != lyCoincidenceFlag) {
|
||||
UpdateStatIrq();
|
||||
}
|
||||
|
||||
_console->ProcessPpuCycle(_state.Scanline, _state.Cycle);
|
||||
|
||||
//TODO: Dot-based renderer, currently draws at the end of the scanline
|
||||
if(_state.Scanline < 144) {
|
||||
if(_state.Cycle < 80) {
|
||||
RunSpriteEvaluation();
|
||||
} else if(_state.Mode == PpuMode::Drawing) {
|
||||
if(_state.Mode == PpuMode::Drawing) {
|
||||
bool fetchWindow = _state.WindowEnabled && _shiftedPixels >= _state.WindowX - 7 && _state.Scanline >= _state.WindowY;
|
||||
if(_fetchWindow != fetchWindow) {
|
||||
//Switched between window & background, reset fetcher & pixel FIFO
|
||||
|
@ -167,7 +168,8 @@ void GbPpu::ExecCycle()
|
|||
}
|
||||
rgbColor = palette[entry.Color];
|
||||
}
|
||||
_currentBuffer[outOffset] = rgbColor;
|
||||
|
||||
_currentBuffer[outOffset] = rgbColor;
|
||||
_fifoPosition = (_fifoPosition + 1) & 0x0F;
|
||||
|
||||
if(_console->IsDebugging()) {
|
||||
|
@ -183,6 +185,8 @@ void GbPpu::ExecCycle()
|
|||
if(_drawnPixels >= 160) {
|
||||
ChangeMode(PpuMode::HBlank);
|
||||
}
|
||||
} else if(_state.Cycle < 80) {
|
||||
RunSpriteEvaluation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,22 +206,26 @@ void GbPpu::RunSpriteEvaluation()
|
|||
|
||||
if(_state.Cycle == 79) {
|
||||
ChangeMode(PpuMode::Drawing);
|
||||
|
||||
//Reset fetcher & pixel FIFO
|
||||
_fetcherStep = 0;
|
||||
_fifoPosition = 0;
|
||||
_fifoSize = 0;
|
||||
_shiftedPixels = 0;
|
||||
_drawnPixels = 0;
|
||||
_fetchSprite = -1;
|
||||
_fetchWindow = false;
|
||||
_fetchColumn = _state.ScrollX / 8;
|
||||
ResetRenderer();
|
||||
}
|
||||
} else {
|
||||
//Hardware probably reads sprite Y and loads the X counter with the value on the next cycle
|
||||
}
|
||||
}
|
||||
|
||||
void GbPpu::ResetRenderer()
|
||||
{
|
||||
//Reset fetcher & pixel FIFO
|
||||
_fetcherStep = 0;
|
||||
_fifoPosition = 0;
|
||||
_fifoSize = 0;
|
||||
_shiftedPixels = 0;
|
||||
_drawnPixels = 0;
|
||||
_fetchSprite = -1;
|
||||
_fetchWindow = false;
|
||||
_fetchColumn = _state.ScrollX / 8;
|
||||
}
|
||||
|
||||
void GbPpu::ClockTileFetcher()
|
||||
{
|
||||
if(_fetchSprite < 0 && _fifoSize >= 8) {
|
||||
|
@ -240,7 +248,7 @@ void GbPpu::ClockTileFetcher()
|
|||
uint8_t sprTile = _oam[_fetchSprite + 2];
|
||||
uint8_t sprAttr = _oam[_fetchSprite + 3];
|
||||
bool vMirror = (sprAttr & 0x40) != 0;
|
||||
uint16_t tileBank = (sprAttr & 0x08) ? 0x2000 : 0x0000;
|
||||
uint16_t tileBank = _gameboy->IsCgb() ? ((sprAttr & 0x08) ? 0x2000 : 0x0000) : 0;
|
||||
|
||||
uint8_t sprOffsetY = vMirror ? (_state.LargeSprites ? 15 : 7) - (_state.Scanline - sprY) : (_state.Scanline - sprY);
|
||||
if(_state.LargeSprites) {
|
||||
|
@ -353,7 +361,6 @@ void GbPpu::PushTileToPixelFifo()
|
|||
void GbPpu::ChangeMode(PpuMode mode)
|
||||
{
|
||||
_state.Mode = mode;
|
||||
UpdateStatIrq();
|
||||
}
|
||||
|
||||
void GbPpu::UpdateStatIrq()
|
||||
|
@ -415,8 +422,9 @@ uint8_t GbPpu::Read(uint16_t addr)
|
|||
case 0xFF41:
|
||||
//FF41 - STAT - LCDC Status (R/W)
|
||||
return (
|
||||
(_state.Status & 0xF8) |
|
||||
((_state.LyCompare == _state.Scanline) ? 0x04 : 0x00) |
|
||||
0x80 |
|
||||
(_state.Status & 0x78) |
|
||||
(_state.LyCoincidenceFlag ? 0x04 : 0x00) |
|
||||
(int)_state.Mode
|
||||
);
|
||||
|
||||
|
@ -444,7 +452,7 @@ void GbPpu::Write(uint16_t addr, uint8_t value)
|
|||
_state.LcdEnabled = (value & 0x80) != 0;
|
||||
|
||||
if(!_state.LcdEnabled) {
|
||||
//Reset LCD to top of screen when it gets turned on
|
||||
//Reset LCD to top of screen when it gets turned off
|
||||
_state.Cycle = 0;
|
||||
_state.Scanline = 0;
|
||||
ChangeMode(PpuMode::HBlank);
|
||||
|
@ -454,6 +462,10 @@ void GbPpu::Write(uint16_t addr, uint8_t value)
|
|||
std::fill(_outputBuffers[0], _outputBuffers[0] + 256 * 239, 0x7FFF);
|
||||
std::fill(_outputBuffers[1], _outputBuffers[1] + 256 * 239, 0x7FFF);
|
||||
SendFrame();
|
||||
} else {
|
||||
_state.Cycle = 6;
|
||||
_state.Scanline = 0;
|
||||
ChangeMode(PpuMode::Drawing);
|
||||
}
|
||||
}
|
||||
_state.WindowTilemapSelect = (value & 0x40) != 0;
|
||||
|
@ -465,24 +477,10 @@ void GbPpu::Write(uint16_t addr, uint8_t value)
|
|||
_state.BgEnabled = (value & 0x01) != 0;
|
||||
break;
|
||||
|
||||
case 0xFF41:
|
||||
_state.Status = value & 0xF8;
|
||||
UpdateStatIrq();
|
||||
break;
|
||||
|
||||
case 0xFF41: _state.Status = value & 0xF8; break;
|
||||
case 0xFF42: _state.ScrollY = value; break;
|
||||
case 0xFF43: _state.ScrollX = value; break;
|
||||
case 0xFF45: _state.LyCompare = value; break;
|
||||
|
||||
case 0xFF46:
|
||||
//OAM DMA
|
||||
//TODO restrict CPU accesses to high ram during this
|
||||
//TODO timing
|
||||
for(int i = 0; i < 0xA0; i++) {
|
||||
_memoryManager->Write(0xFE00 | i, _memoryManager->Read((value << 8) | i, MemoryOperationType::DmaRead));
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xFF47: _state.BgPalette = value; break;
|
||||
case 0xFF48: _state.ObjPalette0 = value; break;
|
||||
case 0xFF49: _state.ObjPalette1 = value; break;
|
||||
|
@ -514,10 +512,10 @@ void GbPpu::WriteVram(uint16_t addr, uint8_t value)
|
|||
uint8_t GbPpu::ReadOam(uint8_t addr)
|
||||
{
|
||||
if(addr < 0xA0) {
|
||||
if((int)_state.Mode <= (int)PpuMode::VBlank) {
|
||||
return _oam[addr];
|
||||
} else {
|
||||
if((int)_state.Mode >= (int)PpuMode::OamEvaluation || _memoryManager->IsOamDmaRunning()) {
|
||||
return 0xFF;
|
||||
} else {
|
||||
return _oam[addr];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
@ -534,11 +532,6 @@ uint8_t GbPpu::ReadCgbRegister(uint16_t addr)
|
|||
{
|
||||
switch(addr) {
|
||||
case 0xFF4F: return _state.CgbVramBank;
|
||||
case 0xFF51: return _state.CgbDmaSource >> 8;
|
||||
case 0xFF52: return _state.CgbDmaSource & 0xFF;
|
||||
case 0xFF53: return _state.CgbDmaDest >> 8;
|
||||
case 0xFF54: return _state.CgbDmaDest & 0xFF;
|
||||
case 0xFF55: return _state.CgbDmaLength | (_state.CgbHdmaMode ? 0x80 : 0);
|
||||
case 0xFF68: return _state.CgbBgPalPosition | (_state.CgbBgPalAutoInc ? 0x80 : 0);
|
||||
case 0xFF69: return (_state.CgbBgPalettes[_state.CgbBgPalPosition >> 1] >> ((_state.CgbBgPalPosition & 0x01) ? 8 : 0) & 0xFF);
|
||||
case 0xFF6A: return _state.CgbObjPalPosition | (_state.CgbObjPalAutoInc ? 0x80 : 0);
|
||||
|
@ -552,27 +545,7 @@ void GbPpu::WriteCgbRegister(uint16_t addr, uint8_t value)
|
|||
{
|
||||
switch(addr) {
|
||||
case 0xFF4F: _state.CgbVramBank = value & 0x01; break;
|
||||
case 0xFF51: _state.CgbDmaSource = (_state.CgbDmaSource & 0xFF) | (value << 8); break;
|
||||
case 0xFF52: _state.CgbDmaSource = (_state.CgbDmaSource & 0xFF00) | value; break;
|
||||
case 0xFF53: _state.CgbDmaDest = (_state.CgbDmaDest & 0xFF) | (value << 8); break;
|
||||
case 0xFF54: _state.CgbDmaDest = (_state.CgbDmaDest & 0xFF00) | value; break;
|
||||
case 0xFF55:
|
||||
_state.CgbDmaLength = value & 0x7F;
|
||||
_state.CgbHdmaMode = (value & 0x80) != 0;
|
||||
|
||||
if(!_state.CgbHdmaMode) {
|
||||
//TODO check invalid dma sources/etc.
|
||||
//TODO timing
|
||||
for(int i = 0; i < _state.CgbDmaLength * 16; i++) {
|
||||
uint16_t dst = 0x8000 | (((_state.CgbDmaDest & 0x1FF0) + i) & 0x1FFF);
|
||||
_memoryManager->Write(dst, _memoryManager->Read((_state.CgbDmaSource & 0xFFF0) + i, MemoryOperationType::DmaRead));
|
||||
}
|
||||
_state.CgbDmaLength = 0x7F;
|
||||
} else {
|
||||
MessageManager::Log("TODO HDMA");
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 0xFF68:
|
||||
//FF68 - BCPS/BGPI - CGB Mode Only - Background Palette Index
|
||||
_state.CgbBgPalPosition = value & 0x3F;
|
||||
|
@ -624,7 +597,7 @@ void GbPpu::Serialize(Serializer& s)
|
|||
_state.ScrollX, _state.ScrollY, _state.WindowX, _state.WindowY, _state.Control, _state.LcdEnabled, _state.WindowTilemapSelect,
|
||||
_state.WindowEnabled, _state.BgTileSelect, _state.BgTilemapSelect, _state.LargeSprites, _state.SpritesEnabled, _state.BgEnabled,
|
||||
_state.Status, _state.FrameCount, _lastFrameTime,
|
||||
_state.CgbBgPalAutoInc, _state.CgbBgPalPosition, _state.CgbDmaDest, _state.CgbDmaLength, _state.CgbDmaSource, _state.CgbHdmaMode,
|
||||
_state.CgbBgPalAutoInc, _state.CgbBgPalPosition,
|
||||
_state.CgbObjPalAutoInc, _state.CgbObjPalPosition, _state.CgbVramBank
|
||||
);
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ private:
|
|||
|
||||
void ExecCycle();
|
||||
void RunSpriteEvaluation();
|
||||
void ResetRenderer();
|
||||
void ClockTileFetcher();
|
||||
void PushSpriteToPixelFifo();
|
||||
void PushTileToPixelFifo();
|
||||
|
|
|
@ -8,6 +8,11 @@ GbTimer::GbTimer(GbMemoryManager* memoryManager, GbApu* apu)
|
|||
{
|
||||
_apu = apu;
|
||||
_memoryManager = memoryManager;
|
||||
|
||||
|
||||
//Passes boot_div-dmgABCmgb
|
||||
//But that test depends on LCD power on timings, so may be wrong.
|
||||
_divider = 0x06;
|
||||
}
|
||||
|
||||
GbTimer::~GbTimer()
|
||||
|
|
|
@ -109,6 +109,7 @@ struct GbPpuState
|
|||
bool StatIrqFlag;
|
||||
|
||||
uint8_t LyCompare;
|
||||
bool LyCoincidenceFlag;
|
||||
uint8_t BgPalette;
|
||||
uint8_t ObjPalette0;
|
||||
uint8_t ObjPalette1;
|
||||
|
@ -131,11 +132,7 @@ struct GbPpuState
|
|||
uint32_t FrameCount;
|
||||
|
||||
uint8_t CgbVramBank;
|
||||
uint16_t CgbDmaSource;
|
||||
uint16_t CgbDmaDest;
|
||||
uint8_t CgbDmaLength;
|
||||
bool CgbHdmaMode;
|
||||
|
||||
|
||||
uint8_t CgbBgPalPosition;
|
||||
bool CgbBgPalAutoInc;
|
||||
uint16_t CgbBgPalettes[4 * 8];
|
||||
|
@ -145,6 +142,20 @@ struct GbPpuState
|
|||
uint16_t CgbObjPalettes[4 * 8];
|
||||
};
|
||||
|
||||
struct GbDmaControllerState
|
||||
{
|
||||
uint8_t OamDmaDest;
|
||||
uint8_t DmaStartDelay;
|
||||
uint8_t InternalDest;
|
||||
uint8_t DmaCounter;
|
||||
uint8_t DmaReadBuffer;
|
||||
|
||||
uint16_t CgbDmaSource;
|
||||
uint16_t CgbDmaDest;
|
||||
uint8_t CgbDmaLength;
|
||||
bool CgbHdmaMode;
|
||||
};
|
||||
|
||||
struct GbSquareState
|
||||
{
|
||||
uint16_t SweepPeriod;
|
||||
|
|
Loading…
Add table
Reference in a new issue