diff --git a/Core/Console.cpp b/Core/Console.cpp index 6bc2d40..8f2999a 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -267,9 +267,17 @@ void Console::ProcessWorkRamRead(uint32_t addr, uint8_t value) _debugger->ProcessWorkRamRead(addr, value); } } + void Console::ProcessWorkRamWrite(uint32_t addr, uint8_t value) { if(_debugger) { _debugger->ProcessWorkRamWrite(addr, value); } +} + +void Console::ProcessPpuCycle() +{ + if(_debugger) { + _debugger->ProcessPpuCycle(); + } } \ No newline at end of file diff --git a/Core/Console.h b/Core/Console.h index 201dd56..d91aa32 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -80,4 +80,5 @@ public: void ProcessPpuWrite(uint32_t addr, uint8_t value, SnesMemoryType memoryType); void ProcessWorkRamRead(uint32_t addr, uint8_t value); void ProcessWorkRamWrite(uint32_t addr, uint8_t value); + void ProcessPpuCycle(); }; \ No newline at end of file diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 4aa9e7a..3718c68 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -61,6 +61,7 @@ + @@ -137,6 +138,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index d3edd07..6578768 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -197,6 +197,9 @@ Debugger + + Debugger + @@ -306,6 +309,7 @@ Debugger + diff --git a/Core/Debugger.cpp b/Core/Debugger.cpp index 2e3efbd..1a6f316 100644 --- a/Core/Debugger.cpp +++ b/Core/Debugger.cpp @@ -14,6 +14,7 @@ #include "CodeDataLogger.h" #include "Disassembler.h" #include "BreakpointManager.h" +#include "PpuTools.h" #include "ExpressionEvaluator.h" #include "../Utilities/HexUtilities.h" #include "../Utilities/FolderUtilities.h" @@ -31,6 +32,7 @@ Debugger::Debugger(shared_ptr console) _traceLogger.reset(new TraceLogger(this, _memoryManager)); _memoryDumper.reset(new MemoryDumper(_ppu, _memoryManager, console->GetCartridge())); _breakpointManager.reset(new BreakpointManager(this)); + _ppuTools.reset(new PpuTools(_console.get(), _ppu.get())); _cpuStepCount = 0; string cdlFile = FolderUtilities::CombinePath(FolderUtilities::GetDebuggerFolder(), FolderUtilities::GetFilename(_console->GetCartridge()->GetRomInfo().RomPath, false) + ".cdl"); @@ -84,10 +86,10 @@ void Debugger::ProcessCpuRead(uint32_t addr, uint8_t value, MemoryOperationType _cpuStepCount--; } - if(value == 0x00 || value == 0xCB) { - //Break on BRK/WAI + /*if(value == 0x00 || value == 0xDB || value == 0x42) { + //Break on BRK/STP/WDM _cpuStepCount = 0; - } + }*/ } else if(type == MemoryOperationType::ExecOperand) { if(addressInfo.Type == SnesMemoryType::PrgRom && addressInfo.Address >= 0) { _codeDataLogger->SetFlags(addressInfo.Address, CdlFlags::Code | (state.PS & (CdlFlags::IndexMode8 | CdlFlags::MemoryMode8))); @@ -142,6 +144,13 @@ void Debugger::ProcessPpuWrite(uint16_t addr, uint8_t value, SnesMemoryType memo ProcessBreakConditions(operation, addressInfo); } +void Debugger::ProcessPpuCycle() +{ + uint16_t scanline = _ppu->GetState().Scanline; + uint16_t cycle = _ppu->GetState().Cycle; + _ppuTools->UpdateViewers(scanline, cycle); +} + void Debugger::ProcessBreakConditions(MemoryOperationInfo &operation, AddressInfo &addressInfo) { if(_breakpointManager->CheckBreakpoint(operation, addressInfo)) { @@ -211,3 +220,8 @@ shared_ptr Debugger::GetBreakpointManager() { return _breakpointManager; } + +shared_ptr Debugger::GetPpuTools() +{ + return _ppuTools; +} diff --git a/Core/Debugger.h b/Core/Debugger.h index 4fd6f59..1baffd3 100644 --- a/Core/Debugger.h +++ b/Core/Debugger.h @@ -19,6 +19,7 @@ class ExpressionEvaluator; class MemoryDumper; class Disassembler; class BreakpointManager; +class PpuTools; struct DebugState; struct MemoryOperationInfo; struct AddressInfo; @@ -37,6 +38,7 @@ private: shared_ptr _codeDataLogger; shared_ptr _disassembler; shared_ptr _breakpointManager; + shared_ptr _ppuTools; unique_ptr _watchExpEval; @@ -55,6 +57,7 @@ public: void ProcessPpuRead(uint16_t addr, uint8_t value, SnesMemoryType memoryType); void ProcessPpuWrite(uint16_t addr, uint8_t value, SnesMemoryType memoryType); + void ProcessPpuCycle(); void ProcessBreakConditions(MemoryOperationInfo &operation, AddressInfo &addressInfo); @@ -70,4 +73,5 @@ public: shared_ptr GetMemoryDumper(); shared_ptr GetDisassembler(); shared_ptr GetBreakpointManager(); + shared_ptr GetPpuTools(); }; \ No newline at end of file diff --git a/Core/INotificationListener.h b/Core/INotificationListener.h index 8b986b9..8ff7717 100644 --- a/Core/INotificationListener.h +++ b/Core/INotificationListener.h @@ -16,6 +16,7 @@ enum class ConsoleNotificationType ExecuteShortcut = 10, EmulationStopped = 11, BeforeEmulationStop = 12, + ViewerRefresh = 13, }; class INotificationListener diff --git a/Core/Ppu.cpp b/Core/Ppu.cpp index 15612e1..24cede7 100644 --- a/Core/Ppu.cpp +++ b/Core/Ppu.cpp @@ -56,12 +56,17 @@ uint32_t Ppu::GetFrameCount() PpuState Ppu::GetState() { - return { - _cycle, - _scanline, - _frameCount, - _overscanMode - }; + PpuState state; + state.Cycle = _cycle; + state.Scanline = _scanline; + state.FrameCount = _frameCount; + state.OverscanMode = _overscanMode; + state.BgMode = _bgMode; + state.Layers[0] = _layerConfig[0]; + state.Layers[1] = _layerConfig[1]; + state.Layers[2] = _layerConfig[2]; + state.Layers[3] = _layerConfig[3]; + return state; } void Ppu::Exec() @@ -86,7 +91,7 @@ void Ppu::Exec() if(_regs->IsNmiEnabled()) { _console->GetCpu()->SetNmiFlag(); } - } else if(_scanline == 240 && _cycle == 0 && _frameCount & 0x01) { + } else if(_scanline == 240 && _frameCount & 0x01) { //Skip 1 tick every other frame _cycle++; } else if(_scanline == 261) { @@ -107,13 +112,14 @@ void Ppu::Exec() } } + _cycle++; + _console->ProcessPpuCycle(); + if(_regs->IsHorizontalIrqEnabled() && _cycle == _regs->GetHorizontalTimer() && (!_regs->IsVerticalIrqEnabled() || _scanline == _regs->GetVerticalTimer())) { //An IRQ will occur sometime just after the H Counter reaches the value set in $4207/$4208. _console->GetCpu()->SetIrqSource(IrqSource::Ppu); } - - _cycle++; - + if(_cycle == 278 && _scanline <= (_overscanMode ? 239 : 224)) { if(_scanline != 0) { RenderScanline(); @@ -536,7 +542,7 @@ void Ppu::RenderTilemap() uint8_t baseYOffset = (realY + config.VScroll) & 0x07; /* Tilemap offset based on the current row & tilemap size options */ - uint16_t addrVerticalScrollingOffset = config.VerticalMirroring ? ((row & 0x20) << (config.HorizontalMirroring ? 6 : 5)) : 0; + uint16_t addrVerticalScrollingOffset = config.DoubleHeight ? ((row & 0x20) << (config.DoubleWidth ? 6 : 5)) : 0; /* The start address for tiles on this row */ uint16_t baseOffset = tilemapAddr + addrVerticalScrollingOffset + ((row & 0x1F) << 5); @@ -570,7 +576,7 @@ void Ppu::RenderTilemap() baseYOffset = (realY + vScroll) & 0x07; } else { column = (realX + hScroll) >> (largeTileWidth ? 4 : 3); - addr = (baseOffset + (column & 0x1F) + (config.HorizontalMirroring ? ((column & 0x20) << 5) : 0)) << 1; + addr = (baseOffset + (column & 0x1F) + (config.DoubleWidth ? ((column & 0x20) << 5) : 0)) << 1; } //Skip pixels that were filled by previous layers (or that don't match the priority level currently being processed) @@ -625,9 +631,7 @@ void Ppu::RenderTilemap() uint16_t color = GetTilePixelColor(pixelStart, shift); if(color > 0) { - /* Ignore palette bits for 256-color layers */ uint16_t paletteColor; - if(bpp == 8 && directColorMode) { uint8_t palette = (_vram[addr + 1] >> 2) & 0x07; paletteColor = ( @@ -636,6 +640,7 @@ void Ppu::RenderTilemap() (((color & 0xC0) | ((palette & 0x04) << 3)) << 7) ); } else { + /* Ignore palette bits for 256-color layers */ uint8_t palette = bpp == 8 ? 0 : (_vram[addr + 1] >> 2) & 0x07; uint16_t paletteRamOffset = basePaletteOffset + (palette * (1 << bpp) + color) * 2; paletteColor = _cgram[paletteRamOffset] | (_cgram[paletteRamOffset + 1] << 8); @@ -698,9 +703,9 @@ void Ppu::ProcessOffsetMode(uint8_t x, uint16_t realX, uint16_t realY, uint16_t uint16_t offsetModeRow = (realY + vScroll) >> (largeTileHeight ? 4 : 3); uint16_t offsetModeColumn = (realX + hScroll) >> (largeTileWidth ? 4 : 3); - uint16_t addrVerticalScrollingOffset = config.VerticalMirroring ? ((offsetModeRow & 0x20) << (config.HorizontalMirroring ? 6 : 5)) : 0; + uint16_t addrVerticalScrollingOffset = config.DoubleHeight ? ((offsetModeRow & 0x20) << (config.DoubleWidth ? 6 : 5)) : 0; uint16_t offsetModeBaseAddress = tilemapAddr + addrVerticalScrollingOffset + ((offsetModeRow & 0x1F) << 5); - addr = (offsetModeBaseAddress + (offsetModeColumn & 0x1F) + (config.HorizontalMirroring ? ((offsetModeColumn & 0x20) << 5) : 0)) << 1; + addr = (offsetModeBaseAddress + (offsetModeColumn & 0x1F) + (config.DoubleWidth ? ((offsetModeColumn & 0x20) << 5) : 0)) << 1; } template @@ -1266,7 +1271,7 @@ void Ppu::Write(uint32_t addr, uint8_t value) case 0x2105: if(_bgMode != (value & 0x07)) { - MessageManager::DisplayMessage("Debug", "Entering mode: " + std::to_string(value & 0x07)); + MessageManager::DisplayMessage("Debug", "Entering mode: " + std::to_string(value & 0x07) + " (SL: " + std::to_string(_scanline) + ")"); } _bgMode = value & 0x07; _mode1Bg3Priority = (value & 0x08) != 0; @@ -1290,8 +1295,8 @@ void Ppu::Write(uint32_t addr, uint8_t value) case 0x2107: case 0x2108: case 0x2109: case 0x210A: //BG 1-4 Tilemap Address and Size (BG1SC, BG2SC, BG3SC, BG4SC) _layerConfig[addr - 0x2107].TilemapAddress = (value & 0xFC) << 9; - _layerConfig[addr - 0x2107].HorizontalMirroring = (value & 0x01) != 0; - _layerConfig[addr - 0x2107].VerticalMirroring = (value & 0x02) != 0; + _layerConfig[addr - 0x2107].DoubleWidth = (value & 0x01) != 0; + _layerConfig[addr - 0x2107].DoubleHeight = (value & 0x02) != 0; break; case 0x210B: case 0x210C: diff --git a/Core/PpuTools.cpp b/Core/PpuTools.cpp new file mode 100644 index 0000000..31270b9 --- /dev/null +++ b/Core/PpuTools.cpp @@ -0,0 +1,190 @@ +#include "stdafx.h" +#include "PpuTools.h" +#include "Ppu.h" +#include "DebugTypes.h" +#include "Console.h" +#include "NotificationManager.h" + +PpuTools::PpuTools(Console *console, Ppu *ppu) +{ + _console = console; + _ppu = ppu; +} + +uint16_t PpuTools::GetTilePixelColor(const uint8_t bpp, const uint16_t pixelStart, const uint8_t shift) +{ + uint8_t *vram = _ppu->GetVideoRam(); + uint16_t color; + if(bpp == 2) { + color = (((vram[pixelStart + 0] >> shift) & 0x01) << 0); + color |= (((vram[pixelStart + 1] >> shift) & 0x01) << 1); + } else if(bpp == 4) { + color = (((vram[pixelStart + 0] >> shift) & 0x01) << 0); + color |= (((vram[pixelStart + 1] >> shift) & 0x01) << 1); + color |= (((vram[pixelStart + 16] >> shift) & 0x01) << 2); + color |= (((vram[pixelStart + 17] >> shift) & 0x01) << 3); + } else if(bpp == 8) { + color = (((vram[pixelStart + 0] >> shift) & 0x01) << 0); + color |= (((vram[pixelStart + 1] >> shift) & 0x01) << 1); + color |= (((vram[pixelStart + 16] >> shift) & 0x01) << 2); + color |= (((vram[pixelStart + 17] >> shift) & 0x01) << 3); + color |= (((vram[pixelStart + 32] >> shift) & 0x01) << 4); + color |= (((vram[pixelStart + 33] >> shift) & 0x01) << 5); + color |= (((vram[pixelStart + 48] >> shift) & 0x01) << 6); + color |= (((vram[pixelStart + 49] >> shift) & 0x01) << 7); + } else { + throw std::runtime_error("unsupported bpp"); + } + return color; +} + +uint32_t PpuTools::ToArgb(uint16_t color) +{ + uint8_t b = (color >> 10) << 3; + uint8_t g = ((color >> 5) & 0x1F) << 3; + uint8_t r = (color & 0x1F) << 3; + + return 0xFF000000 | (r << 16) | (g << 8) | b; +} + +void PpuTools::BlendColors(uint8_t output[4], uint8_t input[4]) +{ + uint8_t alpha = input[3] + 1; + uint8_t invertedAlpha = 256 - input[3]; + output[0] = (uint8_t)((alpha * input[0] + invertedAlpha * output[0]) >> 8); + output[1] = (uint8_t)((alpha * input[1] + invertedAlpha * output[1]) >> 8); + output[2] = (uint8_t)((alpha * input[2] + invertedAlpha * output[2]) >> 8); + output[3] = 0xFF; +} + +void PpuTools::GetTilemap(GetTilemapOptions options, uint32_t* outBuffer) +{ + static constexpr uint8_t layerBpp[8][4] = { + { 2,2,2,2 }, { 4,4,2,0 }, { 4,4,0,0 }, { 8,4,0,0 }, { 8,2,0,0 }, { 4,2,0,0 }, { 4,0,0,0 }, { 8,0,0,0 } + }; + + PpuState state = _ppu->GetState(); + options.BgMode = state.BgMode; + + uint16_t basePaletteOffset = 0; + if(options.BgMode == 0) { + basePaletteOffset = options.Layer * 64; + } + + uint8_t *vram = _ppu->GetVideoRam(); + uint8_t *cgram = _ppu->GetCgRam(); + LayerConfig layer = state.Layers[options.Layer]; + + uint16_t bgColor = (cgram[1] << 8) | cgram[0]; + for(int i = 0; i < 512 * 512; i++) { + outBuffer[i] = ToArgb(bgColor); + } + + uint8_t bpp = layerBpp[options.BgMode][options.Layer]; + if(bpp == 0) { + return; + } + + bool largeTileWidth = layer.LargeTiles || options.BgMode == 5 || options.BgMode == 6; + bool largeTileHeight = layer.LargeTiles; + + for(int row = 0; row < (layer.DoubleHeight ? 64 : 32); row++) { + uint16_t addrVerticalScrollingOffset = layer.DoubleHeight ? ((row & 0x20) << (layer.DoubleWidth ? 6 : 5)) : 0; + uint16_t baseOffset = (layer.TilemapAddress >> 1) + addrVerticalScrollingOffset + ((row & 0x1F) << 5); + + for(int column = 0; column < (layer.DoubleWidth ? 64 : 32); column++) { + uint16_t addr = (baseOffset + (column & 0x1F) + (layer.DoubleWidth ? ((column & 0x20) << 5) : 0)) << 1; + + bool vMirror = (vram[addr + 1] & 0x80) != 0; + bool hMirror = (vram[addr + 1] & 0x40) != 0; + + uint16_t tileIndex = ((vram[addr + 1] & 0x03) << 8) | vram[addr]; + uint16_t tileStart = layer.ChrAddress + tileIndex * 8 * bpp; + + if(largeTileWidth || largeTileHeight) { + tileIndex = ( + tileIndex + + (largeTileHeight ? ((row & 0x01) ? (vMirror ? 0 : 16) : (vMirror ? 16 : 0)) : 0) + + (largeTileWidth ? ((column & 0x01) ? (hMirror ? 0 : 1) : (hMirror ? 1 : 0)) : 0) + ) & 0x3FF; + } + + for(int y = 0; y < 8; y++) { + uint8_t yOffset = vMirror ? (7 - y) : y; + uint16_t pixelStart = tileStart + yOffset * 2; + + for(int x = 0; x < 8; x++) { + uint8_t shift = hMirror ? x : (7 - x); + uint16_t color = GetTilePixelColor(bpp, pixelStart, shift); + if(color != 0) { + uint16_t paletteColor; + /*if(bpp == 8 && directColorMode) { + uint8_t palette = (vram[addr + 1] >> 2) & 0x07; + paletteColor = ( + (((color & 0x07) | (palette & 0x01)) << 1) | + (((color & 0x38) | ((palette & 0x02) << 1)) << 3) | + (((color & 0xC0) | ((palette & 0x04) << 3)) << 7) + ); + } else {*/ + uint8_t palette = bpp == 8 ? 0 : (vram[addr + 1] >> 2) & 0x07; + uint16_t paletteRamOffset = basePaletteOffset + (palette * (1 << bpp) + color) * 2; + paletteColor = cgram[paletteRamOffset] | (cgram[paletteRamOffset + 1] << 8); + //} + + outBuffer[((row * 8) + y) * 512 + column * 8 + x] = ToArgb(paletteColor); + } + } + } + } + } + + if(options.ShowTileGrid) { + uint32_t gridColor = 0xA0AAAAFF; + for(int i = 0; i < 512 * 512; i++) { + if((i & 0x07) == 0x07 || (i & 0x0E00) == 0x0E00) { + BlendColors((uint8_t*)&outBuffer[i], (uint8_t*)&gridColor); + } + } + } + + if(options.ShowScrollOverlay) { + uint32_t overlayColor = 0x40FFFFFF; + for(int y = 0; y < 240; y++) { + for(int x = 0; x < 256; x++) { + int xPos = layer.HScroll + x; + int yPos = layer.VScroll + y; + + xPos &= layer.DoubleWidth ? 0x1FF : 0xFF; + yPos &= layer.DoubleHeight ? 0x1FF : 0xFF; + + if(x == 0 || y == 0 || x == 255 || y == 239) { + outBuffer[(yPos << 9) | xPos] = 0xAFFFFFFF; + } else { + BlendColors((uint8_t*)&outBuffer[(yPos << 9) | xPos], (uint8_t*)&overlayColor); + } + } + } + } +} + +void PpuTools::SetViewerUpdateTiming(uint32_t viewerId, uint16_t scanline, uint16_t cycle) +{ + //TODO Thread safety + _updateTimings[viewerId] = (scanline << 16) | cycle; +} + +void PpuTools::RemoveViewer(uint32_t viewerId) +{ + //TODO Thread safety + _updateTimings.erase(viewerId); +} + +void PpuTools::UpdateViewers(uint16_t scanline, uint16_t cycle) +{ + uint32_t currentCycle = (scanline << 16) | cycle; + for(auto updateTiming : _updateTimings) { + if(updateTiming.second == currentCycle) { + _console->GetNotificationManager()->SendNotification(ConsoleNotificationType::ViewerRefresh, (void*)(uint64_t)updateTiming.first); + } + } +} \ No newline at end of file diff --git a/Core/PpuTools.h b/Core/PpuTools.h new file mode 100644 index 0000000..59d2017 --- /dev/null +++ b/Core/PpuTools.h @@ -0,0 +1,28 @@ +#pragma once +#include "stdafx.h" + +class Ppu; +class Console; +struct GetTilemapOptions; + +class PpuTools +{ +private: + Ppu *_ppu; + Console *_console; + unordered_map _updateTimings; + + uint16_t GetTilePixelColor(const uint8_t bpp, const uint16_t pixelStart, const uint8_t shift); + + uint32_t ToArgb(uint16_t color); + void BlendColors(uint8_t output[4], uint8_t input[4]); + +public: + PpuTools(Console *console, Ppu* ppu); + + void GetTilemap(GetTilemapOptions options, uint32_t *outBuffer); + + void SetViewerUpdateTiming(uint32_t viewerId, uint16_t scanline, uint16_t cycle); + void RemoveViewer(uint32_t viewerId); + void UpdateViewers(uint16_t scanline, uint16_t cycle); +}; \ No newline at end of file diff --git a/Core/PpuTypes.h b/Core/PpuTypes.h index 242a536..135c3d2 100644 --- a/Core/PpuTypes.h +++ b/Core/PpuTypes.h @@ -1,14 +1,6 @@ #pragma once #include "stdafx.h" -struct PpuState -{ - uint16_t Cycle; - uint16_t Scanline; - uint32_t FrameCount; - bool OverscanMode; -}; - struct LayerConfig { uint16_t TilemapAddress; @@ -17,12 +9,23 @@ struct LayerConfig uint16_t HScroll; uint16_t VScroll; - bool HorizontalMirroring; - bool VerticalMirroring; + bool DoubleWidth; + bool DoubleHeight; bool LargeTiles; }; +struct PpuState +{ + uint16_t Cycle; + uint16_t Scanline; + uint32_t FrameCount; + bool OverscanMode; + + uint8_t BgMode; + LayerConfig Layers[4]; +}; + struct Mode7Config { int16_t Matrix[4]; diff --git a/Core/stdafx.h b/Core/stdafx.h index c67ae48..e1348d1 100644 --- a/Core/stdafx.h +++ b/Core/stdafx.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -30,6 +32,8 @@ #endif using std::vector; +using std::unordered_map; +using std::unordered_set; using std::shared_ptr; using std::unique_ptr; using std::weak_ptr; diff --git a/InteropDLL/DebugApiWrapper.cpp b/InteropDLL/DebugApiWrapper.cpp index 288e991..1f8b7b0 100644 --- a/InteropDLL/DebugApiWrapper.cpp +++ b/InteropDLL/DebugApiWrapper.cpp @@ -7,6 +7,7 @@ #include "../Core/DebugTypes.h" #include "../Core/Breakpoint.h" #include "../Core/BreakpointManager.h" +#include "../Core/PpuTools.h" extern shared_ptr _console; @@ -59,4 +60,7 @@ extern "C" DllExport uint8_t __stdcall GetMemoryValue(SnesMemoryType type, uint32_t address) { return GetDebugger()->GetMemoryDumper()->GetMemoryValue(type, address); } DllExport void __stdcall SetMemoryValue(SnesMemoryType type, uint32_t address, uint8_t value) { return GetDebugger()->GetMemoryDumper()->SetMemoryValue(type, address, value); } DllExport void __stdcall SetMemoryValues(SnesMemoryType type, uint32_t address, uint8_t* data, int32_t length) { return GetDebugger()->GetMemoryDumper()->SetMemoryValues(type, address, data, length); } + + DllExport void __stdcall GetTilemap(GetTilemapOptions options, uint32_t *buffer) { GetDebugger()->GetPpuTools()->GetTilemap(options, buffer); } + DllExport void __stdcall SetViewerUpdateTiming(uint32_t viewerId, uint16_t scanline, uint16_t cycle) { GetDebugger()->GetPpuTools()->SetViewerUpdateTiming(viewerId, scanline, cycle); } }; \ No newline at end of file diff --git a/UI/Debugger/DebugWindowManager.cs b/UI/Debugger/DebugWindowManager.cs index 30add7b..c392481 100644 --- a/UI/Debugger/DebugWindowManager.cs +++ b/UI/Debugger/DebugWindowManager.cs @@ -34,6 +34,7 @@ namespace Mesen.GUI.Debugger case DebugWindow.Debugger: frm = new frmDebugger(); frm.Icon = Properties.Resources.Debugger; break; case DebugWindow.TraceLogger: frm = new frmTraceLogger(); frm.Icon = Properties.Resources.LogWindow; break; case DebugWindow.MemoryTools: frm = new frmMemoryTools(); frm.Icon = Properties.Resources.CheatCode; break; + case DebugWindow.TilemapViewer: frm = new frmTilemapViewer(); frm.Icon = Properties.Resources.VideoOptions; break; } _openedWindows.Add(frm); frm.FormClosed += Debugger_FormClosed; @@ -115,6 +116,7 @@ namespace Mesen.GUI.Debugger { Debugger, MemoryTools, - TraceLogger + TraceLogger, + TilemapViewer } } diff --git a/UI/Debugger/PpuViewer/ctrlScanlineCycleSelect.cs b/UI/Debugger/PpuViewer/ctrlScanlineCycleSelect.cs new file mode 100644 index 0000000..0a67421 --- /dev/null +++ b/UI/Debugger/PpuViewer/ctrlScanlineCycleSelect.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Mesen.GUI.Config; + +namespace Mesen.GUI.Debugger.Controls +{ + public partial class ctrlScanlineCycleSelect : UserControl + { + private static int _nextViewerId = 0; + + private int _viewerId = 0; + private int _scanline = 241; + private int _cycle = 0; + + public int Scanline { get { return _scanline; } } + public int Cycle { get { return _cycle; } } + public int ViewerId { get { return _viewerId; } } + + public ctrlScanlineCycleSelect() + { + InitializeComponent(); + _viewerId = GetNextViewerId(); + } + + private int GetNextViewerId() + { + return _nextViewerId++; + } + + public void Initialize(int scanline, int cycle) + { + _scanline = scanline; + _cycle = cycle; + + this.nudScanline.Value = _scanline; + this.nudCycle.Value = _cycle; + + DebugApi.SetViewerUpdateTiming(_viewerId, _scanline, _cycle); + } + + public void RefreshSettings() + { + DebugApi.SetViewerUpdateTiming(_viewerId, _scanline, _cycle); + } + + private void SetUpdateScanlineCycle(int scanline, int cycle) + { + _scanline = scanline; + _cycle = cycle; + RefreshSettings(); + } + + private void nudScanlineCycle_ValueChanged(object sender, EventArgs e) + { + SetUpdateScanlineCycle((int)this.nudScanline.Value, (int)this.nudCycle.Value); + } + + private void btnReset_Click(object sender, EventArgs e) + { + this.nudScanline.Value = 241; + this.nudCycle.Value = 0; + } + } +} diff --git a/UI/Debugger/PpuViewer/ctrlScanlineCycleSelect.designer.cs b/UI/Debugger/PpuViewer/ctrlScanlineCycleSelect.designer.cs new file mode 100644 index 0000000..3797b70 --- /dev/null +++ b/UI/Debugger/PpuViewer/ctrlScanlineCycleSelect.designer.cs @@ -0,0 +1,168 @@ +namespace Mesen.GUI.Debugger.Controls +{ + partial class ctrlScanlineCycleSelect + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if(disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.flpRefreshTiming = new System.Windows.Forms.FlowLayoutPanel(); + this.lblShowFrameAt = new System.Windows.Forms.Label(); + this.nudScanline = new Mesen.GUI.Controls.MesenNumericUpDown(); + this.lblCycle = new System.Windows.Forms.Label(); + this.nudCycle = new Mesen.GUI.Controls.MesenNumericUpDown(); + this.btnReset = new System.Windows.Forms.Button(); + this.flpRefreshTiming.SuspendLayout(); + this.SuspendLayout(); + // + // flpRefreshTiming + // + this.flpRefreshTiming.Controls.Add(this.lblShowFrameAt); + this.flpRefreshTiming.Controls.Add(this.nudScanline); + this.flpRefreshTiming.Controls.Add(this.lblCycle); + this.flpRefreshTiming.Controls.Add(this.nudCycle); + this.flpRefreshTiming.Controls.Add(this.btnReset); + this.flpRefreshTiming.Dock = System.Windows.Forms.DockStyle.Fill; + this.flpRefreshTiming.Location = new System.Drawing.Point(0, 0); + this.flpRefreshTiming.Name = "flpRefreshTiming"; + this.flpRefreshTiming.Size = new System.Drawing.Size(399, 28); + this.flpRefreshTiming.TabIndex = 5; + // + // lblShowFrameAt + // + this.lblShowFrameAt.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblShowFrameAt.AutoSize = true; + this.lblShowFrameAt.Location = new System.Drawing.Point(3, 8); + this.lblShowFrameAt.Name = "lblShowFrameAt"; + this.lblShowFrameAt.Size = new System.Drawing.Size(135, 13); + this.lblShowFrameAt.TabIndex = 0; + this.lblShowFrameAt.Text = "Refresh viewer on scanline"; + // + // nudScanline + // + this.nudScanline.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.nudScanline.DecimalPlaces = 0; + this.nudScanline.Increment = new decimal(new int[] { + 0, + 0, + 0, + 0}); + this.nudScanline.Location = new System.Drawing.Point(144, 4); + this.nudScanline.Maximum = new decimal(new int[] { + 260, + 0, + 0, + 0}); + this.nudScanline.MaximumSize = new System.Drawing.Size(10000, 20); + this.nudScanline.Minimum = new decimal(new int[] { + 1, + 0, + 0, + -2147483648}); + this.nudScanline.MinimumSize = new System.Drawing.Size(0, 21); + this.nudScanline.Name = "nudScanline"; + this.nudScanline.Size = new System.Drawing.Size(52, 21); + this.nudScanline.TabIndex = 5; + this.nudScanline.Value = new decimal(new int[] { + 241, + 0, + 0, + 0}); + this.nudScanline.ValueChanged += new System.EventHandler(this.nudScanlineCycle_ValueChanged); + // + // lblCycle + // + this.lblCycle.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblCycle.AutoSize = true; + this.lblCycle.Location = new System.Drawing.Point(202, 8); + this.lblCycle.Name = "lblCycle"; + this.lblCycle.Size = new System.Drawing.Size(53, 13); + this.lblCycle.TabIndex = 5; + this.lblCycle.Text = "and cycle"; + // + // nudCycle + // + this.nudCycle.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.nudCycle.DecimalPlaces = 0; + this.nudCycle.Increment = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.nudCycle.Location = new System.Drawing.Point(261, 4); + this.nudCycle.Maximum = new decimal(new int[] { + 340, + 0, + 0, + 0}); + this.nudCycle.MaximumSize = new System.Drawing.Size(10000, 20); + this.nudCycle.Minimum = new decimal(new int[] { + 0, + 0, + 0, + 0}); + this.nudCycle.MinimumSize = new System.Drawing.Size(0, 21); + this.nudCycle.Name = "nudCycle"; + this.nudCycle.Size = new System.Drawing.Size(52, 21); + this.nudCycle.TabIndex = 6; + this.nudCycle.Value = new decimal(new int[] { + 0, + 0, + 0, + 0}); + this.nudCycle.ValueChanged += new System.EventHandler(this.nudScanlineCycle_ValueChanged); + // + // btnReset + // + this.btnReset.Location = new System.Drawing.Point(319, 3); + this.btnReset.Name = "btnReset"; + this.btnReset.Size = new System.Drawing.Size(75, 23); + this.btnReset.TabIndex = 7; + this.btnReset.Text = "Reset"; + this.btnReset.UseVisualStyleBackColor = true; + this.btnReset.Click += new System.EventHandler(this.btnReset_Click); + // + // ctrlScanlineCycleSelect + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.flpRefreshTiming); + this.Name = "ctrlScanlineCycleSelect"; + this.Size = new System.Drawing.Size(399, 28); + this.flpRefreshTiming.ResumeLayout(false); + this.flpRefreshTiming.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.FlowLayoutPanel flpRefreshTiming; + private System.Windows.Forms.Label lblShowFrameAt; + private GUI.Controls.MesenNumericUpDown nudScanline; + private System.Windows.Forms.Label lblCycle; + private GUI.Controls.MesenNumericUpDown nudCycle; + private System.Windows.Forms.Button btnReset; + } +} diff --git a/UI/Debugger/PpuViewer/ctrlScanlineCycleSelect.resx b/UI/Debugger/PpuViewer/ctrlScanlineCycleSelect.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/UI/Debugger/PpuViewer/ctrlScanlineCycleSelect.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/UI/Debugger/PpuViewer/frmTilemapViewer.Designer.cs b/UI/Debugger/PpuViewer/frmTilemapViewer.Designer.cs new file mode 100644 index 0000000..ecf74b7 --- /dev/null +++ b/UI/Debugger/PpuViewer/frmTilemapViewer.Designer.cs @@ -0,0 +1,244 @@ +namespace Mesen.GUI.Debugger +{ + partial class frmTilemapViewer + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if(disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.picTilemap = new Mesen.GUI.Controls.ctrlMesenPictureBox(); + this.btnLayer1 = new System.Windows.Forms.Button(); + this.btnLayer2 = new System.Windows.Forms.Button(); + this.btnLayer3 = new System.Windows.Forms.Button(); + this.btnLayer4 = new System.Windows.Forms.Button(); + this.ctrlScanlineCycleSelect = new Mesen.GUI.Debugger.Controls.ctrlScanlineCycleSelect(); + this.pnlTilemap = new System.Windows.Forms.Panel(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.chkShowScrollOverlay = new System.Windows.Forms.CheckBox(); + this.chkShowTileGrid = new System.Windows.Forms.CheckBox(); + this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); + this.lblLayer = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)(this.picTilemap)).BeginInit(); + this.pnlTilemap.SuspendLayout(); + this.tableLayoutPanel1.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); + this.tableLayoutPanel3.SuspendLayout(); + this.SuspendLayout(); + // + // picTilemap + // + this.picTilemap.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; + this.picTilemap.Location = new System.Drawing.Point(0, 0); + this.picTilemap.MinimumSize = new System.Drawing.Size(256, 256); + this.picTilemap.Name = "picTilemap"; + this.picTilemap.Size = new System.Drawing.Size(512, 512); + this.picTilemap.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.picTilemap.TabIndex = 0; + this.picTilemap.TabStop = false; + this.picTilemap.DoubleClick += new System.EventHandler(this.picTilemap_DoubleClick); + // + // btnLayer1 + // + this.btnLayer1.Location = new System.Drawing.Point(45, 3); + this.btnLayer1.Name = "btnLayer1"; + this.btnLayer1.Size = new System.Drawing.Size(32, 22); + this.btnLayer1.TabIndex = 1; + this.btnLayer1.Text = "1"; + this.btnLayer1.UseVisualStyleBackColor = true; + this.btnLayer1.Click += new System.EventHandler(this.btnLayer1_Click); + // + // btnLayer2 + // + this.btnLayer2.Location = new System.Drawing.Point(83, 3); + this.btnLayer2.Name = "btnLayer2"; + this.btnLayer2.Size = new System.Drawing.Size(32, 22); + this.btnLayer2.TabIndex = 2; + this.btnLayer2.Text = "2"; + this.btnLayer2.UseVisualStyleBackColor = true; + this.btnLayer2.Click += new System.EventHandler(this.btnLayer2_Click); + // + // btnLayer3 + // + this.btnLayer3.Location = new System.Drawing.Point(121, 3); + this.btnLayer3.Name = "btnLayer3"; + this.btnLayer3.Size = new System.Drawing.Size(32, 22); + this.btnLayer3.TabIndex = 3; + this.btnLayer3.Text = "3"; + this.btnLayer3.UseVisualStyleBackColor = true; + this.btnLayer3.Click += new System.EventHandler(this.btnLayer3_Click); + // + // btnLayer4 + // + this.btnLayer4.Location = new System.Drawing.Point(159, 3); + this.btnLayer4.Name = "btnLayer4"; + this.btnLayer4.Size = new System.Drawing.Size(32, 22); + this.btnLayer4.TabIndex = 4; + this.btnLayer4.Text = "4"; + this.btnLayer4.UseVisualStyleBackColor = true; + this.btnLayer4.Click += new System.EventHandler(this.btnLayer4_Click); + // + // ctrlScanlineCycleSelect + // + this.ctrlScanlineCycleSelect.Dock = System.Windows.Forms.DockStyle.Bottom; + this.ctrlScanlineCycleSelect.Location = new System.Drawing.Point(0, 546); + this.ctrlScanlineCycleSelect.Name = "ctrlScanlineCycleSelect"; + this.ctrlScanlineCycleSelect.Size = new System.Drawing.Size(667, 28); + this.ctrlScanlineCycleSelect.TabIndex = 5; + // + // pnlTilemap + // + this.pnlTilemap.AutoScroll = true; + this.pnlTilemap.Controls.Add(this.picTilemap); + this.pnlTilemap.Dock = System.Windows.Forms.DockStyle.Fill; + this.pnlTilemap.Location = new System.Drawing.Point(3, 31); + this.pnlTilemap.MinimumSize = new System.Drawing.Size(512, 512); + this.pnlTilemap.Name = "pnlTilemap"; + this.pnlTilemap.Size = new System.Drawing.Size(512, 512); + this.pnlTilemap.TabIndex = 6; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.Controls.Add(this.pnlTilemap, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.tableLayoutPanel2, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.tableLayoutPanel3, 0, 0); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 2; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(667, 546); + this.tableLayoutPanel1.TabIndex = 7; + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.ColumnCount = 1; + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.Controls.Add(this.chkShowScrollOverlay, 0, 1); + this.tableLayoutPanel2.Controls.Add(this.chkShowTileGrid, 0, 0); + this.tableLayoutPanel2.Location = new System.Drawing.Point(521, 3); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 3; + this.tableLayoutPanel1.SetRowSpan(this.tableLayoutPanel2, 2); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.Size = new System.Drawing.Size(143, 320); + this.tableLayoutPanel2.TabIndex = 7; + // + // chkShowScrollOverlay + // + this.chkShowScrollOverlay.AutoSize = true; + this.chkShowScrollOverlay.Location = new System.Drawing.Point(3, 26); + this.chkShowScrollOverlay.Name = "chkShowScrollOverlay"; + this.chkShowScrollOverlay.Size = new System.Drawing.Size(117, 17); + this.chkShowScrollOverlay.TabIndex = 1; + this.chkShowScrollOverlay.Text = "Show scroll overlay"; + this.chkShowScrollOverlay.UseVisualStyleBackColor = true; + this.chkShowScrollOverlay.Click += new System.EventHandler(this.chkShowScrollOverlay_Click); + // + // chkShowTileGrid + // + this.chkShowTileGrid.AutoSize = true; + this.chkShowTileGrid.Location = new System.Drawing.Point(3, 3); + this.chkShowTileGrid.Name = "chkShowTileGrid"; + this.chkShowTileGrid.Size = new System.Drawing.Size(89, 17); + this.chkShowTileGrid.TabIndex = 0; + this.chkShowTileGrid.Text = "Show tile grid"; + this.chkShowTileGrid.UseVisualStyleBackColor = true; + this.chkShowTileGrid.Click += new System.EventHandler(this.chkShowTileGrid_Click); + // + // tableLayoutPanel3 + // + this.tableLayoutPanel3.ColumnCount = 6; + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel3.Controls.Add(this.btnLayer4, 4, 0); + this.tableLayoutPanel3.Controls.Add(this.btnLayer2, 2, 0); + this.tableLayoutPanel3.Controls.Add(this.btnLayer3, 3, 0); + this.tableLayoutPanel3.Controls.Add(this.btnLayer1, 1, 0); + this.tableLayoutPanel3.Controls.Add(this.lblLayer, 0, 0); + this.tableLayoutPanel3.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel3.Margin = new System.Windows.Forms.Padding(0); + this.tableLayoutPanel3.Name = "tableLayoutPanel3"; + this.tableLayoutPanel3.RowCount = 1; + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel3.Size = new System.Drawing.Size(205, 28); + this.tableLayoutPanel3.TabIndex = 8; + // + // lblLayer + // + this.lblLayer.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblLayer.AutoSize = true; + this.lblLayer.Location = new System.Drawing.Point(3, 7); + this.lblLayer.Name = "lblLayer"; + this.lblLayer.Size = new System.Drawing.Size(36, 13); + this.lblLayer.TabIndex = 5; + this.lblLayer.Text = "Layer:"; + // + // frmTilemapViewer + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(667, 574); + this.Controls.Add(this.tableLayoutPanel1); + this.Controls.Add(this.ctrlScanlineCycleSelect); + this.Name = "frmTilemapViewer"; + this.Text = "Tilemap Viewer"; + ((System.ComponentModel.ISupportInitialize)(this.picTilemap)).EndInit(); + this.pnlTilemap.ResumeLayout(false); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); + this.tableLayoutPanel3.ResumeLayout(false); + this.tableLayoutPanel3.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private GUI.Controls.ctrlMesenPictureBox picTilemap; + private System.Windows.Forms.Button btnLayer1; + private System.Windows.Forms.Button btnLayer2; + private System.Windows.Forms.Button btnLayer3; + private System.Windows.Forms.Button btnLayer4; + private Controls.ctrlScanlineCycleSelect ctrlScanlineCycleSelect; + private System.Windows.Forms.Panel pnlTilemap; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; + private System.Windows.Forms.CheckBox chkShowTileGrid; + private System.Windows.Forms.CheckBox chkShowScrollOverlay; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3; + private System.Windows.Forms.Label lblLayer; + } +} \ No newline at end of file diff --git a/UI/Debugger/PpuViewer/frmTilemapViewer.cs b/UI/Debugger/PpuViewer/frmTilemapViewer.cs new file mode 100644 index 0000000..9480b22 --- /dev/null +++ b/UI/Debugger/PpuViewer/frmTilemapViewer.cs @@ -0,0 +1,137 @@ +using Mesen.GUI.Forms; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Mesen.GUI.Debugger +{ + public partial class frmTilemapViewer : BaseForm + { + private NotificationListener _notifListener; + private GetTilemapOptions _options; + private DebugState _state; + private byte[] _tilemapData; + private Bitmap _tilemapImage; + private bool _zoomed; + + public frmTilemapViewer() + { + InitializeComponent(); + + if(!this.DesignMode) { + _options.BgMode = 0; + + _notifListener = new NotificationListener(); + _notifListener.OnNotification += OnNotificationReceived; + + _tilemapImage = new Bitmap(512, 512, PixelFormat.Format32bppArgb); + picTilemap.Image = _tilemapImage; + + ctrlScanlineCycleSelect.Initialize(241, 0); + + RefreshData(); + RefreshViewer(); + } + } + + protected override void OnFormClosed(FormClosedEventArgs e) + { + base.OnFormClosed(e); + _notifListener?.Dispose(); + } + + private void OnNotificationReceived(NotificationEventArgs e) + { + switch(e.NotificationType) { + case ConsoleNotificationType.ViewerRefresh: + if(e.Parameter.ToInt32() == ctrlScanlineCycleSelect.ViewerId) { + RefreshData(); + this.BeginInvoke((Action)(() => { + this.RefreshViewer(); + })); + } + break; + } + } + + private void RefreshData() + { + _state = DebugApi.GetState(); + _tilemapData = DebugApi.GetTilemap(_options); + } + + private void RefreshViewer() + { + int mapWidth = _state.Ppu.Layers[_options.Layer].DoubleWidth ? 512 : 256; + int mapHeight = _state.Ppu.Layers[_options.Layer].DoubleHeight ? 512 : 256; + if(_tilemapImage.Width != mapWidth || _tilemapImage.Height != mapHeight) { + _tilemapImage = new Bitmap(mapWidth, mapHeight, PixelFormat.Format32bppArgb); + picTilemap.Image = _tilemapImage; + } + using(Graphics g = Graphics.FromImage(_tilemapImage)) { + GCHandle handle = GCHandle.Alloc(_tilemapData, GCHandleType.Pinned); + Bitmap source = new Bitmap(512, 512, 4 * 512, PixelFormat.Format32bppArgb, handle.AddrOfPinnedObject()); + try { + g.DrawImage(source, 0, 0); + } finally { + handle.Free(); + } + } + + UpdateMapSize(); + picTilemap.Invalidate(); + } + + private void UpdateMapSize() + { + int mapWidth = _state.Ppu.Layers[_options.Layer].DoubleWidth ? 512 : 256; + int mapHeight = _state.Ppu.Layers[_options.Layer].DoubleHeight ? 512 : 256; + picTilemap.Width = _zoomed ? mapWidth * 2 : mapWidth; + picTilemap.Height = _zoomed ? mapHeight * 2 : mapHeight; + } + + private void btnLayer1_Click(object sender, EventArgs e) + { + _options.Layer = 0; + } + + private void btnLayer2_Click(object sender, EventArgs e) + { + _options.Layer = 1; + } + + private void btnLayer3_Click(object sender, EventArgs e) + { + _options.Layer = 2; + } + + private void btnLayer4_Click(object sender, EventArgs e) + { + _options.Layer = 3; + } + + private void picTilemap_DoubleClick(object sender, EventArgs e) + { + _zoomed = !_zoomed; + UpdateMapSize(); + } + + private void chkShowTileGrid_Click(object sender, EventArgs e) + { + _options.ShowTileGrid = chkShowTileGrid.Checked; + } + + private void chkShowScrollOverlay_Click(object sender, EventArgs e) + { + _options.ShowScrollOverlay = chkShowScrollOverlay.Checked; + } + } +} diff --git a/UI/Debugger/PpuViewer/frmTilemapViewer.resx b/UI/Debugger/PpuViewer/frmTilemapViewer.resx new file mode 100644 index 0000000..8766f29 --- /dev/null +++ b/UI/Debugger/PpuViewer/frmTilemapViewer.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/UI/Forms/frmMain.Designer.cs b/UI/Forms/frmMain.Designer.cs index af11f27..2fd0773 100644 --- a/UI/Forms/frmMain.Designer.cs +++ b/UI/Forms/frmMain.Designer.cs @@ -39,6 +39,7 @@ this.mnuDebugger = new System.Windows.Forms.ToolStripMenuItem(); this.mnuMemoryTools = new System.Windows.Forms.ToolStripMenuItem(); this.mnuTraceLogger = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuTilemapViewer = new System.Windows.Forms.ToolStripMenuItem(); this.mnuMain.SuspendLayout(); this.SuspendLayout(); // @@ -86,7 +87,8 @@ this.toolStripMenuItem1, this.mnuDebugger, this.mnuMemoryTools, - this.mnuTraceLogger}); + this.mnuTraceLogger, + this.mnuTilemapViewer}); this.debugToolStripMenuItem.Name = "debugToolStripMenuItem"; this.debugToolStripMenuItem.Size = new System.Drawing.Size(54, 20); this.debugToolStripMenuItem.Text = "Debug"; @@ -144,6 +146,14 @@ this.mnuTraceLogger.Text = "Trace Logger"; this.mnuTraceLogger.Click += new System.EventHandler(this.mnuTraceLogger_Click); // + // mnuTilemapViewer + // + this.mnuTilemapViewer.Image = global::Mesen.GUI.Properties.Resources.VideoOptions; + this.mnuTilemapViewer.Name = "mnuTilemapViewer"; + this.mnuTilemapViewer.Size = new System.Drawing.Size(163, 22); + this.mnuTilemapViewer.Text = "Tilemap Viewer"; + this.mnuTilemapViewer.Click += new System.EventHandler(this.mnuTilemapViewer_Click); + // // frmMain // this.AllowDrop = true; @@ -176,5 +186,6 @@ private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1; private System.Windows.Forms.ToolStripMenuItem mnuRun100Instructions; private System.Windows.Forms.ToolStripMenuItem mnuMemoryTools; + private System.Windows.Forms.ToolStripMenuItem mnuTilemapViewer; } } \ No newline at end of file diff --git a/UI/Forms/frmMain.cs b/UI/Forms/frmMain.cs index 8031ecc..6ac7b55 100644 --- a/UI/Forms/frmMain.cs +++ b/UI/Forms/frmMain.cs @@ -73,6 +73,11 @@ namespace Mesen.GUI.Forms DebugWindowManager.OpenDebugWindow(DebugWindow.MemoryTools); } + private void mnuTilemapViewer_Click(object sender, EventArgs e) + { + DebugWindowManager.OpenDebugWindow(DebugWindow.TilemapViewer); + } + private void mnuStep_Click(object sender, EventArgs e) { DebugApi.Step(1); diff --git a/UI/Interop/DebugApi.cs b/UI/Interop/DebugApi.cs index cbccbc7..87943a5 100644 --- a/UI/Interop/DebugApi.cs +++ b/UI/Interop/DebugApi.cs @@ -70,6 +70,16 @@ namespace Mesen.GUI DebugApi.GetMemoryStateWrapper(type, buffer); return buffer; } + + [DllImport(DllPath, EntryPoint = "GetTilemap")] private static extern void GetTilemapWrapper(GetTilemapOptions options, [In, Out] byte[] buffer); + public static byte[] GetTilemap(GetTilemapOptions options) + { + byte[] buffer = new byte[512*512*4]; + DebugApi.GetTilemapWrapper(options, buffer); + return buffer; + } + + [DllImport(DllPath)] public static extern void SetViewerUpdateTiming(Int32 viewerId, Int32 scanline, Int32 cycle); } public enum SnesMemoryType @@ -112,14 +122,47 @@ namespace Mesen.GUI public UInt16 Cycle; public UInt16 Scanline; public UInt32 FrameCount; + [MarshalAs(UnmanagedType.I1)] public bool OverscanMode; + + public byte BgMode; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public LayerConfig[] Layers; }; + public struct LayerConfig + { + public UInt16 TilemapAddress; + public UInt16 ChrAddress; + + public UInt16 HScroll; + public UInt16 VScroll; + + [MarshalAs(UnmanagedType.I1)] public bool DoubleWidth; + [MarshalAs(UnmanagedType.I1)] public bool DoubleHeight; + + [MarshalAs(UnmanagedType.I1)] public bool LargeTiles; + } + public struct DebugState { public CpuState Cpu; public PpuState Ppu; } + public struct GetTilemapOptions + { + public byte BgMode; + public byte Layer; + + public byte Bpp; + public Int32 TilemapAddr; + public Int32 ChrAddr; + + [MarshalAs(UnmanagedType.I1)] public bool ShowTileGrid; + [MarshalAs(UnmanagedType.I1)] public bool ShowScrollOverlay; + } + [Serializable] public struct InteropTraceLoggerOptions { diff --git a/UI/Interop/NotificationListener.cs b/UI/Interop/NotificationListener.cs index 29e64e6..586c704 100644 --- a/UI/Interop/NotificationListener.cs +++ b/UI/Interop/NotificationListener.cs @@ -22,7 +22,10 @@ namespace Mesen.GUI public void Dispose() { - EmuApi.UnregisterNotificationCallback(_notificationListener); + if(_notificationListener != IntPtr.Zero) { + EmuApi.UnregisterNotificationCallback(_notificationListener); + _notificationListener = IntPtr.Zero; + } } public void ProcessNotification(int type, IntPtr parameter) @@ -57,5 +60,6 @@ namespace Mesen.GUI ExecuteShortcut = 10, EmulationStopped = 11, BeforeEmulationStop = 12, + ViewerRefresh = 13, } } diff --git a/UI/UI.csproj b/UI/UI.csproj index 1fb786a..9157fbc 100644 --- a/UI/UI.csproj +++ b/UI/UI.csproj @@ -374,6 +374,18 @@ frmMemoryTools.cs + + UserControl + + + ctrlScanlineCycleSelect.cs + + + Form + + + frmTilemapViewer.cs + Form @@ -508,6 +520,12 @@ frmMemoryTools.cs + + ctrlScanlineCycleSelect.cs + + + frmTilemapViewer.cs + frmTraceLogger.cs