From 0262e8c53c5fe41f4375099ca2a13f94a54e20d5 Mon Sep 17 00:00:00 2001 From: Souryo Date: Wed, 18 Jun 2014 22:54:23 -0400 Subject: [PATCH] PPU working (lots of glitches, but draws correctly) --- Core/CPU.cpp | 3 +- Core/CPU.h | 2 +- Core/Console.cpp | 36 ++++--- Core/IMemoryHandler.h | 4 +- Core/MemoryManager.cpp | 23 +++- Core/PPU.cpp | 239 ++++++++++++++++++++++++++++++++++++++--- Core/PPU.h | 33 +++++- Core/stdafx.h | 6 +- GUI/MainWindow.cpp | 10 +- GUI/Renderer.cpp | 22 ++-- 10 files changed, 326 insertions(+), 52 deletions(-) diff --git a/Core/CPU.cpp b/Core/CPU.cpp index 556e50e6..5008a5ca 100644 --- a/Core/CPU.cpp +++ b/Core/CPU.cpp @@ -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(); diff --git a/Core/CPU.h b/Core/CPU.h index c62f47f5..d5bd99a4 100644 --- a/Core/CPU.h +++ b/Core/CPU.h @@ -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)); diff --git a/Core/Console.cpp b/Core/Console.cpp index 237a74fe..bff8766a 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -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 testROMs { - //"mario" - "01-basics", + vector 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); diff --git a/Core/IMemoryHandler.h b/Core/IMemoryHandler.h index a863b95d..002efcb2 100644 --- a/Core/IMemoryHandler.h +++ b/Core/IMemoryHandler.h @@ -9,6 +9,6 @@ public: virtual vector> 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"); } }; \ No newline at end of file diff --git a/Core/MemoryManager.cpp b/Core/MemoryManager.cpp index 0d756ab5..2c4607a2 100644 --- a/Core/MemoryManager.cpp +++ b/Core/MemoryManager.cpp @@ -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; + } } } \ No newline at end of file diff --git a/Core/PPU.cpp b/Core/PPU.cpp index 72fd4d18..ebc95164 100644 --- a/Core/PPU.cpp +++ b/Core/PPU.cpp @@ -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 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++; diff --git a/Core/PPU.h b/Core/PPU.h index 566041fb..8a829850 100644 --- a/Core/PPU.h +++ b/Core/PPU.h @@ -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 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(); -}; \ No newline at end of file + + static uint8_t* GetFrame(); + + uint32_t GetFrameCount() + { + return _frameCount; + } +}; diff --git a/Core/stdafx.h b/Core/stdafx.h index bb2ce948..23795494 100644 --- a/Core/stdafx.h +++ b/Core/stdafx.h @@ -23,6 +23,8 @@ #include #include +#include + #include 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; diff --git a/GUI/MainWindow.cpp b/GUI/MainWindow.cpp index 8d3967bd..6fda955c 100644 --- a/GUI/MainWindow.cpp +++ b/GUI/MainWindow.cpp @@ -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(50)); + std::this_thread::sleep_for(std::chrono::duration(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, diff --git a/GUI/Renderer.cpp b/GUI/Renderer.cpp index 0c128c61..d8227741 100644 --- a/GUI/Renderer.cpp +++ b/GUI/Renderer.cpp @@ -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);