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