diff --git a/Core/PpuTools.cpp b/Core/PpuTools.cpp index 522bb5b..cbb9259 100644 --- a/Core/PpuTools.cpp +++ b/Core/PpuTools.cpp @@ -412,3 +412,67 @@ void PpuTools::GetGameboyTilemap(uint8_t* vram, GbPpuState& state, uint16_t offs } } } + +void PpuTools::GetGameboySpritePreview(GetSpritePreviewOptions options, GbPpuState state, uint8_t* vram, uint8_t* oamRam, uint32_t* outBuffer) +{ + std::fill(outBuffer, outBuffer + 256 * 256, 0xFF333311); + for(int i = 16; i < 16 + 144; i++) { + std::fill(outBuffer + i * 256 + 8, outBuffer + i * 256 + 168, 0xFF888866); + } + + bool isCgb = state.CgbEnabled; + bool largeSprites = state.LargeSprites; + uint8_t height = largeSprites ? 16 : 8; + constexpr uint8_t width = 8; + + uint8_t filled[256]; + for(int row = 0; row < 256; row++) { + std::fill(filled, filled + 256, 0xFF); + + for(int i = 0; i < 0xA0; i += 4) { + uint8_t sprY = oamRam[i]; + if(sprY > row || sprY + height <= row) { + continue; + } + + int y = row - sprY; + uint8_t sprX = oamRam[i + 1]; + uint8_t tileIndex = oamRam[i + 2]; + uint8_t attributes = oamRam[i + 3]; + + uint16_t tileBank = isCgb ? ((attributes & 0x08) ? 0x2000 : 0x0000) : 0; + uint8_t palette = isCgb ? (attributes & 0x07) << 2 : 0; + bool hMirror = (attributes & 0x20) != 0; + bool vMirror = (attributes & 0x40) != 0; + + if(largeSprites) { + tileIndex &= 0xFE; + } + + uint16_t tileStart = tileIndex * 16; + tileStart |= tileBank; + + uint16_t pixelStart = tileStart + (vMirror ? (height - 1 - y) : y) * 2; + for(int x = 0; x < width; x++) { + if(sprX + x >= 256) { + break; + } else if(filled[sprX + x] < sprX) { + continue; + } + + uint8_t shift = hMirror ? (x & 0x07) : (7 - (x & 0x07)); + uint8_t color = GetTilePixelColor(vram, 0x3FFF, 2, pixelStart, shift); + + if(color > 0) { + if(!isCgb) { + color = (((attributes & 0x10) ? state.ObjPalette1 : state.ObjPalette0) >> (color * 2)) & 0x03; + } + + uint32_t outOffset = (row * 256) + sprX + x; + outBuffer[outOffset] = DefaultVideoFilter::ToArgb(state.CgbObjPalettes[palette + color]); + filled[sprX + x] = sprX; + } + } + } + } +} \ No newline at end of file diff --git a/Core/PpuTools.h b/Core/PpuTools.h index 4034d22..c97fc2b 100644 --- a/Core/PpuTools.h +++ b/Core/PpuTools.h @@ -31,4 +31,5 @@ public: void UpdateViewers(uint16_t scanline, uint16_t cycle); void GetGameboyTilemap(uint8_t* vram, GbPpuState& state, uint16_t offset, uint32_t* outBuffer); + void GetGameboySpritePreview(GetSpritePreviewOptions options, GbPpuState state, uint8_t* vram, uint8_t* oamRam, uint32_t* outBuffer); }; \ No newline at end of file diff --git a/InteropDLL/DebugApiWrapper.cpp b/InteropDLL/DebugApiWrapper.cpp index 5d7167a..d5fd10d 100644 --- a/InteropDLL/DebugApiWrapper.cpp +++ b/InteropDLL/DebugApiWrapper.cpp @@ -93,6 +93,7 @@ extern "C" DllExport void __stdcall SetViewerUpdateTiming(uint32_t viewerId, uint16_t scanline, uint16_t cycle) { GetDebugger()->GetPpuTools()->SetViewerUpdateTiming(viewerId, scanline, cycle); } DllExport void __stdcall GetGameboyTilemap(uint8_t* vram, GbPpuState state, uint16_t offset, uint32_t* buffer) { GetDebugger()->GetPpuTools()->GetGameboyTilemap(vram, state, offset, buffer); } + DllExport void __stdcall GetGameboySpritePreview(GetSpritePreviewOptions options, GbPpuState state, uint8_t* vram, uint8_t* oamRam, uint32_t* buffer) { GetDebugger()->GetPpuTools()->GetGameboySpritePreview(options, state, vram, oamRam, buffer); } DllExport void __stdcall GetDebugEvents(DebugEventInfo *infoArray, uint32_t &maxEventCount) { GetDebugger()->GetEventManager()->GetEvents(infoArray, maxEventCount); } DllExport uint32_t __stdcall GetDebugEventCount(EventViewerDisplayOptions options) { return GetDebugger()->GetEventManager()->GetEventCount(options); } diff --git a/UI/Debugger/PpuViewer/SpriteInfo.cs b/UI/Debugger/PpuViewer/SpriteInfo.cs index d4db70b..b6e90ba 100644 --- a/UI/Debugger/PpuViewer/SpriteInfo.cs +++ b/UI/Debugger/PpuViewer/SpriteInfo.cs @@ -25,6 +25,8 @@ namespace Mesen.GUI.Debugger.PpuViewer public bool VerticalMirror; public bool UseSecondTable; + private bool GameboyMode; + public Rectangle GetBounds() { return new Rectangle(X, Y, Width, Height); @@ -32,6 +34,10 @@ namespace Mesen.GUI.Debugger.PpuViewer public bool IsVisible() { + if(GameboyMode) { + return !(X == 0 || X >= 168 || Y+Height <= 16 || Y >= 160); + } + if(X + Width <= 0 || X > 255) { return false; } @@ -44,6 +50,33 @@ namespace Mesen.GUI.Debugger.PpuViewer return true; } + public static SpriteInfo GetGbSpriteInfo(byte[] oamRam, int spriteIndex, bool largeSprite, bool cgbEnabled) + { + SpriteInfo sprite = new SpriteInfo(); + int addr = spriteIndex << 2; + sprite.Index = spriteIndex; + sprite.Y = oamRam[addr]; + + sprite.LargeSprite = largeSprite; + sprite.Height = largeSprite ? 16 : 8; + + sprite.Width = 8; + sprite.X = oamRam[addr + 1]; + sprite.TileIndex = oamRam[addr + 2]; + + byte attributes = oamRam[addr + 3]; + sprite.Flags = attributes; + sprite.UseSecondTable = cgbEnabled ? ((attributes & 0x08) != 0) : false; + sprite.Palette = cgbEnabled ? (attributes & 0x07) : ((attributes & 0x10) >> 4); + sprite.Priority = (attributes & 0x80) != 0 ? 1 : 0; + sprite.HorizontalMirror = (attributes & 0x20) != 0; + sprite.VerticalMirror = (attributes & 0x40) != 0; + + sprite.GameboyMode = true; + + return sprite; + } + public static SpriteInfo GetSpriteInfo(byte[] oamRam, int oamMode, int spriteIndex) { SpriteInfo sprite = new SpriteInfo(); diff --git a/UI/Debugger/PpuViewer/ctrlSpriteList.cs b/UI/Debugger/PpuViewer/ctrlSpriteList.cs index ae2c370..8994582 100644 --- a/UI/Debugger/PpuViewer/ctrlSpriteList.cs +++ b/UI/Debugger/PpuViewer/ctrlSpriteList.cs @@ -16,8 +16,10 @@ namespace Mesen.GUI.Debugger.PpuViewer public event SpriteSelectedHandler SpriteSelected; private List _sprites; + private DebugState _state; private byte[] _oamRam; private int _oamMode; + private bool _isGameboyMode; private int _selectedIndex; private int _sortOrder = 1; private DateTime _lastRefresh = DateTime.MinValue; @@ -63,14 +65,16 @@ namespace Mesen.GUI.Debugger.PpuViewer } } - public void SetData(byte[] oamRam, int oamMode) + public void SetData(DebugState state, byte[] oamRam, int oamMode, bool gameboyMode) { if(_oamRam == oamRam) { return; } + _state = state; _oamRam = oamRam; _oamMode = oamMode; + _isGameboyMode = gameboyMode; if((DateTime.Now - _lastRefresh).TotalMilliseconds > 200) { RefreshList(); } @@ -79,8 +83,14 @@ namespace Mesen.GUI.Debugger.PpuViewer private void RefreshList() { List sprites = new List(); - for(int i = 0; i < 128; i++) { - SpriteInfo sprite = SpriteInfo.GetSpriteInfo(_oamRam, _oamMode, i); + for(int i = 0; i < (_isGameboyMode ? 40 : 128); i++) { + SpriteInfo sprite; + if(_isGameboyMode) { + sprite = SpriteInfo.GetGbSpriteInfo(_oamRam, i, _state.Gameboy.Ppu.LargeSprites, _state.Gameboy.Ppu.CgbEnabled); + } else { + sprite = SpriteInfo.GetSpriteInfo(_oamRam, _state.Ppu.OamMode, i); + } + if(!chkHideOffscreenSprites.Checked || sprite.IsVisible()) { sprites.Add(sprite); } diff --git a/UI/Debugger/PpuViewer/frmSpriteViewer.cs b/UI/Debugger/PpuViewer/frmSpriteViewer.cs index 22ccf46..2dae98d 100644 --- a/UI/Debugger/PpuViewer/frmSpriteViewer.cs +++ b/UI/Debugger/PpuViewer/frmSpriteViewer.cs @@ -18,7 +18,7 @@ namespace Mesen.GUI.Debugger { public partial class frmSpriteViewer : BaseForm, IRefresh { - private PpuState _state; + private DebugState _state; private byte[] _vram; private byte[] _cgram; private byte[] _oamRam; @@ -26,6 +26,7 @@ namespace Mesen.GUI.Debugger private Bitmap _previewImage; private GetSpritePreviewOptions _options = new GetSpritePreviewOptions(); private WindowRefreshManager _refreshManager; + private bool _isGameboyMode = false; public ctrlScanlineCycleSelect ScanlineCycleSelect { get { return this.ctrlScanlineCycleSelect; } } @@ -41,11 +42,6 @@ namespace Mesen.GUI.Debugger return; } - _previewData = new byte[256 * 240 * 4]; - _previewImage = new Bitmap(256, 240, PixelFormat.Format32bppPArgb); - ctrlImagePanel.ImageSize = new Size(256, 240); - ctrlImagePanel.Image = _previewImage; - InitShortcuts(); SpriteViewerConfig config = ConfigManager.Config.Debug.SpriteViewer; @@ -102,27 +98,40 @@ namespace Mesen.GUI.Debugger public void RefreshData() { - _state = DebugApi.GetState().Ppu; - _vram = DebugApi.GetMemoryState(SnesMemoryType.VideoRam); - _oamRam = DebugApi.GetMemoryState(SnesMemoryType.SpriteRam); + _isGameboyMode = EmuApi.GetRomInfo().CoprocessorType == CoprocessorType.Gameboy; + _state = DebugApi.GetState(); + _vram = DebugApi.GetMemoryState(_isGameboyMode ? SnesMemoryType.GbVideoRam : SnesMemoryType.VideoRam); + _oamRam = DebugApi.GetMemoryState(_isGameboyMode ? SnesMemoryType.GbSpriteRam : SnesMemoryType.SpriteRam); _cgram = DebugApi.GetMemoryState(SnesMemoryType.CGRam); } public void RefreshViewer() { - ctrlSpriteList.SetData(_oamRam, _state.OamMode); + int height = _isGameboyMode ? 256 : 240; + if(_previewImage == null || _previewImage.Height != height) { + _previewData = new byte[256 * height * 4]; + _previewImage = new Bitmap(256, height, PixelFormat.Format32bppPArgb); + ctrlImagePanel.ImageSize = new Size(256, height); + ctrlImagePanel.Image = _previewImage; + } - DebugApi.GetSpritePreview(_options, _state, _vram, _oamRam, _cgram, _previewData); + ctrlSpriteList.SetData(_state, _oamRam, _state.Ppu.OamMode, _isGameboyMode); + + if(_isGameboyMode) { + DebugApi.GetGameboySpritePreview(_options, _state.Gameboy.Ppu, _vram, _oamRam, _previewData); + } else { + DebugApi.GetSpritePreview(_options, _state.Ppu, _vram, _oamRam, _cgram, _previewData); + } using(Graphics g = Graphics.FromImage(_previewImage)) { GCHandle handle = GCHandle.Alloc(_previewData, GCHandleType.Pinned); - Bitmap source = new Bitmap(256, 240, 4 * 256, PixelFormat.Format32bppPArgb, handle.AddrOfPinnedObject()); + Bitmap source = new Bitmap(256, height, 4 * 256, PixelFormat.Format32bppPArgb, handle.AddrOfPinnedObject()); g.DrawImage(source, 0, 0); handle.Free(); } if(_options.SelectedSprite >= 0) { - ctrlImagePanel.Selection = SpriteInfo.GetSpriteInfo(_oamRam, _state.OamMode, _options.SelectedSprite).GetBounds(); + ctrlImagePanel.Selection = GetSpriteInfo(_options.SelectedSprite).GetBounds(); } else { ctrlImagePanel.Selection = Rectangle.Empty; } @@ -157,8 +166,8 @@ namespace Mesen.GUI.Debugger int y = e.Y / ctrlImagePanel.ImageScale; SpriteInfo match = null; - for(int i = 0; i < 128; i++) { - SpriteInfo sprite = SpriteInfo.GetSpriteInfo(_oamRam, _state.OamMode, i); + for(int i = 0; i < (_isGameboyMode ? 40 : 128); i++) { + SpriteInfo sprite = GetSpriteInfo(i); if(x >= sprite.X && x <= sprite.X + sprite.Width) { int endY = (sprite.Y + sprite.Height) & 0xFF; bool visible = (y >= sprite.Y && y < endY) || (endY < sprite.Y && y < endY); @@ -172,6 +181,18 @@ namespace Mesen.GUI.Debugger SelectSprite(match); } + private SpriteInfo GetSpriteInfo(int index) + { + SpriteInfo sprite; + if(_isGameboyMode) { + sprite = SpriteInfo.GetGbSpriteInfo(_oamRam, index, _state.Gameboy.Ppu.LargeSprites, _state.Gameboy.Ppu.CgbEnabled); + } else { + sprite = SpriteInfo.GetSpriteInfo(_oamRam, _state.Ppu.OamMode, index); + } + + return sprite; + } + private void SelectSprite(SpriteInfo sprite) { if(sprite != null) { diff --git a/UI/Forms/frmMain.cs b/UI/Forms/frmMain.cs index c294673..2d1227b 100644 --- a/UI/Forms/frmMain.cs +++ b/UI/Forms/frmMain.cs @@ -481,8 +481,6 @@ namespace Mesen.GUI.Forms mnuDebugger.Visible = !isGameboyMode; mnuSpcDebugger.Enabled = !isGameboyMode; mnuSpcDebugger.Visible = !isGameboyMode; - mnuSpriteViewer.Enabled = !isGameboyMode; - mnuSpriteViewer.Visible = !isGameboyMode; mnuAssembler.Enabled = !isGameboyMode; mnuAssembler.Visible = !isGameboyMode; sepCoprocessors.Visible = !isGameboyMode; diff --git a/UI/Interop/DebugApi.cs b/UI/Interop/DebugApi.cs index 7995326..ec41168 100644 --- a/UI/Interop/DebugApi.cs +++ b/UI/Interop/DebugApi.cs @@ -88,10 +88,12 @@ namespace Mesen.GUI } [DllImport(DllPath)] public static extern void GetTilemap(GetTilemapOptions options, PpuState state, byte[] vram, byte[] cgram, [In, Out] byte[] buffer); - [DllImport(DllPath)] public static extern void GetGameboyTilemap(byte[] vram, GbPpuState state, UInt16 offset, [In, Out] byte[] buffer); [DllImport(DllPath)] public static extern void GetTileView(GetTileViewOptions options, byte[] source, int srcSize, byte[] cgram, [In, Out] byte[] buffer); [DllImport(DllPath)] public static extern void GetSpritePreview(GetSpritePreviewOptions options, PpuState state, byte[] vram, byte[] oamRam, byte[] cgram, [In, Out] byte[] buffer); + [DllImport(DllPath)] public static extern void GetGameboyTilemap(byte[] vram, GbPpuState state, UInt16 offset, [In, Out] byte[] buffer); + [DllImport(DllPath)] public static extern void GetGameboySpritePreview(GetSpritePreviewOptions options, GbPpuState state, byte[] vram, byte[] oamRam, [In, Out] byte[] buffer); + [DllImport(DllPath)] public static extern void SetViewerUpdateTiming(Int32 viewerId, Int32 scanline, Int32 cycle); [DllImport(DllPath)] private static extern UInt32 GetDebugEventCount(EventViewerDisplayOptions options);