PPU working (lots of glitches, but draws correctly)

This commit is contained in:
Souryo 2014-06-18 22:54:23 -04:00
parent 4341e47e17
commit 0262e8c53c
10 changed files with 326 additions and 52 deletions

View file

@ -70,7 +70,8 @@ void CPU::Exec()
(this->*_opTable[opCode])();
CPU::CycleCount += this->_cycles[opCode];
} else {
//throw std::exception("Invalid opcode");
//std::cout << "Invalid opcode: " << std::hex << (short)opCode;
//throw exception("Invalid opcode");
}
} else {
NMI();

View file

@ -616,7 +616,7 @@ private:
}
void NMI() {
Push((uint16_t)(PC() + 1));
Push((uint16_t)(PC()));
Push((uint8_t)PS());
SetFlags(PSFlags::Interrupt);
SetPC(MemoryReadWord(0xFFFA));

View file

@ -30,6 +30,8 @@ void Console::Run()
void Console::RunTest(bool callback(Console*))
{
Timer timer;
uint32_t lastFrameCount = 0;
while(true) {
if(callback(this)) {
break;
@ -37,28 +39,36 @@ void Console::RunTest(bool callback(Console*))
_cpu->Exec();
_ppu->Exec();
if(timer.GetElapsedMS() > 5000) {
uint32_t frameCount = _ppu->GetFrameCount();
std::cout << ((frameCount - lastFrameCount) / (timer.GetElapsedMS() / 1000)) << std::endl;
timer.Reset();
lastFrameCount = frameCount;
}
}
}
void Console::RunTests()
{
vector<string> testROMs {
//"mario"
"01-basics",
vector<string> testROMs {
"dk",
"mario",
"01-basics",
"02-implied",
"03-immediate",
"04-zero_page",
"05-zp_xy",
"04-zero_page",
"05-zp_xy",
"06-absolute",
"07-abs_xy",
"08-ind_x",
"09-ind_y",
"07-abs_xy",
"08-ind_x",
"09-ind_y",
"10-branches",
"11-stack",
"12-jmp_jsr",
"13-rts",
"11-stack",
"12-jmp_jsr",
"13-rts",
"14-rti",
"15-brk",
"15-brk",
"16-special"
};
@ -78,7 +88,7 @@ void Console::RunTests()
});
} else {
console->RunTest([] (Console *console) {
static std::ofstream output("test.log", ios::out | ios::binary);
//static std::ofstream output("test.log", ios::out | ios::binary);
static bool testStarted = false;
uint8_t testStatus = console->_memoryManager.Read(0x6000);

View file

@ -9,6 +9,6 @@ public:
virtual vector<std::array<uint16_t, 2>> GetVRAMAddresses() { return{}; }
virtual uint8_t ReadRAM(uint16_t addr) = 0;
virtual void WriteRAM(uint16_t addr, uint8_t value) = 0;
virtual uint8_t ReadVRAM(uint16_t addr) { return 0; }
virtual void WriteVRAM(uint16_t addr, uint8_t value) { }
virtual uint8_t ReadVRAM(uint16_t addr) { throw exception("Operation not implemented"); }
virtual void WriteVRAM(uint16_t addr, uint8_t value) { throw exception("Operation not implemented"); }
};

View file

@ -119,15 +119,36 @@ uint8_t MemoryManager::ReadVRAM(uint16_t addr)
if(addr <= 0x1FFF) {
return ReadMappedVRAM(addr & 0x1FFF);
} else {
if(addr >= 0x3F00) {
addr &= 0x3F1F;
}
return _videoRAM[addr & 0x3FFF];
}
}
void MemoryManager::WriteVRAM(uint16_t addr, uint8_t value)
{
addr = addr & 0x3FFF;
if(addr <= 0x1FFF) {
WriteMappedVRAM(addr, value);
} else {
_videoRAM[addr & 0x3FFF] = value;
if(addr >= 0x3F00) {
addr &= 0x3F1F;
//std::cout << "palette:" << std::hex << (short)addr << " = " << (short)value << std::endl;
}
if(addr == 0x2000) {
//std::cout << "test" << std::endl;
}
_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;
}
}
}

View file

@ -2,6 +2,25 @@
#include "PPU.h"
#include "CPU.h"
uint32_t PPU_PALETTE_RGB[] = {
0x666666, 0x002A88, 0x1412A7, 0x3B00A4, 0x5C007E,
0x6E0040, 0x6C0600, 0x561D00, 0x333500, 0x0B4800,
0x005200, 0x004F08, 0x00404D, 0x000000, 0x000000,
0x000000, 0xADADAD, 0x155FD9, 0x4240FF, 0x7527FE,
0xA01ACC, 0xB71E7B, 0xB53120, 0x994E00, 0x6B6D00,
0x388700, 0x0C9300, 0x008F32, 0x007C8D, 0x000000,
0x000000, 0x000000, 0xFFFEFF, 0x64B0FF, 0x9290FF,
0xC676FF, 0xF36AFF, 0xFE6ECC, 0xFE8170, 0xEA9E22,
0xBCBE00, 0x88D800, 0x5CE430, 0x45E082, 0x48CDDE,
0x4F4F4F, 0x000000, 0x000000, 0xFFFEFF, 0xC0DFFF,
0xD3D2FF, 0xE8C8FF, 0xFBC2FF, 0xFEC4EA, 0xFECCC5,
0xF7D8A5, 0xE4E594, 0xCFEF96, 0xBDF4AB, 0xB3F3CC,
0xB5EBF2, 0xB8B8B8, 0x000000, 0x000000,
};
uint8_t *PPU::FrameBuffer = new uint8_t[256*240*4];
atomic<int> PPU::WaitCounter = 0;
PPU::PPU(MemoryManager *memoryManager)
{
_memoryManager = memoryManager;
@ -54,6 +73,7 @@ uint8_t PPU::ReadRAM(uint16_t addr)
void PPU::WriteRAM(uint16_t addr, uint8_t value)
{
static int counter = 0;
switch(GetRegisterID(addr)) {
case PPURegisters::Control:
_state.Control = value;
@ -72,23 +92,35 @@ void PPU::WriteRAM(uint16_t addr, uint8_t value)
break;
case PPURegisters::ScrollOffsets:
if(_state.WriteToggle) {
_state.TmpVideoRamAddr = (_state.TmpVideoRamAddr & ~0x73E0) | ((value & 0xF8) << 2) | ((value & 0x0F) << 12);
_state.TmpVideoRamAddr = (_state.TmpVideoRamAddr & ~0x73E0) | ((value & 0xF8) << 2) | ((value & 0x07) << 12);
} else {
_state.XScroll = value & 0x07;
_state.TmpVideoRamAddr = (_state.TmpVideoRamAddr & ~0x00F8) | (value >> 3);
_state.TmpVideoRamAddr = (_state.TmpVideoRamAddr & ~0x001F) | (value >> 3);
}
_state.WriteToggle = !_state.WriteToggle;
break;
case PPURegisters::VideoMemoryAddr:
if(counter < 50) {
std::cout << "=> " << std::hex << (short)value << std::endl;
}
if(_state.WriteToggle) {
_state.TmpVideoRamAddr |= (_state.TmpVideoRamAddr & ~0xFF00) | (value & 0x3F) << 8;
_state.VideoRamAddr = _state.TmpVideoRamAddr;
} else {
_state.TmpVideoRamAddr = (_state.TmpVideoRamAddr & ~0x00FF) | value;
_state.VideoRamAddr = _state.TmpVideoRamAddr;
if(counter < 50) {
std::cout << std::hex << _state.VideoRamAddr << std::endl;
}
counter++;
} else {
_state.TmpVideoRamAddr = (_state.TmpVideoRamAddr & ~0xFF00) | ((value & 0x3F) << 8);
}
_state.WriteToggle = !_state.WriteToggle;
break;
case PPURegisters::VideoMemoryData:
if(_state.VideoRamAddr == 0x2001 || _state.VideoRamAddr == 0x2401 || _state.VideoRamAddr == 0x2801 || _state.VideoRamAddr == 0x2C01) {
//std::cout << "test";
}
_memoryManager->WriteVRAM(_state.VideoRamAddr, value);
_state.VideoRamAddr += _flags.VerticalWrite ? 32 : 1;
break;
@ -156,6 +188,7 @@ void PPU::IncVerticalScrolling()
y += 1; // increment coarse Y
}
addr = (addr & ~0x03E0) | (y << 5); // put coarse Y back into v
//std::cout << std::endl;
}
_state.VideoRamAddr = addr;
}
@ -195,8 +228,12 @@ void PPU::UpdateScrolling()
} else if(_cycle == 257) {
//copy horizontal scrolling value from t
_state.VideoRamAddr = (_state.VideoRamAddr & ~0x041F) | (_state.TmpVideoRamAddr & 0x041F);
} else if((_cycle % 8 == 0 && _cycle < 256) || _cycle == 328 || _cycle == 336) {
} else if((_cycle % 8 == 0 && _cycle > 0 && _cycle < 256) || _cycle == 328 || _cycle == 336) {
IncHorizontalScrolling();
} else if((_cycle - 1) % 8 == 0 && _cycle < 250) {
LoadTileInfo();
} else if(_cycle == 321 || _cycle == 329) {
LoadTileInfo();
}
}
@ -215,19 +252,87 @@ void PPU::ProcessPrerenderScanline()
//copy vertical scrolling value from t
_state.VideoRamAddr = (_state.VideoRamAddr & ~0x7BF0) | (_state.TmpVideoRamAddr & 0x7BF0);
}
} else if(_cycle == 339 && _flags.BackgroundEnabled && (_frameCount % 2 == 1)) {
} else if(_cycle == 340 && _flags.BackgroundEnabled && (_frameCount % 2 == 1)) {
//Skip a cycle for odd frames, if background drawing is enabled
_cycle++;
_cycle = 0;
_scanline = 0;
} else if(_cycle == 321 || _cycle == 329) {
LoadTileInfo();
}
}
void PPU::LoadTileInfo()
{
_currentTile = _nextTile;
uint16_t tileIndex = _memoryManager->ReadVRAM(GetTileAddr());
uint16_t tileAddr = (tileIndex << 4) | (_state.VideoRamAddr >> 12) | _flags.BackgroundPatternAddr;
//std::cout << std::hex << GetAttributeAddr() << " ";
uint16_t shift = _state.VideoRamAddr&0x3FF;
_nextTile.Attributes = ((_memoryManager->ReadVRAM(GetAttributeAddr()) >> shift) & 0x03) << 2;
_nextTile.LowByte = _memoryManager->ReadVRAM(tileAddr);
_nextTile.HighByte = _memoryManager->ReadVRAM(tileAddr + 8);
_state.LowBitShift = (_state.LowBitShift << 8) | _nextTile.LowByte;
_state.HighBitShift = (_state.HighBitShift << 8) | _nextTile.HighByte;
}
void PPU::DrawPixel()
{
uint8_t palette = 0;
uint8_t tileXPixel = _cycle % 8;
uint32_t bufferPosition = _scanline * 256 + _cycle;
uint8_t fineXScroll = _state.XScroll;
uint8_t offset = (15 - tileXPixel - fineXScroll);
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
if(pixelColor != 0 && _frameCount > 1000) {
//std::cout << std::hex << (short)pixelColor << std::endl;
}
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*)_outputBuffer)[bufferPosition] = PPU_PALETTE_RGB[palette % 64] | (0xFF << 24);
//p->palettebuffer[fbRow].value = pixel;
//p->palettebuffer[fbRow].pindex = -1;
}
void PPU::ProcessVisibleScanline()
{
if(IsRenderingEnabled()) {
UpdateScrolling();
}
if(_cycle <= 254) {
DrawPixel();
}
if(_cycle == 254) {
//DrawScanline();
if(_scanline == 239) {
CopyFrame();
//std::cout << std::endl << std::endl << std::endl;
}
if(_flags.BackgroundEnabled) {
//Ppu_renderTileRow(p);
}
@ -235,13 +340,120 @@ void PPU::ProcessVisibleScanline()
if(_flags.SpritesEnabled) {
//Ppu_evaluateScanlineSprites(p, p->scanline);
}
} else if(_cycle == 256) {
if(_flags.BackgroundEnabled) {
//Ppu_updateEndScanlineRegisters(p);
}
}
}
void PPU::DrawScanline()
{
// Generates each tile, one scanline at a time and applies the palette
// Move first tile into shift registers
//PpuTileAttributes tileAttrs;
//FetchTileAttributes(&tileAttrs);
/* _state.LowBitShift = tileAttrs.low;
_state.HighBitShift = tileAttrs.high;
uint8_t attr = tileAttrs.attr;
FetchTileAttributes(&tileAttrs);
// Get second tile, move the pixels into the right side of
// shift registers
_state.LowBitShift = (_state.LowBitShift << 8) | tileAttrs.low;
_state.HighBitShift = (_state.HighBitShift << 8) | tileAttrs.high;
// Current tile to render is attrBuf
uint8_t attrBuf = tileAttrs.attr;
for(int x = 0; x < 32; x++) {
int palette = 0;
for(unsigned int b = 0; b < 8; b++) {
int intB = b;
int fbRow = _scanline * 256 + ((x * 8) + intB);
unsigned int uintFineX = _state.XScroll;
uint16_t pixel = (_state.LowBitShift >> (15 - b - uintFineX)) & 0x01;
pixel += ((_state.HighBitShift >> (15 - b - uintFineX) & 0x01) << 1);
// If we're grabbing the pixel from the high
// part of the shift register, use the buffered
// palette, not the current one
if((15 - b - uintFineX) < 8) {
palette = GetBGPaletteEntry(attrBuf, pixel);
} else {
palette = GetBGPaletteEntry(attr, pixel);
}
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];
_outputBuffer[fbRow] = PPU_PALETTE_RGB[palette % 64];
//p->palettebuffer[fbRow].value = pixel;
//p->palettebuffer[fbRow].pindex = -1;
}
// xcoord = p->registers.vramAddress & 0x1F
attr = attrBuf;
// Shift the first tile out, bring the new tile in
FetchTileAttributes(&tileAttrs);
_state.LowBitShift = (_state.LowBitShift << 8) | tileAttrs.low;
_state.HighBitShift = (_state.HighBitShift << 8) | tileAttrs.high;
attrBuf = tileAttrs.attr;
}*/
}
uint8_t PPU::GetBGPaletteEntry(uint8_t a, uint16_t pix)
{
uint16_t baseAddr = 0x3F00;
if(pix == 0x0) {
return _memoryManager->ReadVRAM(baseAddr);
}
switch(a) {
case 0x0:
return _memoryManager->ReadVRAM(baseAddr + pix);
case 0x4:
return _memoryManager->ReadVRAM(baseAddr + 0x04 + pix);
case 0x8:
return _memoryManager->ReadVRAM(baseAddr + 0x08 + pix);
case 0xC:
return _memoryManager->ReadVRAM(baseAddr + 0x0C + pix);
}
return 0;
}
void PPU::CopyFrame()
{
int counter = PPU::WaitCounter.fetch_add(1);
if(counter != 0) {
//We weren't the first thread to increment the value, wait until other locks are released
while(PPU::WaitCounter > 1) {}
}
memcpy(PPU::FrameBuffer, _outputBuffer, 256 * 240 * 4);
PPU::WaitCounter--;
}
uint8_t* PPU::GetFrame()
{
uint8_t *copyBuffer = new uint8_t[256 * 240 * 4];
int counter = PPU::WaitCounter.fetch_add(1);
if(counter != 0) {
//We weren't the first thread to increment the value, wait until other locks are released
while(PPU::WaitCounter > 1) {}
}
memcpy(copyBuffer, PPU::FrameBuffer, 256 * 240 * 4);
PPU::WaitCounter--;
return copyBuffer;
}
void PPU::BeginVBlank()
{
if(_cycle == 1) {
@ -262,12 +474,12 @@ void PPU::EndVBlank()
{
if(_cycle == 340) {
_frameCount++;
//std::cout << _frameCount << std::endl;
}
}
void PPU::Exec()
{
bool renderingEnabled = IsRenderingEnabled();
uint64_t equivalentCycleCount = CPU::GetCycleCount() * 3;
while(_cycleCount < equivalentCycleCount) {
if(_scanline == -1) {
@ -286,7 +498,6 @@ void PPU::Exec()
if(_scanline == 261) {
_scanline = -1;
_frameCount++;
}
} else {
_cycle++;

View file

@ -51,11 +51,24 @@ struct PPUState
uint8_t XScroll;
uint16_t TmpVideoRamAddr;
bool WriteToggle;
uint16_t HighBitShift;
uint16_t LowBitShift;
};
struct TileInfo
{
uint8_t LowByte;
uint8_t HighByte;
uint8_t Attributes;
};
class PPU : public IMemoryHandler
{
private:
static uint8_t *FrameBuffer;
static atomic<int> WaitCounter;
MemoryManager *_memoryManager;
PPUState _state;
@ -73,6 +86,9 @@ class PPU : public IMemoryHandler
PPUControlFlags _flags;
PPUStatusFlags _statusFlags;
TileInfo _currentTile;
TileInfo _nextTile;
void UpdateStatusFlag();
void UpdateFlags();
@ -92,6 +108,14 @@ class PPU : public IMemoryHandler
void BeginVBlank();
void EndVBlank();
uint8_t GetBGPaletteEntry(uint8_t a, uint16_t pix);
void DrawScanline();
void LoadTileInfo();
void DrawPixel();
void CopyFrame();
PPURegisters GetRegisterID(uint16_t addr)
{
return (PPURegisters)(addr & 0x07);
@ -110,4 +134,11 @@ class PPU : public IMemoryHandler
void WriteRAM(uint16_t addr, uint8_t value);
void Exec();
};
static uint8_t* GetFrame();
uint32_t GetFrameCount()
{
return _frameCount;
}
};

View file

@ -23,6 +23,8 @@
#include <array>
#include <sstream>
#include <atomic>
#include <windows.h>
using std::vector;
@ -31,5 +33,5 @@ using std::unique_ptr;
using std::ios;
using std::ifstream;
using std::string;
// TODO: reference additional headers your program requires here
using std::exception;
using std::atomic;

View file

@ -71,16 +71,16 @@ namespace NES
TranslateMessage(&msg);
DispatchMessage(&msg);
} else {
/*_renderer.Render();
_renderer.Render();
frameCount++;
if(frameCount == 500) {
double fps = (double)frameCount / (timer.GetElapsedMS() / 1000);
OutputDebugString((std::to_wstring((int)fps) + L"\n").c_str());
//std::cout << "FPS: " << fps << std::endl;
timer.Reset();
frameCount = 0;
}*/
}
}
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(50));
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(1));
}
return (int)msg.wParam;
@ -109,7 +109,7 @@ namespace NES
return E_FAIL;
// Create window
RECT rc = { 0, 0, 320, 240 };
RECT rc = { 0, 0, 256, 240 };
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
_hWnd = CreateWindow(L"NESEmu", L"NESEmu",
WS_OVERLAPPEDWINDOW,

View file

@ -1,6 +1,7 @@
#include "stdafx.h"
#include "Renderer.h"
#include "DirectXTK\SpriteBatch.h"
#include "..\Core\PPU.h"
namespace NES
{
@ -117,7 +118,7 @@ namespace NES
_pd3dDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, 16, &fred);
uint16_t screenwidth = 320;
uint16_t screenwidth = 256;
uint16_t screenheight = 240;
D3D11_TEXTURE2D_DESC desc;
@ -141,7 +142,7 @@ namespace NES
renderTargetViewDescription.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; // MS;
_videoRAM = new byte[screenwidth*screenheight * 4];
memset(_videoRAM, 0xFF, screenwidth*screenheight);
memset(_videoRAM, 0xFF, screenwidth*screenheight*4);
D3D11_SUBRESOURCE_DATA tbsd;
tbsd.pSysMem = (void *)_videoRAM;
@ -186,23 +187,20 @@ namespace NES
// Clear the back buffer
//_pImmediateContext->ClearRenderTargetView(_pRenderTargetView, Colors::MidnightBlue);
UINT screenwidth = 320, screenheight = 240;
if(rand() % 15 == 0) {
for(int i = 0; i < screenwidth*screenheight * 4; i++) {
_videoRAM[i] += rand() % 255;
}
}
UINT screenwidth = 256, screenheight = 240;
D3D11_MAPPED_SUBRESOURCE dd;
dd.pData = (void *)_videoRAM;
dd.RowPitch = screenwidth * 4;
dd.DepthPitch = screenwidth* screenheight * 4;
uint8_t *frameData = PPU::GetFrame();
_pImmediateContext->Map(_pTexture, 0, D3D11_MAP_WRITE_DISCARD, 0, &dd);
memcpy(dd.pData, _videoRAM, screenwidth*screenheight * 4);
memcpy(dd.pData, frameData, screenwidth*screenheight * 4);
_pImmediateContext->Unmap(_pTexture, 0);
delete[] frameData;
///////////////////////////////////////////////////////////////////////////////
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
D3D11_TEXTURE2D_DESC desc;
@ -228,7 +226,7 @@ namespace NES
_sprites->Begin();
RECT x;
x.left = 0;
x.right = 320;
x.right = 256;
x.bottom = 240;
x.top = 0;
_sprites->Draw(pSRView, x);