From 7df142e420e2ee7a8cc2c51d32cb1be41901178f Mon Sep 17 00:00:00 2001 From: Sour Date: Sat, 6 Aug 2022 11:29:50 -0400 Subject: [PATCH] Debugger: Tilemap viewer - Mode 7 overlay --- Core/Debugger/PpuTools.h | 2 + Core/SNES/Debugger/SnesPpuTools.cpp | 15 ++++++ Core/SNES/Debugger/SnesPpuTools.h | 14 ++++++ Core/SNES/SnesPpu.cpp | 28 +++++++++++ Core/SNES/SnesPpu.h | 7 +++ Core/Shared/Emulator.h | 1 + InteropDLL/DebugApiWrapper.cpp | 1 + NewUI/Debugger/Controls/PictureViewer.cs | 6 ++- .../ViewModels/TilemapViewerViewModel.cs | 46 +++++++++++++++++++ .../Windows/TilemapViewerWindow.axaml | 1 + NewUI/Interop/DebugApi.cs | 19 ++++++++ NewUI/Interop/DebugState.cs | 22 +++++++++ Utilities/safe_ptr.h | 5 ++ 13 files changed, 165 insertions(+), 2 deletions(-) diff --git a/Core/Debugger/PpuTools.h b/Core/Debugger/PpuTools.h index 5eae8633..b7a491b8 100644 --- a/Core/Debugger/PpuTools.h +++ b/Core/Debugger/PpuTools.h @@ -188,6 +188,8 @@ protected: public: PpuTools(Debugger* debugger, Emulator *emu); + virtual void GetPpuToolsState(BaseState& state) {}; + virtual DebugPaletteInfo GetPaletteInfo(GetPaletteInfoOptions options) = 0; void GetTileView(GetTileViewOptions options, uint8_t *source, uint32_t srcSize, const uint32_t* palette, uint32_t *outBuffer); diff --git a/Core/SNES/Debugger/SnesPpuTools.cpp b/Core/SNES/Debugger/SnesPpuTools.cpp index d2d468a2..d18670fe 100644 --- a/Core/SNES/Debugger/SnesPpuTools.cpp +++ b/Core/SNES/Debugger/SnesPpuTools.cpp @@ -12,6 +12,21 @@ static constexpr uint8_t layerBpp[8][4] = { SnesPpuTools::SnesPpuTools(Debugger* debugger, Emulator *emu) : PpuTools(debugger, emu) { + _state = {}; +} + +void SnesPpuTools::GetPpuToolsState(BaseState& state) +{ + (SnesPpuToolsState&)state = _state; +} + +void SnesPpuTools::SetPpuScanlineState(uint16_t scanline, uint8_t mode, int32_t mode7startX, int32_t mode7startY, int32_t mode7endX, int32_t mode7endY) +{ + _state.ScanlineBgMode[scanline] = mode; + _state.Mode7StartX[scanline] = mode7startX; + _state.Mode7StartY[scanline] = mode7startY; + _state.Mode7EndX[scanline] = mode7endX; + _state.Mode7EndY[scanline] = mode7endY; } DebugTilemapInfo SnesPpuTools::GetTilemap(GetTilemapOptions options, BaseState& baseState, uint8_t* vram, uint32_t* palette, uint32_t* outBuffer) diff --git a/Core/SNES/Debugger/SnesPpuTools.h b/Core/SNES/Debugger/SnesPpuTools.h index 9ecbd4e1..e7f7c162 100644 --- a/Core/SNES/Debugger/SnesPpuTools.h +++ b/Core/SNES/Debugger/SnesPpuTools.h @@ -6,14 +6,28 @@ class Debugger; class Emulator; struct BaseState; +struct SnesPpuToolsState +{ + uint8_t ScanlineBgMode[239]; + int32_t Mode7StartX[239]; + int32_t Mode7StartY[239]; + int32_t Mode7EndX[239]; + int32_t Mode7EndY[239]; +}; + class SnesPpuTools final : public PpuTools { private: + SnesPpuToolsState _state; + void GetSpriteInfo(DebugSpriteInfo& sprite, uint16_t spriteIndex, GetSpritePreviewOptions& options, SnesPpuState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette); public: SnesPpuTools(Debugger* debugger, Emulator *emu); + void GetPpuToolsState(BaseState& state) override; + void SetPpuScanlineState(uint16_t scanline, uint8_t mode, int32_t mode7startX, int32_t mode7startY, int32_t mode7endX, int32_t mode7endY); + DebugTilemapInfo GetTilemap(GetTilemapOptions options, BaseState& state, uint8_t* vram, uint32_t* palette, uint32_t* outBuffer) override; FrameInfo GetTilemapSize(GetTilemapOptions options, BaseState& state) override; DebugTilemapTileInfo GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState) override; diff --git a/Core/SNES/SnesPpu.cpp b/Core/SNES/SnesPpu.cpp index 6a58600e..122a7962 100644 --- a/Core/SNES/SnesPpu.cpp +++ b/Core/SNES/SnesPpu.cpp @@ -7,6 +7,8 @@ #include "SNES/InternalRegisters.h" #include "SNES/SnesControlManager.h" #include "SNES/SnesDmaController.h" +#include "SNES/Debugger/SnesPpuTools.h" +#include "Debugger/Debugger.h" #include "Shared/Emulator.h" #include "Shared/EmuSettings.h" #include "Shared/Video/VideoDecoder.h" @@ -19,6 +21,7 @@ #include "Utilities/HexUtilities.h" #include "Utilities/Serializer.h" +//TODO remove #ifdef _MSC_VER #pragma warning ( disable : 4127 ) //conditional expression is constant #endif @@ -443,6 +446,10 @@ bool SnesPpu::ProcessEndOfScanline(uint16_t& hClock) memset(_mainScreenFlags, 0, sizeof(_mainScreenFlags)); memset(_subScreenPriority, 0, sizeof(_subScreenPriority)); + + if(!_skipRender && _emu->IsDebugging()) { + DebugProcessMode7Overlay(); + } } _scanline++; @@ -1141,6 +1148,14 @@ void SnesPpu::RenderTilemapMode7() xStep = -xStep; yStep = -yStep; } + + if(_drawStartX == 0) { + //Keep start/end values - used by tilemap viewer + _debugMode7StartX = xValue; + _debugMode7StartY = yValue; + _debugMode7EndX = xValue + xStep * 256; + _debugMode7EndY = yValue + yStep * 256; + } xValue += xStep * _drawStartX; yValue += yStep * _drawStartX; @@ -1501,6 +1516,19 @@ void SnesPpu::DebugSendFrame() _emu->GetVideoDecoder()->UpdateFrame(frame, false, false); } +void SnesPpu::DebugProcessMode7Overlay() +{ + //Store mode 7 related information in ppu tools to allow tilemap viewer to display mode 7 overlay + SnesPpuTools* ppuTools = ((SnesPpuTools*)_emu->InternalGetDebugger()->GetPpuTools(CpuType::Snes)); + ppuTools->SetPpuScanlineState(_scanline, _state.ForcedBlank ? 0 : _state.BgMode, _debugMode7StartX, _debugMode7StartY, _debugMode7EndX, _debugMode7EndY); + if(_scanline == _vblankStartScanline - 1) { + for(int i = _scanline + 1; i < 239; i++) { + ppuTools->SetPpuScanlineState(i, 0, 0, 0, 0, 0); + } + } + _debugMode7StartX = _debugMode7StartY = _debugMode7EndX = _debugMode7EndY = 0; +} + bool SnesPpu::IsHighResOutput() { return _useHighResOutput; diff --git a/Core/SNES/SnesPpu.h b/Core/SNES/SnesPpu.h index 6907135c..423fd254 100644 --- a/Core/SNES/SnesPpu.h +++ b/Core/SNES/SnesPpu.h @@ -116,6 +116,11 @@ private: uint8_t _spritePaletteCopy[256] = {}; uint8_t _spriteColorsCopy[256] = {}; + int32_t _debugMode7StartX = 0; + int32_t _debugMode7StartY = 0; + int32_t _debugMode7EndX = 0; + int32_t _debugMode7EndY = 0; + void RenderSprites(const uint8_t priorities[4]); template @@ -204,6 +209,8 @@ private: void RandomizeState(); + __noinline void DebugProcessMode7Overlay(); + public: SnesPpu(Emulator* emu, SnesConsole* console); virtual ~SnesPpu(); diff --git a/Core/Shared/Emulator.h b/Core/Shared/Emulator.h index 82cfb710..934d4602 100644 --- a/Core/Shared/Emulator.h +++ b/Core/Shared/Emulator.h @@ -226,6 +226,7 @@ public: void StopDebugger(); DebuggerRequest GetDebugger(bool autoInit = false); bool IsDebugging(); + Debugger* InternalGetDebugger() { return _debugger.get(); } thread::id GetEmulationThreadId(); bool IsEmulationThread(); diff --git a/InteropDLL/DebugApiWrapper.cpp b/InteropDLL/DebugApiWrapper.cpp index 07d33aec..c699f36b 100644 --- a/InteropDLL/DebugApiWrapper.cpp +++ b/InteropDLL/DebugApiWrapper.cpp @@ -150,6 +150,7 @@ extern "C" DllExport void __stdcall GetTileView(CpuType cpuType, GetTileViewOptions options, uint8_t* source, uint32_t srcSize, uint32_t* colors, uint32_t* buffer) { WithToolVoid(GetPpuTools(cpuType), GetTileView(options, source, srcSize, colors, buffer)); } + DllExport void __stdcall GetPpuToolsState(CpuType cpuType, BaseState& state) { return WithToolVoid(GetPpuTools(cpuType), GetPpuToolsState(state)); } DllExport DebugTilemapInfo __stdcall GetTilemap(CpuType cpuType, GetTilemapOptions options, BaseState& state, uint8_t* vram, uint32_t* palette, uint32_t* outputBuffer) { return WithTool(DebugTilemapInfo, GetPpuTools(cpuType), GetTilemap(options, state, vram, palette, outputBuffer)); } DllExport FrameInfo __stdcall GetTilemapSize(CpuType cpuType, GetTilemapOptions options, BaseState& state) { return WithTool(FrameInfo, GetPpuTools(cpuType), GetTilemapSize(options, state)); } DllExport DebugTilemapTileInfo __stdcall GetTilemapTileInfo(uint32_t x, uint32_t y, CpuType cpuType, GetTilemapOptions options, uint8_t* vram, BaseState& state) { return WithTool(DebugTilemapTileInfo, GetPpuTools(cpuType), GetTilemapTileInfo(x, y, vram, options, state)); } diff --git a/NewUI/Debugger/Controls/PictureViewer.cs b/NewUI/Debugger/Controls/PictureViewer.cs index 4751d2e6..3bc436f6 100644 --- a/NewUI/Debugger/Controls/PictureViewer.cs +++ b/NewUI/Debugger/Controls/PictureViewer.cs @@ -151,7 +151,8 @@ namespace Mesen.Debugger.Controls AffectsRender( SourceProperty, ZoomProperty, GridSizeXProperty, GridSizeYProperty, ShowGridProperty, SelectionRectProperty, OverlayRectProperty, - HighlightRectsProperty, MouseOverRectProperty, GridHighlightProperty + HighlightRectsProperty, MouseOverRectProperty, GridHighlightProperty, + OverlayLinesProperty ); SourceProperty.Changed.AddClassHandler((x, e) => { @@ -408,7 +409,7 @@ namespace Mesen.Debugger.Controls if(OverlayLines?.Count > 0) { foreach(PictureViewerLine line in OverlayLines) { - Pen pen = new Pen(line.Color.ToUint32(), 2, line.DashStyle); + Pen pen = new Pen(line.Color.ToUint32(), line.Width ?? 2, line.DashStyle); context.DrawLine(pen, line.Start * Zoom, line.End * Zoom); } } @@ -491,6 +492,7 @@ namespace Mesen.Debugger.Controls public Point Start; public Point End; public Color Color; + public int? Width; public IDashStyle? DashStyle; } } diff --git a/NewUI/Debugger/ViewModels/TilemapViewerViewModel.cs b/NewUI/Debugger/ViewModels/TilemapViewerViewModel.cs index f883f55e..2519f0b2 100644 --- a/NewUI/Debugger/ViewModels/TilemapViewerViewModel.cs +++ b/NewUI/Debugger/ViewModels/TilemapViewerViewModel.cs @@ -45,6 +45,7 @@ namespace Mesen.Debugger.ViewModels [Reactive] public TilemapViewerTab SelectedTab { get; set; } [Reactive] public Rect ScrollOverlayRect { get; private set; } = Rect.Empty; + [Reactive] public List? OverlayLines { get; private set; } = null; public List FileMenuActions { get; } = new(); public List ViewMenuActions { get; } = new(); @@ -53,6 +54,7 @@ namespace Mesen.Debugger.ViewModels private PictureViewer _picViewer; private UInt64 _masterClock; private BaseState? _ppuState; + private BaseState? _ppuToolsState; private byte[] _prevVram = Array.Empty(); private byte[] _vram = Array.Empty(); private UInt32[] _rgbPalette = Array.Empty(); @@ -374,6 +376,7 @@ namespace Mesen.Debugger.ViewModels BaseState ppuState = DebugApi.GetPpuState(CpuType); _ppuState = ppuState; + _ppuToolsState = DebugApi.GetPpuToolsState(CpuType); _prevVram = _vram; _vram = DebugApi.GetMemoryState(GetVramMemoryType()); _accessCounters = DebugApi.GetMemoryAccessCounts(GetVramMemoryType()); @@ -439,8 +442,11 @@ namespace Mesen.Debugger.ViewModels _tilemapInfo.ScrollWidth, _tilemapInfo.ScrollHeight ); + + DrawMode7Overlay(); } else { ScrollOverlayRect = Rect.Empty; + OverlayLines = null; } }); } @@ -563,6 +569,46 @@ namespace Mesen.Debugger.ViewModels } } + private void DrawMode7Overlay() + { + if(_ppuToolsState is SnesPpuToolsState toolsState && _ppuState is SnesPpuState ppuState) { + List lines = new(); + + HslColor baseColor = ColorHelper.RgbToHsl(Color.FromRgb(255, 0, 255)); + for(int i = 0; i < 239; i++) { + if(toolsState.ScanlineBgMode[i] == 7) { + Color lineColor = ColorHelper.HslToRgb(baseColor); + Color alphaColor = Color.FromArgb(0xA0, lineColor.R, lineColor.G, lineColor.B); + + int startX = toolsState.Mode7StartX[i] >> 8; + int startY = toolsState.Mode7StartY[i] >> 8; + int endX = toolsState.Mode7EndX[i] >> 8; + int endY = toolsState.Mode7EndY[i] >> 8; + + lines.Add(new PictureViewerLine() { Start = new Point(startX, startY), End = new Point(endX, endY), Width = 1, Color = alphaColor }); + if(!ppuState.Mode7.LargeMap) { + void Translate(ref int start, ref int end, int offset, Func predicate) { + while(predicate(start) || predicate(end)) { + start += offset; + end += offset; + lines.Add(new PictureViewerLine() { Start = new Point(startX, startY), End = new Point(endX, endY), Width = 1, Color = alphaColor }); + } + } + + Translate(ref startX, ref endX, 1024, x => x < 0); + Translate(ref startY, ref endY, 1024, x => x < 0); + Translate(ref startX, ref endX, -1024, x => x >= 1024); + Translate(ref startY, ref endY, -1024, x => x >= 1024); + } + } + baseColor.H--; + } + OverlayLines = lines; + } else { + OverlayLines = null; + } + } + public void OnGameLoaded() { _inGameLoaded = true; diff --git a/NewUI/Debugger/Windows/TilemapViewerWindow.axaml b/NewUI/Debugger/Windows/TilemapViewerWindow.axaml index 296df8a6..e85026cd 100644 --- a/NewUI/Debugger/Windows/TilemapViewerWindow.axaml +++ b/NewUI/Debugger/Windows/TilemapViewerWindow.axaml @@ -108,6 +108,7 @@ AltGridSizeX="16" AltGridSizeY="16" ShowAltGrid="{CompiledBinding Config.ShowAltGrid}" + OverlayLines="{CompiledBinding OverlayLines}" /> diff --git a/NewUI/Interop/DebugApi.cs b/NewUI/Interop/DebugApi.cs index 68e83005..0653479f 100644 --- a/NewUI/Interop/DebugApi.cs +++ b/NewUI/Interop/DebugApi.cs @@ -136,6 +136,25 @@ namespace Mesen.Interop }; } + [DllImport(DllPath)] private static extern void GetPpuToolsState(CpuType cpuType, IntPtr state); + public unsafe static T GetPpuToolsState(CpuType cpuType) where T : struct, BaseState + { + byte* ptr = stackalloc byte[Marshal.SizeOf(typeof(T))]; + DebugApi.GetPpuToolsState(cpuType, (IntPtr)ptr); + return Marshal.PtrToStructure((IntPtr)ptr); + } + + public static BaseState GetPpuToolsState(CpuType cpuType) + { + return cpuType switch { + CpuType.Snes => GetPpuToolsState(cpuType), + CpuType.Nes => GetPpuToolsState(cpuType), + CpuType.Gameboy => GetPpuToolsState(cpuType), + CpuType.Pce => GetPpuToolsState(cpuType), + _ => throw new Exception("Unsupport cpu type") + }; + } + [DllImport(DllPath)] private static extern void SetPpuState(IntPtr state, CpuType cpuType); public unsafe static void SetPpuState(BaseState state, CpuType cpuType) { diff --git a/NewUI/Interop/DebugState.cs b/NewUI/Interop/DebugState.cs index 1ff4c42c..ed9ab33e 100644 --- a/NewUI/Interop/DebugState.cs +++ b/NewUI/Interop/DebugState.cs @@ -958,6 +958,28 @@ namespace Mesen.Interop public AluState Alu; } + public struct EmptyPpuToolsState : BaseState + { + } + + public struct SnesPpuToolsState : BaseState + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 239)] + public byte[] ScanlineBgMode; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 239)] + public Int32[] Mode7StartX; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 239)] + public Int32[] Mode7StartY; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 239)] + public Int32[] Mode7EndX; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 239)] + public Int32[] Mode7EndY; + } + public struct NesPpuStatusFlags { [MarshalAs(UnmanagedType.I1)] public bool SpriteOverflow; diff --git a/Utilities/safe_ptr.h b/Utilities/safe_ptr.h index 898443ef..26ee5b46 100644 --- a/Utilities/safe_ptr.h +++ b/Utilities/safe_ptr.h @@ -47,6 +47,11 @@ public: _shared = ptr; } + T* get() + { + return _ptr; + } + operator bool() const { return _ptr != nullptr;