From fdb57768560ddd807e4b8b4cf794ac7a00be677d Mon Sep 17 00:00:00 2001 From: Souryo Date: Thu, 19 Jun 2014 19:58:15 -0400 Subject: [PATCH] Fixed remaining color issues, added horizontal/vertical mirroring support --- Core/BaseMapper.h | 11 +++- Core/CPU.cpp | 1 - Core/Console.cpp | 25 +++++--- Core/Console.h | 4 +- Core/MemoryManager.cpp | 97 +++++++++++++++++------------ Core/MemoryManager.h | 7 ++- Core/PPU.cpp | 136 ++++++++++++++++++++++------------------- Core/PPU.h | 11 ++-- Core/ROMLoader.h | 16 +++++ NES.sln | 3 + 10 files changed, 190 insertions(+), 121 deletions(-) diff --git a/Core/BaseMapper.h b/Core/BaseMapper.h index 80b9f8a3..ac525cbc 100644 --- a/Core/BaseMapper.h +++ b/Core/BaseMapper.h @@ -22,6 +22,11 @@ class BaseMapper : public IMemoryHandler InitMapper(); } + + const NESHeader GetHeader() + { + return _header; + } }; class DefaultMapper : public BaseMapper @@ -75,16 +80,16 @@ class DefaultMapper : public BaseMapper class MapperFactory { public: - static shared_ptr InitializeFromFile(string filename) + static unique_ptr InitializeFromFile(string filename) { ROMLoader loader(filename); NESHeader header = loader.GetHeader(); uint8_t mapperID = header.GetMapperID(); - shared_ptr mapper = nullptr; + unique_ptr mapper = nullptr; switch(mapperID) { - case 0: mapper = shared_ptr(new DefaultMapper()); break; + case 0: mapper = unique_ptr(new DefaultMapper()); break; } if(!mapper) { diff --git a/Core/CPU.cpp b/Core/CPU.cpp index 5008a5ca..8c202caf 100644 --- a/Core/CPU.cpp +++ b/Core/CPU.cpp @@ -7,7 +7,6 @@ bool CPU::NMIFlag = false; CPU::CPU(MemoryManager *memoryManager) : _memoryManager(memoryManager) { - Reset(); Func opTable[] = { // 0 1 2 3 4 5 6 7 8 9 A B C D E F &CPU::BRK, &CPU::ORA_IndX, nullptr, nullptr, nullptr, &CPU::ORA_Zero, &CPU::ASL_Zero, nullptr, &CPU::PHP, &CPU::ORA_Imm, &CPU::ASL_Acc, nullptr, nullptr, &CPU::ORA_Abs, &CPU::ASL_Abs, nullptr, //0 diff --git a/Core/Console.cpp b/Core/Console.cpp index bff8766a..e1dda232 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -5,10 +5,13 @@ Console::Console(string filename) { _mapper = MapperFactory::InitializeFromFile(filename); - _memoryManager.RegisterIODevice(_mapper.get()); - _cpu.reset(new CPU(&_memoryManager)); - _ppu.reset(new PPU(&_memoryManager)); - _memoryManager.RegisterIODevice(_ppu.get()); + _memoryManager.reset(new MemoryManager(_mapper->GetHeader())); + _cpu.reset(new CPU(_memoryManager.get())); + _ppu.reset(new PPU(_memoryManager.get())); + _memoryManager->RegisterIODevice(_mapper.get()); + _memoryManager->RegisterIODevice(_ppu.get()); + + Reset(); } Console::~Console() @@ -40,7 +43,7 @@ void Console::RunTest(bool callback(Console*)) _cpu->Exec(); _ppu->Exec(); - if(timer.GetElapsedMS() > 5000) { + if(timer.GetElapsedMS() > 2000) { uint32_t frameCount = _ppu->GetFrameCount(); std::cout << ((frameCount - lastFrameCount) / (timer.GetElapsedMS() / 1000)) << std::endl; timer.Reset(); @@ -51,8 +54,14 @@ void Console::RunTest(bool callback(Console*)) void Console::RunTests() { + //(new Console("TestSuite/mario.nes"))->Run(); + + vector testROMs { - "dk", + //"Bomberman", + "IceClimber", + //"Excitebike", + //"dk", "mario", "01-basics", "02-implied", @@ -90,7 +99,7 @@ void Console::RunTests() console->RunTest([] (Console *console) { //static std::ofstream output("test.log", ios::out | ios::binary); static bool testStarted = false; - uint8_t testStatus = console->_memoryManager.Read(0x6000); + uint8_t testStatus = console->_memoryManager->Read(0x6000); State state = console->_cpu->GetState(); /*output << std::hex << std::uppercase << @@ -107,7 +116,7 @@ void Console::RunTests() } else if(testStatus == 0x80) { testStarted = true; } else if(testStatus < 0x80 && testStarted) { - char *result = console->_memoryManager.GetTestResult(); + char *result = console->_memoryManager->GetTestResult(); std::cout << result; delete[] result; testStarted = false; diff --git a/Core/Console.h b/Core/Console.h index eccafc7f..fec84535 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -9,8 +9,8 @@ class Console private: unique_ptr _cpu; unique_ptr _ppu; - shared_ptr _mapper; - MemoryManager _memoryManager; + unique_ptr _mapper; + unique_ptr _memoryManager; public: Console(string filename); diff --git a/Core/MemoryManager.cpp b/Core/MemoryManager.cpp index d6b00e28..bcbff7c6 100644 --- a/Core/MemoryManager.cpp +++ b/Core/MemoryManager.cpp @@ -1,5 +1,36 @@ #include "stdafx.h" #include "MemoryManager.h" +#include "ROMLoader.h" + +MemoryManager::MemoryManager(NESHeader header) +{ + _header = header; + _mirroringType = _header.GetMirroringType(); + + _internalRAM = new uint8_t[InternalRAMSize]; + _SRAM = new uint8_t[SRAMSize]; + _videoRAM = new uint8_t[VRAMSize]; + _expansionRAM = new uint8_t[0x2000]; + ZeroMemory(_internalRAM, InternalRAMSize); + ZeroMemory(_SRAM, SRAMSize); + ZeroMemory(_videoRAM, VRAMSize); + ZeroMemory(_expansionRAM, 0x2000); + + for(int i = 0; i <= 0xFFFF; i++) { + _ramHandlers.push_back(nullptr); + } + + for(int i = 0; i <= 0x3FFF; i++) { + _vramHandlers.push_back(nullptr); + } +} + +MemoryManager::~MemoryManager() +{ + delete[] _internalRAM; + delete[] _SRAM; + delete[] _expansionRAM; +} uint8_t MemoryManager::ReadRegister(uint16_t addr) { @@ -33,33 +64,6 @@ void MemoryManager::WriteMappedVRAM(uint16_t addr, uint8_t value) } } -MemoryManager::MemoryManager() -{ - _internalRAM = new uint8_t[InternalRAMSize]; - _SRAM = new uint8_t[SRAMSize]; - _videoRAM = new uint8_t[VRAMSize]; - _expansionRAM = new uint8_t[0x2000]; - ZeroMemory(_internalRAM, InternalRAMSize); - ZeroMemory(_SRAM, SRAMSize); - ZeroMemory(_videoRAM, VRAMSize); - ZeroMemory(_expansionRAM, 0x2000); - - for(int i = 0; i <= 0xFFFF; i++) { - _ramHandlers.push_back(nullptr); - } - - for(int i = 0; i <= 0x3FFF; i++) { - _vramHandlers.push_back(nullptr); - } -} - -MemoryManager::~MemoryManager() -{ - delete[] _internalRAM; - delete[] _SRAM; - delete[] _expansionRAM; -} - void MemoryManager::RegisterIODevice(IMemoryHandler *handler) { vector> addresses = handler->GetRAMAddresses(); @@ -124,6 +128,8 @@ uint8_t MemoryManager::ReadVRAM(uint16_t addr) if(addr == 0x3F10 || addr == 0x3F14 || addr == 0x3F18 || addr == 0x3F1C) { addr &= ~0x0010; } + } else if(addr >= 0x3000) { + addr -= 0x1000; } return _videoRAM[addr & 0x3FFF]; } @@ -140,21 +146,34 @@ void MemoryManager::WriteVRAM(uint16_t addr, uint8_t value) if(addr == 0x3F10 || addr == 0x3F14 || addr == 0x3F18 || addr == 0x3F1C) { addr &= ~0x0010; } - //std::cout << "palette:" << std::hex << (short)addr << " = " << (short)value << std::endl; - } - if(addr == 0x2000) { - //std::cout << "test" << std::endl; + } else if(addr >= 0x3000) { + addr -= 0x1000; } + _videoRAM[addr] = value; - if(addr >= 0x2000 && addr < 0x2400) { - _videoRAM[addr + 0x800] = value; - } else if(addr >= 0x2400 && addr < 0x2800) { - _videoRAM[addr + 0x800] = value; - } else if(addr >= 0x2800 && addr < 0x2C00) { - _videoRAM[addr - 0x800] = value; - } else if(addr >= 0x2C00 && addr < 0x3000) { - _videoRAM[addr - 0x800] = value; + if(_mirroringType == MirroringType::Vertical) { + if(addr >= 0x2000 && addr < 0x2400) { + _videoRAM[addr + 0x800] = value; + } else if(addr >= 0x2400 && addr < 0x2800) { + _videoRAM[addr + 0x800] = value; + } else if(addr >= 0x2800 && addr < 0x2C00) { + _videoRAM[addr - 0x800] = value; + } else if(addr >= 0x2C00 && addr < 0x3000) { + _videoRAM[addr - 0x800] = value; + } + } else if(_mirroringType == MirroringType::Horizontal) { + if(addr >= 0x2000 && addr < 0x2400) { + _videoRAM[addr + 0x400] = value; + } else if(addr >= 0x2400 && addr < 0x2800) { + _videoRAM[addr - 0x400] = value; + } else if(addr >= 0x2800 && addr < 0x2C00) { + _videoRAM[addr + 0x400] = value; + } else if(addr >= 0x2C00 && addr < 0x3000) { + _videoRAM[addr - 0x400] = value; + } + } else { + throw exception("Not implemented yet"); } } } \ No newline at end of file diff --git a/Core/MemoryManager.h b/Core/MemoryManager.h index b02acb16..913c06c7 100644 --- a/Core/MemoryManager.h +++ b/Core/MemoryManager.h @@ -2,6 +2,7 @@ #include "stdafx.h" #include "IMemoryHandler.h" +#include "ROMLoader.h" class MemoryManager { @@ -10,6 +11,9 @@ class MemoryManager const int SRAMSize = 0x2000; const int VRAMSize = 0x4000; + NESHeader _header; + MirroringType _mirroringType; + uint8_t *_internalRAM; uint8_t *_expansionRAM; uint8_t *_SRAM; @@ -25,7 +29,7 @@ class MemoryManager void WriteMappedVRAM(uint16_t addr, uint8_t value); public: - MemoryManager(); + MemoryManager(NESHeader header); ~MemoryManager(); void RegisterIODevice(IMemoryHandler *handler); @@ -37,7 +41,6 @@ class MemoryManager uint8_t ReadVRAM(uint16_t addr); void WriteVRAM(uint16_t addr, uint8_t value); - char* GetTestResult() { char *buffer = new char[0x2000]; diff --git a/Core/PPU.cpp b/Core/PPU.cpp index 876fc961..7cb0b73e 100644 --- a/Core/PPU.cpp +++ b/Core/PPU.cpp @@ -195,7 +195,7 @@ void PPU::IncHorizontalScrolling() } //Take from http://wiki.nesdev.com/w/index.php/The_skinny_on_NES_scrolling#Tile_and_attribute_fetching -uint16_t PPU::GetTileAddr() +uint16_t PPU::GetNameTableAddr() { return 0x2000 | (_state.VideoRamAddr & 0x0FFF); } @@ -206,6 +206,69 @@ uint16_t PPU::GetAttributeAddr() return 0x23C0 | (_state.VideoRamAddr & 0x0C00) | ((_state.VideoRamAddr >> 4) & 0x38) | ((_state.VideoRamAddr >> 2) & 0x07); } +void PPU::LoadTileInfo() +{ + _previousTile = _currentTile; + _currentTile = _nextTile; + + uint16_t tileIndex = _memoryManager->ReadVRAM(GetNameTableAddr()); + uint16_t tileAddr = (tileIndex << 4) | (_state.VideoRamAddr >> 12) | _flags.BackgroundPatternAddr; + + uint16_t shift = ((_state.VideoRamAddr >> 4) & 0x04) | (_state.VideoRamAddr & 0x02); + _nextTile.PaletteOffset = ((_memoryManager->ReadVRAM(GetAttributeAddr()) >> shift) & 0x03) << 2; + _nextTile.LowByte = _memoryManager->ReadVRAM(tileAddr); + _nextTile.HighByte = _memoryManager->ReadVRAM(tileAddr + 8); +} + +void PPU::LoadNextTile() +{ + _state.LowBitShift |= _nextTile.LowByte; + _state.HighBitShift |= _nextTile.HighByte; +} + +void PPU::InitializeShiftRegisters() +{ + _state.LowBitShift = (_currentTile.LowByte << 8) | _nextTile.LowByte; + _state.HighBitShift = (_currentTile.HighByte << 8) | _nextTile.HighByte; +} + +void PPU::ShiftTileRegisters() +{ + _state.LowBitShift <<= 1; + _state.HighBitShift <<= 1; +} + +void PPU::DrawPixel() +{ + uint32_t offset = _state.XScroll; + uint32_t pixelColor = (((_state.LowBitShift << offset) & 0x8000) >> 15) | (((_state.HighBitShift << offset) & 0x8000) >> 14); + + // If we're grabbing the pixel from the high part of the shift register, use the buffered palette, not the current one + uint8_t palette = GetBGPaletteEntry(offset < 8 ? _previousTile.PaletteOffset : _currentTile.PaletteOffset, pixelColor); + + /* + if(p->palettebuffer[fbRow].value != 0) { + // Pixel is already rendered and priority + // 1 means show behind background + continue; + }*/ + + //p->palettebuffer[fbRow].color = PPU_PALETTE_RGB[palette % 64]; + uint32_t bufferPosition = _scanline * 256 + (_cycle - 1); + ((uint32_t*)_outputBuffer)[bufferPosition] = 0xFF000000 | PPU_PALETTE_RGB[palette % 64]; + //p->palettebuffer[fbRow].value = pixel; + //p->palettebuffer[fbRow].pindex = -1; +} + +uint8_t PPU::GetBGPaletteEntry(uint8_t paletteOffset, uint8_t pixel) +{ + if(pixel == 0) { + return _memoryManager->ReadVRAM(0x3F00); + } else { + return _memoryManager->ReadVRAM(0x3F00 + paletteOffset + pixel); + } +} + void PPU::UpdateScrolling() { //For pre-render scanline & all visible scanlines @@ -240,71 +303,29 @@ void PPU::ProcessPrerenderScanline() _scanline = 0; } else if(_cycle == 321 || _cycle == 329) { LoadTileInfo(); - LoadShiftRegisters(); + if(_cycle == 329) { + InitializeShiftRegisters(); + } } } -void PPU::LoadTileInfo() -{ - _currentTile = _nextTile; - - uint16_t tileIndex = _memoryManager->ReadVRAM(GetTileAddr()); - uint16_t tileAddr = (tileIndex << 4) | (_state.VideoRamAddr >> 12) | _flags.BackgroundPatternAddr; - - uint16_t addrMask = _state.VideoRamAddr & 0x3FF; - uint16_t shift = ((addrMask >> 4) & 0x04) | (addrMask & 0x02); - _nextTile.Attributes = ((_memoryManager->ReadVRAM(GetAttributeAddr()) >> shift) & 0x03) << 2; - _nextTile.LowByte = _memoryManager->ReadVRAM(tileAddr); - _nextTile.HighByte = _memoryManager->ReadVRAM(tileAddr + 8); -} - -void PPU::LoadShiftRegisters() -{ - _state.LowBitShift = (_state.LowBitShift << 8) | _nextTile.LowByte; - _state.HighBitShift = (_state.HighBitShift << 8) | _nextTile.HighByte; -} - -void PPU::DrawPixel() -{ - uint32_t tileXPixel = (_cycle - 1) % 8; - uint32_t offset = (15 - tileXPixel - _state.XScroll); - - uint8_t pixelColor = ((_state.LowBitShift >> offset) & 0x01) | (((_state.HighBitShift >> offset) & 0x01) << 1); - - // If we're grabbing the pixel from the high part of the shift register, use the buffered palette, not the current one - uint32_t palette = 0; - if(offset < 8) { - palette = GetBGPaletteEntry(_nextTile.Attributes, pixelColor); - } else { - palette = GetBGPaletteEntry(_currentTile.Attributes, pixelColor); - } - /* - if(p->palettebuffer[fbRow].value != 0) { - // Pixel is already rendered and priority - // 1 means show behind background - continue; - }*/ - - //p->palettebuffer[fbRow].color = PPU_PALETTE_RGB[palette % 64]; - uint32_t bufferPosition = _scanline * 256 + (_cycle - 1); - uint32_t paletteColor = PPU_PALETTE_RGB[palette % 64]; - ((uint32_t*)_outputBuffer)[bufferPosition] = paletteColor | (0xFF << 24); - //p->palettebuffer[fbRow].value = pixel; - //p->palettebuffer[fbRow].pindex = -1; -} - void PPU::ProcessVisibleScanline() { if((_cycle - 1) % 8 == 0 && _cycle <= 250) { - LoadShiftRegisters(); + if(_cycle > 1) { + LoadNextTile(); + } LoadTileInfo(); } else if(_cycle == 321 || _cycle == 329) { - LoadShiftRegisters(); LoadTileInfo(); + if(_cycle == 329) { + InitializeShiftRegisters(); + } } if(_cycle > 0 && _cycle <= 256) { DrawPixel(); + ShiftTileRegisters(); } if(IsRenderingEnabled()) { @@ -325,15 +346,6 @@ void PPU::ProcessVisibleScanline() } } -uint8_t PPU::GetBGPaletteEntry(uint8_t a, uint16_t pix) -{ - if(pix == 0) { - return _memoryManager->ReadVRAM(0x3F00); - } else { - return _memoryManager->ReadVRAM(0x3F00 + a + pix); - } -} - void PPU::BeginVBlank() { if(_cycle == 1) { diff --git a/Core/PPU.h b/Core/PPU.h index da8693f3..ba7329be 100644 --- a/Core/PPU.h +++ b/Core/PPU.h @@ -61,7 +61,7 @@ struct TileInfo { uint8_t LowByte; uint8_t HighByte; - uint8_t Attributes; + uint8_t PaletteOffset; }; class PPU : public IMemoryHandler @@ -87,6 +87,7 @@ class PPU : public IMemoryHandler TileInfo _currentTile; TileInfo _nextTile; + TileInfo _previousTile; void UpdateStatusFlag(); @@ -97,7 +98,7 @@ class PPU : public IMemoryHandler void IncVerticalScrolling(); void IncHorizontalScrolling(); - uint16_t GetTileAddr(); + uint16_t GetNameTableAddr(); uint16_t GetAttributeAddr(); void UpdateScrolling(); @@ -107,10 +108,12 @@ class PPU : public IMemoryHandler void BeginVBlank(); void EndVBlank(); - uint8_t GetBGPaletteEntry(uint8_t a, uint16_t pix); + uint8_t GetBGPaletteEntry(uint8_t paletteOffset, uint8_t pixel); void LoadTileInfo(); - void LoadShiftRegisters(); + void ShiftTileRegisters(); + void InitializeShiftRegisters(); + void LoadNextTile(); void DrawPixel(); void CopyFrame(); diff --git a/Core/ROMLoader.h b/Core/ROMLoader.h index 641e9893..f7012d04 100644 --- a/Core/ROMLoader.h +++ b/Core/ROMLoader.h @@ -2,6 +2,13 @@ #include "stdafx.h" +enum class MirroringType +{ + Horizontal, + Vertical, + FourScreens, +}; + struct NESHeader { char NES[4]; @@ -17,6 +24,15 @@ struct NESHeader { return (Flags2 & 0xF0) | (Flags1 >> 4); } + + MirroringType GetMirroringType() + { + if(Flags1 & 0x08) { + return MirroringType::FourScreens; + } else { + return Flags1 & 0x01 ? MirroringType::Vertical : MirroringType::Horizontal; + } + } }; typedef vector MemoryBank; diff --git a/NES.sln b/NES.sln index 0140f01f..68da741c 100644 --- a/NES.sln +++ b/NES.sln @@ -28,4 +28,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal