Fixed remaining color issues, added horizontal/vertical mirroring support

This commit is contained in:
Souryo 2014-06-19 19:58:15 -04:00
parent 8b5618168b
commit fdb5776856
10 changed files with 190 additions and 121 deletions

View file

@ -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<BaseMapper> InitializeFromFile(string filename)
static unique_ptr<BaseMapper> InitializeFromFile(string filename)
{
ROMLoader loader(filename);
NESHeader header = loader.GetHeader();
uint8_t mapperID = header.GetMapperID();
shared_ptr<BaseMapper> mapper = nullptr;
unique_ptr<BaseMapper> mapper = nullptr;
switch(mapperID) {
case 0: mapper = shared_ptr<BaseMapper>(new DefaultMapper()); break;
case 0: mapper = unique_ptr<BaseMapper>(new DefaultMapper()); break;
}
if(!mapper) {

View file

@ -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

View file

@ -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<string> 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;

View file

@ -9,8 +9,8 @@ class Console
private:
unique_ptr<CPU> _cpu;
unique_ptr<PPU> _ppu;
shared_ptr<BaseMapper> _mapper;
MemoryManager _memoryManager;
unique_ptr<BaseMapper> _mapper;
unique_ptr<MemoryManager> _memoryManager;
public:
Console(string filename);

View file

@ -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<std::array<uint16_t, 2>> 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");
}
}
}

View file

@ -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];

View file

@ -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) {

View file

@ -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();

View file

@ -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<uint8_t> MemoryBank;

View file

@ -28,4 +28,7 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal