GB: Added HDMA support (GBC)
This commit is contained in:
parent
06376f8ca9
commit
fd1ff4cb3e
6 changed files with 101 additions and 35 deletions
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -201,7 +201,8 @@ struct GbDmaControllerState
|
|||
uint16_t CgbDmaSource;
|
||||
uint16_t CgbDmaDest;
|
||||
uint8_t CgbDmaLength;
|
||||
bool CgbHdmaMode;
|
||||
bool CgbHdmaDone;
|
||||
bool CgbHdmaRunning;
|
||||
};
|
||||
|
||||
struct GbTimerState
|
||||
|
|
Loading…
Add table
Reference in a new issue