GB: Added HDMA support (GBC)

This commit is contained in:
Sour 2020-05-28 00:15:37 -04:00
parent 06376f8ca9
commit fd1ff4cb3e
6 changed files with 101 additions and 35 deletions

View file

@ -118,12 +118,12 @@ 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()));
_dmaController.reset(new GbDmaController(_memoryManager.get(), _ppu.get()));
_cart->Init(this, _memoryManager.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);
_ppu->Init(_console, this, _memoryManager.get(), _dmaController.get(), _videoRam, _spriteRam);
}
void Gameboy::Exec()

View file

@ -1,12 +1,12 @@
#include "stdafx.h"
#include "GbDmaController.h"
#include "MessageManager.h"
#include "CpuTypes.h"
#include "GbMemoryManager.h"
#include "GbPpu.h"
GbDmaController::GbDmaController(GbMemoryManager* memoryManager)
GbDmaController::GbDmaController(GbMemoryManager* memoryManager, GbPpu* ppu)
{
_memoryManager = memoryManager;
_ppu = ppu;
_state = {};
}
@ -52,7 +52,7 @@ uint8_t GbDmaController::ReadCgb(uint16_t addr)
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 0xFF55: return _state.CgbDmaLength | (_state.CgbHdmaDone ? 0x80 : 0);
}
return 0;
@ -65,34 +65,71 @@ void GbDmaController::WriteCgb(uint16_t addr, uint8_t value)
case 0xFF52: _state.CgbDmaSource = (_state.CgbDmaSource & 0xFF00) | (value & 0xF0); break;
case 0xFF53: _state.CgbDmaDest = (_state.CgbDmaDest & 0xFF) | (value << 8); break;
case 0xFF54: _state.CgbDmaDest = (_state.CgbDmaDest & 0xFF00) | (value & 0xF0); break;
case 0xFF55:
case 0xFF55: {
bool hdmaMode = (value & 0x80) != 0;
_state.CgbDmaLength = value & 0x7F;
_state.CgbHdmaMode = (value & 0x80) != 0;
if(!_state.CgbHdmaMode) {
//TODO check invalid dma sources/etc.
//4 cycles for setup
_memoryManager->Exec();
int count = (_state.CgbDmaLength + 1) * 16;
for(int i = 0; i < count; i++) {
uint16_t dst = 0x8000 | ((_state.CgbDmaDest + i) & 0x1FFF);
//2 or 4 cycles per byte transfered (2x more cycles in high speed mode - effective speed is the same in both modes
if(_memoryManager->IsHighSpeed() || (i & 0x01)) {
//TODO: Not perfectly accurate (in "slow" mode mode both occur on the same cycle)
_memoryManager->Exec();
}
_memoryManager->Write(dst, _memoryManager->Read(_state.CgbDmaSource + i, MemoryOperationType::DmaRead));
if(!hdmaMode) {
if(_state.CgbHdmaRunning) {
//"If HDMA5 is written during a HDMA copy, the behaviour depends on the written bit 7.
// - New bit 7 is 0: Stop copy. HDMA5 new value is ( 80h OR written_value ).
// - New bit 7 is 1: Restart copy. New size is the value of written_value bits 0-6.
//This means that HDMA can't switch to GDMA with only one write. It must be stopped first."
_state.CgbHdmaRunning = false;
_state.CgbHdmaDone = true;
} else {
do {
ProcessDmaBlock();
} while(_state.CgbDmaLength != 0x7F);
}
//Source/Dest/Length are all modified by the DMA process and keep their last value after DMA completes
_state.CgbDmaSource += count;
_state.CgbDmaDest += count;
_state.CgbDmaLength = 0x7F;
} else {
MessageManager::Log("TODO HDMA");
//"After writing a value to HDMA5 that starts the HDMA copy, the upper bit (that indicates HDMA mode when set to '1') will be cleared"
_state.CgbHdmaDone = false;
_state.CgbHdmaRunning = true;
//"If a HDMA transfer is started when the screen is off, one block is copied. "
//" When a HDMA transfer is started during HBL it will start right away."
if(!_ppu->IsLcdEnabled() || _ppu->GetMode() == PpuMode::HBlank) {
ProcessHdma();
}
}
break;
}
}
}
void GbDmaController::ProcessHdma()
{
if(_state.CgbHdmaRunning) {
ProcessDmaBlock();
}
}
void GbDmaController::ProcessDmaBlock()
{
//TODO check invalid dma sources/etc.
//4 cycles for setup
_memoryManager->Exec();
for(int i = 0; i < 16; i++) {
uint16_t dst = 0x8000 | ((_state.CgbDmaDest + i) & 0x1FFF);
//2 or 4 cycles per byte transfered (2x more cycles in high speed mode - effective speed is the same in both modes
if(_memoryManager->IsHighSpeed() || (i & 0x01)) {
//TODO: Not perfectly accurate (in "slow" mode mode both occur on the same cycle)
_memoryManager->Exec();
}
_memoryManager->Write(dst, _memoryManager->Read(_state.CgbDmaSource + i, MemoryOperationType::DmaRead));
}
//Source/Dest/Length are all modified by the DMA process and keep their last value after DMA completes
_state.CgbDmaSource += 16;
_state.CgbDmaDest += 16;
_state.CgbDmaLength = (_state.CgbDmaLength - 1) & 0x7F;
if(_state.CgbHdmaRunning && _state.CgbDmaLength == 0x7F) {
_state.CgbHdmaRunning = false;
_state.CgbHdmaDone = true;
}
}
@ -100,6 +137,6 @@ 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
_state.CgbDmaDest, _state.CgbDmaLength, _state.CgbDmaSource, _state.CgbHdmaDone, _state.CgbHdmaRunning
);
}

View file

@ -4,15 +4,19 @@
#include "../Utilities/ISerializable.h"
class GbMemoryManager;
class GbPpu;
class GbDmaController : public ISerializable
{
private:
GbDmaControllerState _state;
GbMemoryManager* _memoryManager;
GbPpu* _ppu;
void ProcessDmaBlock();
public:
GbDmaController(GbMemoryManager* memoryManager);
GbDmaController(GbMemoryManager* memoryManager, GbPpu* ppu);
void Exec();
bool IsOamDmaRunning();
@ -23,5 +27,7 @@ public:
uint8_t ReadCgb(uint16_t addr);
void WriteCgb(uint16_t addr, uint8_t value);
void ProcessHdma();
void Serialize(Serializer& s) override;
};

View file

@ -7,6 +7,7 @@
#include "VideoDecoder.h"
#include "RewindManager.h"
#include "GbMemoryManager.h"
#include "GbDmaController.h"
#include "NotificationManager.h"
#include "MessageManager.h"
#include "../Utilities/HexUtilities.h"
@ -15,11 +16,12 @@
constexpr uint16_t bwRgbPalette[4] = { 0x7FFF, 0x6318, 0x318C, 0x0000 };
constexpr uint16_t evtColors[6] = { 0x18C6, 0x294A, 0x108C, 0x4210, 0x3084, 0x1184 };
void GbPpu::Init(Console* console, Gameboy* gameboy, GbMemoryManager* memoryManager, uint8_t* vram, uint8_t* oam)
void GbPpu::Init(Console* console, Gameboy* gameboy, GbMemoryManager* memoryManager, GbDmaController* dmaController, uint8_t* vram, uint8_t* oam)
{
_console = console;
_gameboy = gameboy;
_memoryManager = memoryManager;
_dmaController = dmaController;
_vram = vram;
_oam = oam;
@ -151,6 +153,10 @@ void GbPpu::ExecCycle()
RunDrawCycle();
} else {
ChangeMode(PpuMode::HBlank);
if(_state.Scanline < 143) {
//"This mode will transfer one block (16 bytes) during each H-Blank. No data is transferred during VBlank (LY = 143 153"
_dmaController->ProcessHdma();
}
}
} else if(_state.Mode == PpuMode::OamEvaluation) {
RunSpriteEvaluation();
@ -477,6 +483,16 @@ uint32_t GbPpu::GetFrameCount()
return _state.FrameCount;
}
bool GbPpu::IsLcdEnabled()
{
return _state.LcdEnabled;
}
PpuMode GbPpu::GetMode()
{
return _state.Mode;
}
void GbPpu::SendFrame()
{
_console->ProcessEvent(EventType::EndFrame);
@ -555,6 +571,9 @@ 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();
//"If the HDMA started when the screen was on, when the screen is switched off it will copy one block after the switch."
_dmaController->ProcessHdma();
} else {
_state.Cycle = 4;
_state.Scanline = 0;

View file

@ -6,6 +6,7 @@
class Console;
class Gameboy;
class GbMemoryManager;
class GbDmaController;
class GbPpu : public ISerializable
{
@ -14,6 +15,7 @@ private:
Gameboy* _gameboy = nullptr;
GbPpuState _state = {};
GbMemoryManager* _memoryManager = nullptr;
GbDmaController* _dmaController = nullptr;
uint16_t* _outputBuffers[2] = {};
uint16_t* _currentBuffer = nullptr;
@ -63,15 +65,16 @@ private:
public:
virtual ~GbPpu();
void Reset(bool useBootRom);
void Init(Console* console, Gameboy* gameboy, GbMemoryManager* memoryManager, uint8_t* vram, uint8_t* oam);
void Init(Console* console, Gameboy* gameboy, GbMemoryManager* memoryManager, GbDmaController* dmaController, uint8_t* vram, uint8_t* oam);
GbPpuState GetState();
uint16_t* GetEventViewerBuffer();
uint16_t* GetPreviousEventViewerBuffer();
void GetPalette(uint16_t out[4], uint8_t palCfg);
uint32_t GetFrameCount();
bool IsLcdEnabled();
PpuMode GetMode();
void Exec();

View file

@ -201,7 +201,8 @@ struct GbDmaControllerState
uint16_t CgbDmaSource;
uint16_t CgbDmaDest;
uint8_t CgbDmaLength;
bool CgbHdmaMode;
bool CgbHdmaDone;
bool CgbHdmaRunning;
};
struct GbTimerState